@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,572 @@
|
|
|
1
|
+
import { teamAdd, teamList, teamInspect, teamRemove } from '../../lib/core/team.js';
|
|
2
|
+
import { readRegistry, writeRegistry, getLanes, getPM, updateLaneField } from '../../lib/core/registry.js';
|
|
3
|
+
import { createHcmClient } from '../../lib/sync/hcm-client.js';
|
|
4
|
+
import { pickProfile } from '../../lib/core/profile-picker.js';
|
|
5
|
+
import { askText, askMultiChoice, askConfirm } from '../../lib/ui/prompt.js';
|
|
6
|
+
import { interactiveMenu } from '../../lib/ui/menu.js';
|
|
7
|
+
import { renderTree } from '../../lib/ui/tree.js';
|
|
8
|
+
import { renderTable } from '../../lib/ui/table.js';
|
|
9
|
+
import { titledBox } from '../../lib/ui/box.js';
|
|
10
|
+
import { style } from '../../lib/ui/colors.js';
|
|
11
|
+
import { isPidAlive } from '../../lib/kairos/pid-checker.js';
|
|
12
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { ensureProject, pickFromList, getFlag, sanitizeName, BACK_ITEM, HOME_ITEM } from './_helpers.js';
|
|
15
|
+
|
|
16
|
+
const HELP = `
|
|
17
|
+
nemesis team — Gestion equipe agents
|
|
18
|
+
|
|
19
|
+
Usage: nemesis team <subcommand> [options]
|
|
20
|
+
|
|
21
|
+
Subcommands:
|
|
22
|
+
add Onboard un nouvel agent
|
|
23
|
+
list Affiche l'organigramme
|
|
24
|
+
panneau [agent] Panneau agent (alias: inspect)
|
|
25
|
+
leader [agent] Active/desactive le flag leader (--on|--off)
|
|
26
|
+
remove [agent] Desactive un agent (archive)
|
|
27
|
+
|
|
28
|
+
Options (add):
|
|
29
|
+
--profile <id> Profil Kairos
|
|
30
|
+
--name <name> Nom de l'agent
|
|
31
|
+
--role <role> Role sur le projet
|
|
32
|
+
--reports-to <a> Hierarchie
|
|
33
|
+
--no-prompt Mode non-interactif
|
|
34
|
+
-h, --help Aide
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
export async function handler({ args, flags, config }) {
|
|
38
|
+
if (flags.help || args.includes('--help') || args.includes('-h')) {
|
|
39
|
+
console.log(HELP);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let sub = args[0];
|
|
44
|
+
let subArgs = args.slice(1);
|
|
45
|
+
|
|
46
|
+
if (!sub) {
|
|
47
|
+
sub = await interactiveMenu([
|
|
48
|
+
{ label: 'list', value: 'list', description: 'Affiche l\'organigramme' },
|
|
49
|
+
{ label: 'add', value: 'add', description: 'Onboard un nouvel agent' },
|
|
50
|
+
{ label: 'panneau', value: 'panneau', description: 'Panneau agent' },
|
|
51
|
+
{ label: 'leader', value: 'leader', description: 'Active/desactive le flag leader' },
|
|
52
|
+
{ label: 'remove', value: 'remove', description: 'Desactive un agent' },
|
|
53
|
+
BACK_ITEM,
|
|
54
|
+
HOME_ITEM,
|
|
55
|
+
], { title: 'nemesis team' });
|
|
56
|
+
if (!sub || sub === '__back__') return '__back__';
|
|
57
|
+
if (sub === '__home__') return '__home__';
|
|
58
|
+
subArgs = [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
switch (sub) {
|
|
62
|
+
case 'add':
|
|
63
|
+
return handleAdd(subArgs, flags, config);
|
|
64
|
+
case 'list':
|
|
65
|
+
return handleList(subArgs, flags, config);
|
|
66
|
+
case 'panneau':
|
|
67
|
+
case 'inspect':
|
|
68
|
+
return handlePanneau(subArgs, flags, config);
|
|
69
|
+
case 'leader':
|
|
70
|
+
return handleLeader(subArgs, flags, config);
|
|
71
|
+
case 'remove':
|
|
72
|
+
return handleRemove(subArgs, flags, config);
|
|
73
|
+
default:
|
|
74
|
+
console.error(`Sous-commande inconnue : ${sub}`);
|
|
75
|
+
console.log(HELP);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getAgentLanes(project) {
|
|
80
|
+
const registry = readRegistry(project.hcm_dir);
|
|
81
|
+
return registry ? getLanes(registry) : [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function pickAgent(project, title = 'Choisir un agent') {
|
|
85
|
+
const lanes = getAgentLanes(project);
|
|
86
|
+
if (lanes.length === 0) {
|
|
87
|
+
console.log(`\n ${style.dim('Aucun agent enregistre.')}`);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return pickFromList(
|
|
91
|
+
lanes.map(l => ({ id: l.id || l.name, title: l.role, ...l })),
|
|
92
|
+
{
|
|
93
|
+
title,
|
|
94
|
+
labelFn: (l) => `${l.name || l.id} \u2014 ${l.role || style.dim('(pas de role)')}`,
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function handleAdd(args, flags, config) {
|
|
100
|
+
const project = await ensureProject(config, flags);
|
|
101
|
+
if (!project) return;
|
|
102
|
+
|
|
103
|
+
console.log(`\n ${style.bold('Projet')} : ${style.nemesisAccent(project.id)} ${style.dim('(detecte depuis .nemesis/)')}\n`);
|
|
104
|
+
|
|
105
|
+
const noPrompt = args.includes('--no-prompt');
|
|
106
|
+
|
|
107
|
+
// Profile Kairos — fetch from HCM or manual entry
|
|
108
|
+
let profileId = getFlag(args, '--profile');
|
|
109
|
+
let kairosData = null;
|
|
110
|
+
let hcmLive = !!project.hcm_status?.connected;
|
|
111
|
+
if (!profileId && !noPrompt) {
|
|
112
|
+
const client = createHcmClient();
|
|
113
|
+
const picked = await pickProfile(client, { write: console.log });
|
|
114
|
+
profileId = picked.id;
|
|
115
|
+
kairosData = picked.kairosData;
|
|
116
|
+
if (kairosData) hcmLive = true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Agent name
|
|
120
|
+
let agentName = getFlag(args, '--name');
|
|
121
|
+
if (!agentName && !noPrompt) {
|
|
122
|
+
agentName = await askText('Nom de l\'agent');
|
|
123
|
+
}
|
|
124
|
+
if (!agentName) {
|
|
125
|
+
console.log(`\n ${style.red('Nom de l\'agent requis.')}\n`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
agentName = sanitizeName(agentName);
|
|
129
|
+
|
|
130
|
+
// Role
|
|
131
|
+
let role = getFlag(args, '--role');
|
|
132
|
+
if (!role && !noPrompt) {
|
|
133
|
+
role = await askText('Role sur le projet');
|
|
134
|
+
}
|
|
135
|
+
if (!role) {
|
|
136
|
+
console.log(`\n ${style.red('Role requis.')}\n`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Reports to — interactive menu
|
|
141
|
+
let reportsTo = getFlag(args, '--reports-to');
|
|
142
|
+
if (!reportsTo && !noPrompt) {
|
|
143
|
+
const registry = readRegistry(project.hcm_dir);
|
|
144
|
+
const pm = getPM(registry);
|
|
145
|
+
const pmName = pm?.name || 'PM';
|
|
146
|
+
const reportsItems = [{ label: `${pmName} (PM)`, value: pmName }];
|
|
147
|
+
const lanes = getAgentLanes(project);
|
|
148
|
+
for (const lane of lanes) {
|
|
149
|
+
reportsItems.push({
|
|
150
|
+
label: `${lane.name || lane.id} (${lane.role})`,
|
|
151
|
+
value: (lane.name || lane.id).split(' (')[0],
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
reportsTo = await interactiveMenu(reportsItems, { title: 'Reporte a' });
|
|
155
|
+
if (!reportsTo) reportsTo = pmName;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// OdMs
|
|
159
|
+
let odmIds = [];
|
|
160
|
+
if (!noPrompt) {
|
|
161
|
+
const odmDir = join(project.hcm_dir, 'odm');
|
|
162
|
+
if (existsSync(odmDir)) {
|
|
163
|
+
const odmFiles = readdirSync(odmDir)
|
|
164
|
+
.filter(f => f.endsWith('.json') && !f.startsWith('legacy_'));
|
|
165
|
+
if (odmFiles.length > 0) {
|
|
166
|
+
const odmChoices = odmFiles.map(f => {
|
|
167
|
+
try {
|
|
168
|
+
const data = JSON.parse(readFileSync(join(odmDir, f), 'utf-8'));
|
|
169
|
+
return {
|
|
170
|
+
label: `${data.odm_meta?.odm_id || f} \u2014 ${data.odm_payload?.cadrage?.title || ''}`,
|
|
171
|
+
value: data.odm_meta?.odm_id || f,
|
|
172
|
+
};
|
|
173
|
+
} catch {
|
|
174
|
+
return { label: f, value: f };
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
odmIds = await askMultiChoice('OdMs a assigner (optionnel)', odmChoices);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Execute
|
|
183
|
+
console.log('\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n');
|
|
184
|
+
|
|
185
|
+
const result = await teamAdd({
|
|
186
|
+
root: config.cwd,
|
|
187
|
+
agentName,
|
|
188
|
+
role,
|
|
189
|
+
reportsTo,
|
|
190
|
+
profileId: profileId || null,
|
|
191
|
+
kairosData,
|
|
192
|
+
odmIds,
|
|
193
|
+
environment: {
|
|
194
|
+
hcm: hcmLive,
|
|
195
|
+
files: [],
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
for (const step of result.results) {
|
|
200
|
+
console.log(` ${style.green('\u2713')} ${style.bold(step.label)}`);
|
|
201
|
+
console.log(` ${style.dim('\u2192')} ${step.detail}`);
|
|
202
|
+
console.log('');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log(' \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n');
|
|
206
|
+
|
|
207
|
+
const { runOnboarding } = await import('../../lib/core/agent-launcher.js');
|
|
208
|
+
const onboard = await runOnboarding(result.promptPath, result.agentName);
|
|
209
|
+
if (onboard.displayBox) {
|
|
210
|
+
console.log('');
|
|
211
|
+
console.log(onboard.displayBox);
|
|
212
|
+
console.log('');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Always persist onboarding metadata
|
|
216
|
+
const reg = readRegistry(result.project.hcm_dir);
|
|
217
|
+
if (reg) {
|
|
218
|
+
updateLaneField(reg, result.agentName, 'last_onboarding', new Date().toISOString());
|
|
219
|
+
if (onboard.sessionId) {
|
|
220
|
+
updateLaneField(reg, result.agentName, 'session_id', onboard.sessionId);
|
|
221
|
+
}
|
|
222
|
+
if (onboard.pid) {
|
|
223
|
+
updateLaneField(reg, result.agentName, 'pid', onboard.pid);
|
|
224
|
+
}
|
|
225
|
+
writeRegistry(result.project.hcm_dir, reg);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Post-onboarding menu — 2 choices
|
|
229
|
+
if (onboard.sessionId) {
|
|
230
|
+
console.log('');
|
|
231
|
+
console.log(' \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');
|
|
232
|
+
console.log(` ${style.bold(result.agentName)} est pret.`);
|
|
233
|
+
console.log('');
|
|
234
|
+
|
|
235
|
+
const postAction = await interactiveMenu([
|
|
236
|
+
{ label: 'Rejoindre la session interactive', value: 'join', description: '' },
|
|
237
|
+
{ label: 'Retourner au menu principal', value: 'back', description: '' },
|
|
238
|
+
], { title: 'Que souhaitez-vous faire ?' });
|
|
239
|
+
|
|
240
|
+
console.log(' \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');
|
|
241
|
+
|
|
242
|
+
if (postAction === 'join') {
|
|
243
|
+
const { spawn: spawnChild } = await import('node:child_process');
|
|
244
|
+
console.log(`\n ${style.bold('Session interactive')} \u2192 ${style.nemesisAccent(result.agentName)}`);
|
|
245
|
+
console.log(` ${style.dim('Session ID')}: ${onboard.sessionId}\n`);
|
|
246
|
+
|
|
247
|
+
const child = spawnChild('claude', ['--resume', onboard.sessionId, '--dangerously-skip-permissions'], {
|
|
248
|
+
stdio: 'inherit',
|
|
249
|
+
env: { ...process.env },
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
await new Promise((resolve) => child.on('close', resolve));
|
|
253
|
+
process.exit(0);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function handleList(args, flags, config) {
|
|
259
|
+
const project = await ensureProject(config, flags);
|
|
260
|
+
if (!project) return;
|
|
261
|
+
|
|
262
|
+
console.log(`\n ${style.bold('Projet')} : ${style.nemesisAccent(project.id)}\n`);
|
|
263
|
+
|
|
264
|
+
const result = teamList(project.hcm_dir);
|
|
265
|
+
if (!result || (result.tree && result.tree.children && result.tree.children.length === 0)) {
|
|
266
|
+
console.log(` ${style.dim('Equipe vide \u2014 seul le PM est enregistre.')}`);
|
|
267
|
+
const doAdd = await askConfirm('Onboard un agent maintenant ?', true);
|
|
268
|
+
if (doAdd) {
|
|
269
|
+
return handleAdd([], flags, config);
|
|
270
|
+
}
|
|
271
|
+
console.log('');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
console.log(renderTree(result.tree));
|
|
276
|
+
console.log('');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export { isPidAlive as checkAgentAlive } from '../../lib/kairos/pid-checker.js';
|
|
280
|
+
|
|
281
|
+
async function handlePanneau(args, flags, config) {
|
|
282
|
+
const project = await ensureProject(config, flags);
|
|
283
|
+
if (!project) return;
|
|
284
|
+
|
|
285
|
+
let agentId = args[0];
|
|
286
|
+
|
|
287
|
+
// If no agent specified → picker from registry
|
|
288
|
+
if (!agentId) {
|
|
289
|
+
agentId = await pickAgent(project, 'Quel agent inspecter ?');
|
|
290
|
+
if (!agentId) {
|
|
291
|
+
const doAdd = await askConfirm('Onboard un agent maintenant ?', false);
|
|
292
|
+
if (doAdd) return handleAdd([], flags, config);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const result = teamInspect(agentId, config.cwd);
|
|
298
|
+
|
|
299
|
+
if (flags.format === 'json') {
|
|
300
|
+
const { formatOutput } = await import('../../lib/ui/format.js');
|
|
301
|
+
console.log(formatOutput(result, 'json'));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const a = result.agent;
|
|
306
|
+
const alive = isPidAlive(a.pid);
|
|
307
|
+
const statusText = alive
|
|
308
|
+
? `${style.green('\u25CF')} actif (PID ${a.pid})`
|
|
309
|
+
: a.session_id
|
|
310
|
+
? `${style.yellow('\u25CF')} en veille (session disponible)`
|
|
311
|
+
: `${style.dim('\u25CB')} pas de session`;
|
|
312
|
+
|
|
313
|
+
// --- Zone 1 : Identite ---
|
|
314
|
+
const identityLines = [
|
|
315
|
+
`Role ${a.role}${a.leader ? ' ★ Leader' : ''}`,
|
|
316
|
+
...(a.profiles && a.profiles.length > 0 ? [`Profil ${a.profiles[0]}`] : []),
|
|
317
|
+
`Reporte a ${a.reports_to || '?'}`,
|
|
318
|
+
`Statut ${statusText}`,
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
if (result.memory) {
|
|
322
|
+
const memDate = result.memory.modified
|
|
323
|
+
? result.memory.modified.split('-').reverse().join('/')
|
|
324
|
+
: '';
|
|
325
|
+
identityLines.push(`Memoire ${result.memory.path}`);
|
|
326
|
+
identityLines.push(` ${result.memory.path.split('/').pop()} \u2014 ${result.memory.size} \u2014 maj ${memDate}`);
|
|
327
|
+
} else {
|
|
328
|
+
identityLines.push(`Memoire ${style.dim('(introuvable)')}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log('');
|
|
332
|
+
console.log(titledBox(a.name || a.id, identityLines, { border: style.nemesisAccent }));
|
|
333
|
+
|
|
334
|
+
// --- Zone 2 : Commandes ---
|
|
335
|
+
const shortName = (a.name || a.id).replace(/^Agent_/, '');
|
|
336
|
+
const cmdLines = [
|
|
337
|
+
'Session interactive',
|
|
338
|
+
` ${style.bold(`nemesis run agent:${shortName}`)}`,
|
|
339
|
+
'',
|
|
340
|
+
'Inject prompt',
|
|
341
|
+
` ${style.bold(`nemesis run agent:${shortName} --p "<prompt>"`)}`,
|
|
342
|
+
];
|
|
343
|
+
console.log(titledBox('Commandes', cmdLines, { border: style.nemesisAccent }));
|
|
344
|
+
|
|
345
|
+
// --- Zone 3 : Actions ---
|
|
346
|
+
let stayInPanel = true;
|
|
347
|
+
while (stayInPanel) {
|
|
348
|
+
const action = await interactiveMenu([
|
|
349
|
+
{ label: 'Rejoindre la session', value: 'join', description: '' },
|
|
350
|
+
{ label: 'Injecter un prompt', value: 'inject', description: '' },
|
|
351
|
+
{ label: 'Voir les OdMs assignes', value: 'odms', description: '' },
|
|
352
|
+
{ label: 'Voir les CRs recents', value: 'crs', description: '' },
|
|
353
|
+
{ label: a.leader ? 'Desactiver Leader' : 'Activer Leader', value: 'leader', description: '' },
|
|
354
|
+
{ label: 'Archiver l\'agent', value: 'archive', description: '' },
|
|
355
|
+
BACK_ITEM,
|
|
356
|
+
HOME_ITEM,
|
|
357
|
+
], { title: 'Actions' });
|
|
358
|
+
|
|
359
|
+
if (!action || action === '__back__') {
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
if (action === '__home__') return '__home__';
|
|
363
|
+
|
|
364
|
+
switch (action) {
|
|
365
|
+
case 'join':
|
|
366
|
+
await actionJoin(a, args, flags, config);
|
|
367
|
+
stayInPanel = false;
|
|
368
|
+
break;
|
|
369
|
+
case 'inject':
|
|
370
|
+
await actionInject(a);
|
|
371
|
+
break;
|
|
372
|
+
case 'odms':
|
|
373
|
+
actionShowOdms(result);
|
|
374
|
+
break;
|
|
375
|
+
case 'crs':
|
|
376
|
+
actionShowCrs(result);
|
|
377
|
+
break;
|
|
378
|
+
case 'leader': {
|
|
379
|
+
const reg = readRegistry(project.hcm_dir);
|
|
380
|
+
const newVal = !a.leader;
|
|
381
|
+
updateLaneField(reg, a.id, 'leader', newVal);
|
|
382
|
+
writeRegistry(project.hcm_dir, reg);
|
|
383
|
+
a.leader = newVal;
|
|
384
|
+
if (newVal) {
|
|
385
|
+
console.log(`\n ${style.green('★')} leader: ${style.bold('active')}\n`);
|
|
386
|
+
} else {
|
|
387
|
+
console.log(`\n ${style.dim('○')} leader: ${style.bold('desactive')}\n`);
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
case 'archive':
|
|
392
|
+
await actionArchive(a, config);
|
|
393
|
+
stayInPanel = false;
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export async function actionJoin(agent, args, flags, config) {
|
|
400
|
+
if (!agent.session_id) {
|
|
401
|
+
console.log(`\n ${style.dim('Pas de session. Lancez l\'onboarding d\'abord.')}`);
|
|
402
|
+
const doAdd = await askConfirm('Lancer l\'onboarding ?', true);
|
|
403
|
+
if (doAdd) return handleAdd(args || [], flags || {}, config);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
// session_id exists → resume it (PID mort = normal, l'utilisateur n'etait pas connecte)
|
|
407
|
+
const { spawn: spawnChild } = await import('node:child_process');
|
|
408
|
+
console.log(`\n ${style.bold('Session interactive')} \u2192 ${style.nemesisAccent(agent.name || agent.id)}`);
|
|
409
|
+
console.log(` ${style.dim('Session ID')}: ${agent.session_id}\n`);
|
|
410
|
+
const child = spawnChild('claude', ['--resume', agent.session_id, '--dangerously-skip-permissions'], {
|
|
411
|
+
stdio: 'inherit',
|
|
412
|
+
env: { ...process.env },
|
|
413
|
+
});
|
|
414
|
+
await new Promise((resolve) => child.on('close', resolve));
|
|
415
|
+
process.exit(0);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export async function actionInject(agent) {
|
|
419
|
+
if (!agent.session_id) {
|
|
420
|
+
console.log(`\n ${style.dim('Pas de session active. Lancez l\'onboarding d\'abord.')}\n`);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const prompt = await askText('Prompt');
|
|
424
|
+
if (!prompt) return;
|
|
425
|
+
const { spawn: spawnChild } = await import('node:child_process');
|
|
426
|
+
const { createStreamBox } = await import('../../lib/ui/streambox.js');
|
|
427
|
+
const box = createStreamBox(4);
|
|
428
|
+
const child = spawnChild('claude', [
|
|
429
|
+
'--resume', agent.session_id,
|
|
430
|
+
'-p', prompt,
|
|
431
|
+
'--output-format', 'stream-json',
|
|
432
|
+
'--verbose',
|
|
433
|
+
'--dangerously-skip-permissions',
|
|
434
|
+
], { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env } });
|
|
435
|
+
|
|
436
|
+
let finalResult = '';
|
|
437
|
+
child.stdout.on('data', (chunk) => {
|
|
438
|
+
for (const raw of chunk.toString().split('\n')) {
|
|
439
|
+
if (!raw.trim()) continue;
|
|
440
|
+
try {
|
|
441
|
+
const evt = JSON.parse(raw);
|
|
442
|
+
if (evt.type === 'assistant' && evt.message?.content) {
|
|
443
|
+
for (const block of evt.message.content) {
|
|
444
|
+
if (block.type === 'text' && block.text) {
|
|
445
|
+
for (const tl of block.text.split('\n').filter(Boolean)) {
|
|
446
|
+
box.push(style.dim(tl));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
if (evt.type === 'result') finalResult = evt.result || '';
|
|
452
|
+
} catch { /* ignore */ }
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
await new Promise((resolve) => child.on('close', resolve));
|
|
456
|
+
box.clear();
|
|
457
|
+
if (finalResult) {
|
|
458
|
+
console.log(titledBox('Reponse', finalResult.split('\n'), { border: style.nemesisAccent }));
|
|
459
|
+
console.log('');
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function actionShowOdms(inspectResult) {
|
|
464
|
+
if (inspectResult.odms.length === 0) {
|
|
465
|
+
console.log(`\n ${style.dim('Aucun OdM assigne.')}\n`);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
console.log('');
|
|
469
|
+
console.log(renderTable(
|
|
470
|
+
[
|
|
471
|
+
{ key: 'id', label: 'OdM' },
|
|
472
|
+
{ key: 'status', label: 'Status' },
|
|
473
|
+
{ key: 'title', label: 'Titre' },
|
|
474
|
+
],
|
|
475
|
+
inspectResult.odms,
|
|
476
|
+
));
|
|
477
|
+
console.log('');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function actionShowCrs(inspectResult) {
|
|
481
|
+
if (inspectResult.crs.length === 0) {
|
|
482
|
+
console.log(`\n ${style.dim('Aucun CR recent.')}\n`);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
console.log('');
|
|
486
|
+
for (const cr of inspectResult.crs) {
|
|
487
|
+
console.log(` - ${cr.id} (${cr.date})`);
|
|
488
|
+
}
|
|
489
|
+
console.log('');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function actionArchive(agent, config) {
|
|
493
|
+
const confirmed = await askConfirm(
|
|
494
|
+
`Confirmer la desinscription de ${agent.name || agent.id} ?`, false
|
|
495
|
+
);
|
|
496
|
+
if (!confirmed) {
|
|
497
|
+
console.log(' Annule.\n');
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
teamRemove(agent.id, config.cwd);
|
|
501
|
+
console.log(`\n ${style.green('\u2713')} Agent archive.\n`);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async function handleLeader(args, flags, config) {
|
|
505
|
+
const project = await ensureProject(config, flags);
|
|
506
|
+
if (!project) return;
|
|
507
|
+
|
|
508
|
+
let agentId = args[0];
|
|
509
|
+
|
|
510
|
+
if (!agentId) {
|
|
511
|
+
agentId = await pickAgent(project, 'Quel agent ?');
|
|
512
|
+
if (!agentId) return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const registry = readRegistry(project.hcm_dir);
|
|
516
|
+
if (!registry) {
|
|
517
|
+
console.log(`\n ${style.red('Registry introuvable.')}\n`);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const lanes = getLanes(registry);
|
|
522
|
+
const lane = lanes.find(l => l.id === agentId || l.name === agentId);
|
|
523
|
+
if (!lane) {
|
|
524
|
+
console.log(`\n ${style.red(`Agent ${agentId} non trouve.`)}\n`);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
let newValue;
|
|
529
|
+
if (flags.on) {
|
|
530
|
+
newValue = true;
|
|
531
|
+
} else if (flags.off) {
|
|
532
|
+
newValue = false;
|
|
533
|
+
} else {
|
|
534
|
+
newValue = !lane.leader;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
updateLaneField(registry, lane.id, 'leader', newValue);
|
|
538
|
+
writeRegistry(project.hcm_dir, registry);
|
|
539
|
+
|
|
540
|
+
if (newValue) {
|
|
541
|
+
console.log(`\n ${style.green('★')} leader: ${style.bold('active')} pour ${lane.name || lane.id}\n`);
|
|
542
|
+
} else {
|
|
543
|
+
console.log(`\n ${style.dim('○')} leader: ${style.bold('desactive')} pour ${lane.name || lane.id}\n`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function handleRemove(args, flags, config) {
|
|
548
|
+
const project = await ensureProject(config, flags);
|
|
549
|
+
if (!project) return;
|
|
550
|
+
|
|
551
|
+
let agentId = args[0];
|
|
552
|
+
|
|
553
|
+
// If no agent specified → picker from registry
|
|
554
|
+
if (!agentId) {
|
|
555
|
+
agentId = await pickAgent(project, 'Quel agent desactiver ?');
|
|
556
|
+
if (!agentId) return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const confirmed = await askConfirm(`Confirmer la desinscription de ${agentId} ?`, false);
|
|
560
|
+
if (!confirmed) {
|
|
561
|
+
console.log(' Annule.\n');
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const _result = teamRemove(agentId, config.cwd);
|
|
566
|
+
|
|
567
|
+
console.log(`\n ${style.green('\u2713')} Registre mis a jour (lane retiree)`);
|
|
568
|
+
console.log(` ${style.green('\u2713')} CLAUDE.md mis a jour (ligne retiree)`);
|
|
569
|
+
console.log(` ${style.green('\u2713')} memory.md conserve (archive)`);
|
|
570
|
+
console.log(` ${style.green('\u2713')} Contributor card conservee (archive)`);
|
|
571
|
+
console.log(`\n L'agent est desactive. Ses fichiers sont conserves pour tracabilite.\n`);
|
|
572
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const CONFIG_DIR = join(homedir(), '.nemesis');
|
|
7
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
8
|
+
|
|
9
|
+
const DEFAULT_CONFIG = {
|
|
10
|
+
version: '1.0.0',
|
|
11
|
+
defaults: {
|
|
12
|
+
hcm_url: 'https://hcm.arkalabs.app',
|
|
13
|
+
hcm_api_key_env: 'HCM_API_KEY',
|
|
14
|
+
claude_md_path: join(homedir(), '.claude', 'CLAUDE.md'),
|
|
15
|
+
onboarding_template: join(homedir(), '.claude', 'ONBOARDING_AGENT.md'),
|
|
16
|
+
hcm_protocol: join(homedir(), '.claude', 'HCM_PROTOCOL.md'),
|
|
17
|
+
},
|
|
18
|
+
current_project: null,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
async function readConfigFile() {
|
|
22
|
+
try {
|
|
23
|
+
const raw = await readFile(CONFIG_FILE, 'utf-8');
|
|
24
|
+
return JSON.parse(raw);
|
|
25
|
+
} catch {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function detectProject(cwd) {
|
|
31
|
+
// Priority 1: .nemesis/HCM/project/
|
|
32
|
+
let baseDir = '.nemesis';
|
|
33
|
+
let projectDir = join(cwd, '.nemesis', 'HCM', 'project');
|
|
34
|
+
|
|
35
|
+
// Fallback: .owner/HCM/project/
|
|
36
|
+
if (!existsSync(projectDir)) {
|
|
37
|
+
const legacyDir = join(cwd, '.owner', 'HCM', 'project');
|
|
38
|
+
if (existsSync(legacyDir)) {
|
|
39
|
+
baseDir = '.owner';
|
|
40
|
+
projectDir = legacyDir;
|
|
41
|
+
} else {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const files = readdirSync(projectDir);
|
|
48
|
+
const contextFile = files.find(f => f.startsWith('CONTEXT_PROJECT_') && f.endsWith('.json'));
|
|
49
|
+
if (!contextFile) return null;
|
|
50
|
+
|
|
51
|
+
const raw = readFileSync(join(projectDir, contextFile), 'utf-8');
|
|
52
|
+
const ctx = JSON.parse(raw);
|
|
53
|
+
return {
|
|
54
|
+
id: ctx.project_meta?.project_id || contextFile.replace('CONTEXT_PROJECT_', '').replace('.json', ''),
|
|
55
|
+
name: ctx.project_meta?.name || null,
|
|
56
|
+
context_file: join(projectDir, contextFile),
|
|
57
|
+
hcm_dir: join(cwd, baseDir, 'HCM'),
|
|
58
|
+
root: cwd,
|
|
59
|
+
legacy: baseDir === '.owner',
|
|
60
|
+
};
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function loadConfig(cwd = process.cwd()) {
|
|
67
|
+
const fileConfig = await readConfigFile();
|
|
68
|
+
const merged = {
|
|
69
|
+
...DEFAULT_CONFIG,
|
|
70
|
+
...fileConfig,
|
|
71
|
+
defaults: { ...DEFAULT_CONFIG.defaults, ...fileConfig.defaults },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const project = detectProject(cwd);
|
|
75
|
+
if (project) {
|
|
76
|
+
merged.current_project = project;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
merged.cwd = cwd;
|
|
80
|
+
merged.config_dir = CONFIG_DIR;
|
|
81
|
+
merged.config_file = CONFIG_FILE;
|
|
82
|
+
|
|
83
|
+
return merged;
|
|
84
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "arka.contributor_card.v1",
|
|
3
|
+
"contributor_meta": {
|
|
4
|
+
"contributor_id": "",
|
|
5
|
+
"kind": "",
|
|
6
|
+
"project_id": ""
|
|
7
|
+
},
|
|
8
|
+
"contributor_payload": {
|
|
9
|
+
"identity": {
|
|
10
|
+
"display_name": "",
|
|
11
|
+
"role": "",
|
|
12
|
+
"organization": "Arka Labs"
|
|
13
|
+
},
|
|
14
|
+
"expertise": {
|
|
15
|
+
"domains": [],
|
|
16
|
+
"description": ""
|
|
17
|
+
},
|
|
18
|
+
"runtime": null,
|
|
19
|
+
"capabilities": [],
|
|
20
|
+
"profiles": []
|
|
21
|
+
}
|
|
22
|
+
}
|