Clean up obsolete files and refresh MkDocs site

Remove unused planning docs (FEDERATION_PLAN.md, SERVICE_INTEGRATIONS.md),
temporary screenshots, update .gitignore for Playwright MCP logs, and
refresh MkDocs site build with updated repo data.

Bunker Admin
This commit is contained in:
bunker-admin 2026-04-03 08:52:15 -06:00
parent 72622671a2
commit 74e5fa6475
122 changed files with 357 additions and 702 deletions

5
.gitignore vendored
View File

@ -70,3 +70,8 @@ core.*
/changemaker-control-panel/instances/
/changemaker-control-panel/backups/
logs/
# Playwright MCP browser automation logs
.playwright-mcp/
/docs

View File

@ -1,257 +0,0 @@
# Phase 16: Federation — Instance-to-Instance Campaign Network
## Context
Changemaker Lite instances are currently isolated islands. This feature introduces a **federated discovery network** where any instance can act as a **hub** (accepting registrations, serving a directory) and/or a **spoke** (registering with hubs, sharing campaigns). The goal is organic, admin-to-admin networking with public campaign discoverability as a secondary benefit.
**Design principles:**
- Any instance can be a hub, spoke, or both — no central authority
- Medium-depth campaign sharing: enough metadata for discovery, click-through to source
- Per-campaign federation toggle — admins choose what's shared
- Strict privacy boundary: **never** share emails, participant data, queue data, addresses, volunteer/canvass data, or credentials
- Hub admins curate their own directories — organic > control
---
## Prisma Schema Changes
**File:** `api/prisma/schema.prisma`
### New enums
```
FederationPeerStatus: PENDING | ACTIVE | REJECTED | SUSPENDED | OFFLINE
FederationRole: HUB | SPOKE
```
### New models
**FederationIdentity** (singleton — this instance's federation profile)
- `enabled`, `hubEnabled`, `hubAutoApprove`
- Instance profile: `instanceName`, `instanceDescription`, `instanceUrl`, `instanceRegion`, `instanceTags` (Json), `instanceLogoUrl`
- Ed25519 keypair: `publicKey`, `privateKey` (encrypted at rest)
- Hub description, sync interval, last sync timestamp/error
**FederationPeer** (one record per connection, in either direction)
- `role` (HUB or SPOKE), `remoteUrl` (unique per role+url)
- Remote instance profile fields (name, description, region, tags, logo, publicKey)
- Auth: `apiKey` (ours for them), `remoteApiKey` (theirs for us) — both encrypted
- Status tracking: `status`, `statusMessage`, `lastSeenAt`, `lastSyncAt`, `failureCount`
- Stats: `campaignsShared`, `responsesShared`
- Relation to `FederatedCampaign[]`
**FederatedCampaign** (cached campaign metadata from peers)
- `peerId` → FederationPeer
- Remote identifiers: `remoteCampaignId`, `remoteCampaignSlug`
- Safe metadata: title, description, emailSubject (NOT body), callToAction, coverPhoto, status, targetGovernmentLevels, featureFlags (Json), createdByName
- Aggregate stats: `emailCount`, `responseCount`
- Source instance info (denormalized): `sourceInstanceName`, `sourceInstanceUrl`, `sourceInstanceRegion`
- Staleness tracking: `lastSyncedAt`, `isStale`
- Future adoption: `adoptedAsCampaignId` (nullable FK to local Campaign)
- Unique constraint: `[peerId, remoteCampaignId]`
### Modifications to existing models
**Campaign** — add `federated Boolean @default(false)` field
**SiteSettings** — add `enableFederation Boolean @default(false)` feature toggle
---
## API Module Structure
**New directory:** `api/src/modules/federation/`
| File | Purpose |
|------|---------|
| `federation.schemas.ts` | Zod schemas: identity update, peer registration, campaign sync, directory query, list filters |
| `federation.service.ts` | Core business logic: identity CRUD, peer management, `buildSafeCampaignPayload()`, campaign sync, directory serving |
| `federation-admin.routes.ts` | SUPER_ADMIN routes: identity management, peer approve/reject/suspend, manual sync trigger |
| `federation-peer.routes.ts` | Inter-instance routes: inbound registration, campaign sync, directory, heartbeat (API-key auth) |
| `federation-public.routes.ts` | Public browsing: federated campaigns list, instance directory (no auth) |
| `federation-crypto.service.ts` | Ed25519 keypair generation, request signing/verification |
**New file:** `api/src/services/federation-sync-queue.service.ts` — BullMQ repeatable job for periodic sync
### Route table
**Admin routes** (`/api/federation/...`, SUPER_ADMIN + JWT auth):
| Method | Path | Description |
|--------|------|-------------|
| GET | `/identity` | Get federation config |
| PUT | `/identity` | Update config/profile |
| POST | `/identity/generate-keypair` | Generate Ed25519 keypair |
| GET | `/peers` | List all peers |
| POST | `/peers/register` | Register with a remote hub |
| POST | `/peers/:id/approve` | Approve incoming spoke |
| POST | `/peers/:id/reject` | Reject incoming spoke |
| POST | `/peers/:id/suspend` | Suspend peer |
| DELETE | `/peers/:id` | Remove peer |
| POST | `/sync` | Trigger manual sync |
| GET | `/sync/status` | Sync status + history |
**Peer routes** (`/api/federation/peer/...`, API-key auth via `X-Federation-Key` header):
| Method | Path | Description |
|--------|------|-------------|
| POST | `/register` | Inbound spoke registration |
| POST | `/sync` | Inbound campaign metadata push |
| GET | `/directory` | Serve campaign directory |
| GET | `/profile` | Return instance profile |
| POST | `/heartbeat` | Liveness check |
**Public routes** (`/api/federation/...`, no auth):
| Method | Path | Description |
|--------|------|-------------|
| GET | `/campaigns` | Browse federated campaigns (paginated, searchable) |
| GET | `/campaigns/:id` | Single federated campaign detail |
| GET | `/instances` | List known network instances |
### Mounting in server.ts
```
app.use('/api/federation', federationPublicRouter); // No auth — first
app.use('/api/federation', federationPeerRouter); // API-key auth
app.use('/api/federation', federationAdminRouter); // SUPER_ADMIN JWT
```
---
## Federation Protocol
### Registration handshake
1. Spoke admin enters hub URL, clicks "Register"
2. Spoke sends `POST /api/federation/peer/register` to hub with instance profile + generated API key
3. Hub creates peer record (PENDING or ACTIVE if `hubAutoApprove`)
4. Hub responds with its own API key + peer ID
5. If approved (now or later), hub calls back to spoke's `/peer/register` to complete mutual registration
6. Both instances now have each other as peers (Spoke→HUB role, Hub→SPOKE role)
### Campaign sync
- Spokes push federated campaigns to hubs on schedule (BullMQ repeatable job)
- Payload: array of safe campaign metadata + array of un-federated campaign IDs (for removal)
- Hub stores/updates `FederatedCampaign` records
- Sync includes heartbeat (updates `lastSeenAt`)
### Privacy boundary enforcement
`buildSafeCampaignPayload()` in the service layer filters campaigns to only safe fields. **Never included:** emailBody, any email addresses, user IDs, participant data, moderation internals, custom recipients, calls data.
### Offline handling
- Increment `failureCount` on sync failure; after 5 consecutive failures → status `OFFLINE`
- Mark federated campaigns as `isStale` after 24h offline
- Keep checking with exponential backoff (max 24h)
- Auto-recover when heartbeat succeeds
---
## Security
- **API-key auth:** `crypto.randomBytes(32).toString('hex')`, encrypted at rest with existing `encrypt()`/`decrypt()` utility
- **Custom middleware:** `authenticatePeer` checks `X-Federation-Key` header, verifies peer exists + is ACTIVE
- **Request signing (optional):** Ed25519 signatures on `X-Federation-Signature` header for non-repudiation (configurable, not enforced in MVP)
- **Rate limiting:** 30 req/min for peer routes, 60 req/min for public routes (separate Redis prefixes)
- **CORS:** Peer routes need permissive CORS (cross-domain by nature)
- **Input validation:** All incoming peer data Zod-validated + HTML-escaped before storage
---
## Environment Variables
Add to `api/src/config/env.ts`:
```
ENABLE_FEDERATION: z.string().default('false')
FEDERATION_SYNC_INTERVAL_MINUTES: z.coerce.number().default(60)
FEDERATION_MAX_CAMPAIGNS_PER_SYNC: z.coerce.number().default(500)
FEDERATION_PEER_TIMEOUT_MS: z.coerce.number().default(15000)
FEDERATION_MAX_PEERS: z.coerce.number().default(50)
```
---
## Admin UI
### FederationPage (`admin/src/pages/FederationPage.tsx`)
4-tab page following PangolinPage pattern:
**Tab 1 — Identity & Settings:** Toggle federation, instance profile form, keypair management, hub/spoke settings
**Tab 2 — Connected Peers:** Table of peers (name, URL, role tag, status tag, campaigns shared, last sync, actions). "Register with Hub" button opens modal. Pending incoming registrations highlighted.
**Tab 3 — Federated Campaigns:** Card grid/table of federated campaigns with search + filter (region, tags, government level). Click-through links to source instances.
**Tab 4 — Sync Status:** Last/next sync, per-peer status, manual sync button, sync history.
### Sidebar
Add to `buildMenuItems()` in `AppLayout.tsx`, gated on `settings?.enableFederation`:
```typescript
{ key: '/app/federation', icon: <GlobalOutlined />, label: 'Federation' }
```
(Using `<GlobalOutlined />` since `<GlobalOutlined />` is already imported but used for Web submenu — may use `<ClusterOutlined />` or `<DeploymentUnitOutlined />` instead)
### Route in App.tsx
```tsx
<Route path="federation" element={<ProtectedRoute requiredRoles={['SUPER_ADMIN']}><FederationPage /></ProtectedRoute>} />
```
### Campaign form integration
Add `federated` checkbox to campaign create/edit form in CampaignsPage, visible only when `settings.enableFederation` is true.
### TypeScript types
Add `FederationIdentity`, `FederationPeer`, `FederatedCampaign`, `FederationSyncStatus` interfaces to `admin/src/types/api.ts`.
### Public network page (stretch goal in MVP)
`admin/src/pages/public/FederatedCampaignsPage.tsx` at `/network` route — card grid of federated campaigns with PublicLayout dark theme.
---
## Prometheus Metrics
Add to `api/src/utils/metrics.ts`:
- `cm_federation_peers_active` (Gauge)
- `cm_federation_campaigns_shared` (Gauge)
- `cm_federation_sync_duration_seconds` (Histogram)
- `cm_federation_sync_errors_total` (Counter with `peer_id` label)
---
## Implementation Order
| Step | Description | Files Created/Modified | Depends On |
|------|-------------|----------------------|------------|
| 1 | **Prisma schema** — Add enums, 3 new models, Campaign.federated, SiteSettings.enableFederation | `api/prisma/schema.prisma` | — |
| 2 | **Migration**`npx prisma migrate dev --name add-federation` | `api/prisma/migrations/` | Step 1 |
| 3 | **Env vars** — Add federation config to env.ts + .env.example | `api/src/config/env.ts`, `.env.example` | — |
| 4 | **Crypto service** — Ed25519 keypair, sign/verify | `api/src/modules/federation/federation-crypto.service.ts` | — |
| 5 | **Schemas** — Zod validation for all federation endpoints | `api/src/modules/federation/federation.schemas.ts` | Step 1 |
| 6 | **Core service** — Identity CRUD, peer management, buildSafeCampaignPayload, campaign sync logic | `api/src/modules/federation/federation.service.ts` | Steps 2, 4, 5 |
| 7 | **Admin routes** — SUPER_ADMIN federation management | `api/src/modules/federation/federation-admin.routes.ts` | Step 6 |
| 8 | **Peer routes** — Inter-instance API with authenticatePeer middleware | `api/src/modules/federation/federation-peer.routes.ts` | Step 6 |
| 9 | **Public routes** — Browsing federated campaigns | `api/src/modules/federation/federation-public.routes.ts` | Step 6 |
| 10 | **Rate limiting** — Add federation rate limiters | `api/src/middleware/rate-limit.ts` | — |
| 11 | **Server mounting** — Import + mount routers, start sync queue | `api/src/server.ts` | Steps 7-10 |
| 12 | **Sync queue** — BullMQ repeatable job for periodic sync | `api/src/services/federation-sync-queue.service.ts` | Step 6 |
| 13 | **Metrics** — Prometheus counters/gauges | `api/src/utils/metrics.ts` | — |
| 14 | **Campaign form** — Add `federated` to schemas + service + CampaignsPage checkbox | `api/src/modules/influence/campaigns/campaigns.schemas.ts`, `campaigns.service.ts`, `admin/src/pages/CampaignsPage.tsx` | Step 2 |
| 15 | **Frontend types** — Federation TypeScript interfaces | `admin/src/types/api.ts` | — |
| 16 | **FederationPage** — 4-tab admin page | `admin/src/pages/FederationPage.tsx` | Steps 7, 15 |
| 17 | **Sidebar + routing** — Menu item + route in AppLayout/App.tsx | `admin/src/components/AppLayout.tsx`, `admin/src/App.tsx` | Step 16 |
| 18 | **Public network page** (stretch) — Federated campaigns browse | `admin/src/pages/public/FederatedCampaignsPage.tsx` | Steps 9, 15 |
---
## Future Extensions (not in MVP, but models accommodate)
- **Campaign adoption** — "Fork" a federated campaign locally (`FederatedCampaign.adoptedAsCampaignId`)
- **Cross-instance response sharing** — New `FederatedResponse` model synced alongside campaigns
- **Named networks/coalitions**`FederationNetwork` + `FederationNetworkMember` models for named alliances
- **Hub-of-hubs discovery** — Hubs share known-hub lists for transitive discovery (gossip protocol)
---
## Verification
1. **Two-instance test:** Run two API instances on different ports, enable federation on both, register one with the other
2. **Campaign sync:** Create a federated campaign on spoke, verify it appears in hub's directory
3. **Privacy boundary:** Inspect sync payloads — verify no emails, user IDs, or email bodies leak
4. **Offline handling:** Stop one instance, verify the other marks it OFFLINE after 5 failed syncs, then recovers on restart
5. **Rate limiting:** Hit peer endpoints rapidly, verify 429 responses after threshold
6. **Feature gate:** Disable federation in settings, verify all routes return 403/hidden
7. **UI:** Verify sidebar item appears/hides with feature toggle, all 4 tabs functional

View File

@ -1,161 +0,0 @@
# Service Integrations — EventBus Architecture
Tracking document for the platform-wide EventBus and service integration work.
**Started:** 2026-03-30
**Branch:** v2
---
## Architecture Overview
Changemaker Lite has 30+ services but most operate as isolated tools. The EventBus provides a centralized, typed, in-process pub/sub system that decouples event producers from consumers.
```
Service Handler (shift created, donation completed, etc.)
|
v
eventBus.publish('shift.created', payload)
|
+-- ListmonkListener (newsletter sync)
+-- RocketChatListener (team notifications)
+-- CrmActivityListener (contact timeline)
+-- CalendarSyncListener (unified calendar)
+-- N8nWebhookListener (external automation)
+-- GancioSyncListener (public event calendar)
```
### Why In-Process EventEmitter (not Redis PubSub)
- Single Express process — no distributed coordination needed
- Zero serialization overhead (pass JS objects directly)
- Data already persisted in DB — events are ephemeral notifications
- Matches the existing fire-and-forget pattern used by Listmonk/RC services
- Can be swapped to Redis PubSub later if we go multi-process
### Key Files
| File | Purpose |
|------|---------|
| `api/src/types/events.ts` | Typed event catalog (all event names + payloads) |
| `api/src/services/event-bus.service.ts` | Core EventBus (publish/subscribe/stats) |
| `api/src/services/event-listeners/listmonk.listener.ts` | Listmonk newsletter sync |
| `api/src/services/event-listeners/rocketchat.listener.ts` | Rocket.Chat notifications |
| `api/src/services/event-listeners/crm-activity.listener.ts` | CRM ContactActivity writer |
| `api/src/services/event-listeners/calendar-sync.listener.ts` | Calendar unification |
| `api/src/services/event-listeners/n8n-webhook.listener.ts` | n8n automation bridge |
| `api/src/services/event-listeners/gancio.listener.ts` | Gancio event sync (shifts + ticketed events) |
| `api/src/services/event-listeners/engagement-scoring.listener.ts` | Contact engagement scores (Redis ZSET) |
| `api/src/services/event-listeners/homepage-stats.listener.ts` | Homepage real-time counters + cache invalidation |
---
## Progress Tracker
### Phase 1: Core Infrastructure
- [x] Explore existing event patterns (Listmonk, RC, Gancio, provisioning)
- [x] Design EventBus architecture
- [x] Implement EventBus service (`api/src/services/event-bus.service.ts`)
- [x] Define typed event catalog (`api/src/types/events.ts` — 46 events across 14 modules)
- [x] Register EventBus in server.ts startup
- [x] Add EventBus stats endpoint (`GET /api/observability/event-bus`)
### Phase 2: Migrate Existing Integrations
- [x] Listmonk event sync → EventBus listener (9 event subscriptions)
- [x] Rocket.Chat webhook service → EventBus listener (4 event subscriptions)
- [x] Gancio shift/event sync → EventBus listener (3 event subscriptions)
### Phase 3: New Listeners
- [x] CRM Activity auto-generation listener (11 event subscriptions)
- [x] Calendar sync listener (8 event subscriptions)
- [x] n8n webhook emitter listener (wildcard subscription, forwards all events)
- [x] Listmonk webhook receiver (inbound: open, click, bounce, unsubscribe → EventBus)
### Phase 4: Wire Up Publishers (migrated from inline calls)
- [x] Shift CRUD + signup (shift.created/updated/deleted, shift.signup.created/cancelled)
- [x] Canvass session complete + visits (canvass.session.completed, contact.address.updated)
- [x] Response submit (response.submitted)
- [x] Campaign email sent (campaign.email.sent)
- [x] Payment/donation/subscription events (3 event types)
- [x] Contact tag changes (contact.tags.changed — 3 call sites)
- [x] Reengagement sent (reengagement.sent)
- [x] Campaign CRUD + publish + moderation (campaign.created/updated/deleted/published/status.changed)
- [x] User create/update/delete/approve (user.created/updated/deleted/approved)
- [x] SMS campaign start/complete + message send/receive (4 event types)
- [x] Media video publish/unpublish/view (3 event types)
- [x] Ticketed event publish/cancel (EventBus publishes alongside existing Gancio calls)
- [x] Impact story publish (social.impact-story.published)
- [x] Meeting create/delete (jitsi.routes.ts — meeting.created, meeting.deleted)
### Phase 4b: Extended Listeners (2026-03-31)
- [x] RC listener: +7 subscriptions (campaign.published, donations, subscriptions, SMS escalation, user.approved, video.published, ticketed-event.published)
- [x] CRM listener: +2 subscriptions (subscription activated, email bounced)
- [x] RC webhook service: +7 new formatter methods
- [x] Prisma migration: SHIFT, MEETING, TICKETED_EVENT added to CalendarItemSource enum
- [x] Calendar sync listener: uses proper source types (SHIFT, MEETING, TICKETED_EVENT)
### Phase 4c: New Data Listeners (2026-03-31)
- [x] Engagement scoring listener (11 subscriptions, Redis ZSET leaderboard)
- [x] Homepage stats listener (12 subscriptions, Redis counters + recent activity)
- [x] GET /api/homepage/live-stats endpoint (public, real-time counters + recent)
- [x] GET /api/observability/engagement-leaderboard endpoint (admin, top contacts)
### Phase 5: Future
- [ ] Migrate meeting-planner Gancio calls to EventBus (blocked: synchronous return value needed)
- [ ] Homepage service: swap COUNT queries for Redis counters in getStats()
- [ ] Engagement score materialization: periodic job to denormalize scores to Contact model
---
## Event Catalog
### Currently Wired (11 event points, 3 consumers)
| Event | Listmonk | Rocket.Chat | Gancio |
|-------|----------|-------------|--------|
| shift.signup | yes | yes | - |
| shift.signup.cancelled | - | yes | - |
| shift.created | - | - | yes |
| shift.updated | - | - | yes |
| shift.deleted | - | - | yes |
| canvass.session.completed | yes | yes | - |
| canvass.address.updated | yes | - | - |
| campaign.email.sent | yes | - | - |
| response.submitted | - | yes | - |
| subscription.activated | yes | - | - |
| donation.completed | yes | - | - |
| product.purchased | yes | - | - |
| contact.tags.changed | yes | - | - |
| reengagement.sent | yes | - | - |
### New Events (49+ handlers need publishers)
| Event | CRM Activity | Calendar | RC | n8n |
|-------|-------------|----------|-----|-----|
| campaign.created | - | - | - | yes |
| campaign.published | - | - | yes | yes |
| campaign.status.changed | - | - | yes | yes |
| user.approved | - | - | yes | yes |
| user.created | - | - | - | yes |
| video.published | - | - | yes | yes |
| video.viewed | yes | - | - | - |
| sms.message.received | yes | - | yes* | yes |
| sms.campaign.completed | - | - | yes | yes |
| ticketed-event.published | - | yes | - | yes |
| meeting.created | - | yes | - | - |
| impact-story.published | - | - | yes | yes |
| shift.created | - | yes | - | yes |
| donation.completed | yes | - | yes | yes |
| subscription.activated | yes | - | - | yes |
*SMS escalations (QUESTION/NEGATIVE sentiment) to relevant RC channel
---
## Design Decisions
1. **Listeners self-guard**: Each listener checks its own feature flag (ENABLE_CHAT, LISTMONK_SYNC_ENABLED, etc.) — the EventBus doesn't filter
2. **Error isolation**: Each listener wraps its handler in try-catch; one listener failing doesn't affect others
3. **No persistence**: Events are ephemeral — if the server restarts mid-event, it's lost (data is already in DB)
4. **Stats tracking**: EventBus tracks per-event emission counts + per-listener execution counts for observability
5. **Wildcard subscriptions**: Listeners can subscribe to `shift.*` to catch all shift events

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

View File

@ -3,7 +3,7 @@
(function() {
window.MEDIA_API_URL = 'http://localhost:4100';
window.PUBLIC_URL = 'http://localhost:3002';
window.PAYMENT_API_URL = 'http://localhost:4002';
window.PAYMENT_API_URL = 'https://api.cmlite.org';
window.APP_URL = 'http://localhost:3002';
window.GANCIO_URL = 'http://localhost:8092';
window.VIDEO_PLAYER_DEBUG = false;

View File

@ -7,10 +7,10 @@
"stars_count": 0,
"forks_count": 0,
"open_issues_count": 0,
"updated_at": "2026-03-30T11:54:37-06:00",
"updated_at": "2026-04-02T15:39:01-06:00",
"created_at": "2025-05-28T14:54:59-06:00",
"clone_url": "https://gitea.bnkops.com/admin/changemaker.lite.git",
"ssh_url": "git@gitea.bnkops.com:admin/changemaker.lite.git",
"default_branch": "main",
"last_build_update": "2026-03-30T11:54:37-06:00"
"last_build_update": "2026-04-02T15:39:01-06:00"
}

View File

@ -4,13 +4,13 @@
"description": "Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.",
"html_url": "https://github.com/anthropics/claude-code",
"language": "Shell",
"stars_count": 88332,
"forks_count": 9745,
"open_issues_count": 8343,
"updated_at": "2026-03-31T15:50:05Z",
"stars_count": 106579,
"forks_count": 17110,
"open_issues_count": 8935,
"updated_at": "2026-04-02T23:47:09Z",
"created_at": "2025-02-22T17:41:21Z",
"clone_url": "https://github.com/anthropics/claude-code.git",
"ssh_url": "git@github.com:anthropics/claude-code.git",
"default_branch": "main",
"last_build_update": "2026-03-31T14:35:57Z"
"last_build_update": "2026-04-02T23:45:35Z"
}

View File

@ -4,13 +4,13 @@
"description": "VS Code in the browser",
"html_url": "https://github.com/coder/code-server",
"language": "TypeScript",
"stars_count": 76910,
"forks_count": 6575,
"open_issues_count": 172,
"updated_at": "2026-03-31T14:49:15Z",
"stars_count": 76948,
"forks_count": 6581,
"open_issues_count": 182,
"updated_at": "2026-04-02T23:00:45Z",
"created_at": "2019-02-27T16:50:41Z",
"clone_url": "https://github.com/coder/code-server.git",
"ssh_url": "git@github.com:coder/code-server.git",
"default_branch": "main",
"last_build_update": "2026-03-31T00:00:24Z"
"last_build_update": "2026-04-02T17:26:29Z"
}

View File

@ -4,13 +4,13 @@
"description": "A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.",
"html_url": "https://github.com/gethomepage/homepage",
"language": "JavaScript",
"stars_count": 29249,
"forks_count": 1833,
"open_issues_count": 0,
"updated_at": "2026-03-31T14:45:22Z",
"stars_count": 29293,
"forks_count": 1837,
"open_issues_count": 6,
"updated_at": "2026-04-02T23:24:40Z",
"created_at": "2022-08-24T07:29:42Z",
"clone_url": "https://github.com/gethomepage/homepage.git",
"ssh_url": "git@github.com:gethomepage/homepage.git",
"default_branch": "dev",
"last_build_update": "2026-03-31T14:36:10Z"
"last_build_update": "2026-04-02T16:34:28Z"
}

View File

@ -4,13 +4,13 @@
"description": "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD",
"html_url": "https://github.com/go-gitea/gitea",
"language": "Go",
"stars_count": 54629,
"forks_count": 6518,
"open_issues_count": 2866,
"updated_at": "2026-03-31T15:12:57Z",
"stars_count": 54697,
"forks_count": 6529,
"open_issues_count": 2859,
"updated_at": "2026-04-02T22:05:25Z",
"created_at": "2016-11-01T02:13:26Z",
"clone_url": "https://github.com/go-gitea/gitea.git",
"ssh_url": "git@github.com:go-gitea/gitea.git",
"default_branch": "main",
"last_build_update": "2026-03-31T15:40:43Z"
"last_build_update": "2026-04-02T22:05:32Z"
}

View File

@ -4,13 +4,13 @@
"description": "High performance, self-hosted, newsletter and mailing list manager with a modern dashboard. Single binary app.",
"html_url": "https://github.com/knadh/listmonk",
"language": "Go",
"stars_count": 19396,
"forks_count": 1977,
"open_issues_count": 91,
"updated_at": "2026-03-31T14:23:10Z",
"stars_count": 19420,
"forks_count": 1981,
"open_issues_count": 93,
"updated_at": "2026-04-02T23:39:51Z",
"created_at": "2019-06-26T05:08:39Z",
"clone_url": "https://github.com/knadh/listmonk.git",
"ssh_url": "git@github.com:knadh/listmonk.git",
"default_branch": "master",
"last_build_update": "2026-03-31T05:18:48Z"
"last_build_update": "2026-04-01T03:13:59Z"
}

View File

@ -4,13 +4,13 @@
"description": "Create & scan cute qr codes easily \ud83d\udc7e",
"html_url": "https://github.com/lyqht/mini-qr",
"language": "Vue",
"stars_count": 1938,
"forks_count": 245,
"open_issues_count": 23,
"updated_at": "2026-03-31T12:32:17Z",
"stars_count": 1940,
"forks_count": 246,
"open_issues_count": 22,
"updated_at": "2026-04-02T10:46:58Z",
"created_at": "2023-04-21T14:20:14Z",
"clone_url": "https://github.com/lyqht/mini-qr.git",
"ssh_url": "git@github.com:lyqht/mini-qr.git",
"default_branch": "main",
"last_build_update": "2026-03-31T12:43:07Z"
"last_build_update": "2026-04-02T01:49:35Z"
}

View File

@ -4,13 +4,13 @@
"description": "Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.",
"html_url": "https://github.com/n8n-io/n8n",
"language": "TypeScript",
"stars_count": 181869,
"forks_count": 56348,
"open_issues_count": 1447,
"updated_at": "2026-03-31T15:46:32Z",
"stars_count": 182217,
"forks_count": 56419,
"open_issues_count": 1462,
"updated_at": "2026-04-02T23:45:07Z",
"created_at": "2019-06-22T09:24:21Z",
"clone_url": "https://github.com/n8n-io/n8n.git",
"ssh_url": "git@github.com:n8n-io/n8n.git",
"default_branch": "master",
"last_build_update": "2026-03-31T15:47:22Z"
"last_build_update": "2026-04-02T23:31:18Z"
}

View File

@ -4,13 +4,13 @@
"description": "\ud83d\udd25 \ud83d\udd25 \ud83d\udd25 A Free & Self-hostable Airtable Alternative",
"html_url": "https://github.com/nocodb/nocodb",
"language": "TypeScript",
"stars_count": 62566,
"forks_count": 4703,
"open_issues_count": 665,
"updated_at": "2026-03-31T15:24:55Z",
"stars_count": 62599,
"forks_count": 4707,
"open_issues_count": 666,
"updated_at": "2026-04-02T22:40:11Z",
"created_at": "2017-10-29T18:51:48Z",
"clone_url": "https://github.com/nocodb/nocodb.git",
"ssh_url": "git@github.com:nocodb/nocodb.git",
"default_branch": "develop",
"last_build_update": "2026-03-31T15:24:48Z"
"last_build_update": "2026-04-02T15:34:26Z"
}

View File

@ -4,13 +4,13 @@
"description": "Get up and running with Kimi-K2.5, GLM-5, MiniMax, DeepSeek, gpt-oss, Qwen, Gemma and other models.",
"html_url": "https://github.com/ollama/ollama",
"language": "Go",
"stars_count": 166587,
"forks_count": 15255,
"open_issues_count": 2778,
"updated_at": "2026-03-31T15:34:55Z",
"stars_count": 166846,
"forks_count": 15277,
"open_issues_count": 2805,
"updated_at": "2026-04-02T23:45:01Z",
"created_at": "2023-06-26T19:39:32Z",
"clone_url": "https://github.com/ollama/ollama.git",
"ssh_url": "git@github.com:ollama/ollama.git",
"default_branch": "main",
"last_build_update": "2026-03-31T15:11:36Z"
"last_build_update": "2026-04-02T21:23:53Z"
}

View File

@ -4,10 +4,10 @@
"description": "Documentation that simply works",
"html_url": "https://github.com/squidfunk/mkdocs-material",
"language": "Python",
"stars_count": 26430,
"forks_count": 4062,
"stars_count": 26441,
"forks_count": 4064,
"open_issues_count": 1,
"updated_at": "2026-03-31T14:42:16Z",
"updated_at": "2026-04-02T22:20:24Z",
"created_at": "2016-01-28T22:09:23Z",
"clone_url": "https://github.com/squidfunk/mkdocs-material.git",
"ssh_url": "git@github.com:squidfunk/mkdocs-material.git",

View File

@ -71,7 +71,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -73,7 +73,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -3,7 +3,7 @@
(function() {
window.MEDIA_API_URL = 'http://localhost:4100';
window.PUBLIC_URL = 'http://localhost:3002';
window.PAYMENT_API_URL = 'http://localhost:4002';
window.PAYMENT_API_URL = 'https://api.cmlite.org';
window.APP_URL = 'http://localhost:3002';
window.GANCIO_URL = 'http://localhost:8092';
window.VIDEO_PLAYER_DEBUG = false;

View File

@ -7,10 +7,10 @@
"stars_count": 0,
"forks_count": 0,
"open_issues_count": 0,
"updated_at": "2026-03-30T11:54:37-06:00",
"updated_at": "2026-04-02T15:39:01-06:00",
"created_at": "2025-05-28T14:54:59-06:00",
"clone_url": "https://gitea.bnkops.com/admin/changemaker.lite.git",
"ssh_url": "git@gitea.bnkops.com:admin/changemaker.lite.git",
"default_branch": "main",
"last_build_update": "2026-03-30T11:54:37-06:00"
"last_build_update": "2026-04-02T15:39:01-06:00"
}

View File

@ -4,13 +4,13 @@
"description": "Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.",
"html_url": "https://github.com/anthropics/claude-code",
"language": "Shell",
"stars_count": 88332,
"forks_count": 9745,
"open_issues_count": 8343,
"updated_at": "2026-03-31T15:50:05Z",
"stars_count": 106579,
"forks_count": 17110,
"open_issues_count": 8935,
"updated_at": "2026-04-02T23:47:09Z",
"created_at": "2025-02-22T17:41:21Z",
"clone_url": "https://github.com/anthropics/claude-code.git",
"ssh_url": "git@github.com:anthropics/claude-code.git",
"default_branch": "main",
"last_build_update": "2026-03-31T14:35:57Z"
"last_build_update": "2026-04-02T23:45:35Z"
}

View File

@ -4,13 +4,13 @@
"description": "VS Code in the browser",
"html_url": "https://github.com/coder/code-server",
"language": "TypeScript",
"stars_count": 76910,
"forks_count": 6575,
"open_issues_count": 172,
"updated_at": "2026-03-31T14:49:15Z",
"stars_count": 76948,
"forks_count": 6581,
"open_issues_count": 182,
"updated_at": "2026-04-02T23:00:45Z",
"created_at": "2019-02-27T16:50:41Z",
"clone_url": "https://github.com/coder/code-server.git",
"ssh_url": "git@github.com:coder/code-server.git",
"default_branch": "main",
"last_build_update": "2026-03-31T00:00:24Z"
"last_build_update": "2026-04-02T17:26:29Z"
}

View File

@ -4,13 +4,13 @@
"description": "A highly customizable homepage (or startpage / application dashboard) with Docker and service API integrations.",
"html_url": "https://github.com/gethomepage/homepage",
"language": "JavaScript",
"stars_count": 29249,
"forks_count": 1833,
"open_issues_count": 0,
"updated_at": "2026-03-31T14:45:22Z",
"stars_count": 29293,
"forks_count": 1837,
"open_issues_count": 6,
"updated_at": "2026-04-02T23:24:40Z",
"created_at": "2022-08-24T07:29:42Z",
"clone_url": "https://github.com/gethomepage/homepage.git",
"ssh_url": "git@github.com:gethomepage/homepage.git",
"default_branch": "dev",
"last_build_update": "2026-03-31T14:36:10Z"
"last_build_update": "2026-04-02T16:34:28Z"
}

View File

@ -4,13 +4,13 @@
"description": "Git with a cup of tea! Painless self-hosted all-in-one software development service, including Git hosting, code review, team collaboration, package registry and CI/CD",
"html_url": "https://github.com/go-gitea/gitea",
"language": "Go",
"stars_count": 54629,
"forks_count": 6518,
"open_issues_count": 2866,
"updated_at": "2026-03-31T15:12:57Z",
"stars_count": 54697,
"forks_count": 6529,
"open_issues_count": 2859,
"updated_at": "2026-04-02T22:05:25Z",
"created_at": "2016-11-01T02:13:26Z",
"clone_url": "https://github.com/go-gitea/gitea.git",
"ssh_url": "git@github.com:go-gitea/gitea.git",
"default_branch": "main",
"last_build_update": "2026-03-31T15:40:43Z"
"last_build_update": "2026-04-02T22:05:32Z"
}

View File

@ -4,13 +4,13 @@
"description": "High performance, self-hosted, newsletter and mailing list manager with a modern dashboard. Single binary app.",
"html_url": "https://github.com/knadh/listmonk",
"language": "Go",
"stars_count": 19396,
"forks_count": 1977,
"open_issues_count": 91,
"updated_at": "2026-03-31T14:23:10Z",
"stars_count": 19420,
"forks_count": 1981,
"open_issues_count": 93,
"updated_at": "2026-04-02T23:39:51Z",
"created_at": "2019-06-26T05:08:39Z",
"clone_url": "https://github.com/knadh/listmonk.git",
"ssh_url": "git@github.com:knadh/listmonk.git",
"default_branch": "master",
"last_build_update": "2026-03-31T05:18:48Z"
"last_build_update": "2026-04-01T03:13:59Z"
}

View File

@ -4,13 +4,13 @@
"description": "Create & scan cute qr codes easily \ud83d\udc7e",
"html_url": "https://github.com/lyqht/mini-qr",
"language": "Vue",
"stars_count": 1938,
"forks_count": 245,
"open_issues_count": 23,
"updated_at": "2026-03-31T12:32:17Z",
"stars_count": 1940,
"forks_count": 246,
"open_issues_count": 22,
"updated_at": "2026-04-02T10:46:58Z",
"created_at": "2023-04-21T14:20:14Z",
"clone_url": "https://github.com/lyqht/mini-qr.git",
"ssh_url": "git@github.com:lyqht/mini-qr.git",
"default_branch": "main",
"last_build_update": "2026-03-31T12:43:07Z"
"last_build_update": "2026-04-02T01:49:35Z"
}

View File

@ -4,13 +4,13 @@
"description": "Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.",
"html_url": "https://github.com/n8n-io/n8n",
"language": "TypeScript",
"stars_count": 181869,
"forks_count": 56348,
"open_issues_count": 1447,
"updated_at": "2026-03-31T15:46:32Z",
"stars_count": 182217,
"forks_count": 56419,
"open_issues_count": 1462,
"updated_at": "2026-04-02T23:45:07Z",
"created_at": "2019-06-22T09:24:21Z",
"clone_url": "https://github.com/n8n-io/n8n.git",
"ssh_url": "git@github.com:n8n-io/n8n.git",
"default_branch": "master",
"last_build_update": "2026-03-31T15:47:22Z"
"last_build_update": "2026-04-02T23:31:18Z"
}

View File

@ -4,13 +4,13 @@
"description": "\ud83d\udd25 \ud83d\udd25 \ud83d\udd25 A Free & Self-hostable Airtable Alternative",
"html_url": "https://github.com/nocodb/nocodb",
"language": "TypeScript",
"stars_count": 62566,
"forks_count": 4703,
"open_issues_count": 665,
"updated_at": "2026-03-31T15:24:55Z",
"stars_count": 62599,
"forks_count": 4707,
"open_issues_count": 666,
"updated_at": "2026-04-02T22:40:11Z",
"created_at": "2017-10-29T18:51:48Z",
"clone_url": "https://github.com/nocodb/nocodb.git",
"ssh_url": "git@github.com:nocodb/nocodb.git",
"default_branch": "develop",
"last_build_update": "2026-03-31T15:24:48Z"
"last_build_update": "2026-04-02T15:34:26Z"
}

View File

@ -4,13 +4,13 @@
"description": "Get up and running with Kimi-K2.5, GLM-5, MiniMax, DeepSeek, gpt-oss, Qwen, Gemma and other models.",
"html_url": "https://github.com/ollama/ollama",
"language": "Go",
"stars_count": 166587,
"forks_count": 15255,
"open_issues_count": 2778,
"updated_at": "2026-03-31T15:34:55Z",
"stars_count": 166846,
"forks_count": 15277,
"open_issues_count": 2805,
"updated_at": "2026-04-02T23:45:01Z",
"created_at": "2023-06-26T19:39:32Z",
"clone_url": "https://github.com/ollama/ollama.git",
"ssh_url": "git@github.com:ollama/ollama.git",
"default_branch": "main",
"last_build_update": "2026-03-31T15:11:36Z"
"last_build_update": "2026-04-02T21:23:53Z"
}

View File

@ -4,10 +4,10 @@
"description": "Documentation that simply works",
"html_url": "https://github.com/squidfunk/mkdocs-material",
"language": "Python",
"stars_count": 26430,
"forks_count": 4062,
"stars_count": 26441,
"forks_count": 4064,
"open_issues_count": 1,
"updated_at": "2026-03-31T14:42:16Z",
"updated_at": "2026-04-02T22:20:24Z",
"created_at": "2016-01-28T22:09:23Z",
"clone_url": "https://github.com/squidfunk/mkdocs-material.git",
"ssh_url": "git@github.com:squidfunk/mkdocs-material.git",

View File

@ -75,7 +75,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -75,7 +75,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -75,7 +75,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -73,7 +73,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";
@ -3884,10 +3884,12 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
<h3 id="step-3-run-the-setup-script">Step 3: Run the Setup Script<a class="headerlink" href="#step-3-run-the-setup-script" title="Permanent link">&para;</a></h3>
<p>Open Termux on the phone and run:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="c1"># Clone the SMS server (first time only)</span>
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a>pkg<span class="w"> </span>install<span class="w"> </span>-y<span class="w"> </span>git<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>git<span class="w"> </span>clone<span class="w"> </span>https://gitea.bnkops.com/admin/campaign_connector.git<span class="w"> </span>~/sms-server
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a>
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="c1"># Run the setup script — paste your API key at the end</span>
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a>bash<span class="w"> </span>~/sms-server/android/setup.sh<span class="w"> </span>YOUR_API_KEY_HERE
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a>pkg<span class="w"> </span>install<span class="w"> </span>-y<span class="w"> </span>git<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>git<span class="w"> </span>clone<span class="w"> </span>--depth<span class="w"> </span><span class="m">1</span><span class="w"> </span>--filter<span class="o">=</span>blob:none<span class="w"> </span>--sparse<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a><span class="w"> </span>https://gitea.bnkops.com/admin/changemaker.lite.git<span class="w"> </span>~/sms-server<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="nb">cd</span><span class="w"> </span>~/sms-server<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>git<span class="w"> </span>sparse-checkout<span class="w"> </span><span class="nb">set</span><span class="w"> </span>termux-sms
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a>
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6" href="#__codelineno-0-6"></a><span class="c1"># Run the setup script — paste your API key at the end</span>
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a>bash<span class="w"> </span>~/sms-server/termux-sms/setup.sh<span class="w"> </span>YOUR_API_KEY_HERE
</span></code></pre></div>
<p>The setup script automatically:</p>
<ul>
@ -3900,7 +3902,7 @@ body.cm-search-active .md-header--cm-hidden .md-search__output {
<p>When done, note the <strong>Phone URL</strong> displayed (e.g. <code>http://100.64.0.5:5001</code>).</p>
<h4 id="recommended-install-service-supervisor">Recommended: Install Service Supervisor<a class="headerlink" href="#recommended-install-service-supervisor" title="Permanent link">&para;</a></h4>
<p>After initial setup, install <code>termux-services</code> for reliable process management. This uses runit, a proper UNIX service supervisor that automatically restarts the server if it crashes:</p>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a><span class="nb">cd</span><span class="w"> </span>~/sms-server<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>bash<span class="w"> </span>android/setup-services.sh
<div class="language-bash highlight"><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a><span class="nb">cd</span><span class="w"> </span>~/sms-server<span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span>bash<span class="w"> </span>termux-sms/setup-services.sh
</span></code></pre></div>
<p>This registers two supervised services:</p>
<ul>

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

View File

@ -77,7 +77,7 @@
<script>
(function () {
// API URL injected from MkDocs config.extra (set by env_config_hook.py)
var apiUrl = "http://localhost:4002";
var apiUrl = "https://api.cmlite.org";
if (!apiUrl) return;
var trackUrl = apiUrl + "/api/docs-analytics/track";

Some files were not shown because too many files have changed in this diff Show More