neoagent 3.0.1-beta.7 → 3.0.1-beta.8

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.
Files changed (38) hide show
  1. package/.env.example +19 -0
  2. package/README.md +1 -0
  3. package/docs/billing.md +203 -0
  4. package/docs/configuration.md +13 -0
  5. package/docs/index.md +1 -0
  6. package/lib/schema_migrations.js +84 -0
  7. package/package.json +2 -1
  8. package/server/admin/admin.js +2 -1
  9. package/server/admin/billing.js +292 -0
  10. package/server/admin/index.html +37 -0
  11. package/server/db/database.js +1 -0
  12. package/server/http/routes.js +9 -0
  13. package/server/middleware/requireBilling.js +12 -0
  14. package/server/public/.last_build_id +1 -1
  15. package/server/public/flutter_bootstrap.js +1 -1
  16. package/server/public/main.dart.js +4 -4
  17. package/server/routes/admin.js +110 -0
  18. package/server/routes/billing.js +122 -0
  19. package/server/routes/billing_webhook.js +43 -0
  20. package/server/routes/memory.js +1 -4
  21. package/server/routes/settings.js +1 -2
  22. package/server/routes/skills.js +1 -1
  23. package/server/services/ai/loop/agent_engine_core.js +1 -1
  24. package/server/services/ai/loop/completion_judge.js +2 -9
  25. package/server/services/ai/loop/conversation_loop.js +12 -26
  26. package/server/services/ai/loopPolicy.js +2 -39
  27. package/server/services/ai/models.js +18 -0
  28. package/server/services/ai/systemPrompt.js +17 -0
  29. package/server/services/ai/tools.js +1 -11
  30. package/server/services/billing/billing_email.js +106 -0
  31. package/server/services/billing/config.js +17 -0
  32. package/server/services/billing/plans.js +120 -0
  33. package/server/services/billing/stripe_client.js +21 -0
  34. package/server/services/billing/subscriptions.js +462 -0
  35. package/server/services/billing/trial_guard.js +111 -0
  36. package/server/services/manager.js +11 -0
  37. package/server/services/tasks/runtime.js +52 -12
  38. package/server/services/workspace/manager.js +23 -0
package/.env.example CHANGED
@@ -267,3 +267,22 @@ NEOAGENT_SCREEN_RECORDER_RETENTION_DAYS=7
267
267
 
268
268
  MESHTASTIC_ENABLED=true
269
269
  TELNYX_WEBHOOK_TOKEN=
270
+
271
+ ########################################
272
+ # Stripe billing (optional)
273
+ ########################################
274
+
275
+ # Set to true or 1 to enable the billing system. When disabled (default),
276
+ # no billing routes are exposed and no payment UI is shown anywhere.
277
+ # NEOAGENT_BILLING_ENABLED=false
278
+
279
+ # Stripe API keys — required when billing is enabled.
280
+ # Use test keys (sk_test_...) during development; live keys in production.
281
+ # STRIPE_SECRET_KEY=sk_live_...
282
+ # STRIPE_PUBLISHABLE_KEY=pk_live_...
283
+
284
+ # Webhook signing secret from your Stripe dashboard (Developers > Webhooks).
285
+ # STRIPE_WEBHOOK_SECRET=whsec_...
286
+
287
+ # Length of free trial periods in days (default: 14).
288
+ # BILLING_TRIAL_DAYS=14
package/README.md CHANGED
@@ -61,6 +61,7 @@ service to a network.
61
61
  - **It operates real devices.** The agent can use an isolated browser and shell, control an Android device or emulator over ADB, or work through a paired Chrome extension and desktop companion.
62
62
  - **Automation can start without a message.** Tasks can run on a schedule or from supported Gmail, Outlook, Slack, Teams, personal WhatsApp, and weather events. Android notifications can also start an agent run.
63
63
  - **Agents and users have separate state.** Specialist agents can have their own memory, settings, tools, account assignments, conversations, and task history. Multi-user deployments include administrative account controls and optional email confirmation.
64
+ - **SaaS billing is built in and off by default.** Set `NEOAGENT_BILLING_ENABLED=true` to activate Stripe subscriptions, plan management, free trials, and model access restrictions. When disabled, no payment routes or UI appear anywhere. See [Billing](docs/billing.md).
64
65
  - **The same server has several interfaces.** NeoAgent includes web, Android, desktop, and Android launcher clients, messaging bridges, a Chrome extension, and firmware for a supported ESP32-S3 wearable.
65
66
 
66
67
  ## 🖥️ Interfaces
@@ -0,0 +1,203 @@
1
+ # Billing
2
+
3
+ NeoAgent includes an optional Stripe-based billing system for deployments that
4
+ charge users for access. When disabled — the default — no billing routes are
5
+ exposed, no payment UI is shown, and no payment-related information appears
6
+ anywhere in the application.
7
+
8
+ ## Enable billing
9
+
10
+ Set the following environment variables before starting the server:
11
+
12
+ ```bash
13
+ neoagent env set NEOAGENT_BILLING_ENABLED true
14
+ neoagent env set STRIPE_SECRET_KEY sk_live_...
15
+ neoagent env set STRIPE_PUBLISHABLE_KEY pk_live_...
16
+ neoagent env set STRIPE_WEBHOOK_SECRET whsec_...
17
+ ```
18
+
19
+ Restart the server. The admin dashboard will show a **Billing** navigation
20
+ item and the `/api/billing/*` endpoints will become active.
21
+
22
+ Use Stripe test keys (`sk_test_...`, `pk_test_...`) during development.
23
+
24
+ ## Subscription plans
25
+
26
+ Plans are managed in **Admin › Billing › Plans**. Each plan controls:
27
+
28
+ | Field | Purpose |
29
+ |---|---|
30
+ | Name | Displayed to users in the settings submenu |
31
+ | Price | Stripe price in cents (0 = free) |
32
+ | Billing interval | `month`, `year`, or blank for one-time or free |
33
+ | Stripe Price ID | The `price_...` ID from your Stripe dashboard |
34
+ | 4h token limit | Per-user 4-hour rolling token budget (blank = server default) |
35
+ | Weekly token limit | Per-user 7-day rolling token budget (blank = server default) |
36
+ | Allowed models | Comma-separated model IDs; blank = all models accessible |
37
+ | Features | Marketing feature strings shown on pricing pages |
38
+
39
+ Token limits are written directly to the user account when a subscription
40
+ becomes active or is updated via webhook. The existing rate-limit enforcement
41
+ in the runtime reads them with no additional configuration.
42
+
43
+ ### Free plan
44
+
45
+ Create a plan with **Price = 0** to serve as the default for new users. If a
46
+ free plan exists when billing is enabled, every new registration is
47
+ automatically placed on it.
48
+
49
+ ### Model restrictions
50
+
51
+ If **Allowed models** is non-empty, users on that plan can only use the listed
52
+ model IDs. Models outside the allowlist remain visible in the UI with an
53
+ unavailable status so users understand what upgrading unlocks.
54
+
55
+ Leave the field blank to allow all configured models on a plan.
56
+
57
+ ## User subscriptions
58
+
59
+ Users manage their subscription in **Settings › Billing** (Flutter client). From
60
+ there they can:
61
+
62
+ - View their current plan, status, and next renewal date
63
+ - Upgrade or downgrade through Stripe Checkout
64
+ - Open the Stripe Customer Portal to update a payment method or download invoices
65
+ - Cancel at the end of the current billing period
66
+
67
+ ### Stripe Checkout
68
+
69
+ When a user selects a paid plan, the server creates a Stripe Checkout session
70
+ and redirects the client to Stripe's hosted payment page. No card data passes
71
+ through NeoAgent.
72
+
73
+ ### Stripe Customer Portal
74
+
75
+ The Customer Portal is a Stripe-hosted page where users can update their
76
+ payment method, view billing history, and cancel. Configure the portal in your
77
+ [Stripe dashboard](https://dashboard.stripe.com/settings/billing/portal) before
78
+ enabling it.
79
+
80
+ ## Free trials
81
+
82
+ Enable free trials in **Admin › Billing › Plans** by configuring a Stripe price
83
+ that supports trials, then setting `BILLING_TRIAL_DAYS` to the desired length
84
+ (default: 14 days).
85
+
86
+ Trials start when a user calls `POST /api/billing/trial` with a plan ID. The
87
+ server runs anti-abuse checks before granting a trial:
88
+
89
+ | Check | Limit |
90
+ |---|---|
91
+ | IP address | 2 trials per IP per 30 days |
92
+ | Email domain | 3 trials per non-common domain per 30 days |
93
+ | Account age | Account must be at least 1 day old |
94
+ | Device fingerprint | 1 trial per device per 90 days (opaque hash from client) |
95
+
96
+ The Flutter client is responsible for generating and sending the device
97
+ fingerprint. The server hashes it with SHA-256 before storage — the raw
98
+ fingerprint is never persisted.
99
+
100
+ ## Webhooks
101
+
102
+ Stripe sends events to `POST /api/billing/webhook`. Register this URL in your
103
+ [Stripe dashboard](https://dashboard.stripe.com/webhooks) under the endpoint
104
+ for your account.
105
+
106
+ Handled events:
107
+
108
+ | Event | Effect |
109
+ |---|---|
110
+ | `customer.subscription.created` | Creates a local subscription row |
111
+ | `customer.subscription.updated` | Syncs status, period dates, and token limits |
112
+ | `customer.subscription.deleted` | Marks subscription canceled |
113
+ | `customer.subscription.trial_will_end` | Sends trial-ending email (fires 3 days before) |
114
+ | `invoice.payment_succeeded` | Records payment; sends renewal email on cycle |
115
+ | `invoice.payment_failed` | Records failure; sends payment-failed email |
116
+
117
+ Events are idempotent — replaying a webhook event produces the same result.
118
+
119
+ **Required events to enable in Stripe:**
120
+
121
+ ```
122
+ customer.subscription.created
123
+ customer.subscription.updated
124
+ customer.subscription.deleted
125
+ customer.subscription.trial_will_end
126
+ invoice.payment_succeeded
127
+ invoice.payment_failed
128
+ ```
129
+
130
+ ### Verifying the webhook locally
131
+
132
+ Use the Stripe CLI to forward events to a local server during development:
133
+
134
+ ```bash
135
+ stripe listen --forward-to http://localhost:3333/api/billing/webhook
136
+ ```
137
+
138
+ ## Email notifications
139
+
140
+ If [service email](configuration.md#service-email) is configured, users receive
141
+ emails at the following billing events:
142
+
143
+ | Event | Email |
144
+ |---|---|
145
+ | Trial started | Confirmation with trial end date |
146
+ | Trial ending soon | Reminder 3 days before end |
147
+ | Subscription activated | Welcome to the paid plan |
148
+ | Subscription renewed | Renewal confirmation |
149
+ | Payment failed | Action required with next retry date |
150
+ | Subscription canceled | Cancellation confirmation |
151
+ | Plan changed | Summary of the new plan |
152
+
153
+ No emails are sent if SMTP is not configured.
154
+
155
+ ## AI context awareness
156
+
157
+ When billing is enabled, each AI request includes the user's subscription in
158
+ the system prompt:
159
+
160
+ ```
161
+ SUBSCRIPTION: User is on the "Pro" plan, status: active.
162
+ ```
163
+
164
+ Agents and workflows can use this context to adapt their behavior — for
165
+ example, acknowledging feature limitations on a free plan or confirming
166
+ capabilities on a paid one.
167
+
168
+ ## Admin controls
169
+
170
+ The **Admin › Billing** page provides:
171
+
172
+ - **Plans**: Create, edit, and deactivate subscription plans
173
+ - **Subscriptions**: Browse all user subscriptions with status filter and
174
+ pagination; override a user's plan without going through Stripe
175
+
176
+ Override a user's plan directly from the subscriptions table using the
177
+ **Override** button. This is useful for comped accounts, support escalations,
178
+ or fixing a webhook gap.
179
+
180
+ ## Configuration reference
181
+
182
+ | Variable | Default | Purpose |
183
+ |---|---|---|
184
+ | `NEOAGENT_BILLING_ENABLED` | `false` | Master on/off switch |
185
+ | `STRIPE_SECRET_KEY` | required | Stripe server-side API key |
186
+ | `STRIPE_PUBLISHABLE_KEY` | required | Stripe client-side key (returned to Flutter) |
187
+ | `STRIPE_WEBHOOK_SECRET` | required | Webhook signing secret from Stripe dashboard |
188
+ | `BILLING_TRIAL_DAYS` | `14` | Free trial length in days |
189
+
190
+ All variables can be set with `neoagent env set` or added to `~/.neoagent/.env`
191
+ directly. Restart the server after any change.
192
+
193
+ ## Security notes
194
+
195
+ - NeoAgent stores only Stripe customer IDs and subscription IDs — no card
196
+ numbers, bank details, or PII beyond what Stripe already holds.
197
+ - Webhook payloads are verified with HMAC-SHA256 using `STRIPE_WEBHOOK_SECRET`
198
+ before any processing. Requests missing the `stripe-signature` header are
199
+ rejected with 400.
200
+ - Trial fingerprints are stored as SHA-256 hashes — the raw IP, email domain,
201
+ and device identifiers are never persisted.
202
+ - All billing routes return 404 when `NEOAGENT_BILLING_ENABLED` is not set,
203
+ regardless of whether Stripe credentials are present.
@@ -97,6 +97,19 @@ reset, email changes, and security notifications.
97
97
  | `NEOAGENT_EMAIL_PUBLIC_URL` | Base URL used in email links |
98
98
  | `NEOAGENT_EMAIL_TOKEN_TTL_HOURS` | Confirmation token lifetime |
99
99
 
100
+ ## Billing
101
+
102
+ Billing is disabled by default. See [Billing](billing.md) for the full setup
103
+ guide, webhook configuration, and plan management.
104
+
105
+ | Variable | Default | Purpose |
106
+ |---|---|---|
107
+ | `NEOAGENT_BILLING_ENABLED` | `false` | Enable the Stripe billing system |
108
+ | `STRIPE_SECRET_KEY` | unset | Stripe server-side API key |
109
+ | `STRIPE_PUBLISHABLE_KEY` | unset | Stripe client-side key (returned to clients) |
110
+ | `STRIPE_WEBHOOK_SECRET` | unset | Webhook signing secret |
111
+ | `BILLING_TRIAL_DAYS` | `14` | Free trial length in days |
112
+
100
113
  ## Isolated runtime
101
114
 
102
115
  | Variable | Purpose |
package/docs/index.md CHANGED
@@ -45,6 +45,7 @@ Continue with:
45
45
  | [Recordings and health](recordings-and-health.md) | Audio transcription and Health Connect |
46
46
  | [Skills and MCP](skills.md) | Reusable instructions and external tool servers |
47
47
  | [Operations](operations.md) | Updates, backups, logs, and recovery |
48
+ | [Billing](billing.md) | Stripe subscriptions, plans, trials, and webhooks |
48
49
  | [Configuration](configuration.md) | Environment and runtime reference |
49
50
  | [Why NeoAgent](why-neoagent.md) | Factual comparison with OpenClaw and Hermes |
50
51
 
@@ -458,6 +458,88 @@ function migrateToolPoliciesAllowAlways(db) {
458
458
  `);
459
459
  }
460
460
 
461
+ function migrateBilling(db) {
462
+ db.exec(`
463
+ CREATE TABLE IF NOT EXISTS billing_plans (
464
+ id TEXT PRIMARY KEY,
465
+ name TEXT NOT NULL,
466
+ description TEXT DEFAULT '',
467
+ price_cents INTEGER NOT NULL DEFAULT 0,
468
+ currency TEXT NOT NULL DEFAULT 'usd',
469
+ interval TEXT DEFAULT 'month',
470
+ stripe_price_id TEXT,
471
+ token_limit_4h INTEGER,
472
+ token_limit_weekly INTEGER,
473
+ allowed_models_json TEXT DEFAULT '[]',
474
+ features_json TEXT DEFAULT '[]',
475
+ is_active INTEGER DEFAULT 1,
476
+ sort_order INTEGER DEFAULT 0,
477
+ created_at TEXT DEFAULT (datetime('now')),
478
+ updated_at TEXT DEFAULT (datetime('now'))
479
+ );
480
+
481
+ CREATE TABLE IF NOT EXISTS billing_customers (
482
+ user_id INTEGER PRIMARY KEY,
483
+ stripe_customer_id TEXT UNIQUE,
484
+ created_at TEXT DEFAULT (datetime('now')),
485
+ updated_at TEXT DEFAULT (datetime('now')),
486
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
487
+ );
488
+
489
+ CREATE TABLE IF NOT EXISTS user_subscriptions (
490
+ id TEXT PRIMARY KEY,
491
+ user_id INTEGER NOT NULL,
492
+ plan_id TEXT NOT NULL,
493
+ stripe_subscription_id TEXT UNIQUE,
494
+ status TEXT NOT NULL,
495
+ trial_ends_at TEXT,
496
+ current_period_start TEXT,
497
+ current_period_end TEXT,
498
+ cancel_at_period_end INTEGER DEFAULT 0,
499
+ canceled_at TEXT,
500
+ created_at TEXT DEFAULT (datetime('now')),
501
+ updated_at TEXT DEFAULT (datetime('now')),
502
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
503
+ FOREIGN KEY (plan_id) REFERENCES billing_plans(id)
504
+ );
505
+
506
+ CREATE TABLE IF NOT EXISTS subscription_events (
507
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
508
+ user_id INTEGER NOT NULL,
509
+ subscription_id TEXT,
510
+ event_type TEXT NOT NULL,
511
+ stripe_event_id TEXT UNIQUE,
512
+ payload_json TEXT DEFAULT '{}',
513
+ created_at TEXT DEFAULT (datetime('now')),
514
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
515
+ );
516
+
517
+ CREATE TABLE IF NOT EXISTS trial_fingerprints (
518
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
519
+ fingerprint_hash TEXT NOT NULL,
520
+ fingerprint_type TEXT NOT NULL,
521
+ user_id INTEGER,
522
+ used_at TEXT DEFAULT (datetime('now')),
523
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
524
+ );
525
+
526
+ CREATE INDEX IF NOT EXISTS idx_billing_customers_stripe
527
+ ON billing_customers(stripe_customer_id);
528
+
529
+ CREATE INDEX IF NOT EXISTS idx_user_subscriptions_user
530
+ ON user_subscriptions(user_id, status, updated_at DESC);
531
+
532
+ CREATE INDEX IF NOT EXISTS idx_user_subscriptions_stripe
533
+ ON user_subscriptions(stripe_subscription_id);
534
+
535
+ CREATE INDEX IF NOT EXISTS idx_subscription_events_stripe
536
+ ON subscription_events(stripe_event_id);
537
+
538
+ CREATE INDEX IF NOT EXISTS idx_trial_fingerprints_hash
539
+ ON trial_fingerprints(fingerprint_hash, fingerprint_type);
540
+ `);
541
+ }
542
+
461
543
  function runSchemaMigrations(db) {
462
544
  migrateMemoryEmbeddingIndex(db);
463
545
  migrateMemoryProvenance(db);
@@ -469,6 +551,7 @@ function runSchemaMigrations(db) {
469
551
  migrateToolPermissions(db);
470
552
  migrateApprovalPersistence(db);
471
553
  migrateToolPoliciesAllowAlways(db);
554
+ migrateBilling(db);
472
555
  }
473
556
 
474
557
  module.exports = {
@@ -482,5 +565,6 @@ module.exports = {
482
565
  migrateToolPermissions,
483
566
  migrateApprovalPersistence,
484
567
  migrateToolPoliciesAllowAlways,
568
+ migrateBilling,
485
569
  runSchemaMigrations,
486
570
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "3.0.1-beta.7",
3
+ "version": "3.0.1-beta.8",
4
4
  "description": "Self-hosted AI agent for long-running tasks, automation, messaging, device control, and local memory",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",
@@ -100,6 +100,7 @@
100
100
  "remotion": "^4.0.459",
101
101
  "sharp": "^0.34.5",
102
102
  "socket.io": "^4.8.1",
103
+ "stripe": "^22.2.1",
103
104
  "telegraf": "^4.16.3",
104
105
  "telnyx": "^5.51.0",
105
106
  "tesseract.js": "^7.0.0",
@@ -16,7 +16,8 @@ function showPage(page, btn) {
16
16
  if (btn) btn.classList.add('active');
17
17
  currentPage = page;
18
18
 
19
- const loaders = { overview: loadHealth, logs: loadLogs, issues: loadIssues, updates: loadVersion, config: loadConfig, providers: loadProviders, models: loadModels, analytics: loadAnalytics, users: loadUsers, sql: loadSql, access: loadAccess };
19
+ const billingLoader = typeof loadBilling !== 'undefined' ? loadBilling : null;
20
+ const loaders = { overview: loadHealth, logs: loadLogs, issues: loadIssues, updates: loadVersion, config: loadConfig, providers: loadProviders, models: loadModels, analytics: loadAnalytics, users: loadUsers, sql: loadSql, access: loadAccess, billing: billingLoader };
20
21
  loaders[page]?.();
21
22
  }
22
23