@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,187 @@
|
|
|
1
|
+
import { loadAuditTemplates, loadTemplate, buildAuditPrompt, saveReport, listReports } from '../../lib/core/audit.js';
|
|
2
|
+
import { interactiveMenu } from '../../lib/ui/menu.js';
|
|
3
|
+
import { renderTable } from '../../lib/ui/table.js';
|
|
4
|
+
import { askText } from '../../lib/ui/prompt.js';
|
|
5
|
+
import { style } from '../../lib/ui/colors.js';
|
|
6
|
+
import { titledBox } from '../../lib/ui/box.js';
|
|
7
|
+
import { ensureProject, BACK_ITEM, HOME_ITEM } from './_helpers.js';
|
|
8
|
+
|
|
9
|
+
const HELP = `
|
|
10
|
+
nemesis audit — Audit technique
|
|
11
|
+
|
|
12
|
+
Usage: nemesis audit <subcommand>
|
|
13
|
+
|
|
14
|
+
Subcommands:
|
|
15
|
+
list Types d'audit disponibles
|
|
16
|
+
run Lancer un audit
|
|
17
|
+
reports Rapports generes
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
-h, --help Aide
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
export async function handler({ args, flags, config }) {
|
|
24
|
+
if (flags.help || args.includes('--help') || args.includes('-h')) {
|
|
25
|
+
console.log(HELP);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let sub = args[0];
|
|
30
|
+
let subArgs = args.slice(1);
|
|
31
|
+
|
|
32
|
+
if (!sub) {
|
|
33
|
+
sub = await interactiveMenu([
|
|
34
|
+
{ label: 'run', value: 'run', description: 'Lancer un audit' },
|
|
35
|
+
{ label: 'list', value: 'list', description: 'Types d\'audit disponibles' },
|
|
36
|
+
{ label: 'reports', value: 'reports', description: 'Rapports generes' },
|
|
37
|
+
BACK_ITEM,
|
|
38
|
+
HOME_ITEM,
|
|
39
|
+
], { title: 'nemesis audit' });
|
|
40
|
+
if (!sub || sub === '__back__') return '__back__';
|
|
41
|
+
if (sub === '__home__') return '__home__';
|
|
42
|
+
subArgs = [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
switch (sub) {
|
|
46
|
+
case 'list':
|
|
47
|
+
return handleList(flags, config);
|
|
48
|
+
case 'run':
|
|
49
|
+
return handleRun(subArgs, flags, config);
|
|
50
|
+
case 'reports':
|
|
51
|
+
return handleReports(flags, config);
|
|
52
|
+
default:
|
|
53
|
+
console.error(`Sous-commande inconnue : ${sub}`);
|
|
54
|
+
console.log(HELP);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function handleList(flags, config) {
|
|
59
|
+
const project = await ensureProject(config, flags);
|
|
60
|
+
if (!project) return;
|
|
61
|
+
|
|
62
|
+
const templates = loadAuditTemplates(project);
|
|
63
|
+
|
|
64
|
+
if (templates.length === 0) {
|
|
65
|
+
console.log(`\n ${style.dim('Aucun template d\'audit dans .nemesis/HCM/audit/')}\n`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log('');
|
|
70
|
+
console.log(renderTable(
|
|
71
|
+
[
|
|
72
|
+
{ key: 'id', label: 'ID' },
|
|
73
|
+
{ key: 'name', label: 'Nom' },
|
|
74
|
+
{ key: 'description', label: 'Description' },
|
|
75
|
+
],
|
|
76
|
+
templates,
|
|
77
|
+
));
|
|
78
|
+
console.log('');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function handleRun(args, flags, config) {
|
|
82
|
+
const project = await ensureProject(config, flags);
|
|
83
|
+
if (!project) return;
|
|
84
|
+
|
|
85
|
+
const templates = loadAuditTemplates(project);
|
|
86
|
+
if (templates.length === 0) {
|
|
87
|
+
console.log(`\n ${style.dim('Aucun template d\'audit disponible.')}\n`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Choose audit type
|
|
92
|
+
let templateId = args[0];
|
|
93
|
+
if (!templateId) {
|
|
94
|
+
const items = templates.map(t => ({
|
|
95
|
+
label: t.id,
|
|
96
|
+
value: t.id,
|
|
97
|
+
description: t.name,
|
|
98
|
+
}));
|
|
99
|
+
templateId = await interactiveMenu(items, { title: 'Type d\'audit' });
|
|
100
|
+
if (!templateId) return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const template = loadTemplate(project, templateId);
|
|
104
|
+
if (!template) {
|
|
105
|
+
console.error(` Template introuvable : ${templateId}\n`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Define target
|
|
110
|
+
console.log(`\n ${style.bold('Definition de la cible')}\n`);
|
|
111
|
+
|
|
112
|
+
const nom = await askText('Nom du projet / module');
|
|
113
|
+
if (!nom) return;
|
|
114
|
+
|
|
115
|
+
const cibleTypes = template.cible?.types_supportes || ['repo'];
|
|
116
|
+
let type;
|
|
117
|
+
if (cibleTypes.length === 1) {
|
|
118
|
+
type = cibleTypes[0];
|
|
119
|
+
} else {
|
|
120
|
+
type = await interactiveMenu(
|
|
121
|
+
cibleTypes.map(t => ({ label: t, value: t })),
|
|
122
|
+
{ title: 'Type de cible' },
|
|
123
|
+
);
|
|
124
|
+
if (!type) return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const path = await askText('Chemin vers le code source', project.root);
|
|
128
|
+
const contexte = await askText('Contexte (prototype, MVP, production, legacy...)', '');
|
|
129
|
+
const tailleEquipe = await askText('Taille equipe (petite, moyenne, grande)', '');
|
|
130
|
+
const stack = await askText('Stack technique (auto-detecte si vide)', '');
|
|
131
|
+
|
|
132
|
+
const cible = {
|
|
133
|
+
type,
|
|
134
|
+
path: path || project.root,
|
|
135
|
+
nom,
|
|
136
|
+
contexte: contexte || undefined,
|
|
137
|
+
taille_equipe: tailleEquipe || undefined,
|
|
138
|
+
stack: stack || undefined,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Build prompt
|
|
142
|
+
const prompt = buildAuditPrompt(template, cible);
|
|
143
|
+
|
|
144
|
+
// Save as report template
|
|
145
|
+
const filepath = saveReport(project, templateId, prompt);
|
|
146
|
+
|
|
147
|
+
// Display
|
|
148
|
+
console.log('');
|
|
149
|
+
const lines = [
|
|
150
|
+
`${style.bold('Type')} ${template.audit_meta.name}`,
|
|
151
|
+
`${style.bold('Cible')} ${nom} (${type})`,
|
|
152
|
+
`${style.bold('Fichier')} ${filepath}`,
|
|
153
|
+
'',
|
|
154
|
+
`Le prompt d'audit a ete genere et sauvegarde.`,
|
|
155
|
+
`Copiez-le et envoyez-le a un agent ou LLM pour execution.`,
|
|
156
|
+
];
|
|
157
|
+
console.log(titledBox('Audit genere', lines, { border: style.nemesisAccent }));
|
|
158
|
+
console.log('');
|
|
159
|
+
|
|
160
|
+
// Show first lines of prompt
|
|
161
|
+
const preview = prompt.split('\n').slice(0, 8).join('\n');
|
|
162
|
+
console.log(style.dim(preview));
|
|
163
|
+
console.log(style.dim(' ...'));
|
|
164
|
+
console.log('');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function handleReports(flags, config) {
|
|
168
|
+
const project = await ensureProject(config, flags);
|
|
169
|
+
if (!project) return;
|
|
170
|
+
|
|
171
|
+
const reports = listReports(project);
|
|
172
|
+
|
|
173
|
+
if (reports.length === 0) {
|
|
174
|
+
console.log(`\n ${style.dim('Aucun rapport genere.')}\n`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log('');
|
|
179
|
+
console.log(renderTable(
|
|
180
|
+
[
|
|
181
|
+
{ key: 'filename', label: 'Rapport' },
|
|
182
|
+
{ key: 'path', label: 'Chemin' },
|
|
183
|
+
],
|
|
184
|
+
reports,
|
|
185
|
+
));
|
|
186
|
+
console.log('');
|
|
187
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { diagnoseHcm } from '../../lib/sync/health.js';
|
|
5
|
+
import { askText, askConfirm } from '../../lib/ui/prompt.js';
|
|
6
|
+
import { interactiveMenu } from '../../lib/ui/menu.js';
|
|
7
|
+
import { createSpinner, createMultiStepSpinner } from '../../lib/ui/spinner.js';
|
|
8
|
+
import { style } from '../../lib/ui/colors.js';
|
|
9
|
+
import { BACK_ITEM, HOME_ITEM } from './_helpers.js';
|
|
10
|
+
|
|
11
|
+
const CONFIG_DIR = join(homedir(), '.nemesis');
|
|
12
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
13
|
+
|
|
14
|
+
const HELP = `
|
|
15
|
+
nemesis auth — Connexion HCM
|
|
16
|
+
|
|
17
|
+
Usage: nemesis auth <subcommand>
|
|
18
|
+
|
|
19
|
+
Subcommands:
|
|
20
|
+
login Configurer la connexion HCM (URL + API key)
|
|
21
|
+
status Afficher la connexion courante
|
|
22
|
+
logout Supprimer les credentials HCM
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
-h, --help Aide
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
export async function handler({ args, flags }) {
|
|
29
|
+
if (flags.help || args.includes('--help') || args.includes('-h')) {
|
|
30
|
+
console.log(HELP);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let sub = args[0];
|
|
35
|
+
let _subArgs = args.slice(1);
|
|
36
|
+
|
|
37
|
+
if (!sub) {
|
|
38
|
+
sub = await interactiveMenu([
|
|
39
|
+
{ label: 'status', value: 'status', description: 'Afficher la connexion courante' },
|
|
40
|
+
{ label: 'login', value: 'login', description: 'Configurer la connexion HCM' },
|
|
41
|
+
{ label: 'logout', value: 'logout', description: 'Supprimer les credentials' },
|
|
42
|
+
BACK_ITEM,
|
|
43
|
+
HOME_ITEM,
|
|
44
|
+
], { title: 'nemesis auth' });
|
|
45
|
+
if (!sub || sub === '__back__') return '__back__';
|
|
46
|
+
if (sub === '__home__') return '__home__';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
switch (sub) {
|
|
50
|
+
case 'login':
|
|
51
|
+
return handleLogin(flags);
|
|
52
|
+
case 'status':
|
|
53
|
+
return handleStatus(flags);
|
|
54
|
+
case 'logout':
|
|
55
|
+
return handleLogout(flags);
|
|
56
|
+
default:
|
|
57
|
+
console.error(`Sous-commande inconnue : ${sub}`);
|
|
58
|
+
console.log(HELP);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Read saved HCM config from ~/.nemesis/config.json
|
|
64
|
+
*/
|
|
65
|
+
export function readAuthConfig() {
|
|
66
|
+
if (!existsSync(CONFIG_FILE)) return null;
|
|
67
|
+
try {
|
|
68
|
+
const data = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
|
|
69
|
+
return {
|
|
70
|
+
hcm_url: data.hcm_url || null,
|
|
71
|
+
hcm_api_key: data.hcm_api_key || null,
|
|
72
|
+
};
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Save HCM config to ~/.nemesis/config.json
|
|
80
|
+
*/
|
|
81
|
+
function saveAuthConfig(hcmUrl, hcmApiKey) {
|
|
82
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
83
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let existing = {};
|
|
87
|
+
if (existsSync(CONFIG_FILE)) {
|
|
88
|
+
try {
|
|
89
|
+
existing = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
|
|
90
|
+
} catch {
|
|
91
|
+
existing = {};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
existing.hcm_url = hcmUrl;
|
|
96
|
+
existing.hcm_api_key = hcmApiKey;
|
|
97
|
+
|
|
98
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(existing, null, 2) + '\n', { encoding: 'utf-8', mode: 0o600 });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Remove HCM credentials from ~/.nemesis/config.json
|
|
103
|
+
*/
|
|
104
|
+
function clearAuthConfig() {
|
|
105
|
+
if (!existsSync(CONFIG_FILE)) return false;
|
|
106
|
+
try {
|
|
107
|
+
const data = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
|
|
108
|
+
delete data.hcm_url;
|
|
109
|
+
delete data.hcm_api_key;
|
|
110
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2) + '\n', { encoding: 'utf-8', mode: 0o600 });
|
|
111
|
+
return true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Find shell RC files that export HCM_API_KEY or HCM_URL.
|
|
119
|
+
* Returns array of { file, line, lineNum, variable }.
|
|
120
|
+
*/
|
|
121
|
+
function findShellExports(...vars) {
|
|
122
|
+
const home = homedir();
|
|
123
|
+
const rcFiles = ['.zshrc', '.zprofile', '.zshenv', '.bashrc', '.bash_profile']
|
|
124
|
+
.map(f => join(home, f));
|
|
125
|
+
|
|
126
|
+
const found = [];
|
|
127
|
+
for (const file of rcFiles) {
|
|
128
|
+
if (!existsSync(file)) continue;
|
|
129
|
+
const lines = readFileSync(file, 'utf-8').split('\n');
|
|
130
|
+
for (let i = 0; i < lines.length; i++) {
|
|
131
|
+
for (const v of vars) {
|
|
132
|
+
if (lines[i].match(new RegExp(`^\\s*export\\s+${v}=`))) {
|
|
133
|
+
found.push({ file, line: lines[i], lineNum: i, variable: v });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return found;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Update HCM_API_KEY value in shell RC files.
|
|
143
|
+
*/
|
|
144
|
+
function updateShellExport(file, lineNum, variable, newValue) {
|
|
145
|
+
const lines = readFileSync(file, 'utf-8').split('\n');
|
|
146
|
+
lines[lineNum] = `export ${variable}="${newValue}"`;
|
|
147
|
+
writeFileSync(file, lines.join('\n'), 'utf-8');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Remove HCM_API_KEY / HCM_URL lines from shell RC files.
|
|
152
|
+
*/
|
|
153
|
+
function removeShellExport(file, lineNum) {
|
|
154
|
+
const lines = readFileSync(file, 'utf-8').split('\n');
|
|
155
|
+
lines.splice(lineNum, 1);
|
|
156
|
+
writeFileSync(file, lines.join('\n'), 'utf-8');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* After login: detect stale HCM_API_KEY in shell RC and offer to update.
|
|
161
|
+
*/
|
|
162
|
+
function syncShellKeys(newKey, _newUrl) {
|
|
163
|
+
const exports = findShellExports('HCM_API_KEY', 'HCM_URL');
|
|
164
|
+
if (exports.length === 0) return;
|
|
165
|
+
|
|
166
|
+
const staleKeys = exports.filter(e =>
|
|
167
|
+
e.variable === 'HCM_API_KEY' && !e.line.includes(newKey)
|
|
168
|
+
);
|
|
169
|
+
if (staleKeys.length === 0) return;
|
|
170
|
+
|
|
171
|
+
for (const e of staleKeys) {
|
|
172
|
+
const shortFile = e.file.replace(homedir(), '~');
|
|
173
|
+
updateShellExport(e.file, e.lineNum, 'HCM_API_KEY', newKey);
|
|
174
|
+
console.log(` ${style.green('\u2713')} ${shortFile} mis a jour (ancienne cle remplacee)`);
|
|
175
|
+
}
|
|
176
|
+
if (staleKeys.length > 0) {
|
|
177
|
+
console.log(` ${style.dim('\u2192 Rechargez votre shell :')} source ~/.zshrc`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Sync API key into .mcp.json at cwd if it exists and key is stale.
|
|
183
|
+
*/
|
|
184
|
+
function syncMcpJson(apiKey, hcmUrl) {
|
|
185
|
+
const mcpPath = join(process.cwd(), '.mcp.json');
|
|
186
|
+
if (!existsSync(mcpPath)) return;
|
|
187
|
+
try {
|
|
188
|
+
const existing = JSON.parse(readFileSync(mcpPath, 'utf-8'));
|
|
189
|
+
const currentKey = existing?.mcpServers?.HCM_Sipher?.env?.HCM_API_KEY || '';
|
|
190
|
+
if (currentKey === apiKey) return;
|
|
191
|
+
existing.mcpServers.HCM_Sipher.env.HCM_API_KEY = apiKey;
|
|
192
|
+
if (hcmUrl) existing.mcpServers.HCM_Sipher.env.HCM_API_URL = hcmUrl;
|
|
193
|
+
writeFileSync(mcpPath, JSON.stringify(existing, null, 2) + '\n', { encoding: 'utf-8', mode: 0o600 });
|
|
194
|
+
console.log(` ${style.green('\u2713')} .mcp.json mis a jour (cle API synchronisee)`);
|
|
195
|
+
} catch {
|
|
196
|
+
// .mcp.json malformed or no HCM_Sipher section — skip silently
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function handleLogin(_flags) {
|
|
201
|
+
console.log(`\n ${style.bold('Connexion HCM')}\n`);
|
|
202
|
+
|
|
203
|
+
const hcmUrl = await askText('URL du HCM', 'https://hcm.arkalabs.app');
|
|
204
|
+
if (!hcmUrl) return;
|
|
205
|
+
|
|
206
|
+
const hcmApiKey = await askText('API Key HCM');
|
|
207
|
+
if (!hcmApiKey) {
|
|
208
|
+
console.log(`\n ${style.red('API key requise.')}\n`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Test connection
|
|
213
|
+
const multiSpinner = createMultiStepSpinner(['Connexion HCM', 'Validation credentials']);
|
|
214
|
+
multiSpinner.start();
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const result = await diagnoseHcm({ baseUrl: hcmUrl, apiKey: hcmApiKey });
|
|
218
|
+
|
|
219
|
+
multiSpinner.nextStep();
|
|
220
|
+
|
|
221
|
+
if (result.ok) {
|
|
222
|
+
multiSpinner.complete('Connexion OK');
|
|
223
|
+
saveAuthConfig(hcmUrl, hcmApiKey);
|
|
224
|
+
|
|
225
|
+
console.log(`\n ${style.green('\u2713')} Credentials sauvegardes dans ~/.nemesis/config.json`);
|
|
226
|
+
console.log(` ${style.green('\u2713')} HCM : ${hcmUrl}`);
|
|
227
|
+
for (const check of result.checks) {
|
|
228
|
+
const sym = check.ok ? style.green('\u2713') : style.red('\u2717');
|
|
229
|
+
console.log(` ${sym} ${check.label} : ${check.detail}`);
|
|
230
|
+
}
|
|
231
|
+
syncShellKeys(hcmApiKey, hcmUrl);
|
|
232
|
+
syncMcpJson(hcmApiKey, hcmUrl);
|
|
233
|
+
console.log('');
|
|
234
|
+
} else {
|
|
235
|
+
multiSpinner.fail('Connexion echouee');
|
|
236
|
+
console.log('');
|
|
237
|
+
for (const check of result.checks) {
|
|
238
|
+
const sym = check.ok ? style.green('\u2713') : style.red('\u2717');
|
|
239
|
+
console.log(` ${sym} ${check.label} : ${check.detail}`);
|
|
240
|
+
}
|
|
241
|
+
console.log(`\n ${style.yellow('\u26A0')} Credentials NON sauvegardes. Verifiez l'URL et la cle API.\n`);
|
|
242
|
+
}
|
|
243
|
+
} catch (err) {
|
|
244
|
+
multiSpinner.fail('Erreur');
|
|
245
|
+
console.log(`\n ${style.red('Erreur')} : ${err.message}\n`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function handleStatus(_flags) {
|
|
250
|
+
const auth = readAuthConfig();
|
|
251
|
+
|
|
252
|
+
if (!auth || !auth.hcm_url) {
|
|
253
|
+
console.log(`\n ${style.dim('Aucune connexion HCM configuree.')}`);
|
|
254
|
+
console.log(` ${style.dim('\u2192 Lancez')} nemesis auth login ${style.dim('pour configurer.')}\n`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const envKey = process.env.HCM_API_KEY;
|
|
259
|
+
const envUrl = process.env.HCM_URL;
|
|
260
|
+
const activeUrl = envUrl || auth.hcm_url;
|
|
261
|
+
const activeKey = envKey || auth.hcm_api_key;
|
|
262
|
+
const keySource = envKey ? 'env $HCM_API_KEY' : 'config.json';
|
|
263
|
+
|
|
264
|
+
console.log(`\n ${style.bold('HCM')} : ${activeUrl}${envUrl ? style.dim(' (env $HCM_URL)') : ''}`);
|
|
265
|
+
console.log(` ${style.bold('API Key')} : ${activeKey ? style.dim(activeKey.slice(0, 8) + '...' + activeKey.slice(-4)) : style.red('(manquante)')} ${style.dim(`(${keySource})`)}`);
|
|
266
|
+
if (envKey && auth.hcm_api_key && envKey !== auth.hcm_api_key) {
|
|
267
|
+
console.log(` ${style.yellow('\u26A0')} $HCM_API_KEY ecrase la cle de config.json (cles differentes)`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Live check
|
|
271
|
+
const spinner = createSpinner('Verification...');
|
|
272
|
+
spinner.start();
|
|
273
|
+
try {
|
|
274
|
+
const result = await diagnoseHcm({ baseUrl: auth.hcm_url, apiKey: auth.hcm_api_key });
|
|
275
|
+
spinner.stop(result.ok ? 'HCM connecte' : 'HCM injoignable');
|
|
276
|
+
for (const check of result.checks) {
|
|
277
|
+
const sym = check.ok ? style.green('\u2713') : style.red('\u2717');
|
|
278
|
+
console.log(` ${sym} ${check.label} : ${check.detail}`);
|
|
279
|
+
}
|
|
280
|
+
} catch (err) {
|
|
281
|
+
spinner.fail('Erreur');
|
|
282
|
+
console.log(` ${style.red('\u2717')} ${err.message}`);
|
|
283
|
+
}
|
|
284
|
+
console.log('');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function handleLogout(_flags) {
|
|
288
|
+
const auth = readAuthConfig();
|
|
289
|
+
if (!auth || !auth.hcm_url) {
|
|
290
|
+
console.log(`\n ${style.dim('Aucune connexion HCM a supprimer.')}\n`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const cleared = clearAuthConfig();
|
|
295
|
+
if (cleared) {
|
|
296
|
+
console.log(`\n ${style.green('\u2713')} Credentials HCM supprimes de ~/.nemesis/config.json`);
|
|
297
|
+
} else {
|
|
298
|
+
console.log(`\n ${style.red('Erreur lors de la suppression.')}\n`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Clean shell RC files
|
|
303
|
+
const exports = findShellExports('HCM_API_KEY', 'HCM_URL');
|
|
304
|
+
for (const e of exports) {
|
|
305
|
+
const shortFile = e.file.replace(homedir(), '~');
|
|
306
|
+
const doRemove = await askConfirm(` Supprimer ${e.variable} de ${shortFile} ?`, true);
|
|
307
|
+
if (doRemove) {
|
|
308
|
+
removeShellExport(e.file, e.lineNum);
|
|
309
|
+
console.log(` ${style.green('\u2713')} ${e.variable} supprime de ${shortFile}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (exports.length > 0) {
|
|
313
|
+
console.log(` ${style.dim('\u2192 Rechargez votre shell pour appliquer')}`);
|
|
314
|
+
}
|
|
315
|
+
console.log('');
|
|
316
|
+
}
|