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
|
@@ -3,379 +3,920 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Klio
|
|
7
|
-
<
|
|
6
|
+
<title>Klio</title>
|
|
7
|
+
<link rel="stylesheet" href="/tailwind.generated.css">
|
|
8
8
|
<style>
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
#sidebar {
|
|
10
|
+
transform: translateX(-100%);
|
|
11
11
|
}
|
|
12
|
-
.
|
|
13
|
-
|
|
12
|
+
#sidebar.is-open {
|
|
13
|
+
transform: translateX(0);
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.nav-link {
|
|
20
|
-
transition: all 0.2s ease;
|
|
21
|
-
}
|
|
22
|
-
.nav-link:hover {
|
|
23
|
-
background-color: rgba(59, 130, 246, 0.1);
|
|
24
|
-
}
|
|
25
|
-
.nav-link.active {
|
|
26
|
-
background-color: rgba(59, 130, 246, 0.2);
|
|
27
|
-
border-left: 3px solid #3b82f6;
|
|
15
|
+
@media (min-width: 768px) {
|
|
16
|
+
#sidebar {
|
|
17
|
+
transform: translateX(0);
|
|
18
|
+
}
|
|
28
19
|
}
|
|
29
20
|
</style>
|
|
30
21
|
</head>
|
|
31
|
-
<body class="bg-
|
|
32
|
-
<div class="flex
|
|
33
|
-
<!-- Sidebar
|
|
34
|
-
<div class="
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
<body class="bg-[#1e1e1e] text-[#e0e0e0] font-mono m-0">
|
|
23
|
+
<div class="flex h-screen overflow-hidden">
|
|
24
|
+
<!-- Mobile Sidebar Backdrop -->
|
|
25
|
+
<div id="sidebarBackdrop" class="fixed inset-0 bg-black/60 z-20 hidden md:hidden" onclick="closeSidebar()"></div>
|
|
26
|
+
<!-- Sidebar -->
|
|
27
|
+
<div id="sidebar" class="fixed left-0 top-0 bottom-0 w-[85vw] max-w-[300px] bg-[#1e1e1e] border-r border-[#3d3d3d] overflow-y-auto p-4 z-30 transition-transform duration-300 ease-in-out flex flex-col md:w-[250px]">
|
|
28
|
+
<div class="flex items-center justify-between mb-4 md:hidden">
|
|
29
|
+
<span class="text-sm text-gray-400">Menu</span>
|
|
30
|
+
<button id="sidebarClose" class="text-gray-400 hover:text-white" aria-label="Close menu" onclick="closeSidebar()">
|
|
31
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
32
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
33
|
+
</svg>
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="mb-6">
|
|
37
|
+
<h1 class="text-xl font-bold text-white mb-2">Klio</h1>
|
|
38
|
+
<p class="text-sm text-gray-400">Online Astrological CLI</p>
|
|
39
|
+
<div id="userInfo" class="mt-2 text-xs text-gray-400">
|
|
40
|
+
<span id="userId">Loading user...</span>
|
|
41
|
+
<button id="logoutBtn" class="ml-2 text-blue-400 hover:text-blue-300 cursor-pointer" onclick="logout()" style="display: none;">
|
|
42
|
+
(Logout)
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
38
45
|
</div>
|
|
39
46
|
|
|
40
|
-
<nav class="
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
<
|
|
62
|
-
</
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
47
|
+
<nav class="space-y-2">
|
|
48
|
+
<div class="text-sm">
|
|
49
|
+
<div class="mb-4">
|
|
50
|
+
<h3 class="text-gray-300 font-semibold mb-2">Example Commands</h3>
|
|
51
|
+
<div class="space-y-1">
|
|
52
|
+
<button onclick="insertCommand('moon')" class="w-full text-left rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a]">
|
|
53
|
+
<code class="text-[#4dabf7]">moon</code>
|
|
54
|
+
</button>
|
|
55
|
+
<button onclick="insertCommand('--help')" class="w-full text-left rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a]">
|
|
56
|
+
<code class="text-[#4dabf7]">--help</code>
|
|
57
|
+
</button>
|
|
58
|
+
<button onclick="insertCommand('--c')" class="w-full text-left rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a]">
|
|
59
|
+
<code class="text-[#4dabf7]">--c</code> Critical degrees
|
|
60
|
+
</button>
|
|
61
|
+
<button onclick="insertCommand('--rx')" class="w-full text-left rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a]">
|
|
62
|
+
<code class="text-[#4dabf7]">--rx</code> Retrograde planets
|
|
63
|
+
</button>
|
|
64
|
+
<button onclick="insertCommand('--el')" class="w-full text-left rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a]">
|
|
65
|
+
<code class="text-[#4dabf7]">--el</code> Element distribution
|
|
66
|
+
</button>
|
|
67
|
+
<button onclick="insertCommand('--s')" class="w-full text-left rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a]">
|
|
68
|
+
<code class="text-[#4dabf7]">--s</code> All planet positions
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="mb-4">
|
|
74
|
+
<h3 class="text-gray-300 font-semibold mb-2">Configuration</h3>
|
|
75
|
+
<div class="space-y-1">
|
|
76
|
+
<button onclick="insertCommand('--status')" class="w-full text-left rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a]">
|
|
77
|
+
<code class="text-[#4dabf7]">--status</code>
|
|
78
|
+
</button>
|
|
79
|
+
<button onclick="insertCommand('--setup')" class="w-full text-left rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a]">
|
|
80
|
+
<code class="text-[#4dabf7]">--setup</code>
|
|
81
|
+
</button>
|
|
82
|
+
<button onclick="insertCommand('--people')" class="w-full text-left rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a]">
|
|
83
|
+
<code class="text-[#4dabf7]">--people</code>
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
67
88
|
</nav>
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!-- Main Content -->
|
|
92
|
+
<div class="flex-1 flex flex-col md:ml-[250px]">
|
|
93
|
+
<div class="sticky top-0 z-20 bg-[#1e1e1e] border-b border-[#3d3d3d]">
|
|
94
|
+
<div class="flex items-center gap-3 px-4 py-2">
|
|
95
|
+
<button id="menuToggle" class="md:hidden text-gray-200 hover:text-white" aria-label="Open menu">
|
|
96
|
+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
97
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
|
|
98
|
+
</svg>
|
|
99
|
+
</button>
|
|
100
|
+
<div id="webcontainerStatus" class="ml-auto text-xs text-red-200 bg-[#2d2d2d] border border-[#3d3d3d] rounded-full px-3 py-1">
|
|
101
|
+
<span id="wcStatusText">Checking WebContainer support...</span>
|
|
102
|
+
</div>
|
|
73
103
|
</div>
|
|
74
104
|
</div>
|
|
105
|
+
<div class="flex-1 flex flex-col m-4 bg-[#2d2d2d] rounded-lg shadow-[0_4px_20px_rgba(0,0,0,0.5)] min-h-0">
|
|
106
|
+
<div class="bg-[#3d3d3d] rounded-t-lg px-3 py-2 flex items-center gap-2">
|
|
107
|
+
<div class="w-3 h-3 rounded-full bg-[#ff5f56]"></div>
|
|
108
|
+
<div class="w-3 h-3 rounded-full bg-[#ffbd2e]"></div>
|
|
109
|
+
<div class="w-3 h-3 rounded-full bg-[#27ca3f]"></div>
|
|
110
|
+
<button id="copyTerminalBtn" class="ml-auto text-xs text-gray-200 bg-[#4a4a4a] hover:bg-[#555] px-2 py-1 rounded">Copy</button>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div id="terminalContent" class="flex-1 min-h-0 overflow-y-auto p-4 select-text">
|
|
114
|
+
<div class="flex items-center gap-2 mb-1">
|
|
115
|
+
<span class="text-[#4dabf7]">klio@online:~$</span>
|
|
116
|
+
<span class="text-gray-400">Welcome to Klio Online</span>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="flex items-center gap-2 mb-1">
|
|
119
|
+
<span class="text-[#4dabf7]">klio@online:~$</span>
|
|
120
|
+
<span class="text-gray-400">Type astrological commands and press Enter or run --help for example commands</span>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="flex items-center gap-2 mb-1">
|
|
123
|
+
<span class="text-[#4dabf7]">klio@online:~$</span>
|
|
124
|
+
<span class="text-gray-400">Example: --el</span>
|
|
125
|
+
</div>
|
|
126
|
+
<div class="flex items-center gap-2 mt-4">
|
|
127
|
+
<span class="text-[#4dabf7]">klio@online:~$</span>
|
|
128
|
+
<input
|
|
129
|
+
type="text"
|
|
130
|
+
id="terminalInput"
|
|
131
|
+
class="flex-1 min-w-0 bg-transparent text-[#e0e0e0] outline-none font-mono"
|
|
132
|
+
autocomplete="off"
|
|
133
|
+
autocorrect="off"
|
|
134
|
+
autocapitalize="off"
|
|
135
|
+
spellcheck="false"
|
|
136
|
+
>
|
|
137
|
+
<span class="inline-block w-2.5 h-4 bg-[#e0e0e0] align-middle animate-pulse"></span>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<!-- Help Panel Toggle -->
|
|
145
|
+
<button id="helpToggle" class="fixed right-0 top-1/2 -translate-y-1/2 translate-x-0 bg-[#4dabf7] text-white border-0 px-2 py-2 rounded-l-md cursor-pointer transition-transform duration-300 ease-in-out [writing-mode:vertical-rl] [text-orientation:mixed]" onclick="toggleHelp()">
|
|
146
|
+
Help & Commands
|
|
147
|
+
</button>
|
|
148
|
+
|
|
149
|
+
<!-- Help Panel -->
|
|
150
|
+
<div id="helpPanel" class="fixed right-0 top-0 bottom-0 w-[85vw] md:w-[300px] bg-[#2d2d2d] border-l-2 border-[#4dabf7] p-4 overflow-y-auto transform translate-x-full transition-transform duration-300 ease-in-out">
|
|
151
|
+
<div class="flex justify-between items-center mb-4">
|
|
152
|
+
<h2 class="text-lg font-bold text-white">Command Reference</h2>
|
|
153
|
+
<button onclick="toggleHelp()" class="text-gray-400 hover:text-white">
|
|
154
|
+
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
155
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
156
|
+
</svg>
|
|
157
|
+
</button>
|
|
75
158
|
</div>
|
|
76
159
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
160
|
+
<div class="space-y-4 text-sm">
|
|
161
|
+
<div>
|
|
162
|
+
<h3 class="text-white font-semibold mb-2">Basic Commands</h3>
|
|
163
|
+
<div class="space-y-2">
|
|
164
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('--help')">
|
|
165
|
+
<code class="text-[#4dabf7]">--help</code> - Show help
|
|
166
|
+
</div>
|
|
167
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('moon')">
|
|
168
|
+
<code class="text-[#4dabf7]">moon</code> - Moon information
|
|
169
|
+
</div>
|
|
170
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('sun')">
|
|
171
|
+
<code class="text-[#4dabf7]">sun</code> - Sun information
|
|
172
|
+
</div>
|
|
173
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('mars --a')">
|
|
174
|
+
<code class="text-[#4dabf7]">mars --a</code> - Mars with aspects
|
|
175
|
+
</div>
|
|
83
176
|
</div>
|
|
84
|
-
</
|
|
177
|
+
</div>
|
|
85
178
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<div class="mb-4">
|
|
92
|
-
<label for="commandInput" class="block text-sm font-medium text-gray-700 mb-2">Command</label>
|
|
93
|
-
<div class="flex gap-2">
|
|
94
|
-
<input
|
|
95
|
-
type="text"
|
|
96
|
-
id="commandInput"
|
|
97
|
-
placeholder="e.g., moon --s"
|
|
98
|
-
class="flex-1 command-input px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
99
|
-
>
|
|
100
|
-
<button
|
|
101
|
-
onclick="runCommand()"
|
|
102
|
-
class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
|
103
|
-
>
|
|
104
|
-
Run
|
|
105
|
-
</button>
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
108
|
-
<div class="text-sm text-gray-600">
|
|
109
|
-
<p class="mb-2">Examples:</p>
|
|
110
|
-
<div class="flex flex-wrap gap-4 text-xs">
|
|
111
|
-
<button onclick="commandInput.value = '--v 3 saturn neptune'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">moon --s</button>
|
|
112
|
-
<button onclick="commandInput.value = '--rx --i'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">--rx</button>
|
|
113
|
-
<button onclick="commandInput.value = '--c'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">--c</button>
|
|
114
|
-
<button onclick="commandInput.value = 'mars venus --k'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">mars venus --k</button>
|
|
115
|
-
<button onclick="commandInput.value = '--el'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">--el</button>
|
|
116
|
-
<button onclick="commandInput.value = '--s'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">--el</button>
|
|
117
|
-
<button onclick="commandInput.value = '--a'; commandInput.focus()" class="px-3 py-1 bg-gray-100 rounded hover:bg-gray-200">--el</button>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
179
|
+
<div>
|
|
180
|
+
<h3 class="text-white font-semibold mb-2">Analysis Commands</h3>
|
|
181
|
+
<div class="space-y-2">
|
|
182
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('--c')">
|
|
183
|
+
<code class="text-[#4dabf7]">--c</code> - Critical degrees
|
|
122
184
|
</div>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
185
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('--rx')">
|
|
186
|
+
<code class="text-[#4dabf7]">--rx</code> - Retrograde planets
|
|
187
|
+
</div>
|
|
188
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('--el')">
|
|
189
|
+
<code class="text-[#4dabf7]">--el</code> - Element distribution
|
|
190
|
+
</div>
|
|
191
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('--af')">
|
|
192
|
+
<code class="text-[#4dabf7]">--af</code> - Aspect figures
|
|
129
193
|
</div>
|
|
130
194
|
</div>
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">klio --s --hs placidus</code> - Different house systems</li>
|
|
145
|
-
</ul>
|
|
146
|
-
|
|
147
|
-
<h3 class="text-base font-semibold text-gray-800 mt-6 mb-3">House Systems</h3>
|
|
148
|
-
<ul class="list-disc pl-6 space-y-2">
|
|
149
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--hs <system></code> - Shows planet + house position (Placidus, Koch, Porphyry, Regiomontanus, Campanus, Equal, WholeSign, Gauquelin, Vehlow, Topocentric, Alcabitius, Morinus)</li>
|
|
150
|
-
</ul>
|
|
151
|
-
|
|
152
|
-
<h3 class="text-base font-semibold text-gray-800 mt-6 mb-3">Date and Time</h3>
|
|
153
|
-
<ul class="list-disc pl-6 space-y-2">
|
|
154
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--d <date></code> - Use a specific date (Format: DD.MM.YYYY or "DD.MM.YYYY HH:MM")</li>
|
|
155
|
-
</ul>
|
|
156
|
-
|
|
157
|
-
<h3 class="text-base font-semibold text-gray-800 mt-6 mb-3">Advanced Features</h3>
|
|
158
|
-
<ul class="list-disc pl-6 space-y-2">
|
|
159
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--rx</code> - Shows all retrograde or stationary planets</li>
|
|
160
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--c</code> - Shows all planets on critical degrees</li>
|
|
161
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--el</code> - Shows element distribution of planets</li>
|
|
162
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--af</code> - Shows active aspect figures</li>
|
|
163
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--tr</code> - Shows personal transits based on birth data</li>
|
|
164
|
-
</ul>
|
|
165
|
-
|
|
166
|
-
<h3 class="text-base font-semibold text-gray-800 mt-6 mb-3">Past and Future Aspects</h3>
|
|
167
|
-
<ul class="list-disc pl-6 space-y-2">
|
|
168
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--v <count> [planet1] [aspect-type] [planet2]</code> - Shows past aspects</li>
|
|
169
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--z <count> [planet1] [aspect-type] [planet2]</code> - Shows future aspects</li>
|
|
170
|
-
</ul>
|
|
171
|
-
|
|
172
|
-
<h3 class="text-base font-semibold text-gray-800 mt-6 mb-3">Configuration</h3>
|
|
173
|
-
<ul class="list-disc pl-6 space-y-2">
|
|
174
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--setup</code> - Setup for a default chart</li>
|
|
175
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--status</code> - Shows the stored configuration data</li>
|
|
176
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--i</code> - Uses birth data from setup</li>
|
|
177
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--person <id> <data></code> - Creates or updates any person</li>
|
|
178
|
-
<li><code class="bg-gray-100 px-2 py-1 rounded">--people</code> - Lists all saved persons</li>
|
|
179
|
-
</ul>
|
|
180
|
-
</div>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<div>
|
|
198
|
+
<h3 class="text-white font-semibold mb-2">Chart Commands</h3>
|
|
199
|
+
<div class="space-y-2">
|
|
200
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('--s')">
|
|
201
|
+
<code class="text-[#4dabf7]">--s</code> - All planet positions
|
|
202
|
+
</div>
|
|
203
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('--s --hs placidus')">
|
|
204
|
+
<code class="text-[#4dabf7]">--s --hs placidus</code> - With house system
|
|
205
|
+
</div>
|
|
206
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('--hl koch')">
|
|
207
|
+
<code class="text-[#4dabf7]">--hl koch</code> - House list
|
|
181
208
|
</div>
|
|
182
209
|
</div>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<div>
|
|
213
|
+
<h3 class="text-white font-semibold mb-2">Configuration</h3>
|
|
214
|
+
<div class="space-y-2">
|
|
215
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('--status')">
|
|
216
|
+
<code class="text-[#4dabf7]">--status</code> - Show config
|
|
217
|
+
</div>
|
|
218
|
+
<div class="rounded bg-[#3d3d3d] px-2 py-1 text-sm transition-colors hover:bg-[#4a4a4a] cursor-pointer" onclick="insertCommand('--people')">
|
|
219
|
+
<code class="text-[#4dabf7]">--people</code> - List people
|
|
189
220
|
</div>
|
|
190
221
|
</div>
|
|
191
|
-
</
|
|
222
|
+
</div>
|
|
192
223
|
</div>
|
|
193
224
|
</div>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
225
|
+
|
|
226
|
+
<!-- WebContainer API -->
|
|
227
|
+
<script type="module">
|
|
228
|
+
try {
|
|
229
|
+
const { WebContainer } = await import('https://cdn.jsdelivr.net/npm/@webcontainer/api@1/+esm');
|
|
230
|
+
|
|
231
|
+
// Make WebContainer available globally for our service
|
|
232
|
+
window.WebContainer = WebContainer;
|
|
233
|
+
console.log('WebContainer API loaded successfully');
|
|
234
|
+
|
|
235
|
+
// Now load the WebContainer service
|
|
236
|
+
const { webcontainerService, WebContainerService } = await import('./webcontainerService.js');
|
|
237
|
+
window.webcontainerService = webcontainerService;
|
|
238
|
+
window.WebContainerService = WebContainerService;
|
|
239
|
+
console.log('WebContainer service loaded successfully');
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error('Failed to load WebContainer API or service:', error);
|
|
242
|
+
// WebContainer will not be available
|
|
243
|
+
|
|
244
|
+
// Load fallback WebContainer service
|
|
245
|
+
const script = document.createElement('script');
|
|
246
|
+
script.src = 'webcontainerService.js';
|
|
247
|
+
script.type = 'module';
|
|
248
|
+
document.body.appendChild(script);
|
|
249
|
+
}
|
|
250
|
+
</script>
|
|
251
|
+
|
|
197
252
|
<script>
|
|
253
|
+
const terminalContent = document.getElementById('terminalContent');
|
|
254
|
+
const terminalInput = document.getElementById('terminalInput');
|
|
255
|
+
const copyTerminalBtn = document.getElementById('copyTerminalBtn');
|
|
256
|
+
const helpPanel = document.getElementById('helpPanel');
|
|
257
|
+
const helpToggle = document.getElementById('helpToggle');
|
|
258
|
+
const sidebar = document.getElementById('sidebar');
|
|
259
|
+
const sidebarBackdrop = document.getElementById('sidebarBackdrop');
|
|
260
|
+
const menuToggle = document.getElementById('menuToggle');
|
|
261
|
+
const sidebarClose = document.getElementById('sidebarClose');
|
|
262
|
+
|
|
198
263
|
let commandHistory = [];
|
|
199
|
-
|
|
264
|
+
let historyIndex = -1;
|
|
265
|
+
let webcontainerAvailable = false;
|
|
266
|
+
|
|
200
267
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
268
|
+
function openSidebar() {
|
|
269
|
+
if (!sidebar || !sidebarBackdrop) return;
|
|
270
|
+
sidebar.classList.add('is-open');
|
|
271
|
+
sidebarBackdrop.classList.remove('hidden');
|
|
272
|
+
}
|
|
206
273
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
274
|
+
function closeSidebar() {
|
|
275
|
+
if (!sidebar || !sidebarBackdrop) return;
|
|
276
|
+
sidebar.classList.remove('is-open');
|
|
277
|
+
sidebarBackdrop.classList.add('hidden');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function handleSidebarResize() {
|
|
281
|
+
if (window.innerWidth >= 768) {
|
|
282
|
+
if (sidebarBackdrop) {
|
|
283
|
+
sidebarBackdrop.classList.add('hidden');
|
|
215
284
|
}
|
|
216
|
-
|
|
285
|
+
if (sidebar) {
|
|
286
|
+
sidebar.classList.remove('is-open');
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
if (sidebar && !sidebar.classList.contains('is-open')) {
|
|
290
|
+
sidebar.classList.remove('is-open');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
window.openSidebar = openSidebar;
|
|
296
|
+
window.closeSidebar = closeSidebar;
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
// Handle key down
|
|
300
|
+
function handleKeyDown(e) {
|
|
301
|
+
if (e.key === 'Enter') {
|
|
302
|
+
executeCommand();
|
|
303
|
+
} else if (e.key === 'ArrowUp') {
|
|
304
|
+
navigateHistory('up');
|
|
305
|
+
} else if (e.key === 'ArrowDown') {
|
|
306
|
+
navigateHistory('down');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Handle key up to preserve text selection
|
|
311
|
+
function handleKeyUp(e) {
|
|
312
|
+
// Check if there's currently a text selection in the terminal content
|
|
313
|
+
const selection = window.getSelection();
|
|
314
|
+
const hasTerminalSelection = selection && selection.toString().length > 0 &&
|
|
315
|
+
terminalContent.contains(selection.anchorNode);
|
|
217
316
|
|
|
218
|
-
//
|
|
219
|
-
|
|
317
|
+
// If there's an active selection in terminal content, don't steal focus
|
|
318
|
+
if (hasTerminalSelection) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Only focus the input if no text is selected and it's a relevant key
|
|
323
|
+
if (e.key !== 'Shift' && e.key !== 'Control' && e.key !== 'Alt' && e.key !== 'Meta' &&
|
|
324
|
+
e.key !== 'Tab' && document.activeElement !== terminalInput) {
|
|
325
|
+
terminalInput.focus();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Navigate command history
|
|
330
|
+
function navigateHistory(direction) {
|
|
331
|
+
if (direction === 'up') {
|
|
332
|
+
if (historyIndex < commandHistory.length - 1) {
|
|
333
|
+
historyIndex++;
|
|
334
|
+
terminalInput.value = commandHistory[historyIndex];
|
|
335
|
+
}
|
|
336
|
+
} else if (direction === 'down') {
|
|
337
|
+
if (historyIndex > 0) {
|
|
338
|
+
historyIndex--;
|
|
339
|
+
terminalInput.value = commandHistory[historyIndex];
|
|
340
|
+
} else if (historyIndex === 0) {
|
|
341
|
+
historyIndex = -1;
|
|
342
|
+
terminalInput.value = '';
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Execute command with WebContainer fallback
|
|
348
|
+
function shouldSyncConfigFromCommand(command) {
|
|
349
|
+
return command === '--setup' ||
|
|
350
|
+
command.includes('--person') ||
|
|
351
|
+
command.includes('--person1') ||
|
|
352
|
+
command.includes('--person2') ||
|
|
353
|
+
command.includes('--delete-person') ||
|
|
354
|
+
command.includes('--ai') ||
|
|
355
|
+
command.includes('--system');
|
|
220
356
|
}
|
|
221
357
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
358
|
+
async function executeCommand() {
|
|
359
|
+
const command = terminalInput.value.trim();
|
|
360
|
+
|
|
361
|
+
if (!command) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Add to history
|
|
366
|
+
if (command && !commandHistory.includes(command)) {
|
|
367
|
+
commandHistory.unshift(command);
|
|
368
|
+
if (commandHistory.length > 50) {
|
|
369
|
+
commandHistory.pop();
|
|
370
|
+
}
|
|
371
|
+
saveHistory();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Reset history index
|
|
375
|
+
historyIndex = -1;
|
|
376
|
+
|
|
377
|
+
// Sync config from localStorage to WebContainer before executing command
|
|
378
|
+
await syncConfigToWebContainer();
|
|
379
|
+
|
|
380
|
+
// Create command line
|
|
381
|
+
const commandLine = document.createElement('div');
|
|
382
|
+
commandLine.className = 'flex items-center gap-2 mb-1';
|
|
383
|
+
commandLine.innerHTML = `<span class="text-[#4dabf7]">klio@online:~$</span> ${escapeHtml(command)}`;
|
|
384
|
+
|
|
385
|
+
// Add to terminal
|
|
386
|
+
terminalContent.insertBefore(commandLine, terminalInput.parentElement);
|
|
387
|
+
|
|
388
|
+
// Clear input
|
|
389
|
+
terminalInput.value = '';
|
|
390
|
+
|
|
391
|
+
// Create output line
|
|
392
|
+
const outputLine = document.createElement('div');
|
|
393
|
+
outputLine.className = 'flex items-center gap-2 mb-1';
|
|
394
|
+
outputLine.innerHTML = '<span class="text-[#4dabf7]">klio@online:~$</span> <span class="text-gray-400">Running command...</span>';
|
|
395
|
+
|
|
396
|
+
// Add to terminal
|
|
397
|
+
terminalContent.insertBefore(outputLine, terminalInput.parentElement);
|
|
228
398
|
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
document.getElementById('nav-commands').classList.remove('active');
|
|
232
|
-
document.getElementById('nav-history').classList.remove('active');
|
|
399
|
+
// Scroll to bottom
|
|
400
|
+
terminalContent.scrollTop = terminalContent.scrollHeight;
|
|
233
401
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
402
|
+
try {
|
|
403
|
+
if (command === '--setup') {
|
|
404
|
+
outputLine.innerHTML = await runSetupFlow();
|
|
405
|
+
} else {
|
|
406
|
+
const result = await executeCommandInWebContainer(command);
|
|
407
|
+
outputLine.innerHTML = result;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (shouldSyncConfigFromCommand(command)) {
|
|
411
|
+
await syncConfigFromWebContainer();
|
|
412
|
+
}
|
|
413
|
+
} catch (error) {
|
|
414
|
+
outputLine.innerHTML = `<span class="text-[#4dabf7]">klio@online:~$</span><pre class="whitespace-pre-wrap mb-4 text-red-400">Error: ${escapeHtml(error.message)}</pre>`;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Scroll to bottom
|
|
418
|
+
terminalContent.scrollTop = terminalContent.scrollHeight;
|
|
419
|
+
|
|
420
|
+
// Focus input again
|
|
421
|
+
terminalInput.focus();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function normalizeOutput(rawOutput) {
|
|
425
|
+
return (rawOutput || '')
|
|
426
|
+
.replace(/\r\n/g, '\n')
|
|
427
|
+
.replace(/\n{2,}/g, '\n')
|
|
428
|
+
.replace(/^\n+|\n+$/g, '');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function formatCommandOutput(output, className) {
|
|
432
|
+
const normalizedOutput = normalizeOutput(output);
|
|
433
|
+
return `<pre class="whitespace-pre-wrap mb-4 ${className}">${escapeHtml(normalizedOutput)}</pre>`;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Execute command in WebContainer
|
|
437
|
+
async function executeCommandInWebContainer(command) {
|
|
438
|
+
if (!window.webcontainerService || !window.WebContainerService ||
|
|
439
|
+
typeof window.WebContainerService.isSupported !== 'function' ||
|
|
440
|
+
typeof window.webcontainerService.initialize !== 'function' ||
|
|
441
|
+
typeof window.webcontainerService.executeCommand !== 'function') {
|
|
442
|
+
return `<pre class="whitespace-pre-wrap mb-4 text-yellow-400">⚠️ WebContainer service is not available.</pre>`;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (!window.WebContainerService.isSupported()) {
|
|
446
|
+
return `<pre class="whitespace-pre-wrap mb-4 text-yellow-400">⚠️ WebContainer is not supported in this browser or context.</pre>`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
let isInitialized = false;
|
|
450
|
+
try {
|
|
451
|
+
isInitialized = await window.webcontainerService.initialize();
|
|
452
|
+
} catch (initError) {
|
|
453
|
+
return `<pre class="whitespace-pre-wrap mb-4 text-yellow-400">⚠️ WebContainer initialization failed: ${escapeHtml(initError.message)}</pre>`;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (!isInitialized) {
|
|
457
|
+
return `<pre class="whitespace-pre-wrap mb-4 text-yellow-400">⚠️ WebContainer is not available in this environment.</pre>`;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const result = await window.webcontainerService.executeCommand(command);
|
|
461
|
+
if (result && result.success) {
|
|
462
|
+
return formatCommandOutput(result.output || '', 'text-green-400');
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return formatCommandOutput(result && result.output ? result.output : 'WebContainer command failed.', 'text-red-400');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function parseCoordinates(rawValue) {
|
|
469
|
+
if (!rawValue) {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
const match = rawValue.trim().match(/^([-+]?\d+\.?\d*),\s*([-+]?\d+\.?\d*)$/);
|
|
473
|
+
if (!match) {
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
latitude: parseFloat(match[1]),
|
|
478
|
+
longitude: parseFloat(match[2])
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function geocodeLocation(locationName) {
|
|
483
|
+
const url = new URL('https://geocoding-api.open-meteo.com/v1/search');
|
|
484
|
+
url.searchParams.set('name', locationName);
|
|
485
|
+
url.searchParams.set('count', '1');
|
|
486
|
+
url.searchParams.set('language', 'en');
|
|
487
|
+
url.searchParams.set('format', 'json');
|
|
488
|
+
|
|
489
|
+
const response = await fetch(url.toString());
|
|
490
|
+
if (!response.ok) {
|
|
491
|
+
throw new Error('Geocoding request failed.');
|
|
492
|
+
}
|
|
493
|
+
const data = await response.json();
|
|
494
|
+
if (!data || !data.results || data.results.length === 0) {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const result = data.results[0];
|
|
499
|
+
return {
|
|
500
|
+
latitude: result.latitude,
|
|
501
|
+
longitude: result.longitude,
|
|
502
|
+
name: result.name,
|
|
503
|
+
country: result.country,
|
|
504
|
+
timezone: result.timezone
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// LocalStorage config management functions
|
|
509
|
+
function saveConfigToLocalStorage(config) {
|
|
510
|
+
try {
|
|
511
|
+
localStorage.setItem('astrocliConfig', JSON.stringify(config));
|
|
512
|
+
return true;
|
|
513
|
+
} catch (error) {
|
|
514
|
+
console.error('Failed to save config to localStorage:', error);
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function loadConfigFromLocalStorage() {
|
|
520
|
+
try {
|
|
521
|
+
const configData = localStorage.getItem('astrocliConfig');
|
|
522
|
+
return configData ? JSON.parse(configData) : null;
|
|
523
|
+
} catch (error) {
|
|
524
|
+
console.error('Failed to load config from localStorage:', error);
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Function to sync config from localStorage to WebContainer
|
|
530
|
+
async function syncConfigToWebContainer() {
|
|
531
|
+
if (!window.webcontainerService || !window.WebContainerService) {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
const config = loadConfigFromLocalStorage();
|
|
537
|
+
if (!config) {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const initialized = await window.webcontainerService.initialize();
|
|
542
|
+
if (!initialized) {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
await window.webcontainerService.writeConfig(config);
|
|
547
|
+
return true;
|
|
548
|
+
} catch (error) {
|
|
549
|
+
console.error('Failed to sync config to WebContainer:', error);
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function syncConfigFromWebContainer() {
|
|
555
|
+
if (!window.webcontainerService || !window.WebContainerService) {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
try {
|
|
560
|
+
const config = await window.webcontainerService.readConfig();
|
|
561
|
+
if (!config) {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return saveConfigToLocalStorage(config);
|
|
566
|
+
} catch (error) {
|
|
567
|
+
console.error('Failed to sync config from WebContainer:', error);
|
|
568
|
+
return false;
|
|
247
569
|
}
|
|
248
570
|
}
|
|
249
571
|
|
|
572
|
+
async function runSetupFlow() {
|
|
573
|
+
if (!window.webcontainerService || !window.WebContainerService) {
|
|
574
|
+
return `<pre class="whitespace-pre-wrap mb-4 text-yellow-400">⚠️ WebContainer service is not available.</pre>`;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const locationInput = prompt('Location (e.g., "Berlin, Germany" or "52.52,13.41"):');
|
|
578
|
+
if (!locationInput || !locationInput.trim()) {
|
|
579
|
+
return `<pre class="whitespace-pre-wrap mb-4 text-yellow-400">Setup cancelled.</pre>`;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
let geoData = null;
|
|
583
|
+
const coords = parseCoordinates(locationInput);
|
|
584
|
+
if (coords) {
|
|
585
|
+
geoData = {
|
|
586
|
+
latitude: coords.latitude,
|
|
587
|
+
longitude: coords.longitude,
|
|
588
|
+
name: `Custom location (${coords.latitude}°, ${coords.longitude}°)`,
|
|
589
|
+
country: 'Custom',
|
|
590
|
+
timezone: 'UTC'
|
|
591
|
+
};
|
|
592
|
+
} else {
|
|
593
|
+
try {
|
|
594
|
+
geoData = await geocodeLocation(locationInput.trim());
|
|
595
|
+
} catch (error) {
|
|
596
|
+
return `<pre class="whitespace-pre-wrap mb-4 text-red-400">Geocoding failed: ${escapeHtml(error.message)}</pre>`;
|
|
597
|
+
}
|
|
598
|
+
if (!geoData) {
|
|
599
|
+
return `<pre class="whitespace-pre-wrap mb-4 text-red-400">Location not found. Setup cancelled.</pre>`;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const birthDate = prompt('Birth date (DD.MM.YYYY):') || '';
|
|
604
|
+
const birthTime = prompt('Birth time (HH:MM):') || '';
|
|
605
|
+
const birthLocationInput = prompt('Birth location (optional, e.g., "Hamburg, Germany" or "53.55,10.00"):') || '';
|
|
606
|
+
|
|
607
|
+
let birthLocation = null;
|
|
608
|
+
const warnings = [];
|
|
609
|
+
if (birthLocationInput.trim()) {
|
|
610
|
+
const birthCoords = parseCoordinates(birthLocationInput);
|
|
611
|
+
if (birthCoords) {
|
|
612
|
+
birthLocation = {
|
|
613
|
+
name: `Custom location (${birthCoords.latitude}°, ${birthCoords.longitude}°)`,
|
|
614
|
+
country: 'Custom',
|
|
615
|
+
latitude: birthCoords.latitude,
|
|
616
|
+
longitude: birthCoords.longitude
|
|
617
|
+
};
|
|
618
|
+
} else {
|
|
619
|
+
try {
|
|
620
|
+
const birthGeoData = await geocodeLocation(birthLocationInput.trim());
|
|
621
|
+
if (birthGeoData) {
|
|
622
|
+
birthLocation = {
|
|
623
|
+
name: birthGeoData.name,
|
|
624
|
+
country: birthGeoData.country,
|
|
625
|
+
latitude: birthGeoData.latitude,
|
|
626
|
+
longitude: birthGeoData.longitude
|
|
627
|
+
};
|
|
628
|
+
} else {
|
|
629
|
+
warnings.push('Birth location not found; continuing without birth location.');
|
|
630
|
+
}
|
|
631
|
+
} catch (error) {
|
|
632
|
+
warnings.push('Birth location lookup failed; continuing without birth location.');
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const config = {
|
|
638
|
+
currentLocation: {
|
|
639
|
+
name: geoData.name,
|
|
640
|
+
country: geoData.country,
|
|
641
|
+
latitude: geoData.latitude,
|
|
642
|
+
longitude: geoData.longitude,
|
|
643
|
+
timezone: geoData.timezone
|
|
644
|
+
},
|
|
645
|
+
birthData: {
|
|
646
|
+
date: birthDate,
|
|
647
|
+
time: birthTime,
|
|
648
|
+
location: birthLocation
|
|
649
|
+
},
|
|
650
|
+
aiConfiguration: null,
|
|
651
|
+
setupDate: new Date().toISOString()
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
try {
|
|
655
|
+
const initialized = await window.webcontainerService.initialize();
|
|
656
|
+
if (initialized) {
|
|
657
|
+
await window.webcontainerService.writeConfig(config);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const saved = saveConfigToLocalStorage(config);
|
|
661
|
+
if (!saved) {
|
|
662
|
+
return `<pre class="whitespace-pre-wrap mb-4 text-red-400">Setup failed: Could not save configuration.</pre>`;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const warningText = warnings.length ? `\n${warnings.join('\n')}` : '';
|
|
666
|
+
return `<pre class="whitespace-pre-wrap mb-4 text-green-400">✅ Setup saved.${escapeHtml(warningText)}</pre>`;
|
|
667
|
+
} catch (error) {
|
|
668
|
+
return `<pre class="whitespace-pre-wrap mb-4 text-red-400">Setup failed: ${escapeHtml(error.message)}</pre>`;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Insert command from sidebar/help
|
|
673
|
+
function insertCommand(cmd) {
|
|
674
|
+
terminalInput.value = cmd;
|
|
675
|
+
terminalInput.focus();
|
|
676
|
+
// Move cursor to end
|
|
677
|
+
terminalInput.selectionStart = terminalInput.selectionEnd = terminalInput.value.length;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Toggle help panel
|
|
681
|
+
function toggleHelp() {
|
|
682
|
+
const isOpen = helpPanel.classList.contains('translate-x-0');
|
|
683
|
+
|
|
684
|
+
if (isOpen) {
|
|
685
|
+
helpPanel.classList.remove('translate-x-0');
|
|
686
|
+
helpPanel.classList.add('translate-x-full');
|
|
687
|
+
helpToggle.classList.remove('-translate-x-[85vw]', 'md:-translate-x-[300px]');
|
|
688
|
+
} else {
|
|
689
|
+
helpPanel.classList.remove('translate-x-full');
|
|
690
|
+
helpPanel.classList.add('translate-x-0');
|
|
691
|
+
helpToggle.classList.add('-translate-x-[85vw]', 'md:-translate-x-[300px]');
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
250
695
|
// Load history from localStorage
|
|
251
696
|
function loadHistory() {
|
|
252
697
|
try {
|
|
253
|
-
const savedHistory = localStorage.getItem('
|
|
698
|
+
const savedHistory = localStorage.getItem('klioTerminalHistory');
|
|
254
699
|
if (savedHistory) {
|
|
255
700
|
commandHistory = JSON.parse(savedHistory);
|
|
256
|
-
renderHistory();
|
|
257
701
|
}
|
|
258
702
|
} catch (error) {
|
|
259
703
|
console.error('Error loading history:', error);
|
|
260
704
|
}
|
|
261
705
|
}
|
|
262
|
-
|
|
706
|
+
|
|
263
707
|
// Save history to localStorage
|
|
264
708
|
function saveHistory() {
|
|
265
709
|
try {
|
|
266
|
-
localStorage.setItem('
|
|
710
|
+
localStorage.setItem('klioTerminalHistory', JSON.stringify(commandHistory));
|
|
267
711
|
} catch (error) {
|
|
268
712
|
console.error('Error saving history:', error);
|
|
269
713
|
}
|
|
270
714
|
}
|
|
271
|
-
|
|
272
|
-
//
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// Limit history size
|
|
281
|
-
if (commandHistory.length > maxHistory) {
|
|
282
|
-
commandHistory = commandHistory.slice(0, maxHistory);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
saveHistory();
|
|
286
|
-
renderHistory();
|
|
715
|
+
|
|
716
|
+
// Escape HTML for security
|
|
717
|
+
function escapeHtml(text) {
|
|
718
|
+
const div = document.createElement('div');
|
|
719
|
+
div.textContent = text;
|
|
720
|
+
return div.innerHTML
|
|
721
|
+
.replace(/\n/g, '<br>')
|
|
722
|
+
.replace(/ /g, ' ');
|
|
287
723
|
}
|
|
288
|
-
|
|
289
|
-
//
|
|
290
|
-
function
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
724
|
+
|
|
725
|
+
// Function to load user session information
|
|
726
|
+
async function loadUserSession() {
|
|
727
|
+
try {
|
|
728
|
+
const response = await fetch('/api/user-session');
|
|
729
|
+
const sessionData = await response.json();
|
|
730
|
+
|
|
731
|
+
if (sessionData.userId) {
|
|
732
|
+
document.getElementById('userId').textContent = `User: ${sessionData.userId}`;
|
|
733
|
+
document.getElementById('logoutBtn').style.display = 'inline';
|
|
734
|
+
} else {
|
|
735
|
+
document.getElementById('userId').textContent = 'User: Guest';
|
|
736
|
+
}
|
|
737
|
+
} catch (error) {
|
|
738
|
+
console.error('Error loading user session:', error);
|
|
739
|
+
document.getElementById('userId').textContent = 'User: Error loading session';
|
|
294
740
|
}
|
|
295
|
-
|
|
296
|
-
let html = '<div class="space-y-1">';
|
|
297
|
-
commandHistory.forEach((cmd, index) => {
|
|
298
|
-
html += `<div
|
|
299
|
-
class="command-input px-3 py-2 bg-gray-100 rounded cursor-pointer hover:bg-gray-200 text-sm truncate"
|
|
300
|
-
title="${cmd}"
|
|
301
|
-
onclick="commandInput.value = '${cmd.replace(/'/g, "\\'")}'; showPage('runner'); commandInput.focus()"
|
|
302
|
-
>${cmd}</div>`;
|
|
303
|
-
});
|
|
304
|
-
html += '</div>';
|
|
305
|
-
|
|
306
|
-
historyContainer.innerHTML = html;
|
|
307
741
|
}
|
|
308
|
-
|
|
309
|
-
//
|
|
310
|
-
async function
|
|
311
|
-
const command = commandInput.value.trim();
|
|
312
|
-
|
|
313
|
-
if (!command) {
|
|
314
|
-
showToast('Please enter a command');
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
|
|
742
|
+
|
|
743
|
+
// Function to logout
|
|
744
|
+
async function logout() {
|
|
318
745
|
try {
|
|
319
|
-
|
|
320
|
-
outputArea.textContent = 'Running command...\n\n';
|
|
321
|
-
|
|
322
|
-
// Add to history
|
|
323
|
-
addToHistory(command);
|
|
324
|
-
|
|
325
|
-
// Clear input for next command
|
|
326
|
-
commandInput.value = '';
|
|
327
|
-
|
|
328
|
-
// Run the command via API
|
|
329
|
-
const response = await fetch('/api/run-command', {
|
|
746
|
+
const response = await fetch('/api/logout', {
|
|
330
747
|
method: 'POST',
|
|
331
748
|
headers: {
|
|
332
749
|
'Content-Type': 'application/json'
|
|
333
|
-
}
|
|
334
|
-
body: JSON.stringify({ command })
|
|
750
|
+
}
|
|
335
751
|
});
|
|
336
752
|
|
|
337
753
|
const result = await response.json();
|
|
754
|
+
if (result.success) {
|
|
755
|
+
// Reload the page to get a new session
|
|
756
|
+
window.location.reload();
|
|
757
|
+
} else {
|
|
758
|
+
console.error('Logout failed:', result.error);
|
|
759
|
+
}
|
|
760
|
+
} catch (error) {
|
|
761
|
+
console.error('Error during logout:', error);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Initialize WebContainer status
|
|
766
|
+
async function initializeWebContainerStatus() {
|
|
767
|
+
try {
|
|
768
|
+
const statusElement = document.getElementById('wcStatusText');
|
|
769
|
+
const statusListener = (event) => {
|
|
770
|
+
if (!event || !event.detail) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
statusElement.textContent = `WebContainer: ${event.detail}`;
|
|
774
|
+
statusElement.className = 'text-red-400';
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
window.addEventListener('webcontainer-status', statusListener);
|
|
778
|
+
|
|
779
|
+
// Check if page is cross-origin isolated (required for WebContainers)
|
|
780
|
+
if (!window.crossOriginIsolated) {
|
|
781
|
+
statusElement.textContent = 'WebContainer: Not isolated (COOP/COEP headers missing)';
|
|
782
|
+
statusElement.className = 'text-red-400';
|
|
783
|
+
console.warn('Page is not cross-origin isolated. WebContainers require COOP and COEP headers.');
|
|
784
|
+
|
|
785
|
+
// Check if we're on localhost (which allows COOP/COEP without HTTPS)
|
|
786
|
+
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
|
787
|
+
console.log('Running on localhost - COOP/COEP headers should work without HTTPS');
|
|
788
|
+
} else if (window.location.protocol !== 'https:') {
|
|
789
|
+
console.warn('For non-localhost domains, HTTPS is required for COOP/COEP headers to work.');
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
338
794
|
|
|
339
|
-
if
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
795
|
+
// Check if WebContainer service is available
|
|
796
|
+
let retries = 0;
|
|
797
|
+
const maxRetries = 5;
|
|
798
|
+
|
|
799
|
+
while ((!window.webcontainerService || !window.WebContainerService) && retries < maxRetries) {
|
|
800
|
+
if (retries === 0) {
|
|
801
|
+
statusElement.textContent = 'WebContainer: Service loading...';
|
|
346
802
|
} else {
|
|
347
|
-
|
|
803
|
+
statusElement.textContent = 'WebContainer: Service loading... (retry ' + retries + ')';
|
|
348
804
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
805
|
+
statusElement.className = 'text-blue-400';
|
|
806
|
+
|
|
807
|
+
// Wait longer for the service to load (WebContainer API is large)
|
|
808
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
809
|
+
retries++;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (!window.webcontainerService || !window.WebContainerService) {
|
|
813
|
+
statusElement.textContent = 'WebContainer: Service not available';
|
|
814
|
+
statusElement.className = 'text-yellow-400';
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Check if WebContainers are supported
|
|
819
|
+
if (!window.WebContainerService ||
|
|
820
|
+
typeof window.WebContainerService.isSupported !== 'function' ||
|
|
821
|
+
!window.webcontainerService ||
|
|
822
|
+
typeof window.webcontainerService.initialize !== 'function' ||
|
|
823
|
+
typeof window.webcontainerService.executeCommand !== 'function') {
|
|
824
|
+
statusElement.textContent = 'WebContainer: Service class not available';
|
|
825
|
+
statusElement.className = 'text-yellow-400';
|
|
826
|
+
console.warn('WebContainerService is not defined or not properly loaded');
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (!window.WebContainerService.isSupported()) {
|
|
831
|
+
statusElement.textContent = 'WebContainer: Not supported in this browser';
|
|
832
|
+
statusElement.className = 'text-yellow-400';
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
statusElement.textContent = 'WebContainer: Supported, initializing...';
|
|
837
|
+
statusElement.className = 'text-blue-400';
|
|
838
|
+
|
|
839
|
+
// Try to initialize WebContainer
|
|
840
|
+
try {
|
|
841
|
+
const initialized = await window.webcontainerService.initialize();
|
|
842
|
+
if (initialized) {
|
|
843
|
+
statusElement.textContent = 'WebContainer: Ready!';
|
|
844
|
+
statusElement.className = 'text-green-400';
|
|
845
|
+
} else {
|
|
846
|
+
statusElement.textContent = 'WebContainer: Not available in this environment';
|
|
847
|
+
statusElement.className = 'text-red-400';
|
|
353
848
|
}
|
|
354
|
-
|
|
355
|
-
|
|
849
|
+
} catch (error) {
|
|
850
|
+
console.error('WebContainer initialization error:', error);
|
|
851
|
+
statusElement.textContent = 'WebContainer: Initialization error - ' + error.message;
|
|
852
|
+
statusElement.className = 'text-red-400';
|
|
356
853
|
}
|
|
357
854
|
|
|
358
855
|
} catch (error) {
|
|
359
|
-
console.error('Error
|
|
360
|
-
|
|
361
|
-
|
|
856
|
+
console.error('Error initializing WebContainer status:', error);
|
|
857
|
+
const statusElement = document.getElementById('wcStatusText');
|
|
858
|
+
statusElement.textContent = 'WebContainer: Error checking support - ' + error.message;
|
|
859
|
+
statusElement.className = 'text-red-400';
|
|
362
860
|
}
|
|
363
861
|
}
|
|
862
|
+
|
|
863
|
+
// Initialize when DOM is loaded
|
|
864
|
+
document.addEventListener('DOMContentLoaded', async function() {
|
|
865
|
+
terminalInput.focus();
|
|
866
|
+
loadHistory();
|
|
867
|
+
await loadUserSession();
|
|
868
|
+
|
|
869
|
+
// Sync config from localStorage to WebContainer on page load
|
|
870
|
+
await syncConfigToWebContainer();
|
|
871
|
+
await syncConfigFromWebContainer();
|
|
872
|
+
|
|
873
|
+
// Initialize WebContainer status with delay to allow service to load
|
|
874
|
+
setTimeout(async function() {
|
|
875
|
+
await initializeWebContainerStatus();
|
|
876
|
+
}, 1000);
|
|
877
|
+
|
|
878
|
+
// Handle input
|
|
879
|
+
terminalInput.addEventListener('keydown', handleKeyDown);
|
|
880
|
+
|
|
881
|
+
// Handle keyup events globally to preserve text selection
|
|
882
|
+
document.addEventListener('keyup', handleKeyUp);
|
|
364
883
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
884
|
+
if (menuToggle) {
|
|
885
|
+
menuToggle.addEventListener('click', openSidebar);
|
|
886
|
+
}
|
|
887
|
+
if (sidebarClose) {
|
|
888
|
+
sidebarClose.addEventListener('click', closeSidebar);
|
|
889
|
+
}
|
|
890
|
+
if (sidebarBackdrop) {
|
|
891
|
+
sidebarBackdrop.addEventListener('click', closeSidebar);
|
|
892
|
+
}
|
|
893
|
+
window.addEventListener('resize', handleSidebarResize);
|
|
894
|
+
handleSidebarResize();
|
|
370
895
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
896
|
+
// Auto-focus when clicking anywhere in terminal, but preserve text selection
|
|
897
|
+
terminalContent.addEventListener('click', (e) => {
|
|
898
|
+
// If the click was on selected text, don't steal focus
|
|
899
|
+
const selection = window.getSelection();
|
|
900
|
+
if (selection && selection.toString().length > 0 &&
|
|
901
|
+
terminalContent.contains(selection.anchorNode)) {
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
terminalInput.focus();
|
|
905
|
+
});
|
|
376
906
|
|
|
377
|
-
|
|
378
|
-
|
|
907
|
+
copyTerminalBtn.addEventListener('click', async () => {
|
|
908
|
+
try {
|
|
909
|
+
const text = terminalContent.innerText;
|
|
910
|
+
await navigator.clipboard.writeText(text);
|
|
911
|
+
copyTerminalBtn.textContent = 'Copied';
|
|
912
|
+
setTimeout(() => {
|
|
913
|
+
copyTerminalBtn.textContent = 'Copy';
|
|
914
|
+
}, 1200);
|
|
915
|
+
} catch (error) {
|
|
916
|
+
console.error('Failed to copy terminal contents:', error);
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
});
|
|
379
920
|
</script>
|
|
380
921
|
</body>
|
|
381
|
-
</html>
|
|
922
|
+
</html>
|