@arka-labs/nemesis 1.2.0
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/LICENSE +201 -0
- package/README.md +668 -0
- package/lib/core/agent-launcher.js +193 -0
- package/lib/core/audit.js +210 -0
- package/lib/core/connexions.js +80 -0
- package/lib/core/flowmap/api.js +111 -0
- package/lib/core/flowmap/cli-helpers.js +80 -0
- package/lib/core/flowmap/machine.js +281 -0
- package/lib/core/flowmap/persistence.js +83 -0
- package/lib/core/generators.js +183 -0
- package/lib/core/inbox.js +275 -0
- package/lib/core/logger.js +20 -0
- package/lib/core/mission.js +109 -0
- package/lib/core/notewriter/config.js +36 -0
- package/lib/core/notewriter/cr.js +237 -0
- package/lib/core/notewriter/log.js +112 -0
- package/lib/core/notewriter/notes.js +168 -0
- package/lib/core/notewriter/paths.js +45 -0
- package/lib/core/notewriter/reader.js +121 -0
- package/lib/core/notewriter/registry.js +80 -0
- package/lib/core/odm.js +191 -0
- package/lib/core/profile-picker.js +323 -0
- package/lib/core/project.js +287 -0
- package/lib/core/registry.js +129 -0
- package/lib/core/secrets.js +137 -0
- package/lib/core/services.js +45 -0
- package/lib/core/team.js +287 -0
- package/lib/core/templates.js +80 -0
- package/lib/kairos/agent-runner.js +261 -0
- package/lib/kairos/claude-invoker.js +90 -0
- package/lib/kairos/context-injector.js +331 -0
- package/lib/kairos/context-loader.js +108 -0
- package/lib/kairos/context-writer.js +45 -0
- package/lib/kairos/dispatcher-router.js +173 -0
- package/lib/kairos/dispatcher.js +139 -0
- package/lib/kairos/event-bus.js +287 -0
- package/lib/kairos/event-router.js +131 -0
- package/lib/kairos/flowmap-bridge.js +120 -0
- package/lib/kairos/hook-handlers.js +351 -0
- package/lib/kairos/hook-installer.js +207 -0
- package/lib/kairos/hook-prompts.js +54 -0
- package/lib/kairos/leader-rules.js +94 -0
- package/lib/kairos/pid-checker.js +108 -0
- package/lib/kairos/situation-detector.js +123 -0
- package/lib/sync/fallback-engine.js +97 -0
- package/lib/sync/hcm-client.js +170 -0
- package/lib/sync/health.js +47 -0
- package/lib/sync/llm-client.js +387 -0
- package/lib/sync/nemesis-client.js +379 -0
- package/lib/sync/service-session.js +74 -0
- package/lib/sync/sync-engine.js +178 -0
- package/lib/ui/box.js +104 -0
- package/lib/ui/brand.js +42 -0
- package/lib/ui/colors.js +57 -0
- package/lib/ui/dashboard.js +580 -0
- package/lib/ui/error-hints.js +49 -0
- package/lib/ui/format.js +61 -0
- package/lib/ui/menu.js +306 -0
- package/lib/ui/note-card.js +198 -0
- package/lib/ui/note-colors.js +26 -0
- package/lib/ui/note-detail.js +297 -0
- package/lib/ui/note-filters.js +252 -0
- package/lib/ui/note-views.js +283 -0
- package/lib/ui/prompt.js +81 -0
- package/lib/ui/spinner.js +139 -0
- package/lib/ui/streambox.js +46 -0
- package/lib/ui/table.js +42 -0
- package/lib/ui/tree.js +33 -0
- package/package.json +53 -0
- package/src/cli.js +457 -0
- package/src/commands/_helpers.js +119 -0
- package/src/commands/audit.js +187 -0
- package/src/commands/auth.js +316 -0
- package/src/commands/doctor.js +243 -0
- package/src/commands/hcm.js +147 -0
- package/src/commands/inbox.js +333 -0
- package/src/commands/init.js +160 -0
- package/src/commands/kairos.js +216 -0
- package/src/commands/kars.js +134 -0
- package/src/commands/mission.js +275 -0
- package/src/commands/notes.js +316 -0
- package/src/commands/notewriter.js +296 -0
- package/src/commands/odm.js +329 -0
- package/src/commands/orch.js +68 -0
- package/src/commands/project.js +123 -0
- package/src/commands/run.js +123 -0
- package/src/commands/services.js +705 -0
- package/src/commands/status.js +231 -0
- package/src/commands/team.js +572 -0
- package/src/config.js +84 -0
- package/src/index.js +5 -0
- package/templates/project-context.json +10 -0
- package/templates/template_CONTRIB-NAME.json +22 -0
- package/templates/template_CR-ODM-NAME-000.exemple.json +32 -0
- package/templates/template_DEC-NAME-000.json +18 -0
- package/templates/template_INTV-NAME-000.json +15 -0
- package/templates/template_MISSION_CONTRACT.json +46 -0
- package/templates/template_ODM-NAME-000.json +89 -0
- package/templates/template_REGISTRY-PROJECT.json +26 -0
- package/templates/template_TXN-NAME-000.json +24 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { emitKeypressEvents } from 'node:readline';
|
|
2
|
+
import { renderNoteCard, renderCrCard, formatTime } from './note-card.js';
|
|
3
|
+
import { style } from './colors.js';
|
|
4
|
+
|
|
5
|
+
const MAX_VISIBLE = 5;
|
|
6
|
+
|
|
7
|
+
function renderCard(entry, isActive) {
|
|
8
|
+
const card = entry.type === 'CR'
|
|
9
|
+
? renderCrCard(entry.data, { compact: !isActive })
|
|
10
|
+
: renderNoteCard(entry.data, { compact: !isActive });
|
|
11
|
+
return card.map(line => isActive ? style.bold(line) : style.dim(line));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function timeGroup(ts) {
|
|
15
|
+
if (!ts) return '--:--';
|
|
16
|
+
const t = formatTime(ts);
|
|
17
|
+
// Round to 5-minute groups
|
|
18
|
+
const h = t.slice(0, 2);
|
|
19
|
+
const m = String(Math.floor(parseInt(t.slice(3)) / 5) * 5).padStart(2, '0');
|
|
20
|
+
return `${h}:${m}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function renderAgentView(entries, options = {}) {
|
|
24
|
+
const { stdin, stdout } = process;
|
|
25
|
+
if (!stdin.isTTY) return { action: 'quit' };
|
|
26
|
+
if (entries.length === 0) return { action: 'quit' };
|
|
27
|
+
|
|
28
|
+
return new Promise(resolve => {
|
|
29
|
+
let cursor = 0;
|
|
30
|
+
let viewOffset = 0;
|
|
31
|
+
const maxVisible = Math.min(entries.length, MAX_VISIBLE);
|
|
32
|
+
|
|
33
|
+
emitKeypressEvents(stdin);
|
|
34
|
+
stdin.setRawMode(true);
|
|
35
|
+
stdin.resume();
|
|
36
|
+
stdin.setEncoding('utf8');
|
|
37
|
+
|
|
38
|
+
// Clear screen before initial render to avoid stacking
|
|
39
|
+
stdout.write('\x1b[2J\x1b[H');
|
|
40
|
+
let lastLineCount = 0;
|
|
41
|
+
|
|
42
|
+
function adjustViewport() {
|
|
43
|
+
if (cursor < viewOffset) viewOffset = cursor;
|
|
44
|
+
if (cursor >= viewOffset + maxVisible) viewOffset = cursor - maxVisible + 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function draw() {
|
|
48
|
+
const lines = [];
|
|
49
|
+
|
|
50
|
+
// Header
|
|
51
|
+
const agentShort = (options.agentName || '').replace(/^Agent_/i, '');
|
|
52
|
+
const filterInfo = options.filterSummary || 'aucun';
|
|
53
|
+
lines.push(` ${style.bold(`Notes \u2014 ${agentShort}`)}`);
|
|
54
|
+
lines.push(` ${entries.length} entrees \u00b7 filtre : ${filterInfo}`);
|
|
55
|
+
lines.push('');
|
|
56
|
+
|
|
57
|
+
// Scroll up
|
|
58
|
+
if (viewOffset > 0) {
|
|
59
|
+
lines.push(` ${style.dim(`\u25b2 ${viewOffset} de plus`)}`);
|
|
60
|
+
} else if (entries.length > maxVisible) {
|
|
61
|
+
lines.push('');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Visible cards
|
|
65
|
+
const visible = entries.slice(viewOffset, viewOffset + maxVisible);
|
|
66
|
+
for (let i = 0; i < visible.length; i++) {
|
|
67
|
+
const isActive = (viewOffset + i) === cursor;
|
|
68
|
+
const cardLines = renderCard(visible[i], isActive);
|
|
69
|
+
lines.push(...cardLines);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Scroll down
|
|
73
|
+
if (viewOffset + maxVisible < entries.length) {
|
|
74
|
+
const remaining = entries.length - viewOffset - maxVisible;
|
|
75
|
+
lines.push(` ${style.dim(`\u25bc ${remaining} de plus`)}`);
|
|
76
|
+
} else if (entries.length > maxVisible) {
|
|
77
|
+
lines.push('');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Footer navigation
|
|
81
|
+
lines.push('');
|
|
82
|
+
lines.push(` ${style.dim('[\u2191\u2193 naviguer] [Entree ouvrir] [f filtrer] [q quitter]')}`);
|
|
83
|
+
|
|
84
|
+
return lines;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function render() {
|
|
88
|
+
// Erase previous output
|
|
89
|
+
if (lastLineCount > 0) {
|
|
90
|
+
stdout.write(`\x1b[${lastLineCount}A\x1b[J`);
|
|
91
|
+
}
|
|
92
|
+
const lines = draw();
|
|
93
|
+
for (const l of lines) stdout.write(l + '\n');
|
|
94
|
+
lastLineCount = lines.length;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function cleanup() {
|
|
98
|
+
stdin.removeListener('keypress', onKeypress);
|
|
99
|
+
stdin.setRawMode(false);
|
|
100
|
+
stdin.pause();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function onKeypress(ch, key) {
|
|
104
|
+
if (!key) return;
|
|
105
|
+
|
|
106
|
+
if (key.ctrl && key.name === 'c') {
|
|
107
|
+
cleanup();
|
|
108
|
+
resolve({ action: 'quit' });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (key.name === 'escape' || (ch === 'q' && !key.ctrl && !key.meta)) {
|
|
113
|
+
cleanup();
|
|
114
|
+
resolve({ action: 'quit' });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (key.name === 'return') {
|
|
119
|
+
cleanup();
|
|
120
|
+
resolve({ action: 'inspect', entry: entries[cursor] });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (ch === 'f' && !key.ctrl && !key.meta) {
|
|
125
|
+
cleanup();
|
|
126
|
+
resolve({ action: 'filter' });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (key.name === 'up' || ch === 'k') {
|
|
131
|
+
cursor = (cursor - 1 + entries.length) % entries.length;
|
|
132
|
+
adjustViewport();
|
|
133
|
+
render();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (key.name === 'down' || ch === 'j') {
|
|
138
|
+
cursor = (cursor + 1) % entries.length;
|
|
139
|
+
adjustViewport();
|
|
140
|
+
render();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Initial draw
|
|
146
|
+
render();
|
|
147
|
+
stdin.on('keypress', onKeypress);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function renderTimelineView(entries, options = {}) {
|
|
152
|
+
const { stdin, stdout } = process;
|
|
153
|
+
if (!stdin.isTTY) return { action: 'quit' };
|
|
154
|
+
if (entries.length === 0) return { action: 'quit' };
|
|
155
|
+
|
|
156
|
+
return new Promise(resolve => {
|
|
157
|
+
let cursor = 0;
|
|
158
|
+
let viewOffset = 0;
|
|
159
|
+
const maxVisible = Math.min(entries.length, MAX_VISIBLE);
|
|
160
|
+
|
|
161
|
+
emitKeypressEvents(stdin);
|
|
162
|
+
stdin.setRawMode(true);
|
|
163
|
+
stdin.resume();
|
|
164
|
+
stdin.setEncoding('utf8');
|
|
165
|
+
|
|
166
|
+
// Clear screen before initial render to avoid stacking
|
|
167
|
+
stdout.write('\x1b[2J\x1b[H');
|
|
168
|
+
let lastLineCount = 0;
|
|
169
|
+
|
|
170
|
+
function adjustViewport() {
|
|
171
|
+
if (cursor < viewOffset) viewOffset = cursor;
|
|
172
|
+
if (cursor >= viewOffset + maxVisible) viewOffset = cursor - maxVisible + 1;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function draw() {
|
|
176
|
+
const lines = [];
|
|
177
|
+
|
|
178
|
+
// Header
|
|
179
|
+
const filterInfo = options.filterSummary || 'aucun';
|
|
180
|
+
const agentCount = new Set(entries.map(e => e.agentId)).size;
|
|
181
|
+
lines.push(` ${style.bold('Timeline projet')}`);
|
|
182
|
+
lines.push(` ${agentCount} agents \u00b7 ${entries.length} entrees \u00b7 filtre : ${filterInfo}`);
|
|
183
|
+
lines.push('');
|
|
184
|
+
|
|
185
|
+
// Scroll up
|
|
186
|
+
if (viewOffset > 0) {
|
|
187
|
+
lines.push(` ${style.dim(`\u25b2 ${viewOffset} de plus`)}`);
|
|
188
|
+
} else if (entries.length > maxVisible) {
|
|
189
|
+
lines.push('');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Visible cards with time separators
|
|
193
|
+
const visible = entries.slice(viewOffset, viewOffset + maxVisible);
|
|
194
|
+
let lastGroup = null;
|
|
195
|
+
for (let i = 0; i < visible.length; i++) {
|
|
196
|
+
const entry = visible[i];
|
|
197
|
+
const group = timeGroup(entry.timestamp);
|
|
198
|
+
if (group !== lastGroup) {
|
|
199
|
+
const sep = `\u2500\u2500 ${group} ` + '\u2500'.repeat(60);
|
|
200
|
+
lines.push(` ${style.dim(sep)}`);
|
|
201
|
+
lastGroup = group;
|
|
202
|
+
}
|
|
203
|
+
const isActive = (viewOffset + i) === cursor;
|
|
204
|
+
const cardLines = renderCard(entry, isActive);
|
|
205
|
+
lines.push(...cardLines);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Scroll down
|
|
209
|
+
if (viewOffset + maxVisible < entries.length) {
|
|
210
|
+
const remaining = entries.length - viewOffset - maxVisible;
|
|
211
|
+
lines.push(` ${style.dim(`\u25bc ${remaining} de plus`)}`);
|
|
212
|
+
} else if (entries.length > maxVisible) {
|
|
213
|
+
lines.push('');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Footer
|
|
217
|
+
lines.push('');
|
|
218
|
+
lines.push(` ${style.dim('[\u2191\u2193 naviguer] [Entree ouvrir] [f filtrer] [q quitter]')}`);
|
|
219
|
+
|
|
220
|
+
return lines;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function render() {
|
|
224
|
+
if (lastLineCount > 0) {
|
|
225
|
+
stdout.write(`\x1b[${lastLineCount}A\x1b[J`);
|
|
226
|
+
}
|
|
227
|
+
const lines = draw();
|
|
228
|
+
for (const l of lines) stdout.write(l + '\n');
|
|
229
|
+
lastLineCount = lines.length;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function cleanup() {
|
|
233
|
+
stdin.removeListener('keypress', onKeypress);
|
|
234
|
+
stdin.setRawMode(false);
|
|
235
|
+
stdin.pause();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function onKeypress(ch, key) {
|
|
239
|
+
if (!key) return;
|
|
240
|
+
|
|
241
|
+
if (key.ctrl && key.name === 'c') {
|
|
242
|
+
cleanup();
|
|
243
|
+
resolve({ action: 'quit' });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (key.name === 'escape' || (ch === 'q' && !key.ctrl && !key.meta)) {
|
|
248
|
+
cleanup();
|
|
249
|
+
resolve({ action: 'quit' });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (key.name === 'return') {
|
|
254
|
+
cleanup();
|
|
255
|
+
resolve({ action: 'inspect', entry: entries[cursor] });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (ch === 'f' && !key.ctrl && !key.meta) {
|
|
260
|
+
cleanup();
|
|
261
|
+
resolve({ action: 'filter' });
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (key.name === 'up' || ch === 'k') {
|
|
266
|
+
cursor = (cursor - 1 + entries.length) % entries.length;
|
|
267
|
+
adjustViewport();
|
|
268
|
+
render();
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (key.name === 'down' || ch === 'j') {
|
|
273
|
+
cursor = (cursor + 1) % entries.length;
|
|
274
|
+
adjustViewport();
|
|
275
|
+
render();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
render();
|
|
281
|
+
stdin.on('keypress', onKeypress);
|
|
282
|
+
});
|
|
283
|
+
}
|
package/lib/ui/prompt.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interactive prompts — stdin-based, zero deps.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export async function askText(question, defaultValue = '') {
|
|
8
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
9
|
+
const suffix = defaultValue ? ` (${defaultValue})` : '';
|
|
10
|
+
|
|
11
|
+
return new Promise(resolve => {
|
|
12
|
+
rl.question(` ? ${question}${suffix} : `, answer => {
|
|
13
|
+
rl.close();
|
|
14
|
+
resolve(answer.trim() || defaultValue);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function askChoice(question, choices) {
|
|
20
|
+
console.log(` ? ${question}`);
|
|
21
|
+
choices.forEach((c, i) => {
|
|
22
|
+
const label = typeof c === 'string' ? c : c.label;
|
|
23
|
+
const desc = typeof c === 'string' ? '' : (c.description ? ` — ${c.description}` : '');
|
|
24
|
+
const marker = i === 0 ? '›' : ' ';
|
|
25
|
+
console.log(` ${marker} ${label}${desc}`);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
29
|
+
return new Promise(resolve => {
|
|
30
|
+
rl.question(' Choix (numero ou texte) : ', answer => {
|
|
31
|
+
rl.close();
|
|
32
|
+
const idx = parseInt(answer, 10);
|
|
33
|
+
if (!isNaN(idx) && idx >= 1 && idx <= choices.length) {
|
|
34
|
+
const c = choices[idx - 1];
|
|
35
|
+
resolve(typeof c === 'string' ? c : c.value || c.label);
|
|
36
|
+
} else {
|
|
37
|
+
const match = choices.find(c => {
|
|
38
|
+
const label = typeof c === 'string' ? c : c.label;
|
|
39
|
+
return label.toLowerCase().startsWith(answer.toLowerCase());
|
|
40
|
+
});
|
|
41
|
+
resolve(match ? (typeof match === 'string' ? match : match.value || match.label) : answer);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function askMultiChoice(question, choices) {
|
|
48
|
+
console.log(` ? ${question} (numeros separes par virgules)`);
|
|
49
|
+
choices.forEach((c, i) => {
|
|
50
|
+
const label = typeof c === 'string' ? c : c.label;
|
|
51
|
+
console.log(` ${i + 1}. ${label}`);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
55
|
+
return new Promise(resolve => {
|
|
56
|
+
rl.question(' Choix : ', answer => {
|
|
57
|
+
rl.close();
|
|
58
|
+
const indices = answer.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
|
|
59
|
+
const selected = indices
|
|
60
|
+
.filter(i => i >= 1 && i <= choices.length)
|
|
61
|
+
.map(i => {
|
|
62
|
+
const c = choices[i - 1];
|
|
63
|
+
return typeof c === 'string' ? c : c.value || c.label;
|
|
64
|
+
});
|
|
65
|
+
resolve(selected);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function askConfirm(question, defaultYes = false) {
|
|
71
|
+
const hint = defaultYes ? '(Y/n)' : '(y/N)';
|
|
72
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
73
|
+
return new Promise(resolve => {
|
|
74
|
+
rl.question(` ? ${question} ${hint} › `, answer => {
|
|
75
|
+
rl.close();
|
|
76
|
+
const a = answer.trim().toLowerCase();
|
|
77
|
+
if (!a) return resolve(defaultYes);
|
|
78
|
+
resolve(a === 'y' || a === 'yes' || a === 'oui');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple spinner — unicode frames, no deps.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
6
|
+
|
|
7
|
+
export function createSpinner(message) {
|
|
8
|
+
let frameIdx = 0;
|
|
9
|
+
let timer = null;
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
start() {
|
|
13
|
+
process.stdout.write(` ${FRAMES[0]} ${message}`);
|
|
14
|
+
timer = setInterval(() => {
|
|
15
|
+
frameIdx = (frameIdx + 1) % FRAMES.length;
|
|
16
|
+
process.stdout.write(`\r ${FRAMES[frameIdx]} ${message}`);
|
|
17
|
+
}, 80);
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
update(newMessage) {
|
|
21
|
+
message = newMessage;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
stop(finalMessage, symbol = '✓') {
|
|
25
|
+
if (timer) clearInterval(timer);
|
|
26
|
+
process.stdout.write(`\r ${symbol} ${finalMessage || message}\n`);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
fail(finalMessage) {
|
|
30
|
+
this.stop(finalMessage || message, '✗');
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Multi-step spinner — shows progress through a list of steps.
|
|
37
|
+
* Format: ⠋ [2/3] Validation registre...
|
|
38
|
+
* Completed steps show ✓ above the current step.
|
|
39
|
+
*
|
|
40
|
+
* @param {string[]} steps — array of step labels
|
|
41
|
+
*/
|
|
42
|
+
export function createMultiStepSpinner(steps) {
|
|
43
|
+
const isTTY = process.stdout.isTTY;
|
|
44
|
+
let currentStep = 0;
|
|
45
|
+
let frameIdx = 0;
|
|
46
|
+
let timer = null;
|
|
47
|
+
let linesWritten = 0;
|
|
48
|
+
|
|
49
|
+
function clearLines() {
|
|
50
|
+
if (!isTTY || linesWritten === 0) return;
|
|
51
|
+
for (let i = 0; i < linesWritten; i++) {
|
|
52
|
+
process.stdout.write('\x1b[1A\x1b[2K');
|
|
53
|
+
}
|
|
54
|
+
linesWritten = 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function render(frame) {
|
|
58
|
+
const lines = [];
|
|
59
|
+
// Completed steps
|
|
60
|
+
for (let i = 0; i < currentStep; i++) {
|
|
61
|
+
lines.push(` \u2713 [${i + 1}/${steps.length}] ${steps[i]}`);
|
|
62
|
+
}
|
|
63
|
+
// Current step with spinner
|
|
64
|
+
if (currentStep < steps.length) {
|
|
65
|
+
lines.push(` ${frame} [${currentStep + 1}/${steps.length}] ${steps[currentStep]}...`);
|
|
66
|
+
}
|
|
67
|
+
return lines;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function draw() {
|
|
71
|
+
clearLines();
|
|
72
|
+
const lines = render(FRAMES[frameIdx]);
|
|
73
|
+
const output = lines.join('\n') + '\n';
|
|
74
|
+
process.stdout.write(output);
|
|
75
|
+
linesWritten = lines.length;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
start() {
|
|
80
|
+
if (!isTTY) {
|
|
81
|
+
// Non-TTY: print first step immediately
|
|
82
|
+
process.stdout.write(` [1/${steps.length}] ${steps[0]}...\n`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
draw();
|
|
86
|
+
timer = setInterval(() => {
|
|
87
|
+
frameIdx = (frameIdx + 1) % FRAMES.length;
|
|
88
|
+
draw();
|
|
89
|
+
}, 80);
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
nextStep() {
|
|
93
|
+
currentStep++;
|
|
94
|
+
if (!isTTY) {
|
|
95
|
+
if (currentStep < steps.length) {
|
|
96
|
+
process.stdout.write(` [${currentStep + 1}/${steps.length}] ${steps[currentStep]}...\n`);
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
draw();
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
complete(msg) {
|
|
104
|
+
if (timer) clearInterval(timer);
|
|
105
|
+
if (!isTTY) {
|
|
106
|
+
process.stdout.write(` \u2713 ${msg || 'Termine'}\n`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
clearLines();
|
|
110
|
+
// Show all steps as completed
|
|
111
|
+
for (let i = 0; i < steps.length; i++) {
|
|
112
|
+
process.stdout.write(` \u2713 [${i + 1}/${steps.length}] ${steps[i]}\n`);
|
|
113
|
+
}
|
|
114
|
+
if (msg) {
|
|
115
|
+
process.stdout.write(` \u2713 ${msg}\n`);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
fail(msg) {
|
|
120
|
+
if (timer) clearInterval(timer);
|
|
121
|
+
if (!isTTY) {
|
|
122
|
+
process.stdout.write(` \u2717 ${msg || 'Echec'}\n`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
clearLines();
|
|
126
|
+
// Show completed steps
|
|
127
|
+
for (let i = 0; i < currentStep; i++) {
|
|
128
|
+
process.stdout.write(` \u2713 [${i + 1}/${steps.length}] ${steps[i]}\n`);
|
|
129
|
+
}
|
|
130
|
+
// Show failed step
|
|
131
|
+
if (currentStep < steps.length) {
|
|
132
|
+
process.stdout.write(` \u2717 [${currentStep + 1}/${steps.length}] ${steps[currentStep]}\n`);
|
|
133
|
+
}
|
|
134
|
+
if (msg) {
|
|
135
|
+
process.stdout.write(` \u2717 ${msg}\n`);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamBox — fixed-height rolling output (N visible lines).
|
|
3
|
+
* TTY: uses ANSI cursor-up + clear-line to rewrite in place.
|
|
4
|
+
* Non-TTY / NO_COLOR: simple line-by-line stdout.write.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { isColorEnabled } from './colors.js';
|
|
8
|
+
|
|
9
|
+
export function createStreamBox(maxLines = 4) {
|
|
10
|
+
const buffer = [];
|
|
11
|
+
let linesWritten = 0;
|
|
12
|
+
const isTTY = Boolean(process.stdout.isTTY) && isColorEnabled();
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
push(line) {
|
|
16
|
+
const trimmed = line.replace(/\n+$/, '');
|
|
17
|
+
buffer.push(trimmed);
|
|
18
|
+
if (buffer.length > maxLines) buffer.shift();
|
|
19
|
+
|
|
20
|
+
if (isTTY) {
|
|
21
|
+
// Move cursor up to overwrite previous lines
|
|
22
|
+
if (linesWritten > 0) {
|
|
23
|
+
process.stdout.write(`\x1b[${linesWritten}A`);
|
|
24
|
+
}
|
|
25
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
26
|
+
process.stdout.write(`\x1b[2K ${buffer[i]}\n`);
|
|
27
|
+
}
|
|
28
|
+
linesWritten = buffer.length;
|
|
29
|
+
} else {
|
|
30
|
+
process.stdout.write(` ${trimmed}\n`);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
clear() {
|
|
35
|
+
if (isTTY && linesWritten > 0) {
|
|
36
|
+
process.stdout.write(`\x1b[${linesWritten}A`);
|
|
37
|
+
for (let i = 0; i < linesWritten; i++) {
|
|
38
|
+
process.stdout.write('\x1b[2K\n');
|
|
39
|
+
}
|
|
40
|
+
process.stdout.write(`\x1b[${linesWritten}A`);
|
|
41
|
+
}
|
|
42
|
+
buffer.length = 0;
|
|
43
|
+
linesWritten = 0;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
package/lib/ui/table.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ASCII table renderer — zero deps, emoji-aware.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { stringWidth, padEndVisible } from './box.js';
|
|
6
|
+
|
|
7
|
+
export function renderTable(columns, rows, _opts = {}) {
|
|
8
|
+
if (!rows.length) {
|
|
9
|
+
return ' (aucune donnee)';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const widths = columns.map((col) => {
|
|
13
|
+
const header = col.label || col.key;
|
|
14
|
+
const maxData = rows.reduce((max, row) => {
|
|
15
|
+
const val = String(row[col.key] ?? '');
|
|
16
|
+
return Math.max(max, stringWidth(val));
|
|
17
|
+
}, 0);
|
|
18
|
+
return Math.max(stringWidth(header), maxData);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const hr = ' ' + border('\u250c', '\u252c', '\u2510', widths);
|
|
22
|
+
const sep = ' ' + border('\u251c', '\u253c', '\u2524', widths);
|
|
23
|
+
const bottom = ' ' + border('\u2514', '\u2534', '\u2518', widths);
|
|
24
|
+
|
|
25
|
+
const headerRow = ' ' + row(columns.map(c => c.label || c.key), widths);
|
|
26
|
+
const dataRows = rows.map(r => {
|
|
27
|
+
const cells = columns.map(c => String(r[c.key] ?? ''));
|
|
28
|
+
return ' ' + row(cells, widths);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return [hr, headerRow, sep, ...dataRows, bottom].join('\n');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function border(left, mid, right, widths) {
|
|
35
|
+
const segments = widths.map(w => '\u2500'.repeat(w + 2));
|
|
36
|
+
return left + segments.join(mid) + right;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function row(cells, widths) {
|
|
40
|
+
const padded = cells.map((cell, i) => ' ' + padEndVisible(cell, widths[i]) + ' ');
|
|
41
|
+
return '\u2502' + padded.join('\u2502') + '\u2502';
|
|
42
|
+
}
|
package/lib/ui/tree.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree renderer — hierarchical display with unicode prefixes.
|
|
3
|
+
*
|
|
4
|
+
* Input: { label, children?: TreeNode[] }
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { style } from './colors.js';
|
|
8
|
+
|
|
9
|
+
export function renderTree(node, _opts = {}) {
|
|
10
|
+
const lines = [];
|
|
11
|
+
buildLines(node, '', true, lines, true);
|
|
12
|
+
return lines.map(l => ' ' + l).join('\n');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function buildLines(node, prefix, isLast, lines, isRoot = false) {
|
|
16
|
+
const connector = isRoot ? '' : (isLast ? '\u2514\u2500\u2500 ' : '\u251c\u2500\u2500 ');
|
|
17
|
+
const label = node.label || node.name || '(unknown)';
|
|
18
|
+
|
|
19
|
+
const styledConnector = connector ? style.dim(connector) : '';
|
|
20
|
+
let line = prefix + styledConnector + style.bold(label);
|
|
21
|
+
if (node.meta) {
|
|
22
|
+
line += style.dim(` (${node.meta})`);
|
|
23
|
+
}
|
|
24
|
+
lines.push(line);
|
|
25
|
+
|
|
26
|
+
const children = node.children || [];
|
|
27
|
+
children.forEach((child, i) => {
|
|
28
|
+
const childPrefix = isRoot
|
|
29
|
+
? ''
|
|
30
|
+
: prefix + (isLast ? ' ' : style.dim('\u2502') + ' ');
|
|
31
|
+
buildLines(child, childPrefix, i === children.length - 1, lines);
|
|
32
|
+
});
|
|
33
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@arka-labs/nemesis",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "PM cockpit for orchestrating AI agent teams — file-first, HCM-native, Kairos hooks",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nemesis": "./src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"templates/",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"test:coverage": "vitest run --coverage",
|
|
19
|
+
"lint": "eslint src/ lib/",
|
|
20
|
+
"lint:fix": "eslint src/ lib/ --fix",
|
|
21
|
+
"prepublishOnly": "npm run lint && npm test",
|
|
22
|
+
"release": "npm test && git tag v$(node -p \"require('./package.json').version\") && git push --tags"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"cli",
|
|
26
|
+
"agents",
|
|
27
|
+
"ai",
|
|
28
|
+
"llm",
|
|
29
|
+
"orchestration",
|
|
30
|
+
"project-management",
|
|
31
|
+
"hcm",
|
|
32
|
+
"arka-labs"
|
|
33
|
+
],
|
|
34
|
+
"author": "ARKA LABS <contact@arkalabs.app>",
|
|
35
|
+
"license": "Apache-2.0",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/arka-squad/arkalabs-nemesis-cli"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/arka-squad/arkalabs-nemesis-cli#readme",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=20.0.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"xstate": "^5.28.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@eslint/js": "^10.0.1",
|
|
49
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
50
|
+
"eslint": "^10.0.3",
|
|
51
|
+
"vitest": "^3.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|