lsh-framework 3.1.1 → 3.1.3
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/self.js +4 -3
- package/dist/constants/api.js +34 -1
- package/dist/constants/config.js +68 -1
- package/dist/constants/database.js +30 -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 +7 -6
- 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/cloud-config-manager.js +2 -1
- package/dist/lib/cron-job-manager.js +5 -4
- package/dist/lib/daemon-client.js +3 -2
- package/dist/lib/database-persistence.js +2 -1
- package/dist/lib/enhanced-history-system.js +3 -2
- package/dist/lib/history-system.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/saas-organizations.js +4 -3
- package/dist/lib/saas-secrets.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 +2 -1
- package/dist/services/supabase/supabase-registrar.js +4 -3
- package/package.json +1 -1
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/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,72 @@ 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
|
+
// Cloud sync intervals
|
|
113
|
+
CLOUD_CONFIG_SYNC_INTERVAL_MS: 60000, // 1 minute
|
|
114
|
+
HISTORY_SYNC_INTERVAL_MS: 30000, // 30 seconds
|
|
115
|
+
// Job registry
|
|
116
|
+
MAX_RECORDS_PER_JOB: 1000,
|
|
117
|
+
MAX_TOTAL_RECORDS: 50000,
|
|
118
|
+
METRICS_RETENTION_DAYS: 90,
|
|
119
|
+
// Shell defaults
|
|
120
|
+
DEFAULT_SHELL_UNIX: '/bin/sh',
|
|
121
|
+
DEFAULT_SHELL_WIN: 'cmd.exe',
|
|
122
|
+
DEFAULT_EDITOR: 'vi',
|
|
56
123
|
};
|
|
@@ -14,8 +14,38 @@ 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',
|
|
20
41
|
DATA_PULL_JOBS: 'data_pull_jobs',
|
|
42
|
+
// ML tables
|
|
43
|
+
ML_TRAINING_JOBS: 'ml_training_jobs',
|
|
44
|
+
ML_MODELS: 'ml_models',
|
|
45
|
+
ML_FEATURES: 'ml_features',
|
|
46
|
+
// Views (read-only aggregations)
|
|
47
|
+
ORGANIZATION_MEMBERS_DETAILED: 'organization_members_detailed',
|
|
48
|
+
ORGANIZATION_USAGE_SUMMARY: 'organization_usage_summary',
|
|
49
|
+
TEAM_MEMBERS_DETAILED: 'team_members_detailed',
|
|
50
|
+
SECRETS_SUMMARY: 'secrets_summary',
|
|
21
51
|
};
|
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, DEFAULTS, PATHS } from '../constants/index.js';
|
|
14
15
|
export class JobRegistry extends BaseJobManager {
|
|
15
16
|
config;
|
|
16
17
|
records = new Map(); // jobId -> execution records
|
|
@@ -19,12 +20,12 @@ export class JobRegistry extends BaseJobManager {
|
|
|
19
20
|
constructor(config) {
|
|
20
21
|
super(new MemoryJobStorage(), 'JobRegistry');
|
|
21
22
|
this.config = {
|
|
22
|
-
registryFile:
|
|
23
|
-
maxRecordsPerJob:
|
|
24
|
-
maxTotalRecords:
|
|
23
|
+
registryFile: PATHS.JOB_REGISTRY_FILE,
|
|
24
|
+
maxRecordsPerJob: DEFAULTS.MAX_RECORDS_PER_JOB,
|
|
25
|
+
maxTotalRecords: DEFAULTS.MAX_TOTAL_RECORDS,
|
|
25
26
|
compressionEnabled: true,
|
|
26
|
-
metricsRetentionDays:
|
|
27
|
-
outputLogDir:
|
|
27
|
+
metricsRetentionDays: DEFAULTS.METRICS_RETENTION_DAYS,
|
|
28
|
+
outputLogDir: PATHS.JOB_LOGS_DIR,
|
|
28
29
|
indexingEnabled: true,
|
|
29
30
|
...config
|
|
30
31
|
};
|
|
@@ -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,
|
|
@@ -6,6 +6,7 @@ import DatabasePersistence from './database-persistence.js';
|
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as os from 'os';
|
|
9
|
+
import { DEFAULTS } from '../constants/index.js';
|
|
9
10
|
export class CloudConfigManager {
|
|
10
11
|
databasePersistence;
|
|
11
12
|
options;
|
|
@@ -17,7 +18,7 @@ export class CloudConfigManager {
|
|
|
17
18
|
userId: undefined,
|
|
18
19
|
enableCloudSync: true,
|
|
19
20
|
localConfigPath: path.join(os.homedir(), '.lshrc'),
|
|
20
|
-
syncInterval:
|
|
21
|
+
syncInterval: DEFAULTS.CLOUD_CONFIG_SYNC_INTERVAL_MS,
|
|
21
22
|
...options,
|
|
22
23
|
};
|
|
23
24
|
this.databasePersistence = new DatabasePersistence(this.options.userId);
|
|
@@ -8,6 +8,7 @@ import { BaseJobManager, } from './base-job-manager.js';
|
|
|
8
8
|
import DatabaseJobStorage from './job-storage-database.js';
|
|
9
9
|
import DaemonClient from './daemon-client.js';
|
|
10
10
|
import DatabasePersistence from './database-persistence.js';
|
|
11
|
+
import { DEFAULTS } from '../constants/index.js';
|
|
11
12
|
export class CronJobManager extends BaseJobManager {
|
|
12
13
|
daemonClient;
|
|
13
14
|
databasePersistence;
|
|
@@ -36,7 +37,7 @@ export class CronJobManager extends BaseJobManager {
|
|
|
36
37
|
workingDirectory: '/backups',
|
|
37
38
|
priority: 8,
|
|
38
39
|
maxRetries: 3,
|
|
39
|
-
timeout:
|
|
40
|
+
timeout: DEFAULTS.JOB_TIMEOUT_1H_MS,
|
|
40
41
|
},
|
|
41
42
|
{
|
|
42
43
|
id: 'log-cleanup',
|
|
@@ -48,7 +49,7 @@ export class CronJobManager extends BaseJobManager {
|
|
|
48
49
|
tags: ['logs', 'cleanup', 'weekly'],
|
|
49
50
|
priority: 3,
|
|
50
51
|
maxRetries: 2,
|
|
51
|
-
timeout:
|
|
52
|
+
timeout: DEFAULTS.JOB_TIMEOUT_5M_MS,
|
|
52
53
|
},
|
|
53
54
|
{
|
|
54
55
|
id: 'disk-monitor',
|
|
@@ -60,7 +61,7 @@ export class CronJobManager extends BaseJobManager {
|
|
|
60
61
|
tags: ['monitoring', 'disk', 'alert'],
|
|
61
62
|
priority: 7,
|
|
62
63
|
maxRetries: 1,
|
|
63
|
-
timeout:
|
|
64
|
+
timeout: DEFAULTS.JOB_TIMEOUT_1M_MS,
|
|
64
65
|
},
|
|
65
66
|
{
|
|
66
67
|
id: 'data-sync',
|
|
@@ -73,7 +74,7 @@ export class CronJobManager extends BaseJobManager {
|
|
|
73
74
|
workingDirectory: '/data',
|
|
74
75
|
priority: 6,
|
|
75
76
|
maxRetries: 5,
|
|
76
|
-
timeout:
|
|
77
|
+
timeout: DEFAULTS.JOB_TIMEOUT_2H_MS,
|
|
77
78
|
},
|
|
78
79
|
];
|
|
79
80
|
templates.forEach(template => {
|
|
@@ -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, DEFAULTS } from '../constants/index.js';
|
|
11
12
|
export class DaemonClient extends EventEmitter {
|
|
12
13
|
socketPath;
|
|
13
14
|
socket;
|
|
@@ -66,7 +67,7 @@ export class DaemonClient extends EventEmitter {
|
|
|
66
67
|
resolve(true);
|
|
67
68
|
});
|
|
68
69
|
let buffer = '';
|
|
69
|
-
const MAX_BUFFER_SIZE =
|
|
70
|
+
const MAX_BUFFER_SIZE = DEFAULTS.MAX_BUFFER_SIZE_BYTES;
|
|
70
71
|
this.socket.on('data', (data) => {
|
|
71
72
|
try {
|
|
72
73
|
buffer += data.toString();
|
|
@@ -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);
|
|
@@ -6,6 +6,7 @@ import HistorySystem from './history-system.js';
|
|
|
6
6
|
import DatabasePersistence from './database-persistence.js';
|
|
7
7
|
import * as os from 'os';
|
|
8
8
|
import * as path from 'path';
|
|
9
|
+
import { DEFAULTS } from '../constants/index.js';
|
|
9
10
|
export class EnhancedHistorySystem extends HistorySystem {
|
|
10
11
|
databasePersistence;
|
|
11
12
|
enhancedConfig;
|
|
@@ -13,7 +14,7 @@ export class EnhancedHistorySystem extends HistorySystem {
|
|
|
13
14
|
pendingSync = false;
|
|
14
15
|
constructor(config = {}) {
|
|
15
16
|
const defaultConfig = {
|
|
16
|
-
maxSize:
|
|
17
|
+
maxSize: DEFAULTS.MAX_HISTORY_SIZE,
|
|
17
18
|
filePath: path.join(os.homedir(), '.lsh_history'),
|
|
18
19
|
shareHistory: true,
|
|
19
20
|
ignoreDups: true,
|
|
@@ -21,7 +22,7 @@ export class EnhancedHistorySystem extends HistorySystem {
|
|
|
21
22
|
expireDuplicatesFirst: true,
|
|
22
23
|
enableCloudSync: true,
|
|
23
24
|
userId: undefined,
|
|
24
|
-
syncInterval:
|
|
25
|
+
syncInterval: DEFAULTS.HISTORY_SYNC_INTERVAL_MS,
|
|
25
26
|
...config,
|
|
26
27
|
};
|
|
27
28
|
super(defaultConfig);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import * as os from 'os';
|
|
8
|
+
import { DEFAULTS } from '../constants/index.js';
|
|
8
9
|
export class HistorySystem {
|
|
9
10
|
entries = [];
|
|
10
11
|
currentIndex = -1;
|
|
@@ -12,7 +13,7 @@ export class HistorySystem {
|
|
|
12
13
|
isEnabled = true;
|
|
13
14
|
constructor(config) {
|
|
14
15
|
this.config = {
|
|
15
|
-
maxSize:
|
|
16
|
+
maxSize: DEFAULTS.MAX_HISTORY_SIZE,
|
|
16
17
|
filePath: path.join(os.homedir(), '.lsh_history'),
|
|
17
18
|
shareHistory: false,
|
|
18
19
|
ignoreDups: true,
|
|
@@ -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
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { getSupabaseClient } from './supabase-client.js';
|
|
6
6
|
import { auditLogger } from './saas-audit.js';
|
|
7
|
+
import { TABLES } from '../constants/index.js';
|
|
7
8
|
/**
|
|
8
9
|
* Generate URL-friendly slug from name
|
|
9
10
|
*/
|
|
@@ -232,7 +233,7 @@ export class OrganizationService {
|
|
|
232
233
|
*/
|
|
233
234
|
async getOrganizationMembers(organizationId) {
|
|
234
235
|
const { data, error } = await this.supabase
|
|
235
|
-
.from(
|
|
236
|
+
.from(TABLES.ORGANIZATION_MEMBERS_DETAILED)
|
|
236
237
|
.select('*')
|
|
237
238
|
.eq('organization_id', organizationId);
|
|
238
239
|
if (error) {
|
|
@@ -270,7 +271,7 @@ export class OrganizationService {
|
|
|
270
271
|
*/
|
|
271
272
|
async getUsageSummary(organizationId) {
|
|
272
273
|
const { data, error } = await this.supabase
|
|
273
|
-
.from(
|
|
274
|
+
.from(TABLES.ORGANIZATION_USAGE_SUMMARY)
|
|
274
275
|
.select('*')
|
|
275
276
|
.eq('organization_id', organizationId)
|
|
276
277
|
.single();
|
|
@@ -550,7 +551,7 @@ export class TeamService {
|
|
|
550
551
|
*/
|
|
551
552
|
async getTeamMembers(teamId) {
|
|
552
553
|
const { data, error } = await this.supabase
|
|
553
|
-
.from(
|
|
554
|
+
.from(TABLES.TEAM_MEMBERS_DETAILED)
|
|
554
555
|
.select('*')
|
|
555
556
|
.eq('team_id', teamId);
|
|
556
557
|
if (error) {
|
package/dist/lib/saas-secrets.js
CHANGED
|
@@ -7,6 +7,7 @@ import { getSupabaseClient } from './supabase-client.js';
|
|
|
7
7
|
import { encryptionService } from './saas-encryption.js';
|
|
8
8
|
import { auditLogger } from './saas-audit.js';
|
|
9
9
|
import { organizationService } from './saas-organizations.js';
|
|
10
|
+
import { TABLES } from '../constants/index.js';
|
|
10
11
|
/**
|
|
11
12
|
* Secrets Service
|
|
12
13
|
*/
|
|
@@ -211,7 +212,7 @@ export class SecretsService {
|
|
|
211
212
|
*/
|
|
212
213
|
async getSecretsSummary(teamId) {
|
|
213
214
|
const { data, error } = await this.supabase
|
|
214
|
-
.from(
|
|
215
|
+
.from(TABLES.SECRETS_SUMMARY)
|
|
215
216
|
.select('*')
|
|
216
217
|
.eq('team_id', teamId);
|
|
217
218
|
if (error) {
|
|
@@ -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
|
|
@@ -892,7 +893,7 @@ API_KEY=
|
|
|
892
893
|
.option('-y, --yes', 'Skip confirmation prompts')
|
|
893
894
|
.action(async (options) => {
|
|
894
895
|
try {
|
|
895
|
-
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');
|
|
896
897
|
const metadataPath = path.join(lshDir, 'secrets-metadata.json');
|
|
897
898
|
const cacheDir = path.join(lshDir, 'secrets-cache');
|
|
898
899
|
// Determine what we're clearing
|
|
@@ -7,6 +7,7 @@ import { supabaseClient } from '../../lib/supabase-client.js';
|
|
|
7
7
|
import DatabasePersistence from '../../lib/database-persistence.js';
|
|
8
8
|
import CloudConfigManager from '../../lib/cloud-config-manager.js';
|
|
9
9
|
import { CREATE_TABLES_SQL } from '../../lib/database-schema.js';
|
|
10
|
+
import { TABLES } from '../../constants/index.js';
|
|
10
11
|
export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
11
12
|
constructor() {
|
|
12
13
|
super('SupabaseService');
|
|
@@ -259,7 +260,7 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
259
260
|
const opts = options;
|
|
260
261
|
if (opts.list) {
|
|
261
262
|
let query = supabaseClient.getClient()
|
|
262
|
-
.from(
|
|
263
|
+
.from(TABLES.ML_TRAINING_JOBS)
|
|
263
264
|
.select('*')
|
|
264
265
|
.order('created_at', { ascending: false });
|
|
265
266
|
if (opts.status) {
|
|
@@ -315,7 +316,7 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
315
316
|
const opts = options;
|
|
316
317
|
if (opts.list) {
|
|
317
318
|
let query = supabaseClient.getClient()
|
|
318
|
-
.from(
|
|
319
|
+
.from(TABLES.ML_MODELS)
|
|
319
320
|
.select('*')
|
|
320
321
|
.order('created_at', { ascending: false });
|
|
321
322
|
if (opts.deployed) {
|
|
@@ -351,7 +352,7 @@ export class SupabaseCommandRegistrar extends BaseCommandRegistrar {
|
|
|
351
352
|
const opts = options;
|
|
352
353
|
if (opts.list) {
|
|
353
354
|
const { data: features, error } = await supabaseClient.getClient()
|
|
354
|
-
.from(
|
|
355
|
+
.from(TABLES.ML_FEATURES)
|
|
355
356
|
.select('*')
|
|
356
357
|
.order('created_at', { ascending: false })
|
|
357
358
|
.limit(20);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lsh-framework",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.3",
|
|
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": {
|