@orion-monitoring/cli 1.0.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/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # orion-cli
2
+
3
+ CLI d'initialisation pour le SDK **Orion** — se connecte à ton instance Orion, récupère ou crée un projet, et génère `orion.config.ts`.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx orion-cli
9
+ # ou
10
+ npx create-orion
11
+ ```
12
+
13
+ ## Flow
14
+
15
+ ```
16
+ 2. Login avec ton compte (email + mot de passe)
17
+ 3. Sélectionner un projet (ou en créer un nouveau)
18
+ 4. Nom de la source (ex: api-backend, worker-queue)
19
+ 5. Environnement (production / development / staging / test)
20
+ 6. Génère nom.config.ts (token dans .env ou directement)
21
+ ```
22
+
23
+ ## Fichier généré
24
+
25
+ ```ts
26
+ // nom.config.ts
27
+ import { defineConfig } from 'orion-cli'
28
+
29
+ export default defineConfig({
30
+ token: process.env.ORION_TOKEN!, // ou le token directement
31
+ source: 'api-backend',
32
+ environment: 'production',
33
+ serverUrl: 'wss://api.monorion.com',
34
+ })
35
+ ```
36
+
37
+ ## Variable d'environnement
38
+
39
+ | Variable | Description |
40
+ |------------------|------------------------------------------|
41
+ | `ORION_API_URL` | Pré-remplit l'URL du serveur au prompt |
42
+ | `ORION_TOKEN` | Token injecté si stocké dans `.env` |
43
+
44
+ ## Dev
45
+
46
+ ```bash
47
+ npm install
48
+ npm run dev # tsx watch
49
+ npm run build # compile vers dist/
50
+ npm link # teste `orion-cli` en global
51
+ ```
package/dist/api.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { LoginResponse, Project, CreateProjectResponse } from './types.js';
2
+ export declare class ApiError extends Error {
3
+ status: number;
4
+ constructor(status: number, message: string);
5
+ }
6
+ export declare function login(baseUrl: string, email: string, password: string): Promise<LoginResponse>;
7
+ export declare function listProjects(baseUrl: string, token: string): Promise<Project[]>;
8
+ export declare function createProject(baseUrl: string, token: string, name: string, label: string): Promise<CreateProjectResponse>;
9
+ export declare function getProjectToken(baseUrl: string, token: string, projectName: string): Promise<string>;
10
+ export declare function registerSource(baseUrl: string, token: string, projectName: string, name: string, description: string, env: string): Promise<{
11
+ name: string;
12
+ }>;
package/dist/api.js ADDED
@@ -0,0 +1,57 @@
1
+ export class ApiError extends Error {
2
+ status;
3
+ constructor(status, message) {
4
+ super(message);
5
+ this.status = status;
6
+ this.name = 'ApiError';
7
+ }
8
+ }
9
+ async function request(baseUrl, path, options = {}) {
10
+ const { token, ...fetchOptions } = options;
11
+ const headers = {
12
+ 'Content-Type': 'application/json',
13
+ ...(fetchOptions.headers ?? {}),
14
+ };
15
+ if (token)
16
+ headers['Authorization'] = `Bearer ${token}`;
17
+ const res = await fetch(`${baseUrl}${path}`, {
18
+ ...fetchOptions,
19
+ headers,
20
+ signal: AbortSignal.timeout(10_000),
21
+ });
22
+ const data = await res.json().catch(() => ({}));
23
+ if (!res.ok) {
24
+ const message = data.message ?? `HTTP ${res.status}`;
25
+ throw new ApiError(res.status, message);
26
+ }
27
+ return data;
28
+ }
29
+ // ─── Auth ─────────────────────────────────────────────────────────────────────
30
+ export async function login(baseUrl, email, password) {
31
+ return request(baseUrl, '/api/auth/login', {
32
+ method: 'POST',
33
+ body: JSON.stringify({ email, password }),
34
+ });
35
+ }
36
+ // ─── Projects ─────────────────────────────────────────────────────────────────
37
+ export async function listProjects(baseUrl, token) {
38
+ return request(baseUrl, '/api/projects', { token });
39
+ }
40
+ export async function createProject(baseUrl, token, name, label) {
41
+ return request(baseUrl, '/api/projects', {
42
+ method: 'POST',
43
+ token,
44
+ body: JSON.stringify({ name, label }),
45
+ });
46
+ }
47
+ export async function getProjectToken(baseUrl, token, projectName) {
48
+ const res = await request(baseUrl, `/api/projects/${encodeURIComponent(projectName)}/token`, { token });
49
+ return res.token;
50
+ }
51
+ export async function registerSource(baseUrl, token, projectName, name, description, env) {
52
+ return request(baseUrl, `/api/projects/${encodeURIComponent(projectName)}/sources`, {
53
+ method: 'POST',
54
+ token,
55
+ body: JSON.stringify({ name, description, environment: env }),
56
+ });
57
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * auth-browser.ts
3
+ *
4
+ * Ce module gère le flow d'authentification CLI via navigateur.
5
+ *
6
+ * POURQUOI un serveur HTTP local ?
7
+ * Le navigateur ne peut pas "écrire" dans le terminal directement.
8
+ * La seule façon pour lui de transmettre une donnée au CLI est
9
+ * de faire une requête HTTP. On ouvre donc un mini serveur sur
10
+ * localhost:7777 qui écoute pendant max 5 minutes.
11
+ *
12
+ * POURQUOI le module `http` natif ?
13
+ * Pour ne pas ajouter de dépendance (express, etc.) à un CLI léger.
14
+ * Le module natif suffit pour une seule route GET /callback.
15
+ *
16
+ * DÉPENDANCE NÉCESSAIRE : `open`
17
+ * npm install open
18
+ * (ouvre le navigateur par défaut sur macOS, Linux, Windows)
19
+ */
20
+ /**
21
+ * Lance le flow d'auth via navigateur.
22
+ *
23
+ * @param apiBase - URL de base de l'API (ex: "http://localhost:3001/")
24
+ * @returns Le JWT que le CLI pourra utiliser pour les appels API
25
+ */
26
+ export declare function loginWithBrowser(apiBase: string): Promise<string>;
@@ -0,0 +1,233 @@
1
+ /**
2
+ * auth-browser.ts
3
+ *
4
+ * Ce module gère le flow d'authentification CLI via navigateur.
5
+ *
6
+ * POURQUOI un serveur HTTP local ?
7
+ * Le navigateur ne peut pas "écrire" dans le terminal directement.
8
+ * La seule façon pour lui de transmettre une donnée au CLI est
9
+ * de faire une requête HTTP. On ouvre donc un mini serveur sur
10
+ * localhost:7777 qui écoute pendant max 5 minutes.
11
+ *
12
+ * POURQUOI le module `http` natif ?
13
+ * Pour ne pas ajouter de dépendance (express, etc.) à un CLI léger.
14
+ * Le module natif suffit pour une seule route GET /callback.
15
+ *
16
+ * DÉPENDANCE NÉCESSAIRE : `open`
17
+ * npm install open
18
+ * (ouvre le navigateur par défaut sur macOS, Linux, Windows)
19
+ */
20
+ import http from 'http';
21
+ import { URL } from 'url';
22
+ import { spinner, log } from '@clack/prompts';
23
+ import pc from 'picocolors';
24
+ // Port fixe sur lequel le CLI écoute le callback
25
+ // Ce port doit être whitelisté dans le CORS du backend si besoin
26
+ const CALLBACK_PORT = 7777;
27
+ // Timeout de 5 minutes max pour que l'user se connecte dans le navigateur
28
+ const AUTH_TIMEOUT_MS = 5 * 60 * 1000;
29
+ /**
30
+ * Lance le flow d'auth via navigateur.
31
+ *
32
+ * @param apiBase - URL de base de l'API (ex: "http://localhost:3001/")
33
+ * @returns Le JWT que le CLI pourra utiliser pour les appels API
34
+ */
35
+ export async function loginWithBrowser(apiBase) {
36
+ // ── ÉTAPE 1 : Appelle /api/auth/cli/init ─────────────────────────────────
37
+ // On envoie notre port de callback au backend.
38
+ // Le backend génère un state UUID et nous retourne l'URL de login.
39
+ const initRes = await fetch(`${apiBase}/api/auth/cli/init`, {
40
+ method: 'POST',
41
+ headers: { 'Content-Type': 'application/json' },
42
+ body: JSON.stringify({ callbackPort: CALLBACK_PORT }),
43
+ });
44
+ if (!initRes.ok) {
45
+ throw new Error(`Erreur lors de l'initialisation (${initRes.status})`);
46
+ }
47
+ const { loginUrl } = await initRes.json();
48
+ // ── ÉTAPE 2 : Ouvre le navigateur ─────────────────────────────────────────
49
+ // `open` est un package qui appelle `xdg-open` (Linux), `open` (macOS),
50
+ // ou `start` (Windows) selon la plateforme.
51
+ log.info(pc.cyan(`Ouverture du navigateur... si le navigateur ne s'ouvre pas, copiez cette URL :\n ${loginUrl}`));
52
+ const { default: open } = await import('open');
53
+ await open(loginUrl);
54
+ // ── ÉTAPE 3 : Démarre le serveur local et attend le token ─────────────────
55
+ // Le website va rediriger vers http://localhost:7777/callback?token=xxx
56
+ // Notre serveur intercepte ça, extrait le token, et se ferme.
57
+ const spin = spinner();
58
+ spin.start('En attente de l\'authentification dans le navigateur');
59
+ const token = await waitForCallback();
60
+ spin.stop(pc.green('✓ Authentification réussie !'));
61
+ return token;
62
+ }
63
+ /**
64
+ * Démarre un serveur HTTP local sur CALLBACK_PORT,
65
+ * attend un GET /callback?token=xxx,
66
+ * retourne le token et ferme le serveur.
67
+ *
68
+ * Timeout automatique après AUTH_TIMEOUT_MS.
69
+ */
70
+ function waitForCallback() {
71
+ return new Promise((resolve, reject) => {
72
+ const server = http.createServer((req, res) => {
73
+ // On parse l'URL pour extraire le ?token= param
74
+ const reqUrl = new URL(req.url ?? '/', `http://localhost:${CALLBACK_PORT}`);
75
+ if (reqUrl.pathname !== '/callback') {
76
+ // Route inconnue → on ignore
77
+ res.writeHead(404);
78
+ res.end();
79
+ return;
80
+ }
81
+ const token = reqUrl.searchParams.get('token');
82
+ if (!token) {
83
+ // Pas de token dans l'URL → erreur
84
+ res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
85
+ res.end('<h2>Token manquant. Relancez orion-cli.</h2>');
86
+ server.close();
87
+ reject(new Error('Token manquant dans le callback'));
88
+ return;
89
+ }
90
+ // ✅ On a le token ! On répond au navigateur avec une belle page
91
+ // et on résout la Promise.
92
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
93
+ res.end(`<!DOCTYPE html>
94
+ <html lang="fr">
95
+ <head>
96
+ <meta charset="UTF-8">
97
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
98
+ <title>Orion CLI — Authentifié</title>
99
+ <style>
100
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
101
+
102
+ body {
103
+ font-family: system-ui, -apple-system, sans-serif;
104
+ background: #161a24;
105
+ color: #e8eaef;
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ min-height: 100vh;
110
+ }
111
+
112
+ .card {
113
+ width: 100%;
114
+ max-width: 22rem;
115
+ background: #1c2130;
116
+ border: 1px solid #252b3b;
117
+ border-radius: 1rem;
118
+ padding: 2.5rem 2rem;
119
+ text-align: center;
120
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
121
+ }
122
+
123
+ .logo {
124
+ font-size: 1.5rem;
125
+ font-weight: 700;
126
+ color: #ffffff;
127
+ letter-spacing: -0.02em;
128
+ margin-bottom: 0.25rem;
129
+ }
130
+
131
+ .subtitle {
132
+ font-size: 0.875rem;
133
+ color: #8b92a4;
134
+ margin-bottom: 2rem;
135
+ }
136
+
137
+ .icon-wrap {
138
+ width: 3.5rem;
139
+ height: 3.5rem;
140
+ background: rgba(2, 241, 148, 0.1);
141
+ border: 1px solid rgba(2, 241, 148, 0.25);
142
+ border-radius: 50%;
143
+ display: flex;
144
+ align-items: center;
145
+ justify-content: center;
146
+ margin: 0 auto 1.25rem;
147
+ }
148
+
149
+ .icon-wrap svg {
150
+ color: #02f194;
151
+ }
152
+
153
+ h2 {
154
+ font-size: 1.125rem;
155
+ font-weight: 600;
156
+ color: #ffffff;
157
+ margin-bottom: 0.5rem;
158
+ }
159
+
160
+ p {
161
+ font-size: 0.875rem;
162
+ color: #8b92a4;
163
+ line-height: 1.5;
164
+ }
165
+
166
+ .divider {
167
+ height: 1px;
168
+ background: #252b3b;
169
+ margin: 1.5rem 0;
170
+ }
171
+
172
+ .hint {
173
+ font-size: 0.8125rem;
174
+ color: #8b92a4;
175
+ }
176
+
177
+ .hint code {
178
+ color: #02f194;
179
+ background: rgba(2, 241, 148, 0.08);
180
+ padding: 0.125rem 0.375rem;
181
+ border-radius: 0.25rem;
182
+ font-family: ui-monospace, monospace;
183
+ font-size: 0.8125rem;
184
+ }
185
+ </style>
186
+ </head>
187
+ <body>
188
+ <div class="card">
189
+ <p class="logo">Orion</p>
190
+ <p class="subtitle">Authentification CLI</p>
191
+
192
+ <div class="icon-wrap">
193
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
194
+ <polyline points="20 6 9 17 4 12"></polyline>
195
+ </svg>
196
+ </div>
197
+
198
+ <h2>Authentification réussie !</h2>
199
+ <p>Vous pouvez fermer cet onglet<br>et retourner dans le terminal.</p>
200
+
201
+ <div class="divider"></div>
202
+
203
+ <p class="hint">Le setup continue dans <code>orion-cli</code></p>
204
+ </div>
205
+ </body>
206
+ </html>`);
207
+ // Ferme le serveur proprement après avoir répondu
208
+ server.close();
209
+ resolve(token);
210
+ });
211
+ // Timeout de sécurité : si l'user ne se connecte pas dans les temps
212
+ const timeout = setTimeout(() => {
213
+ server.close();
214
+ reject(new Error('Timeout : aucune authentification reçue en 5 minutes.'));
215
+ }, AUTH_TIMEOUT_MS);
216
+ // Quand le serveur se ferme, on clear le timeout
217
+ server.on('close', () => clearTimeout(timeout));
218
+ // Lance l'écoute sur le port
219
+ server.listen(CALLBACK_PORT, '127.0.0.1', () => {
220
+ // Le serveur est prêt, le spinner peut afficher son message
221
+ });
222
+ // Gestion d'erreur si le port est déjà occupé
223
+ server.on('error', (err) => {
224
+ if (err.code === 'EADDRINUSE') {
225
+ reject(new Error(`Le port ${CALLBACK_PORT} est déjà utilisé.\n` +
226
+ `Fermez le processus qui l'utilise et relancez orion-cli.`));
227
+ }
228
+ else {
229
+ reject(err);
230
+ }
231
+ });
232
+ });
233
+ }
package/dist/auth.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { ApiVerifyResponse, NomConfig } from './types.js';
2
+ /**
3
+ * Vérifie le token auprès de l'API et retourne les infos du projet.
4
+ */
5
+ export declare function verifyToken(token: string, apiUrl?: string): Promise<ApiVerifyResponse>;
6
+ /**
7
+ * Construit les headers d'authentification pour les requêtes SDK.
8
+ */
9
+ export declare function buildAuthHeaders(config: Pick<NomConfig, 'token'>): Record<string, string>;
package/dist/auth.js ADDED
@@ -0,0 +1,44 @@
1
+ // URL de ton API — peut être surchargée via NOM_API_URL
2
+ const DEFAULT_API_URL = 'http://localhost:3001/api';
3
+ /**
4
+ * Vérifie le token auprès de l'API et retourne les infos du projet.
5
+ */
6
+ export async function verifyToken(token, apiUrl = DEFAULT_API_URL) {
7
+ try {
8
+ const res = await fetch(`${apiUrl}/auth/me`, {
9
+ method: 'POST',
10
+ headers: {
11
+ 'Content-Type': 'application/json',
12
+ Authorization: `Bearer ${token}`,
13
+ },
14
+ body: JSON.stringify({ token }),
15
+ signal: AbortSignal.timeout(8000), // timeout 8s
16
+ });
17
+ if (!res.ok) {
18
+ if (res.status === 401) {
19
+ return { valid: false, error: 'Token invalide ou expiré.' };
20
+ }
21
+ if (res.status === 404) {
22
+ return { valid: false, error: 'Projet introuvable.' };
23
+ }
24
+ return { valid: false, error: `Erreur serveur (${res.status}).` };
25
+ }
26
+ const data = (await res.json());
27
+ return { valid: true, projectName: data.projectName };
28
+ }
29
+ catch (err) {
30
+ if (err instanceof Error && err.name === 'TimeoutError') {
31
+ return { valid: false, error: 'Timeout — serveur injoignable.' };
32
+ }
33
+ return { valid: false, error: 'Impossible de contacter le serveur.' };
34
+ }
35
+ }
36
+ /**
37
+ * Construit les headers d'authentification pour les requêtes SDK.
38
+ */
39
+ export function buildAuthHeaders(config) {
40
+ return {
41
+ Authorization: `Bearer ${config.token}`,
42
+ 'X-Nom-Source': 'sdk',
43
+ };
44
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env node
2
+ import { intro, outro, text, password, select, confirm, spinner, isCancel, cancel, note, log, } from '@clack/prompts';
3
+ import pc from 'picocolors';
4
+ import { listProjects, createProject, getProjectToken, registerSource } from './api.js';
5
+ import { writeConfig, configExists, normalizeName } from './writer.js';
6
+ import { loginWithBrowser } from './auth-browser.js';
7
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
8
+ function bail(value) {
9
+ if (isCancel(value)) {
10
+ cancel('Setup annulé.');
11
+ process.exit(0);
12
+ }
13
+ }
14
+ function orionBanner() {
15
+ console.log();
16
+ console.log(pc.cyan(' ╔═══════════════════════╗'));
17
+ console.log(pc.cyan(' ║ ') + pc.bold(pc.white('O R I O N')) + pc.cyan(' · CLI ║'));
18
+ console.log(pc.cyan(' ╚═══════════════════════╝'));
19
+ console.log();
20
+ }
21
+ // ─── Main ─────────────────────────────────────────────────────────────────────
22
+ async function main() {
23
+ orionBanner();
24
+ intro(pc.bgCyan(pc.black(' orion-cli ')));
25
+ // 0. Config existante ?
26
+ if (configExists()) {
27
+ const overwrite = await confirm({
28
+ message: pc.yellow('Un fichier orion.config.ts existe déjà. L\'écraser ?'),
29
+ initialValue: false,
30
+ });
31
+ bail(overwrite);
32
+ if (!overwrite) {
33
+ cancel('Setup annulé — configuration existante conservée.');
34
+ process.exit(0);
35
+ }
36
+ }
37
+ const base = "http://localhost:3001";
38
+ // 2. Connexion au compte Orion
39
+ log.step('Connexion à votre compte Orion');
40
+ // Propose deux méthodes d'auth
41
+ const authMethod = await select({
42
+ message: 'Comment voulez-vous vous connecter ?',
43
+ options: [
44
+ {
45
+ value: 'browser',
46
+ label: '🌐 Via le navigateur',
47
+ hint: 'Recommandé',
48
+ },
49
+ {
50
+ value: 'token',
51
+ label: '🔑 Token personnel',
52
+ hint: 'Collez votre token depuis le dashboard',
53
+ },
54
+ ],
55
+ });
56
+ bail(authMethod);
57
+ let accessToken;
58
+ if (authMethod === 'browser') {
59
+ try {
60
+ accessToken = await loginWithBrowser(base);
61
+ }
62
+ catch (err) {
63
+ cancel(`Échec de l'authentification : ${err instanceof Error ? err.message : String(err)}`);
64
+ process.exit(1);
65
+ }
66
+ }
67
+ else {
68
+ // Fallback : token manuel (utile en CI/CD ou si le navigateur ne s'ouvre pas)
69
+ const manualToken = await password({
70
+ message: 'Collez votre token personnel (depuis orion.dev/settings) :',
71
+ mask: '*',
72
+ });
73
+ bail(manualToken);
74
+ accessToken = manualToken;
75
+ }
76
+ // 3. Choisir ou créer un projet
77
+ const projectsSpinner = spinner();
78
+ projectsSpinner.start('Récupération des projets...');
79
+ let projects = [];
80
+ try {
81
+ projects = await listProjects(base, accessToken);
82
+ projectsSpinner.stop(pc.green(`✓ ${projects.length} projet(s) trouvé(s)`));
83
+ }
84
+ catch {
85
+ projectsSpinner.stop(pc.yellow('⚠ Impossible de charger les projets'));
86
+ }
87
+ const projectOptions = [
88
+ ...projects.map((p) => ({
89
+ value: p.name,
90
+ label: p.label,
91
+ hint: p.name,
92
+ })),
93
+ {
94
+ value: '__new__',
95
+ label: pc.cyan('+ Créer un nouveau projet'),
96
+ hint: '',
97
+ },
98
+ ];
99
+ const selectedProject = await select({
100
+ message: 'Quel projet utiliser ?',
101
+ options: projectOptions,
102
+ });
103
+ bail(selectedProject);
104
+ let projectToken;
105
+ let projectName;
106
+ if (selectedProject === '__new__') {
107
+ // Créer un nouveau projet
108
+ const rawLabel = await text({
109
+ message: 'Nom affiché du projet (label) ?',
110
+ placeholder: 'Mon Backend',
111
+ validate: (v) => !v.trim() ? 'Le label est requis.' : undefined,
112
+ });
113
+ bail(rawLabel);
114
+ const rawName = await text({
115
+ message: 'Identifiant du projet (slug) ?',
116
+ placeholder: normalizeName(rawLabel),
117
+ initialValue: normalizeName(rawLabel),
118
+ validate: (v) => {
119
+ const n = normalizeName(v);
120
+ if (!n)
121
+ return 'Identifiant requis.';
122
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(n))
123
+ return 'Minuscules, chiffres et tirets uniquement.';
124
+ return undefined;
125
+ },
126
+ });
127
+ bail(rawName);
128
+ const createSpinner = spinner();
129
+ createSpinner.start('Création du projet...');
130
+ try {
131
+ const created = await createProject(base, accessToken, normalizeName(rawName), rawLabel.trim());
132
+ projectToken = created.token;
133
+ projectName = created.name;
134
+ createSpinner.stop(pc.green(`✓ Projet "${created.label}" créé`));
135
+ }
136
+ catch (err) {
137
+ createSpinner.stop(pc.red('✗ Échec de la création'));
138
+ cancel(`Erreur : ${err instanceof Error ? err.message : String(err)}`);
139
+ process.exit(1);
140
+ }
141
+ }
142
+ else {
143
+ // Projet existant → récupérer son token
144
+ projectName = selectedProject;
145
+ const tokenSpinner = spinner();
146
+ tokenSpinner.start('Récupération du token...');
147
+ try {
148
+ projectToken = await getProjectToken(base, accessToken, projectName);
149
+ tokenSpinner.stop(pc.green('✓ Token récupéré'));
150
+ }
151
+ catch (err) {
152
+ tokenSpinner.stop(pc.red('✗ Impossible de récupérer le token'));
153
+ cancel(`Erreur : ${err instanceof Error ? err.message : String(err)}`);
154
+ process.exit(1);
155
+ }
156
+ }
157
+ // 4. Nom de la source
158
+ const source = await text({
159
+ message: 'Nom de la source (identifie l\'origine des logs) ?',
160
+ placeholder: 'api-backend',
161
+ validate: (v) => {
162
+ if (!v.trim())
163
+ return 'Requis.';
164
+ if (!/^[a-z0-9_-]+$/.test(v))
165
+ return 'Minuscules, chiffres, et - uniquement.';
166
+ return undefined;
167
+ },
168
+ });
169
+ bail(source);
170
+ // 5. Description de la source
171
+ const description = await text({
172
+ message: 'Description de la source ?',
173
+ placeholder: 'Gestion des utilisateurs',
174
+ });
175
+ bail(description);
176
+ // 6. Environnement
177
+ const environment = await select({
178
+ message: 'Environnement ?',
179
+ options: [
180
+ { value: 'prod', label: 'Production', hint: 'prod' },
181
+ { value: 'dev', label: 'Development', hint: 'dev' },
182
+ { value: 'staging', label: 'Staging', hint: 'staging' },
183
+ { value: 'test', label: 'Test', hint: 'test' },
184
+ ],
185
+ });
186
+ bail(environment);
187
+ // 7. Register source on server
188
+ let result = await registerSource(base, accessToken, projectName, source, description, environment);
189
+ // 7. Écriture
190
+ const writeSpinner = spinner();
191
+ writeSpinner.start('Écriture de orion.config.ts...');
192
+ const { configPath } = writeConfig({
193
+ token: projectToken,
194
+ projectName: projectName,
195
+ sourceName: result.name,
196
+ }, process.cwd());
197
+ writeSpinner.stop(pc.green('✓ Configuration écrite'));
198
+ // 8. Résumé
199
+ note([
200
+ `Projet : ${pc.bold(projectName)}`,
201
+ `Source : ${pc.bold(result.name)}`,
202
+ `Config : ${pc.cyan(configPath)}`,
203
+ ].join('\n'), 'Récapitulatif');
204
+ // 9. Outro
205
+ outro(pc.green('✓ Setup terminé !\n\n') +
206
+ ' Installez le SDK :\n' +
207
+ pc.cyan(' npm install orion\n\n') +
208
+ ' Puis dans votre code :\n' +
209
+ pc.gray(" import { createLogger } from 'orion'\n") +
210
+ pc.gray(' const logger = await createLogger()\n') +
211
+ pc.gray(" logger.info('Hello from Orion!')"));
212
+ }
213
+ main().catch((err) => {
214
+ console.error(pc.red('\nErreur inattendue :'), err);
215
+ process.exit(1);
216
+ });
@@ -0,0 +1,30 @@
1
+ export type Environment = 'production' | 'development' | 'staging' | 'test';
2
+ export interface LoginResponse {
3
+ accessToken: string;
4
+ user: {
5
+ id: number;
6
+ email: string;
7
+ pseudo: string;
8
+ first_name: string;
9
+ last_name: string;
10
+ };
11
+ }
12
+ export interface Project {
13
+ id: string;
14
+ name: string;
15
+ label: string;
16
+ }
17
+ export interface CreateProjectResponse {
18
+ id: number;
19
+ name: string;
20
+ label: string;
21
+ token: string;
22
+ }
23
+ export interface GetTokenResponse {
24
+ token: string;
25
+ }
26
+ export interface OrionConfig {
27
+ token: string;
28
+ projectName: string;
29
+ sourceName: string;
30
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { OrionConfig } from './types.js';
2
+ export declare function writeConfig(config: OrionConfig, targetDir?: string): {
3
+ configPath: string;
4
+ };
5
+ export declare function configExists(targetDir?: string): boolean;
6
+ export declare function normalizeName(raw: string): string;
package/dist/writer.js ADDED
@@ -0,0 +1,29 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export function writeConfig(config, targetDir = process.cwd()) {
4
+ const configPath = path.join(targetDir, 'orion.config.ts');
5
+ const content = [
6
+ `import { defineConfig } from 'orion-cli'`,
7
+ ``,
8
+ `export default defineConfig({`,
9
+ ` token: '${config.token}',`,
10
+ ` projectName: '${config.projectName}',`,
11
+ ` sourceName: '${config.sourceName}',`,
12
+ `})`,
13
+ ``,
14
+ ].join('\n');
15
+ fs.writeFileSync(configPath, content, 'utf-8');
16
+ return { configPath };
17
+ }
18
+ export function configExists(targetDir = process.cwd()) {
19
+ return fs.existsSync(path.join(targetDir, 'orion.config.ts'));
20
+ }
21
+ // Normalise un nom de projet comme le fait ton backend
22
+ export function normalizeName(raw) {
23
+ return raw
24
+ .toLowerCase()
25
+ .trim()
26
+ .replace(/\s+/g, '-')
27
+ .replace(/-+/g, '-')
28
+ .replace(/^-|-$/g, '');
29
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@orion-monitoring/cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI to initialize an Orion logging project",
5
+ "type": "module",
6
+ "bin": {
7
+ "orion-cli": "./dist/index.js",
8
+ "create-orion": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx src/index.ts",
13
+ "start": "node dist/index.js"
14
+ },
15
+ "dependencies": {
16
+ "@clack/prompts": "^0.7.0",
17
+ "open": "^11.0.0",
18
+ "picocolors": "^1.0.1"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^20.0.0",
22
+ "tsx": "^4.0.0",
23
+ "typescript": "^5.0.0"
24
+ },
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md"
31
+ ]
32
+ }