@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.
Files changed (100) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +668 -0
  3. package/lib/core/agent-launcher.js +193 -0
  4. package/lib/core/audit.js +210 -0
  5. package/lib/core/connexions.js +80 -0
  6. package/lib/core/flowmap/api.js +111 -0
  7. package/lib/core/flowmap/cli-helpers.js +80 -0
  8. package/lib/core/flowmap/machine.js +281 -0
  9. package/lib/core/flowmap/persistence.js +83 -0
  10. package/lib/core/generators.js +183 -0
  11. package/lib/core/inbox.js +275 -0
  12. package/lib/core/logger.js +20 -0
  13. package/lib/core/mission.js +109 -0
  14. package/lib/core/notewriter/config.js +36 -0
  15. package/lib/core/notewriter/cr.js +237 -0
  16. package/lib/core/notewriter/log.js +112 -0
  17. package/lib/core/notewriter/notes.js +168 -0
  18. package/lib/core/notewriter/paths.js +45 -0
  19. package/lib/core/notewriter/reader.js +121 -0
  20. package/lib/core/notewriter/registry.js +80 -0
  21. package/lib/core/odm.js +191 -0
  22. package/lib/core/profile-picker.js +323 -0
  23. package/lib/core/project.js +287 -0
  24. package/lib/core/registry.js +129 -0
  25. package/lib/core/secrets.js +137 -0
  26. package/lib/core/services.js +45 -0
  27. package/lib/core/team.js +287 -0
  28. package/lib/core/templates.js +80 -0
  29. package/lib/kairos/agent-runner.js +261 -0
  30. package/lib/kairos/claude-invoker.js +90 -0
  31. package/lib/kairos/context-injector.js +331 -0
  32. package/lib/kairos/context-loader.js +108 -0
  33. package/lib/kairos/context-writer.js +45 -0
  34. package/lib/kairos/dispatcher-router.js +173 -0
  35. package/lib/kairos/dispatcher.js +139 -0
  36. package/lib/kairos/event-bus.js +287 -0
  37. package/lib/kairos/event-router.js +131 -0
  38. package/lib/kairos/flowmap-bridge.js +120 -0
  39. package/lib/kairos/hook-handlers.js +351 -0
  40. package/lib/kairos/hook-installer.js +207 -0
  41. package/lib/kairos/hook-prompts.js +54 -0
  42. package/lib/kairos/leader-rules.js +94 -0
  43. package/lib/kairos/pid-checker.js +108 -0
  44. package/lib/kairos/situation-detector.js +123 -0
  45. package/lib/sync/fallback-engine.js +97 -0
  46. package/lib/sync/hcm-client.js +170 -0
  47. package/lib/sync/health.js +47 -0
  48. package/lib/sync/llm-client.js +387 -0
  49. package/lib/sync/nemesis-client.js +379 -0
  50. package/lib/sync/service-session.js +74 -0
  51. package/lib/sync/sync-engine.js +178 -0
  52. package/lib/ui/box.js +104 -0
  53. package/lib/ui/brand.js +42 -0
  54. package/lib/ui/colors.js +57 -0
  55. package/lib/ui/dashboard.js +580 -0
  56. package/lib/ui/error-hints.js +49 -0
  57. package/lib/ui/format.js +61 -0
  58. package/lib/ui/menu.js +306 -0
  59. package/lib/ui/note-card.js +198 -0
  60. package/lib/ui/note-colors.js +26 -0
  61. package/lib/ui/note-detail.js +297 -0
  62. package/lib/ui/note-filters.js +252 -0
  63. package/lib/ui/note-views.js +283 -0
  64. package/lib/ui/prompt.js +81 -0
  65. package/lib/ui/spinner.js +139 -0
  66. package/lib/ui/streambox.js +46 -0
  67. package/lib/ui/table.js +42 -0
  68. package/lib/ui/tree.js +33 -0
  69. package/package.json +53 -0
  70. package/src/cli.js +457 -0
  71. package/src/commands/_helpers.js +119 -0
  72. package/src/commands/audit.js +187 -0
  73. package/src/commands/auth.js +316 -0
  74. package/src/commands/doctor.js +243 -0
  75. package/src/commands/hcm.js +147 -0
  76. package/src/commands/inbox.js +333 -0
  77. package/src/commands/init.js +160 -0
  78. package/src/commands/kairos.js +216 -0
  79. package/src/commands/kars.js +134 -0
  80. package/src/commands/mission.js +275 -0
  81. package/src/commands/notes.js +316 -0
  82. package/src/commands/notewriter.js +296 -0
  83. package/src/commands/odm.js +329 -0
  84. package/src/commands/orch.js +68 -0
  85. package/src/commands/project.js +123 -0
  86. package/src/commands/run.js +123 -0
  87. package/src/commands/services.js +705 -0
  88. package/src/commands/status.js +231 -0
  89. package/src/commands/team.js +572 -0
  90. package/src/config.js +84 -0
  91. package/src/index.js +5 -0
  92. package/templates/project-context.json +10 -0
  93. package/templates/template_CONTRIB-NAME.json +22 -0
  94. package/templates/template_CR-ODM-NAME-000.exemple.json +32 -0
  95. package/templates/template_DEC-NAME-000.json +18 -0
  96. package/templates/template_INTV-NAME-000.json +15 -0
  97. package/templates/template_MISSION_CONTRACT.json +46 -0
  98. package/templates/template_ODM-NAME-000.json +89 -0
  99. package/templates/template_REGISTRY-PROJECT.json +26 -0
  100. 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
+ }
@@ -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
+ }
@@ -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
+ }