@orbitpanel/cli 0.5.0 → 0.7.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/dist/lib/ai-stream.d.ts +34 -0
- package/dist/lib/ai-stream.js +131 -0
- package/dist/lib/ai-stream.js.map +1 -0
- package/dist/lib/report-builder.d.ts +1 -0
- package/dist/lib/report-builder.js +1 -1
- package/dist/lib/report-builder.js.map +1 -1
- package/dist/lib/risk.d.ts +38 -0
- package/dist/lib/risk.js +82 -0
- package/dist/lib/risk.js.map +1 -0
- package/dist/lib/runtime.d.ts +11 -0
- package/dist/lib/runtime.js +34 -0
- package/dist/lib/runtime.js.map +1 -0
- package/dist/lib/session.d.ts +2 -0
- package/dist/lib/session.js +20 -0
- package/dist/lib/session.js.map +1 -1
- package/dist/lib/shell-commands.d.ts +20 -0
- package/dist/lib/shell-commands.js +437 -0
- package/dist/lib/shell-commands.js.map +1 -0
- package/dist/lib/shell-render.d.ts +25 -0
- package/dist/lib/shell-render.js +54 -0
- package/dist/lib/shell-render.js.map +1 -0
- package/dist/lib/shell.d.ts +1 -1
- package/dist/lib/shell.js +155 -489
- package/dist/lib/shell.js.map +1 -1
- package/dist/lib/stream-state.d.ts +28 -0
- package/dist/lib/stream-state.js +69 -0
- package/dist/lib/stream-state.js.map +1 -0
- package/dist/lib/ui.d.ts +14 -0
- package/dist/lib/ui.js +35 -0
- package/dist/lib/ui.js.map +1 -1
- package/dist/state/store.d.ts +29 -0
- package/dist/state/store.js +84 -0
- package/dist/state/store.js.map +1 -0
- package/dist/state/types.d.ts +43 -0
- package/dist/state/types.js +6 -0
- package/dist/state/types.js.map +1 -0
- package/dist/types.d.ts +4 -0
- package/oclif.manifest.json +49 -49
- package/package.json +1 -1
package/dist/lib/shell.js
CHANGED
|
@@ -1,68 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Interactive REPL shell for @orbitpanel/cli.
|
|
3
|
+
* Thin orchestration layer — delegates to shell-commands.ts and shell-render.ts.
|
|
3
4
|
* Pure readline + chalk — no Ink/React dependency.
|
|
4
|
-
* Stable, works in all terminals and TTY contexts.
|
|
5
5
|
*/
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
-
import gradient from 'gradient-string';
|
|
8
7
|
import ora from 'ora';
|
|
9
8
|
import { createInterface } from 'node:readline';
|
|
10
|
-
import { loadConfig
|
|
11
|
-
import { OrbitClient } from './client.js';
|
|
12
|
-
import { requestDeviceCode, pollDeviceCode, saveDeviceAuth, openBrowser } from './device-auth.js';
|
|
9
|
+
import { loadConfig } from './config.js';
|
|
13
10
|
import { loadSiteLink } from './site.js';
|
|
14
|
-
import { loadActiveSession
|
|
15
|
-
import { getGitBranch
|
|
16
|
-
import { buildReportPayload } from './report-builder.js';
|
|
11
|
+
import { loadActiveSession } from './session.js';
|
|
12
|
+
import { getGitBranch } from './git.js';
|
|
17
13
|
import { sendChat } from './ai-chat.js';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
' ██║ ██║██╔══██╗██╔══██╗██║ ██║ ',
|
|
33
|
-
' ╚██████╔╝██║ ██║██████╔╝██║ ██║ ',
|
|
34
|
-
' ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ ',
|
|
35
|
-
];
|
|
36
|
-
const SLASH_COMMANDS = [
|
|
37
|
-
{ cmd: '/status', alias: '/s', desc: 'panoramica completa' },
|
|
38
|
-
{ cmd: '/doctor', alias: '/doc', desc: 'verifica configurazione' },
|
|
39
|
-
{ cmd: '/session start', alias: '/ss', desc: 'avvia sessione di lavoro' },
|
|
40
|
-
{ cmd: '/session end', alias: '/se', desc: 'chiudi sessione' },
|
|
41
|
-
{ cmd: '/session', alias: '', desc: 'info sessione attiva' },
|
|
42
|
-
{ cmd: '/note', alias: '/n', desc: 'aggiungi nota (+ testo)' },
|
|
43
|
-
{ cmd: '/report', alias: '/r', desc: 'invia report a Orbit' },
|
|
44
|
-
{ cmd: '/list', alias: '/ls', desc: 'lista interventi' },
|
|
45
|
-
{ cmd: '/get', alias: '/g', desc: 'dettaglio intervento (+ id)' },
|
|
46
|
-
{ cmd: '/sites', alias: '', desc: 'lista siti e seleziona' },
|
|
47
|
-
{ cmd: '/auth', alias: '', desc: 'accedi con browser' },
|
|
48
|
-
{ cmd: '/login', alias: '', desc: 'configura token (+ token)' },
|
|
49
|
-
{ cmd: '/link', alias: '', desc: 'collega directory (+ site_id)' },
|
|
50
|
-
{ cmd: '/clear', alias: '', desc: 'pulisci schermo' },
|
|
51
|
-
{ cmd: '/help', alias: '/h', desc: 'lista comandi' },
|
|
52
|
-
{ cmd: '/exit', alias: '/q', desc: 'esci' },
|
|
53
|
-
];
|
|
14
|
+
import { OrbitStore } from '../state/store.js';
|
|
15
|
+
import { sendChatStream } from './ai-stream.js';
|
|
16
|
+
import { StreamStateMachine } from './stream-state.js';
|
|
17
|
+
import { outputBlock } from './ui.js';
|
|
18
|
+
import { B, BL, BB, YELLOW, DIM, WHITE, orbitGrad, sparkle, logoLines, SLASH_COMMANDS, sep, } from './shell-render.js';
|
|
19
|
+
import { statusLine } from './ui.js';
|
|
20
|
+
import { cmdStatus, cmdDoctor, cmdSessionStart, cmdSessionEnd, cmdSessionInfo, cmdNoteAdd, cmdReport, cmdList, cmdGet, cmdHistory, cmdSites, cmdAuth, cmdLogin, cmdLink, } from './shell-commands.js';
|
|
21
|
+
// Default IO adapter — bridges store to real filesystem modules
|
|
22
|
+
const defaultStoreIO = {
|
|
23
|
+
loadConfig,
|
|
24
|
+
loadSiteLink: (cwd) => loadSiteLink(cwd),
|
|
25
|
+
loadActiveSession: (siteId) => loadActiveSession(siteId),
|
|
26
|
+
getGitBranch,
|
|
27
|
+
};
|
|
54
28
|
function sleep(ms) {
|
|
55
29
|
return new Promise(r => setTimeout(r, ms));
|
|
56
30
|
}
|
|
57
|
-
function sep() {
|
|
58
|
-
console.log(orbitGrad(' ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
59
|
-
}
|
|
60
|
-
function getClient(config) {
|
|
61
|
-
const t = config.default_site ? config.tokens[config.default_site] : undefined;
|
|
62
|
-
if (!t)
|
|
63
|
-
return null;
|
|
64
|
-
return new OrbitClient(t.token, config.api_url);
|
|
65
|
-
}
|
|
66
31
|
// Tab-completion handler
|
|
67
32
|
function completer(line) {
|
|
68
33
|
if (!line.startsWith('/'))
|
|
@@ -82,26 +47,24 @@ export async function startShell(version) {
|
|
|
82
47
|
}
|
|
83
48
|
console.log('');
|
|
84
49
|
console.log(` ${chalk.bgRgb(37, 99, 235).white.bold(' CLI ')} ${chalk.bgHex('#1E293B').hex('#60A5FA')(` v${version} `)} ${DIM('·')} ${BL('Gestione Orbit Panel AI-powered')}`);
|
|
85
|
-
//
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
parts.push(`${chalk.red('●')} Non connesso`);
|
|
95
|
-
if (siteLink)
|
|
96
|
-
parts.push(BL(siteLink.final_url ?? siteLink.orbit_site_id.slice(0, 16)));
|
|
97
|
-
if (session) {
|
|
98
|
-
const elapsed = Math.round((Date.now() - new Date(session.started_at).getTime()) / 60_000);
|
|
99
|
-
parts.push(`${GREEN('●')} Sessione ${DIM(`${elapsed}min`)}`);
|
|
100
|
-
if (session.git_branch)
|
|
101
|
-
parts.push(BL(session.git_branch));
|
|
102
|
-
}
|
|
50
|
+
// Initialize centralized state
|
|
51
|
+
const cwd = process.cwd();
|
|
52
|
+
const store = new OrbitStore(defaultStoreIO);
|
|
53
|
+
await store.init(cwd);
|
|
54
|
+
const { config, tokenEntry, siteLink, session, gitBranch, runtime } = store.getState();
|
|
55
|
+
// Status bar (using centralized statusLine renderer)
|
|
56
|
+
const sessionMinutes = session
|
|
57
|
+
? Math.round((Date.now() - new Date(session.started_at).getTime()) / 60_000)
|
|
58
|
+
: undefined;
|
|
103
59
|
console.log('');
|
|
104
|
-
console.log(
|
|
60
|
+
console.log(statusLine({
|
|
61
|
+
connected: !!tokenEntry,
|
|
62
|
+
siteUrl: siteLink?.final_url ?? siteLink?.name ?? (siteLink ? siteLink.orbit_site_id.slice(0, 16) : undefined),
|
|
63
|
+
environment: siteLink?.environment,
|
|
64
|
+
sessionMinutes,
|
|
65
|
+
gitBranch: (session?.git_branch ?? gitBranch) ?? undefined,
|
|
66
|
+
runtimeMode: runtime,
|
|
67
|
+
}));
|
|
105
68
|
console.log('');
|
|
106
69
|
sep();
|
|
107
70
|
// Auto-prompt auth
|
|
@@ -125,7 +88,6 @@ export async function startShell(version) {
|
|
|
125
88
|
completer,
|
|
126
89
|
terminal: true,
|
|
127
90
|
});
|
|
128
|
-
// Ctrl+C and Ctrl+Z to exit
|
|
129
91
|
rl.on('SIGINT', () => {
|
|
130
92
|
console.log(`\n\n ${DIM('Alla prossima!')}\n`);
|
|
131
93
|
rl.close();
|
|
@@ -135,6 +97,15 @@ export async function startShell(version) {
|
|
|
135
97
|
rl.close();
|
|
136
98
|
process.exit(0);
|
|
137
99
|
});
|
|
100
|
+
// Prompt function for risk confirmations (uses the active readline)
|
|
101
|
+
const promptConfirm = (message) => {
|
|
102
|
+
return new Promise((resolve) => {
|
|
103
|
+
rl.question(message, (answer) => {
|
|
104
|
+
const a = answer.trim().toLowerCase();
|
|
105
|
+
resolve(a === 's' || a === 'si' || a === 'y' || a === 'yes');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
};
|
|
138
109
|
rl.prompt();
|
|
139
110
|
return new Promise((resolve) => {
|
|
140
111
|
rl.on('close', () => resolve());
|
|
@@ -145,528 +116,223 @@ export async function startShell(version) {
|
|
|
145
116
|
rl.prompt();
|
|
146
117
|
return;
|
|
147
118
|
}
|
|
148
|
-
//
|
|
119
|
+
// --- Command routing (thin dispatcher) ---
|
|
149
120
|
if (cmd === '/exit' || cmd === '/quit' || cmd === '/q') {
|
|
150
121
|
console.log(`\n ${DIM('Alla prossima!')}\n`);
|
|
151
122
|
rl.close();
|
|
152
123
|
return;
|
|
153
124
|
}
|
|
154
|
-
// Clear
|
|
155
125
|
if (cmd === '/clear' || cmd === '/cls') {
|
|
156
126
|
console.clear();
|
|
157
127
|
rl.prompt();
|
|
158
128
|
return;
|
|
159
129
|
}
|
|
160
|
-
// Help
|
|
161
130
|
if (cmd === '/help' || cmd === '/h') {
|
|
162
|
-
|
|
163
|
-
console.log(` ${sparkle} ${BB('Comandi disponibili')}`);
|
|
164
|
-
console.log('');
|
|
165
|
-
for (const c of SLASH_COMMANDS) {
|
|
166
|
-
const alias = c.alias ? DIM(` ${c.alias}`) : '';
|
|
167
|
-
console.log(` ${WHITE(c.cmd.padEnd(18))}${alias.padEnd(12)}${DIM(c.desc)}`);
|
|
168
|
-
}
|
|
169
|
-
console.log('');
|
|
170
|
-
console.log(` ${DIM('Oppure scrivi in linguaggio naturale per parlare con Orbit AI.')}`);
|
|
171
|
-
console.log('');
|
|
131
|
+
printHelp();
|
|
172
132
|
rl.prompt();
|
|
173
133
|
return;
|
|
174
134
|
}
|
|
175
|
-
// Status
|
|
176
135
|
if (cmd === '/status' || cmd === '/s') {
|
|
177
136
|
await cmdStatus();
|
|
178
137
|
rl.prompt();
|
|
179
138
|
return;
|
|
180
139
|
}
|
|
181
|
-
// Doctor
|
|
182
140
|
if (cmd === '/doctor' || cmd === '/doc') {
|
|
183
141
|
await cmdDoctor();
|
|
184
142
|
rl.prompt();
|
|
185
143
|
return;
|
|
186
144
|
}
|
|
187
|
-
// Session start
|
|
188
145
|
if (cmd === '/session start' || cmd === '/ss') {
|
|
189
146
|
await cmdSessionStart();
|
|
147
|
+
await store.refresh(cwd);
|
|
190
148
|
rl.prompt();
|
|
191
149
|
return;
|
|
192
150
|
}
|
|
193
|
-
// Session end
|
|
194
151
|
if (cmd === '/session end' || cmd === '/se') {
|
|
195
152
|
await cmdSessionEnd();
|
|
153
|
+
await store.refresh(cwd);
|
|
196
154
|
rl.prompt();
|
|
197
155
|
return;
|
|
198
156
|
}
|
|
199
|
-
// Session info
|
|
200
157
|
if (cmd === '/session' || cmd === '/session info') {
|
|
201
158
|
await cmdSessionInfo();
|
|
202
159
|
rl.prompt();
|
|
203
160
|
return;
|
|
204
161
|
}
|
|
205
|
-
// Note
|
|
206
162
|
if (cmd.startsWith('/note ') || cmd.startsWith('/n ')) {
|
|
207
163
|
await cmdNoteAdd(raw.slice(raw.indexOf(' ') + 1));
|
|
208
164
|
rl.prompt();
|
|
209
165
|
return;
|
|
210
166
|
}
|
|
211
|
-
// Report
|
|
212
167
|
if (cmd === '/report' || cmd === '/r') {
|
|
213
|
-
await cmdReport();
|
|
168
|
+
await cmdReport(promptConfirm);
|
|
169
|
+
await store.refresh(cwd);
|
|
214
170
|
rl.prompt();
|
|
215
171
|
return;
|
|
216
172
|
}
|
|
217
|
-
// List interventions
|
|
218
173
|
if (cmd === '/list' || cmd === '/ls' || cmd === '/interventions') {
|
|
219
174
|
await cmdList();
|
|
220
175
|
rl.prompt();
|
|
221
176
|
return;
|
|
222
177
|
}
|
|
223
|
-
|
|
178
|
+
if (cmd === '/history' || cmd === '/hist') {
|
|
179
|
+
await cmdHistory();
|
|
180
|
+
rl.prompt();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
224
183
|
if (cmd.startsWith('/get ') || cmd.startsWith('/g ')) {
|
|
225
184
|
await cmdGet(raw.split(' ')[1]);
|
|
226
185
|
rl.prompt();
|
|
227
186
|
return;
|
|
228
187
|
}
|
|
229
|
-
// Sites
|
|
230
188
|
if (cmd === '/sites' || cmd.startsWith('/sites ')) {
|
|
231
189
|
await cmdSites(cmd.split(' ')[1]);
|
|
190
|
+
await store.refresh(cwd);
|
|
232
191
|
rl.prompt();
|
|
233
192
|
return;
|
|
234
193
|
}
|
|
235
|
-
// Auth (device code)
|
|
236
194
|
if (cmd === '/auth') {
|
|
237
195
|
await cmdAuth();
|
|
196
|
+
await store.refresh(cwd);
|
|
238
197
|
rl.prompt();
|
|
239
198
|
return;
|
|
240
199
|
}
|
|
241
|
-
// Login (token)
|
|
242
200
|
if (cmd.startsWith('/login ')) {
|
|
243
201
|
await cmdLogin(raw.split(' ')[1]);
|
|
202
|
+
await store.refresh(cwd);
|
|
244
203
|
rl.prompt();
|
|
245
204
|
return;
|
|
246
205
|
}
|
|
247
|
-
// Link
|
|
248
206
|
if (cmd.startsWith('/link ')) {
|
|
249
207
|
await cmdLink(raw.split(' ')[1]);
|
|
208
|
+
await store.refresh(cwd);
|
|
250
209
|
rl.prompt();
|
|
251
210
|
return;
|
|
252
211
|
}
|
|
253
|
-
// Unknown slash command
|
|
254
212
|
if (cmd.startsWith('/')) {
|
|
255
213
|
console.log(`\n ${DIM('Comando sconosciuto:')} ${WHITE(cmd)} ${DIM('— digita /help')}\n`);
|
|
256
214
|
rl.prompt();
|
|
257
215
|
return;
|
|
258
216
|
}
|
|
259
|
-
// Free text → AI chat
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
const chatResult = await sendChat(raw);
|
|
263
|
-
spinner.stop();
|
|
264
|
-
if (chatResult.error) {
|
|
265
|
-
console.log(`\n ${chalk.red('✗')} ${chatResult.error}\n`);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
const provider = chatResult.provider_used ? DIM(` (${chatResult.provider_used})`) : '';
|
|
269
|
-
console.log('');
|
|
270
|
-
console.log(` ${sparkle} ${BB('Orbit AI')}${provider}`);
|
|
271
|
-
console.log('');
|
|
272
|
-
for (const line of (chatResult.response ?? '').split('\n')) {
|
|
273
|
-
console.log(` ${line}`);
|
|
274
|
-
}
|
|
275
|
-
console.log('');
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
catch (err) {
|
|
279
|
-
spinner.stop();
|
|
280
|
-
console.log(`\n ${chalk.red('✗')} ${err instanceof Error ? err.message : String(err)}\n`);
|
|
281
|
-
}
|
|
217
|
+
// Free text → AI chat (streaming with state machine + fallback)
|
|
218
|
+
await handleAIChat(raw, store);
|
|
282
219
|
rl.prompt();
|
|
283
220
|
});
|
|
284
221
|
});
|
|
285
222
|
}
|
|
286
|
-
// ---
|
|
287
|
-
|
|
288
|
-
const config = await loadConfig();
|
|
289
|
-
const client = getClient(config);
|
|
290
|
-
console.log('');
|
|
291
|
-
console.log(` ${sparkle} ${BB('Stato')}`);
|
|
292
|
-
console.log('');
|
|
293
|
-
if (!client) {
|
|
294
|
-
console.log(` ${chalk.red('✗')} ${WHITE('Token')} ${DIM('non configurato')}`);
|
|
295
|
-
console.log(` ${DIM('Esegui: /auth o /login <token>')}`);
|
|
296
|
-
console.log('');
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
const tokenEntry = config.tokens[config.default_site];
|
|
300
|
-
const spinner = ora({ text: 'Verifica connessione...', color: 'blue', indent: 5 }).start();
|
|
301
|
-
const v = await client.validateToken();
|
|
302
|
-
if (!v.valid) {
|
|
303
|
-
spinner.fail(`API: ${v.error}`);
|
|
304
|
-
console.log('');
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
spinner.stop();
|
|
308
|
-
console.log(` ${GREEN('✓')} ${WHITE('Token')} ${DIM(tokenEntry.token.slice(0, 15) + '...')}`);
|
|
309
|
-
console.log(` ${GREEN('✓')} ${WHITE('API')} ${GREEN('raggiungibile')}`);
|
|
310
|
-
const siteLink = await loadSiteLink(process.cwd());
|
|
311
|
-
if (siteLink)
|
|
312
|
-
console.log(` ${GREEN('✓')} ${WHITE('Sito')} ${BL(siteLink.final_url ?? siteLink.orbit_site_id)}`);
|
|
313
|
-
const session = siteLink ? await loadActiveSession(siteLink.orbit_site_id) : null;
|
|
314
|
-
if (session) {
|
|
315
|
-
const elapsed = Math.round((Date.now() - new Date(session.started_at).getTime()) / 60_000);
|
|
316
|
-
console.log(` ${GREEN('✓')} ${WHITE('Sessione')} ${GREEN('attiva')} ${DIM(`${elapsed}min · ${session.notes.length} note`)}`);
|
|
317
|
-
}
|
|
318
|
-
try {
|
|
319
|
-
const stats = await client.getStats();
|
|
320
|
-
const total = stats.total_interventions ?? 0;
|
|
321
|
-
const by = stats.by_status ?? {};
|
|
322
|
-
console.log('');
|
|
323
|
-
console.log(` ${BL(String(total))} interventi totali`);
|
|
324
|
-
const p = [];
|
|
325
|
-
if (by.completed)
|
|
326
|
-
p.push(GREEN(`${by.completed} completati`));
|
|
327
|
-
if (by.in_progress)
|
|
328
|
-
p.push(YELLOW(`${by.in_progress} in corso`));
|
|
329
|
-
if (by.scheduled)
|
|
330
|
-
p.push(BL(`${by.scheduled} pianificati`));
|
|
331
|
-
if (p.length)
|
|
332
|
-
console.log(` ${p.join(DIM(' · '))}`);
|
|
333
|
-
}
|
|
334
|
-
catch { }
|
|
335
|
-
console.log('');
|
|
336
|
-
}
|
|
337
|
-
async function cmdDoctor() {
|
|
338
|
-
const config = await loadConfig();
|
|
339
|
-
console.log('');
|
|
340
|
-
console.log(` ${sparkle} ${BB('Diagnostica')}`);
|
|
341
|
-
console.log('');
|
|
342
|
-
const t = config.default_site ? config.tokens[config.default_site] : undefined;
|
|
343
|
-
console.log(` ${t ? GREEN('✓') : YELLOW('!')} ${WHITE('Token')} ${t ? DIM(t.token.slice(0, 15) + '...') : DIM('non configurato')}`);
|
|
344
|
-
if (t) {
|
|
345
|
-
const c = new OrbitClient(t.token, config.api_url);
|
|
346
|
-
const start = Date.now();
|
|
347
|
-
const v = await c.validateToken();
|
|
348
|
-
console.log(` ${v.valid ? GREEN('✓') : chalk.red('✗')} ${WHITE('API')} ${v.valid ? `${GREEN('raggiungibile')} ${DIM(`(${Date.now() - start}ms)`)}` : DIM(v.error ?? 'errore')}`);
|
|
349
|
-
}
|
|
350
|
-
const site = await loadSiteLink(process.cwd());
|
|
351
|
-
console.log(` ${site ? GREEN('✓') : YELLOW('!')} ${WHITE('Sito')} ${site ? `${BL('.orbit.json')} ${DIM('presente')}` : DIM('non collegato')}`);
|
|
352
|
-
const branch = await getGitBranch();
|
|
353
|
-
console.log(` ${branch ? GREEN('✓') : YELLOW('!')} ${WHITE('Git')} ${branch ? BL(branch) : DIM('nessun repository')}`);
|
|
354
|
-
const allOk = !!(t && site && branch);
|
|
355
|
-
console.log('');
|
|
356
|
-
console.log(` ${allOk ? GREEN.bold('✓ Tutto ok') : YELLOW('! Attenzione')} ${DIM(allOk ? '— pronto per lavorare' : '— vedi sopra')}`);
|
|
357
|
-
console.log('');
|
|
358
|
-
}
|
|
359
|
-
async function cmdSessionStart() {
|
|
360
|
-
const siteLink = await loadSiteLink(process.cwd());
|
|
361
|
-
if (!siteLink) {
|
|
362
|
-
console.log(`\n ${chalk.red('✗')} ${DIM('Nessun sito collegato. Esegui: /sites o /link <id>')}\n`);
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
const existing = await loadActiveSession(siteLink.orbit_site_id);
|
|
366
|
-
if (existing) {
|
|
367
|
-
console.log(`\n ${YELLOW('!')} ${DIM(`Sessione gia attiva (${existing.id}). Chiudi con: /session end`)}\n`);
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
const branch = await getGitBranch();
|
|
371
|
-
const commit = await getGitCommitHead();
|
|
372
|
-
const s = await createSession(siteLink.orbit_site_id, { git_branch: branch ?? undefined, git_commit: commit ?? undefined });
|
|
373
|
-
console.log('');
|
|
374
|
-
console.log(` ${sparkle} ${BB('Sessione avviata')}`);
|
|
375
|
-
console.log('');
|
|
376
|
-
console.log(` ${DIM('ID')} ${BL(s.id)}`);
|
|
377
|
-
if (branch)
|
|
378
|
-
console.log(` ${DIM('Branch')} ${WHITE(branch)}`);
|
|
379
|
-
if (commit)
|
|
380
|
-
console.log(` ${DIM('Commit')} ${BL(commit)}`);
|
|
381
|
-
console.log(` ${DIM('Inizio')} ${WHITE(new Date().toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' }))}`);
|
|
382
|
-
console.log('');
|
|
383
|
-
}
|
|
384
|
-
async function cmdSessionEnd() {
|
|
385
|
-
const siteLink = await loadSiteLink(process.cwd());
|
|
386
|
-
if (!siteLink) {
|
|
387
|
-
console.log(`\n ${chalk.red('✗')} ${DIM('Nessun sito collegato')}\n`);
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
const session = await loadActiveSession(siteLink.orbit_site_id);
|
|
391
|
-
if (!session) {
|
|
392
|
-
console.log(`\n ${DIM('Nessuna sessione attiva. /session start')}\n`);
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
const commitEnd = await getGitCommitHead();
|
|
396
|
-
await endSession(session.id, { git_commit_end: commitEnd ?? undefined });
|
|
397
|
-
const elapsed = Math.round((Date.now() - new Date(session.started_at).getTime()) / 60_000);
|
|
398
|
-
console.log('');
|
|
399
|
-
console.log(` ${GREEN('✓')} ${GREEN.bold('Sessione chiusa')} ${DIM(`(${elapsed}min · ${session.notes.length} note)`)}`);
|
|
400
|
-
console.log(` ${DIM('Invia il report con: /report')}`);
|
|
401
|
-
console.log('');
|
|
402
|
-
}
|
|
403
|
-
async function cmdSessionInfo() {
|
|
404
|
-
const siteLink = await loadSiteLink(process.cwd());
|
|
405
|
-
if (!siteLink) {
|
|
406
|
-
console.log(`\n ${DIM('Nessun sito collegato')}\n`);
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
const session = siteLink ? await loadActiveSession(siteLink.orbit_site_id) : null;
|
|
410
|
-
if (!session) {
|
|
411
|
-
console.log(`\n ${DIM('Nessuna sessione attiva')}\n`);
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
const elapsed = Math.round((Date.now() - new Date(session.started_at).getTime()) / 60_000);
|
|
223
|
+
// --- Help output ---
|
|
224
|
+
function printHelp() {
|
|
415
225
|
console.log('');
|
|
416
|
-
console.log(` ${sparkle} ${BB('
|
|
226
|
+
console.log(` ${sparkle} ${BB('Comandi disponibili')}`);
|
|
417
227
|
console.log('');
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
console.log(` ${DIM('Branch')} ${BL(session.git_branch)}`);
|
|
422
|
-
console.log(` ${DIM('Note')} ${WHITE(String(session.notes.length))}`);
|
|
423
|
-
for (const n of session.notes.slice(-5))
|
|
424
|
-
console.log(` ${DIM('·')} ${WHITE(n.text)}`);
|
|
425
|
-
console.log('');
|
|
426
|
-
}
|
|
427
|
-
async function cmdNoteAdd(text) {
|
|
428
|
-
const siteLink = await loadSiteLink(process.cwd());
|
|
429
|
-
if (!siteLink) {
|
|
430
|
-
console.log(`\n ${chalk.red('✗')} ${DIM('Nessun sito collegato')}\n`);
|
|
431
|
-
return;
|
|
228
|
+
for (const c of SLASH_COMMANDS) {
|
|
229
|
+
const alias = c.alias ? DIM(` ${c.alias}`) : '';
|
|
230
|
+
console.log(` ${WHITE(c.cmd.padEnd(18))}${alias.padEnd(12)}${DIM(c.desc)}`);
|
|
432
231
|
}
|
|
433
|
-
const session = await loadActiveSession(siteLink.orbit_site_id);
|
|
434
|
-
if (!session) {
|
|
435
|
-
console.log(`\n ${DIM('Nessuna sessione attiva. /session start')}\n`);
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
await addNote(session.id, text, 'medium');
|
|
439
232
|
console.log('');
|
|
440
|
-
console.log(` ${
|
|
441
|
-
console.log(` ${B('│')} ${WHITE(text)}`);
|
|
233
|
+
console.log(` ${DIM('Oppure scrivi in linguaggio naturale per parlare con Orbit AI.')}`);
|
|
442
234
|
console.log('');
|
|
443
235
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
if (!client) {
|
|
448
|
-
console.log(`\n ${chalk.red('✗')} ${DIM('Non autenticato. /auth')}\n`);
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
const siteLink = await loadSiteLink(process.cwd());
|
|
452
|
-
if (!siteLink) {
|
|
453
|
-
console.log(`\n ${chalk.red('✗')} ${DIM('Nessun sito collegato')}\n`);
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
let session = await loadActiveSession(siteLink.orbit_site_id);
|
|
457
|
-
const commitEnd = await getGitCommitHead();
|
|
458
|
-
let filesChanged = 0;
|
|
459
|
-
if (session?.git_commit_start && commitEnd) {
|
|
460
|
-
const diff = await getGitDiffStat(session.git_commit_start, commitEnd);
|
|
461
|
-
filesChanged = diff.files_changed;
|
|
462
|
-
}
|
|
463
|
-
if (session && session.status === 'active') {
|
|
464
|
-
session = await endSession(session.id, { git_commit_end: commitEnd ?? undefined });
|
|
465
|
-
}
|
|
466
|
-
const payload = buildReportPayload({
|
|
467
|
-
session: session ?? { id: 'no-session', site_id: siteLink.orbit_site_id, started_at: new Date().toISOString(), notes: [], status: 'completed' },
|
|
468
|
-
git_commit_end: commitEnd ?? undefined, files_changed: filesChanged,
|
|
469
|
-
});
|
|
470
|
-
payload.site_id = siteLink.orbit_site_id;
|
|
471
|
-
const spinner = ora({ text: 'Invio report a Orbit...', color: 'blue', indent: 2 }).start();
|
|
472
|
-
try {
|
|
473
|
-
const result = await client.createIntervention(payload);
|
|
474
|
-
spinner.succeed(`${GREEN.bold('Report inviato')} ${DIM(String(result.id).slice(0, 8))}`);
|
|
475
|
-
}
|
|
476
|
-
catch (err) {
|
|
477
|
-
spinner.fail(`Invio fallito: ${err instanceof Error ? err.message : String(err)}`);
|
|
478
|
-
}
|
|
236
|
+
// --- AI Chat with streaming + state machine ---
|
|
237
|
+
async function handleAIChat(raw, store) {
|
|
238
|
+
const { config, siteLink } = store.getState();
|
|
479
239
|
console.log('');
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const
|
|
483
|
-
const client = getClient(config);
|
|
484
|
-
if (!client) {
|
|
485
|
-
console.log(`\n ${chalk.red('✗')} ${DIM('Non autenticato')}\n`);
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
const siteLink = await loadSiteLink(process.cwd());
|
|
489
|
-
const filters = { limit: 10, sort: '-created_at' };
|
|
490
|
-
if (siteLink)
|
|
491
|
-
filters.site_id = siteLink.orbit_site_id;
|
|
492
|
-
const spinner = ora({ text: 'Caricamento...', color: 'blue', indent: 2 }).start();
|
|
240
|
+
console.log(` ${sparkle} ${BB('Orbit AI')}`);
|
|
241
|
+
const spinner = ora({ text: DIM('Recupero contesto...'), color: 'blue', indent: 2 }).start();
|
|
242
|
+
const sm = new StreamStateMachine();
|
|
493
243
|
try {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
244
|
+
store.startRequest();
|
|
245
|
+
const result = await sendChatStream(raw, (event) => {
|
|
246
|
+
// State machine gates event processing
|
|
247
|
+
if (!sm.transition(event.type))
|
|
248
|
+
return;
|
|
249
|
+
switch (event.type) {
|
|
250
|
+
case 'status': {
|
|
251
|
+
const msg = (event.data.message ?? event.data.phase ?? '');
|
|
252
|
+
if (spinner.isSpinning)
|
|
253
|
+
spinner.text = DIM(msg);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case 'thinking': {
|
|
257
|
+
if (spinner.isSpinning)
|
|
258
|
+
spinner.text = DIM('Analisi in corso...');
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case 'delta': {
|
|
262
|
+
if (spinner.isSpinning) {
|
|
263
|
+
spinner.stop();
|
|
264
|
+
console.log('');
|
|
265
|
+
process.stdout.write(' ');
|
|
266
|
+
}
|
|
267
|
+
const chunk = event.data.content;
|
|
268
|
+
process.stdout.write(chunk.replaceAll('\n', '\n '));
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case 'error': {
|
|
272
|
+
if (spinner.isSpinning)
|
|
273
|
+
spinner.stop();
|
|
274
|
+
console.log(`\n ${chalk.red('✗')} ${event.data.message}`);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case 'done':
|
|
278
|
+
if (spinner.isSpinning)
|
|
279
|
+
spinner.stop();
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}, config, siteLink);
|
|
283
|
+
if (spinner.isSpinning)
|
|
284
|
+
spinner.stop();
|
|
285
|
+
store.endRequest();
|
|
286
|
+
const contentStarted = sm.current === 'generating' || sm.current === 'done';
|
|
287
|
+
// Fallback: stream failed before any content → try non-streaming
|
|
288
|
+
if (result.error && !contentStarted) {
|
|
289
|
+
const fallback = await sendChat(raw);
|
|
290
|
+
if (fallback.error) {
|
|
291
|
+
console.log('');
|
|
292
|
+
console.log(outputBlock('error', fallback.error));
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
console.log('');
|
|
296
|
+
for (const line of (fallback.response ?? '').split('\n')) {
|
|
297
|
+
console.log(` ${line}`);
|
|
298
|
+
}
|
|
299
|
+
if (fallback.provider_used) {
|
|
300
|
+
console.log(`\n ${DIM(`(${fallback.provider_used})`)}`);
|
|
301
|
+
}
|
|
512
302
|
}
|
|
513
|
-
console.log('');
|
|
514
|
-
console.log(` ${DIM(`${result.total} interventi`)}`);
|
|
515
303
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if (!id) {
|
|
524
|
-
console.log(`\n ${DIM('Uso: /get <intervention_id>')}\n`);
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
const config = await loadConfig();
|
|
528
|
-
const client = getClient(config);
|
|
529
|
-
if (!client) {
|
|
530
|
-
console.log(`\n ${chalk.red('✗')} ${DIM('Non autenticato')}\n`);
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
const spinner = ora({ text: 'Caricamento...', color: 'blue', indent: 2 }).start();
|
|
534
|
-
try {
|
|
535
|
-
const item = await client.getIntervention(id);
|
|
536
|
-
spinner.stop();
|
|
537
|
-
console.log('');
|
|
538
|
-
console.log(` ${sparkle} ${BB(String(item.title))}`);
|
|
539
|
-
console.log('');
|
|
540
|
-
console.log(` ${DIM('ID')} ${BL(String(item.id))}`);
|
|
541
|
-
console.log(` ${DIM('Stato')} ${WHITE(String(item.status))}`);
|
|
542
|
-
console.log(` ${DIM('Priorita')} ${WHITE(String(item.priority))}`);
|
|
543
|
-
console.log(` ${DIM('Origin')} ${item.origin === 'cli' ? B('CLI') : DIM(String(item.origin))}`);
|
|
544
|
-
if (item.type_tag)
|
|
545
|
-
console.log(` ${DIM('Tipo')} ${BL(String(item.type_tag))}`);
|
|
546
|
-
console.log(` ${DIM('Creato')} ${DIM(String(item.created_at))}`);
|
|
547
|
-
if (item.description) {
|
|
548
|
-
console.log('');
|
|
549
|
-
console.log(` ${DIM(String(item.description).slice(0, 300))}`);
|
|
304
|
+
else {
|
|
305
|
+
if (contentStarted)
|
|
306
|
+
process.stdout.write('\n');
|
|
307
|
+
if (result.provider)
|
|
308
|
+
console.log(` ${DIM(`(${result.provider})`)}`);
|
|
309
|
+
if (result.error)
|
|
310
|
+
console.log(` ${chalk.red('✗')} ${result.error}`);
|
|
550
311
|
}
|
|
551
312
|
}
|
|
552
313
|
catch (err) {
|
|
553
|
-
spinner.
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (!client) {
|
|
561
|
-
console.log(`\n ${chalk.red('✗')} ${DIM('Non autenticato. /auth')}\n`);
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
const spinner = ora({ text: 'Caricamento siti...', color: 'blue', indent: 2 }).start();
|
|
565
|
-
try {
|
|
566
|
-
const sites = await client.listSites();
|
|
567
|
-
spinner.stop();
|
|
568
|
-
if (!Array.isArray(sites) || sites.length === 0) {
|
|
569
|
-
console.log(`\n ${DIM('Nessun sito trovato.')}\n`);
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
// Select by number
|
|
573
|
-
if (selectNum && /^\d+$/.test(selectNum)) {
|
|
574
|
-
const idx = parseInt(selectNum, 10) - 1;
|
|
575
|
-
if (idx >= 0 && idx < sites.length) {
|
|
576
|
-
const site = sites[idx];
|
|
577
|
-
const siteId = String(site.id);
|
|
578
|
-
const domain = String(site.domain ?? site.home_url ?? siteId);
|
|
579
|
-
const { saveSiteLink } = await import('./site.js');
|
|
580
|
-
await saveSiteLink(process.cwd(), {
|
|
581
|
-
orbit_site_id: siteId,
|
|
582
|
-
platform: 'wordpress',
|
|
583
|
-
final_url: String(site.home_url ?? site.domain ?? ''),
|
|
584
|
-
});
|
|
585
|
-
console.log('');
|
|
586
|
-
console.log(` ${GREEN('✓')} ${GREEN.bold('Sito selezionato:')} ${BL(domain)}`);
|
|
587
|
-
console.log(` ${DIM('ID:')} ${DIM(siteId)}`);
|
|
314
|
+
if (spinner.isSpinning)
|
|
315
|
+
spinner.stop();
|
|
316
|
+
store.endRequest();
|
|
317
|
+
// Network-level failure → fallback to non-streaming
|
|
318
|
+
try {
|
|
319
|
+
const fallback = await sendChat(raw);
|
|
320
|
+
if (fallback.error) {
|
|
588
321
|
console.log('');
|
|
322
|
+
console.log(outputBlock('error', fallback.error));
|
|
589
323
|
}
|
|
590
324
|
else {
|
|
591
|
-
console.log(
|
|
325
|
+
console.log('');
|
|
326
|
+
for (const line of (fallback.response ?? '').split('\n')) {
|
|
327
|
+
console.log(` ${line}`);
|
|
328
|
+
}
|
|
592
329
|
}
|
|
593
|
-
return;
|
|
594
330
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
sites.forEach((site, i) => {
|
|
600
|
-
const num = String(i + 1).padStart(2);
|
|
601
|
-
const domain = WHITE(String(site.domain ?? site.home_url ?? '—').slice(0, 35).padEnd(35));
|
|
602
|
-
const status = site.status;
|
|
603
|
-
const icon = status === 'active' ? GREEN('●') : chalk.red('○');
|
|
604
|
-
const wp = DIM(String(site.wp_version ? `WP ${site.wp_version}` : '').padEnd(10));
|
|
605
|
-
const id = DIM(String(site.id ?? '').slice(0, 8));
|
|
606
|
-
console.log(` ${DIM(num)}. ${icon} ${domain} ${wp} ${id}`);
|
|
607
|
-
});
|
|
608
|
-
console.log('');
|
|
609
|
-
console.log(` ${DIM('Seleziona con: /sites <numero>')}`);
|
|
610
|
-
console.log('');
|
|
611
|
-
}
|
|
612
|
-
catch (err) {
|
|
613
|
-
spinner.fail(err instanceof Error ? err.message : String(err));
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
async function cmdAuth() {
|
|
617
|
-
const config = await loadConfig();
|
|
618
|
-
const apiUrl = config.api_url || DEFAULT_API_URL;
|
|
619
|
-
const spinner = ora({ text: 'Generazione codice...', color: 'blue', indent: 2 }).start();
|
|
620
|
-
try {
|
|
621
|
-
const deviceCode = await requestDeviceCode(apiUrl);
|
|
622
|
-
spinner.stop();
|
|
623
|
-
console.log('');
|
|
624
|
-
console.log(` ${sparkle} ${BB('Accesso con browser')}`);
|
|
625
|
-
console.log('');
|
|
626
|
-
console.log(` Apri questo URL nel browser:`);
|
|
627
|
-
console.log(` ${BL(deviceCode.verification_url)}`);
|
|
628
|
-
console.log('');
|
|
629
|
-
console.log(` Codice: ${chalk.bgRgb(37, 99, 235).white.bold(` ${deviceCode.user_code} `)}`);
|
|
630
|
-
console.log('');
|
|
631
|
-
await openBrowser(deviceCode.verification_url);
|
|
632
|
-
const pollSpinner = ora({ text: 'In attesa di autorizzazione dal browser...', color: 'blue', indent: 2 }).start();
|
|
633
|
-
const result = await pollDeviceCode(apiUrl, deviceCode.device_code);
|
|
634
|
-
await saveDeviceAuth(result);
|
|
635
|
-
pollSpinner.succeed(`${GREEN.bold(`Autenticato come ${result.user_email ?? 'utente'}`)}`);
|
|
636
|
-
console.log(` ${DIM('Token salvato in ~/.orbit/config.json')}`);
|
|
637
|
-
console.log('');
|
|
638
|
-
}
|
|
639
|
-
catch (err) {
|
|
640
|
-
spinner.stop();
|
|
641
|
-
console.log(`\n ${chalk.red('✗')} Auth fallita: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
async function cmdLogin(token) {
|
|
645
|
-
if (!token?.startsWith('orbit_mcp_')) {
|
|
646
|
-
console.log(`\n ${DIM('Uso: /login orbit_mcp_xxx')}\n`);
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
const spinner = ora({ text: 'Validazione...', color: 'blue', indent: 2 }).start();
|
|
650
|
-
const client = new OrbitClient(token, DEFAULT_API_URL);
|
|
651
|
-
const v = await client.validateToken();
|
|
652
|
-
if (v.valid) {
|
|
653
|
-
const { setToken } = await import('./config.js');
|
|
654
|
-
await setToken('default', { token, label: 'default', site_id: 'default', created_at: new Date().toISOString() });
|
|
655
|
-
spinner.succeed(GREEN.bold('Autenticazione riuscita'));
|
|
656
|
-
}
|
|
657
|
-
else {
|
|
658
|
-
spinner.fail(`Token non valido: ${v.error}`);
|
|
331
|
+
catch (e2) {
|
|
332
|
+
console.log('');
|
|
333
|
+
console.log(outputBlock('error', e2 instanceof Error ? e2.message : String(e2)));
|
|
334
|
+
}
|
|
659
335
|
}
|
|
660
336
|
console.log('');
|
|
661
337
|
}
|
|
662
|
-
async function cmdLink(siteId) {
|
|
663
|
-
if (!siteId) {
|
|
664
|
-
console.log(`\n ${DIM('Uso: /link <site_id>')}\n`);
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
const { saveSiteLink, detectPlatform } = await import('./site.js');
|
|
668
|
-
const platform = await detectPlatform(process.cwd());
|
|
669
|
-
await saveSiteLink(process.cwd(), { orbit_site_id: siteId, platform });
|
|
670
|
-
console.log(`\n ${GREEN('✓')} ${GREEN.bold('Sito collegato')} ${DIM(`(${platform})`)}\n`);
|
|
671
|
-
}
|
|
672
338
|
//# sourceMappingURL=shell.js.map
|