oopsdb 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 OopsDB Contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OopsDB Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,127 +1,146 @@
1
- # OopsDB
2
-
3
- **Don't let AI nuke your database.**
4
-
5
- Auto-backup and 1-click restore for developers using Claude Code, Cursor, Windsurf, and other AI coding agents.
6
-
7
- <p align="center">
8
- <img src="website/assets/oopsdb_demo.gif" alt="OopsDB Demo Screen Recording" style="max-width: 100%; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1)">
9
- </p>
10
-
11
- ---
12
-
13
- ## The Problem
14
-
15
- You're vibing. Claude Code is cranking through tasks. Then it decides the fastest way to fix a migration is `DROP TABLE users`. Or it runs `DELETE FROM orders` without a `WHERE` clause. Or it helpfully "cleans up" your SQLite file.
16
-
17
- Your data is gone. Your afternoon is gone. Your will to live is negotiable.
18
-
19
- ## The Fix
20
-
21
- ```bash
22
- npm install -g oopsdb
23
- oopsdb init # connect your DB (Supabase, Postgres, MySQL, SQLite)
24
- oopsdb watch # auto-backup every 5 min
25
- # ... AI nukes your DB ...
26
- oopsdb restore # pick a snapshot, roll back instantly
27
- ```
28
-
29
- That's it. Three commands. Your data survives the AI apocalypse.
30
-
31
- ## What It Does
32
-
33
- - **Auto-backups** on a timer (`oopsdb watch`) — set it and forget it
34
- - **Manual snapshots** (`oopsdb snapshot`) — before risky migrations or YOLO prompts
35
- - **Interactive restore** (`oopsdb restore`) — pick any snapshot, roll back in seconds
36
- - **Safety snapshots** — automatically backs up your current state before restoring, so you can't oops your oops
37
- - **Encrypted at rest** — AES-256-CBC encryption on every backup file
38
- - **Zero cloud, zero accounts** — everything stays on your machine
39
- - **Streaming backups** — near-zero memory footprint regardless of DB size
40
-
41
- ## Supported Databases
42
-
43
- | Database | Backup Tool | Restore Tool |
44
- |----------|------------|--------------|
45
- | **Supabase** | `pg_dump` (with Supabase flags) | `psql` |
46
- | PostgreSQL (Neon, local, other hosted) | `pg_dump` | `psql` |
47
- | MySQL / MariaDB | `mysqldump` | `mysql` |
48
- | SQLite | `sqlite3` | `sqlite3` |
49
-
50
- ### Supabase (first-class support)
51
-
52
- OopsDB has dedicated Supabase support. Just paste your connection string:
53
-
54
- ```bash
55
- oopsdb init
56
- # → Select "Supabase"
57
- # → Paste your connection string from Supabase Dashboard → Settings → Database
58
- # → Done. SSL and Supabase-specific pg_dump flags are handled automatically.
59
- ```
60
-
61
- Supabase-specific flags applied automatically: `--no-owner`, `--no-privileges`, `--no-subscriptions`, `sslmode=require`.
62
-
63
- ## Commands
64
-
65
- ```
66
- oopsdb init Set up your database connection
67
- oopsdb watch Auto-backup every 5 minutes
68
- oopsdb watch -i 1 Auto-backup every 1 minute (paranoid mode)
69
- oopsdb snapshot One-time manual backup
70
- oopsdb restore Interactive restore from any snapshot
71
- oopsdb status View backup history and stats
72
- oopsdb secure Immutable cloud backups (Coming Soon)
73
- oopsdb activate <key> Activate a Secure license
74
- oopsdb deactivate Deactivate your license on this machine
75
- oopsdb license Show current license status
76
- oopsdb clean Remove all OopsDB data from project
77
- ```
78
-
79
- ## How It Works
80
-
81
- 1. `oopsdb init` walks you through connecting your database. Credentials are encrypted and saved locally in `.oopsdb/config.json`.
82
- 2. `oopsdb watch` runs the native dump tool (`pg_dump`, `mysqldump`, or `sqlite3 .backup`) at your chosen interval. Output is streamed through AES-256-CBC encryption directly to disk — memory usage stays flat even for large databases.
83
- 3. `oopsdb restore` shows your snapshots with timestamps and sizes. Pick one, confirm, and your database is rolled back. It takes a safety snapshot first, so you can always undo the undo.
84
-
85
- ## Quick Demo
86
-
87
- Want to see OopsDB in action without touching your real database? We built a safe demo playground just for you.
88
-
89
- 1. Clone or download this repository.
90
- 2. Navigate to the `demo/` folder: `cd demo`
91
- 3. Generate the dummy database: `sqlite3 test.db < seed.sql`
92
- 4. Initialize OopsDB (it won't affect your global config because `.oopsdb` is gitignored here): `oopsdb init`
93
- 5. Try running `oopsdb shield` and firing a `DROP TABLE users` against it to watch the interceptor catch the query!
94
-
95
- ## Requirements
96
-
97
- Your system needs the native database CLI tools:
98
-
99
- - **PostgreSQL**: `pg_dump` + `psql`
100
- - **MySQL**: `mysqldump` + `mysql`
101
- - **SQLite**: `sqlite3`
102
-
103
- OopsDB checks for these on `init` and gives install instructions if they're missing.
104
-
105
- ## Security
106
-
107
- - Credentials encrypted at rest (AES-256-CBC, machine-local key)
108
- - Backup files encrypted at rest (AES-256-CBC, streaming encryption)
109
- - Nothing leaves your machine no cloud, no telemetry, no accounts
110
- - Add `.oopsdb/` to `.gitignore` (already in ours)
111
-
112
- ## Pricing
113
-
114
- **Free** — Everything local. All databases, unlimited snapshots, encrypted backups. No limits, no accounts, no strings attached.
115
-
116
- **Secure** ($9/mo) Immutable cloud backups that even a rogue AI can't delete. Local backups are great until the AI decides to `rm -rf .oopsdb/`. `oopsdb secure` pushes encrypted snapshots to tamper-proof cloud storage with write-once retention policies. Even if your entire machine gets wiped, your backups survive.
117
-
118
- Learn more at [oopsdb.com](https://oopsdb.com).
119
-
120
- ## Support
121
-
122
- If OopsDB saved your database (and your afternoon), consider buying me a coffee!
123
- [ko-fi.com/pintayo](https://ko-fi.com/pintayo)
124
-
125
- ## License
126
-
127
- MIT
1
+ # OopsDB
2
+
3
+ **Don't let AI nuke your database.**
4
+
5
+ Auto-backup and 1-click restore for developers using Claude Code, Cursor, Windsurf, and other AI coding agents.
6
+
7
+ <p align="center">
8
+ <img src="website/assets/oopsdb_demo.gif" alt="OopsDB Demo Screen Recording" style="max-width: 100%; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1)">
9
+ </p>
10
+
11
+ ---
12
+
13
+ ## The Problem
14
+
15
+ You're vibing. Claude Code is cranking through tasks. Then it decides the fastest way to fix a migration is `DROP TABLE users`. Or it runs `DELETE FROM orders` without a `WHERE` clause. Or it helpfully "cleans up" your SQLite file.
16
+
17
+ Your data is gone. Your afternoon is gone. Your will to live is negotiable.
18
+
19
+ ## The Fix
20
+
21
+ ```bash
22
+ npm install -g oopsdb
23
+ oopsdb init # connect your DB (Supabase, Postgres, MySQL, SQLite)
24
+ oopsdb watch # auto-backup every 5 min
25
+ # ... AI nukes your DB ...
26
+ oopsdb restore # pick a snapshot, roll back instantly
27
+ ```
28
+
29
+ That's it. Three commands. Your data survives the AI apocalypse.
30
+
31
+ ## What It Does
32
+
33
+ - **Auto-backups** on a timer (`oopsdb watch`) — set it and forget it
34
+ - **Manual snapshots** (`oopsdb snapshot`) — before risky migrations or YOLO prompts
35
+ - **Interactive restore** (`oopsdb restore`) — pick any snapshot, roll back in seconds
36
+ - **Safety snapshots** — automatically backs up your current state before restoring, so you can't oops your oops
37
+ - **Encrypted at rest** — AES-256-CBC encryption on every backup file
38
+ - **Zero cloud, zero accounts** — everything stays on your machine
39
+ - **Streaming backups** — near-zero memory footprint regardless of DB size
40
+
41
+ ## Supported Databases
42
+
43
+ | Database | Backup Tool | Restore Tool |
44
+ |----------|------------|--------------|
45
+ | **Supabase** | `pg_dump` (with Supabase flags) | `psql` |
46
+ | PostgreSQL (Neon, local, other hosted) | `pg_dump` | `psql` |
47
+ | MySQL / MariaDB | `mysqldump` | `mysql` |
48
+ | SQLite | `sqlite3` | `sqlite3` |
49
+
50
+ ### Supabase (first-class support)
51
+
52
+ OopsDB has dedicated Supabase support. Just paste your connection string:
53
+
54
+ ```bash
55
+ oopsdb init
56
+ # → Select "Supabase"
57
+ # → Paste your connection string from Supabase Dashboard → Settings → Database
58
+ # → Done. SSL and Supabase-specific pg_dump flags are handled automatically.
59
+ ```
60
+
61
+ Supabase-specific flags applied automatically: `--no-owner`, `--no-privileges`, `--no-subscriptions`, `sslmode=require`.
62
+
63
+ ## Commands
64
+
65
+ ```
66
+ oopsdb init Set up your database connection
67
+ oopsdb watch Auto-backup every 5 minutes
68
+ oopsdb watch -i 1 Auto-backup every 1 minute (paranoid mode)
69
+ oopsdb snapshot One-time manual backup
70
+ oopsdb shield Active query interceptor (blocks DROP/DELETE)
71
+ oopsdb restore Interactive restore from any snapshot
72
+ oopsdb status View backup history and stats
73
+ oopsdb secure Immutable cloud backups
74
+ oopsdb activate <key> Activate a Secure license
75
+ oopsdb deactivate Deactivate your license on this machine
76
+ oopsdb license Show current license status
77
+ oopsdb lock Lock schema with Postgres event triggers
78
+ oopsdb unlock Temporarily unlock schema for migrations
79
+ oopsdb clean Remove all OopsDB data from project
80
+ ```
81
+
82
+ ## The Shield (Query Interceptor)
83
+
84
+ OopsDB Shield acts as a proxy between your application and your database. It monitors incoming SQL traffic and automatically takes a safety snapshot if it detects destructive commands like `DROP TABLE` or `DELETE` without a `WHERE` clause.
85
+
86
+ ```bash
87
+ oopsdb shield --port 5433
88
+ # Now connect your app to port 5433 instead of the default DB port.
89
+ ```
90
+
91
+ ## Antigravity (PostgreSQL / Supabase)
92
+
93
+ OopsDB Antigravity provides database-level protection using native Postgres event triggers. Even if an AI agent bypasses the OopsDB proxy or connects directly to the DB, destructive DDL commands will be blocked at the database level.
94
+
95
+ - `oopsdb lock`: Creates a trigger that blocks `DROP`, `ALTER`, and `TRUNCATE` operations.
96
+ - `oopsdb unlock`: Takes a safety snapshot, drops the trigger, and allows schema modifications for 60 seconds before automatically re-locking.
97
+
98
+ ## How It Works
99
+
100
+ 1. `oopsdb init` walks you through connecting your database. Credentials are encrypted and saved locally in `.oopsdb/config.json`.
101
+ 2. `oopsdb watch` runs the native dump tool (`pg_dump`, `mysqldump`, or `sqlite3 .backup`) at your chosen interval. Output is streamed through AES-256-CBC encryption directly to disk — memory usage stays flat even for large databases.
102
+ 3. `oopsdb restore` shows your snapshots with timestamps and sizes. Pick one, confirm, and your database is rolled back. It takes a safety snapshot first, so you can always undo the undo.
103
+
104
+ ## Quick Demo
105
+
106
+ Want to see OopsDB in action without touching your real database? We built a safe demo playground just for you.
107
+
108
+ 1. Clone or download this repository.
109
+ 2. Navigate to the `demo/` folder: `cd demo`
110
+ 3. Generate the dummy database: `sqlite3 test.db < seed.sql`
111
+ 4. Initialize OopsDB (it won't affect your global config because `.oopsdb` is gitignored here): `oopsdb init`
112
+ 5. Try running `oopsdb shield` and firing a `DROP TABLE users` against it to watch the interceptor catch the query!
113
+
114
+ ## Requirements
115
+
116
+ Your system needs the native database CLI tools:
117
+
118
+ - **PostgreSQL**: `pg_dump` + `psql`
119
+ - **MySQL**: `mysqldump` + `mysql`
120
+ - **SQLite**: `sqlite3`
121
+
122
+ OopsDB checks for these on `init` and gives install instructions if they're missing.
123
+
124
+ ## Security
125
+
126
+ - Credentials encrypted at rest (AES-256-CBC, machine-local key)
127
+ - Backup files encrypted at rest (AES-256-CBC, streaming encryption)
128
+ - Nothing leaves your machine — no cloud, no telemetry, no accounts
129
+ - Add `.oopsdb/` to `.gitignore` (already in ours)
130
+
131
+ ## Pricing
132
+
133
+ **Free** — Everything local. All databases, unlimited snapshots, encrypted backups. No limits, no accounts, no strings attached.
134
+
135
+ **Secure** ($9/mo) — Immutable cloud backups that even a rogue AI can't delete. Local backups are great until the AI decides to `rm -rf .oopsdb/`. `oopsdb secure` pushes encrypted snapshots to tamper-proof cloud storage with write-once retention policies. Even if your entire machine gets wiped, your backups survive.
136
+
137
+ Learn more at [oopsdb.com](https://oopsdb.com).
138
+
139
+ ## Support
140
+
141
+ If OopsDB saved your database (and your afternoon), consider buying me a coffee!
142
+ [ko-fi.com/pintayo](https://ko-fi.com/pintayo)
143
+
144
+ ## License
145
+
146
+ MIT
@@ -0,0 +1 @@
1
+ export declare function lockCommand(): Promise<void>;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.lockCommand = lockCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const config_1 = require("../utils/config");
10
+ const psql_1 = require("../utils/psql");
11
+ async function lockCommand() {
12
+ const config = (0, config_1.loadConfig)();
13
+ if (!config) {
14
+ console.log(chalk_1.default.red('\n No config found. Run `oopsdb init` first.\n'));
15
+ process.exit(1);
16
+ return;
17
+ }
18
+ if (config.db.type !== 'postgres') {
19
+ console.log(chalk_1.default.yellow('\n OopsDB Antigravity (Lock/Unlock) is currently only supported for PostgreSQL (including Supabase).\n'));
20
+ return;
21
+ }
22
+ console.log(chalk_1.default.bold('\n OopsDB Antigravity ' + chalk_1.default.green('LOCK')));
23
+ console.log(chalk_1.default.gray(` Securing ${config.db.database} at ${config.db.host || 'localhost'}...\n`));
24
+ const spinner = (0, ora_1.default)('Engaging database-level event triggers...').start();
25
+ const sql = `
26
+ CREATE OR REPLACE FUNCTION oopsdb_block_ddl()
27
+ RETURNS event_trigger AS $$
28
+ BEGIN
29
+ RAISE EXCEPTION 'OopsDB Antigravity Lock is ACTIVE. Schema changes (DROP, ALTER, etc.) are blocked. Run \`oopsdb unlock\` to modify the schema.';
30
+ END;
31
+ $$ LANGUAGE plpgsql;
32
+
33
+ DROP EVENT TRIGGER IF EXISTS oopsdb_protect_schema;
34
+
35
+ CREATE EVENT TRIGGER oopsdb_protect_schema
36
+ ON ddl_command_start
37
+ EXECUTE FUNCTION oopsdb_block_ddl();
38
+ `;
39
+ try {
40
+ await (0, psql_1.runPsqlCommand)(config.db, sql);
41
+ spinner.succeed('Antigravity Lock engaged.');
42
+ console.log(chalk_1.default.green('\n Your database schema is now bulletproof.'));
43
+ console.log(chalk_1.default.gray(' Any attempt to DROP, ALTER, or TRUNCATE will be rejected by the database engine itself.'));
44
+ console.log(chalk_1.default.gray(' Need to run migrations? Use ') + chalk_1.default.cyan('oopsdb unlock') + chalk_1.default.gray(' first.\n'));
45
+ }
46
+ catch (err) {
47
+ spinner.fail(`Failed to engage lock: ${err.message}`);
48
+ // Suggest that they might need superuser depending on the environment
49
+ console.log(chalk_1.default.gray('\n Note: Creating event triggers in Postgres requires superuser privileges.'));
50
+ console.log(chalk_1.default.gray(' Ensure the user configured in OopsDB has sufficient permissions.\n'));
51
+ }
52
+ }
@@ -42,6 +42,7 @@ const ora_1 = __importDefault(require("ora"));
42
42
  const fs = __importStar(require("fs"));
43
43
  const path = __importStar(require("path"));
44
44
  const config_1 = require("../utils/config");
45
+ const license_1 = require("../utils/license");
45
46
  const dumper_1 = require("../utils/dumper");
46
47
  async function secureCommand(options) {
47
48
  console.log(chalk_1.default.bold('\n OopsDB Secure'));
@@ -50,7 +51,13 @@ async function secureCommand(options) {
50
51
  if (!config) {
51
52
  console.log(chalk_1.default.red(' No config found. Run `oopsdb init` first.\n'));
52
53
  process.exit(1);
53
- return; // Keeps TS happy
54
+ return;
55
+ }
56
+ const license = (0, license_1.loadLicense)();
57
+ if (!license || license.tier !== 'secure') {
58
+ console.log(chalk_1.default.yellow(' Secure features require a Secure license.'));
59
+ console.log(chalk_1.default.gray(' Get one at ') + chalk_1.default.cyan('https://oopsdb.com\n'));
60
+ return;
54
61
  }
55
62
  // Find the latest snapshot
56
63
  const snapshots = (0, dumper_1.listSnapshots)();
@@ -63,14 +70,13 @@ async function secureCommand(options) {
63
70
  const fileSizeInBytes = fs.statSync(latestFile).size;
64
71
  const fileSizeMB = (fileSizeInBytes / (1024 * 1024)).toFixed(2);
65
72
  console.log(chalk_1.default.blue(` Found latest snapshot: ${fileName} (${fileSizeMB} MB)`));
66
- // In production, hit the live endpoint. If testing locally, hit the wrangler dev server.
67
73
  const baseUrl = process.env.TEST_LOCAL_API ? 'http://localhost:8788' : 'https://oopsdb.com';
68
- console.log(chalk_1.default.gray(` Requesting secure upload token from ${baseUrl}...\n`));
74
+ console.log(chalk_1.default.gray(` Requesting secure upload token...\n`));
69
75
  let actualUploadUrl = '';
70
76
  try {
71
77
  const res = await fetch(`${baseUrl}/api/upload-url?fileName=${fileName}`, {
72
78
  headers: {
73
- 'Authorization': 'Bearer oops_sec_9f8b2c7d4e5a1b3c8f9d0e2a5b7c8d9e'
79
+ 'Authorization': `Bearer ${license.licenseKey}`
74
80
  }
75
81
  });
76
82
  if (!res.ok) {
@@ -84,7 +90,6 @@ async function secureCommand(options) {
84
90
  }
85
91
  catch (err) {
86
92
  console.log(chalk_1.default.red(` Network error reaching backend: ${err.message}\n`));
87
- console.log(chalk_1.default.yellow(` Tip: If testing locally, ensure you ran 'npx wrangler pages dev website' first and set TEST_LOCAL_API=1\n`));
88
93
  return;
89
94
  }
90
95
  await uploadToS3(latestFile, actualUploadUrl);
@@ -104,9 +109,8 @@ async function uploadToS3(filePath, uploadUrl) {
104
109
  }
105
110
  });
106
111
  if (!response.ok) {
107
- spinner.fail(`Upload intercepted for MVP. The pre-signed URL must be provided by the backend.`);
108
- console.log(chalk_1.default.yellow(` Reason: ${response.status} ${response.statusText}\n`));
109
- console.log(chalk_1.default.gray(` (This is expected until the Cloudflare backend endpoint is reachable)\n`));
112
+ spinner.fail(`Upload failed: ${response.status} ${response.statusText}`);
113
+ console.log(chalk_1.default.gray(` Check your connection or license status.\n`));
110
114
  }
111
115
  else {
112
116
  spinner.succeed('Snapshot safely stored in immutable S3 Cloud Vault.');
@@ -115,6 +119,5 @@ async function uploadToS3(filePath, uploadUrl) {
115
119
  }
116
120
  catch (err) {
117
121
  spinner.fail(`Upload failed: ${err.message}`);
118
- console.log(chalk_1.default.gray(` (Ensure the backend feature is fully implemented to receive a valid S3 URL)\n`));
119
122
  }
120
123
  }
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.shieldCommand = shieldCommand;
40
40
  const net = __importStar(require("net"));
41
+ const tls = __importStar(require("tls"));
41
42
  const chalk_1 = __importDefault(require("chalk"));
42
43
  const ora_1 = __importDefault(require("ora"));
43
44
  const config_1 = require("../utils/config");
@@ -52,14 +53,30 @@ async function shieldCommand(options) {
52
53
  const proxyPort = parseInt(options.port || '5433', 10);
53
54
  const targetPort = config.db.port || (config.db.type === 'postgres' ? 5432 : 3306);
54
55
  const targetHost = config.db.host || 'localhost';
56
+ const isSsl = config.db.supabase || config.db.sslmode === 'require';
55
57
  console.log(chalk_1.default.bold('\n OopsDB Shield ' + chalk_1.default.green('ACTIVE')));
56
58
  console.log(chalk_1.default.gray(` Listening on port ${proxyPort} -> Forwarding to ${config.db.type} on ${targetPort}\n`));
59
+ if (isSsl) {
60
+ console.log(chalk_1.default.yellow(' [SSL Detected] Proxy running in Pass-Through mode.'));
61
+ console.log(chalk_1.default.gray(' Deep-packet inspection (destructive command detection) is DISABLED.'));
62
+ console.log(chalk_1.default.gray(' To enable interception on SSL connections, local certificates/MITM are required.\n'));
63
+ }
57
64
  const DESTRUCTIVE_REGEX = /(DROP\s+TABLE|TRUNCATE\s+TABLE|DELETE\s+FROM)/i;
58
65
  const server = net.createServer((clientSocket) => {
59
- const targetSocket = net.createConnection({
60
- host: targetHost,
61
- port: targetPort
62
- });
66
+ let targetSocket;
67
+ if (isSsl) {
68
+ targetSocket = tls.connect({
69
+ host: targetHost,
70
+ port: targetPort,
71
+ servername: targetHost
72
+ });
73
+ }
74
+ else {
75
+ targetSocket = net.createConnection({
76
+ host: targetHost,
77
+ port: targetPort
78
+ });
79
+ }
63
80
  targetSocket.on('error', (err) => {
64
81
  console.log(chalk_1.default.red(`\n Database connection error: ${err.message}`));
65
82
  clientSocket.destroy(err);
@@ -71,6 +88,12 @@ async function shieldCommand(options) {
71
88
  targetSocket.pipe(clientSocket);
72
89
  // Handle traffic from Client to DB (with interception)
73
90
  clientSocket.on('data', async (chunk) => {
91
+ // If SSL is active, we bypass deep-packet inspection because the traffic is encrypted
92
+ // (or we are acting as a simple TLS pass-through for the application)
93
+ if (isSsl) {
94
+ targetSocket.write(chunk);
95
+ return;
96
+ }
74
97
  const dataString = chunk.toString();
75
98
  if (DESTRUCTIVE_REGEX.test(dataString)) {
76
99
  // Destructive command detected!
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.statusCommand = statusCommand;
40
40
  const chalk_1 = __importDefault(require("chalk"));
41
41
  const config_1 = require("../utils/config");
42
+ const license_1 = require("../utils/license");
42
43
  const dumper_1 = require("../utils/dumper");
43
44
  const path = __importStar(require("path"));
44
45
  function formatSize(bytes) {
@@ -74,6 +75,12 @@ async function statusCommand() {
74
75
  console.log(` Host: ${chalk_1.default.cyan(config.db.host + ':' + config.db.port)}`);
75
76
  }
76
77
  console.log(` Since: ${chalk_1.default.cyan(new Date(config.createdAt).toLocaleDateString())}`);
78
+ const license = (0, license_1.loadLicense)();
79
+ const tierLabel = license ? license.tier.toUpperCase() : 'FREE';
80
+ console.log(` Plan: ${chalk_1.default.green(tierLabel)}`);
81
+ if (tierLabel === 'FREE') {
82
+ console.log(chalk_1.default.yellow(' ⚠️ Local snapshots only. Rogue AI or disk failure could wipe these.'));
83
+ }
77
84
  console.log();
78
85
  console.log(chalk_1.default.gray(' Backups'));
79
86
  console.log(` Location: ${chalk_1.default.cyan((0, config_1.getBackupsDir)())}`);
@@ -0,0 +1 @@
1
+ export declare function unlockCommand(): Promise<void>;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.unlockCommand = unlockCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const config_1 = require("../utils/config");
10
+ const dumper_1 = require("../utils/dumper");
11
+ const psql_1 = require("../utils/psql");
12
+ async function unlockCommand() {
13
+ const config = (0, config_1.loadConfig)();
14
+ if (!config) {
15
+ console.log(chalk_1.default.red('\n No config found. Run `oopsdb init` first.\n'));
16
+ process.exit(1);
17
+ return;
18
+ }
19
+ if (config.db.type !== 'postgres') {
20
+ console.log(chalk_1.default.yellow('\n OopsDB Antigravity (Lock/Unlock) is currently only supported for PostgreSQL (including Supabase).\n'));
21
+ return;
22
+ }
23
+ console.log(chalk_1.default.bold('\n OopsDB Antigravity ' + chalk_1.default.yellow('UNLOCK')));
24
+ // 1. Take a snapshot
25
+ const snapSpinner = (0, ora_1.default)('Securing a fresh snapshot before unlocking...').start();
26
+ try {
27
+ await (0, dumper_1.createSnapshot)(config.db);
28
+ snapSpinner.succeed('Fresh safety snapshot secured.');
29
+ }
30
+ catch (err) {
31
+ snapSpinner.fail(`Failed to take safety snapshot: ${err.message}`);
32
+ console.log(chalk_1.default.red('\n Aborting unlock. We must secure your data first.\n'));
33
+ return;
34
+ }
35
+ // 2. Drop the trigger
36
+ const sql = `DROP EVENT TRIGGER IF EXISTS oopsdb_protect_schema;`;
37
+ const unlockSpinner = (0, ora_1.default)('Dropping Antigravity locks...').start();
38
+ try {
39
+ await (0, psql_1.runPsqlCommand)(config.db, sql);
40
+ unlockSpinner.succeed('Lock removed.');
41
+ console.log(chalk_1.default.bgYellow.black.bold('\n WARNING ') + chalk_1.default.yellow(' Schema modifications are now ALLOWED.'));
42
+ console.log(chalk_1.default.gray(' You have 60 seconds to run your migrations.'));
43
+ // 3. Setup re-lock timeout
44
+ let timer = 60;
45
+ const interval = setInterval(() => {
46
+ process.stdout.write(`\r ${chalk_1.default.cyan(timer)} seconds remaining...`);
47
+ timer--;
48
+ if (timer < 0) {
49
+ clearInterval(interval);
50
+ process.stdout.write('\n');
51
+ reLock(config.db);
52
+ }
53
+ }, 1000);
54
+ }
55
+ catch (err) {
56
+ unlockSpinner.fail(`Failed to remove lock: ${err.message}`);
57
+ }
58
+ }
59
+ async function reLock(dbConfig) {
60
+ const relockSpinner = (0, ora_1.default)('Time is up. Re-engaging Antigravity Lock...').start();
61
+ const sql = `
62
+ CREATE EVENT TRIGGER oopsdb_protect_schema
63
+ ON ddl_command_start
64
+ EXECUTE FUNCTION oopsdb_block_ddl();
65
+ `;
66
+ try {
67
+ await (0, psql_1.runPsqlCommand)(dbConfig, sql);
68
+ relockSpinner.succeed('Antigravity Lock re-engaged.');
69
+ console.log(chalk_1.default.green('\n Your database schema is bulletproof again.\n'));
70
+ process.exit(0);
71
+ }
72
+ catch (err) {
73
+ relockSpinner.fail(`Failed to re-engage lock: ${err.message}`);
74
+ console.log(chalk_1.default.red('\n CRITICAL WARNING: Database remains unprotected! Run `oopsdb lock` manually.\n'));
75
+ process.exit(1);
76
+ }
77
+ }
package/dist/index.js CHANGED
@@ -11,6 +11,8 @@ const secure_1 = require("./commands/secure");
11
11
  const clean_1 = require("./commands/clean");
12
12
  const activate_1 = require("./commands/activate");
13
13
  const shield_1 = require("./commands/shield");
14
+ const lock_1 = require("./commands/lock");
15
+ const unlock_1 = require("./commands/unlock");
14
16
  // Read version from package.json at runtime
15
17
  const pkg = require('../package.json');
16
18
  const program = new commander_1.Command();
@@ -45,6 +47,14 @@ program
45
47
  .description('Start a database proxy interceptor that prevents destructive queries')
46
48
  .option('-p, --port <port>', 'Proxy port', '5433')
47
49
  .action(shield_1.shieldCommand);
50
+ program
51
+ .command('lock')
52
+ .description('Bulletproof your Postgres database against schema deletion')
53
+ .action(lock_1.lockCommand);
54
+ program
55
+ .command('unlock')
56
+ .description('Temporarily allow schema migrations (auto-relocks after 60s)')
57
+ .action(unlock_1.unlockCommand);
48
58
  program
49
59
  .command('secure')
50
60
  .description('Immutable cloud backups — tamper-proof, AI-proof')
@@ -0,0 +1,2 @@
1
+ import { DbConfig } from './config';
2
+ export declare function runPsqlCommand(dbConfig: DbConfig, sql: string): Promise<void>;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runPsqlCommand = runPsqlCommand;
4
+ const child_process_1 = require("child_process");
5
+ function runPsqlCommand(dbConfig, sql) {
6
+ return new Promise((resolve, reject) => {
7
+ const env = { ...process.env };
8
+ if (dbConfig.password)
9
+ env.PGPASSWORD = dbConfig.password;
10
+ if (dbConfig.sslmode)
11
+ env.PGSSLMODE = dbConfig.sslmode;
12
+ const args = [
13
+ '-h', dbConfig.host || 'localhost',
14
+ '-p', String(dbConfig.port || 5432),
15
+ '-U', dbConfig.user || 'postgres',
16
+ '-c', sql,
17
+ dbConfig.database
18
+ ];
19
+ const child = (0, child_process_1.spawn)('psql', args, { env, stdio: ['ignore', 'pipe', 'pipe'] });
20
+ let stderr = '';
21
+ child.stderr.on('data', (chunk) => {
22
+ stderr += chunk.toString();
23
+ });
24
+ child.on('error', (err) => reject(err));
25
+ child.on('close', (code) => {
26
+ if (code !== 0) {
27
+ reject(new Error(stderr || `psql exited with code ${code}`));
28
+ }
29
+ else {
30
+ resolve();
31
+ }
32
+ });
33
+ });
34
+ }
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
- {
2
- "name": "oopsdb",
3
- "version": "1.5.0",
4
- "description": "Don't let AI nuke your database. Auto-backup and 1-click restore for developers using Claude Code, Cursor, Windsurf, and other AI coding agents.",
5
- "main": "dist/index.js",
6
- "bin": {
7
- "oopsdb": "dist/index.js"
8
- },
9
- "scripts": {
10
- "build": "tsc",
11
- "dev": "ts-node src/index.ts",
12
- "start": "node dist/index.js",
13
- "prepublishOnly": "npm run build",
14
- "test": "tsc && vitest run tests/unit.test.ts",
15
- "test:e2e": "tsc && vitest run tests/e2e.test.ts",
16
- "test:all": "tsc && vitest run"
17
- },
18
- "keywords": [
19
- "database",
20
- "backup",
21
- "restore",
22
- "rollback",
23
- "claude-code",
24
- "ai-safety",
25
- "mysql",
26
- "postgres",
27
- "supabase",
28
- "sqlite"
29
- ],
30
- "author": "",
31
- "license": "MIT",
32
- "repository": {
33
- "type": "git",
34
- "url": "git+https://github.com/pintayo/oopsdb.git"
35
- },
36
- "homepage": "https://oopsdb.com",
37
- "engines": {
38
- "node": ">=16.0.0"
39
- },
40
- "files": [
41
- "dist",
42
- "README.md",
43
- "LICENSE"
44
- ],
45
- "dependencies": {
46
- "@aws-sdk/client-s3": "^3.1005.0",
47
- "@aws-sdk/s3-request-presigner": "^3.1005.0",
48
- "chalk": "^4.1.2",
49
- "commander": "^12.1.0",
50
- "inquirer": "^8.2.6",
51
- "ora": "^5.4.1"
52
- },
53
- "devDependencies": {
54
- "@cloudflare/workers-types": "^4.20260310.1",
55
- "@types/inquirer": "^8.2.10",
56
- "@types/node": "^22.0.0",
57
- "dotenv": "^17.3.1",
58
- "testcontainers": "^11.12.0",
59
- "typescript": "^5.7.0",
60
- "vitest": "^4.0.18"
61
- },
62
- "directories": {
63
- "test": "tests"
64
- },
65
- "types": "./dist/index.d.ts",
66
- "bugs": {
67
- "url": "https://github.com/pintayo/oopsdb/issues"
68
- }
69
- }
1
+ {
2
+ "name": "oopsdb",
3
+ "version": "1.6.0",
4
+ "description": "Don't let AI nuke your database. Auto-backup and 1-click restore for developers using Claude Code, Cursor, Windsurf, and other AI coding agents.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "oopsdb": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "ts-node src/index.ts",
12
+ "start": "node dist/index.js",
13
+ "prepublishOnly": "npm run build",
14
+ "test": "tsc && vitest run tests/unit.test.ts",
15
+ "test:e2e": "tsc && vitest run tests/e2e.test.ts",
16
+ "test:all": "tsc && vitest run"
17
+ },
18
+ "keywords": [
19
+ "database",
20
+ "backup",
21
+ "restore",
22
+ "rollback",
23
+ "claude-code",
24
+ "ai-safety",
25
+ "mysql",
26
+ "postgres",
27
+ "supabase",
28
+ "sqlite"
29
+ ],
30
+ "author": "",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/pintayo/oopsdb.git"
35
+ },
36
+ "homepage": "https://oopsdb.com",
37
+ "engines": {
38
+ "node": ">=16.0.0"
39
+ },
40
+ "files": [
41
+ "dist",
42
+ "README.md",
43
+ "LICENSE"
44
+ ],
45
+ "dependencies": {
46
+ "@aws-sdk/client-s3": "^3.1005.0",
47
+ "@aws-sdk/s3-request-presigner": "^3.1005.0",
48
+ "chalk": "^4.1.2",
49
+ "commander": "^12.1.0",
50
+ "inquirer": "^8.2.6",
51
+ "ora": "^5.4.1"
52
+ },
53
+ "devDependencies": {
54
+ "@cloudflare/workers-types": "^4.20260310.1",
55
+ "@types/inquirer": "^8.2.10",
56
+ "@types/node": "^22.0.0",
57
+ "dotenv": "^17.3.1",
58
+ "testcontainers": "^11.12.0",
59
+ "typescript": "^5.7.0",
60
+ "vitest": "^4.0.18"
61
+ },
62
+ "directories": {
63
+ "test": "tests"
64
+ },
65
+ "types": "./dist/index.d.ts",
66
+ "bugs": {
67
+ "url": "https://github.com/pintayo/oopsdb/issues"
68
+ }
69
+ }