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.
- package/.env.example +51 -0
- package/README.md +399 -0
- package/dist/app.js +33 -0
- package/dist/cicd/analytics.js +261 -0
- package/dist/cicd/auth.js +269 -0
- package/dist/cicd/cache-manager.js +172 -0
- package/dist/cicd/data-retention.js +305 -0
- package/dist/cicd/performance-monitor.js +224 -0
- package/dist/cicd/webhook-receiver.js +634 -0
- package/dist/cli.js +500 -0
- package/dist/commands/api.js +343 -0
- package/dist/commands/self.js +318 -0
- package/dist/commands/theme.js +257 -0
- package/dist/commands/zsh-import.js +240 -0
- package/dist/components/App.js +1 -0
- package/dist/components/Divider.js +29 -0
- package/dist/components/REPL.js +43 -0
- package/dist/components/Terminal.js +232 -0
- package/dist/components/UserInput.js +30 -0
- package/dist/daemon/api-server.js +315 -0
- package/dist/daemon/job-registry.js +554 -0
- package/dist/daemon/lshd.js +822 -0
- package/dist/daemon/monitoring-api.js +220 -0
- package/dist/examples/supabase-integration.js +106 -0
- package/dist/lib/api-error-handler.js +183 -0
- package/dist/lib/associative-arrays.js +285 -0
- package/dist/lib/base-api-server.js +290 -0
- package/dist/lib/base-command-registrar.js +286 -0
- package/dist/lib/base-job-manager.js +293 -0
- package/dist/lib/brace-expansion.js +160 -0
- package/dist/lib/builtin-commands.js +439 -0
- package/dist/lib/cloud-config-manager.js +347 -0
- package/dist/lib/command-validator.js +190 -0
- package/dist/lib/completion-system.js +344 -0
- package/dist/lib/cron-job-manager.js +364 -0
- package/dist/lib/daemon-client-helper.js +141 -0
- package/dist/lib/daemon-client.js +501 -0
- package/dist/lib/database-persistence.js +638 -0
- package/dist/lib/database-schema.js +259 -0
- package/dist/lib/enhanced-history-system.js +246 -0
- package/dist/lib/env-validator.js +265 -0
- package/dist/lib/executors/builtin-executor.js +52 -0
- package/dist/lib/extended-globbing.js +411 -0
- package/dist/lib/extended-parameter-expansion.js +227 -0
- package/dist/lib/floating-point-arithmetic.js +256 -0
- package/dist/lib/history-system.js +245 -0
- package/dist/lib/interactive-shell.js +460 -0
- package/dist/lib/job-builtins.js +580 -0
- package/dist/lib/job-manager.js +386 -0
- package/dist/lib/job-storage-database.js +156 -0
- package/dist/lib/job-storage-memory.js +73 -0
- package/dist/lib/logger.js +274 -0
- package/dist/lib/lshrc-init.js +177 -0
- package/dist/lib/pathname-expansion.js +216 -0
- package/dist/lib/prompt-system.js +328 -0
- package/dist/lib/script-runner.js +226 -0
- package/dist/lib/secrets-manager.js +193 -0
- package/dist/lib/shell-executor.js +2504 -0
- package/dist/lib/shell-parser.js +958 -0
- package/dist/lib/shell-types.js +6 -0
- package/dist/lib/shell.lib.js +40 -0
- package/dist/lib/supabase-client.js +58 -0
- package/dist/lib/theme-manager.js +476 -0
- package/dist/lib/variable-expansion.js +385 -0
- package/dist/lib/zsh-compatibility.js +658 -0
- package/dist/lib/zsh-import-manager.js +699 -0
- package/dist/lib/zsh-options.js +328 -0
- package/dist/pipeline/job-tracker.js +491 -0
- package/dist/pipeline/mcli-bridge.js +302 -0
- package/dist/pipeline/pipeline-service.js +1116 -0
- package/dist/pipeline/workflow-engine.js +867 -0
- package/dist/services/api/api.js +58 -0
- package/dist/services/api/auth.js +35 -0
- package/dist/services/api/config.js +7 -0
- package/dist/services/api/file.js +22 -0
- package/dist/services/cron/cron-registrar.js +235 -0
- package/dist/services/cron/cron.js +9 -0
- package/dist/services/daemon/daemon-registrar.js +565 -0
- package/dist/services/daemon/daemon.js +9 -0
- package/dist/services/lib/lib.js +86 -0
- package/dist/services/log-file-extractor.js +170 -0
- package/dist/services/secrets/secrets.js +94 -0
- package/dist/services/shell/shell.js +28 -0
- package/dist/services/supabase/supabase-registrar.js +367 -0
- package/dist/services/supabase/supabase.js +9 -0
- package/dist/services/zapier.js +16 -0
- package/dist/simple-api-server.js +148 -0
- package/dist/store/store.js +31 -0
- package/dist/util/lib.util.js +11 -0
- package/package.json +144 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSH Secrets Manager
|
|
3
|
+
* Sync .env files across machines using encrypted Supabase storage
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as crypto from 'crypto';
|
|
7
|
+
import DatabasePersistence from './database-persistence.js';
|
|
8
|
+
import { createLogger } from './logger.js';
|
|
9
|
+
const logger = createLogger('SecretsManager');
|
|
10
|
+
export class SecretsManager {
|
|
11
|
+
persistence;
|
|
12
|
+
encryptionKey;
|
|
13
|
+
constructor(userId, encryptionKey) {
|
|
14
|
+
this.persistence = new DatabasePersistence(userId);
|
|
15
|
+
// Use provided key or generate from machine ID + user
|
|
16
|
+
this.encryptionKey = encryptionKey || this.getDefaultEncryptionKey();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get default encryption key from environment or machine
|
|
20
|
+
*/
|
|
21
|
+
getDefaultEncryptionKey() {
|
|
22
|
+
// Check for explicit key
|
|
23
|
+
if (process.env.LSH_SECRETS_KEY) {
|
|
24
|
+
return process.env.LSH_SECRETS_KEY;
|
|
25
|
+
}
|
|
26
|
+
// Generate from machine ID and user
|
|
27
|
+
const machineId = process.env.HOSTNAME || 'localhost';
|
|
28
|
+
const user = process.env.USER || 'unknown';
|
|
29
|
+
const seed = `${machineId}-${user}-lsh-secrets`;
|
|
30
|
+
// Create deterministic key
|
|
31
|
+
return crypto.createHash('sha256').update(seed).digest('hex');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Encrypt a value
|
|
35
|
+
*/
|
|
36
|
+
encrypt(text) {
|
|
37
|
+
const iv = crypto.randomBytes(16);
|
|
38
|
+
const key = Buffer.from(this.encryptionKey, 'hex');
|
|
39
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', key.slice(0, 32), iv);
|
|
40
|
+
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
41
|
+
encrypted += cipher.final('hex');
|
|
42
|
+
return iv.toString('hex') + ':' + encrypted;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Decrypt a value
|
|
46
|
+
*/
|
|
47
|
+
decrypt(text) {
|
|
48
|
+
const parts = text.split(':');
|
|
49
|
+
const iv = Buffer.from(parts[0], 'hex');
|
|
50
|
+
const encryptedText = parts[1];
|
|
51
|
+
const key = Buffer.from(this.encryptionKey, 'hex');
|
|
52
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', key.slice(0, 32), iv);
|
|
53
|
+
let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
|
|
54
|
+
decrypted += decipher.final('utf8');
|
|
55
|
+
return decrypted;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse .env file into key-value pairs
|
|
59
|
+
*/
|
|
60
|
+
parseEnvFile(content) {
|
|
61
|
+
const env = {};
|
|
62
|
+
const lines = content.split('\n');
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
// Skip comments and empty lines
|
|
65
|
+
if (line.trim().startsWith('#') || !line.trim()) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// Parse KEY=VALUE
|
|
69
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
70
|
+
if (match) {
|
|
71
|
+
const key = match[1].trim();
|
|
72
|
+
let value = match[2].trim();
|
|
73
|
+
// Remove quotes if present
|
|
74
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
75
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
76
|
+
value = value.slice(1, -1);
|
|
77
|
+
}
|
|
78
|
+
env[key] = value;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return env;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Format env vars as .env file content
|
|
85
|
+
*/
|
|
86
|
+
formatEnvFile(vars) {
|
|
87
|
+
return Object.entries(vars)
|
|
88
|
+
.map(([key, value]) => {
|
|
89
|
+
// Quote values with spaces or special characters
|
|
90
|
+
const needsQuotes = /[\s#]/.test(value);
|
|
91
|
+
const quotedValue = needsQuotes ? `"${value}"` : value;
|
|
92
|
+
return `${key}=${quotedValue}`;
|
|
93
|
+
})
|
|
94
|
+
.join('\n') + '\n';
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Push local .env to Supabase
|
|
98
|
+
*/
|
|
99
|
+
async push(envFilePath = '.env', environment = 'dev') {
|
|
100
|
+
if (!fs.existsSync(envFilePath)) {
|
|
101
|
+
throw new Error(`File not found: ${envFilePath}`);
|
|
102
|
+
}
|
|
103
|
+
logger.info(`Pushing ${envFilePath} to Supabase (${environment})...`);
|
|
104
|
+
const content = fs.readFileSync(envFilePath, 'utf8');
|
|
105
|
+
const env = this.parseEnvFile(content);
|
|
106
|
+
// Encrypt entire .env content
|
|
107
|
+
const encrypted = this.encrypt(content);
|
|
108
|
+
// Store in Supabase (using job system for now)
|
|
109
|
+
const secretData = {
|
|
110
|
+
job_id: `secrets_${environment}_${Date.now()}`,
|
|
111
|
+
command: 'secrets_sync',
|
|
112
|
+
status: 'completed',
|
|
113
|
+
output: encrypted,
|
|
114
|
+
started_at: new Date().toISOString(),
|
|
115
|
+
completed_at: new Date().toISOString(),
|
|
116
|
+
working_directory: process.cwd(),
|
|
117
|
+
};
|
|
118
|
+
await this.persistence.saveJob(secretData);
|
|
119
|
+
logger.info(`ā
Pushed ${Object.keys(env).length} secrets to Supabase`);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Pull .env from Supabase
|
|
123
|
+
*/
|
|
124
|
+
async pull(envFilePath = '.env', environment = 'dev') {
|
|
125
|
+
logger.info(`Pulling ${environment} secrets from Supabase...`);
|
|
126
|
+
// Get latest secrets
|
|
127
|
+
const jobs = await this.persistence.getActiveJobs();
|
|
128
|
+
const secretsJobs = jobs
|
|
129
|
+
.filter(j => j.command === 'secrets_sync' && j.job_id.includes(environment))
|
|
130
|
+
.sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
|
|
131
|
+
if (secretsJobs.length === 0) {
|
|
132
|
+
throw new Error(`No secrets found for environment: ${environment}`);
|
|
133
|
+
}
|
|
134
|
+
const latestSecret = secretsJobs[0];
|
|
135
|
+
if (!latestSecret.output) {
|
|
136
|
+
throw new Error(`No encrypted data found for environment: ${environment}`);
|
|
137
|
+
}
|
|
138
|
+
const decrypted = this.decrypt(latestSecret.output);
|
|
139
|
+
// Backup existing .env if it exists
|
|
140
|
+
if (fs.existsSync(envFilePath)) {
|
|
141
|
+
const backup = `${envFilePath}.backup.${Date.now()}`;
|
|
142
|
+
fs.copyFileSync(envFilePath, backup);
|
|
143
|
+
logger.info(`Backed up existing .env to ${backup}`);
|
|
144
|
+
}
|
|
145
|
+
// Write new .env
|
|
146
|
+
fs.writeFileSync(envFilePath, decrypted, 'utf8');
|
|
147
|
+
const env = this.parseEnvFile(decrypted);
|
|
148
|
+
logger.info(`ā
Pulled ${Object.keys(env).length} secrets from Supabase`);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* List all stored environments
|
|
152
|
+
*/
|
|
153
|
+
async listEnvironments() {
|
|
154
|
+
const jobs = await this.persistence.getActiveJobs();
|
|
155
|
+
const secretsJobs = jobs.filter(j => j.command === 'secrets_sync');
|
|
156
|
+
const envs = new Set();
|
|
157
|
+
for (const job of secretsJobs) {
|
|
158
|
+
const match = job.job_id.match(/secrets_(.+?)_\d+/);
|
|
159
|
+
if (match) {
|
|
160
|
+
envs.add(match[1]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return Array.from(envs).sort();
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Show secrets (masked)
|
|
167
|
+
*/
|
|
168
|
+
async show(environment = 'dev') {
|
|
169
|
+
const jobs = await this.persistence.getActiveJobs();
|
|
170
|
+
const secretsJobs = jobs
|
|
171
|
+
.filter(j => j.command === 'secrets_sync' && j.job_id.includes(environment))
|
|
172
|
+
.sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
|
|
173
|
+
if (secretsJobs.length === 0) {
|
|
174
|
+
console.log(`No secrets found for environment: ${environment}`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const latestSecret = secretsJobs[0];
|
|
178
|
+
if (!latestSecret.output) {
|
|
179
|
+
throw new Error(`No encrypted data found for environment: ${environment}`);
|
|
180
|
+
}
|
|
181
|
+
const decrypted = this.decrypt(latestSecret.output);
|
|
182
|
+
const env = this.parseEnvFile(decrypted);
|
|
183
|
+
console.log(`\nš¦ Secrets for ${environment} (${Object.keys(env).length} total):\n`);
|
|
184
|
+
for (const [key, value] of Object.entries(env)) {
|
|
185
|
+
const masked = value.length > 4
|
|
186
|
+
? value.substring(0, 4) + '*'.repeat(Math.min(value.length - 4, 20))
|
|
187
|
+
: '****';
|
|
188
|
+
console.log(` ${key}=${masked}`);
|
|
189
|
+
}
|
|
190
|
+
console.log();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
export default SecretsManager;
|