lsh-framework 0.5.4

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 (90) hide show
  1. package/.env.example +51 -0
  2. package/README.md +399 -0
  3. package/dist/app.js +33 -0
  4. package/dist/cicd/analytics.js +261 -0
  5. package/dist/cicd/auth.js +269 -0
  6. package/dist/cicd/cache-manager.js +172 -0
  7. package/dist/cicd/data-retention.js +305 -0
  8. package/dist/cicd/performance-monitor.js +224 -0
  9. package/dist/cicd/webhook-receiver.js +634 -0
  10. package/dist/cli.js +500 -0
  11. package/dist/commands/api.js +343 -0
  12. package/dist/commands/self.js +318 -0
  13. package/dist/commands/theme.js +257 -0
  14. package/dist/commands/zsh-import.js +240 -0
  15. package/dist/components/App.js +1 -0
  16. package/dist/components/Divider.js +29 -0
  17. package/dist/components/REPL.js +43 -0
  18. package/dist/components/Terminal.js +232 -0
  19. package/dist/components/UserInput.js +30 -0
  20. package/dist/daemon/api-server.js +315 -0
  21. package/dist/daemon/job-registry.js +554 -0
  22. package/dist/daemon/lshd.js +822 -0
  23. package/dist/daemon/monitoring-api.js +220 -0
  24. package/dist/examples/supabase-integration.js +106 -0
  25. package/dist/lib/api-error-handler.js +183 -0
  26. package/dist/lib/associative-arrays.js +285 -0
  27. package/dist/lib/base-api-server.js +290 -0
  28. package/dist/lib/base-command-registrar.js +286 -0
  29. package/dist/lib/base-job-manager.js +293 -0
  30. package/dist/lib/brace-expansion.js +160 -0
  31. package/dist/lib/builtin-commands.js +439 -0
  32. package/dist/lib/cloud-config-manager.js +347 -0
  33. package/dist/lib/command-validator.js +190 -0
  34. package/dist/lib/completion-system.js +344 -0
  35. package/dist/lib/cron-job-manager.js +364 -0
  36. package/dist/lib/daemon-client-helper.js +141 -0
  37. package/dist/lib/daemon-client.js +501 -0
  38. package/dist/lib/database-persistence.js +638 -0
  39. package/dist/lib/database-schema.js +259 -0
  40. package/dist/lib/enhanced-history-system.js +246 -0
  41. package/dist/lib/env-validator.js +265 -0
  42. package/dist/lib/executors/builtin-executor.js +52 -0
  43. package/dist/lib/extended-globbing.js +411 -0
  44. package/dist/lib/extended-parameter-expansion.js +227 -0
  45. package/dist/lib/floating-point-arithmetic.js +256 -0
  46. package/dist/lib/history-system.js +245 -0
  47. package/dist/lib/interactive-shell.js +460 -0
  48. package/dist/lib/job-builtins.js +580 -0
  49. package/dist/lib/job-manager.js +386 -0
  50. package/dist/lib/job-storage-database.js +156 -0
  51. package/dist/lib/job-storage-memory.js +73 -0
  52. package/dist/lib/logger.js +274 -0
  53. package/dist/lib/lshrc-init.js +177 -0
  54. package/dist/lib/pathname-expansion.js +216 -0
  55. package/dist/lib/prompt-system.js +328 -0
  56. package/dist/lib/script-runner.js +226 -0
  57. package/dist/lib/secrets-manager.js +193 -0
  58. package/dist/lib/shell-executor.js +2504 -0
  59. package/dist/lib/shell-parser.js +958 -0
  60. package/dist/lib/shell-types.js +6 -0
  61. package/dist/lib/shell.lib.js +40 -0
  62. package/dist/lib/supabase-client.js +58 -0
  63. package/dist/lib/theme-manager.js +476 -0
  64. package/dist/lib/variable-expansion.js +385 -0
  65. package/dist/lib/zsh-compatibility.js +658 -0
  66. package/dist/lib/zsh-import-manager.js +699 -0
  67. package/dist/lib/zsh-options.js +328 -0
  68. package/dist/pipeline/job-tracker.js +491 -0
  69. package/dist/pipeline/mcli-bridge.js +302 -0
  70. package/dist/pipeline/pipeline-service.js +1116 -0
  71. package/dist/pipeline/workflow-engine.js +867 -0
  72. package/dist/services/api/api.js +58 -0
  73. package/dist/services/api/auth.js +35 -0
  74. package/dist/services/api/config.js +7 -0
  75. package/dist/services/api/file.js +22 -0
  76. package/dist/services/cron/cron-registrar.js +235 -0
  77. package/dist/services/cron/cron.js +9 -0
  78. package/dist/services/daemon/daemon-registrar.js +565 -0
  79. package/dist/services/daemon/daemon.js +9 -0
  80. package/dist/services/lib/lib.js +86 -0
  81. package/dist/services/log-file-extractor.js +170 -0
  82. package/dist/services/secrets/secrets.js +94 -0
  83. package/dist/services/shell/shell.js +28 -0
  84. package/dist/services/supabase/supabase-registrar.js +367 -0
  85. package/dist/services/supabase/supabase.js +9 -0
  86. package/dist/services/zapier.js +16 -0
  87. package/dist/simple-api-server.js +148 -0
  88. package/dist/store/store.js +31 -0
  89. package/dist/util/lib.util.js +11 -0
  90. package/package.json +144 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Log File Extractor
3
+ * Utility for extracting relevant log entries from log files
4
+ * based on patterns and filters.
5
+ */
6
+ import fs from 'fs/promises';
7
+ import readline from 'readline';
8
+ /**
9
+ * Extract relevant log entries from a log file
10
+ */
11
+ export async function extractRelevantLogs(filePath, options) {
12
+ const { pattern, maxLines = 1000, tailLines, contextBefore = 0, contextAfter = 0, } = options;
13
+ // Read the file
14
+ const fileHandle = await fs.open(filePath, 'r');
15
+ const stream = fileHandle.createReadStream();
16
+ const rl = readline.createInterface({
17
+ input: stream,
18
+ crlfDelay: Infinity,
19
+ });
20
+ const allLines = [];
21
+ const matchingIndices = [];
22
+ const result = [];
23
+ // If tailLines is specified, we need to buffer all lines first
24
+ if (tailLines !== undefined) {
25
+ for await (const line of rl) {
26
+ allLines.push(line);
27
+ }
28
+ // Only process last N lines
29
+ const startIndex = Math.max(0, allLines.length - tailLines);
30
+ const linesToProcess = allLines.slice(startIndex);
31
+ // Find matching lines
32
+ linesToProcess.forEach((line, index) => {
33
+ if (pattern.test(line)) {
34
+ matchingIndices.push(startIndex + index);
35
+ }
36
+ });
37
+ }
38
+ else {
39
+ // Process line by line without buffering all
40
+ let lineIndex = 0;
41
+ for await (const line of rl) {
42
+ allLines.push(line);
43
+ if (pattern.test(line)) {
44
+ matchingIndices.push(lineIndex);
45
+ }
46
+ lineIndex++;
47
+ // Stop if we have enough matches (considering context)
48
+ if (matchingIndices.length >= maxLines) {
49
+ break;
50
+ }
51
+ }
52
+ }
53
+ await fileHandle.close();
54
+ // Extract lines with context
55
+ const includedIndices = new Set();
56
+ for (const matchIndex of matchingIndices) {
57
+ if (result.length >= maxLines) {
58
+ break;
59
+ }
60
+ // Calculate range of lines to include
61
+ const startIndex = Math.max(0, matchIndex - contextBefore);
62
+ const endIndex = Math.min(allLines.length - 1, matchIndex + contextAfter);
63
+ for (let i = startIndex; i <= endIndex; i++) {
64
+ if (!includedIndices.has(i)) {
65
+ result.push(allLines[i]);
66
+ includedIndices.add(i);
67
+ }
68
+ }
69
+ }
70
+ return result.slice(0, maxLines);
71
+ }
72
+ /**
73
+ * Extract error logs from a file
74
+ */
75
+ export async function extractErrors(filePath, options) {
76
+ return extractRelevantLogs(filePath, {
77
+ pattern: /ERROR|FATAL|CRITICAL/i,
78
+ maxLines: 100,
79
+ contextBefore: 2,
80
+ contextAfter: 2,
81
+ ...options,
82
+ });
83
+ }
84
+ /**
85
+ * Extract warning logs from a file
86
+ */
87
+ export async function extractWarnings(filePath, options) {
88
+ return extractRelevantLogs(filePath, {
89
+ pattern: /WARN|WARNING/i,
90
+ maxLines: 100,
91
+ contextBefore: 1,
92
+ contextAfter: 1,
93
+ ...options,
94
+ });
95
+ }
96
+ /**
97
+ * Extract logs from the last N minutes
98
+ */
99
+ export async function extractRecent(filePath, minutes, options) {
100
+ const now = new Date();
101
+ const cutoffTime = new Date(now.getTime() - minutes * 60 * 1000);
102
+ // Common timestamp patterns
103
+ const timestampPattern = /\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}/;
104
+ return extractRelevantLogs(filePath, {
105
+ pattern: timestampPattern,
106
+ maxLines: 1000,
107
+ ...options,
108
+ }).then((lines) => {
109
+ // Filter lines by timestamp
110
+ return lines.filter((line) => {
111
+ const match = line.match(timestampPattern);
112
+ if (match) {
113
+ try {
114
+ const lineTime = new Date(match[0].replace(' ', 'T'));
115
+ return lineTime >= cutoffTime;
116
+ }
117
+ catch (_error) {
118
+ return false;
119
+ }
120
+ }
121
+ return false;
122
+ });
123
+ });
124
+ }
125
+ /**
126
+ * Get summary statistics from a log file
127
+ */
128
+ export async function getLogStatistics(filePath) {
129
+ const fileHandle = await fs.open(filePath, 'r');
130
+ const stream = fileHandle.createReadStream();
131
+ const rl = readline.createInterface({
132
+ input: stream,
133
+ crlfDelay: Infinity,
134
+ });
135
+ let totalLines = 0;
136
+ let errorCount = 0;
137
+ let warningCount = 0;
138
+ let infoCount = 0;
139
+ let debugCount = 0;
140
+ for await (const line of rl) {
141
+ totalLines++;
142
+ if (/ERROR|FATAL|CRITICAL/i.test(line)) {
143
+ errorCount++;
144
+ }
145
+ else if (/WARN|WARNING/i.test(line)) {
146
+ warningCount++;
147
+ }
148
+ else if (/INFO/i.test(line)) {
149
+ infoCount++;
150
+ }
151
+ else if (/DEBUG|TRACE/i.test(line)) {
152
+ debugCount++;
153
+ }
154
+ }
155
+ await fileHandle.close();
156
+ return {
157
+ totalLines,
158
+ errorCount,
159
+ warningCount,
160
+ infoCount,
161
+ debugCount,
162
+ };
163
+ }
164
+ export default {
165
+ extractRelevantLogs,
166
+ extractErrors,
167
+ extractWarnings,
168
+ extractRecent,
169
+ getLogStatistics,
170
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Secrets Management Commands
3
+ * Sync .env files across development environments
4
+ */
5
+ import SecretsManager from '../../lib/secrets-manager.js';
6
+ export async function init_secrets(program) {
7
+ const secretsCmd = program
8
+ .command('secrets')
9
+ .description('Manage environment secrets across machines');
10
+ // Push secrets to cloud
11
+ secretsCmd
12
+ .command('push')
13
+ .description('Push local .env to encrypted cloud storage')
14
+ .option('-f, --file <path>', 'Path to .env file', '.env')
15
+ .option('-e, --env <name>', 'Environment name (dev/staging/prod)', 'dev')
16
+ .action(async (options) => {
17
+ try {
18
+ const manager = new SecretsManager();
19
+ await manager.push(options.file, options.env);
20
+ }
21
+ catch (error) {
22
+ console.error('❌ Failed to push secrets:', error.message);
23
+ process.exit(1);
24
+ }
25
+ });
26
+ // Pull secrets from cloud
27
+ secretsCmd
28
+ .command('pull')
29
+ .description('Pull .env from encrypted cloud storage')
30
+ .option('-f, --file <path>', 'Path to .env file', '.env')
31
+ .option('-e, --env <name>', 'Environment name (dev/staging/prod)', 'dev')
32
+ .action(async (options) => {
33
+ try {
34
+ const manager = new SecretsManager();
35
+ await manager.pull(options.file, options.env);
36
+ }
37
+ catch (error) {
38
+ console.error('❌ Failed to pull secrets:', error.message);
39
+ process.exit(1);
40
+ }
41
+ });
42
+ // List environments
43
+ secretsCmd
44
+ .command('list')
45
+ .alias('ls')
46
+ .description('List all stored environments')
47
+ .action(async () => {
48
+ try {
49
+ const manager = new SecretsManager();
50
+ const envs = await manager.listEnvironments();
51
+ if (envs.length === 0) {
52
+ console.log('No environments found. Push your first .env with: lsh secrets push');
53
+ return;
54
+ }
55
+ console.log('\n📦 Available environments:\n');
56
+ for (const env of envs) {
57
+ console.log(` • ${env}`);
58
+ }
59
+ console.log();
60
+ }
61
+ catch (error) {
62
+ console.error('❌ Failed to list environments:', error.message);
63
+ process.exit(1);
64
+ }
65
+ });
66
+ // Show secrets (masked)
67
+ secretsCmd
68
+ .command('show')
69
+ .description('Show secrets for an environment (masked)')
70
+ .option('-e, --env <name>', 'Environment name', 'dev')
71
+ .action(async (options) => {
72
+ try {
73
+ const manager = new SecretsManager();
74
+ await manager.show(options.env);
75
+ }
76
+ catch (error) {
77
+ console.error('❌ Failed to show secrets:', error.message);
78
+ process.exit(1);
79
+ }
80
+ });
81
+ // Generate encryption key
82
+ secretsCmd
83
+ .command('key')
84
+ .description('Generate a new encryption key')
85
+ .action(async () => {
86
+ const { randomBytes } = await import('crypto');
87
+ const key = randomBytes(32).toString('hex');
88
+ console.log('\n🔑 New encryption key (add to your .env):\n');
89
+ console.log(`LSH_SECRETS_KEY=${key}\n`);
90
+ console.log('💡 Tip: Share this key securely with your team to sync secrets.');
91
+ console.log(' Never commit it to git!\n');
92
+ });
93
+ }
94
+ export default init_secrets;
@@ -0,0 +1,28 @@
1
+ import { render } from "ink";
2
+ import React from "react";
3
+ import { Terminal } from "../../components/Terminal.js";
4
+ export async function get_ishell(_type = "Jvm", _spec = { function: "currentUsage" }) { }
5
+ export async function init_ishell(program) {
6
+ await cmd_interactive(program);
7
+ }
8
+ async function cmd_interactive(program) {
9
+ program
10
+ .command("repl")
11
+ .description("lsh interactive shell")
12
+ .action(async (_type, _action, _spec) => {
13
+ // Check if raw mode is supported before rendering
14
+ if (process.stdin.isTTY && process.stdin.setRawMode !== undefined) {
15
+ render(React.createElement(Terminal, null));
16
+ }
17
+ else {
18
+ console.log('⚠️ Interactive mode not supported');
19
+ console.log('Raw mode is not supported in this environment.');
20
+ console.log('To use the interactive REPL, run this command in a proper terminal:');
21
+ console.log(' npm start repl');
22
+ console.log('or');
23
+ console.log(' node dist/app.js repl');
24
+ console.log('For testing, use the shell lib functions directly in your Node.js code.');
25
+ process.exit(1);
26
+ }
27
+ });
28
+ }
@@ -0,0 +1,367 @@
1
+ /**
2
+ * Supabase Command Registrar
3
+ * Registers all Supabase-related CLI commands using BaseCommandRegistrar
4
+ */
5
+ import { BaseCommandRegistrar } from '../../lib/base-command-registrar.js';
6
+ import { supabaseClient } from '../../lib/supabase-client.js';
7
+ import DatabasePersistence from '../../lib/database-persistence.js';
8
+ import CloudConfigManager from '../../lib/cloud-config-manager.js';
9
+ import { CREATE_TABLES_SQL } from '../../lib/database-schema.js';
10
+ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
11
+ constructor() {
12
+ super('SupabaseService');
13
+ }
14
+ async register(program) {
15
+ const supabaseCmd = this.createCommand(program, 'supabase', 'Supabase database management commands');
16
+ this.registerConnectionCommands(supabaseCmd);
17
+ this.registerDataCommands(supabaseCmd);
18
+ this.registerMLCommands(supabaseCmd);
19
+ }
20
+ registerConnectionCommands(supabaseCmd) {
21
+ // Test connection
22
+ this.addSubcommand(supabaseCmd, {
23
+ name: 'test',
24
+ description: 'Test Supabase database connection',
25
+ action: async () => {
26
+ this.logInfo('Testing Supabase connection...');
27
+ const isConnected = await supabaseClient.testConnection();
28
+ if (isConnected) {
29
+ this.logSuccess('Supabase connection successful');
30
+ const info = supabaseClient.getConnectionInfo();
31
+ this.logInfo(`Connection info: ${JSON.stringify(info)}`);
32
+ }
33
+ else {
34
+ throw new Error('Supabase connection failed');
35
+ }
36
+ }
37
+ });
38
+ // Initialize schema
39
+ this.addSubcommand(supabaseCmd, {
40
+ name: 'init',
41
+ description: 'Initialize database schema',
42
+ action: async () => {
43
+ this.logInfo('Initializing database schema...');
44
+ const persistence = new DatabasePersistence();
45
+ const success = await persistence.initializeSchema();
46
+ if (success) {
47
+ this.logSuccess('Database schema initialized');
48
+ this.logInfo('Note: Run the following SQL in your Supabase dashboard:');
49
+ this.logInfo(CREATE_TABLES_SQL);
50
+ }
51
+ else {
52
+ throw new Error('Failed to initialize schema');
53
+ }
54
+ }
55
+ });
56
+ // Sync management
57
+ this.addSubcommand(supabaseCmd, {
58
+ name: 'sync',
59
+ description: 'Synchronize data with Supabase',
60
+ options: [
61
+ { flags: '-f, --force', description: 'Force full synchronization', defaultValue: false }
62
+ ],
63
+ action: async (_options) => {
64
+ this.logInfo('Synchronizing data with Supabase...');
65
+ const persistence = new DatabasePersistence();
66
+ // Test connection first
67
+ const isConnected = await persistence.testConnection();
68
+ if (!isConnected) {
69
+ throw new Error('Cannot sync - database not available');
70
+ }
71
+ // Sync configuration
72
+ this.logInfo('Syncing configuration...');
73
+ // Configuration sync is handled automatically by CloudConfigManager
74
+ // Sync history (this would be done automatically by the enhanced history system)
75
+ this.logInfo('Syncing history...');
76
+ // History sync is handled by EnhancedHistorySystem
77
+ this.logSuccess('Synchronization completed');
78
+ }
79
+ });
80
+ }
81
+ registerDataCommands(supabaseCmd) {
82
+ // History management
83
+ this.addSubcommand(supabaseCmd, {
84
+ name: 'history',
85
+ description: 'Manage shell history',
86
+ options: [
87
+ { flags: '-l, --list', description: 'List recent history entries', defaultValue: false },
88
+ { flags: '-c, --count <number>', description: 'Number of entries to show', defaultValue: '10' },
89
+ { flags: '-s, --search <query>', description: 'Search history entries' }
90
+ ],
91
+ action: async (options) => {
92
+ const persistence = new DatabasePersistence();
93
+ if (options.list) {
94
+ const count = parseInt(options.count);
95
+ const entries = await persistence.getHistoryEntries(count);
96
+ this.logInfo(`Recent ${entries.length} history entries:`);
97
+ entries.forEach((entry, index) => {
98
+ const timestamp = new Date(entry.timestamp).toLocaleString();
99
+ const exitCode = entry.exit_code ? ` (exit: ${entry.exit_code})` : '';
100
+ this.logInfo(`${index + 1}. [${timestamp}] ${entry.command}${exitCode}`);
101
+ });
102
+ }
103
+ else if (options.search) {
104
+ const entries = await persistence.getHistoryEntries(100);
105
+ const filtered = entries.filter(entry => entry.command.toLowerCase().includes(options.search.toLowerCase()));
106
+ this.logInfo(`Found ${filtered.length} matching entries:`);
107
+ filtered.forEach((entry, index) => {
108
+ const timestamp = new Date(entry.timestamp).toLocaleString();
109
+ this.logInfo(`${index + 1}. [${timestamp}] ${entry.command}`);
110
+ });
111
+ }
112
+ else {
113
+ this.logInfo('Use --list or --search to manage history');
114
+ }
115
+ }
116
+ });
117
+ // Configuration management
118
+ this.addSubcommand(supabaseCmd, {
119
+ name: 'config',
120
+ description: 'Manage shell configuration',
121
+ options: [
122
+ { flags: '-l, --list', description: 'List all configuration', defaultValue: false },
123
+ { flags: '-g, --get <key>', description: 'Get configuration value' },
124
+ { flags: '-s, --set <key> <value>', description: 'Set configuration value' },
125
+ { flags: '-d, --delete <key>', description: 'Delete configuration key' },
126
+ { flags: '-e, --export', description: 'Export configuration to JSON', defaultValue: false }
127
+ ],
128
+ action: async (options) => {
129
+ const configManager = new CloudConfigManager();
130
+ if (options.list) {
131
+ const config = configManager.getAll();
132
+ this.logInfo('Current configuration:');
133
+ config.forEach(item => {
134
+ this.logInfo(` ${item.key}: ${JSON.stringify(item.value)}`);
135
+ });
136
+ }
137
+ else if (options.get) {
138
+ const value = configManager.get(options.get);
139
+ if (value !== undefined) {
140
+ this.logInfo(`${options.get}: ${JSON.stringify(value)}`);
141
+ }
142
+ else {
143
+ this.logWarning(`Configuration key '${options.get}' not found`);
144
+ }
145
+ }
146
+ else if (options.set) {
147
+ const [key, value] = options.set;
148
+ configManager.set(key, value);
149
+ this.logSuccess(`Configuration '${key}' set to: ${value}`);
150
+ }
151
+ else if (options.delete) {
152
+ configManager.delete(options.delete);
153
+ this.logSuccess(`Configuration '${options.delete}' deleted`);
154
+ }
155
+ else if (options.export) {
156
+ const exported = configManager.export();
157
+ this.logInfo(exported);
158
+ }
159
+ else {
160
+ this.logInfo('Use --list, --get, --set, --delete, or --export to manage configuration');
161
+ }
162
+ }
163
+ });
164
+ // Jobs management
165
+ this.addSubcommand(supabaseCmd, {
166
+ name: 'jobs',
167
+ description: 'Manage shell jobs',
168
+ options: [
169
+ { flags: '-l, --list', description: 'List active jobs', defaultValue: false },
170
+ { flags: '-h, --history', description: 'List job history', defaultValue: false }
171
+ ],
172
+ action: async (options) => {
173
+ const persistence = new DatabasePersistence();
174
+ if (options.list) {
175
+ const jobs = await persistence.getActiveJobs();
176
+ this.logInfo(`Active jobs (${jobs.length}):`);
177
+ jobs.forEach(job => {
178
+ const started = new Date(job.started_at).toLocaleString();
179
+ this.logInfo(`${job.job_id}: ${job.command} (${job.status}) - Started: ${started}`);
180
+ });
181
+ }
182
+ else if (options.history) {
183
+ this.logInfo('Job history feature not yet implemented');
184
+ }
185
+ else {
186
+ this.logInfo('Use --list or --history to manage jobs');
187
+ }
188
+ }
189
+ });
190
+ // Database rows management
191
+ this.addSubcommand(supabaseCmd, {
192
+ name: 'rows',
193
+ description: 'Show latest database entries',
194
+ options: [
195
+ { flags: '-l, --limit <number>', description: 'Number of rows to show per table', defaultValue: '5' },
196
+ { flags: '-t, --table <name>', description: 'Show rows from specific table only' }
197
+ ],
198
+ action: async (options) => {
199
+ const persistence = new DatabasePersistence();
200
+ const limit = parseInt(options.limit);
201
+ // Test connection first
202
+ const isConnected = await persistence.testConnection();
203
+ if (!isConnected) {
204
+ throw new Error('Cannot fetch rows - database not available');
205
+ }
206
+ if (options.table) {
207
+ // Show rows from specific table
208
+ this.logInfo(`Latest ${limit} entries from table '${options.table}':`);
209
+ const rows = await persistence.getLatestRowsFromTable(options.table, limit);
210
+ if (rows.length === 0) {
211
+ this.logInfo('No entries found.');
212
+ }
213
+ else {
214
+ rows.forEach((row, index) => {
215
+ const timestamp = row.created_at ? new Date(row.created_at).toLocaleString() : 'N/A';
216
+ this.logInfo(`\n${index + 1}. [${timestamp}]`);
217
+ this.logInfo(JSON.stringify(row, null, 2));
218
+ });
219
+ }
220
+ }
221
+ else {
222
+ // Show rows from all tables
223
+ this.logInfo(`Latest ${limit} entries from each table:`);
224
+ const allRows = await persistence.getLatestRows(limit);
225
+ for (const [tableName, rows] of Object.entries(allRows)) {
226
+ this.logInfo(`\n=== ${tableName.toUpperCase()} ===`);
227
+ if (rows.length === 0) {
228
+ this.logInfo('No entries found.');
229
+ }
230
+ else {
231
+ rows.forEach((row, index) => {
232
+ const timestamp = row.created_at ? new Date(row.created_at).toLocaleString() : 'N/A';
233
+ this.logInfo(`\n${index + 1}. [${timestamp}]`);
234
+ this.logInfo(JSON.stringify(row, null, 2));
235
+ });
236
+ }
237
+ }
238
+ }
239
+ }
240
+ });
241
+ }
242
+ registerMLCommands(supabaseCmd) {
243
+ // ML Training Jobs
244
+ this.addSubcommand(supabaseCmd, {
245
+ name: 'ml-train',
246
+ description: 'Manage ML training jobs',
247
+ options: [
248
+ { flags: '-l, --list', description: 'List training jobs', defaultValue: false },
249
+ { flags: '-s, --status <status>', description: 'Filter by status (pending, running, completed, failed)' },
250
+ { flags: '-c, --create <name>', description: 'Create new training job' },
251
+ { flags: '--model-type <type>', description: 'Model type for new job' },
252
+ { flags: '--dataset <name>', description: 'Dataset name for new job' }
253
+ ],
254
+ action: async (options) => {
255
+ if (options.list) {
256
+ let query = supabaseClient.getClient()
257
+ .from('ml_training_jobs')
258
+ .select('*')
259
+ .order('created_at', { ascending: false });
260
+ if (options.status) {
261
+ query = query.eq('status', options.status);
262
+ }
263
+ const { data: jobs, error } = await query.limit(20);
264
+ if (error) {
265
+ throw new Error(`Failed to fetch training jobs: ${error.message}`);
266
+ }
267
+ this.logInfo(`Training Jobs (${jobs?.length || 0}):`);
268
+ jobs?.forEach(job => {
269
+ const created = new Date(job.created_at).toLocaleString();
270
+ this.logInfo(`\n${job.job_name} (${job.model_type})`);
271
+ this.logInfo(` Status: ${job.status}`);
272
+ this.logInfo(` Created: ${created}`);
273
+ this.logInfo(` Dataset: ${job.dataset_name}`);
274
+ });
275
+ }
276
+ else if (options.create) {
277
+ if (!options.modelType || !options.dataset) {
278
+ throw new Error('Both --model-type and --dataset are required to create a job');
279
+ }
280
+ const { data, error } = await supabaseClient.getClient()
281
+ .from('ml_training_jobs')
282
+ .insert({
283
+ job_name: options.create,
284
+ model_type: options.modelType,
285
+ dataset_name: options.dataset,
286
+ status: 'pending',
287
+ created_at: new Date().toISOString()
288
+ })
289
+ .select();
290
+ if (error) {
291
+ throw new Error(`Failed to create training job: ${error.message}`);
292
+ }
293
+ this.logSuccess(`Created training job: ${options.create}`);
294
+ this.logInfo(JSON.stringify(data, null, 2));
295
+ }
296
+ else {
297
+ this.logInfo('Use --list or --create to manage training jobs');
298
+ }
299
+ }
300
+ });
301
+ // ML Models
302
+ this.addSubcommand(supabaseCmd, {
303
+ name: 'ml-models',
304
+ description: 'Manage ML models',
305
+ options: [
306
+ { flags: '-l, --list', description: 'List ML models', defaultValue: false },
307
+ { flags: '--deployed', description: 'Filter by deployed models only', defaultValue: false }
308
+ ],
309
+ action: async (options) => {
310
+ if (options.list) {
311
+ let query = supabaseClient.getClient()
312
+ .from('ml_models')
313
+ .select('*')
314
+ .order('created_at', { ascending: false });
315
+ if (options.deployed) {
316
+ query = query.eq('deployed', true);
317
+ }
318
+ const { data: models, error } = await query.limit(20);
319
+ if (error) {
320
+ throw new Error(`Failed to fetch models: ${error.message}`);
321
+ }
322
+ this.logInfo(`ML Models (${models?.length || 0}):`);
323
+ models?.forEach(model => {
324
+ const created = new Date(model.created_at).toLocaleString();
325
+ this.logInfo(`\n${model.model_name} (v${model.version})`);
326
+ this.logInfo(` Type: ${model.model_type}`);
327
+ this.logInfo(` Accuracy: ${model.accuracy}`);
328
+ this.logInfo(` Deployed: ${model.deployed ? 'Yes' : 'No'}`);
329
+ this.logInfo(` Created: ${created}`);
330
+ });
331
+ }
332
+ else {
333
+ this.logInfo('Use --list to manage ML models');
334
+ }
335
+ }
336
+ });
337
+ // ML Features
338
+ this.addSubcommand(supabaseCmd, {
339
+ name: 'ml-features',
340
+ description: 'Manage ML feature definitions',
341
+ options: [
342
+ { flags: '-l, --list', description: 'List feature definitions', defaultValue: false }
343
+ ],
344
+ action: async (options) => {
345
+ if (options.list) {
346
+ const { data: features, error } = await supabaseClient.getClient()
347
+ .from('ml_features')
348
+ .select('*')
349
+ .order('created_at', { ascending: false })
350
+ .limit(20);
351
+ if (error) {
352
+ throw new Error(`Failed to fetch features: ${error.message}`);
353
+ }
354
+ this.logInfo(`ML Features (${features?.length || 0}):`);
355
+ features?.forEach(feature => {
356
+ this.logInfo(`\n${feature.feature_name}`);
357
+ this.logInfo(` Type: ${feature.feature_type}`);
358
+ this.logInfo(` Importance: ${feature.importance_score}`);
359
+ });
360
+ }
361
+ else {
362
+ this.logInfo('Use --list to manage ML features');
363
+ }
364
+ }
365
+ });
366
+ }
367
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Supabase Service - CLI command registration
3
+ * Uses SupabaseCommandRegistrar for clean, maintainable command setup
4
+ */
5
+ import { SupabaseCommandRegistrar } from './supabase-registrar.js';
6
+ export async function init_supabase(program) {
7
+ const registrar = new SupabaseCommandRegistrar();
8
+ await registrar.register(program);
9
+ }
@@ -0,0 +1,16 @@
1
+ import axios from 'axios';
2
+ export async function test() {
3
+ const options = {
4
+ method: 'POST',
5
+ url: 'https://stoplight.io/mocks/zapier/public-api/181772442/authentications',
6
+ headers: { 'Content-Type': 'application/vnd.api+json', Accept: 'application/vnd.api+json' },
7
+ data: '{\n "data": {\n "title": "SuperExampleCRM (example@zapier.com)",\n "app": "868f9d3c-2ea0-4f19-a32d-a61b276ab8de",\n "authentication_fields": {}\n }\n}'
8
+ };
9
+ try {
10
+ const { data } = await axios.request(options);
11
+ console.log(data);
12
+ }
13
+ catch (error) {
14
+ console.error(error);
15
+ }
16
+ }