@nometria-ai/nom 0.2.9 → 0.3.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nometria-ai/nom",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "Deploy any project to any cloud from your terminal. One command, zero config.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -29,6 +29,11 @@ import { domain } from './commands/domain.js';
29
29
  import { env } from './commands/env.js';
30
30
  import { scan } from './commands/scan.js';
31
31
  import { setup } from './commands/setup.js';
32
+ import { list } from './commands/list.js';
33
+ import { rollback } from './commands/rollback.js';
34
+ import { ssh } from './commands/ssh.js';
35
+ import { db } from './commands/db.js';
36
+ import { cron } from './commands/cron.js';
32
37
 
33
38
  const { values, positionals } = parseArgs({
34
39
  allowPositionals: true,
@@ -41,6 +46,9 @@ const { values, positionals } = parseArgs({
41
46
  from: { type: 'string' },
42
47
  'api-key': { type: 'boolean', default: false },
43
48
  token: { type: 'boolean', default: false },
49
+ 'dry-run': { type: 'boolean', default: false },
50
+ preview: { type: 'boolean', default: false },
51
+ production:{ type: 'boolean', default: false },
44
52
  message: { type: 'string', short: 'm' },
45
53
  },
46
54
  strict: false,
@@ -86,6 +94,11 @@ const commands = {
86
94
  env,
87
95
  scan,
88
96
  setup,
97
+ list,
98
+ rollback,
99
+ ssh,
100
+ db,
101
+ cron,
89
102
  };
90
103
 
91
104
  const handler = commands[command];
@@ -99,12 +112,25 @@ try {
99
112
  await handler(values, positionals.slice(1));
100
113
  } catch (err) {
101
114
  if (err.code === 'ERR_AUTH') {
102
- console.error(`\n Not authenticated. Run: nom login\n`);
115
+ console.error(`\n Not authenticated. Run: nom login`);
116
+ console.error(` Get your API key at: https://nometria.com/settings/api-keys\n`);
103
117
  } else if (err.code === 'ERR_CONFIG') {
104
- console.error(`\n No nometria.json found. Run: nom init\n`);
118
+ console.error(`\n No nometria.json found. Run: nom init`);
119
+ console.error(` Docs: https://docs.nometria.com/cli/install\n`);
120
+ } else if (err.status === 402) {
121
+ console.error(`\n Payment required. Upgrade your plan:`);
122
+ console.error(` https://nometria.com/pricing\n`);
123
+ } else if (err.status === 404) {
124
+ console.error(`\n App not found. List your apps: nom list`);
125
+ console.error(` Dashboard: https://nometria.com/dashboard\n`);
126
+ } else if (err.status === 429) {
127
+ console.error(`\n Rate limited. Wait a moment and try again.\n`);
105
128
  } else {
106
- console.error(`\n Error: ${err.message}\n`);
129
+ console.error(`\n Error: ${err.message}`);
130
+ console.error(` Docs: https://docs.nometria.com`);
131
+ console.error(` Dashboard: https://nometria.com/dashboard`);
107
132
  if (process.env.NOM_DEBUG) console.error(err.stack);
133
+ console.error();
108
134
  }
109
135
  process.exit(1);
110
136
  }
@@ -121,6 +147,8 @@ function printHelp() {
121
147
  deploy Deploy to production (default)
122
148
  preview Deploy a staging preview
123
149
  status Check deployment status
150
+ list List all your apps
151
+ rollback [id] Roll back to a previous deployment
124
152
  logs [-f] View deployment logs
125
153
  login Sign in via browser (or --api-key)
126
154
  whoami Show current user
@@ -134,12 +162,21 @@ function printHelp() {
134
162
  stop Stop a running instance
135
163
  terminate Terminate instance permanently
136
164
  upgrade <size> Upgrade instance (2gb/4gb/8gb/16gb)
165
+ ssh [command] Connect to instance or run remote command
166
+
167
+ db backup Create a database backup
168
+ db restore <id> Restore from a backup
169
+ db shell Show database connection details
170
+ db migrate Run pending migrations
137
171
 
138
172
  domain add <domain> Add custom domain
139
173
  env set KEY=VALUE Set environment variables
140
174
  env list List environment variables
141
175
  env delete KEY Delete environment variable
142
176
  scan Run AI security scan
177
+ cron add <s> <cmd> Add a scheduled task
178
+ cron list List cron jobs
179
+ cron delete <id> Delete a cron job
143
180
 
144
181
  Options:
145
182
  -h, --help Show this help
@@ -148,6 +185,9 @@ function printHelp() {
148
185
  -f, --follow Follow logs in real-time
149
186
  -m, --message Commit message (for github push)
150
187
  --json Output as JSON
188
+ --dry-run Validate config and build without deploying
189
+ --preview Target preview environment (for nom env)
190
+ --production Target production environment (for nom env)
151
191
  --api-key Login with API key paste instead of browser
152
192
  --from <url> Deploy from a GitHub URL
153
193
 
@@ -0,0 +1,162 @@
1
+ /**
2
+ * nom cron — Manage scheduled tasks on deployed instances.
3
+ *
4
+ * Subcommands:
5
+ * add <schedule> <command> Add a cron job (e.g., nom cron add "0 3 * * *" "npm run cleanup")
6
+ * list List active cron jobs
7
+ * delete <id> Delete a cron job
8
+ */
9
+ import { readConfig } from '../lib/config.js';
10
+ import { requireApiKey } from '../lib/auth.js';
11
+ import { apiRequest } from '../lib/api.js';
12
+
13
+ export async function cron(flags, positionals) {
14
+ const sub = positionals[0];
15
+
16
+ switch (sub) {
17
+ case 'add':
18
+ return cronAdd(flags, positionals.slice(1));
19
+ case 'list':
20
+ case 'ls':
21
+ return cronList(flags);
22
+ case 'delete':
23
+ case 'rm':
24
+ return cronDelete(flags, positionals[1]);
25
+ default:
26
+ console.log(`
27
+ Usage: nom cron <command>
28
+
29
+ Commands:
30
+ add <schedule> <cmd> Add a cron job
31
+ list List active cron jobs
32
+ delete <id> Delete a cron job
33
+
34
+ Schedule format: standard cron expression
35
+ "0 3 * * *" Daily at 3am UTC
36
+ "*/15 * * * *" Every 15 minutes
37
+ "0 0 * * 0" Weekly on Sunday
38
+
39
+ Examples:
40
+ nom cron add "0 3 * * *" "npm run cleanup"
41
+ nom cron add "*/5 * * * *" "curl -sf https://myapp.com/health"
42
+ nom cron list
43
+ nom cron delete cron_abc123
44
+
45
+ Help: https://docs.nometria.com/deploy/environment
46
+ `);
47
+ }
48
+ }
49
+
50
+ async function cronAdd(flags, args) {
51
+ const apiKey = requireApiKey();
52
+ const config = readConfig();
53
+ const appId = config.app_id;
54
+
55
+ if (!appId) {
56
+ console.error('\n No app_id in nometria.json. Deploy first: nom deploy\n');
57
+ process.exit(1);
58
+ }
59
+
60
+ if (args.length < 2) {
61
+ console.error('\n Usage: nom cron add "<schedule>" "<command>"');
62
+ console.error(' Example: nom cron add "0 3 * * *" "npm run cleanup"\n');
63
+ process.exit(1);
64
+ }
65
+
66
+ const schedule = args[0];
67
+ const command = args.slice(1).join(' ');
68
+
69
+ // Basic cron validation
70
+ const parts = schedule.split(/\s+/);
71
+ if (parts.length < 5 || parts.length > 6) {
72
+ console.error(`\n Invalid cron schedule: "${schedule}"`);
73
+ console.error(' Expected format: "minute hour day month weekday"');
74
+ console.error(' Example: "0 3 * * *" (daily at 3am UTC)\n');
75
+ process.exit(1);
76
+ }
77
+
78
+ try {
79
+ const result = await apiRequest('/cli/cron', {
80
+ apiKey,
81
+ body: { app_id: appId, action: 'add', schedule, command },
82
+ });
83
+
84
+ console.log(`\n Cron job added: ${result.cron_id || 'pending'}`);
85
+ console.log(` Schedule: ${schedule}`);
86
+ console.log(` Command: ${command}`);
87
+ console.log(` Timezone: UTC\n`);
88
+ } catch (err) {
89
+ console.error(`\n Failed to add cron job: ${err.message}`);
90
+ console.error(' Help: https://docs.nometria.com/deploy/environment\n');
91
+ process.exit(1);
92
+ }
93
+ }
94
+
95
+ async function cronList(flags) {
96
+ const apiKey = requireApiKey();
97
+ const config = readConfig();
98
+ const appId = config.app_id;
99
+
100
+ if (!appId) {
101
+ console.error('\n No app_id in nometria.json.\n');
102
+ process.exit(1);
103
+ }
104
+
105
+ try {
106
+ const result = await apiRequest('/cli/cron', {
107
+ apiKey,
108
+ body: { app_id: appId, action: 'list' },
109
+ });
110
+
111
+ const jobs = result.jobs || [];
112
+ if (!jobs.length) {
113
+ console.log('\n No cron jobs configured. Add one: nom cron add "0 3 * * *" "npm run cleanup"\n');
114
+ return;
115
+ }
116
+
117
+ if (flags.json) {
118
+ console.log(JSON.stringify(jobs, null, 2));
119
+ return;
120
+ }
121
+
122
+ console.log(`\n Cron jobs (${jobs.length}):\n`);
123
+ for (const job of jobs) {
124
+ console.log(` ${job.id || '—'}`);
125
+ console.log(` Schedule: ${job.schedule}`);
126
+ console.log(` Command: ${job.command}`);
127
+ if (job.last_run) console.log(` Last run: ${job.last_run}`);
128
+ console.log();
129
+ }
130
+ } catch (err) {
131
+ console.error(`\n Failed to list cron jobs: ${err.message}\n`);
132
+ process.exit(1);
133
+ }
134
+ }
135
+
136
+ async function cronDelete(flags, cronId) {
137
+ const apiKey = requireApiKey();
138
+ const config = readConfig();
139
+ const appId = config.app_id;
140
+
141
+ if (!appId) {
142
+ console.error('\n No app_id in nometria.json.\n');
143
+ process.exit(1);
144
+ }
145
+
146
+ if (!cronId) {
147
+ console.error('\n Usage: nom cron delete <cron_id>');
148
+ console.error(' List jobs first: nom cron list\n');
149
+ process.exit(1);
150
+ }
151
+
152
+ try {
153
+ await apiRequest('/cli/cron', {
154
+ apiKey,
155
+ body: { app_id: appId, action: 'delete', cron_id: cronId },
156
+ });
157
+ console.log(`\n Cron job ${cronId} deleted.\n`);
158
+ } catch (err) {
159
+ console.error(`\n Failed to delete cron job: ${err.message}\n`);
160
+ process.exit(1);
161
+ }
162
+ }
@@ -0,0 +1,267 @@
1
+ /**
2
+ * nom db — Database management commands.
3
+ *
4
+ * Subcommands:
5
+ * backup Create a database backup
6
+ * restore <id> Restore from a backup
7
+ * shell Show connection instructions
8
+ * migrate Run pending migrations
9
+ */
10
+ import { readConfig } from '../lib/config.js';
11
+ import { requireApiKey } from '../lib/auth.js';
12
+ import { apiRequest } from '../lib/api.js';
13
+
14
+ export async function db(flags, positionals) {
15
+ const sub = positionals[0];
16
+
17
+ switch (sub) {
18
+ case 'backup':
19
+ return dbBackup(flags);
20
+ case 'restore':
21
+ return dbRestore(flags, positionals[1]);
22
+ case 'shell':
23
+ return dbShell(flags);
24
+ case 'migrate':
25
+ return dbMigrate(flags);
26
+ case 'ownership':
27
+ case 'own':
28
+ return dbOwnership(flags);
29
+ default:
30
+ console.log(`
31
+ Usage: nom db <command>
32
+
33
+ Commands:
34
+ backup Create a database backup
35
+ restore <id> Restore from a backup
36
+ shell Show database connection details
37
+ migrate Run pending migrations
38
+ ownership Transfer database to your own Supabase project
39
+
40
+ Help: https://docs.nometria.com/deploy/environment
41
+ `);
42
+ }
43
+ }
44
+
45
+ async function dbBackup(flags) {
46
+ const apiKey = requireApiKey();
47
+ const config = readConfig();
48
+ const appId = config.app_id;
49
+
50
+ if (!appId) {
51
+ console.error('\n No app_id in nometria.json. Deploy first: nom deploy\n');
52
+ process.exit(1);
53
+ }
54
+
55
+ console.log('\n Creating database backup...\n');
56
+ try {
57
+ const result = await apiRequest('/cli/db', {
58
+ apiKey,
59
+ body: { app_id: appId, action: 'backup' },
60
+ });
61
+
62
+ if (flags.json) {
63
+ console.log(JSON.stringify(result, null, 2));
64
+ return;
65
+ }
66
+
67
+ console.log(` Backup created: ${result.backup_id || 'pending'}`);
68
+ if (result.size) console.log(` Size: ${result.size}`);
69
+ if (result.s3_path) console.log(` Stored: ${result.s3_path}`);
70
+ console.log();
71
+ } catch (err) {
72
+ console.error(`\n Backup failed: ${err.message}`);
73
+ console.error(' This may require the app to have a running database.');
74
+ console.error(' Help: https://docs.nometria.com/deploy/environment\n');
75
+ process.exit(1);
76
+ }
77
+ }
78
+
79
+ async function dbRestore(flags, backupId) {
80
+ const apiKey = requireApiKey();
81
+ const config = readConfig();
82
+ const appId = config.app_id;
83
+
84
+ if (!appId) {
85
+ console.error('\n No app_id in nometria.json.\n');
86
+ process.exit(1);
87
+ }
88
+
89
+ if (!backupId) {
90
+ // List available backups
91
+ console.log('\n Fetching available backups...\n');
92
+ try {
93
+ const result = await apiRequest('/cli/db', {
94
+ apiKey,
95
+ body: { app_id: appId, action: 'list_backups' },
96
+ });
97
+ const backups = result.backups || [];
98
+ if (!backups.length) {
99
+ console.log(' No backups found. Create one: nom db backup\n');
100
+ return;
101
+ }
102
+ console.log(' Available backups:\n');
103
+ for (const b of backups) {
104
+ console.log(` ${b.id} ${b.created_at || '—'} ${b.size || '—'}`);
105
+ }
106
+ console.log('\n Restore: nom db restore <backup_id>\n');
107
+ } catch (err) {
108
+ console.error(`\n Failed to list backups: ${err.message}\n`);
109
+ }
110
+ return;
111
+ }
112
+
113
+ console.log(`\n Restoring from backup: ${backupId}...\n`);
114
+ try {
115
+ const result = await apiRequest('/cli/db', {
116
+ apiKey,
117
+ body: { app_id: appId, action: 'restore', backup_id: backupId },
118
+ });
119
+ console.log(` Restore ${result.success ? 'completed' : 'failed'}.`);
120
+ if (result.message) console.log(` ${result.message}`);
121
+ console.log();
122
+ } catch (err) {
123
+ console.error(`\n Restore failed: ${err.message}\n`);
124
+ process.exit(1);
125
+ }
126
+ }
127
+
128
+ async function dbShell(flags) {
129
+ const apiKey = requireApiKey();
130
+ const config = readConfig();
131
+ const appId = config.app_id;
132
+
133
+ if (!appId) {
134
+ console.error('\n No app_id in nometria.json.\n');
135
+ process.exit(1);
136
+ }
137
+
138
+ // Get instance details for connection info
139
+ try {
140
+ const result = await apiRequest('/checkAwsStatus', {
141
+ apiKey,
142
+ body: { app_id: appId },
143
+ });
144
+ const ip = result.data?.ipAddress;
145
+
146
+ console.log(`\n Database connection for: ${appId}\n`);
147
+ console.log(' Via SSH tunnel (recommended):');
148
+ if (ip) {
149
+ console.log(` ssh -L 5432:localhost:5432 ubuntu@${ip}`);
150
+ console.log(' psql postgresql://postgres:postgres@localhost:5432/postgres\n');
151
+ }
152
+ console.log(' Via SSM:');
153
+ const instanceId = result.data?.instanceId;
154
+ if (instanceId) {
155
+ console.log(` aws ssm start-session --target ${instanceId} \\`);
156
+ console.log(' --document-name AWS-StartPortForwardingSession \\');
157
+ console.log(" --parameters '{\"portNumber\":[\"5432\"],\"localPortNumber\":[\"5432\"]}'\n");
158
+ }
159
+ console.log(' Direct on instance:');
160
+ console.log(' psql postgresql://postgres:postgres@localhost:5432/postgres\n');
161
+ } catch (err) {
162
+ console.error(`\n Could not fetch instance details: ${err.message}\n`);
163
+ process.exit(1);
164
+ }
165
+ }
166
+
167
+ async function dbMigrate(flags) {
168
+ const apiKey = requireApiKey();
169
+ const config = readConfig();
170
+ const appId = config.app_id;
171
+
172
+ if (!appId) {
173
+ console.error('\n No app_id in nometria.json.\n');
174
+ process.exit(1);
175
+ }
176
+
177
+ console.log('\n Running database migrations...\n');
178
+ try {
179
+ const result = await apiRequest('/cli/db', {
180
+ apiKey,
181
+ body: { app_id: appId, action: 'migrate' },
182
+ });
183
+ console.log(` ${result.message || 'Migrations complete.'}\n`);
184
+ } catch (err) {
185
+ console.error(`\n Migration failed: ${err.message}`);
186
+ console.error(' Check migration files in db/seeds/ or schema.sql\n');
187
+ process.exit(1);
188
+ }
189
+ }
190
+
191
+ async function dbOwnership(flags) {
192
+ const apiKey = requireApiKey();
193
+ const config = readConfig();
194
+ const appId = config.app_id;
195
+
196
+ if (!appId) {
197
+ console.error('\n No app_id in nometria.json.\n');
198
+ process.exit(1);
199
+ }
200
+
201
+ console.log(`
202
+ Database Ownership Transfer
203
+ ═══════════════════════════
204
+
205
+ This transfers your app's database to a Supabase project YOU own.
206
+
207
+ What happens:
208
+ 1. Export schema + data from current managed database
209
+ 2. Create a new Supabase project under your account
210
+ 3. Import schema + data into your project
211
+ 4. Update your app's .env to point to your Supabase
212
+ 5. Resync the app to use the new database
213
+
214
+ After transfer:
215
+ - You own the Supabase project directly
216
+ - You control backups, scaling, and access
217
+ - Nometria no longer manages the database
218
+ - Your app keeps running with no downtime
219
+ `);
220
+
221
+ try {
222
+ console.log(' Step 1: Exporting current database...');
223
+ const exportResult = await apiRequest('/cli/db', {
224
+ apiKey,
225
+ body: { app_id: appId, action: 'export_schema' },
226
+ });
227
+ console.log(` Exported: ${exportResult.tables || '?'} tables, ${exportResult.rows || '?'} rows`);
228
+
229
+ console.log(' Step 2: Creating your Supabase project...');
230
+ const createResult = await apiRequest('/cli/db', {
231
+ apiKey,
232
+ body: { app_id: appId, action: 'create_owned_supabase' },
233
+ });
234
+ const supaUrl = createResult.supabase_url;
235
+ console.log(` Supabase project: ${supaUrl || 'pending'}`);
236
+
237
+ console.log(' Step 3: Importing schema + data...');
238
+ const importResult = await apiRequest('/cli/db', {
239
+ apiKey,
240
+ body: { app_id: appId, action: 'import_to_owned', supabase_url: supaUrl },
241
+ });
242
+ console.log(` Imported: ${importResult.message || 'complete'}`);
243
+
244
+ console.log(' Step 4: Updating app configuration...');
245
+ const updateResult = await apiRequest('/cli/db', {
246
+ apiKey,
247
+ body: { app_id: appId, action: 'switch_to_owned', supabase_url: supaUrl },
248
+ });
249
+ console.log(` ${updateResult.message || 'Configuration updated'}`);
250
+
251
+ console.log(`
252
+ Database ownership transferred!
253
+
254
+ Your Supabase project: ${supaUrl || 'See Supabase dashboard'}
255
+ Dashboard: https://supabase.com/dashboard
256
+ App: https://nometria.com/AppDetails?app_id=${appId}
257
+
258
+ Your app is now using YOUR Supabase project.
259
+ You have full control over backups, scaling, and access.
260
+ `);
261
+ } catch (err) {
262
+ console.error(`\n Ownership transfer failed: ${err.message}`);
263
+ console.error(' This feature requires a running instance with Supabase.');
264
+ console.error(' Help: https://docs.nometria.com/deploy/environment\n');
265
+ process.exit(1);
266
+ }
267
+ }