@orbitpanel/cli 0.5.0 → 0.6.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/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/shell-commands.d.ts +19 -0
- package/dist/lib/shell-commands.js +407 -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 +53 -0
- package/dist/lib/shell-render.js.map +1 -0
- package/dist/lib/shell.d.ts +1 -1
- package/dist/lib/shell.js +149 -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 +13 -0
- package/dist/lib/ui.js +33 -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/oclif.manifest.json +1 -1
- 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, 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,23 @@ 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 ? siteLink.orbit_site_id.slice(0, 16) : undefined),
|
|
63
|
+
sessionMinutes,
|
|
64
|
+
gitBranch: (session?.git_branch ?? gitBranch) ?? undefined,
|
|
65
|
+
runtimeMode: runtime,
|
|
66
|
+
}));
|
|
105
67
|
console.log('');
|
|
106
68
|
sep();
|
|
107
69
|
// Auto-prompt auth
|
|
@@ -125,7 +87,6 @@ export async function startShell(version) {
|
|
|
125
87
|
completer,
|
|
126
88
|
terminal: true,
|
|
127
89
|
});
|
|
128
|
-
// Ctrl+C and Ctrl+Z to exit
|
|
129
90
|
rl.on('SIGINT', () => {
|
|
130
91
|
console.log(`\n\n ${DIM('Alla prossima!')}\n`);
|
|
131
92
|
rl.close();
|
|
@@ -135,6 +96,15 @@ export async function startShell(version) {
|
|
|
135
96
|
rl.close();
|
|
136
97
|
process.exit(0);
|
|
137
98
|
});
|
|
99
|
+
// Prompt function for risk confirmations (uses the active readline)
|
|
100
|
+
const promptConfirm = (message) => {
|
|
101
|
+
return new Promise((resolve) => {
|
|
102
|
+
rl.question(message, (answer) => {
|
|
103
|
+
const a = answer.trim().toLowerCase();
|
|
104
|
+
resolve(a === 's' || a === 'si' || a === 'y' || a === 'yes');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
};
|
|
138
108
|
rl.prompt();
|
|
139
109
|
return new Promise((resolve) => {
|
|
140
110
|
rl.on('close', () => resolve());
|
|
@@ -145,528 +115,218 @@ export async function startShell(version) {
|
|
|
145
115
|
rl.prompt();
|
|
146
116
|
return;
|
|
147
117
|
}
|
|
148
|
-
//
|
|
118
|
+
// --- Command routing (thin dispatcher) ---
|
|
149
119
|
if (cmd === '/exit' || cmd === '/quit' || cmd === '/q') {
|
|
150
120
|
console.log(`\n ${DIM('Alla prossima!')}\n`);
|
|
151
121
|
rl.close();
|
|
152
122
|
return;
|
|
153
123
|
}
|
|
154
|
-
// Clear
|
|
155
124
|
if (cmd === '/clear' || cmd === '/cls') {
|
|
156
125
|
console.clear();
|
|
157
126
|
rl.prompt();
|
|
158
127
|
return;
|
|
159
128
|
}
|
|
160
|
-
// Help
|
|
161
129
|
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('');
|
|
130
|
+
printHelp();
|
|
172
131
|
rl.prompt();
|
|
173
132
|
return;
|
|
174
133
|
}
|
|
175
|
-
// Status
|
|
176
134
|
if (cmd === '/status' || cmd === '/s') {
|
|
177
135
|
await cmdStatus();
|
|
178
136
|
rl.prompt();
|
|
179
137
|
return;
|
|
180
138
|
}
|
|
181
|
-
// Doctor
|
|
182
139
|
if (cmd === '/doctor' || cmd === '/doc') {
|
|
183
140
|
await cmdDoctor();
|
|
184
141
|
rl.prompt();
|
|
185
142
|
return;
|
|
186
143
|
}
|
|
187
|
-
// Session start
|
|
188
144
|
if (cmd === '/session start' || cmd === '/ss') {
|
|
189
145
|
await cmdSessionStart();
|
|
146
|
+
await store.refresh(cwd);
|
|
190
147
|
rl.prompt();
|
|
191
148
|
return;
|
|
192
149
|
}
|
|
193
|
-
// Session end
|
|
194
150
|
if (cmd === '/session end' || cmd === '/se') {
|
|
195
151
|
await cmdSessionEnd();
|
|
152
|
+
await store.refresh(cwd);
|
|
196
153
|
rl.prompt();
|
|
197
154
|
return;
|
|
198
155
|
}
|
|
199
|
-
// Session info
|
|
200
156
|
if (cmd === '/session' || cmd === '/session info') {
|
|
201
157
|
await cmdSessionInfo();
|
|
202
158
|
rl.prompt();
|
|
203
159
|
return;
|
|
204
160
|
}
|
|
205
|
-
// Note
|
|
206
161
|
if (cmd.startsWith('/note ') || cmd.startsWith('/n ')) {
|
|
207
162
|
await cmdNoteAdd(raw.slice(raw.indexOf(' ') + 1));
|
|
208
163
|
rl.prompt();
|
|
209
164
|
return;
|
|
210
165
|
}
|
|
211
|
-
// Report
|
|
212
166
|
if (cmd === '/report' || cmd === '/r') {
|
|
213
|
-
await cmdReport();
|
|
167
|
+
await cmdReport(promptConfirm);
|
|
168
|
+
await store.refresh(cwd);
|
|
214
169
|
rl.prompt();
|
|
215
170
|
return;
|
|
216
171
|
}
|
|
217
|
-
// List interventions
|
|
218
172
|
if (cmd === '/list' || cmd === '/ls' || cmd === '/interventions') {
|
|
219
173
|
await cmdList();
|
|
220
174
|
rl.prompt();
|
|
221
175
|
return;
|
|
222
176
|
}
|
|
223
|
-
// Get intervention
|
|
224
177
|
if (cmd.startsWith('/get ') || cmd.startsWith('/g ')) {
|
|
225
178
|
await cmdGet(raw.split(' ')[1]);
|
|
226
179
|
rl.prompt();
|
|
227
180
|
return;
|
|
228
181
|
}
|
|
229
|
-
// Sites
|
|
230
182
|
if (cmd === '/sites' || cmd.startsWith('/sites ')) {
|
|
231
183
|
await cmdSites(cmd.split(' ')[1]);
|
|
184
|
+
await store.refresh(cwd);
|
|
232
185
|
rl.prompt();
|
|
233
186
|
return;
|
|
234
187
|
}
|
|
235
|
-
// Auth (device code)
|
|
236
188
|
if (cmd === '/auth') {
|
|
237
189
|
await cmdAuth();
|
|
190
|
+
await store.refresh(cwd);
|
|
238
191
|
rl.prompt();
|
|
239
192
|
return;
|
|
240
193
|
}
|
|
241
|
-
// Login (token)
|
|
242
194
|
if (cmd.startsWith('/login ')) {
|
|
243
195
|
await cmdLogin(raw.split(' ')[1]);
|
|
196
|
+
await store.refresh(cwd);
|
|
244
197
|
rl.prompt();
|
|
245
198
|
return;
|
|
246
199
|
}
|
|
247
|
-
// Link
|
|
248
200
|
if (cmd.startsWith('/link ')) {
|
|
249
201
|
await cmdLink(raw.split(' ')[1]);
|
|
202
|
+
await store.refresh(cwd);
|
|
250
203
|
rl.prompt();
|
|
251
204
|
return;
|
|
252
205
|
}
|
|
253
|
-
// Unknown slash command
|
|
254
206
|
if (cmd.startsWith('/')) {
|
|
255
207
|
console.log(`\n ${DIM('Comando sconosciuto:')} ${WHITE(cmd)} ${DIM('— digita /help')}\n`);
|
|
256
208
|
rl.prompt();
|
|
257
209
|
return;
|
|
258
210
|
}
|
|
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
|
-
}
|
|
211
|
+
// Free text → AI chat (streaming with state machine + fallback)
|
|
212
|
+
await handleAIChat(raw, store);
|
|
282
213
|
rl.prompt();
|
|
283
214
|
});
|
|
284
215
|
});
|
|
285
216
|
}
|
|
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);
|
|
217
|
+
// --- Help output ---
|
|
218
|
+
function printHelp() {
|
|
398
219
|
console.log('');
|
|
399
|
-
console.log(` ${
|
|
400
|
-
console.log(` ${DIM('Invia il report con: /report')}`);
|
|
220
|
+
console.log(` ${sparkle} ${BB('Comandi disponibili')}`);
|
|
401
221
|
console.log('');
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
if (!siteLink) {
|
|
406
|
-
console.log(`\n ${DIM('Nessun sito collegato')}\n`);
|
|
407
|
-
return;
|
|
222
|
+
for (const c of SLASH_COMMANDS) {
|
|
223
|
+
const alias = c.alias ? DIM(` ${c.alias}`) : '';
|
|
224
|
+
console.log(` ${WHITE(c.cmd.padEnd(18))}${alias.padEnd(12)}${DIM(c.desc)}`);
|
|
408
225
|
}
|
|
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);
|
|
415
|
-
console.log('');
|
|
416
|
-
console.log(` ${sparkle} ${BB('Sessione attiva')}`);
|
|
417
226
|
console.log('');
|
|
418
|
-
console.log(`
|
|
419
|
-
console.log(` ${DIM('Durata')} ${WHITE(`${elapsed} min`)}`);
|
|
420
|
-
if (session.git_branch)
|
|
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)}`);
|
|
227
|
+
console.log(` ${DIM('Oppure scrivi in linguaggio naturale per parlare con Orbit AI.')}`);
|
|
425
228
|
console.log('');
|
|
426
229
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
console.log(`\n ${chalk.red('✗')} ${DIM('Nessun sito collegato')}\n`);
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
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');
|
|
230
|
+
// --- AI Chat with streaming + state machine ---
|
|
231
|
+
async function handleAIChat(raw, store) {
|
|
232
|
+
const { config, siteLink } = store.getState();
|
|
439
233
|
console.log('');
|
|
440
|
-
console.log(` ${
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
444
|
-
async function cmdReport() {
|
|
445
|
-
const config = await loadConfig();
|
|
446
|
-
const client = getClient(config);
|
|
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();
|
|
234
|
+
console.log(` ${sparkle} ${BB('Orbit AI')}`);
|
|
235
|
+
const spinner = ora({ text: DIM('Recupero contesto...'), color: 'blue', indent: 2 }).start();
|
|
236
|
+
const sm = new StreamStateMachine();
|
|
472
237
|
try {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
238
|
+
store.startRequest();
|
|
239
|
+
const result = await sendChatStream(raw, (event) => {
|
|
240
|
+
// State machine gates event processing
|
|
241
|
+
if (!sm.transition(event.type))
|
|
242
|
+
return;
|
|
243
|
+
switch (event.type) {
|
|
244
|
+
case 'status': {
|
|
245
|
+
const msg = (event.data.message ?? event.data.phase ?? '');
|
|
246
|
+
if (spinner.isSpinning)
|
|
247
|
+
spinner.text = DIM(msg);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case 'thinking': {
|
|
251
|
+
if (spinner.isSpinning)
|
|
252
|
+
spinner.text = DIM('Analisi in corso...');
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
case 'delta': {
|
|
256
|
+
if (spinner.isSpinning) {
|
|
257
|
+
spinner.stop();
|
|
258
|
+
console.log('');
|
|
259
|
+
process.stdout.write(' ');
|
|
260
|
+
}
|
|
261
|
+
const chunk = event.data.content;
|
|
262
|
+
process.stdout.write(chunk.replaceAll('\n', '\n '));
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
case 'error': {
|
|
266
|
+
if (spinner.isSpinning)
|
|
267
|
+
spinner.stop();
|
|
268
|
+
console.log(`\n ${chalk.red('✗')} ${event.data.message}`);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case 'done':
|
|
272
|
+
if (spinner.isSpinning)
|
|
273
|
+
spinner.stop();
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}, config, siteLink);
|
|
277
|
+
if (spinner.isSpinning)
|
|
278
|
+
spinner.stop();
|
|
279
|
+
store.endRequest();
|
|
280
|
+
const contentStarted = sm.current === 'generating' || sm.current === 'done';
|
|
281
|
+
// Fallback: stream failed before any content → try non-streaming
|
|
282
|
+
if (result.error && !contentStarted) {
|
|
283
|
+
const fallback = await sendChat(raw);
|
|
284
|
+
if (fallback.error) {
|
|
285
|
+
console.log('');
|
|
286
|
+
console.log(outputBlock('error', fallback.error));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
console.log('');
|
|
290
|
+
for (const line of (fallback.response ?? '').split('\n')) {
|
|
291
|
+
console.log(` ${line}`);
|
|
292
|
+
}
|
|
293
|
+
if (fallback.provider_used) {
|
|
294
|
+
console.log(`\n ${DIM(`(${fallback.provider_used})`)}`);
|
|
295
|
+
}
|
|
512
296
|
}
|
|
513
|
-
console.log('');
|
|
514
|
-
console.log(` ${DIM(`${result.total} interventi`)}`);
|
|
515
297
|
}
|
|
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))}`);
|
|
298
|
+
else {
|
|
299
|
+
if (contentStarted)
|
|
300
|
+
process.stdout.write('\n');
|
|
301
|
+
if (result.provider)
|
|
302
|
+
console.log(` ${DIM(`(${result.provider})`)}`);
|
|
303
|
+
if (result.error)
|
|
304
|
+
console.log(` ${chalk.red('✗')} ${result.error}`);
|
|
550
305
|
}
|
|
551
306
|
}
|
|
552
307
|
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)}`);
|
|
308
|
+
if (spinner.isSpinning)
|
|
309
|
+
spinner.stop();
|
|
310
|
+
store.endRequest();
|
|
311
|
+
// Network-level failure → fallback to non-streaming
|
|
312
|
+
try {
|
|
313
|
+
const fallback = await sendChat(raw);
|
|
314
|
+
if (fallback.error) {
|
|
588
315
|
console.log('');
|
|
316
|
+
console.log(outputBlock('error', fallback.error));
|
|
589
317
|
}
|
|
590
318
|
else {
|
|
591
|
-
console.log(
|
|
319
|
+
console.log('');
|
|
320
|
+
for (const line of (fallback.response ?? '').split('\n')) {
|
|
321
|
+
console.log(` ${line}`);
|
|
322
|
+
}
|
|
592
323
|
}
|
|
593
|
-
return;
|
|
594
324
|
}
|
|
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}`);
|
|
325
|
+
catch (e2) {
|
|
326
|
+
console.log('');
|
|
327
|
+
console.log(outputBlock('error', e2 instanceof Error ? e2.message : String(e2)));
|
|
328
|
+
}
|
|
659
329
|
}
|
|
660
330
|
console.log('');
|
|
661
331
|
}
|
|
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
332
|
//# sourceMappingURL=shell.js.map
|