plazbot-cli 0.4.1 → 0.5.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 +67 -111
- package/dist/cli.js +26 -11
- package/dist/commands/agent/ai-config.js +264 -63
- package/dist/commands/agent/chat.js +16 -11
- package/dist/commands/agent/copy.js +49 -13
- package/dist/commands/agent/create.js +62 -17
- package/dist/commands/agent/delete.js +50 -39
- package/dist/commands/agent/enable-widget.js +47 -27
- package/dist/commands/agent/export.js +179 -88
- package/dist/commands/agent/files.js +155 -73
- package/dist/commands/agent/get.js +24 -16
- package/dist/commands/agent/index.js +12 -10
- package/dist/commands/agent/list.js +20 -11
- package/dist/commands/agent/monitor.js +101 -53
- package/dist/commands/agent/on-message.js +55 -32
- package/dist/commands/agent/set.js +230 -82
- package/dist/commands/agent/templates.js +259 -156
- package/dist/commands/agent/tools.js +356 -123
- package/dist/commands/agent/update.js +69 -28
- package/dist/commands/agent/validate.js +72 -40
- package/dist/commands/agent/wizard.js +240 -329
- package/dist/commands/auth/browser-login.js +37 -31
- package/dist/commands/auth/login.js +49 -45
- package/dist/commands/auth/logout.js +20 -11
- package/dist/commands/auth/status.js +36 -12
- package/dist/commands/auth/workspace.js +61 -27
- package/dist/commands/portal/add-agent.js +106 -45
- package/dist/commands/portal/add-link.js +25 -28
- package/dist/commands/portal/clear-links.js +39 -19
- package/dist/commands/portal/create.js +66 -37
- package/dist/commands/portal/delete.js +40 -45
- package/dist/commands/portal/get.js +21 -54
- package/dist/commands/portal/index.js +12 -12
- package/dist/commands/portal/list.js +34 -52
- package/dist/commands/portal/shared.js +92 -0
- package/dist/commands/portal/update.js +100 -70
- package/dist/commands/whatsapp/broadcast.js +169 -77
- package/dist/commands/whatsapp/channels.js +167 -65
- package/dist/commands/whatsapp/chat.js +45 -46
- package/dist/commands/whatsapp/connect-flow.js +16 -15
- package/dist/commands/whatsapp/connect.js +36 -21
- package/dist/commands/whatsapp/delete-webhook.js +42 -25
- package/dist/commands/whatsapp/index.js +15 -16
- package/dist/commands/whatsapp/register-webhook.js +43 -26
- package/dist/commands/whatsapp/send-template.js +62 -61
- package/dist/commands/whatsapp/send.js +46 -33
- package/dist/commands/whatsapp/setup.js +62 -52
- package/dist/commands/whatsapp/shared.js +75 -0
- package/dist/commands/whatsapp/widget.js +82 -45
- package/dist/commands/workers/deploy.js +80 -58
- package/dist/commands/workers/index.js +8 -8
- package/dist/commands/workers/list.js +42 -20
- package/dist/commands/workers/logs.js +139 -36
- package/dist/commands/workers/remove.js +45 -47
- package/dist/commands/workers/secret.js +159 -83
- package/dist/commands/workers/test.js +108 -60
- package/dist/schemas/agent.config.schema.json +159 -8
- package/dist/studio/api/sseClient.js +1 -1
- package/dist/studio/api/studioApi.js +8 -0
- package/dist/studio/components/App.js +12 -0
- package/dist/studio/components/Footer.js +1 -1
- package/dist/studio/index.js +77 -14
- package/dist/studio/runOneShot.js +62 -23
- package/dist/studio/runRepl.js +5 -5
- package/dist/studio/slash/handlers.js +11 -8
- package/dist/studio/state/store.js +1 -1
- package/dist/utils/agent-errors.js +14 -11
- package/dist/utils/agent-patch.js +60 -0
- package/dist/utils/banner.js +9 -5
- package/dist/utils/confirm.js +31 -0
- package/dist/utils/credentials.js +13 -3
- package/dist/utils/logger.js +13 -10
- package/dist/utils/output.js +78 -0
- package/dist/utils/phone.js +22 -0
- package/dist/utils/ui.js +14 -11
- package/package.json +2 -2
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
"name": { "type": "string" },
|
|
8
8
|
"description": { "type": "string" },
|
|
9
9
|
"prompt": { "type": "string" },
|
|
10
|
+
"imagePrompt": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Instruccion usada cuando llega una imagen/archivo sin texto del usuario (caption vacio)."
|
|
13
|
+
},
|
|
10
14
|
"zone": {
|
|
11
15
|
"type": "string",
|
|
12
16
|
"enum": ["LA", "EU"]
|
|
@@ -43,15 +47,15 @@
|
|
|
43
47
|
"personality": { "type": "string" },
|
|
44
48
|
"objective": { "type": "string" },
|
|
45
49
|
"language": { "type": "string" },
|
|
46
|
-
"emojis": { "type": "boolean" },
|
|
50
|
+
"emojis": { "type": ["boolean", "null"] },
|
|
47
51
|
"preferredFormat": { "type": "string" },
|
|
48
52
|
"maxWords": { "type": "integer" },
|
|
49
53
|
"avoidTopics": {
|
|
50
54
|
"type": "array",
|
|
51
55
|
"items": { "type": "string" }
|
|
52
56
|
},
|
|
53
|
-
"respondOnlyIfKnows": { "type": "boolean" },
|
|
54
|
-
"maintainToneBetweenMessages": { "type": "boolean" },
|
|
57
|
+
"respondOnlyIfKnows": { "type": ["boolean", "null"] },
|
|
58
|
+
"maintainToneBetweenMessages": { "type": ["boolean", "null"] },
|
|
55
59
|
"greeting": { "type": "string" }
|
|
56
60
|
}
|
|
57
61
|
},
|
|
@@ -164,6 +168,17 @@
|
|
|
164
168
|
"default": false,
|
|
165
169
|
"description": "Si true, divide la respuesta del agente IA en varios mensajes (split por doble salto de linea)."
|
|
166
170
|
},
|
|
171
|
+
"advisorNameModeWidget": {
|
|
172
|
+
"type": ["string", "null"],
|
|
173
|
+
"enum": ["agent", "custom", null],
|
|
174
|
+
"default": "agent",
|
|
175
|
+
"description": "Nombre que ve el cliente sobre los mensajes del asesor humano en el widget: agent (nombre del asesor asignado) o custom (advisorNameWidget)."
|
|
176
|
+
},
|
|
177
|
+
"advisorNameWidget": {
|
|
178
|
+
"type": ["string", "null"],
|
|
179
|
+
"maxLength": 60,
|
|
180
|
+
"description": "Nombre personalizado del asesor humano en el widget (solo si advisorNameModeWidget = custom). Ej: Equipo de Soporte."
|
|
181
|
+
},
|
|
167
182
|
"enableAvatarWidget": { "type": "boolean" },
|
|
168
183
|
"avatarGender": {
|
|
169
184
|
"type": "string",
|
|
@@ -202,14 +217,24 @@
|
|
|
202
217
|
"properties": {
|
|
203
218
|
"enabled": { "type": "boolean" },
|
|
204
219
|
"phoneNumber": { "type": "string", "description": "DID phone number, e.g., +573151234567" },
|
|
205
|
-
"ttsProvider": { "type": "string", "enum": ["google", "openai"] },
|
|
220
|
+
"ttsProvider": { "type": "string", "enum": ["google", "openai", "elevenlabs"] },
|
|
206
221
|
"ttsVoiceId": { "type": "string", "description": "TTS voice ID, e.g., nova, es-US-Journey-D" },
|
|
207
222
|
"ttsLanguage": { "type": "string", "description": "Language code, e.g., es-CO, es-MX" },
|
|
208
223
|
"greeting": { "type": "string", "description": "Initial greeting message for TTS" },
|
|
209
224
|
"maxCallDurationSeconds": { "type": "integer", "minimum": 60, "maximum": 1800, "default": 300 },
|
|
210
|
-
"silenceTimeoutMs": { "type": "integer", "minimum":
|
|
225
|
+
"silenceTimeoutMs": { "type": "integer", "minimum": 15000, "maximum": 60000, "default": 15000 },
|
|
211
226
|
"transferExtension": { "type": "string", "description": "SIP extension for human transfer" },
|
|
212
|
-
"voiceType": { "type": "string", "enum": ["compact", "complete"], "description": "compact=fast no tools, complete=full toolcalling+RAG" }
|
|
227
|
+
"voiceType": { "type": "string", "enum": ["compact", "complete"], "description": "compact=fast no tools, complete=full toolcalling+RAG" },
|
|
228
|
+
"elevenlabsApiKey": { "type": "string", "description": "ElevenLabs API key (BYOK)" },
|
|
229
|
+
"elevenlabsVoiceId": { "type": "string", "description": "ElevenLabs voice ID" },
|
|
230
|
+
"elevenlabsVoiceName": { "type": "string", "description": "ElevenLabs voice display name" },
|
|
231
|
+
"elevenlabsModelId": { "type": "string", "description": "ElevenLabs model ID (e.g., eleven_multilingual_v2)" },
|
|
232
|
+
"elevenlabsStability": { "type": "number", "minimum": 0, "maximum": 100, "description": "Voice stability 0-100" },
|
|
233
|
+
"elevenlabsSimilarity": { "type": "number", "minimum": 0, "maximum": 100, "description": "Voice similarity 0-100" },
|
|
234
|
+
"elevenlabsStyle": { "type": "number", "minimum": 0, "maximum": 100, "description": "Style exaggeration 0-100" },
|
|
235
|
+
"elevenlabsSpeakerBoost": { "type": "boolean", "description": "Enable speaker boost" },
|
|
236
|
+
"languageInstruction": { "type": "string", "description": "Custom language instruction appended to the LLM prompt (if empty, the PBX generates an automatic one based on ttsLanguage)" },
|
|
237
|
+
"fillersEnabled": { "type": "boolean", "description": "Enable filler phrases (e.g. 'Dejame revisar') while the AI processes the response", "default": true }
|
|
213
238
|
}
|
|
214
239
|
},
|
|
215
240
|
"useToolCalling": {
|
|
@@ -280,6 +305,16 @@
|
|
|
280
305
|
}
|
|
281
306
|
}
|
|
282
307
|
},
|
|
308
|
+
"enableContingency": {
|
|
309
|
+
"type": "boolean",
|
|
310
|
+
"default": false,
|
|
311
|
+
"description": "If true and multiple aiProviders exist, retries with the remaining providers when the default provider fails (Tool Calling orchestrator only)"
|
|
312
|
+
},
|
|
313
|
+
"enableAudioInput": {
|
|
314
|
+
"type": "boolean",
|
|
315
|
+
"default": false,
|
|
316
|
+
"description": "If true, transcribes received voice audios (Whisper) and the agent replies as text (Tool Calling orchestrator only)"
|
|
317
|
+
},
|
|
283
318
|
"channels": {
|
|
284
319
|
"type": "array",
|
|
285
320
|
"items": {
|
|
@@ -301,7 +336,7 @@
|
|
|
301
336
|
"intent": { "type": "string" },
|
|
302
337
|
"reference": { "type": "string" },
|
|
303
338
|
"enabled": { "type": "boolean", "default": true },
|
|
304
|
-
"method": { "type": "string", "enum": ["GET", "POST"] },
|
|
339
|
+
"method": { "type": "string", "enum": ["GET", "POST", "PUT", "PATCH", "DELETE"] },
|
|
305
340
|
"tags": {
|
|
306
341
|
"type": "array",
|
|
307
342
|
"items": { "type": "string" }
|
|
@@ -334,6 +369,21 @@
|
|
|
334
369
|
"type": { "type": "string" }
|
|
335
370
|
}
|
|
336
371
|
}
|
|
372
|
+
},
|
|
373
|
+
"buttons": {
|
|
374
|
+
"type": "array",
|
|
375
|
+
"maxItems": 3,
|
|
376
|
+
"items": {
|
|
377
|
+
"type": "object",
|
|
378
|
+
"required": ["text"],
|
|
379
|
+
"properties": {
|
|
380
|
+
"text": { "type": "string", "minLength": 1, "maxLength": 20 }
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
"contactField": {
|
|
385
|
+
"type": "string",
|
|
386
|
+
"enum": ["name", "lastname", "email", "phoneNumber", "documentNumber", "businessName"]
|
|
337
387
|
}
|
|
338
388
|
}
|
|
339
389
|
}
|
|
@@ -508,6 +558,21 @@
|
|
|
508
558
|
"type": { "type": "string" }
|
|
509
559
|
}
|
|
510
560
|
}
|
|
561
|
+
},
|
|
562
|
+
"buttons": {
|
|
563
|
+
"type": "array",
|
|
564
|
+
"maxItems": 3,
|
|
565
|
+
"items": {
|
|
566
|
+
"type": "object",
|
|
567
|
+
"required": ["text"],
|
|
568
|
+
"properties": {
|
|
569
|
+
"text": { "type": "string", "minLength": 1, "maxLength": 20 }
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
"contactField": {
|
|
574
|
+
"type": "string",
|
|
575
|
+
"enum": ["name", "lastname", "email", "phoneNumber", "documentNumber", "businessName"]
|
|
511
576
|
}
|
|
512
577
|
}
|
|
513
578
|
}
|
|
@@ -541,10 +606,96 @@
|
|
|
541
606
|
"action.stage.remove",
|
|
542
607
|
"action.tag.remove",
|
|
543
608
|
"action.segmentation.remove",
|
|
544
|
-
"action.worker"
|
|
609
|
+
"action.worker",
|
|
610
|
+
"action.transfer.voip",
|
|
611
|
+
"action.transfer.voip.external",
|
|
612
|
+
"action.send.image",
|
|
613
|
+
"action.send.file",
|
|
614
|
+
"action.send.template",
|
|
615
|
+
"action.send.template.voip",
|
|
616
|
+
"action.ai.summary",
|
|
617
|
+
"action.agent.ask"
|
|
545
618
|
]
|
|
546
619
|
},
|
|
547
620
|
"value": { "type": "string" },
|
|
621
|
+
"queryTemplate": {
|
|
622
|
+
"type": "string",
|
|
623
|
+
"description": "Pregunta a enviar al agente consultado (solo action.agent.ask; el agentId destino va en 'value'). Soporta {{variables}}. Vacio = el LLM formula la pregunta con el parametro 'query'."
|
|
624
|
+
},
|
|
625
|
+
"outputVariable": {
|
|
626
|
+
"type": "string",
|
|
627
|
+
"description": "Codigo de variable del contacto donde guardar la respuesta del agente consultado (solo action.agent.ask, opcional)."
|
|
628
|
+
},
|
|
629
|
+
"assignMode": {
|
|
630
|
+
"type": "string",
|
|
631
|
+
"enum": ["user", "workload", "random", "workgroup"],
|
|
632
|
+
"description": "Solo action.asign. user (default): email fijo en 'value'. workload: miembro con menos chats abiertos. random: al azar. workgroup: miembro de workgroupId por carga de trabajo. En modos automaticos, 'value' (email) es el respaldo si no hay nadie disponible."
|
|
633
|
+
},
|
|
634
|
+
"workgroupId": {
|
|
635
|
+
"type": "string",
|
|
636
|
+
"description": "Solo action.asign con assignMode workgroup: id del grupo de trabajo del workspace."
|
|
637
|
+
},
|
|
638
|
+
"onlyOnlineAgents": {
|
|
639
|
+
"type": "boolean",
|
|
640
|
+
"description": "Solo action.asign en modos automaticos: considerar solo miembros conectados."
|
|
641
|
+
},
|
|
642
|
+
"sipServer": {
|
|
643
|
+
"type": "string",
|
|
644
|
+
"description": "Servidor/Proxy SIP del partner (solo action.transfer.voip.external). Ej: trunks.vpbx.me. La extension destino se guarda en 'value'."
|
|
645
|
+
},
|
|
646
|
+
"sipPort": {
|
|
647
|
+
"type": "integer",
|
|
648
|
+
"minimum": 1,
|
|
649
|
+
"maximum": 65535,
|
|
650
|
+
"default": 5060,
|
|
651
|
+
"description": "Puerto SIP del proxy (solo action.transfer.voip.external). Default: 5060"
|
|
652
|
+
},
|
|
653
|
+
"sipUser": {
|
|
654
|
+
"type": "string",
|
|
655
|
+
"description": "Usuario SIP para autenticacion saliente (solo action.transfer.voip.external)"
|
|
656
|
+
},
|
|
657
|
+
"sipPassword": {
|
|
658
|
+
"type": "string",
|
|
659
|
+
"description": "Clave SIP para autenticacion saliente (solo action.transfer.voip.external)"
|
|
660
|
+
},
|
|
661
|
+
"sipDomain": {
|
|
662
|
+
"type": "string",
|
|
663
|
+
"description": "From-domain opcional para PBX multi-tenant que lo exijan (solo action.transfer.voip.external)"
|
|
664
|
+
},
|
|
665
|
+
"fileName": {
|
|
666
|
+
"type": "string",
|
|
667
|
+
"description": "Nombre original del archivo subido a S3 (solo para action.send.image / action.send.file). La URL publica de S3 se guarda en 'value'."
|
|
668
|
+
},
|
|
669
|
+
"mimeType": {
|
|
670
|
+
"type": "string",
|
|
671
|
+
"description": "Content-Type del archivo subido (solo para action.send.image / action.send.file). Ej: image/png, application/pdf."
|
|
672
|
+
},
|
|
673
|
+
"countryPrefix": {
|
|
674
|
+
"type": "string",
|
|
675
|
+
"description": "Prefijo de pais obligatorio para action.send.template y action.send.template.voip (ej: +34 o 34). Se antepone al numero destino si este no lo tiene. El templateId se guarda en 'value'."
|
|
676
|
+
},
|
|
677
|
+
"phoneNumber": {
|
|
678
|
+
"type": "string",
|
|
679
|
+
"description": "Numero destino de la alerta SIN prefijo de pais (solo action.send.template). La plantilla se envia a este numero (alerta a otra persona), NO al contacto de la conversacion. Para varios numeros, crear varias acciones."
|
|
680
|
+
},
|
|
681
|
+
"waSenderId": {
|
|
682
|
+
"type": "string",
|
|
683
|
+
"description": "codeCellphoneNumberId de la integracion WhatsApp del workspace desde la que se envia la plantilla (obligatorio en action.send.template y action.send.template.voip)."
|
|
684
|
+
},
|
|
685
|
+
"templateVariables": {
|
|
686
|
+
"type": "array",
|
|
687
|
+
"description": "Valores de las variables {{n}} de la plantilla (action.send.template / .voip). Cada 'value' admite texto fijo y placeholders: {{contact.x}}, {{system.x}}, {{custom.x}} o {{campoRequerido}} de la accion.",
|
|
688
|
+
"items": {
|
|
689
|
+
"type": "object",
|
|
690
|
+
"required": ["componentType", "variable"],
|
|
691
|
+
"properties": {
|
|
692
|
+
"componentType": { "type": "string", "enum": ["HEADER", "BODY"] },
|
|
693
|
+
"variable": { "type": "string" },
|
|
694
|
+
"value": { "type": "string" }
|
|
695
|
+
},
|
|
696
|
+
"additionalProperties": false
|
|
697
|
+
}
|
|
698
|
+
},
|
|
548
699
|
"durationMinutes": {
|
|
549
700
|
"type": "integer",
|
|
550
701
|
"minimum": 5,
|
|
@@ -33,7 +33,7 @@ export async function streamStudio(opts, request, onChunk, signal) {
|
|
|
33
33
|
throw new StudioHttpError(res.status, res.statusText, text);
|
|
34
34
|
}
|
|
35
35
|
if (!res.body) {
|
|
36
|
-
throw new Error('
|
|
36
|
+
throw new Error('Empty response body from POST /api/agent/studio.');
|
|
37
37
|
}
|
|
38
38
|
const reader = res.body.getReader();
|
|
39
39
|
const decoder = new TextDecoder();
|
|
@@ -23,3 +23,11 @@ export function buildStudioHeaders(opts) {
|
|
|
23
23
|
headers['x-user-id'] = opts.userId;
|
|
24
24
|
return headers;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* userId sent to Studio (optional; the backend uses it to attribute created/saved
|
|
28
|
+
* agents). Stored login sessions carry it; env-var credentials
|
|
29
|
+
* (PLAZBOT_API_KEY/PLAZBOT_WORKSPACE_ID) don't, so PLAZBOT_USER_ID fills the gap.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveStudioUserId(creds) {
|
|
32
|
+
return creds.userId || process.env.PLAZBOT_USER_ID?.trim() || creds.email || undefined;
|
|
33
|
+
}
|
|
@@ -161,6 +161,13 @@ export function App({ version, stream, initialAgentId, dev, supportMode }) {
|
|
|
161
161
|
agentId: useStudioStore.getState().agentId,
|
|
162
162
|
agentConfig: useStudioStore.getState().agentConfig,
|
|
163
163
|
}, onChunk, controller.signal);
|
|
164
|
+
if (controller.signal.aborted) {
|
|
165
|
+
// Esc: the SSE client returns quietly on abort. Mark running steps and
|
|
166
|
+
// don't compact a half-written summary.
|
|
167
|
+
useStudioStore.getState().failAllRunningSteps('Cancelled');
|
|
168
|
+
useStudioStore.getState().setPendingCompact(false);
|
|
169
|
+
useStudioStore.getState().pushSyntheticAssistant('Response cancelled.');
|
|
170
|
+
}
|
|
164
171
|
}
|
|
165
172
|
catch (err) {
|
|
166
173
|
const msg = formatError(err, dev);
|
|
@@ -215,6 +222,11 @@ function formatError(err, dev) {
|
|
|
215
222
|
if (err instanceof Error) {
|
|
216
223
|
if (err.name === 'AbortError')
|
|
217
224
|
return '✖ Stream cancelled.';
|
|
225
|
+
if (err.name === 'TypeError' && /fetch failed/i.test(err.message)) {
|
|
226
|
+
return dev
|
|
227
|
+
? '✖ Could not reach the local backend (http://localhost:5090). Is it running?'
|
|
228
|
+
: '✖ Could not reach the Plazbot API. Check your internet connection.';
|
|
229
|
+
}
|
|
218
230
|
return `✖ ${err.message}`;
|
|
219
231
|
}
|
|
220
232
|
return '✖ Unknown error';
|
|
@@ -7,5 +7,5 @@ function fmt(n) {
|
|
|
7
7
|
return String(n);
|
|
8
8
|
}
|
|
9
9
|
export function Footer({ inputTokens, outputTokens, streaming }) {
|
|
10
|
-
return (_jsxs(Box, { paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "tokens: " }), _jsx(Text, { children: fmt(inputTokens) }), _jsx(Text, { dimColor: true, children: " in / " }), _jsx(Text, { children: fmt(outputTokens) }), _jsx(Text, { dimColor: true, children: " out" }), streaming ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { color: "yellow", children: " streaming\u2026" })] })) : null] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children:
|
|
10
|
+
return (_jsxs(Box, { paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "tokens: " }), _jsx(Text, { children: fmt(inputTokens) }), _jsx(Text, { dimColor: true, children: " in / " }), _jsx(Text, { children: fmt(outputTokens) }), _jsx(Text, { dimColor: true, children: " out" }), streaming ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { color: "yellow", children: " streaming\u2026" })] })) : null] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: streaming ? 'Esc cancel · Ctrl+C exit' : '/help · ↑↓ history · Tab/→ accept suggestion · Esc cancel · Ctrl+C exit' }) })] }));
|
|
11
11
|
}
|
package/dist/studio/index.js
CHANGED
|
@@ -1,42 +1,105 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
|
+
import { jsonOption, fail, CliError, setJsonMode } from '../utils/output.js';
|
|
3
|
+
import { addExamples, addNotes } from '../utils/help.js';
|
|
4
|
+
const WORKSPACE_DESC = 'Act on another workspace (support / white-label partner mode)';
|
|
5
|
+
/** Hidden legacy alias of `--workspace` (kept for backwards compatibility). */
|
|
6
|
+
function legacyWorkspaceIdOption() {
|
|
7
|
+
return new Option('--workspace-id <id>', WORKSPACE_DESC).hideHelp();
|
|
8
|
+
}
|
|
2
9
|
export const studioCommand = new Command('studio')
|
|
3
|
-
.description('
|
|
10
|
+
.description('Open Plazbot Studio, an interactive assistant to create, diagnose and manage AI agents')
|
|
11
|
+
// Options written after `ask` belong to `ask` (otherwise `studio` would swallow
|
|
12
|
+
// `--json`, `--dev`, `-a`, `-w` and the subcommand would never see them).
|
|
13
|
+
.enablePositionalOptions()
|
|
4
14
|
.option('--dev', 'Target the local backend (http://localhost:5090)', false)
|
|
5
|
-
.option('-a, --agent-id <id>', 'Preload an agent on startup')
|
|
6
|
-
.option('-w, --workspace
|
|
7
|
-
.
|
|
15
|
+
.option('-a, --agent-id <id>', 'Preload an agent as context on startup')
|
|
16
|
+
.option('-w, --workspace <id>', WORKSPACE_DESC)
|
|
17
|
+
.addOption(legacyWorkspaceIdOption())
|
|
18
|
+
.option('-m, --message <text>', 'Alias of `studio ask`: run a one-shot prompt and exit')
|
|
19
|
+
.addOption(jsonOption('With -m: print the SSE chunks as NDJSON (one JSON object per line)'))
|
|
8
20
|
.action(async (opts) => {
|
|
9
|
-
|
|
21
|
+
const workspaceOverride = opts.workspace ?? opts.workspaceId;
|
|
22
|
+
if (opts.message !== undefined) {
|
|
23
|
+
if (!opts.message.trim()) {
|
|
24
|
+
fail(new CliError('The message is empty.', 'missing_message', 'Pass a prompt: plazbot studio -m "List my agents"'));
|
|
25
|
+
}
|
|
10
26
|
const { runOneShot } = await import('./runOneShot.js');
|
|
11
27
|
await runOneShot({
|
|
12
28
|
dev: opts.dev,
|
|
13
29
|
agentId: opts.agentId,
|
|
14
30
|
message: opts.message,
|
|
15
|
-
|
|
31
|
+
json: opts.json,
|
|
32
|
+
workspaceOverride,
|
|
16
33
|
});
|
|
17
34
|
return;
|
|
18
35
|
}
|
|
36
|
+
if (opts.json) {
|
|
37
|
+
fail(new CliError('--json only works with a one-shot prompt.', 'json_requires_message', 'Use: plazbot studio ask "List my agents" --json'));
|
|
38
|
+
}
|
|
39
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
40
|
+
fail(new CliError('The Studio REPL needs an interactive terminal.', 'not_interactive', 'Use a one-shot prompt instead: plazbot studio ask "List my agents"'));
|
|
41
|
+
}
|
|
19
42
|
const { runRepl } = await import('./runRepl.js');
|
|
20
43
|
await runRepl({
|
|
21
44
|
dev: opts.dev,
|
|
22
45
|
agentId: opts.agentId,
|
|
23
|
-
workspaceOverride
|
|
46
|
+
workspaceOverride,
|
|
24
47
|
});
|
|
25
48
|
});
|
|
26
|
-
studioCommand
|
|
49
|
+
addExamples(studioCommand, [
|
|
50
|
+
{ description: 'Open the interactive Studio', command: 'plazbot studio' },
|
|
51
|
+
{ description: 'Open Studio with an agent already loaded', command: 'plazbot studio -a age_AbcDef123' },
|
|
52
|
+
{ description: 'Run a single prompt and exit (same as `studio ask`)', command: 'plazbot studio -m "Diagnose this agent" -a age_AbcDef123' },
|
|
53
|
+
{ description: 'Act on a customer workspace (support / partner mode)', command: 'plazbot studio -w wok_AbcDef123' },
|
|
54
|
+
]);
|
|
55
|
+
addNotes(studioCommand, [
|
|
56
|
+
'Requires a session: run `plazbot login` (or set PLAZBOT_API_KEY and PLAZBOT_WORKSPACE_ID).',
|
|
57
|
+
'Keys: Enter send · ↑/↓ history · Tab or → accept the gray suggestion · Esc cancel the response · Ctrl+C exit.',
|
|
58
|
+
'Type /help inside Studio to list slash commands (/agents, /load, /diagnose, /save, /export, /clear, /compact...).',
|
|
59
|
+
'In support mode (-w) every action is tied to your user and visible in audit trails.',
|
|
60
|
+
]);
|
|
61
|
+
const askCommand = studioCommand
|
|
27
62
|
.command('ask <message>')
|
|
28
|
-
.description('Run a one-shot
|
|
63
|
+
.description('Run a one-shot Studio prompt without opening the interactive UI')
|
|
29
64
|
.option('--dev', 'Target the local backend (http://localhost:5090)', false)
|
|
30
65
|
.option('-a, --agent-id <id>', 'Agent to use as context')
|
|
31
|
-
.option('-w, --workspace
|
|
32
|
-
.
|
|
33
|
-
.
|
|
66
|
+
.option('-w, --workspace <id>', WORKSPACE_DESC)
|
|
67
|
+
.addOption(legacyWorkspaceIdOption())
|
|
68
|
+
.addOption(jsonOption('Print the SSE chunks as NDJSON (one JSON object per line)'))
|
|
69
|
+
.action(async (message, askOpts, cmd) => {
|
|
70
|
+
// Also honor options written before `ask` (`plazbot studio --dev ask "..."`).
|
|
71
|
+
const parent = (cmd.parent?.opts() ?? {});
|
|
72
|
+
const opts = {
|
|
73
|
+
dev: askOpts.dev || !!parent.dev,
|
|
74
|
+
json: askOpts.json || !!parent.json,
|
|
75
|
+
agentId: askOpts.agentId ?? parent.agentId,
|
|
76
|
+
workspace: askOpts.workspace ?? parent.workspace,
|
|
77
|
+
workspaceId: askOpts.workspaceId ?? parent.workspaceId,
|
|
78
|
+
};
|
|
79
|
+
if (opts.json)
|
|
80
|
+
setJsonMode(true);
|
|
81
|
+
if (!message.trim()) {
|
|
82
|
+
fail(new CliError('The message is empty.', 'missing_message', 'Pass a prompt: plazbot studio ask "List my agents"'));
|
|
83
|
+
}
|
|
34
84
|
const { runOneShot } = await import('./runOneShot.js');
|
|
35
85
|
await runOneShot({
|
|
36
86
|
dev: opts.dev,
|
|
37
87
|
agentId: opts.agentId,
|
|
38
88
|
message,
|
|
39
89
|
json: opts.json,
|
|
40
|
-
workspaceOverride: opts.workspaceId,
|
|
90
|
+
workspaceOverride: opts.workspace ?? opts.workspaceId,
|
|
41
91
|
});
|
|
42
92
|
});
|
|
93
|
+
addExamples(askCommand, [
|
|
94
|
+
{ description: 'List the agents in your workspace', command: 'plazbot studio ask "List my agents"' },
|
|
95
|
+
{ description: 'Diagnose a specific agent', command: 'plazbot studio ask "Diagnose this agent and report issues" -a age_AbcDef123' },
|
|
96
|
+
{ description: 'Stream raw chunks for scripting', command: 'plazbot studio ask "List my agents" --json' },
|
|
97
|
+
{ description: 'Ask about a customer workspace (support / partner mode)', command: 'plazbot studio ask "List the agents" -w wok_AbcDef123' },
|
|
98
|
+
]);
|
|
99
|
+
addNotes(askCommand, [
|
|
100
|
+
'The answer streams to stdout; tool steps, token usage and errors go to stderr.',
|
|
101
|
+
'With --json, stdout carries one JSON chunk per line: text, tool_call, tool_result, usage, error, done.',
|
|
102
|
+
'Exits with code 1 when the request fails or the stream returns an error chunk.',
|
|
103
|
+
'Each call is a fresh conversation (no history). Use `plazbot studio` for multi-turn sessions.',
|
|
104
|
+
'With PLAZBOT_API_KEY / PLAZBOT_WORKSPACE_ID credentials, set PLAZBOT_USER_ID to attribute created or saved agents to a user (optional).',
|
|
105
|
+
]);
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import { getStoredCredentials } from '../utils/credentials.js';
|
|
2
|
-
import {
|
|
2
|
+
import { fail, CliError } from '../utils/output.js';
|
|
3
3
|
import { streamStudio } from './api/sseClient.js';
|
|
4
4
|
import { StudioHttpError } from './api/types.js';
|
|
5
5
|
import { stepLabel } from './render/steps.js';
|
|
6
|
+
import { resolveStudioUserId } from './api/studioApi.js';
|
|
6
7
|
export async function runOneShot(opts) {
|
|
7
8
|
let creds;
|
|
8
9
|
try {
|
|
9
10
|
creds = await getStoredCredentials();
|
|
10
11
|
}
|
|
11
12
|
catch {
|
|
12
|
-
|
|
13
|
-
console.log(' plazbot login');
|
|
14
|
-
process.exit(1);
|
|
15
|
-
return;
|
|
13
|
+
fail(new CliError('You are not signed in.', 'not_signed_in', 'Run `plazbot login`, or set PLAZBOT_API_KEY and PLAZBOT_WORKSPACE_ID.'));
|
|
16
14
|
}
|
|
17
15
|
const effectiveWorkspace = opts.workspaceOverride ?? creds.workspace;
|
|
18
16
|
const supportMode = !!opts.workspaceOverride && opts.workspaceOverride !== creds.workspace;
|
|
@@ -22,15 +20,18 @@ export async function runOneShot(opts) {
|
|
|
22
20
|
const stream = {
|
|
23
21
|
apiKey: creds.apiKey,
|
|
24
22
|
workspaceId: effectiveWorkspace,
|
|
25
|
-
userId: creds
|
|
23
|
+
userId: resolveStudioUserId(creds),
|
|
26
24
|
zone: creds.zone,
|
|
27
25
|
dev: opts.dev,
|
|
28
26
|
};
|
|
29
27
|
const controller = new AbortController();
|
|
30
28
|
process.on('SIGINT', () => controller.abort());
|
|
31
29
|
let assistantText = '';
|
|
30
|
+
let streamError = null;
|
|
32
31
|
const onChunk = (chunk) => {
|
|
33
32
|
if (opts.json) {
|
|
33
|
+
if (chunk.type === 'error')
|
|
34
|
+
streamError = chunk.error || 'The Studio stream returned an error.';
|
|
34
35
|
process.stdout.write(JSON.stringify(chunk) + '\n');
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
@@ -53,7 +54,8 @@ export async function runOneShot(opts) {
|
|
|
53
54
|
process.stderr.write(` tokens: ${chunk.input_tokens} in / ${chunk.output_tokens} out\n`);
|
|
54
55
|
break;
|
|
55
56
|
case 'error':
|
|
56
|
-
|
|
57
|
+
streamError = chunk.error || 'The Studio stream returned an error.';
|
|
58
|
+
process.stderr.write(`\n ✖ ${streamError}\n`);
|
|
57
59
|
break;
|
|
58
60
|
case 'done':
|
|
59
61
|
break;
|
|
@@ -65,32 +67,69 @@ export async function runOneShot(opts) {
|
|
|
65
67
|
messages: [{ role: 'user', content: opts.message }],
|
|
66
68
|
agentId: opts.agentId ?? null,
|
|
67
69
|
}, onChunk, controller.signal);
|
|
70
|
+
if (controller.signal.aborted) {
|
|
71
|
+
if (!opts.json)
|
|
72
|
+
process.stdout.write('\n');
|
|
73
|
+
process.stderr.write(' Cancelled.\n');
|
|
74
|
+
process.exit(130);
|
|
75
|
+
}
|
|
68
76
|
if (!opts.json)
|
|
69
77
|
process.stdout.write('\n');
|
|
78
|
+
if (streamError) {
|
|
79
|
+
// In JSON mode the error chunk is already on stdout; also report it on stderr.
|
|
80
|
+
if (opts.json)
|
|
81
|
+
process.stderr.write(JSON.stringify({ error: { message: streamError, code: 'studio_error' } }) + '\n');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
70
84
|
if (!assistantText && !opts.json)
|
|
71
85
|
process.stderr.write(' (no text response)\n');
|
|
72
86
|
}
|
|
73
87
|
catch (err) {
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
if (!opts.json)
|
|
89
|
+
process.stdout.write('\n');
|
|
90
|
+
fail(toCliError(err, opts.dev));
|
|
76
91
|
}
|
|
77
92
|
}
|
|
78
|
-
function
|
|
93
|
+
function toCliError(err, dev) {
|
|
79
94
|
if (err instanceof StudioHttpError) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (err.status ===
|
|
85
|
-
return '
|
|
95
|
+
const apiMessage = extractApiMessage(err.body);
|
|
96
|
+
if (err.status === 401) {
|
|
97
|
+
return new CliError(apiMessage || 'Your session is invalid or expired.', 'unauthorized', 'Run `plazbot login` to sign in again.');
|
|
98
|
+
}
|
|
99
|
+
if (err.status === 403) {
|
|
100
|
+
return new CliError(apiMessage || 'You do not have permission for this workspace.', 'forbidden', 'Check the active workspace with `plazbot whoami`.');
|
|
101
|
+
}
|
|
102
|
+
if (err.status === 429) {
|
|
103
|
+
return new CliError(apiMessage || 'Too many requests.', 'rate_limited', 'Wait a few seconds and try again.');
|
|
104
|
+
}
|
|
86
105
|
if (err.status >= 500) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
: `✖ Backend returned ${err.status}. Retry.`;
|
|
106
|
+
const detail = dev && err.body ? `: ${err.body}` : '.';
|
|
107
|
+
return new CliError(`Plazbot Studio error (HTTP ${err.status})${detail}`, 'api_error', 'Try again in a few seconds.');
|
|
90
108
|
}
|
|
91
|
-
return
|
|
109
|
+
return new CliError(apiMessage || `Studio request failed (HTTP ${err.status} ${err.statusText}).`, 'request_failed');
|
|
110
|
+
}
|
|
111
|
+
if (err instanceof CliError)
|
|
112
|
+
return err;
|
|
113
|
+
if (err instanceof Error) {
|
|
114
|
+
const code = err.cause?.code;
|
|
115
|
+
if (err.name === 'TypeError' && /fetch failed/i.test(err.message)) {
|
|
116
|
+
return new CliError(dev
|
|
117
|
+
? `Could not reach the local backend (http://localhost:5090)${code ? ` (${code})` : ''}.`
|
|
118
|
+
: `Could not reach the Plazbot API${code ? ` (${code})` : ''}.`, 'network_error', dev ? 'Is the backend running?' : 'Check your internet connection.');
|
|
119
|
+
}
|
|
120
|
+
return new CliError(err.message.endsWith('.') ? err.message : `${err.message}.`);
|
|
121
|
+
}
|
|
122
|
+
return new CliError('Unknown error.');
|
|
123
|
+
}
|
|
124
|
+
function extractApiMessage(body) {
|
|
125
|
+
if (!body)
|
|
126
|
+
return undefined;
|
|
127
|
+
try {
|
|
128
|
+
const parsed = JSON.parse(body);
|
|
129
|
+
const msg = parsed.message ?? parsed.error;
|
|
130
|
+
return typeof msg === 'string' && msg ? msg : undefined;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return undefined;
|
|
92
134
|
}
|
|
93
|
-
if (err instanceof Error)
|
|
94
|
-
return `✖ ${err.message}`;
|
|
95
|
-
return '✖ Unknown error';
|
|
96
135
|
}
|
package/dist/studio/runRepl.js
CHANGED
|
@@ -6,6 +6,8 @@ import { dirname, join } from 'node:path';
|
|
|
6
6
|
import { App } from './components/App.js';
|
|
7
7
|
import { getStoredCredentials } from '../utils/credentials.js';
|
|
8
8
|
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { fail, CliError } from '../utils/output.js';
|
|
10
|
+
import { resolveStudioUserId } from './api/studioApi.js';
|
|
9
11
|
// Estilo Claude Code: NO usamos alt screen buffer. Razon: en el alt screen el
|
|
10
12
|
// scrollback nativo de la terminal queda inaccesible y el usuario no puede
|
|
11
13
|
// revisar la conversacion anterior. Aceptamos el tradeoff de que en algunos
|
|
@@ -16,10 +18,8 @@ export async function runRepl(opts) {
|
|
|
16
18
|
try {
|
|
17
19
|
creds = await getStoredCredentials();
|
|
18
20
|
}
|
|
19
|
-
catch
|
|
20
|
-
|
|
21
|
-
console.log(' plazbot login');
|
|
22
|
-
process.exit(1);
|
|
21
|
+
catch {
|
|
22
|
+
fail(new CliError('You are not signed in.', 'not_signed_in', 'Run `plazbot login`, or set PLAZBOT_API_KEY and PLAZBOT_WORKSPACE_ID.'));
|
|
23
23
|
}
|
|
24
24
|
const effectiveWorkspace = opts.workspaceOverride ?? creds.workspace;
|
|
25
25
|
const supportMode = !!opts.workspaceOverride && opts.workspaceOverride !== creds.workspace;
|
|
@@ -30,7 +30,7 @@ export async function runRepl(opts) {
|
|
|
30
30
|
const stream = {
|
|
31
31
|
apiKey: creds.apiKey,
|
|
32
32
|
workspaceId: effectiveWorkspace,
|
|
33
|
-
userId: creds
|
|
33
|
+
userId: resolveStudioUserId(creds),
|
|
34
34
|
zone: creds.zone,
|
|
35
35
|
dev: opts.dev,
|
|
36
36
|
};
|