klio 1.4.9 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +9 -0
- package/.nvmrc +1 -0
- package/Dockerfile +18 -0
- package/README.md +22 -0
- package/compose.yaml +6 -0
- package/package.json +8 -3
- package/src/astrology/astrologyConstants.js +7 -0
- package/src/astrology/astrologyService.js +327 -302
- package/src/astrology/astrologyServiceWeb.js +369 -0
- package/src/astrology/swephWasmLoader.js +106 -0
- package/src/astrology/swissephAdapter.js +279 -0
- package/src/cli/cli.js +148 -152
- package/src/cli/cliService.js +1197 -0
- package/src/cli/cliServiceWeb.js +406 -0
- package/src/config/configService.js +59 -35
- package/src/gui/public/index.html +839 -298
- package/src/gui/public/sweph/astro.data +0 -0
- package/src/gui/public/sweph/astro.js +3934 -0
- package/src/gui/public/sweph/astro.wasm +0 -0
- package/src/gui/public/tailwind.css +3 -0
- package/src/gui/public/tailwind.generated.css +1 -0
- package/src/gui/public/webcontainerService.js +435 -0
- package/src/gui/routes/api.js +64 -101
- package/src/gui/server.js +80 -31
- package/src/gui/webcontainerSetup.js +244 -0
- package/src/health/fileAnalysis.js +2 -2
- package/commands.db +0 -0
- package/src/gui/commandLogger.js +0 -67
- package/src/gui/database.js +0 -135
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.static{position:static}.fixed{position:fixed}.bottom-0{bottom:0}.right-0{right:0}.top-0{top:0}.top-1\/2{top:50%}.m-0{margin:0}.m-4{margin:1rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-2{margin-left:.5rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-auto{margin-top:auto}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.contents{display:contents}.hidden{display:none}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-screen{height:100vh}.min-h-0{min-height:0}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-5{width:1.25rem}.w-\[85vw\]{width:85vw}.w-full{width:100%}.min-w-0{min-width:0}.flex-1{flex:1 1 0%}.-translate-x-\[85vw\]{--tw-translate-x:-85vw}.-translate-x-\[85vw\],.-translate-y-1\/2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y:-50%}.translate-x-0{--tw-translate-x:0px}.translate-x-0,.translate-x-full{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-full{--tw-translate-x:100%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-l-md{border-top-left-radius:.375rem;border-bottom-left-radius:.375rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.border-0{border-width:0}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-\[\#4dabf7\]{--tw-border-opacity:1;border-color:rgb(77 171 247/var(--tw-border-opacity,1))}.border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity,1))}.bg-\[\#1e1e1e\]{--tw-bg-opacity:1;background-color:rgb(30 30 30/var(--tw-bg-opacity,1))}.bg-\[\#27ca3f\]{--tw-bg-opacity:1;background-color:rgb(39 202 63/var(--tw-bg-opacity,1))}.bg-\[\#2d2d2d\]{--tw-bg-opacity:1;background-color:rgb(45 45 45/var(--tw-bg-opacity,1))}.bg-\[\#3d3d3d\]{--tw-bg-opacity:1;background-color:rgb(61 61 61/var(--tw-bg-opacity,1))}.bg-\[\#4dabf7\]{--tw-bg-opacity:1;background-color:rgb(77 171 247/var(--tw-bg-opacity,1))}.bg-\[\#e0e0e0\]{--tw-bg-opacity:1;background-color:rgb(224 224 224/var(--tw-bg-opacity,1))}.bg-\[\#ff5f56\]{--tw-bg-opacity:1;background-color:rgb(255 95 86/var(--tw-bg-opacity,1))}.bg-\[\#ffbd2e\]{--tw-bg-opacity:1;background-color:rgb(255 189 46/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.align-middle{vertical-align:middle}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.text-\[\#4dabf7\]{--tw-text-opacity:1;color:rgb(77 171 247/var(--tw-text-opacity,1))}.text-\[\#e0e0e0\]{--tw-text-opacity:1;color:rgb(224 224 224/var(--tw-text-opacity,1))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity,1))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-green-400{--tw-text-opacity:1;color:rgb(74 222 128/var(--tw-text-opacity,1))}.text-red-400{--tw-text-opacity:1;color:rgb(248 113 113/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-400{--tw-text-opacity:1;color:rgb(250 204 21/var(--tw-text-opacity,1))}.shadow-\[0_4px_20px_rgba\(0\2c 0\2c 0\2c 0\.5\)\]{--tw-shadow:0 4px 20px rgba(0,0,0,.5);--tw-shadow-colored:0 4px 20px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.hover\:bg-\[\#4a4a4a\]:hover{--tw-bg-opacity:1;background-color:rgb(74 74 74/var(--tw-bg-opacity,1))}.hover\:text-blue-300:hover{--tw-text-opacity:1;color:rgb(147 197 253/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}@media (min-width:768px){.md\:fixed{position:fixed}.md\:bottom-0{bottom:0}.md\:left-0{left:0}.md\:top-0{top:0}.md\:ml-\[250px\]{margin-left:250px}.md\:flex{display:flex}.md\:w-\[250px\]{width:250px}.md\:w-\[300px\]{width:300px}.md\:-translate-x-\[300px\]{--tw-translate-x:-300px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.md\:flex-col{flex-direction:column}.md\:overflow-y-auto{overflow-y:auto}.md\:border-r{border-right-width:1px}.md\:border-\[\#3d3d3d\]{--tw-border-opacity:1;border-color:rgb(61 61 61/var(--tw-border-opacity,1))}.md\:bg-\[\#1e1e1e\]{--tw-bg-opacity:1;background-color:rgb(30 30 30/var(--tw-bg-opacity,1))}.md\:p-4{padding:1rem}}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
// WebContainer Service for browser-based execution
|
|
2
|
+
// This service allows running CLI commands directly in the browser using WebContainers
|
|
3
|
+
|
|
4
|
+
class WebContainerService {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.webcontainerInstance = null;
|
|
7
|
+
this.initialized = false;
|
|
8
|
+
this.setupPromise = null;
|
|
9
|
+
this.initializing = false;
|
|
10
|
+
this.filesLoaded = false;
|
|
11
|
+
this.dependenciesInstalled = false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Initialize WebContainer
|
|
15
|
+
async initialize() {
|
|
16
|
+
if (this.initialized) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (this.initializing) {
|
|
21
|
+
// Already initializing, wait for it to complete
|
|
22
|
+
return this.setupPromise;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.initializing = true;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
this._notifyStatus('Loading project files...');
|
|
29
|
+
// Check if WebContainer is available
|
|
30
|
+
if (typeof WebContainer === 'undefined') {
|
|
31
|
+
console.warn('WebContainer API not available - WebContainer functionality will be disabled');
|
|
32
|
+
this.initialized = true;
|
|
33
|
+
this.initializing = false;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if the page is cross-origin isolated
|
|
38
|
+
if (!globalThis.crossOriginIsolated) {
|
|
39
|
+
console.error('WebContainer requires cross-origin isolation. Please ensure the server is configured with proper COOP/COEP headers.');
|
|
40
|
+
|
|
41
|
+
// Check if we're on localhost (which allows COOP/COEP without HTTPS)
|
|
42
|
+
if (typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
|
|
43
|
+
console.log('Running on localhost - COOP/COEP headers should work without HTTPS');
|
|
44
|
+
} else if (typeof window !== 'undefined' && window.location.protocol !== 'https:') {
|
|
45
|
+
console.warn('For non-localhost domains, HTTPS is required for COOP/COEP headers to work.');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.initialized = true;
|
|
49
|
+
this.initializing = false;
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Load project files from server
|
|
54
|
+
if (!this.filesLoaded) {
|
|
55
|
+
await this._loadProjectFiles();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this._notifyStatus('Booting WebContainer...');
|
|
59
|
+
// Create a new WebContainer instance
|
|
60
|
+
this.webcontainerInstance = await WebContainer.boot();
|
|
61
|
+
|
|
62
|
+
this._notifyStatus('Mounting project files...');
|
|
63
|
+
// Mount the project files
|
|
64
|
+
await this._mountProjectFiles();
|
|
65
|
+
|
|
66
|
+
console.log('WebContainer initialized successfully');
|
|
67
|
+
this.initialized = true;
|
|
68
|
+
this.initializing = false;
|
|
69
|
+
this._notifyStatus('WebContainer ready.');
|
|
70
|
+
return true;
|
|
71
|
+
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('Failed to initialize WebContainer:', error);
|
|
74
|
+
this.initializing = false;
|
|
75
|
+
|
|
76
|
+
// Check for specific SharedArrayBuffer error
|
|
77
|
+
if (error.message && error.message.includes('SharedArrayBuffer')) {
|
|
78
|
+
console.error('WebContainer requires SharedArrayBuffer support. Please ensure the page is served with proper cross-origin isolation headers (COOP/COEP).');
|
|
79
|
+
|
|
80
|
+
// Check if we're on localhost (which allows COOP/COEP without HTTPS)
|
|
81
|
+
if (typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
|
|
82
|
+
console.log('Running on localhost - COOP/COEP headers should work without HTTPS');
|
|
83
|
+
} else if (typeof window !== 'undefined' && window.location.protocol !== 'https:') {
|
|
84
|
+
console.warn('For non-localhost domains, HTTPS is required for COOP/COEP headers to work.');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// If initialization fails, mark as initialized but not functional
|
|
89
|
+
this.initialized = true;
|
|
90
|
+
this.webcontainerInstance = null;
|
|
91
|
+
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Write config to the WebContainer filesystem
|
|
97
|
+
async writeConfig(config) {
|
|
98
|
+
const initialized = await this.initialize();
|
|
99
|
+
|
|
100
|
+
if (!initialized || !this.webcontainerInstance) {
|
|
101
|
+
throw new Error('WebContainer is not available in this environment.');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const configPath = '/src/.astrocli/config.json';
|
|
105
|
+
const configDir = '/src/.astrocli';
|
|
106
|
+
|
|
107
|
+
await this.webcontainerInstance.fs.mkdir(configDir, { recursive: true });
|
|
108
|
+
await this.webcontainerInstance.fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Read config from the WebContainer filesystem
|
|
112
|
+
async readConfig() {
|
|
113
|
+
const initialized = await this.initialize();
|
|
114
|
+
|
|
115
|
+
if (!initialized || !this.webcontainerInstance) {
|
|
116
|
+
throw new Error('WebContainer is not available in this environment.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const configPath = '/src/.astrocli/config.json';
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const data = await this.webcontainerInstance.fs.readFile(configPath, 'utf8');
|
|
123
|
+
return data ? JSON.parse(data) : null;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Load project files from server
|
|
130
|
+
async _loadProjectFiles() {
|
|
131
|
+
try {
|
|
132
|
+
const response = await fetch('/api/webcontainer-files');
|
|
133
|
+
const result = await response.json();
|
|
134
|
+
|
|
135
|
+
if (!result.success) {
|
|
136
|
+
throw new Error(result.error || 'Failed to load WebContainer files');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.projectFiles = result.files;
|
|
140
|
+
this.filesLoaded = true;
|
|
141
|
+
console.log('WebContainer files loaded successfully');
|
|
142
|
+
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error('Error loading WebContainer files:', error);
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Mount project files
|
|
150
|
+
async _mountProjectFiles() {
|
|
151
|
+
if (!this.projectFiles) {
|
|
152
|
+
await this._loadProjectFiles();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const filesToMount = this._buildFileTree(this.projectFiles);
|
|
156
|
+
|
|
157
|
+
await this.webcontainerInstance.mount(filesToMount);
|
|
158
|
+
|
|
159
|
+
await this._syncSwephAssets();
|
|
160
|
+
|
|
161
|
+
if (!this.dependenciesInstalled) {
|
|
162
|
+
this._notifyStatus('Installing dependencies...');
|
|
163
|
+
await this._installDependencies();
|
|
164
|
+
this.dependenciesInstalled = true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async _installDependencies() {
|
|
169
|
+
const installProcess = await this.webcontainerInstance.spawn('npm', [
|
|
170
|
+
'install',
|
|
171
|
+
'--no-fund',
|
|
172
|
+
'--no-audit'
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
this._notifyStatus('Installing dependencies (npm install)...');
|
|
176
|
+
const output = await this._readProcessOutput(installProcess);
|
|
177
|
+
const exitCode = await installProcess.exit;
|
|
178
|
+
|
|
179
|
+
if (exitCode !== 0) {
|
|
180
|
+
throw new Error(`npm install failed (${exitCode}). ${output}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async _syncSwephAssets() {
|
|
185
|
+
try {
|
|
186
|
+
const wasmResponse = await fetch('/sweph/astro.wasm');
|
|
187
|
+
const dataResponse = await fetch('/sweph/astro.data');
|
|
188
|
+
|
|
189
|
+
if (!wasmResponse.ok || !dataResponse.ok) {
|
|
190
|
+
throw new Error('Failed to fetch Sweph assets from server.');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const wasmBuffer = await wasmResponse.arrayBuffer();
|
|
194
|
+
const dataBuffer = await dataResponse.arrayBuffer();
|
|
195
|
+
|
|
196
|
+
const targetDir = '/src/gui/public/sweph';
|
|
197
|
+
await this.webcontainerInstance.fs.mkdir(targetDir, { recursive: true });
|
|
198
|
+
|
|
199
|
+
await this.webcontainerInstance.fs.writeFile(
|
|
200
|
+
`${targetDir}/astro.wasm`,
|
|
201
|
+
new Uint8Array(wasmBuffer)
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
await this.webcontainerInstance.fs.writeFile(
|
|
205
|
+
`${targetDir}/astro.data`,
|
|
206
|
+
new Uint8Array(dataBuffer)
|
|
207
|
+
);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error('Error syncing Sweph assets:', error);
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_notifyStatus(message) {
|
|
215
|
+
if (typeof window !== 'undefined' && window.dispatchEvent) {
|
|
216
|
+
window.dispatchEvent(new CustomEvent('webcontainer-status', { detail: message }));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Build WebContainer file tree from flat file map
|
|
221
|
+
_buildFileTree(fileMap) {
|
|
222
|
+
const tree = {};
|
|
223
|
+
|
|
224
|
+
for (const [filePath, content] of Object.entries(fileMap)) {
|
|
225
|
+
const fileContents = this._normalizeFileContents(content);
|
|
226
|
+
if (fileContents === null) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
231
|
+
const parts = normalizedPath.split('/').filter(Boolean);
|
|
232
|
+
let node = tree;
|
|
233
|
+
|
|
234
|
+
for (let i = 0; i < parts.length; i++) {
|
|
235
|
+
const part = parts[i];
|
|
236
|
+
const isFile = i === parts.length - 1;
|
|
237
|
+
|
|
238
|
+
if (isFile) {
|
|
239
|
+
node[part] = { file: { contents: fileContents } };
|
|
240
|
+
} else {
|
|
241
|
+
if (!node[part]) {
|
|
242
|
+
node[part] = { directory: {} };
|
|
243
|
+
}
|
|
244
|
+
node = node[part].directory;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return tree;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_normalizeFileContents(content) {
|
|
253
|
+
if (typeof content === 'string') {
|
|
254
|
+
return content;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!content || typeof content !== 'object') {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (content.encoding === 'base64' && typeof content.contents === 'string') {
|
|
262
|
+
const binary = atob(content.contents);
|
|
263
|
+
const bytes = new Uint8Array(binary.length);
|
|
264
|
+
for (let i = 0; i < binary.length; i++) {
|
|
265
|
+
bytes[i] = binary.charCodeAt(i);
|
|
266
|
+
}
|
|
267
|
+
return bytes;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Read process output
|
|
274
|
+
async _readProcessOutput(process) {
|
|
275
|
+
let output = '';
|
|
276
|
+
|
|
277
|
+
const readStream = async (stream) => {
|
|
278
|
+
if (!stream || typeof stream.getReader !== 'function') {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const reader = stream.getReader();
|
|
283
|
+
while (true) {
|
|
284
|
+
const { done, value } = await reader.read();
|
|
285
|
+
if (done) break;
|
|
286
|
+
if (value == null) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (typeof value === 'string') {
|
|
290
|
+
output += value;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (value instanceof ArrayBuffer) {
|
|
294
|
+
output += new TextDecoder().decode(value);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (ArrayBuffer.isView(value)) {
|
|
298
|
+
output += new TextDecoder().decode(value);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
output += String(value);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
if (process.output) {
|
|
306
|
+
await readStream(process.output);
|
|
307
|
+
return output;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
await readStream(process.stdout);
|
|
311
|
+
await readStream(process.stderr);
|
|
312
|
+
|
|
313
|
+
return output;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Execute a command in the WebContainer
|
|
317
|
+
async executeCommand(command) {
|
|
318
|
+
try {
|
|
319
|
+
// Ensure WebContainer is initialized
|
|
320
|
+
const initialized = await this.initialize();
|
|
321
|
+
|
|
322
|
+
// If WebContainer is not available, return a fallback response
|
|
323
|
+
if (!initialized || !this.webcontainerInstance) {
|
|
324
|
+
return {
|
|
325
|
+
success: false,
|
|
326
|
+
output: 'WebContainer is not available in this environment. Falling back to server execution.'
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const configJson = (() => {
|
|
331
|
+
try {
|
|
332
|
+
return localStorage.getItem('astrocliConfig') || '';
|
|
333
|
+
} catch (error) {
|
|
334
|
+
return '';
|
|
335
|
+
}
|
|
336
|
+
})();
|
|
337
|
+
|
|
338
|
+
// Run the command in the WebContainer
|
|
339
|
+
const commandProcess = await this.webcontainerInstance.spawn('node', ['webcontainer-entry.js', command], {
|
|
340
|
+
env: {
|
|
341
|
+
KLIO_CONFIG_JSON: configJson
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Capture output
|
|
346
|
+
const output = await this._readProcessOutput(commandProcess);
|
|
347
|
+
|
|
348
|
+
// Wait for command to complete
|
|
349
|
+
const exitCode = await commandProcess.exit;
|
|
350
|
+
|
|
351
|
+
if (exitCode !== 0) {
|
|
352
|
+
throw new Error(`Command failed with exit code ${exitCode}: ${output}`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
success: true,
|
|
357
|
+
output: output
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
} catch (error) {
|
|
361
|
+
console.error('Error executing command in WebContainer:', error);
|
|
362
|
+
|
|
363
|
+
// Check for specific SharedArrayBuffer error
|
|
364
|
+
if (error.message && error.message.includes('SharedArrayBuffer')) {
|
|
365
|
+
console.error('WebContainer requires SharedArrayBuffer support. Please ensure the page is served with proper cross-origin isolation headers (COOP/COEP).');
|
|
366
|
+
|
|
367
|
+
// Check if we're on localhost (which allows COOP/COEP without HTTPS)
|
|
368
|
+
if (typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
|
|
369
|
+
console.log('Running on localhost - COOP/COEP headers should work without HTTPS');
|
|
370
|
+
} else if (typeof window !== 'undefined' && window.location.protocol !== 'https:') {
|
|
371
|
+
console.warn('For non-localhost domains, HTTPS is required for COOP/COEP headers to work.');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
success: false,
|
|
376
|
+
output: 'WebContainer requires cross-origin isolation. Please check server configuration for COOP/COEP headers.'
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
success: false,
|
|
382
|
+
output: error.message || 'Unknown error in WebContainer'
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Check if WebContainers are supported
|
|
388
|
+
static isSupported() {
|
|
389
|
+
try {
|
|
390
|
+
// Check basic browser capabilities
|
|
391
|
+
if (typeof WebAssembly !== 'object' ||
|
|
392
|
+
typeof globalThis !== 'object' ||
|
|
393
|
+
typeof globalThis.crypto !== 'object') {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Check if WebContainer API is available
|
|
398
|
+
if (typeof WebContainer === 'undefined') {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Check if the page is cross-origin isolated (required for SharedArrayBuffer)
|
|
403
|
+
if (!globalThis.crossOriginIsolated) {
|
|
404
|
+
console.warn('WebContainer requires cross-origin isolation. Please ensure the server is configured with proper COOP/COEP headers.');
|
|
405
|
+
|
|
406
|
+
// Check if we're on localhost (which allows COOP/COEP without HTTPS)
|
|
407
|
+
if (typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
|
|
408
|
+
console.log('Running on localhost - COOP/COEP headers should work without HTTPS');
|
|
409
|
+
} else if (typeof window !== 'undefined' && window.location.protocol !== 'https:') {
|
|
410
|
+
console.warn('For non-localhost domains, HTTPS is required for COOP/COEP headers to work.');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return true;
|
|
417
|
+
} catch (error) {
|
|
418
|
+
console.error('Error checking WebContainer support:', error);
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Singleton instance
|
|
425
|
+
const webcontainerService = new WebContainerService();
|
|
426
|
+
|
|
427
|
+
// Only set global variables if we're in a browser environment
|
|
428
|
+
if (typeof window !== 'undefined') {
|
|
429
|
+
window.webcontainerService = webcontainerService;
|
|
430
|
+
window.WebContainerService = WebContainerService; // Expose the class globally
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
console.log('WebContainer service loaded');
|
|
434
|
+
|
|
435
|
+
export { webcontainerService, WebContainerService };
|
package/src/gui/routes/api.js
CHANGED
|
@@ -1,134 +1,97 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
|
-
const commandDatabase = require('../database');
|
|
4
3
|
const { exec } = require('child_process');
|
|
5
4
|
const path = require('path');
|
|
6
5
|
|
|
7
|
-
// Initialize database
|
|
8
|
-
commandDatabase.initialize().catch(err => {
|
|
9
|
-
console.error('Failed to initialize database:', err);
|
|
10
|
-
});
|
|
11
6
|
|
|
12
|
-
//
|
|
13
|
-
router.get('/
|
|
14
|
-
|
|
15
|
-
const commands = await commandDatabase.getAllCommands();
|
|
16
|
-
res.json(commands);
|
|
17
|
-
} catch (err) {
|
|
18
|
-
console.error('Error fetching commands:', err);
|
|
19
|
-
res.status(500).json({ error: 'Failed to fetch commands' });
|
|
20
|
-
}
|
|
7
|
+
// Health check endpoint
|
|
8
|
+
router.get('/health', (req, res) => {
|
|
9
|
+
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
|
|
21
10
|
});
|
|
22
11
|
|
|
23
|
-
//
|
|
24
|
-
router.get('/
|
|
25
|
-
const query = req.query.q || '';
|
|
26
|
-
|
|
12
|
+
// Get WebContainer files endpoint
|
|
13
|
+
router.get('/webcontainer-files', async (req, res) => {
|
|
27
14
|
try {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
15
|
+
const webcontainerSetup = require('../webcontainerSetup');
|
|
16
|
+
const files = await webcontainerSetup.getProjectFiles();
|
|
17
|
+
|
|
18
|
+
res.json({
|
|
19
|
+
success: true,
|
|
20
|
+
files: files
|
|
21
|
+
});
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('Error getting WebContainer files:', error);
|
|
24
|
+
res.status(500).json({
|
|
25
|
+
success: false,
|
|
26
|
+
error: error.message
|
|
27
|
+
});
|
|
33
28
|
}
|
|
34
29
|
});
|
|
35
30
|
|
|
36
|
-
//
|
|
37
|
-
router.get('/
|
|
31
|
+
// User session information endpoint
|
|
32
|
+
router.get('/user-session', (req, res) => {
|
|
38
33
|
try {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
34
|
+
const userId = req.session.userId;
|
|
35
|
+
res.json({
|
|
36
|
+
userId: userId,
|
|
37
|
+
sessionId: req.sessionID,
|
|
38
|
+
isNewSession: !req.session.cookie.originalMaxAge
|
|
39
|
+
});
|
|
45
40
|
} catch (err) {
|
|
46
|
-
console.error('Error
|
|
47
|
-
res.status(500).json({ error: 'Failed to
|
|
41
|
+
console.error('Error getting user session:', err);
|
|
42
|
+
res.status(500).json({ error: 'Failed to get user session' });
|
|
48
43
|
}
|
|
49
44
|
});
|
|
50
45
|
|
|
51
|
-
// Get commands by category
|
|
52
|
-
router.get('/commands/category/:category', async (req, res) => {
|
|
53
|
-
try {
|
|
54
|
-
const commands = await commandDatabase.getCommandsByCategory(req.params.category);
|
|
55
|
-
res.json(commands);
|
|
56
|
-
} catch (err) {
|
|
57
|
-
console.error('Error fetching commands by category:', err);
|
|
58
|
-
res.status(500).json({ error: 'Failed to fetch commands by category' });
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
46
|
|
|
62
|
-
//
|
|
63
|
-
router.
|
|
47
|
+
// Get user-specific commands endpoint
|
|
48
|
+
router.get('/user-commands', async (req, res) => {
|
|
64
49
|
try {
|
|
65
|
-
const
|
|
50
|
+
const userId = req.session.userId;
|
|
51
|
+
const commands = await commandDatabase.getAllCommands();
|
|
66
52
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
53
|
+
// Filter commands by user
|
|
54
|
+
const userCommands = commands.filter(cmd =>
|
|
55
|
+
cmd.tags && cmd.tags.includes(`user:${userId}`)
|
|
56
|
+
);
|
|
70
57
|
|
|
71
|
-
|
|
72
|
-
res.status(201).json({ id: commandId, message: 'Command saved successfully' });
|
|
58
|
+
res.json(userCommands);
|
|
73
59
|
} catch (err) {
|
|
74
|
-
console.error('Error
|
|
75
|
-
res.status(500).json({ error: 'Failed to
|
|
60
|
+
console.error('Error fetching user commands:', err);
|
|
61
|
+
res.status(500).json({ error: 'Failed to fetch user commands' });
|
|
76
62
|
}
|
|
77
63
|
});
|
|
78
64
|
|
|
79
|
-
//
|
|
80
|
-
router.
|
|
81
|
-
|
|
65
|
+
// User logout endpoint
|
|
66
|
+
router.post('/logout', (req, res) => {
|
|
67
|
+
try {
|
|
68
|
+
req.session.destroy((err) => {
|
|
69
|
+
if (err) {
|
|
70
|
+
console.error('Error destroying session:', err);
|
|
71
|
+
return res.status(500).json({ error: 'Failed to logout' });
|
|
72
|
+
}
|
|
73
|
+
res.clearCookie('connect.sid'); // Clear the session cookie
|
|
74
|
+
res.json({ success: true, message: 'Logged out successfully' });
|
|
75
|
+
});
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error('Error in logout endpoint:', err);
|
|
78
|
+
res.status(500).json({ error: 'Failed to logout' });
|
|
79
|
+
}
|
|
82
80
|
});
|
|
83
81
|
|
|
84
|
-
//
|
|
85
|
-
router.
|
|
82
|
+
// Get all user sessions (for admin/debug purposes)
|
|
83
|
+
router.get('/sessions', async (req, res) => {
|
|
86
84
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// Execute the command using child_process
|
|
94
|
-
const commandToRun = command.startsWith('klio ') ? command : `klio ${command}`;
|
|
95
|
-
const mainJsPath = path.join(__dirname, '..', '..', '..', 'src', 'main.js');
|
|
96
|
-
|
|
97
|
-
exec(`node ${mainJsPath} ${command}`, {
|
|
98
|
-
cwd: path.join(__dirname, '..', '..', '..'),
|
|
99
|
-
maxBuffer: 1024 * 1024 * 10 // 10MB buffer
|
|
100
|
-
}, (error, stdout, stderr) => {
|
|
101
|
-
if (error) {
|
|
102
|
-
console.error('Command execution error:', error);
|
|
103
|
-
console.error('Stderr:', stderr);
|
|
104
|
-
return res.status(500).json({
|
|
105
|
-
error: 'Command execution failed',
|
|
106
|
-
details: stderr || error.message,
|
|
107
|
-
command: commandToRun
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Save command to history
|
|
112
|
-
commandDatabase.saveCommand(
|
|
113
|
-
command,
|
|
114
|
-
'',
|
|
115
|
-
stdout,
|
|
116
|
-
'',
|
|
117
|
-
'executed'
|
|
118
|
-
).catch(err => {
|
|
119
|
-
console.error('Failed to save command to history:', err);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
res.json({
|
|
123
|
-
output: stdout,
|
|
124
|
-
command: command
|
|
125
|
-
});
|
|
85
|
+
// This would require accessing the session store directly
|
|
86
|
+
// For now, just return the current session info
|
|
87
|
+
res.json({
|
|
88
|
+
currentUser: req.session.userId,
|
|
89
|
+
message: 'Session management implemented'
|
|
126
90
|
});
|
|
127
|
-
|
|
128
91
|
} catch (err) {
|
|
129
|
-
console.error('Error
|
|
130
|
-
res.status(500).json({ error: 'Failed to
|
|
92
|
+
console.error('Error getting sessions:', err);
|
|
93
|
+
res.status(500).json({ error: 'Failed to get sessions' });
|
|
131
94
|
}
|
|
132
95
|
});
|
|
133
96
|
|
|
134
|
-
module.exports = router;
|
|
97
|
+
module.exports = router;
|