lsh-framework 3.1.0 ā 3.1.2
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/dist/commands/config.js +2 -1
- package/dist/commands/doctor.js +31 -14
- package/dist/commands/init.js +29 -10
- package/dist/commands/self.js +4 -3
- package/dist/constants/api.js +34 -1
- package/dist/constants/config.js +61 -1
- package/dist/constants/database.js +21 -0
- package/dist/constants/errors.js +24 -0
- package/dist/constants/paths.js +3 -0
- package/dist/constants/ui.js +80 -0
- package/dist/daemon/job-registry.js +2 -1
- package/dist/daemon/lshd.js +14 -15
- package/dist/daemon/saas-api-server.js +9 -7
- package/dist/lib/base-job-manager.js +2 -1
- package/dist/lib/daemon-client.js +2 -1
- package/dist/lib/database-persistence.js +2 -1
- package/dist/lib/ipfs-secrets-storage.js +2 -1
- package/dist/lib/ipfs-sync-logger.js +3 -2
- package/dist/lib/logger.js +5 -4
- package/dist/lib/lsh-config.js +2 -1
- package/dist/lib/lshrc-init.js +3 -2
- package/dist/lib/platform-utils.js +5 -4
- package/dist/lib/saas-auth.js +4 -3
- package/dist/lib/saas-billing.js +7 -6
- package/dist/lib/saas-email.js +4 -3
- package/dist/lib/saas-encryption.js +2 -1
- package/dist/lib/secrets-manager.js +13 -11
- package/dist/lib/storacha-client.js +3 -2
- package/dist/lib/supabase-client.js +7 -6
- package/dist/services/secrets/secrets.js +6 -2
- package/package.json +2 -2
package/dist/commands/config.js
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
6
|
import { getConfigManager, loadGlobalConfig } from '../lib/config-manager.js';
|
|
7
7
|
import * as fs from 'fs/promises';
|
|
8
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
8
9
|
/**
|
|
9
10
|
* Get user's preferred editor
|
|
10
11
|
*/
|
|
11
12
|
function getEditor() {
|
|
12
|
-
return process.env.VISUAL || process.env.EDITOR || 'vi';
|
|
13
|
+
return process.env[ENV_VARS.VISUAL] || process.env[ENV_VARS.EDITOR] || 'vi';
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Open config file in user's editor
|
package/dist/commands/doctor.js
CHANGED
|
@@ -8,6 +8,7 @@ import * as path from 'path';
|
|
|
8
8
|
import { createClient } from '@supabase/supabase-js';
|
|
9
9
|
import { getPlatformPaths, getPlatformInfo } from '../lib/platform-utils.js';
|
|
10
10
|
import { IPFSClientManager } from '../lib/ipfs-client-manager.js';
|
|
11
|
+
import * as os from 'os';
|
|
11
12
|
/**
|
|
12
13
|
* Register doctor commands
|
|
13
14
|
*/
|
|
@@ -15,6 +16,7 @@ export function registerDoctorCommands(program) {
|
|
|
15
16
|
program
|
|
16
17
|
.command('doctor')
|
|
17
18
|
.description('Health check and troubleshooting')
|
|
19
|
+
.option('-g, --global', 'Use global workspace ($HOME)')
|
|
18
20
|
.option('-v, --verbose', 'Show detailed information')
|
|
19
21
|
.option('--json', 'Output results as JSON')
|
|
20
22
|
.action(async (options) => {
|
|
@@ -28,12 +30,25 @@ export function registerDoctorCommands(program) {
|
|
|
28
30
|
}
|
|
29
31
|
});
|
|
30
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the base directory for .env files
|
|
35
|
+
*/
|
|
36
|
+
function getBaseDir(globalMode) {
|
|
37
|
+
return globalMode ? os.homedir() : process.cwd();
|
|
38
|
+
}
|
|
31
39
|
/**
|
|
32
40
|
* Run comprehensive health check
|
|
33
41
|
*/
|
|
34
42
|
async function runHealthCheck(options) {
|
|
43
|
+
const baseDir = getBaseDir(options.global);
|
|
35
44
|
if (!options.json) {
|
|
36
|
-
|
|
45
|
+
if (options.global) {
|
|
46
|
+
console.log(chalk.bold.cyan('\nš„ LSH Health Check (Global Workspace)'));
|
|
47
|
+
console.log(chalk.yellow(` Location: ${baseDir}`));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.log(chalk.bold.cyan('\nš„ LSH Health Check'));
|
|
51
|
+
}
|
|
37
52
|
console.log(chalk.gray('ā'.repeat(50)));
|
|
38
53
|
console.log('');
|
|
39
54
|
}
|
|
@@ -41,18 +56,20 @@ async function runHealthCheck(options) {
|
|
|
41
56
|
// Platform check
|
|
42
57
|
checks.push(await checkPlatform(options.verbose));
|
|
43
58
|
// .env file check
|
|
44
|
-
checks.push(await checkEnvFile(options.verbose));
|
|
59
|
+
checks.push(await checkEnvFile(options.verbose, baseDir));
|
|
45
60
|
// Encryption key check
|
|
46
|
-
checks.push(await checkEncryptionKey(options.verbose));
|
|
61
|
+
checks.push(await checkEncryptionKey(options.verbose, baseDir));
|
|
47
62
|
// Storage backend check
|
|
48
|
-
const storageChecks = await checkStorageBackend(options.verbose);
|
|
63
|
+
const storageChecks = await checkStorageBackend(options.verbose, baseDir);
|
|
49
64
|
checks.push(...storageChecks);
|
|
50
|
-
// Git repository check
|
|
51
|
-
|
|
65
|
+
// Git repository check (skip for global mode)
|
|
66
|
+
if (!options.global) {
|
|
67
|
+
checks.push(await checkGitRepository(options.verbose));
|
|
68
|
+
}
|
|
52
69
|
// IPFS client check
|
|
53
70
|
checks.push(await checkIPFSClient(options.verbose));
|
|
54
71
|
// Permissions check
|
|
55
|
-
checks.push(await checkPermissions(options.verbose));
|
|
72
|
+
checks.push(await checkPermissions(options.verbose, baseDir));
|
|
56
73
|
// Display results
|
|
57
74
|
if (options.json) {
|
|
58
75
|
console.log(JSON.stringify({ checks, summary: getSummary(checks) }, null, 2));
|
|
@@ -86,9 +103,9 @@ async function checkPlatform(verbose) {
|
|
|
86
103
|
/**
|
|
87
104
|
* Check .env file
|
|
88
105
|
*/
|
|
89
|
-
async function checkEnvFile(verbose) {
|
|
106
|
+
async function checkEnvFile(verbose, baseDir) {
|
|
90
107
|
try {
|
|
91
|
-
const envPath = path.join(process.cwd(), '.env');
|
|
108
|
+
const envPath = path.join(baseDir || process.cwd(), '.env');
|
|
92
109
|
// Read file directly without access check to avoid TOCTOU race condition
|
|
93
110
|
const content = await fs.readFile(envPath, 'utf-8');
|
|
94
111
|
const lines = content.split('\n').filter(l => l.trim() && !l.startsWith('#'));
|
|
@@ -111,9 +128,9 @@ async function checkEnvFile(verbose) {
|
|
|
111
128
|
/**
|
|
112
129
|
* Check encryption key
|
|
113
130
|
*/
|
|
114
|
-
async function checkEncryptionKey(verbose) {
|
|
131
|
+
async function checkEncryptionKey(verbose, baseDir) {
|
|
115
132
|
try {
|
|
116
|
-
const envPath = path.join(process.cwd(), '.env');
|
|
133
|
+
const envPath = path.join(baseDir || process.cwd(), '.env');
|
|
117
134
|
const content = await fs.readFile(envPath, 'utf-8');
|
|
118
135
|
const match = content.match(/^LSH_SECRETS_KEY=(.+)$/m);
|
|
119
136
|
if (!match) {
|
|
@@ -163,10 +180,10 @@ async function checkEncryptionKey(verbose) {
|
|
|
163
180
|
/**
|
|
164
181
|
* Check storage backend configuration
|
|
165
182
|
*/
|
|
166
|
-
async function checkStorageBackend(verbose) {
|
|
183
|
+
async function checkStorageBackend(verbose, baseDir) {
|
|
167
184
|
const checks = [];
|
|
168
185
|
try {
|
|
169
|
-
const envPath = path.join(process.cwd(), '.env');
|
|
186
|
+
const envPath = path.join(baseDir || process.cwd(), '.env');
|
|
170
187
|
const content = await fs.readFile(envPath, 'utf-8');
|
|
171
188
|
const supabaseUrl = content.match(/^SUPABASE_URL=(.+)$/m)?.[1]?.trim();
|
|
172
189
|
const supabaseKey = content.match(/^SUPABASE_ANON_KEY=(.+)$/m)?.[1]?.trim();
|
|
@@ -332,7 +349,7 @@ async function checkIPFSClient(verbose) {
|
|
|
332
349
|
/**
|
|
333
350
|
* Check file permissions
|
|
334
351
|
*/
|
|
335
|
-
async function checkPermissions(verbose) {
|
|
352
|
+
async function checkPermissions(verbose, _baseDir) {
|
|
336
353
|
try {
|
|
337
354
|
const paths = getPlatformPaths();
|
|
338
355
|
// Check if we can write to temp directory with secure permissions
|
package/dist/commands/init.js
CHANGED
|
@@ -11,6 +11,7 @@ import { createClient } from '@supabase/supabase-js';
|
|
|
11
11
|
import ora from 'ora';
|
|
12
12
|
import { getPlatformPaths } from '../lib/platform-utils.js';
|
|
13
13
|
import { getGitRepoInfo } from '../lib/git-utils.js';
|
|
14
|
+
import * as os from 'os';
|
|
14
15
|
/**
|
|
15
16
|
* Register init commands
|
|
16
17
|
*/
|
|
@@ -18,6 +19,7 @@ export function registerInitCommands(program) {
|
|
|
18
19
|
program
|
|
19
20
|
.command('init')
|
|
20
21
|
.description('Interactive setup wizard (first-time configuration)')
|
|
22
|
+
.option('-g, --global', 'Use global workspace ($HOME)')
|
|
21
23
|
.option('--local', 'Use local-only encryption (no cloud sync)')
|
|
22
24
|
.option('--storacha', 'Use Storacha IPFS network sync (recommended)')
|
|
23
25
|
.option('--supabase', 'Use Supabase cloud storage')
|
|
@@ -34,15 +36,30 @@ export function registerInitCommands(program) {
|
|
|
34
36
|
}
|
|
35
37
|
});
|
|
36
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the base directory for .env files
|
|
41
|
+
*/
|
|
42
|
+
function getBaseDir(globalMode) {
|
|
43
|
+
return globalMode ? os.homedir() : process.cwd();
|
|
44
|
+
}
|
|
37
45
|
/**
|
|
38
46
|
* Run the interactive setup wizard
|
|
39
47
|
*/
|
|
40
48
|
async function runSetupWizard(options) {
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
const globalMode = options.global ?? false;
|
|
50
|
+
const baseDir = getBaseDir(globalMode);
|
|
51
|
+
if (globalMode) {
|
|
52
|
+
console.log(chalk.bold.cyan('\nš LSH Secrets Manager - Global Setup Wizard'));
|
|
53
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
54
|
+
console.log(chalk.yellow(`\nš Global Mode: Using $HOME (${baseDir})`));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(chalk.bold.cyan('\nš LSH Secrets Manager - Setup Wizard'));
|
|
58
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
59
|
+
}
|
|
43
60
|
console.log('');
|
|
44
61
|
// Check if already configured
|
|
45
|
-
const existingConfig = await checkExistingConfig();
|
|
62
|
+
const existingConfig = await checkExistingConfig(baseDir);
|
|
46
63
|
if (existingConfig) {
|
|
47
64
|
const { overwrite } = await inquirer.prompt([
|
|
48
65
|
{
|
|
@@ -181,16 +198,16 @@ async function runSetupWizard(options) {
|
|
|
181
198
|
}
|
|
182
199
|
}
|
|
183
200
|
// Save configuration
|
|
184
|
-
await saveConfiguration(config);
|
|
201
|
+
await saveConfiguration(config, baseDir, globalMode);
|
|
185
202
|
// Show success message
|
|
186
203
|
showSuccessMessage(config);
|
|
187
204
|
}
|
|
188
205
|
/**
|
|
189
206
|
* Check if LSH is already configured
|
|
190
207
|
*/
|
|
191
|
-
async function checkExistingConfig() {
|
|
208
|
+
async function checkExistingConfig(baseDir) {
|
|
192
209
|
try {
|
|
193
|
-
const envPath = path.join(
|
|
210
|
+
const envPath = path.join(baseDir, '.env');
|
|
194
211
|
// Read file directly without access check to avoid TOCTOU race condition
|
|
195
212
|
const content = await fs.readFile(envPath, 'utf-8');
|
|
196
213
|
return content.includes('LSH_SECRETS_KEY') ||
|
|
@@ -417,10 +434,10 @@ function generateEncryptionKey() {
|
|
|
417
434
|
/**
|
|
418
435
|
* Save configuration to .env file
|
|
419
436
|
*/
|
|
420
|
-
async function saveConfiguration(config) {
|
|
437
|
+
async function saveConfiguration(config, baseDir, globalMode) {
|
|
421
438
|
const spinner = ora('Saving configuration...').start();
|
|
422
439
|
try {
|
|
423
|
-
const envPath = path.join(
|
|
440
|
+
const envPath = path.join(baseDir, '.env');
|
|
424
441
|
let envContent = '';
|
|
425
442
|
// Try to read existing .env
|
|
426
443
|
try {
|
|
@@ -461,8 +478,10 @@ async function saveConfiguration(config) {
|
|
|
461
478
|
}
|
|
462
479
|
// Write .env file
|
|
463
480
|
await fs.writeFile(envPath, envContent, 'utf-8');
|
|
464
|
-
// Update .gitignore
|
|
465
|
-
|
|
481
|
+
// Update .gitignore (skip for global mode since it's in $HOME)
|
|
482
|
+
if (!globalMode) {
|
|
483
|
+
await updateGitignore();
|
|
484
|
+
}
|
|
466
485
|
spinner.succeed(chalk.green('ā
Configuration saved'));
|
|
467
486
|
}
|
|
468
487
|
catch (error) {
|
package/dist/commands/self.js
CHANGED
|
@@ -9,6 +9,7 @@ import * as fs from 'fs';
|
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import chalk from 'chalk';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
12
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
14
|
const __dirname = path.dirname(__filename);
|
|
14
15
|
const selfCommand = new Command('self');
|
|
@@ -300,9 +301,9 @@ selfCommand
|
|
|
300
301
|
console.log();
|
|
301
302
|
// Environment
|
|
302
303
|
console.log(chalk.yellow('Environment:'));
|
|
303
|
-
console.log(' NODE_ENV:', process.env.NODE_ENV || 'not set');
|
|
304
|
-
console.log(' HOME:', process.env.HOME || 'not set');
|
|
305
|
-
console.log(' USER:', process.env.USER || 'not set');
|
|
304
|
+
console.log(' NODE_ENV:', process.env[ENV_VARS.NODE_ENV] || 'not set');
|
|
305
|
+
console.log(' HOME:', process.env[ENV_VARS.HOME] || 'not set');
|
|
306
|
+
console.log(' USER:', process.env[ENV_VARS.USER] || 'not set');
|
|
306
307
|
console.log();
|
|
307
308
|
// Configuration
|
|
308
309
|
const envFile = path.join(process.cwd(), '.env');
|
package/dist/constants/api.js
CHANGED
|
@@ -7,7 +7,7 @@ export const ENDPOINTS = {
|
|
|
7
7
|
// Health and status
|
|
8
8
|
HEALTH: '/health',
|
|
9
9
|
ROOT: '/',
|
|
10
|
-
// Authentication
|
|
10
|
+
// Authentication (legacy)
|
|
11
11
|
AUTH: '/api/auth',
|
|
12
12
|
AUTH_REGISTER: '/auth/register',
|
|
13
13
|
AUTH_LOGIN: '/auth/login',
|
|
@@ -48,6 +48,39 @@ export const ENDPOINTS = {
|
|
|
48
48
|
API_ANALYTICS_PREDICTIONS: '/api/analytics/predictions',
|
|
49
49
|
API_ANALYTICS_COSTS: '/api/analytics/costs',
|
|
50
50
|
API_ANALYTICS_BOTTLENECKS: '/api/analytics/bottlenecks',
|
|
51
|
+
// SaaS API v1 - Authentication
|
|
52
|
+
API_V1_AUTH_SIGNUP: '/api/v1/auth/signup',
|
|
53
|
+
API_V1_AUTH_LOGIN: '/api/v1/auth/login',
|
|
54
|
+
API_V1_AUTH_VERIFY_EMAIL: '/api/v1/auth/verify-email',
|
|
55
|
+
API_V1_AUTH_RESEND_VERIFICATION: '/api/v1/auth/resend-verification',
|
|
56
|
+
API_V1_AUTH_REFRESH: '/api/v1/auth/refresh',
|
|
57
|
+
API_V1_AUTH_ME: '/api/v1/auth/me',
|
|
58
|
+
// SaaS API v1 - Organizations
|
|
59
|
+
API_V1_ORGANIZATIONS: '/api/v1/organizations',
|
|
60
|
+
API_V1_ORGANIZATION: '/api/v1/organizations/:organizationId',
|
|
61
|
+
API_V1_ORGANIZATION_MEMBERS: '/api/v1/organizations/:organizationId/members',
|
|
62
|
+
API_V1_ORGANIZATION_TEAMS: '/api/v1/organizations/:organizationId/teams',
|
|
63
|
+
API_V1_ORGANIZATION_AUDIT: '/api/v1/organizations/:organizationId/audit',
|
|
64
|
+
API_V1_ORGANIZATION_BILLING_SUBSCRIPTION: '/api/v1/organizations/:organizationId/billing/subscription',
|
|
65
|
+
API_V1_ORGANIZATION_BILLING_CHECKOUT: '/api/v1/organizations/:organizationId/billing/checkout',
|
|
66
|
+
// SaaS API v1 - Teams
|
|
67
|
+
API_V1_TEAM_SECRETS: '/api/v1/teams/:teamId/secrets',
|
|
68
|
+
API_V1_TEAM_SECRET: '/api/v1/teams/:teamId/secrets/:secretId',
|
|
69
|
+
API_V1_TEAM_SECRET_RETRIEVE: '/api/v1/teams/:teamId/secrets/:secretId/retrieve',
|
|
70
|
+
API_V1_TEAM_SECRETS_EXPORT: '/api/v1/teams/:teamId/secrets/export/env',
|
|
71
|
+
API_V1_TEAM_SECRETS_IMPORT: '/api/v1/teams/:teamId/secrets/import/env',
|
|
72
|
+
// SaaS API v1 - Webhooks
|
|
73
|
+
API_V1_WEBHOOKS_STRIPE: '/api/v1/webhooks/stripe',
|
|
74
|
+
API_V1_BILLING_WEBHOOKS: '/api/v1/billing/webhooks',
|
|
75
|
+
API_V1_ORGANIZATION_AUDIT_LOGS: '/api/v1/organizations/:organizationId/audit-logs',
|
|
76
|
+
// API Info/Documentation (patterns)
|
|
77
|
+
API_V1_ROOT: '/api/v1',
|
|
78
|
+
API_V1_AUTH_WILDCARD: '/api/v1/auth/*',
|
|
79
|
+
API_V1_ORGANIZATIONS_WILDCARD: '/api/v1/organizations/*',
|
|
80
|
+
API_V1_ORG_TEAMS_WILDCARD: '/api/v1/organizations/:id/teams/*',
|
|
81
|
+
API_V1_TEAMS_SECRETS_WILDCARD: '/api/v1/teams/:id/secrets/*',
|
|
82
|
+
API_V1_ORG_BILLING_WILDCARD: '/api/v1/organizations/:id/billing/*',
|
|
83
|
+
API_V1_ORG_AUDIT_LOGS: '/api/v1/organizations/:id/audit-logs',
|
|
51
84
|
};
|
|
52
85
|
export const HTTP_HEADERS = {
|
|
53
86
|
// Standard headers
|
package/dist/constants/config.js
CHANGED
|
@@ -8,14 +8,37 @@ export const ENV_VARS = {
|
|
|
8
8
|
NODE_ENV: 'NODE_ENV',
|
|
9
9
|
USER: 'USER',
|
|
10
10
|
HOSTNAME: 'HOSTNAME',
|
|
11
|
+
HOME: 'HOME',
|
|
12
|
+
SHELL: 'SHELL',
|
|
13
|
+
EDITOR: 'EDITOR',
|
|
14
|
+
VISUAL: 'VISUAL',
|
|
15
|
+
// Platform-specific
|
|
16
|
+
USERPROFILE: 'USERPROFILE',
|
|
17
|
+
APPDATA: 'APPDATA',
|
|
18
|
+
LOCALAPPDATA: 'LOCALAPPDATA',
|
|
19
|
+
COMSPEC: 'COMSPEC',
|
|
11
20
|
// LSH API configuration
|
|
12
21
|
LSH_API_ENABLED: 'LSH_API_ENABLED',
|
|
13
22
|
LSH_API_PORT: 'LSH_API_PORT',
|
|
14
23
|
LSH_API_KEY: 'LSH_API_KEY',
|
|
15
24
|
LSH_JWT_SECRET: 'LSH_JWT_SECRET',
|
|
16
25
|
LSH_ALLOW_DANGEROUS_COMMANDS: 'LSH_ALLOW_DANGEROUS_COMMANDS',
|
|
26
|
+
// LSH SaaS API
|
|
27
|
+
LSH_SAAS_API_PORT: 'LSH_SAAS_API_PORT',
|
|
28
|
+
LSH_SAAS_API_HOST: 'LSH_SAAS_API_HOST',
|
|
29
|
+
LSH_CORS_ORIGINS: 'LSH_CORS_ORIGINS',
|
|
17
30
|
// Secrets management
|
|
18
31
|
LSH_SECRETS_KEY: 'LSH_SECRETS_KEY',
|
|
32
|
+
LSH_MASTER_KEY: 'LSH_MASTER_KEY',
|
|
33
|
+
// Feature flags
|
|
34
|
+
LSH_LOCAL_STORAGE_QUIET: 'LSH_LOCAL_STORAGE_QUIET',
|
|
35
|
+
LSH_V1_COMPAT: 'LSH_V1_COMPAT',
|
|
36
|
+
LSH_STORACHA_ENABLED: 'LSH_STORACHA_ENABLED',
|
|
37
|
+
DISABLE_IPFS_SYNC: 'DISABLE_IPFS_SYNC',
|
|
38
|
+
// Logging
|
|
39
|
+
LSH_LOG_LEVEL: 'LSH_LOG_LEVEL',
|
|
40
|
+
LSH_LOG_FORMAT: 'LSH_LOG_FORMAT',
|
|
41
|
+
NO_COLOR: 'NO_COLOR',
|
|
19
42
|
// Webhooks
|
|
20
43
|
LSH_ENABLE_WEBHOOKS: 'LSH_ENABLE_WEBHOOKS',
|
|
21
44
|
WEBHOOK_PORT: 'WEBHOOK_PORT',
|
|
@@ -29,28 +52,65 @@ export const ENV_VARS = {
|
|
|
29
52
|
REDIS_URL: 'REDIS_URL',
|
|
30
53
|
// Monitoring
|
|
31
54
|
MONITORING_API_PORT: 'MONITORING_API_PORT',
|
|
55
|
+
// Stripe billing
|
|
56
|
+
STRIPE_SECRET_KEY: 'STRIPE_SECRET_KEY',
|
|
57
|
+
STRIPE_WEBHOOK_SECRET: 'STRIPE_WEBHOOK_SECRET',
|
|
58
|
+
STRIPE_PRICE_PRO_MONTHLY: 'STRIPE_PRICE_PRO_MONTHLY',
|
|
59
|
+
STRIPE_PRICE_PRO_YEARLY: 'STRIPE_PRICE_PRO_YEARLY',
|
|
60
|
+
STRIPE_PRICE_ENTERPRISE_MONTHLY: 'STRIPE_PRICE_ENTERPRISE_MONTHLY',
|
|
61
|
+
STRIPE_PRICE_ENTERPRISE_YEARLY: 'STRIPE_PRICE_ENTERPRISE_YEARLY',
|
|
62
|
+
// Email (Resend)
|
|
63
|
+
RESEND_API_KEY: 'RESEND_API_KEY',
|
|
64
|
+
EMAIL_FROM: 'EMAIL_FROM',
|
|
65
|
+
BASE_URL: 'BASE_URL',
|
|
32
66
|
};
|
|
33
67
|
export const DEFAULTS = {
|
|
34
68
|
// Version
|
|
35
69
|
VERSION: '0.5.1',
|
|
36
70
|
// Ports
|
|
37
71
|
API_PORT: 3030,
|
|
72
|
+
SAAS_API_PORT: 3031,
|
|
38
73
|
WEBHOOK_PORT: 3033,
|
|
39
74
|
MONITORING_API_PORT: 3031,
|
|
75
|
+
MONITORING_PORT: 3000,
|
|
76
|
+
// Hosts
|
|
77
|
+
DEFAULT_HOST: '0.0.0.0',
|
|
78
|
+
DEFAULT_CORS_ORIGINS: ['*'],
|
|
40
79
|
// URLs
|
|
41
80
|
REDIS_URL: 'redis://localhost:6379',
|
|
42
81
|
DATABASE_URL: 'postgresql://localhost:5432/cicd',
|
|
43
|
-
|
|
82
|
+
STRIPE_API_URL: 'https://api.stripe.com/v1',
|
|
83
|
+
RESEND_API_URL: 'https://api.resend.com/emails',
|
|
84
|
+
LSH_APP_URL: 'https://app.lsh.dev',
|
|
85
|
+
LSH_DOCS_URL: 'https://docs.lsh.dev',
|
|
86
|
+
KUBO_RELEASES_URL: 'https://api.github.com/repos/ipfs/kubo/releases/latest',
|
|
87
|
+
// Email defaults
|
|
88
|
+
DEFAULT_EMAIL_FROM: 'noreply@lsh.dev',
|
|
89
|
+
// Timeouts and intervals (in milliseconds)
|
|
44
90
|
CHECK_INTERVAL_MS: 2000,
|
|
45
91
|
REQUEST_TIMEOUT_MS: 10000,
|
|
92
|
+
DAEMON_RESTART_DELAY_MS: 1000,
|
|
93
|
+
JOB_TIMEOUT_1H_MS: 3600000,
|
|
94
|
+
JOB_TIMEOUT_5M_MS: 300000,
|
|
95
|
+
JOB_TIMEOUT_1M_MS: 60000,
|
|
96
|
+
JOB_TIMEOUT_2H_MS: 7200000,
|
|
46
97
|
// Sizes
|
|
47
98
|
MAX_BUFFER_SIZE_BYTES: 1024 * 1024, // 1MB
|
|
48
99
|
MAX_LOG_SIZE_BYTES: 10 * 1024 * 1024, // 10MB
|
|
49
100
|
MAX_COMMAND_LENGTH: 10000,
|
|
101
|
+
SOCKET_BUFFER_SIZE: 1024,
|
|
102
|
+
MAX_EVENTS_LIMIT: 100,
|
|
103
|
+
// History limits
|
|
104
|
+
MAX_HISTORY_SIZE: 10000,
|
|
105
|
+
HISTORY_SAVE_INTERVAL_MS: 30000,
|
|
50
106
|
// Limits
|
|
51
107
|
MAX_COMMAND_CHAINS: 5,
|
|
52
108
|
MAX_PIPE_USAGE: 3,
|
|
53
109
|
// Cache and retention
|
|
54
110
|
REDIS_CACHE_EXPIRY_SECONDS: 3600, // 1 hour
|
|
55
111
|
METRICS_RETENTION_SECONDS: 30 * 24 * 60 * 60, // 30 days
|
|
112
|
+
// Shell defaults
|
|
113
|
+
DEFAULT_SHELL_UNIX: '/bin/sh',
|
|
114
|
+
DEFAULT_SHELL_WIN: 'cmd.exe',
|
|
115
|
+
DEFAULT_EDITOR: 'vi',
|
|
56
116
|
};
|
|
@@ -14,6 +14,27 @@ export const TABLES = {
|
|
|
14
14
|
SHELL_COMPLETIONS: 'shell_completions',
|
|
15
15
|
// CI/CD tables
|
|
16
16
|
PIPELINE_EVENTS: 'pipeline_events',
|
|
17
|
+
// SaaS tables - Users and Authentication
|
|
18
|
+
USERS: 'users',
|
|
19
|
+
USER_SESSIONS: 'user_sessions',
|
|
20
|
+
// SaaS tables - Organizations and Teams
|
|
21
|
+
ORGANIZATIONS: 'organizations',
|
|
22
|
+
ORGANIZATION_MEMBERS: 'organization_members',
|
|
23
|
+
TEAMS: 'teams',
|
|
24
|
+
TEAM_MEMBERS: 'team_members',
|
|
25
|
+
// SaaS tables - Secrets
|
|
26
|
+
SECRETS: 'secrets',
|
|
27
|
+
SECRET_ACCESS_LOGS: 'secret_access_logs',
|
|
28
|
+
SECRET_VERSIONS: 'secret_versions',
|
|
29
|
+
// SaaS tables - Audit
|
|
30
|
+
AUDIT_LOGS: 'audit_logs',
|
|
31
|
+
// SaaS tables - Billing
|
|
32
|
+
SUBSCRIPTIONS: 'subscriptions',
|
|
33
|
+
INVOICES: 'invoices',
|
|
34
|
+
// SaaS tables - Encryption
|
|
35
|
+
ENCRYPTION_KEYS: 'encryption_keys',
|
|
36
|
+
// LSH Secrets (legacy name)
|
|
37
|
+
LSH_SECRETS: 'lsh_secrets',
|
|
17
38
|
// Trading/ML tables (legacy)
|
|
18
39
|
TRADING_DISCLOSURES: 'trading_disclosures',
|
|
19
40
|
POLITICIANS: 'politicians',
|
package/dist/constants/errors.js
CHANGED
|
@@ -77,3 +77,27 @@ export const RISK_LEVELS = {
|
|
|
77
77
|
MEDIUM: 'medium',
|
|
78
78
|
LOW: 'low',
|
|
79
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* API Error Codes
|
|
82
|
+
*
|
|
83
|
+
* Standard error codes used across the SaaS API and services.
|
|
84
|
+
*/
|
|
85
|
+
export const ERROR_CODES = {
|
|
86
|
+
// Authentication errors
|
|
87
|
+
UNAUTHORIZED: 'UNAUTHORIZED',
|
|
88
|
+
INVALID_CREDENTIALS: 'INVALID_CREDENTIALS',
|
|
89
|
+
INVALID_TOKEN: 'INVALID_TOKEN',
|
|
90
|
+
EMAIL_NOT_VERIFIED: 'EMAIL_NOT_VERIFIED',
|
|
91
|
+
EMAIL_ALREADY_EXISTS: 'EMAIL_ALREADY_EXISTS',
|
|
92
|
+
// Authorization errors
|
|
93
|
+
FORBIDDEN: 'FORBIDDEN',
|
|
94
|
+
// Validation errors
|
|
95
|
+
INVALID_INPUT: 'INVALID_INPUT',
|
|
96
|
+
NOT_FOUND: 'NOT_FOUND',
|
|
97
|
+
ALREADY_EXISTS: 'ALREADY_EXISTS',
|
|
98
|
+
// Payment errors
|
|
99
|
+
PAYMENT_REQUIRED: 'PAYMENT_REQUIRED',
|
|
100
|
+
TIER_LIMIT_EXCEEDED: 'TIER_LIMIT_EXCEEDED',
|
|
101
|
+
// Server errors
|
|
102
|
+
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
|
103
|
+
};
|
package/dist/constants/paths.js
CHANGED
|
@@ -17,6 +17,9 @@ export const PATHS = {
|
|
|
17
17
|
DAEMON_JOBS_FILE_TEMPLATE: '/tmp/lsh-daemon-jobs-${USER}.json',
|
|
18
18
|
// Job persistence
|
|
19
19
|
DEFAULT_JOBS_PERSISTENCE_FILE: '/tmp/lsh-jobs.json',
|
|
20
|
+
// Job registry files
|
|
21
|
+
JOB_REGISTRY_FILE: '/tmp/lsh-job-registry.json',
|
|
22
|
+
JOB_LOGS_DIR: '/tmp/lsh-job-logs',
|
|
20
23
|
};
|
|
21
24
|
export const PREFIXES = {
|
|
22
25
|
SESSION_ID: 'lsh_',
|
package/dist/constants/ui.js
CHANGED
|
@@ -71,3 +71,83 @@ export const LOG_LEVELS = {
|
|
|
71
71
|
ERROR: 'ERROR',
|
|
72
72
|
DEBUG: 'DEBUG',
|
|
73
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Emoji prefixes for consistent UI output
|
|
76
|
+
*/
|
|
77
|
+
export const EMOJI = {
|
|
78
|
+
SUCCESS: 'ā
',
|
|
79
|
+
ERROR: 'ā',
|
|
80
|
+
WARNING: 'ā ļø',
|
|
81
|
+
INFO: 'ā¹ļø',
|
|
82
|
+
TIP: 'š”',
|
|
83
|
+
KEY: 'š',
|
|
84
|
+
FILE: 'š',
|
|
85
|
+
FOLDER: 'š',
|
|
86
|
+
LIST: 'š',
|
|
87
|
+
SEARCH: 'š',
|
|
88
|
+
LOCATION: 'š',
|
|
89
|
+
UP: 'ā¬ļø',
|
|
90
|
+
DOWN: 'ā¬ļø',
|
|
91
|
+
CALENDAR: 'š
',
|
|
92
|
+
GEAR: 'āļø',
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Status messages with emoji
|
|
96
|
+
*/
|
|
97
|
+
export const STATUS_MESSAGES = {
|
|
98
|
+
// Success messages
|
|
99
|
+
SUCCESS: 'ā
',
|
|
100
|
+
SUCCESS_GENERIC: 'ā
Success',
|
|
101
|
+
CONNECTION_SUCCESS: 'ā
Connection successful!',
|
|
102
|
+
CONFIG_SAVED: 'ā
Configuration saved',
|
|
103
|
+
SECRETS_PULLED: 'ā
Secrets pulled successfully!',
|
|
104
|
+
IPFS_INSTALLED: 'ā
IPFS client installed',
|
|
105
|
+
// Error messages
|
|
106
|
+
ERROR: 'ā',
|
|
107
|
+
ERROR_GENERIC: 'ā Error',
|
|
108
|
+
CONNECTION_FAILED: 'ā Connection failed',
|
|
109
|
+
CONFIG_SAVE_FAILED: 'ā Failed to save configuration',
|
|
110
|
+
PULL_FAILED: 'ā Failed to pull secrets',
|
|
111
|
+
// Warning messages
|
|
112
|
+
WARNING: 'ā ļø',
|
|
113
|
+
WARNING_GENERIC: 'ā ļø Warning',
|
|
114
|
+
IPFS_NOT_INSTALLED: 'ā ļø IPFS client not installed',
|
|
115
|
+
NOT_GIT_REPO: 'ā¹ļø Not in a git repository',
|
|
116
|
+
// Info messages
|
|
117
|
+
INFO: 'ā¹ļø',
|
|
118
|
+
RECOMMENDATIONS: 'š” Recommendations:',
|
|
119
|
+
CURRENT_REPO: 'š Current Repository:',
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Doctor/diagnostic messages
|
|
123
|
+
*/
|
|
124
|
+
export const DOCTOR_MESSAGES = {
|
|
125
|
+
CHECKING: 'š Checking:',
|
|
126
|
+
ALL_PASSED: 'ā
All checks passed!',
|
|
127
|
+
ISSUES_FOUND: 'ā Issues found',
|
|
128
|
+
RECOMMENDATIONS: 'š” Recommendations:',
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Init/setup messages
|
|
132
|
+
*/
|
|
133
|
+
export const INIT_MESSAGES = {
|
|
134
|
+
WELCOME: 'š Welcome to LSH Setup',
|
|
135
|
+
STEP_COMPLETE: 'ā
Step complete',
|
|
136
|
+
SETUP_COMPLETE: 'ā
Setup complete!',
|
|
137
|
+
CONNECTION_TEST_SKIPPED: 'ā ļø Connection test skipped. Run "lsh doctor" after setup to verify.',
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Migration messages
|
|
141
|
+
*/
|
|
142
|
+
export const MIGRATION_MESSAGES = {
|
|
143
|
+
SCANNING: 'š Scanning for Firebase references...',
|
|
144
|
+
MIGRATING: 'š Migrating...',
|
|
145
|
+
COMPLETE: 'ā
Migration complete',
|
|
146
|
+
NO_CHANGES: 'ā¹ļø No changes needed',
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Deprecation warnings
|
|
150
|
+
*/
|
|
151
|
+
export const DEPRECATION_WARNINGS = {
|
|
152
|
+
LIB_COMMANDS: '\x1b[33mā ļø WARNING: "lsh lib" commands are deprecated as of v1.0.0\x1b[0m',
|
|
153
|
+
};
|
|
@@ -11,6 +11,7 @@ import * as os from 'os';
|
|
|
11
11
|
import { exec } from 'child_process';
|
|
12
12
|
import { BaseJobManager } from '../lib/base-job-manager.js';
|
|
13
13
|
import MemoryJobStorage from '../lib/job-storage-memory.js';
|
|
14
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
14
15
|
export class JobRegistry extends BaseJobManager {
|
|
15
16
|
config;
|
|
16
17
|
records = new Map(); // jobId -> execution records
|
|
@@ -47,7 +48,7 @@ export class JobRegistry extends BaseJobManager {
|
|
|
47
48
|
outputSize: 0,
|
|
48
49
|
environment: { ...(job.env || {}) },
|
|
49
50
|
workingDirectory: job.cwd || process.cwd(),
|
|
50
|
-
user: job.user || process.env.USER || 'unknown',
|
|
51
|
+
user: job.user || process.env[ENV_VARS.USER] || 'unknown',
|
|
51
52
|
hostname: os.hostname(),
|
|
52
53
|
tags: [...(job.tags || [])],
|
|
53
54
|
priority: job.priority || 5,
|
package/dist/daemon/lshd.js
CHANGED
|
@@ -14,6 +14,7 @@ import { validateCommand } from '../lib/command-validator.js';
|
|
|
14
14
|
import { validateEnvironment, printValidationResults } from '../lib/env-validator.js';
|
|
15
15
|
import { createLogger } from '../lib/logger.js';
|
|
16
16
|
import { getPlatformPaths } from '../lib/platform-utils.js';
|
|
17
|
+
import { ENV_VARS, DEFAULTS, ERRORS } from '../constants/index.js';
|
|
17
18
|
const execAsync = promisify(exec);
|
|
18
19
|
export class LSHJobDaemon extends EventEmitter {
|
|
19
20
|
config;
|
|
@@ -34,13 +35,13 @@ export class LSHJobDaemon extends EventEmitter {
|
|
|
34
35
|
logFile: platformPaths.logFile,
|
|
35
36
|
jobsFile: jobsFilePath,
|
|
36
37
|
socketPath: platformPaths.socketPath,
|
|
37
|
-
checkInterval:
|
|
38
|
-
maxLogSize:
|
|
38
|
+
checkInterval: DEFAULTS.CHECK_INTERVAL_MS,
|
|
39
|
+
maxLogSize: DEFAULTS.MAX_LOG_SIZE_BYTES,
|
|
39
40
|
autoRestart: true,
|
|
40
|
-
apiEnabled: process.env.LSH_API_ENABLED === 'true' || false,
|
|
41
|
-
apiPort: parseInt(process.env.LSH_API_PORT ||
|
|
42
|
-
apiKey: process.env.LSH_API_KEY,
|
|
43
|
-
enableWebhooks: process.env.LSH_ENABLE_WEBHOOKS === 'true',
|
|
41
|
+
apiEnabled: process.env[ENV_VARS.LSH_API_ENABLED] === 'true' || false,
|
|
42
|
+
apiPort: parseInt(process.env[ENV_VARS.LSH_API_PORT] || String(DEFAULTS.API_PORT)),
|
|
43
|
+
apiKey: process.env[ENV_VARS.LSH_API_KEY],
|
|
44
|
+
enableWebhooks: process.env[ENV_VARS.LSH_ENABLE_WEBHOOKS] === 'true',
|
|
44
45
|
...config
|
|
45
46
|
};
|
|
46
47
|
this.jobManager = new JobManager(this.config.jobsFile);
|
|
@@ -62,9 +63,9 @@ export class LSHJobDaemon extends EventEmitter {
|
|
|
62
63
|
printValidationResults(envValidation, false);
|
|
63
64
|
}
|
|
64
65
|
// Fail fast in production if validation fails
|
|
65
|
-
if (!envValidation.isValid && process.env.NODE_ENV === 'production') {
|
|
66
|
+
if (!envValidation.isValid && process.env[ENV_VARS.NODE_ENV] === 'production') {
|
|
66
67
|
this.log('ERROR', 'Environment validation failed in production');
|
|
67
|
-
throw new Error(
|
|
68
|
+
throw new Error(ERRORS.INVALID_ENV_CONFIG);
|
|
68
69
|
}
|
|
69
70
|
// Log warnings even in development
|
|
70
71
|
if (envValidation.warnings.length > 0) {
|
|
@@ -124,7 +125,7 @@ export class LSHJobDaemon extends EventEmitter {
|
|
|
124
125
|
*/
|
|
125
126
|
async restart() {
|
|
126
127
|
await this.stop();
|
|
127
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
128
|
+
await new Promise(resolve => setTimeout(resolve, DEFAULTS.DAEMON_RESTART_DELAY_MS));
|
|
128
129
|
await this.start();
|
|
129
130
|
}
|
|
130
131
|
/**
|
|
@@ -181,8 +182,8 @@ export class LSHJobDaemon extends EventEmitter {
|
|
|
181
182
|
}
|
|
182
183
|
// Validate command for security issues
|
|
183
184
|
const validation = validateCommand(job.command, {
|
|
184
|
-
allowDangerousCommands: process.env.LSH_ALLOW_DANGEROUS_COMMANDS === 'true',
|
|
185
|
-
maxLength:
|
|
185
|
+
allowDangerousCommands: process.env[ENV_VARS.LSH_ALLOW_DANGEROUS_COMMANDS] === 'true',
|
|
186
|
+
maxLength: DEFAULTS.MAX_COMMAND_LENGTH
|
|
186
187
|
});
|
|
187
188
|
if (!validation.isValid) {
|
|
188
189
|
const errorMsg = `Command validation failed: ${validation.errors.join(', ')}`;
|
|
@@ -298,8 +299,7 @@ export class LSHJobDaemon extends EventEmitter {
|
|
|
298
299
|
return sanitizedJobs.slice(0, limit);
|
|
299
300
|
}
|
|
300
301
|
// Default limit to prevent oversized responses
|
|
301
|
-
|
|
302
|
-
return sanitizedJobs.slice(0, defaultLimit);
|
|
302
|
+
return sanitizedJobs.slice(0, DEFAULTS.MAX_EVENTS_LIMIT);
|
|
303
303
|
}
|
|
304
304
|
catch (error) {
|
|
305
305
|
this.log('ERROR', `Failed to list jobs: ${error.message}`);
|
|
@@ -736,7 +736,6 @@ const cliLogger = createLogger('LSHDaemonCLI');
|
|
|
736
736
|
const isMainModule = () => {
|
|
737
737
|
try {
|
|
738
738
|
// Use Function constructor to avoid parse-time errors with import.meta
|
|
739
|
-
// eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
|
|
740
739
|
const getImportMetaUrl = new Function('return import.meta.url');
|
|
741
740
|
const metaUrl = getImportMetaUrl();
|
|
742
741
|
return metaUrl === `file://${process.argv[1]}`;
|
|
@@ -774,7 +773,7 @@ if (isMainModule()) {
|
|
|
774
773
|
schedule: { interval: 0 }, // Run once
|
|
775
774
|
env: process.env,
|
|
776
775
|
cwd: process.cwd(),
|
|
777
|
-
user: process.env.USER,
|
|
776
|
+
user: process.env[ENV_VARS.USER],
|
|
778
777
|
priority: 5,
|
|
779
778
|
tags: ['manual'],
|
|
780
779
|
maxRetries: 0,
|
|
@@ -7,6 +7,8 @@ import cors from 'cors';
|
|
|
7
7
|
import rateLimit from 'express-rate-limit';
|
|
8
8
|
import { setupSaaSApiRoutes } from './saas-api-routes.js';
|
|
9
9
|
import { getErrorMessage } from '../lib/saas-types.js';
|
|
10
|
+
import { ENV_VARS, DEFAULTS } from '../constants/index.js';
|
|
11
|
+
import { ERROR_CODES } from '../constants/errors.js';
|
|
10
12
|
/**
|
|
11
13
|
* SaaS API Server
|
|
12
14
|
*/
|
|
@@ -16,11 +18,11 @@ export class SaaSApiServer {
|
|
|
16
18
|
server;
|
|
17
19
|
constructor(config) {
|
|
18
20
|
this.config = {
|
|
19
|
-
port: config?.port || parseInt(process.env.LSH_SAAS_API_PORT ||
|
|
20
|
-
host: config?.host || process.env.LSH_SAAS_API_HOST ||
|
|
21
|
-
corsOrigins: config?.corsOrigins || (process.env.LSH_CORS_ORIGINS?.split(',') || [
|
|
21
|
+
port: config?.port || parseInt(process.env[ENV_VARS.LSH_SAAS_API_PORT] || String(DEFAULTS.SAAS_API_PORT)),
|
|
22
|
+
host: config?.host || process.env[ENV_VARS.LSH_SAAS_API_HOST] || DEFAULTS.DEFAULT_HOST,
|
|
23
|
+
corsOrigins: config?.corsOrigins || (process.env[ENV_VARS.LSH_CORS_ORIGINS]?.split(',') || [...DEFAULTS.DEFAULT_CORS_ORIGINS]),
|
|
22
24
|
rateLimitWindowMs: config?.rateLimitWindowMs || 15 * 60 * 1000, // 15 minutes
|
|
23
|
-
rateLimitMax: config?.rateLimitMax ||
|
|
25
|
+
rateLimitMax: config?.rateLimitMax || DEFAULTS.MAX_EVENTS_LIMIT, // Max 100 requests per windowMs
|
|
24
26
|
};
|
|
25
27
|
this.app = express();
|
|
26
28
|
this.setupMiddleware();
|
|
@@ -113,7 +115,7 @@ export class SaaSApiServer {
|
|
|
113
115
|
res.status(404).json({
|
|
114
116
|
success: false,
|
|
115
117
|
error: {
|
|
116
|
-
code:
|
|
118
|
+
code: ERROR_CODES.NOT_FOUND,
|
|
117
119
|
message: `Endpoint ${req.method} ${req.path} not found`,
|
|
118
120
|
},
|
|
119
121
|
});
|
|
@@ -127,9 +129,9 @@ export class SaaSApiServer {
|
|
|
127
129
|
const errorHandler = (err, _req, res, _next) => {
|
|
128
130
|
console.error('API Error:', err);
|
|
129
131
|
// Don't leak error details in production
|
|
130
|
-
const isDev = process.env.NODE_ENV !== 'production';
|
|
132
|
+
const isDev = process.env[ENV_VARS.NODE_ENV] !== 'production';
|
|
131
133
|
const statusCode = err.status || 500;
|
|
132
|
-
const errorCode = err.code ||
|
|
134
|
+
const errorCode = err.code || ERROR_CODES.INTERNAL_ERROR;
|
|
133
135
|
res.status(statusCode).json({
|
|
134
136
|
success: false,
|
|
135
137
|
error: {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { EventEmitter } from 'events';
|
|
13
13
|
import { createLogger } from './logger.js';
|
|
14
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
14
15
|
/**
|
|
15
16
|
* Abstract base class for job managers
|
|
16
17
|
*/
|
|
@@ -54,7 +55,7 @@ export class BaseJobManager extends EventEmitter {
|
|
|
54
55
|
createdAt: new Date(),
|
|
55
56
|
env: spec.env,
|
|
56
57
|
cwd: spec.cwd || process.cwd(),
|
|
57
|
-
user: spec.user || process.env.USER,
|
|
58
|
+
user: spec.user || process.env[ENV_VARS.USER],
|
|
58
59
|
schedule: spec.schedule,
|
|
59
60
|
tags: spec.tags || [],
|
|
60
61
|
description: spec.description,
|
|
@@ -8,6 +8,7 @@ import { EventEmitter } from 'events';
|
|
|
8
8
|
import DatabasePersistence from './database-persistence.js';
|
|
9
9
|
import { createLogger } from './logger.js';
|
|
10
10
|
import { getPlatformPaths } from './platform-utils.js';
|
|
11
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
11
12
|
export class DaemonClient extends EventEmitter {
|
|
12
13
|
socketPath;
|
|
13
14
|
socket;
|
|
@@ -253,7 +254,7 @@ export class DaemonClient extends EventEmitter {
|
|
|
253
254
|
schedule: jobSpec.schedule,
|
|
254
255
|
env: jobSpec.environment || {},
|
|
255
256
|
cwd: jobSpec.workingDirectory || process.cwd(),
|
|
256
|
-
user: jobSpec.user || process.env.USER,
|
|
257
|
+
user: jobSpec.user || process.env[ENV_VARS.USER],
|
|
257
258
|
priority: jobSpec.priority || 0,
|
|
258
259
|
tags: jobSpec.tags || [],
|
|
259
260
|
enabled: jobSpec.enabled !== false,
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { supabaseClient, isSupabaseConfigured } from './supabase-client.js';
|
|
6
6
|
import * as os from 'os';
|
|
7
7
|
import { LocalStorageAdapter } from './local-storage-adapter.js';
|
|
8
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
8
9
|
export class DatabasePersistence {
|
|
9
10
|
client;
|
|
10
11
|
localStorage;
|
|
@@ -16,7 +17,7 @@ export class DatabasePersistence {
|
|
|
16
17
|
if (this.useLocalStorage) {
|
|
17
18
|
// Using local storage is normal when Supabase is not configured
|
|
18
19
|
// Only show this message once per session to avoid noise
|
|
19
|
-
if (!process.env.LSH_LOCAL_STORAGE_QUIET) {
|
|
20
|
+
if (!process.env[ENV_VARS.LSH_LOCAL_STORAGE_QUIET]) {
|
|
20
21
|
console.log('ā¹ļø Using local storage (Supabase not configured)');
|
|
21
22
|
}
|
|
22
23
|
this.localStorage = new LocalStorageAdapter(userId);
|
|
@@ -8,6 +8,7 @@ import * as os from 'os';
|
|
|
8
8
|
import * as crypto from 'crypto';
|
|
9
9
|
import { createLogger } from './logger.js';
|
|
10
10
|
import { getStorachaClient } from './storacha-client.js';
|
|
11
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
11
12
|
const logger = createLogger('IPFSSecretsStorage');
|
|
12
13
|
/**
|
|
13
14
|
* IPFS Secrets Storage
|
|
@@ -25,7 +26,7 @@ export class IPFSSecretsStorage {
|
|
|
25
26
|
metadataPath;
|
|
26
27
|
metadata;
|
|
27
28
|
constructor() {
|
|
28
|
-
const homeDir = process.env.HOME || os.homedir();
|
|
29
|
+
const homeDir = process.env[ENV_VARS.HOME] || os.homedir();
|
|
29
30
|
const lshDir = path.join(homeDir, '.lsh');
|
|
30
31
|
this.cacheDir = path.join(lshDir, 'secrets-cache');
|
|
31
32
|
this.metadataPath = path.join(lshDir, 'secrets-metadata.json');
|
|
@@ -7,6 +7,7 @@ import * as path from 'path';
|
|
|
7
7
|
import * as os from 'os';
|
|
8
8
|
import * as crypto from 'crypto';
|
|
9
9
|
import { getGitRepoInfo } from './git-utils.js';
|
|
10
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
10
11
|
/**
|
|
11
12
|
* IPFS Sync Logger
|
|
12
13
|
*
|
|
@@ -36,7 +37,7 @@ export class IPFSSyncLogger {
|
|
|
36
37
|
* Check if IPFS sync is enabled
|
|
37
38
|
*/
|
|
38
39
|
isEnabled() {
|
|
39
|
-
return process.env.DISABLE_IPFS_SYNC !== 'true';
|
|
40
|
+
return process.env[ENV_VARS.DISABLE_IPFS_SYNC] !== 'true';
|
|
40
41
|
}
|
|
41
42
|
/**
|
|
42
43
|
* Record a sync operation to IPFS
|
|
@@ -195,7 +196,7 @@ export class IPFSSyncLogger {
|
|
|
195
196
|
* Get encryption key fingerprint
|
|
196
197
|
*/
|
|
197
198
|
getKeyFingerprint() {
|
|
198
|
-
const key = process.env.LSH_SECRETS_KEY || 'default';
|
|
199
|
+
const key = process.env[ENV_VARS.LSH_SECRETS_KEY] || 'default';
|
|
199
200
|
return `sha256:${crypto.createHash('sha256').update(key).digest('hex').substring(0, 16)}`;
|
|
200
201
|
}
|
|
201
202
|
/**
|
package/dist/lib/logger.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Centralized logging utility with support for different log levels,
|
|
4
4
|
* structured logging, and environment-based configuration.
|
|
5
5
|
*/
|
|
6
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
6
7
|
export var LogLevel;
|
|
7
8
|
(function (LogLevel) {
|
|
8
9
|
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
|
@@ -41,7 +42,7 @@ const colors = {
|
|
|
41
42
|
* Get log level from environment variable
|
|
42
43
|
*/
|
|
43
44
|
function getLogLevelFromEnv() {
|
|
44
|
-
const level = process.env.LSH_LOG_LEVEL?.toUpperCase();
|
|
45
|
+
const level = process.env[ENV_VARS.LSH_LOG_LEVEL]?.toUpperCase();
|
|
45
46
|
switch (level) {
|
|
46
47
|
case 'DEBUG':
|
|
47
48
|
return LogLevel.DEBUG;
|
|
@@ -54,7 +55,7 @@ function getLogLevelFromEnv() {
|
|
|
54
55
|
case 'NONE':
|
|
55
56
|
return LogLevel.NONE;
|
|
56
57
|
default:
|
|
57
|
-
return process.env.NODE_ENV === 'production' ? LogLevel.INFO : LogLevel.DEBUG;
|
|
58
|
+
return process.env[ENV_VARS.NODE_ENV] === 'production' ? LogLevel.INFO : LogLevel.DEBUG;
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
/**
|
|
@@ -66,8 +67,8 @@ export class Logger {
|
|
|
66
67
|
this.config = {
|
|
67
68
|
level: config?.level ?? getLogLevelFromEnv(),
|
|
68
69
|
enableTimestamp: config?.enableTimestamp ?? true,
|
|
69
|
-
enableColors: config?.enableColors ?? (!process.env.NO_COLOR && process.stdout.isTTY),
|
|
70
|
-
enableJSON: config?.enableJSON ?? (process.env.LSH_LOG_FORMAT === 'json'),
|
|
70
|
+
enableColors: config?.enableColors ?? (!process.env[ENV_VARS.NO_COLOR] && process.stdout.isTTY),
|
|
71
|
+
enableJSON: config?.enableJSON ?? (process.env[ENV_VARS.LSH_LOG_FORMAT] === 'json'),
|
|
71
72
|
context: config?.context,
|
|
72
73
|
};
|
|
73
74
|
}
|
package/dist/lib/lsh-config.js
CHANGED
|
@@ -10,6 +10,7 @@ import * as fs from 'fs';
|
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import * as os from 'os';
|
|
12
12
|
import { logger } from './logger.js';
|
|
13
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
13
14
|
export class LshConfigManager {
|
|
14
15
|
configPath;
|
|
15
16
|
config;
|
|
@@ -66,7 +67,7 @@ export class LshConfigManager {
|
|
|
66
67
|
return this.config.keys[repoName].key;
|
|
67
68
|
}
|
|
68
69
|
// Fall back to environment variables
|
|
69
|
-
const envKey = process.env.LSH_SECRETS_KEY || process.env.LSH_MASTER_KEY;
|
|
70
|
+
const envKey = process.env[ENV_VARS.LSH_SECRETS_KEY] || process.env[ENV_VARS.LSH_MASTER_KEY];
|
|
70
71
|
if (envKey) {
|
|
71
72
|
logger.debug(`Using encryption key from environment for ${repoName}`);
|
|
72
73
|
return envKey;
|
package/dist/lib/lshrc-init.js
CHANGED
|
@@ -6,13 +6,14 @@ import * as fs from 'fs';
|
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import * as os from 'os';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = path.dirname(__filename);
|
|
11
12
|
export class LshrcManager {
|
|
12
13
|
lshrcPath;
|
|
13
14
|
constructor(lshrcPath) {
|
|
14
|
-
// Use process.env.HOME if set (for testability), fallback to os.homedir()
|
|
15
|
-
const homeDir = process.env.HOME || os.homedir();
|
|
15
|
+
// Use process.env[ENV_VARS.HOME] if set (for testability), fallback to os.homedir()
|
|
16
|
+
const homeDir = process.env[ENV_VARS.HOME] || os.homedir();
|
|
16
17
|
this.lshrcPath = lshrcPath || path.join(homeDir, '.lshrc');
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import * as os from 'os';
|
|
7
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
7
8
|
/**
|
|
8
9
|
* Get platform-specific paths
|
|
9
10
|
* Handles differences between Windows, macOS, and Linux
|
|
@@ -31,7 +32,7 @@ export function getPlatformPaths(appName = 'lsh') {
|
|
|
31
32
|
// Linux: ~/.config/lsh
|
|
32
33
|
let configDir;
|
|
33
34
|
if (isWindows) {
|
|
34
|
-
configDir = path.join(process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming'), appName);
|
|
35
|
+
configDir = path.join(process.env[ENV_VARS.APPDATA] || path.join(homeDir, 'AppData', 'Roaming'), appName);
|
|
35
36
|
}
|
|
36
37
|
else if (isMac) {
|
|
37
38
|
configDir = path.join(homeDir, 'Library', 'Application Support', appName);
|
|
@@ -45,7 +46,7 @@ export function getPlatformPaths(appName = 'lsh') {
|
|
|
45
46
|
// Linux: ~/.local/share/lsh
|
|
46
47
|
let dataDir;
|
|
47
48
|
if (isWindows) {
|
|
48
|
-
dataDir = path.join(process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local'), appName);
|
|
49
|
+
dataDir = path.join(process.env[ENV_VARS.LOCALAPPDATA] || path.join(homeDir, 'AppData', 'Local'), appName);
|
|
49
50
|
}
|
|
50
51
|
else if (isMac) {
|
|
51
52
|
dataDir = path.join(homeDir, 'Library', 'Application Support', appName);
|
|
@@ -134,9 +135,9 @@ export async function ensureDir(dirPath) {
|
|
|
134
135
|
*/
|
|
135
136
|
export function getDefaultShell() {
|
|
136
137
|
if (isWindows()) {
|
|
137
|
-
return process.env.COMSPEC || 'cmd.exe';
|
|
138
|
+
return process.env[ENV_VARS.COMSPEC] || 'cmd.exe';
|
|
138
139
|
}
|
|
139
|
-
return process.env.SHELL || '/bin/sh';
|
|
140
|
+
return process.env[ENV_VARS.SHELL] || '/bin/sh';
|
|
140
141
|
}
|
|
141
142
|
/**
|
|
142
143
|
* Get path separator for current platform
|
package/dist/lib/saas-auth.js
CHANGED
|
@@ -6,6 +6,7 @@ import { randomBytes } from 'crypto';
|
|
|
6
6
|
import bcrypt from 'bcrypt';
|
|
7
7
|
import jwt from 'jsonwebtoken';
|
|
8
8
|
import { getSupabaseClient } from './supabase-client.js';
|
|
9
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
9
10
|
const BCRYPT_ROUNDS = 12;
|
|
10
11
|
const TOKEN_EXPIRY = 7 * 24 * 60 * 60; // 7 days in seconds
|
|
11
12
|
const REFRESH_TOKEN_EXPIRY = 30 * 24 * 60 * 60; // 30 days
|
|
@@ -32,7 +33,7 @@ export async function verifyPassword(password, hash) {
|
|
|
32
33
|
* Generate JWT access token
|
|
33
34
|
*/
|
|
34
35
|
export function generateAccessToken(userId, email) {
|
|
35
|
-
const secret = process.env.LSH_JWT_SECRET;
|
|
36
|
+
const secret = process.env[ENV_VARS.LSH_JWT_SECRET];
|
|
36
37
|
if (!secret) {
|
|
37
38
|
throw new Error('LSH_JWT_SECRET is not set');
|
|
38
39
|
}
|
|
@@ -50,7 +51,7 @@ export function generateAccessToken(userId, email) {
|
|
|
50
51
|
* Generate JWT refresh token
|
|
51
52
|
*/
|
|
52
53
|
export function generateRefreshToken(userId) {
|
|
53
|
-
const secret = process.env.LSH_JWT_SECRET;
|
|
54
|
+
const secret = process.env[ENV_VARS.LSH_JWT_SECRET];
|
|
54
55
|
if (!secret) {
|
|
55
56
|
throw new Error('LSH_JWT_SECRET is not set');
|
|
56
57
|
}
|
|
@@ -67,7 +68,7 @@ export function generateRefreshToken(userId) {
|
|
|
67
68
|
* Verify and decode JWT token
|
|
68
69
|
*/
|
|
69
70
|
export function verifyToken(token) {
|
|
70
|
-
const secret = process.env.LSH_JWT_SECRET;
|
|
71
|
+
const secret = process.env[ENV_VARS.LSH_JWT_SECRET];
|
|
71
72
|
if (!secret) {
|
|
72
73
|
throw new Error('LSH_JWT_SECRET is not set');
|
|
73
74
|
}
|
package/dist/lib/saas-billing.js
CHANGED
|
@@ -4,22 +4,23 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { getSupabaseClient } from './supabase-client.js';
|
|
6
6
|
import { auditLogger } from './saas-audit.js';
|
|
7
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
7
8
|
/**
|
|
8
9
|
* Stripe Pricing IDs (set via environment variables)
|
|
9
10
|
*/
|
|
10
11
|
export const STRIPE_PRICE_IDS = {
|
|
11
|
-
pro_monthly: process.env.STRIPE_PRICE_PRO_MONTHLY || '',
|
|
12
|
-
pro_yearly: process.env.STRIPE_PRICE_PRO_YEARLY || '',
|
|
13
|
-
enterprise_monthly: process.env.STRIPE_PRICE_ENTERPRISE_MONTHLY || '',
|
|
14
|
-
enterprise_yearly: process.env.STRIPE_PRICE_ENTERPRISE_YEARLY || '',
|
|
12
|
+
pro_monthly: process.env[ENV_VARS.STRIPE_PRICE_PRO_MONTHLY] || '',
|
|
13
|
+
pro_yearly: process.env[ENV_VARS.STRIPE_PRICE_PRO_YEARLY] || '',
|
|
14
|
+
enterprise_monthly: process.env[ENV_VARS.STRIPE_PRICE_ENTERPRISE_MONTHLY] || '',
|
|
15
|
+
enterprise_yearly: process.env[ENV_VARS.STRIPE_PRICE_ENTERPRISE_YEARLY] || '',
|
|
15
16
|
};
|
|
16
17
|
/**
|
|
17
18
|
* Billing Service
|
|
18
19
|
*/
|
|
19
20
|
export class BillingService {
|
|
20
21
|
supabase = getSupabaseClient();
|
|
21
|
-
stripeSecretKey = process.env.STRIPE_SECRET_KEY || '';
|
|
22
|
-
stripeWebhookSecret = process.env.STRIPE_WEBHOOK_SECRET || '';
|
|
22
|
+
stripeSecretKey = process.env[ENV_VARS.STRIPE_SECRET_KEY] || '';
|
|
23
|
+
stripeWebhookSecret = process.env[ENV_VARS.STRIPE_WEBHOOK_SECRET] || '';
|
|
23
24
|
stripeApiUrl = 'https://api.stripe.com/v1';
|
|
24
25
|
/**
|
|
25
26
|
* Create Stripe customer
|
package/dist/lib/saas-email.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* LSH SaaS Email Service
|
|
3
3
|
* Email sending using Resend API
|
|
4
4
|
*/
|
|
5
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
5
6
|
/**
|
|
6
7
|
* Email Service
|
|
7
8
|
*/
|
|
@@ -10,10 +11,10 @@ export class EmailService {
|
|
|
10
11
|
resendApiUrl = 'https://api.resend.com/emails';
|
|
11
12
|
constructor(config) {
|
|
12
13
|
this.config = {
|
|
13
|
-
apiKey: config?.apiKey || process.env.RESEND_API_KEY || '',
|
|
14
|
-
fromEmail: config?.fromEmail || process.env.EMAIL_FROM || 'noreply@lsh.dev',
|
|
14
|
+
apiKey: config?.apiKey || process.env[ENV_VARS.RESEND_API_KEY] || '',
|
|
15
|
+
fromEmail: config?.fromEmail || process.env[ENV_VARS.EMAIL_FROM] || 'noreply@lsh.dev',
|
|
15
16
|
fromName: config?.fromName || 'LSH Secrets Manager',
|
|
16
|
-
baseUrl: config?.baseUrl || process.env.BASE_URL || 'https://app.lsh.dev',
|
|
17
|
+
baseUrl: config?.baseUrl || process.env[ENV_VARS.BASE_URL] || 'https://app.lsh.dev',
|
|
17
18
|
};
|
|
18
19
|
if (!this.config.apiKey) {
|
|
19
20
|
console.warn('RESEND_API_KEY not set - emails will not be sent');
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { randomBytes, createCipheriv, createDecipheriv, createHash, pbkdf2Sync } from 'crypto';
|
|
6
6
|
import { getSupabaseClient } from './supabase-client.js';
|
|
7
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
7
8
|
const ALGORITHM = 'aes-256-cbc';
|
|
8
9
|
const KEY_LENGTH = 32; // 256 bits
|
|
9
10
|
const IV_LENGTH = 16; // 128 bits
|
|
@@ -14,7 +15,7 @@ const PBKDF2_ITERATIONS = 100000;
|
|
|
14
15
|
* This key is used to encrypt/decrypt team encryption keys
|
|
15
16
|
*/
|
|
16
17
|
function getMasterKey() {
|
|
17
|
-
const masterKeyHex = process.env.LSH_MASTER_KEY || process.env.LSH_SECRETS_KEY;
|
|
18
|
+
const masterKeyHex = process.env[ENV_VARS.LSH_MASTER_KEY] || process.env[ENV_VARS.LSH_SECRETS_KEY];
|
|
18
19
|
if (!masterKeyHex) {
|
|
19
20
|
throw new Error('LSH_MASTER_KEY or LSH_SECRETS_KEY environment variable must be set for encryption');
|
|
20
21
|
}
|
|
@@ -9,6 +9,7 @@ import { createLogger, LogLevel } from './logger.js';
|
|
|
9
9
|
import { getGitRepoInfo, hasEnvExample, ensureEnvInGitignore } from './git-utils.js';
|
|
10
10
|
import { IPFSSyncLogger } from './ipfs-sync-logger.js';
|
|
11
11
|
import { IPFSSecretsStorage } from './ipfs-secrets-storage.js';
|
|
12
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
12
13
|
const logger = createLogger('SecretsManager');
|
|
13
14
|
export class SecretsManager {
|
|
14
15
|
storage;
|
|
@@ -18,7 +19,7 @@ export class SecretsManager {
|
|
|
18
19
|
homeDir;
|
|
19
20
|
constructor(userIdOrOptions, encryptionKey, detectGit) {
|
|
20
21
|
this.storage = new IPFSSecretsStorage();
|
|
21
|
-
this.homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
22
|
+
this.homeDir = process.env[ENV_VARS.HOME] || process.env[ENV_VARS.USERPROFILE] || '';
|
|
22
23
|
// Handle both legacy and new constructor signatures
|
|
23
24
|
let options;
|
|
24
25
|
if (typeof userIdOrOptions === 'object') {
|
|
@@ -73,12 +74,13 @@ export class SecretsManager {
|
|
|
73
74
|
*/
|
|
74
75
|
getDefaultEncryptionKey() {
|
|
75
76
|
// Check for explicit key
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
const envKey = process.env[ENV_VARS.LSH_SECRETS_KEY];
|
|
78
|
+
if (envKey) {
|
|
79
|
+
return envKey;
|
|
78
80
|
}
|
|
79
81
|
// Generate from machine ID and user
|
|
80
|
-
const machineId = process.env.HOSTNAME || 'localhost';
|
|
81
|
-
const user = process.env.USER || 'unknown';
|
|
82
|
+
const machineId = process.env[ENV_VARS.HOSTNAME] || 'localhost';
|
|
83
|
+
const user = process.env[ENV_VARS.USER] || 'unknown';
|
|
82
84
|
const seed = `${machineId}-${user}-lsh-secrets`;
|
|
83
85
|
// Create deterministic key
|
|
84
86
|
return crypto.createHash('sha256').update(seed).digest('hex');
|
|
@@ -242,7 +244,7 @@ export class SecretsManager {
|
|
|
242
244
|
// Get the effective environment name (repo-aware)
|
|
243
245
|
const effectiveEnv = this.getRepoAwareEnvironment(environment);
|
|
244
246
|
// Warn if using default key
|
|
245
|
-
if (!process.env.LSH_SECRETS_KEY) {
|
|
247
|
+
if (!process.env[ENV_VARS.LSH_SECRETS_KEY]) {
|
|
246
248
|
logger.warn('ā ļø Warning: No LSH_SECRETS_KEY set. Using machine-specific key.');
|
|
247
249
|
logger.warn(' To share secrets across machines, generate a key with: lsh key');
|
|
248
250
|
logger.warn(' Then add LSH_SECRETS_KEY=<key> to your .env on all machines');
|
|
@@ -428,7 +430,7 @@ export class SecretsManager {
|
|
|
428
430
|
cloudExists: false,
|
|
429
431
|
cloudKeys: 0,
|
|
430
432
|
cloudModified: undefined,
|
|
431
|
-
keySet: !!process.env.LSH_SECRETS_KEY,
|
|
433
|
+
keySet: !!process.env[ENV_VARS.LSH_SECRETS_KEY],
|
|
432
434
|
keyMatches: undefined,
|
|
433
435
|
suggestions: [],
|
|
434
436
|
};
|
|
@@ -476,7 +478,7 @@ export class SecretsManager {
|
|
|
476
478
|
return 'dev';
|
|
477
479
|
}
|
|
478
480
|
// Check for v1 compatibility mode
|
|
479
|
-
if (process.env.LSH_V1_COMPAT === 'true') {
|
|
481
|
+
if (process.env[ENV_VARS.LSH_V1_COMPAT] === 'true') {
|
|
480
482
|
return 'dev'; // v1.x behavior
|
|
481
483
|
}
|
|
482
484
|
// v2.0 behavior: use repo name as default in git repos
|
|
@@ -516,7 +518,7 @@ export class SecretsManager {
|
|
|
516
518
|
* Generate encryption key if not set
|
|
517
519
|
*/
|
|
518
520
|
async ensureEncryptionKey() {
|
|
519
|
-
if (process.env.LSH_SECRETS_KEY) {
|
|
521
|
+
if (process.env[ENV_VARS.LSH_SECRETS_KEY]) {
|
|
520
522
|
return true; // Key already set
|
|
521
523
|
}
|
|
522
524
|
logger.warn('ā ļø No encryption key found. Generating a new key...');
|
|
@@ -540,7 +542,7 @@ export class SecretsManager {
|
|
|
540
542
|
}
|
|
541
543
|
fs.writeFileSync(envPath, content, 'utf8');
|
|
542
544
|
// Set in current process
|
|
543
|
-
process.env.LSH_SECRETS_KEY = key;
|
|
545
|
+
process.env[ENV_VARS.LSH_SECRETS_KEY] = key;
|
|
544
546
|
this.encryptionKey = key;
|
|
545
547
|
logger.info('ā
Generated and saved encryption key to .env');
|
|
546
548
|
logger.info('š” Load it now: export LSH_SECRETS_KEY=' + key.substring(0, 8) + '...');
|
|
@@ -677,7 +679,7 @@ LSH_SECRETS_KEY=${this.encryptionKey}
|
|
|
677
679
|
out();
|
|
678
680
|
}
|
|
679
681
|
// Step 1: Ensure encryption key exists
|
|
680
|
-
if (!process.env.LSH_SECRETS_KEY) {
|
|
682
|
+
if (!process.env[ENV_VARS.LSH_SECRETS_KEY]) {
|
|
681
683
|
logger.info('š No encryption key found...');
|
|
682
684
|
await this.ensureEncryptionKey();
|
|
683
685
|
out();
|
|
@@ -7,6 +7,7 @@ import * as fs from 'fs';
|
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
import { logger } from './logger.js';
|
|
10
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
10
11
|
export class StorachaClient {
|
|
11
12
|
client = null;
|
|
12
13
|
configPath;
|
|
@@ -35,7 +36,7 @@ export class StorachaClient {
|
|
|
35
36
|
logger.warn('Failed to load Storacha config, using defaults');
|
|
36
37
|
}
|
|
37
38
|
// Default to enabled unless explicitly disabled
|
|
38
|
-
const envDisabled = process.env.LSH_STORACHA_ENABLED === 'false';
|
|
39
|
+
const envDisabled = process.env[ENV_VARS.LSH_STORACHA_ENABLED] === 'false';
|
|
39
40
|
return {
|
|
40
41
|
enabled: !envDisabled,
|
|
41
42
|
};
|
|
@@ -57,7 +58,7 @@ export class StorachaClient {
|
|
|
57
58
|
*/
|
|
58
59
|
isEnabled() {
|
|
59
60
|
// Explicitly disabled via env var
|
|
60
|
-
if (process.env.LSH_STORACHA_ENABLED === 'false') {
|
|
61
|
+
if (process.env[ENV_VARS.LSH_STORACHA_ENABLED] === 'false') {
|
|
61
62
|
return false;
|
|
62
63
|
}
|
|
63
64
|
// Use config setting (defaults to true)
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
* Provides database connectivity for LSH features
|
|
4
4
|
*/
|
|
5
5
|
import { createClient } from '@supabase/supabase-js';
|
|
6
|
+
import { ENV_VARS } from '../constants/index.js';
|
|
6
7
|
export class SupabaseClient {
|
|
7
8
|
client;
|
|
8
9
|
config;
|
|
9
10
|
constructor(config) {
|
|
10
|
-
const url = config?.url || process.env.SUPABASE_URL;
|
|
11
|
-
const anonKey = config?.anonKey || process.env.SUPABASE_ANON_KEY;
|
|
12
|
-
const databaseUrl = config?.databaseUrl || process.env.DATABASE_URL;
|
|
11
|
+
const url = config?.url || process.env[ENV_VARS.SUPABASE_URL];
|
|
12
|
+
const anonKey = config?.anonKey || process.env[ENV_VARS.SUPABASE_ANON_KEY];
|
|
13
|
+
const databaseUrl = config?.databaseUrl || process.env[ENV_VARS.DATABASE_URL];
|
|
13
14
|
if (!url || !anonKey) {
|
|
14
15
|
throw new Error('Supabase configuration missing. Please set SUPABASE_URL and SUPABASE_ANON_KEY environment variables.');
|
|
15
16
|
}
|
|
@@ -76,7 +77,7 @@ function getDefaultClient() {
|
|
|
76
77
|
* Check if Supabase is configured and available
|
|
77
78
|
*/
|
|
78
79
|
export function isSupabaseConfigured() {
|
|
79
|
-
return !!(process.env.SUPABASE_URL && process.env.SUPABASE_ANON_KEY);
|
|
80
|
+
return !!(process.env[ENV_VARS.SUPABASE_URL] && process.env[ENV_VARS.SUPABASE_ANON_KEY]);
|
|
80
81
|
}
|
|
81
82
|
export const supabaseClient = {
|
|
82
83
|
getClient() {
|
|
@@ -114,8 +115,8 @@ export const supabaseClient = {
|
|
|
114
115
|
* @throws {Error} If SUPABASE_URL or SUPABASE_ANON_KEY are not set
|
|
115
116
|
*/
|
|
116
117
|
export function getSupabaseClient() {
|
|
117
|
-
const url = process.env.SUPABASE_URL;
|
|
118
|
-
const key = process.env.SUPABASE_ANON_KEY;
|
|
118
|
+
const url = process.env[ENV_VARS.SUPABASE_URL];
|
|
119
|
+
const key = process.env[ENV_VARS.SUPABASE_ANON_KEY];
|
|
119
120
|
if (!url || !key) {
|
|
120
121
|
throw new Error('Supabase configuration missing. Please set SUPABASE_URL and SUPABASE_ANON_KEY environment variables.');
|
|
121
122
|
}
|
|
@@ -7,6 +7,7 @@ import * as fs from 'fs';
|
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as readline from 'readline';
|
|
9
9
|
import { getGitRepoInfo } from '../../lib/git-utils.js';
|
|
10
|
+
import { ENV_VARS } from '../../constants/index.js';
|
|
10
11
|
export async function init_secrets(program) {
|
|
11
12
|
// Push secrets to cloud
|
|
12
13
|
program
|
|
@@ -222,10 +223,12 @@ export async function init_secrets(program) {
|
|
|
222
223
|
.command('create')
|
|
223
224
|
.description('Create a new .env file')
|
|
224
225
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
226
|
+
.option('-g, --global', 'Use global workspace ($HOME)')
|
|
225
227
|
.option('-t, --template', 'Create with common template variables')
|
|
226
228
|
.action(async (options) => {
|
|
227
229
|
try {
|
|
228
|
-
const
|
|
230
|
+
const manager = new SecretsManager({ globalMode: options.global });
|
|
231
|
+
const envPath = path.resolve(manager.resolveFilePath(options.file));
|
|
229
232
|
// Check if file already exists
|
|
230
233
|
if (fs.existsSync(envPath)) {
|
|
231
234
|
console.log(`ā File already exists: ${envPath}`);
|
|
@@ -882,6 +885,7 @@ API_KEY=
|
|
|
882
885
|
program
|
|
883
886
|
.command('clear')
|
|
884
887
|
.description('Clear local metadata and cache to resolve stuck registries')
|
|
888
|
+
.option('-g, --global', 'Use global workspace ($HOME) - default behavior')
|
|
885
889
|
.option('--repo <name>', 'Clear metadata for specific repo only')
|
|
886
890
|
.option('--cache', 'Also clear local encrypted secrets cache')
|
|
887
891
|
.option('--storacha', 'Also delete old Storacha uploads (registries and secrets)')
|
|
@@ -889,7 +893,7 @@ API_KEY=
|
|
|
889
893
|
.option('-y, --yes', 'Skip confirmation prompts')
|
|
890
894
|
.action(async (options) => {
|
|
891
895
|
try {
|
|
892
|
-
const lshDir = path.join(process.env.HOME || process.env.USERPROFILE || '', '.lsh');
|
|
896
|
+
const lshDir = path.join(process.env[ENV_VARS.HOME] || process.env[ENV_VARS.USERPROFILE] || '', '.lsh');
|
|
893
897
|
const metadataPath = path.join(lshDir, 'secrets-metadata.json');
|
|
894
898
|
const cacheDir = path.join(lshDir, 'secrets-cache');
|
|
895
899
|
// Determine what we're clearing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lsh-framework",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "Simple, cross-platform encrypted secrets manager with automatic sync, IPFS audit logs, and multi-environment support. Just run lsh sync and start managing your secrets.",
|
|
5
5
|
"main": "dist/app.js",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc",
|
|
19
19
|
"watch": "tsc --watch",
|
|
20
|
-
"test": "node --experimental-vm-modules ./node_modules/.bin/jest",
|
|
20
|
+
"test": "node --experimental-vm-modules ./node_modules/.bin/jest --runInBand",
|
|
21
21
|
"test:ci": "node --experimental-vm-modules ./node_modules/.bin/jest --runInBand",
|
|
22
22
|
"test:coverage": "node --experimental-vm-modules ./node_modules/.bin/jest --coverage --runInBand",
|
|
23
23
|
"clean": "rm -rf ./build; rm -rf ./bin; rm -rf ./dist",
|