trackops 1.0.0 → 1.0.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/README.md +59 -6
- package/lib/control.js +1 -0
- package/lib/init.js +12 -10
- package/lib/opera.js +3 -3
- package/lib/server.js +141 -0
- package/lib/skills.js +1 -1
- package/package.json +16 -3
- package/templates/etapa/agent.md +2 -2
- package/templates/etapa/references/etapa-cycle.md +1 -1
- package/templates/opera/agent.md +1 -1
- package/templates/skills/project-starter-skill/SKILL.md +5 -3
- package/ui/css/base.css +266 -0
- package/ui/css/charts.css +327 -0
- package/ui/css/components.css +570 -0
- package/ui/css/panels.css +953 -0
- package/ui/css/tokens.css +227 -0
- package/ui/favicon.svg +5 -0
- package/ui/index.html +91 -351
- package/ui/js/api.js +203 -0
- package/ui/js/app.js +199 -0
- package/ui/js/console-logger.js +172 -0
- package/ui/js/icons.js +104 -0
- package/ui/js/onboarding.js +437 -0
- package/ui/js/router.js +125 -0
- package/ui/js/state.js +129 -0
- package/ui/js/theme.js +100 -0
- package/ui/js/time-tracker.js +248 -0
- package/ui/js/utils.js +172 -0
- package/ui/js/views/board.js +254 -0
- package/ui/js/views/execution.js +256 -0
- package/ui/js/views/flash.js +47 -0
- package/ui/js/views/insights.js +339 -0
- package/ui/js/views/overview.js +364 -0
- package/ui/js/views/settings.js +243 -0
- package/ui/js/views/sidebar.js +132 -0
- package/ui/js/views/skills.js +162 -0
- package/ui/js/views/tasks.js +405 -0
- package/ui/js/views/topbar.js +183 -0
package/ui/index.html
CHANGED
|
@@ -1,356 +1,96 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="es">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
</div>
|
|
90
|
-
</form>
|
|
91
|
-
</div>
|
|
92
|
-
</article>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
</details>
|
|
96
|
-
|
|
97
|
-
<div class="overview-grid">
|
|
98
|
-
<details class="panel panel-details" open>
|
|
99
|
-
<summary class="details-summary">
|
|
100
|
-
<div>
|
|
101
|
-
<p class="eyebrow">Charts</p>
|
|
102
|
-
<h2>Evolucion y desempeno</h2>
|
|
103
|
-
</div>
|
|
104
|
-
</summary>
|
|
105
|
-
<div class="details-body charts-grid">
|
|
106
|
-
<div class="chart-card">
|
|
107
|
-
<p class="label">Progreso por fase</p>
|
|
108
|
-
<div id="phaseChart"></div>
|
|
109
|
-
</div>
|
|
110
|
-
<div class="chart-card">
|
|
111
|
-
<p class="label">Distribucion por estado</p>
|
|
112
|
-
<div id="statusChart"></div>
|
|
113
|
-
</div>
|
|
114
|
-
<div class="chart-card">
|
|
115
|
-
<p class="label">Actividad reciente</p>
|
|
116
|
-
<div id="activityChart"></div>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
</details>
|
|
120
|
-
|
|
121
|
-
<details class="panel panel-details" open>
|
|
122
|
-
<summary class="details-summary">
|
|
123
|
-
<div>
|
|
124
|
-
<p class="eyebrow">Repo</p>
|
|
125
|
-
<h2>Salud operativa</h2>
|
|
126
|
-
</div>
|
|
127
|
-
<span id="branchBadge" class="secondary-badge">branch</span>
|
|
128
|
-
</summary>
|
|
129
|
-
<div class="details-body">
|
|
130
|
-
<div id="repoOverview" class="stack-list"></div>
|
|
131
|
-
<div>
|
|
132
|
-
<p class="label">Documentacion derivada</p>
|
|
133
|
-
<div id="docsList" class="tag-row"></div>
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
</details>
|
|
137
|
-
|
|
138
|
-
<details class="panel panel-details" open>
|
|
139
|
-
<summary class="details-summary">
|
|
140
|
-
<div>
|
|
141
|
-
<p class="eyebrow">Decisions</p>
|
|
142
|
-
<h2>Externos y bloqueos</h2>
|
|
143
|
-
</div>
|
|
144
|
-
</summary>
|
|
145
|
-
<div class="details-body">
|
|
146
|
-
<div id="decisionList" class="stack-list"></div>
|
|
147
|
-
</div>
|
|
148
|
-
</details>
|
|
149
|
-
</div>
|
|
150
|
-
</section>
|
|
151
|
-
|
|
152
|
-
<section id="tab-board" class="tab-panel" hidden>
|
|
153
|
-
<div class="split-grid">
|
|
154
|
-
<details class="panel panel-details" open>
|
|
155
|
-
<summary class="details-summary">
|
|
156
|
-
<div>
|
|
157
|
-
<p class="eyebrow">Task Board</p>
|
|
158
|
-
<h2>Tablero operativo</h2>
|
|
159
|
-
</div>
|
|
160
|
-
<button id="newTaskButton" class="ghost-button" type="button">Nueva tarea</button>
|
|
161
|
-
</summary>
|
|
162
|
-
<div class="details-body">
|
|
163
|
-
<div id="board" class="board-grid" aria-label="Tablero de tareas"></div>
|
|
164
|
-
</div>
|
|
165
|
-
</details>
|
|
166
|
-
|
|
167
|
-
<details class="panel panel-details" open>
|
|
168
|
-
<summary class="details-summary">
|
|
169
|
-
<div>
|
|
170
|
-
<p class="eyebrow">Task Studio</p>
|
|
171
|
-
<h2 id="editorTitle">Nueva tarea</h2>
|
|
172
|
-
</div>
|
|
173
|
-
<button id="resetTaskButton" class="ghost-button" type="button">Limpiar</button>
|
|
174
|
-
</summary>
|
|
175
|
-
<div class="details-body">
|
|
176
|
-
<div class="action-strip" id="taskActionStrip" aria-label="Acciones rapidas de tarea">
|
|
177
|
-
<button type="button" class="chip-button" data-task-action="start">Iniciar</button>
|
|
178
|
-
<button type="button" class="chip-button" data-task-action="review">Revision</button>
|
|
179
|
-
<button type="button" class="chip-button" data-task-action="complete">Completar</button>
|
|
180
|
-
<button type="button" class="chip-button" data-task-action="block">Bloquear</button>
|
|
181
|
-
<button type="button" class="chip-button" data-task-action="pending">Pendiente</button>
|
|
182
|
-
</div>
|
|
183
|
-
|
|
184
|
-
<form id="taskForm" class="task-form">
|
|
185
|
-
<div class="field">
|
|
186
|
-
<label for="taskTitle">Titulo</label>
|
|
187
|
-
<input id="taskTitle" name="title" type="text" required />
|
|
188
|
-
</div>
|
|
189
|
-
|
|
190
|
-
<div class="form-grid two-up">
|
|
191
|
-
<div class="field">
|
|
192
|
-
<label for="taskPhase">Fase</label>
|
|
193
|
-
<select id="taskPhase" name="phase">
|
|
194
|
-
<!-- Populated dynamically from API -->
|
|
195
|
-
</select>
|
|
196
|
-
</div>
|
|
197
|
-
|
|
198
|
-
<div class="field">
|
|
199
|
-
<label for="taskPriority">Prioridad</label>
|
|
200
|
-
<select id="taskPriority" name="priority">
|
|
201
|
-
<option value="P0">P0</option>
|
|
202
|
-
<option value="P1">P1</option>
|
|
203
|
-
<option value="P2">P2</option>
|
|
204
|
-
<option value="P3">P3</option>
|
|
205
|
-
</select>
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
|
|
209
|
-
<div class="form-grid two-up">
|
|
210
|
-
<div class="field">
|
|
211
|
-
<label for="taskStatus">Estado</label>
|
|
212
|
-
<select id="taskStatus" name="status">
|
|
213
|
-
<option value="pending">Pendiente</option>
|
|
214
|
-
<option value="in_progress">En progreso</option>
|
|
215
|
-
<option value="in_review">En revision</option>
|
|
216
|
-
<option value="blocked">Bloqueada</option>
|
|
217
|
-
<option value="completed">Completada</option>
|
|
218
|
-
<option value="cancelled">Cancelada</option>
|
|
219
|
-
</select>
|
|
220
|
-
</div>
|
|
221
|
-
|
|
222
|
-
<div class="field">
|
|
223
|
-
<label for="taskStream">Stream</label>
|
|
224
|
-
<input id="taskStream" name="stream" type="text" placeholder="Operations" />
|
|
225
|
-
</div>
|
|
226
|
-
</div>
|
|
227
|
-
|
|
228
|
-
<div class="field checkbox-field">
|
|
229
|
-
<label for="taskRequired">
|
|
230
|
-
<input id="taskRequired" name="required" type="checkbox" checked />
|
|
231
|
-
Tarea requerida para entrega
|
|
232
|
-
</label>
|
|
233
|
-
</div>
|
|
234
|
-
|
|
235
|
-
<div class="field">
|
|
236
|
-
<label for="taskSummary">Resumen</label>
|
|
237
|
-
<textarea id="taskSummary" name="summary" rows="3"></textarea>
|
|
238
|
-
</div>
|
|
239
|
-
|
|
240
|
-
<div class="field">
|
|
241
|
-
<label for="taskDependsOn">Dependencias</label>
|
|
242
|
-
<textarea id="taskDependsOn" name="dependsOn" rows="2"></textarea>
|
|
243
|
-
</div>
|
|
244
|
-
|
|
245
|
-
<div class="field">
|
|
246
|
-
<label for="taskAcceptance">Criterios de aceptacion</label>
|
|
247
|
-
<textarea id="taskAcceptance" name="acceptance" rows="3"></textarea>
|
|
248
|
-
</div>
|
|
249
|
-
|
|
250
|
-
<div class="field">
|
|
251
|
-
<label for="taskBlocker">Bloqueador</label>
|
|
252
|
-
<textarea id="taskBlocker" name="blocker" rows="2"></textarea>
|
|
253
|
-
</div>
|
|
254
|
-
|
|
255
|
-
<div class="field">
|
|
256
|
-
<label for="taskNote">Nota de actualizacion</label>
|
|
257
|
-
<textarea id="taskNote" name="note" rows="2"></textarea>
|
|
258
|
-
</div>
|
|
259
|
-
|
|
260
|
-
<div class="form-actions">
|
|
261
|
-
<button class="primary-button" type="submit">Guardar tarea</button>
|
|
262
|
-
<button id="duplicateTaskButton" class="ghost-button" type="button">Duplicar base</button>
|
|
263
|
-
</div>
|
|
264
|
-
</form>
|
|
265
|
-
</div>
|
|
266
|
-
</details>
|
|
267
|
-
</div>
|
|
268
|
-
</section>
|
|
269
|
-
|
|
270
|
-
<section id="tab-execution" class="tab-panel" hidden>
|
|
271
|
-
<div class="split-grid">
|
|
272
|
-
<details class="panel panel-details" open>
|
|
273
|
-
<summary class="details-summary">
|
|
274
|
-
<div>
|
|
275
|
-
<p class="eyebrow">Command Deck</p>
|
|
276
|
-
<h2>Consola integrada</h2>
|
|
277
|
-
</div>
|
|
278
|
-
<span id="terminalStatus" class="secondary-badge">Lista</span>
|
|
279
|
-
</summary>
|
|
280
|
-
<div class="details-body">
|
|
281
|
-
<form id="commandForm" class="command-form">
|
|
282
|
-
<label class="sr-only" for="commandInput">Comando</label>
|
|
283
|
-
<input id="commandInput" name="command" type="text" spellcheck="false" autocomplete="off" placeholder="npm run ops:status" />
|
|
284
|
-
<button class="primary-button" type="submit">Ejecutar</button>
|
|
285
|
-
</form>
|
|
286
|
-
|
|
287
|
-
<div id="commandPresets" class="preset-row" aria-label="Comandos rapidos"></div>
|
|
288
|
-
<div id="sessionList" class="session-list"></div>
|
|
289
|
-
|
|
290
|
-
<div class="terminal-surface">
|
|
291
|
-
<pre id="terminalOutput">Selecciona o ejecuta un comando para ver la salida.</pre>
|
|
292
|
-
</div>
|
|
293
|
-
</div>
|
|
294
|
-
</details>
|
|
295
|
-
|
|
296
|
-
<details class="panel panel-details" open>
|
|
297
|
-
<summary class="details-summary">
|
|
298
|
-
<div>
|
|
299
|
-
<p class="eyebrow">Execution</p>
|
|
300
|
-
<h2>Resumen del runtime</h2>
|
|
301
|
-
</div>
|
|
302
|
-
</summary>
|
|
303
|
-
<div class="details-body">
|
|
304
|
-
<div id="executionMetrics" class="stack-list"></div>
|
|
305
|
-
</div>
|
|
306
|
-
</details>
|
|
307
|
-
</div>
|
|
308
|
-
</section>
|
|
309
|
-
|
|
310
|
-
<section id="tab-insights" class="tab-panel" hidden>
|
|
311
|
-
<div class="overview-grid">
|
|
312
|
-
<details class="panel panel-details" open>
|
|
313
|
-
<summary class="details-summary">
|
|
314
|
-
<div>
|
|
315
|
-
<p class="eyebrow">Activity</p>
|
|
316
|
-
<h2>Actividad reciente</h2>
|
|
317
|
-
</div>
|
|
318
|
-
</summary>
|
|
319
|
-
<div class="details-body">
|
|
320
|
-
<div id="activityList" class="stack-list"></div>
|
|
321
|
-
</div>
|
|
322
|
-
</details>
|
|
323
|
-
|
|
324
|
-
<details class="panel panel-details" open>
|
|
325
|
-
<summary class="details-summary">
|
|
326
|
-
<div>
|
|
327
|
-
<p class="eyebrow">Findings</p>
|
|
328
|
-
<h2>Hallazgos abiertos</h2>
|
|
329
|
-
</div>
|
|
330
|
-
</summary>
|
|
331
|
-
<div class="details-body">
|
|
332
|
-
<div id="findingList" class="stack-list"></div>
|
|
333
|
-
</div>
|
|
334
|
-
</details>
|
|
335
|
-
|
|
336
|
-
<details class="panel panel-details" open>
|
|
337
|
-
<summary class="details-summary">
|
|
338
|
-
<div>
|
|
339
|
-
<p class="eyebrow">Health</p>
|
|
340
|
-
<h2>KPIs del proyecto</h2>
|
|
341
|
-
</div>
|
|
342
|
-
</summary>
|
|
343
|
-
<div class="details-body">
|
|
344
|
-
<div id="healthRail" class="stack-list"></div>
|
|
345
|
-
</div>
|
|
346
|
-
</details>
|
|
347
|
-
</div>
|
|
348
|
-
</section>
|
|
349
|
-
</main>
|
|
350
|
-
|
|
351
|
-
<div id="flash" class="flash" aria-live="polite"></div>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>TrackOps Beta — Centro de control</title>
|
|
7
|
+
<meta name="description" content="Panel local beta de TrackOps: gestión de proyectos, tareas, analíticas y seguimiento de tiempo para desarrolladores." />
|
|
8
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
9
|
+
|
|
10
|
+
<!-- Google Fonts (coherente con web corporativa) -->
|
|
11
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
12
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
13
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Outfit:wght@300;400;500;600;700&family=Plus+Jakarta+Sans:wght@700;800&display=swap" rel="stylesheet" />
|
|
14
|
+
|
|
15
|
+
<!-- Anti-FOUC: aplica el tema antes del primer paint -->
|
|
16
|
+
<script>
|
|
17
|
+
(function(){
|
|
18
|
+
var t = localStorage.getItem('trackops-theme');
|
|
19
|
+
if (!t) t = 'light';
|
|
20
|
+
if (t === 'light') document.documentElement.setAttribute('data-theme','light');
|
|
21
|
+
})();
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<!-- CSS Modular -->
|
|
25
|
+
<link rel="stylesheet" href="/css/tokens.css" />
|
|
26
|
+
<link rel="stylesheet" href="/css/base.css" />
|
|
27
|
+
<link rel="stylesheet" href="/css/components.css" />
|
|
28
|
+
<link rel="stylesheet" href="/css/panels.css" />
|
|
29
|
+
<link rel="stylesheet" href="/css/charts.css" />
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
|
|
33
|
+
<!-- Skip to content (WCAG 2.2 AA) -->
|
|
34
|
+
<a class="skip-link" href="#view-container">Saltar al contenido principal</a>
|
|
35
|
+
|
|
36
|
+
<!-- App shell -->
|
|
37
|
+
<div class="app-shell">
|
|
38
|
+
|
|
39
|
+
<!-- ══ SIDEBAR ══ -->
|
|
40
|
+
<aside id="sidebar" role="navigation" aria-label="Navegación principal">
|
|
41
|
+
<!-- Rendered by sidebar.js -->
|
|
42
|
+
</aside>
|
|
43
|
+
|
|
44
|
+
<!-- ══ TOPBAR ══ -->
|
|
45
|
+
<header id="topbar" role="banner">
|
|
46
|
+
<!-- Rendered by topbar.js -->
|
|
47
|
+
</header>
|
|
48
|
+
|
|
49
|
+
<!-- ══ MAIN CONTENT ══ -->
|
|
50
|
+
<main id="view-container" role="main" tabindex="-1">
|
|
51
|
+
<!-- Rendered by router.js / views -->
|
|
52
|
+
</main>
|
|
53
|
+
|
|
54
|
+
</div><!-- /.app-shell -->
|
|
55
|
+
|
|
56
|
+
<!-- Flash / Toast container (aria-live para a11y) -->
|
|
57
|
+
<div id="flash-container" aria-live="polite" aria-atomic="false"></div>
|
|
58
|
+
|
|
59
|
+
<!-- Panel de registros -->
|
|
60
|
+
<div id="console-panel" class="console-panel" role="log" aria-label="Consola de errores" aria-live="off">
|
|
61
|
+
<div class="console-panel-header">
|
|
62
|
+
<div class="console-panel-title" id="console-panel-title">
|
|
63
|
+
<span>·</span> Registros
|
|
64
|
+
<span id="console-error-count" class="badge badge-danger" style="display:none">0</span>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="console-panel-actions">
|
|
67
|
+
<button class="btn btn-ghost btn-sm" id="console-clear-btn" type="button" aria-label="Limpiar logs">Limpiar</button>
|
|
68
|
+
<button class="btn btn-ghost btn-sm" id="console-close-btn" type="button" aria-label="Cerrar consola">✕</button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="console-logs" id="console-logs" role="list"></div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<!-- Onboarding Spotlight -->
|
|
75
|
+
<div id="onboarding-spotlight" class="onboarding-spotlight is-hidden" aria-hidden="true"></div>
|
|
76
|
+
|
|
77
|
+
<div id="onboarding-tooltip" class="onboarding-tooltip is-hidden" role="dialog" aria-modal="true" aria-labelledby="ob-title">
|
|
78
|
+
<div class="ob-step-label" id="ob-step-label">Paso 1 de X</div>
|
|
79
|
+
<h2 class="ob-title" id="ob-title"></h2>
|
|
80
|
+
<p class="ob-desc" id="ob-desc"></p>
|
|
81
|
+
|
|
82
|
+
<div class="ob-nav">
|
|
83
|
+
<div class="ob-dots" id="ob-dots" aria-hidden="true"></div>
|
|
84
|
+
<div class="ob-actions">
|
|
85
|
+
<button class="btn btn-ghost btn-sm" id="ob-skip" type="button">Saltar</button>
|
|
86
|
+
<button class="btn btn-ghost btn-sm" id="ob-prev" type="button">Anterior</button>
|
|
87
|
+
<button class="btn btn-primary btn-sm" id="ob-next" type="button">Siguiente</button>
|
|
88
|
+
</div>
|
|
352
89
|
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<!-- JS Módulos (ES modules) -->
|
|
93
|
+
<script type="module" src="/js/app.js"></script>
|
|
353
94
|
|
|
354
|
-
|
|
355
|
-
</body>
|
|
95
|
+
</body>
|
|
356
96
|
</html>
|
package/ui/js/api.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* api.js — Capa de comunicación con el backend TrackOps
|
|
3
|
+
* Wrapper sobre fetch con gestión de errores, project-awareness
|
|
4
|
+
* y tipado de los endpoints disponibles en lib/server.js
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as state from './state.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Llamada base a la API
|
|
11
|
+
* @param {string} url
|
|
12
|
+
* @param {RequestInit & { projectAware?: boolean }} options
|
|
13
|
+
* @returns {Promise<Object>}
|
|
14
|
+
*/
|
|
15
|
+
async function call(url, options = {}) {
|
|
16
|
+
const target = new URL(url, window.location.origin);
|
|
17
|
+
const currentId = state.get('currentProjectId');
|
|
18
|
+
if (options.projectAware !== false && currentId && !target.searchParams.has('project')) {
|
|
19
|
+
target.searchParams.set('project', currentId);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const response = await fetch(target, {
|
|
23
|
+
...options,
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
...(options.headers || {}),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const text = await response.text();
|
|
31
|
+
const json = text ? JSON.parse(text) : {};
|
|
32
|
+
|
|
33
|
+
if (!response.ok || json.ok === false) {
|
|
34
|
+
const err = new Error(json.error || `HTTP ${response.status}: ${response.statusText}`);
|
|
35
|
+
err.status = response.status;
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return json;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─────────────────────────────── PROYECTOS ──────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Lista todos los proyectos del portfolio
|
|
46
|
+
*/
|
|
47
|
+
export async function getProjects() {
|
|
48
|
+
return call('/api/projects', { projectAware: false });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Registra un proyecto existente en el portfolio
|
|
53
|
+
* @param {string} root - ruta del directorio del proyecto
|
|
54
|
+
*/
|
|
55
|
+
export async function registerProject(root) {
|
|
56
|
+
return call('/api/projects/register', {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
projectAware: false,
|
|
59
|
+
body: JSON.stringify({ root }),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Instala trackops en un nuevo proyecto
|
|
65
|
+
* @param {string} root
|
|
66
|
+
*/
|
|
67
|
+
export async function installProject(root) {
|
|
68
|
+
return call('/api/projects/install', {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
projectAware: false,
|
|
71
|
+
body: JSON.stringify({ root }),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─────────────────────────────── ESTADO ─────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Obtiene el estado completo del proyecto activo
|
|
79
|
+
*/
|
|
80
|
+
export async function getState() {
|
|
81
|
+
return call('/api/state');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ─────────────────────────────── TAREAS ─────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Crea una nueva tarea
|
|
88
|
+
* @param {Object} payload
|
|
89
|
+
*/
|
|
90
|
+
export async function createTask(payload) {
|
|
91
|
+
return call('/api/tasks', {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
body: JSON.stringify({ projectId: state.get('currentProjectId'), ...payload }),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Actualiza una tarea existente (edición completa)
|
|
99
|
+
* @param {string} taskId
|
|
100
|
+
* @param {Object} payload
|
|
101
|
+
*/
|
|
102
|
+
export async function updateTask(taskId, payload) {
|
|
103
|
+
return call(`/api/tasks/${encodeURIComponent(taskId)}`, {
|
|
104
|
+
method: 'PUT',
|
|
105
|
+
body: JSON.stringify({ projectId: state.get('currentProjectId'), ...payload }),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Ejecuta una acción sobre una tarea (start, review, complete, block, pending, cancel)
|
|
111
|
+
* @param {string} taskId
|
|
112
|
+
* @param {string} action
|
|
113
|
+
* @param {string} [note]
|
|
114
|
+
*/
|
|
115
|
+
export async function taskAction(taskId, action, note = '') {
|
|
116
|
+
return call(`/api/tasks/${encodeURIComponent(taskId)}/action`, {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
body: JSON.stringify({ projectId: state.get('currentProjectId'), action, note }),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─────────────────────────────── SYNC ───────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Sincroniza los docs del proyecto (task_plan.md, progress.md, findings.md)
|
|
126
|
+
*/
|
|
127
|
+
export async function syncDocs() {
|
|
128
|
+
return call('/api/sync', {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
body: JSON.stringify({ projectId: state.get('currentProjectId') }),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ─────────────────────────────── COMANDOS ───────────────────────────────────
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Ejecuta un comando en el shell del proyecto
|
|
138
|
+
* @param {string} command
|
|
139
|
+
*/
|
|
140
|
+
export async function runCommand(command) {
|
|
141
|
+
return call('/api/commands', {
|
|
142
|
+
method: 'POST',
|
|
143
|
+
body: JSON.stringify({ projectId: state.get('currentProjectId'), command }),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Crea un EventSource para hacer streaming de salida de una sesión
|
|
149
|
+
* @param {string} sessionId
|
|
150
|
+
* @returns {EventSource}
|
|
151
|
+
*/
|
|
152
|
+
export function streamSession(sessionId) {
|
|
153
|
+
return new EventSource(`/api/commands/${encodeURIComponent(sessionId)}/stream`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─────────────────────────────── TIME TRACKING ──────────────────────────────
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Inicia un time entry para una tarea
|
|
160
|
+
* @param {string} taskId
|
|
161
|
+
* @param {string} taskTitle
|
|
162
|
+
*/
|
|
163
|
+
export async function startTimeEntry(taskId, taskTitle) {
|
|
164
|
+
return call('/api/time/start', {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
body: JSON.stringify({ projectId: state.get('currentProjectId'), taskId, taskTitle }),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Detiene el time entry activo
|
|
172
|
+
* @param {string} entryId
|
|
173
|
+
*/
|
|
174
|
+
export async function stopTimeEntry(entryId) {
|
|
175
|
+
return call('/api/time/stop', {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
body: JSON.stringify({ projectId: state.get('currentProjectId'), entryId }),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Obtiene los time entries del proyecto activo
|
|
183
|
+
*/
|
|
184
|
+
export async function getTimeEntries() {
|
|
185
|
+
return call('/api/time');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─────────────────────────────── SKILLS HUB ────────────────────────────────
|
|
189
|
+
|
|
190
|
+
export async function fetchSkillsLocal() {
|
|
191
|
+
return call('/api/skills/local');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export async function fetchSkillsDiscover() {
|
|
195
|
+
return call('/api/skills/discover');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function installSkill(skillId) {
|
|
199
|
+
return call('/api/skills/install', {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
body: JSON.stringify({ skillId })
|
|
202
|
+
});
|
|
203
|
+
}
|