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.
- checksums.yaml +4 -4
- data/lib/gsd/ai/commands/api.rb +42 -11
- data/lib/gsd/api/client.rb +90 -0
- data/lib/gsd/api/middleware/cors.rb +33 -0
- data/lib/gsd/api/public/app.js +133 -0
- data/lib/gsd/api/public/index.html +100 -0
- data/lib/gsd/api/public/style.css +416 -0
- data/lib/gsd/api/routes/health.rb +51 -0
- data/lib/gsd/api/routes/phases.rb +57 -0
- data/lib/gsd/api/routes/roadmap.rb +40 -0
- data/lib/gsd/api/routes/state.rb +68 -0
- data/lib/gsd/api/server.rb +300 -0
- data/lib/gsd/api.rb +32 -0
- data/lib/gsd/cli.rb +124 -3
- data/lib/gsd/tui/app.rb +24 -0
- data/lib/gsd/version.rb +1 -1
- metadata +26 -1
|
@@ -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
|