fantasy-cli 1.2.10 → 1.2.11

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.
@@ -0,0 +1,416 @@
1
+ :root {
2
+ /* Colors - Hacker/Fantasy Theme */
3
+ --bg-base: #050510;
4
+ --bg-panel: #0a0a1a;
5
+ --bg-input: #111122;
6
+ --text-main: #e0e0e0;
7
+ --text-dim: #8888aa;
8
+ --accent-cyan: #00f0ff;
9
+ --accent-purple: #9d4edd;
10
+ --accent-pink: #ff007f;
11
+ --border-dim: #1a1a3a;
12
+ --glow-cyan: 0 0 10px rgba(0, 240, 255, 0.4);
13
+ --glow-purple: 0 0 10px rgba(157, 78, 221, 0.4);
14
+
15
+ /* Fonts */
16
+ --font-body: "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
17
+ --font-mono: "Fira Code", "Consolas", monospace;
18
+ }
19
+
20
+ * {
21
+ box-sizing: border-box;
22
+ margin: 0;
23
+ padding: 0;
24
+ }
25
+
26
+ body {
27
+ background-color: var(--bg-base);
28
+ color: var(--text-main);
29
+ font-family: var(--font-body);
30
+ min-height: 100vh;
31
+ display: flex;
32
+ flex-direction: column;
33
+ padding: 20px;
34
+ gap: 20px;
35
+ }
36
+
37
+ /* Header */
38
+ .app-header {
39
+ display: flex;
40
+ justify-content: space-between;
41
+ align-items: center;
42
+ padding: 15px 20px;
43
+ background-color: var(--bg-panel);
44
+ border: 1px solid var(--border-dim);
45
+ border-radius: 8px;
46
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5);
47
+ }
48
+
49
+ .logo h1 {
50
+ font-family: var(--font-mono);
51
+ color: var(--accent-cyan);
52
+ text-transform: uppercase;
53
+ font-size: 1.5rem;
54
+ text-shadow: var(--glow-cyan);
55
+ }
56
+
57
+ .logo .subtitle {
58
+ font-size: 0.8rem;
59
+ color: var(--accent-purple);
60
+ font-family: var(--font-mono);
61
+ letter-spacing: 2px;
62
+ text-transform: uppercase;
63
+ }
64
+
65
+ .app-nav ul {
66
+ display: flex;
67
+ list-style: none;
68
+ gap: 20px;
69
+ }
70
+
71
+ .app-nav a {
72
+ color: var(--text-main);
73
+ text-decoration: none;
74
+ font-family: var(--font-mono);
75
+ font-size: 0.9rem;
76
+ transition:
77
+ color 0.3s,
78
+ text-shadow 0.3s;
79
+ }
80
+
81
+ .app-nav a:hover {
82
+ color: var(--accent-pink);
83
+ text-shadow: 0 0 8px var(--accent-pink);
84
+ }
85
+
86
+ /* Main Grid */
87
+ .dashboard-grid {
88
+ display: grid;
89
+ grid-template-columns: 1fr 1.5fr;
90
+ grid-template-rows: auto 1fr;
91
+ gap: 20px;
92
+ flex-grow: 1;
93
+ /* Cap height so inner scrollbars work */
94
+ height: calc(100vh - 120px);
95
+ }
96
+
97
+ /* Specific Placements */
98
+ #project-state {
99
+ grid-column: 1 / 2;
100
+ grid-row: 1 / 2;
101
+ }
102
+ #phases-list {
103
+ grid-column: 1 / 2;
104
+ grid-row: 2 / 3;
105
+ }
106
+ #roadmap-summary {
107
+ grid-column: 2 / 3;
108
+ grid-row: 1 / 2;
109
+ }
110
+ #ai-chat {
111
+ grid-column: 2 / 3;
112
+ grid-row: 2 / 3;
113
+ }
114
+
115
+ /* Panels */
116
+ .panel {
117
+ background-color: var(--bg-panel);
118
+ border: 1px solid var(--border-dim);
119
+ border-radius: 8px;
120
+ display: flex;
121
+ flex-direction: column;
122
+ overflow: hidden;
123
+ position: relative;
124
+ }
125
+
126
+ .panel::before {
127
+ content: "";
128
+ position: absolute;
129
+ top: 0;
130
+ left: 0;
131
+ right: 0;
132
+ height: 2px;
133
+ background: linear-gradient(
134
+ 90deg,
135
+ var(--accent-cyan),
136
+ var(--accent-purple)
137
+ );
138
+ }
139
+
140
+ .panel-header {
141
+ padding: 15px 20px;
142
+ border-bottom: 1px solid var(--border-dim);
143
+ background-color: rgba(255, 255, 255, 0.02);
144
+ }
145
+
146
+ .panel-header h2 {
147
+ font-family: var(--font-mono);
148
+ font-size: 1.1rem;
149
+ color: var(--accent-purple);
150
+ text-transform: uppercase;
151
+ letter-spacing: 1px;
152
+ }
153
+
154
+ .panel-content {
155
+ padding: 20px;
156
+ flex-grow: 1;
157
+ overflow-y: auto;
158
+ display: flex;
159
+ flex-direction: column;
160
+ gap: 15px;
161
+ }
162
+
163
+ .loading-indicator {
164
+ color: var(--text-dim);
165
+ font-family: var(--font-mono);
166
+ font-size: 0.9rem;
167
+ animation: pulse 1.5s infinite;
168
+ }
169
+
170
+ @keyframes pulse {
171
+ 0%,
172
+ 100% {
173
+ opacity: 0.5;
174
+ }
175
+ 50% {
176
+ opacity: 1;
177
+ }
178
+ }
179
+
180
+ /* State Content (Injected by JS) */
181
+ .stat-row {
182
+ display: flex;
183
+ justify-content: space-between;
184
+ font-family: var(--font-mono);
185
+ font-size: 0.9rem;
186
+ }
187
+
188
+ .stat-row .label {
189
+ color: var(--text-dim);
190
+ }
191
+
192
+ .stat-row .value {
193
+ color: var(--accent-cyan);
194
+ font-weight: bold;
195
+ }
196
+
197
+ .progress-bar-container {
198
+ height: 8px;
199
+ background-color: var(--bg-input);
200
+ border-radius: 4px;
201
+ overflow: hidden;
202
+ margin-top: 10px;
203
+ }
204
+
205
+ .progress-bar {
206
+ height: 100%;
207
+ background: linear-gradient(
208
+ 90deg,
209
+ var(--accent-cyan),
210
+ var(--accent-purple)
211
+ );
212
+ width: 0%;
213
+ transition: width 0.3s ease;
214
+ box-shadow: var(--glow-cyan);
215
+ }
216
+
217
+ /* Phase and Roadmap Items (Injected by JS) */
218
+ .phase-item,
219
+ .roadmap-item {
220
+ background: var(--bg-input);
221
+ border: 1px solid var(--border-dim);
222
+ padding: 10px 15px;
223
+ border-radius: 6px;
224
+ font-size: 0.9rem;
225
+ font-family: var(--font-mono);
226
+ }
227
+
228
+ .status-badge {
229
+ display: inline-block;
230
+ padding: 2px 6px;
231
+ border-radius: 4px;
232
+ font-size: 0.75rem;
233
+ margin-left: 10px;
234
+ }
235
+
236
+ .status-active {
237
+ color: var(--accent-cyan);
238
+ border: 1px solid var(--accent-cyan);
239
+ }
240
+ .status-completed {
241
+ color: #10b981;
242
+ border: 1px solid #10b981;
243
+ }
244
+ .status-pending {
245
+ color: var(--text-dim);
246
+ border: 1px solid var(--text-dim);
247
+ }
248
+
249
+ /* Chat Section */
250
+ .chat-layout {
251
+ padding: 0;
252
+ display: flex;
253
+ flex-direction: column;
254
+ height: 100%;
255
+ }
256
+
257
+ .chat-log-area {
258
+ flex-grow: 1;
259
+ padding: 20px;
260
+ overflow-y: auto;
261
+ display: flex;
262
+ flex-direction: column;
263
+ gap: 10px;
264
+ background-color: var(--bg-panel);
265
+ }
266
+
267
+ .message {
268
+ padding: 10px 15px;
269
+ border-radius: 6px;
270
+ font-family: var(--font-mono);
271
+ font-size: 0.85rem;
272
+ line-height: 1.4;
273
+ max-width: 85%;
274
+ }
275
+
276
+ .message.system {
277
+ color: var(--text-dim);
278
+ align-self: center;
279
+ border-left: 2px solid var(--text-dim);
280
+ max-width: 100%;
281
+ }
282
+
283
+ .message.user {
284
+ align-self: flex-end;
285
+ background-color: rgba(157, 78, 221, 0.1);
286
+ border: 1px solid var(--accent-purple);
287
+ color: var(--text-main);
288
+ border-bottom-right-radius: 0;
289
+ }
290
+
291
+ .message.ai {
292
+ align-self: flex-start;
293
+ background-color: rgba(0, 240, 255, 0.1);
294
+ border: 1px solid var(--accent-cyan);
295
+ color: var(--text-main);
296
+ border-bottom-left-radius: 0;
297
+ }
298
+
299
+ .timestamp {
300
+ color: var(--accent-pink);
301
+ margin-right: 5px;
302
+ }
303
+
304
+ .chat-input-area {
305
+ padding: 15px;
306
+ background-color: var(--bg-input);
307
+ border-top: 1px solid var(--border-dim);
308
+ display: flex;
309
+ flex-direction: column;
310
+ gap: 10px;
311
+ }
312
+
313
+ .config-row {
314
+ display: flex;
315
+ gap: 15px;
316
+ }
317
+
318
+ .message-row {
319
+ display: flex;
320
+ gap: 15px;
321
+ align-items: flex-end;
322
+ }
323
+
324
+ .input-group {
325
+ display: flex;
326
+ flex-direction: column;
327
+ gap: 5px;
328
+ }
329
+
330
+ .flex-grow {
331
+ flex-grow: 1;
332
+ }
333
+
334
+ .input-group label {
335
+ font-family: var(--font-mono);
336
+ font-size: 0.75rem;
337
+ color: var(--text-dim);
338
+ text-transform: uppercase;
339
+ }
340
+
341
+ select,
342
+ input,
343
+ textarea {
344
+ background-color: var(--bg-base);
345
+ color: var(--text-main);
346
+ border: 1px solid var(--border-dim);
347
+ padding: 8px 12px;
348
+ border-radius: 4px;
349
+ font-family: var(--font-mono);
350
+ font-size: 0.85rem;
351
+ outline: none;
352
+ transition:
353
+ border-color 0.3s,
354
+ box-shadow 0.3s;
355
+ }
356
+
357
+ textarea {
358
+ resize: vertical;
359
+ }
360
+
361
+ select:focus,
362
+ input:focus,
363
+ textarea:focus {
364
+ border-color: var(--accent-cyan);
365
+ box-shadow: 0 0 5px rgba(0, 240, 255, 0.3);
366
+ }
367
+
368
+ .action-btn {
369
+ background-color: transparent;
370
+ color: var(--accent-cyan);
371
+ border: 1px solid var(--accent-cyan);
372
+ padding: 0 20px;
373
+ border-radius: 4px;
374
+ font-family: var(--font-mono);
375
+ font-weight: bold;
376
+ text-transform: uppercase;
377
+ cursor: pointer;
378
+ transition: all 0.3s ease;
379
+ height: 38px;
380
+ }
381
+
382
+ .action-btn:hover {
383
+ background-color: var(--accent-cyan);
384
+ color: var(--bg-base);
385
+ box-shadow: var(--glow-cyan);
386
+ }
387
+
388
+ /* Scrollbar */
389
+ ::-webkit-scrollbar {
390
+ width: 8px;
391
+ }
392
+ ::-webkit-scrollbar-track {
393
+ background: var(--bg-base);
394
+ }
395
+ ::-webkit-scrollbar-thumb {
396
+ background: var(--border-dim);
397
+ border-radius: 4px;
398
+ }
399
+ ::-webkit-scrollbar-thumb:hover {
400
+ background: var(--accent-purple);
401
+ }
402
+
403
+ @media (max-width: 900px) {
404
+ .dashboard-grid {
405
+ grid-template-columns: 1fr;
406
+ height: auto;
407
+ }
408
+ #project-state,
409
+ #phases-list,
410
+ #roadmap-summary,
411
+ #ai-chat {
412
+ grid-column: 1 / 2;
413
+ grid-row: auto;
414
+ min-height: 300px;
415
+ }
416
+ }
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module Gsd
6
+ module API
7
+ module Routes
8
+ # Health route handler - Returns API server status and metadata
9
+ class Health
10
+ class << self
11
+ # Returns health check response
12
+ #
13
+ # @param cwd [String] Working directory
14
+ # @return [String] JSON response
15
+ def check(cwd: nil)
16
+ cwd ||= Dir.pwd
17
+ JSON.pretty_generate(
18
+ status: 'ok',
19
+ service: 'fantasy-cli-api',
20
+ version: Gsd::VERSION,
21
+ cwd: cwd,
22
+ timestamp: Time.now.iso8601,
23
+ uptime: uptime_seconds,
24
+ ruby_version: RUBY_VERSION,
25
+ endpoints: available_endpoints
26
+ )
27
+ end
28
+
29
+ private
30
+
31
+ def uptime_seconds
32
+ # Track server start time
33
+ @start_time ||= Time.now
34
+ (Time.now - @start_time).round
35
+ end
36
+
37
+ def available_endpoints
38
+ [
39
+ 'GET /api/health',
40
+ 'GET /api/state',
41
+ 'GET /api/state/:section',
42
+ 'GET /api/phases',
43
+ 'GET /api/phases/:num',
44
+ 'GET /api/roadmap'
45
+ ]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Gsd
6
+ module API
7
+ module Routes
8
+ # Phases route handlers - Exposes Gsd::Phase operations via HTTP
9
+ class Phases
10
+ class << self
11
+ # Lists all phases
12
+ #
13
+ # @param cwd [String] Working directory
14
+ # @return [String] JSON response
15
+ def list(cwd)
16
+ data = Gsd::Phase.list(cwd: cwd)
17
+ JSON.pretty_generate({
18
+ success: true,
19
+ data: data
20
+ })
21
+ end
22
+
23
+ # Finds a specific phase
24
+ #
25
+ # @param phase_num [String] Phase number to find
26
+ # @param cwd [String] Working directory
27
+ # @return [String] JSON response
28
+ def find(phase_num, cwd)
29
+ data = Gsd::Phase.find(phase_num, cwd: cwd)
30
+ JSON.pretty_generate({
31
+ success: true,
32
+ phase: phase_num,
33
+ data: data
34
+ })
35
+ end
36
+
37
+ # Creates a new phase
38
+ #
39
+ # @param description [String] Phase description
40
+ # @param cwd [String] Working directory
41
+ # @return [String] JSON response
42
+ def add(description, cwd)
43
+ unless description && !description.strip.empty?
44
+ raise ArgumentError, "description is required"
45
+ end
46
+
47
+ data = Gsd::Phase.add(description, cwd: cwd)
48
+ JSON.pretty_generate({
49
+ success: true,
50
+ data: data
51
+ })
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Gsd
6
+ module API
7
+ module Routes
8
+ # Roadmap route handlers - Exposes Gsd::Roadmap operations via HTTP
9
+ class Roadmap
10
+ class << self
11
+ # Analyzes the full roadmap
12
+ #
13
+ # @param cwd [String] Working directory
14
+ # @return [String] JSON response
15
+ def analyze(cwd)
16
+ data = Gsd::Roadmap.analyze(cwd: cwd)
17
+ JSON.pretty_generate({
18
+ success: true,
19
+ data: data
20
+ })
21
+ end
22
+
23
+ # Gets a specific phase from the roadmap
24
+ #
25
+ # @param phase_num [String] Phase number to retrieve
26
+ # @param cwd [String] Working directory
27
+ # @return [String] JSON response
28
+ def get_phase(phase_num, cwd)
29
+ data = Gsd::Roadmap.get_phase(phase_num, cwd: cwd)
30
+ JSON.pretty_generate({
31
+ success: true,
32
+ phase: phase_num,
33
+ data: data
34
+ })
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Gsd
6
+ module API
7
+ module Routes
8
+ # State route handlers - Exposes Gsd::State operations via HTTP
9
+ class State
10
+ class << self
11
+ # Returns state JSON
12
+ #
13
+ # @param cwd [String] Working directory
14
+ # @return [String] JSON response
15
+ def json(cwd)
16
+ data = Gsd::State.json(cwd: cwd)
17
+ JSON.pretty_generate({
18
+ success: true,
19
+ data: data
20
+ })
21
+ end
22
+
23
+ # Returns a specific state section
24
+ #
25
+ # @param section [String] Section name to retrieve
26
+ # @param cwd [String] Working directory
27
+ # @return [String] JSON response
28
+ def get_section(section, cwd)
29
+ data = Gsd::State.get(section: section, cwd: cwd)
30
+ JSON.pretty_generate({
31
+ success: true,
32
+ section: section,
33
+ data: data
34
+ })
35
+ end
36
+
37
+ # Updates a single state field
38
+ #
39
+ # @param field [String] Field name to update
40
+ # @param value [String] New value
41
+ # @param cwd [String] Working directory
42
+ # @return [String] JSON response
43
+ def update(field, value, cwd)
44
+ data = Gsd::State.update(field: field, value: value, cwd: cwd)
45
+ JSON.pretty_generate({
46
+ success: true,
47
+ field: field,
48
+ data: data
49
+ })
50
+ end
51
+
52
+ # Patches multiple state fields
53
+ #
54
+ # @param fields [Hash] Fields to update
55
+ # @param cwd [String] Working directory
56
+ # @return [String] JSON response
57
+ def patch(fields, cwd)
58
+ data = Gsd::State.patch(fields: fields, cwd: cwd)
59
+ JSON.pretty_generate({
60
+ success: true,
61
+ data: data
62
+ })
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end