@parallel-cli/parallel 0.4.5 → 0.4.7

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/i18n.js CHANGED
@@ -76,6 +76,10 @@ const en = {
76
76
  'main.ready1': '⚡ Ready — folder: {folder}',
77
77
  'main.ready2': 'Type a task + Enter to launch your first agent. /help for help.',
78
78
  'main.empty': 'No agents yet. Type a task + Enter to launch your first agent — then launch more at any time, even while they work.',
79
+ 'main.prompt': 'Example: Redesign the UI',
80
+ 'main.emptyCard.tagline': 'Multi-agent coding from one terminal.',
81
+ 'main.emptyCard.cta': 'Describe work below to launch the first agent.',
82
+ 'main.emptyCard.hints': '/ for commands · @agent to steer · /help for shortcuts',
79
83
  'main.status': 'Enter = new agent N+1 (even while others work) · @Name = real-time instruction · /help · views: /agents /board /diff /notes',
80
84
  'main.placeholder': 'Type a task (= new agent N+1) · @Agent message · /command',
81
85
  'agent.summary': 'Summary',
@@ -100,6 +104,11 @@ const en = {
100
104
  'board.none': '(no agents)',
101
105
  'board.activity': 'Who works where (file activity, no locks):',
102
106
  'board.workMap': 'Work map warnings:',
107
+ 'board.workMapOk': 'Work map: no overlapping claims.',
108
+ 'board.warningMeta': '{agents} · {paths} · {time}',
109
+ 'board.warningSuggestion': 'Suggestion: /focus agent or /diff before editing the same area.',
110
+ 'board.secondsAgo': '{n}s ago',
111
+ 'board.minutesAgo': '{n}m ago',
103
112
  'board.noActivity': '(no edits yet)',
104
113
  'board.notes': 'Latest notes:',
105
114
  'notes.title': '✉ INTER-AGENT NOTES (last 30)',
@@ -126,6 +135,7 @@ const en = {
126
135
  // commands (descriptions)
127
136
  'cmd.ask': 'Ask-only agent: answers and advises without editing',
128
137
  'cmd.task': 'Task agent: executes, edits, validates, summarizes',
138
+ 'cmd.review': 'Lightweight reviewer: verdict, risks, tests, files to inspect',
129
139
  'cmd.agents': 'Agents panel view (real time)',
130
140
  'cmd.board': 'Coordination view: who works where, notes',
131
141
  'cmd.notes': 'Inter-agent notes history',
@@ -150,6 +160,14 @@ const en = {
150
160
  'timeline.section.coordinate': 'Coordinating',
151
161
  'timeline.section.result': 'Result',
152
162
  'timeline.section.other': 'Activity',
163
+ 'timeline.narration.inspectAfterValidate': 'That signal is not enough, so I inspect the project again to confirm the real state.',
164
+ 'timeline.narration.inspect': 'I inspect the project state and relevant files before deciding.',
165
+ 'timeline.narration.change': 'I change the targeted files while keeping the edit focused.',
166
+ 'timeline.narration.validate': 'I run local validation to check the changes technically hold.',
167
+ 'timeline.narration.publish': 'I prepare Git synchronization after checking the local state.',
168
+ 'timeline.narration.coordinate': 'I handle coordination signals, notes, approvals, and questions.',
169
+ 'timeline.narration.result': 'I check the final result and any important errors.',
170
+ 'timeline.narration.other': 'I continue the current activity.',
153
171
  'cmd.send': 'Instruction to an agent (same as: @Agent message)',
154
172
  'cmd.pause': 'Pause',
155
173
  'cmd.resume': 'Resume',
@@ -454,6 +472,10 @@ const fr = {
454
472
  'main.ready1': '⚡ Prêt — dossier : {folder}',
455
473
  'main.ready2': "Tape une tâche + Entrée pour lancer ton premier agent. /help pour l'aide.",
456
474
  'main.empty': "Aucun agent pour le moment. Tape une tâche + Entrée pour lancer ton premier agent — puis relances-en d'autres à tout moment, même pendant qu'ils travaillent.",
475
+ 'main.prompt': 'Exemple : Fais-moi une refonte UI',
476
+ 'main.emptyCard.tagline': 'Code multi-agent depuis un terminal.',
477
+ 'main.emptyCard.cta': 'Décris le travail ci-dessous pour lancer le premier agent.',
478
+ 'main.emptyCard.hints': '/ pour les commandes · @agent pour piloter · /help pour les raccourcis',
457
479
  'main.status': 'Entrée = nouvel agent N+1 (même pendant que les autres travaillent) · @Nom = instruction temps réel · /help · vues : /agents /board /diff /notes',
458
480
  'main.placeholder': 'Tape une tâche (= nouvel agent N+1) · @Agent message · /commande',
459
481
  'agent.summary': 'Récapitulatif',
@@ -475,6 +497,11 @@ const fr = {
475
497
  'board.none': '(aucun agent)',
476
498
  'board.activity': 'Qui travaille où (activité fichiers, sans verrou) :',
477
499
  'board.workMap': 'Alertes work map :',
500
+ 'board.workMapOk': 'Work map : aucune zone déclarée en chevauchement.',
501
+ 'board.warningMeta': '{agents} · {paths} · {time}',
502
+ 'board.warningSuggestion': 'Suggestion : /focus agent ou /diff avant de modifier la même zone.',
503
+ 'board.secondsAgo': 'il y a {n}s',
504
+ 'board.minutesAgo': 'il y a {n}min',
478
505
  'board.noActivity': '(aucune modification pour le moment)',
479
506
  'board.notes': 'Dernières notes :',
480
507
  'notes.title': '✉ NOTES INTER-AGENTS (30 dernières)',
@@ -500,6 +527,7 @@ const fr = {
500
527
  'help.keys': 'Esc : revenir à la vue agents · Tab : complétion · PgUp/PgDn : scroll hub/focus · ↑/↓ : suggestions, historique ou scroll selon la vue · Ctrl+V : coller une image',
501
528
  'cmd.ask': 'Agent ask : répond et conseille sans modifier',
502
529
  'cmd.task': 'Agent task : exécute, modifie, valide, résume',
530
+ 'cmd.review': 'Reviewer léger : verdict, risques, tests, fichiers à inspecter',
503
531
  'cmd.agents': 'Vue panneaux agents (temps réel)',
504
532
  'cmd.board': 'Vue coordination : qui travaille où, notes',
505
533
  'cmd.notes': 'Historique des notes inter-agents',
@@ -510,7 +538,7 @@ const fr = {
510
538
  'timeline.activity': 'Activité',
511
539
  'timeline.raw': 'Activité brute',
512
540
  'timeline.empty': 'Aucune activité pour le moment.',
513
- 'timeline.ran': 'Ran',
541
+ 'timeline.ran': 'Exécuté',
514
542
  'timeline.readFiles': '{count} fichier(s) lu(s)',
515
543
  'timeline.editedFiles': '{count} fichier(s) modifié(s)',
516
544
  'timeline.wroteFiles': '{count} fichier(s) écrit(s)',
@@ -524,6 +552,14 @@ const fr = {
524
552
  'timeline.section.coordinate': 'Coordination',
525
553
  'timeline.section.result': 'Résultat',
526
554
  'timeline.section.other': 'Activité',
555
+ 'timeline.narration.inspectAfterValidate': 'Ce signal ne suffit pas, donc je réinspecte le projet pour confirmer l’état réel.',
556
+ 'timeline.narration.inspect': 'J’inspecte l’état du projet et les fichiers concernés avant de décider.',
557
+ 'timeline.narration.change': 'Je modifie les fichiers ciblés en gardant le changement focalisé.',
558
+ 'timeline.narration.validate': 'Je lance les validations locales pour vérifier que les changements tiennent techniquement.',
559
+ 'timeline.narration.publish': 'Je prépare la synchronisation Git après avoir vérifié l’état local.',
560
+ 'timeline.narration.coordinate': 'Je traite les signaux de coordination, notes, approbations et questions.',
561
+ 'timeline.narration.result': 'Je vérifie le résultat final et les erreurs importantes.',
562
+ 'timeline.narration.other': 'Je poursuis l’activité en cours.',
527
563
  'cmd.send': 'Instruction à un agent (équivalent : @Agent message)',
528
564
  'cmd.pause': 'Mettre en pause',
529
565
  'cmd.resume': 'Reprendre',
@@ -641,7 +677,7 @@ const fr = {
641
677
  'm.missingModel': 'Le provider {name} n’a pas de modèle par défaut. Utilise /settings → Modèles du provider.',
642
678
  'm.doctorOk': '✓ Configuration prête : {pm}. Changements : /settings · modèle temporaire : /model [provider:]modèle',
643
679
  'm.doctorReport': 'Doctor\n{lines}',
644
- 'm.doctorNoProvider': '✗ Aucun provider configuré. Ouvre /settings → Add a provider.',
680
+ 'm.doctorNoProvider': '✗ Aucun provider configuré. Ouvre /settings → Ajouter un fournisseur.',
645
681
  'm.doctorProvider': '• Provider : {provider}:{model}',
646
682
  'm.doctorKeyMissing': '✗ Clef API manquante. Ouvre /settings → Providers → Clef API.',
647
683
  'm.doctorKeyOk': '✓ Clef API présente.',
@@ -696,7 +732,7 @@ const fr = {
696
732
  'cost.unknown': '(prix inconnu — règle-le dans /settings → Prix des modèles)',
697
733
  'cost.total': 'Total session :',
698
734
  'cost.partial': '(partiel — certains modèles n’ont pas de prix connu)',
699
- 'cost.hint': 'Prix en USD. Override par modèle : /settings → Prix des modèles. Les sessions restaurées gardent leur historique de coût.',
735
+ 'cost.hint': 'Prix en USD. Surcharge par modèle : /settings → Prix des modèles. Les sessions restaurées gardent leur historique de coût.',
700
736
  'skills.title': '🧩 SKILLS — instructions réutilisables que les agents peuvent charger',
701
737
  'skills.empty': 'Aucun skill. Crée-en un : /skill new <nom> [global], puis édite le fichier .md.',
702
738
  'skills.hint1': 'Fichiers : ~/.parallel/skills/*.md (global) · <projet>/.parallel/skills/*.md (projet).',
@@ -820,6 +856,10 @@ const es = {
820
856
  'main.ready1': '⚡ Listo — carpeta: {folder}',
821
857
  'main.ready2': 'Escribe una tarea + Enter para lanzar tu primer agente. /help para ayuda.',
822
858
  'main.empty': 'Aún no hay agentes. Escribe una tarea + Enter para lanzar tu primer agente — luego lanza más en cualquier momento, incluso mientras trabajan.',
859
+ 'main.prompt': 'Ejemplo: hazme un rediseño de UI',
860
+ 'main.emptyCard.tagline': 'Código multiagente desde un terminal.',
861
+ 'main.emptyCard.cta': 'Describe el trabajo abajo para lanzar el primer agente.',
862
+ 'main.emptyCard.hints': '/ para comandos · @agente para dirigir · /help para atajos',
823
863
  'main.status': 'Enter = nuevo agente N+1 (incluso mientras otros trabajan) · @Nombre = instrucción en tiempo real · /help · vistas: /agents /board /diff /notes',
824
864
  'main.placeholder': 'Escribe una tarea (= nuevo agente N+1) · @Agente mensaje · /comando',
825
865
  'agent.summary': 'Resumen',
@@ -841,6 +881,11 @@ const es = {
841
881
  'board.none': '(sin agentes)',
842
882
  'board.activity': 'Quién trabaja dónde (actividad de archivos, sin bloqueos):',
843
883
  'board.workMap': 'Avisos del mapa de trabajo:',
884
+ 'board.workMapOk': 'Mapa de trabajo: no hay reclamos superpuestos.',
885
+ 'board.warningMeta': '{agents} · {paths} · {time}',
886
+ 'board.warningSuggestion': 'Sugerencia: /focus agente o /diff antes de editar la misma zona.',
887
+ 'board.secondsAgo': 'hace {n}s',
888
+ 'board.minutesAgo': 'hace {n}min',
844
889
  'board.noActivity': '(sin modificaciones por ahora)',
845
890
  'board.notes': 'Últimas notas:',
846
891
  'notes.title': '✉ NOTAS ENTRE AGENTES (últimas 30)',
@@ -866,6 +911,7 @@ const es = {
866
911
  'help.keys': 'Esc: volver a la vista de agentes · Tab: autocompletar · PgUp/PgDn: scroll hub/focus · ↑/↓: sugerencias, historial o scroll de vista · Ctrl+V: pegar imagen',
867
912
  'cmd.ask': 'Agente ask: responde y aconseja sin editar',
868
913
  'cmd.task': 'Agente task: ejecuta, edita, valida y resume',
914
+ 'cmd.review': 'Revisor ligero: veredicto, riesgos, pruebas y archivos a inspeccionar',
869
915
  'cmd.agents': 'Vista de paneles de agentes (tiempo real)',
870
916
  'cmd.board': 'Vista de coordinación: quién trabaja dónde, notas',
871
917
  'cmd.notes': 'Historial de notas entre agentes',
@@ -876,7 +922,7 @@ const es = {
876
922
  'timeline.activity': 'Actividad',
877
923
  'timeline.raw': 'Actividad sin procesar',
878
924
  'timeline.empty': 'Aún no hay actividad.',
879
- 'timeline.ran': 'Ran',
925
+ 'timeline.ran': 'Ejecutó',
880
926
  'timeline.readFiles': '{count} archivo(s) leído(s)',
881
927
  'timeline.editedFiles': '{count} archivo(s) editado(s)',
882
928
  'timeline.wroteFiles': '{count} archivo(s) escrito(s)',
@@ -890,6 +936,14 @@ const es = {
890
936
  'timeline.section.coordinate': 'Coordinando',
891
937
  'timeline.section.result': 'Resultado',
892
938
  'timeline.section.other': 'Actividad',
939
+ 'timeline.narration.inspectAfterValidate': 'Esa señal no basta, así que vuelvo a inspeccionar el proyecto para confirmar el estado real.',
940
+ 'timeline.narration.inspect': 'Inspecciono el estado del proyecto y los archivos relevantes antes de decidir.',
941
+ 'timeline.narration.change': 'Modifico los archivos objetivo manteniendo el cambio enfocado.',
942
+ 'timeline.narration.validate': 'Ejecuto validaciones locales para comprobar que los cambios se sostienen técnicamente.',
943
+ 'timeline.narration.publish': 'Preparo la sincronización Git después de revisar el estado local.',
944
+ 'timeline.narration.coordinate': 'Gestiono señales de coordinación, notas, aprobaciones y preguntas.',
945
+ 'timeline.narration.result': 'Reviso el resultado final y los errores importantes.',
946
+ 'timeline.narration.other': 'Continúo la actividad en curso.',
893
947
  'cmd.send': 'Instrucción a un agente (igual que: @Agente mensaje)',
894
948
  'cmd.pause': 'Pausar',
895
949
  'cmd.resume': 'Reanudar',
@@ -941,9 +995,9 @@ const es = {
941
995
  'm.saved': '💾 Sesión guardada.',
942
996
  'm.savedAs': '💾 Sesión guardada como "{name}".',
943
997
  'm.usageFocus': 'Uso: /focus <agente|off>',
944
- 'm.focusOn': '🎯 Foco en {name} — el texto simple se le envía. Esc o /focus off para salir.',
945
- 'm.focusOff': '🎯 Foco desactivado.',
946
- 'm.focusHint': '🎯 foco {name} · escribe para hablarle · PgUp/PgDn desplazamiento · Esc para salir',
998
+ 'm.focusOn': '🎯 Enfoque en {name} — el texto simple se le envía. Esc o /focus off para salir.',
999
+ 'm.focusOff': '🎯 Enfoque desactivado.',
1000
+ 'm.focusHint': '🎯 enfoque {name} · escribe para hablarle · PgUp/PgDn desplazamiento · Esc para salir',
947
1001
  'm.usageRestore': 'Uso: /restore <agente> (tras cargar una sesión con /session)',
948
1002
  'm.restored': '⏪ {name} relanzado con su conversación anterior.',
949
1003
  'm.noRestoredAgent': 'No hay ningún agente llamado "{name}" en la sesión restaurada.',
@@ -1007,7 +1061,7 @@ const es = {
1007
1061
  'm.missingModel': 'El proveedor {name} no tiene modelo por defecto. Usa /settings → Modelos del proveedor.',
1008
1062
  'm.doctorOk': '✓ Configuración lista: {pm}. Cambios: /settings · modelo temporal: /model [proveedor:]modelo',
1009
1063
  'm.doctorReport': 'Doctor\n{lines}',
1010
- 'm.doctorNoProvider': '✗ No hay proveedor configurado. Abre /settings → Add a provider.',
1064
+ 'm.doctorNoProvider': '✗ No hay proveedor configurado. Abre /settings → Añadir un proveedor.',
1011
1065
  'm.doctorProvider': '• Proveedor: {provider}:{model}',
1012
1066
  'm.doctorKeyMissing': '✗ Falta la clave API. Abre /settings → Providers → Clave API.',
1013
1067
  'm.doctorKeyOk': '✓ Clave API presente.',
@@ -1062,7 +1116,7 @@ const es = {
1062
1116
  'cost.unknown': '(precio desconocido — defínelo en /settings → Precios de modelos)',
1063
1117
  'cost.total': 'Total de la sesión:',
1064
1118
  'cost.partial': '(parcial — algunos modelos no tienen precio conocido)',
1065
- 'cost.hint': 'Precios en USD. Override por modelo: /settings → Precios de modelos. Las sesiones restauradas conservan su historial de costes.',
1119
+ 'cost.hint': 'Precios en USD. Sobrescritura por modelo: /settings → Precios de modelos. Las sesiones restauradas conservan su historial de costes.',
1066
1120
  'skills.title': '🧩 SKILLS — instrucciones reutilizables que los agentes pueden cargar',
1067
1121
  'skills.empty': 'Sin skills. Crea uno: /skill new <nombre> [global], luego edita el archivo .md.',
1068
1122
  'skills.hint1': 'Archivos: ~/.parallel/skills/*.md (global) · <proyecto>/.parallel/skills/*.md (proyecto).',
@@ -1186,6 +1240,10 @@ const zh = {
1186
1240
  'main.ready1': '⚡ 就绪 — 文件夹:{folder}',
1187
1241
  'main.ready2': '输入任务 + 回车即可启动第一个智能体。/help 查看帮助。',
1188
1242
  'main.empty': '尚无智能体。输入任务 + 回车启动第一个 — 之后可随时启动更多,即使它们正在工作。',
1243
+ 'main.prompt': '示例:帮我重做 UI',
1244
+ 'main.emptyCard.tagline': '在一个终端中进行多智能体编码。',
1245
+ 'main.emptyCard.cta': '在下方描述工作以启动第一个智能体。',
1246
+ 'main.emptyCard.hints': '/ 查看命令 · @智能体 可指挥 · /help 查看快捷键',
1189
1247
  'main.status': '回车 = 新智能体 N+1(即使其他智能体正在工作)· @名称 = 实时指令 · /help · 视图:/agents /board /diff /notes',
1190
1248
  'main.placeholder': '输入任务(= 新智能体 N+1)· @智能体 消息 · /命令',
1191
1249
  'agent.summary': '摘要',
@@ -1207,6 +1265,11 @@ const zh = {
1207
1265
  'board.none': '(无智能体)',
1208
1266
  'board.activity': '谁在哪里工作(文件活动,无锁定):',
1209
1267
  'board.workMap': '工作地图提醒:',
1268
+ 'board.workMapOk': '工作地图:没有重叠的声明区域。',
1269
+ 'board.warningMeta': '{agents} · {paths} · {time}',
1270
+ 'board.warningSuggestion': '建议:编辑同一区域前先用 /focus agent 或 /diff。',
1271
+ 'board.secondsAgo': '{n} 秒前',
1272
+ 'board.minutesAgo': '{n} 分钟前',
1210
1273
  'board.noActivity': '(暂无修改)',
1211
1274
  'board.notes': '最新便签:',
1212
1275
  'notes.title': '✉ 智能体间便签(最近 30 条)',
@@ -1232,6 +1295,7 @@ const zh = {
1232
1295
  'help.keys': 'Esc:返回智能体视图 · Tab:补全 · PgUp/PgDn:hub/focus 滚动 · ↑/↓:建议、历史或视图滚动 · Ctrl+V:粘贴图片',
1233
1296
  'cmd.ask': 'Ask 智能体:只回答和建议,不编辑',
1234
1297
  'cmd.task': 'Task 智能体:执行、编辑、验证并总结',
1298
+ 'cmd.review': '轻量审核智能体:结论、风险、测试和需检查文件',
1235
1299
  'cmd.agents': '智能体面板视图(实时)',
1236
1300
  'cmd.board': '协调视图:谁在哪里工作、便签',
1237
1301
  'cmd.notes': '智能体间便签历史',
@@ -1242,7 +1306,7 @@ const zh = {
1242
1306
  'timeline.activity': '活动',
1243
1307
  'timeline.raw': '原始活动',
1244
1308
  'timeline.empty': '暂无活动。',
1245
- 'timeline.ran': 'Ran',
1309
+ 'timeline.ran': '已执行',
1246
1310
  'timeline.readFiles': '读取 {count} 个文件',
1247
1311
  'timeline.editedFiles': '编辑 {count} 个文件',
1248
1312
  'timeline.wroteFiles': '写入 {count} 个文件',
@@ -1256,6 +1320,14 @@ const zh = {
1256
1320
  'timeline.section.coordinate': '协调',
1257
1321
  'timeline.section.result': '结果',
1258
1322
  'timeline.section.other': '活动',
1323
+ 'timeline.narration.inspectAfterValidate': '这个信号还不够,所以我会重新检查项目以确认真实状态。',
1324
+ 'timeline.narration.inspect': '我先检查项目状态和相关文件,再做决定。',
1325
+ 'timeline.narration.change': '我正在修改目标文件,并保持改动聚焦。',
1326
+ 'timeline.narration.validate': '我运行本地验证,确认改动在技术上成立。',
1327
+ 'timeline.narration.publish': '我会在确认本地状态后准备 Git 同步。',
1328
+ 'timeline.narration.coordinate': '我处理协调信号、便签、审批和问题。',
1329
+ 'timeline.narration.result': '我检查最终结果和重要错误。',
1330
+ 'timeline.narration.other': '我继续当前活动。',
1259
1331
  'cmd.send': '向某个智能体发指令(等同于:@智能体 消息)',
1260
1332
  'cmd.pause': '暂停',
1261
1333
  'cmd.resume': '恢复',
@@ -1373,7 +1445,7 @@ const zh = {
1373
1445
  'm.missingModel': '提供商 {name} 没有默认模型。使用 /settings → 提供商模型。',
1374
1446
  'm.doctorOk': '✓ 配置就绪:{pm}。修改:/settings · 临时模型:/model [提供商:]模型',
1375
1447
  'm.doctorReport': 'Doctor\n{lines}',
1376
- 'm.doctorNoProvider': '✗ 未配置提供商。打开 /settings → Add a provider。',
1448
+ 'm.doctorNoProvider': '✗ 未配置提供商。打开 /settings → 添加提供商。',
1377
1449
  'm.doctorProvider': '• 提供商:{provider}:{model}',
1378
1450
  'm.doctorKeyMissing': '✗ 缺少 API 密钥。打开 /settings → Providers → API 密钥。',
1379
1451
  'm.doctorKeyOk': '✓ API 密钥已设置。',
@@ -1463,7 +1535,7 @@ const zh = {
1463
1535
  // provider-pick section headers
1464
1536
  'wiz.provider.section.configured': '已配置的提供商',
1465
1537
  'wiz.provider.section.western': '西方',
1466
- 'wiz.provider.section.chinese': '中文',
1538
+ 'wiz.provider.section.chinese': '国产',
1467
1539
  'wiz.provider.section.gateways': '网关',
1468
1540
  'wiz.provider.section.inference': '推理',
1469
1541
  'wiz.provider.section.local': '本地模型',
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { App } from './ui/App.js';
8
8
  import { Controller } from './controller.js';
9
9
  import { loadConfig, providerReady, setConfigHome } from './config.js';
10
10
  import { setLang } from './i18n.js';
11
+ import { maybeRunStartupUpdate } from './update.js';
11
12
  const argv = process.argv.slice(2);
12
13
  function takeFlagValue(flag) {
13
14
  const i = argv.indexOf(flag);
@@ -26,6 +27,9 @@ if (headless)
26
27
  const jsonOut = argv.includes('--json');
27
28
  if (jsonOut)
28
29
  argv.splice(argv.indexOf('--json'), 1);
30
+ const noUpdate = argv.includes('--no-update');
31
+ if (noUpdate)
32
+ argv.splice(argv.indexOf('--no-update'), 1);
29
33
  const configHome = takeFlagValue('--config-home');
30
34
  if (argv.includes('--help') || argv.includes('-h')) {
31
35
  console.log(`⚡ Parallel — real-time parallel coding agents.
@@ -39,6 +43,8 @@ Usage:
39
43
  parallel --first-run Test the first-run wizard with a temporary config home
40
44
  parallel --config-home <dir> [folder]
41
45
  Use <dir>/config.json instead of ~/.parallel/config.json
46
+ parallel --no-update [folder]
47
+ Start without checking npm for a newer Parallel version
42
48
  parallel --headless "task1" ["task2"…] [--json]
43
49
  No TUI: one agent per task in the current folder,
44
50
  auto-approved commands, summary (or JSON) on stdout — for CI
@@ -48,10 +54,12 @@ Environment variables:
48
54
  PARALLEL_MODEL Default model
49
55
  PARALLEL_BASE_URL OpenAI-compatible endpoint
50
56
  PARALLEL_NO_ALT_SCREEN=1 Disable the alternate terminal screen.
57
+ PARALLEL_SKIP_UPDATE_CHECK=1 Disable npm update checks.
51
58
 
52
59
  Inside the TUI:
53
60
  <task> + Enter Launch agent N+1 — even while the others are working
54
61
  @a1 <message> Real-time instruction to an agent (@all for everyone)
62
+ /review [agent|all] Ask-mode reviewer: verdict, risks, tests, files
55
63
  /project [folder] Change project folder or reopen the folder picker
56
64
  /wizard Relaunch the setup wizard
57
65
  /help All commands
@@ -172,6 +180,8 @@ if (!process.stdout.isTTY) {
172
180
  console.error('Parallel requires an interactive terminal (TTY).');
173
181
  process.exit(1);
174
182
  }
183
+ if (await maybeRunStartupUpdate(firstRun || noUpdate))
184
+ process.exit(0);
175
185
  const config = loadConfig();
176
186
  if (config.language)
177
187
  setLang(config.language);
@@ -1,4 +1,4 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRef, useState, useEffect } from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import { fmtCost } from '../pricing.js';
@@ -6,7 +6,7 @@ import { elapsed, truncate } from './theme.js';
6
6
  import { Md } from './Md.js';
7
7
  import { Spinner } from './Spinner.js';
8
8
  import { Timeline } from './Timeline.js';
9
- import { MARK, MODE, STATE_META, UI, ANIM } from './tokens.js';
9
+ import { MARK, MODE, STATE_META, UI, ANIM, COLOR } from './tokens.js';
10
10
  import { latestSignal, toUIEvents } from './events.js';
11
11
  export const KIND_COLOR = {
12
12
  tool: UI.accent,
@@ -28,43 +28,98 @@ export function cleanHubSummary(text) {
28
28
  }
29
29
  export function formatAgentTelemetry(agent) {
30
30
  const ctx = agent.ctxPct !== undefined ? ` · ${agent.ctxPct}% ctx` : '';
31
- return `${elapsed(agent.startedAt)}${ctx} · ${agent.cost === null ? '$-' : fmtCost(agent.cost)}`;
31
+ const perf = agent.perf ? ` · ${agent.perf.modelTurns}t/${agent.perf.toolCalls} tools` : '';
32
+ return `${elapsed(agent.startedAt)}${ctx}${perf} · ${agent.cost === null ? '$-' : fmtCost(agent.cost)}`;
32
33
  }
33
- function compactResultSummary(text, max) {
34
- const clean = cleanHubSummary(text);
35
- const validation = text.match(/validation[^:\n]*[:\n]\s*([^\n]+)/i)?.[1]?.trim();
36
- const risk = text.match(/risks?[^:\n]*[:\n]\s*([^\n]+)/i)?.[1]?.trim() ?? text.match(/risques?[^:\n]*[:\n]\s*([^\n]+)/i)?.[1]?.trim();
37
- const parts = [clean.slice(0, Math.max(40, Math.floor(max * 0.55)))];
38
- if (validation)
39
- parts.push(`V: ${validation}`);
40
- if (risk)
41
- parts.push(`R: ${risk}`);
42
- return truncate(parts.join(' · '), max);
34
+ function firstSectionLine(text, labels) {
35
+ const lines = text.replace(/\r/g, '').split('\n');
36
+ for (let i = 0; i < lines.length; i++) {
37
+ const clean = lines[i].replace(/^#{1,6}\s+/, '').replace(/\*\*/g, '').trim();
38
+ const match = clean.match(/^([^:]+):?\s*(.*)$/);
39
+ if (!match)
40
+ continue;
41
+ const label = match[1].toLowerCase();
42
+ if (!labels.some((l) => label.includes(l)))
43
+ continue;
44
+ const inline = cleanHubSummary(match[2] ?? '');
45
+ if (inline)
46
+ return inline;
47
+ const next = cleanHubSummary(lines.slice(i + 1).find((l) => l.trim()) ?? '');
48
+ if (next)
49
+ return next;
50
+ }
51
+ return null;
52
+ }
53
+ function fileSummary(text) {
54
+ const paths = [...text.matchAll(/\b(?:src|test|tests|bin|scripts|docs|\.parallel|\.cursor)\/[A-Za-z0-9._/-]+|\b[A-Za-z0-9._-]+\.(?:ts|tsx|js|mjs|json|md|sh)\b/g)]
55
+ .map((m) => m[0])
56
+ .filter((p, i, arr) => arr.indexOf(p) === i)
57
+ .slice(0, 3);
58
+ if (paths.length === 0)
59
+ return null;
60
+ return paths.join(', ');
61
+ }
62
+ export function hubSummaryLines(text, maxLines = 4, maxWidth = 100) {
63
+ const cleanLines = text
64
+ .replace(/\r/g, '')
65
+ .split('\n')
66
+ .map(cleanHubSummary)
67
+ .filter(Boolean);
68
+ const outcome = firstSectionLine(text, ['ce que j', 'résultat', 'outcome', 'what i did', 'réponse courte', 'mode task']) ?? cleanLines[0] ?? 'Task complete.';
69
+ const validation = firstSectionLine(text, ['validation', 'vérifié', 'verified', 'tests']);
70
+ const files = firstSectionLine(text, ['fichiers', 'files']) ?? fileSummary(text);
71
+ const risks = firstSectionLine(text, ['risque', 'risk', 'caveat', 'problème', 'remaining']);
72
+ const candidates = [
73
+ outcome,
74
+ validation ? `Validation: ${validation}` : null,
75
+ files ? `Files: ${files}` : null,
76
+ risks ? `Risks: ${risks}` : null,
77
+ ].filter((line) => Boolean(line));
78
+ const out = [];
79
+ for (const line of candidates) {
80
+ const normalized = truncate(line.replace(/^[•\-✓]\s*/, ''), maxWidth);
81
+ if (!out.includes(normalized))
82
+ out.push(normalized);
83
+ if (out.length >= maxLines)
84
+ break;
85
+ }
86
+ return out;
43
87
  }
44
88
  function ResultBlock({ agent, compact = false }) {
45
89
  if (!agent.lastResult)
46
90
  return null;
47
91
  if (compact) {
48
- return (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { color: UI.ok, children: [MARK.done, " "] }), _jsx(Text, { children: truncate(agent.lastResult, 110) })] }));
92
+ return (_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: COLOR.cream, children: "\u2022 " }), _jsx(Text, { children: truncate(agent.lastResult, 110) })] }));
49
93
  }
50
94
  return (_jsxs(Box, { borderStyle: "single", borderColor: "gray", flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: UI.ok, bold: true, children: "Result" }), _jsx(Md, { text: agent.lastResult })] }));
51
95
  }
52
96
  const SPINNER_STATES = new Set(['thinking', 'working', 'listening', 'waiting']);
53
97
  function spinnerColor(state) {
54
98
  if (state === 'working')
55
- return 'cyan';
99
+ return COLOR.cream;
56
100
  return 'yellow'; // thinking, listening, waiting
57
101
  }
58
- function modeChar(mode) {
102
+ export function modeBadge(mode) {
59
103
  if (mode === 'ask')
60
- return { char: '?', color: MODE.ask };
104
+ return { label: 'ASK', color: MODE.ask };
61
105
  if (mode === 'plan')
62
- return { char: '', color: MODE.plan };
63
- return null; // task = no mark
106
+ return { label: 'PLAN', color: MODE.plan };
107
+ return { label: 'TASK', color: MODE.task };
64
108
  }
65
109
  function agentDisplayName(agent) {
66
110
  return agent.alias && agent.alias !== agent.name ? `${agent.alias} ${agent.name}` : agent.alias || agent.name;
67
111
  }
112
+ export function ProgressSteps({ agent, max = 4, cols = 100 }) {
113
+ const steps = agent.progressSteps?.slice(0, max) ?? [];
114
+ if (steps.length === 0)
115
+ return null;
116
+ const textMax = Math.max(20, cols - 8);
117
+ return (_jsx(Box, { flexDirection: "column", children: steps.map((step, i) => {
118
+ const active = step.status === 'active';
119
+ const done = step.status === 'done';
120
+ return (_jsxs(Text, { color: done ? UI.ok : active ? COLOR.cream : UI.muted, wrap: "truncate-end", children: [_jsxs(Text, { color: done ? UI.ok : active ? COLOR.cream : UI.muted, children: [done ? MARK.done : active ? MARK.active : MARK.idle, " "] }), truncate(step.text, textMax)] }, `${i}-${step.text}`));
121
+ }) }));
122
+ }
68
123
  export function AgentRow({ agent, logs, cols, }) {
69
124
  const meta = STATE_META[agent.state];
70
125
  // ── State transition pulse (Phase 5) ──
@@ -81,28 +136,31 @@ export function AgentRow({ agent, logs, cols, }) {
81
136
  // Pulse bumps the mark/spinner color to whiteBright for 400ms
82
137
  const pulseColor = pulse ? 'whiteBright' : null;
83
138
  const name = agentDisplayName(agent);
84
- const mode = modeChar(agent.mode);
85
- const taskMax = Math.max(10, cols - 18);
139
+ const mode = modeBadge(agent.mode);
140
+ const quickActions = `full /focus ${agent.alias || agent.name} · term /attach ${agent.alias || agent.name}`;
141
+ const actionBudget = Math.min(44, quickActions.length + 2);
142
+ const taskMax = Math.max(10, cols - 18 - actionBudget);
86
143
  const line2Max = Math.max(10, cols - 2);
87
144
  const telemetry = formatAgentTelemetry(agent);
88
145
  const signal = latestSignal(agent, toUIEvents(logs));
89
146
  const specialist = agent.specialist ? ` #${agent.specialist}` : '';
90
- // Line 2 content
147
+ const claims = agent.claims && agent.claims.length > 0 ? `⚑ ${truncate(agent.claims.join(', '), Math.max(12, Math.floor(line2Max * 0.35)))}` : '';
148
+ const summary = agent.lastResult ? hubSummaryLines(agent.lastResult, 4, Math.max(20, line2Max - 4)) : [];
91
149
  let line2 = null;
92
- if (agent.lastResult) {
93
- line2 = { text: `✓ ${compactResultSummary(agent.lastResult, line2Max)}`, color: UI.ok };
94
- }
95
- else if (signal) {
96
- line2 = { text: `▸ ${truncate(signal, line2Max)}`, color: UI.accent };
150
+ if (!agent.lastResult && signal && signal !== agent.task) {
151
+ const detail = claims ? `${truncate(signal, Math.max(10, line2Max - claims.length - 4))} · ${claims}` : signal;
152
+ line2 = { text: `▸ ${truncate(detail, line2Max)}`, color: UI.accent };
97
153
  }
98
- else {
99
- line2 = { text: meta.label, color: meta.color };
154
+ else if (claims) {
155
+ line2 = { text: claims, color: UI.warn };
100
156
  }
101
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 1, children: [_jsxs(Text, { wrap: "truncate-end", children: [SPINNER_STATES.has(agent.state) ? (_jsx(Spinner, { color: pulseColor ?? spinnerColor(agent.state) })) : (_jsx(Text, { color: pulseColor ?? meta.color, bold: true, children: meta.mark })), _jsx(Text, { children: " " }), _jsx(Text, { color: agent.color, bold: true, children: name }), mode ? (_jsxs(Text, { color: mode.color, children: [" ", mode.char] })) : null, specialist ? _jsx(Text, { color: UI.note, children: specialist }) : null, _jsxs(Text, { color: UI.text, children: [" ", truncate(agent.task, taskMax)] })] }), _jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Text, { color: line2.color, wrap: "truncate-end", children: line2.text }), _jsx(Text, { color: UI.muted, children: telemetry })] })] }));
157
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 1, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Text, { wrap: "truncate-end", children: [SPINNER_STATES.has(agent.state) ? (_jsx(Spinner, { color: pulseColor ?? spinnerColor(agent.state) })) : (_jsx(Text, { color: pulseColor ?? meta.color, bold: true, children: meta.mark })), _jsx(Text, { children: " " }), _jsx(Text, { color: agent.color, bold: true, children: name }), _jsxs(Text, { color: mode.color, children: [" [", mode.label, "]"] }), specialist ? _jsx(Text, { color: UI.note, children: specialist }) : null, _jsxs(Text, { color: UI.text, children: [" ", truncate(agent.task, taskMax)] })] }), _jsx(Text, { color: UI.muted, wrap: "truncate-end", children: truncate(quickActions, actionBudget) })] }), summary.length > 0 ? (_jsx(Box, { flexDirection: "column", children: summary.map((line, i) => (_jsxs(Box, { flexDirection: "row", justifyContent: i === 0 ? 'space-between' : undefined, children: [_jsxs(Text, { color: COLOR.cream, wrap: "truncate-end", children: [_jsx(Text, { color: COLOR.cream, children: "\u2022 " }), line] }), i === 0 ? _jsx(Text, { color: UI.muted, children: telemetry }) : null] }, `${i}-${line}`))) })) : line2 ? (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Text, { color: line2.color, wrap: "truncate-end", children: line2.text }), _jsx(Text, { color: UI.muted, children: telemetry })] })) : null, !agent.lastResult ? _jsx(ProgressSteps, { agent: agent, max: 3, cols: line2Max }) : null] }));
102
158
  }
103
159
  export function AgentTranscript({ agent, logs, raw = false, scrolled = 0, cols = 100, }) {
104
160
  const meta = STATE_META[agent.state];
105
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { children: [_jsx(Text, { color: agent.color, bold: true, children: agent.name }), agent.alias && agent.alias !== agent.name ? _jsxs(Text, { color: UI.muted, children: [" @", agent.alias] }) : null, _jsx(Text, { color: UI.muted, children: " " }), _jsxs(Text, { color: meta.color, bold: true, children: [meta.mark, " ", meta.label] })] }), _jsxs(Text, { color: UI.muted, wrap: "truncate-end", children: [agent.model, " \u00B7 ", formatAgentTelemetry(agent)] })] }), _jsxs(Text, { color: UI.muted, wrap: "wrap", children: ["Task ", _jsx(Text, { color: UI.text, children: agent.task })] }), agent.claims && agent.claims.length > 0 ? (_jsxs(Text, { color: UI.warn, wrap: "truncate-end", children: ["Claims ", agent.claims.join(' ')] })) : null, agent.currentAction ? (_jsxs(Text, { color: UI.accent, wrap: "truncate-end", children: ["Current ", truncate(agent.currentAction, 140)] })) : null, agent.state === 'done' || agent.lastResult ? _jsx(ResultBlock, { agent: agent }) : null, _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: UI.muted, bold: true, children: ["Activity", raw ? ' raw' : ''] }), _jsx(Timeline, { logs: logs, raw: raw, cols: cols })] }), _jsxs(Text, { color: UI.muted, wrap: "truncate-end", children: ["PgUp/PgDn scroll \u00B7 /raw toggles detail \u00B7 Esc returns", scrolled > 0 ? ` · ${scrolled} older` : ''] })] }));
161
+ const mode = modeBadge(agent.mode);
162
+ const shell = agent.perf && agent.perf.shellCommands > 0 ? ` · shell ${agent.perf.shellCommands}/${Math.round(agent.perf.shellMs / 1000)}s` : '';
163
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { children: [_jsx(Text, { color: agent.color, bold: true, children: agent.name }), agent.alias && agent.alias !== agent.name ? _jsxs(Text, { color: UI.muted, children: [" @", agent.alias] }) : null, _jsxs(Text, { color: mode.color, children: [" [", mode.label, "]"] }), _jsx(Text, { color: UI.muted, children: " " }), _jsxs(Text, { color: meta.color, bold: true, children: [meta.mark, " ", meta.label] })] }), _jsxs(Text, { color: UI.muted, wrap: "truncate-end", children: [agent.model, " \u00B7 ", formatAgentTelemetry(agent), shell] })] }), _jsxs(Text, { color: UI.muted, wrap: "wrap", children: ["Task ", _jsx(Text, { color: UI.text, children: agent.task })] }), agent.claims && agent.claims.length > 0 ? (_jsxs(Text, { color: UI.warn, wrap: "truncate-end", children: ["Claims ", agent.claims.join(' ')] })) : null, agent.currentAction ? (_jsxs(Text, { color: UI.accent, wrap: "truncate-end", children: ["Current ", truncate(agent.currentAction, 140)] })) : null, _jsx(ProgressSteps, { agent: agent, max: 6, cols: cols }), agent.state === 'done' || agent.lastResult ? _jsx(ResultBlock, { agent: agent }) : null, _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: UI.muted, bold: true, children: ["Activity", raw ? ' raw' : ''] }), _jsx(Timeline, { logs: logs, raw: raw, cols: cols })] }), _jsxs(Text, { color: UI.muted, wrap: "truncate-end", children: ["PgUp/PgDn scroll \u00B7 /raw toggles detail \u00B7 Esc returns", scrolled > 0 ? ` · ${scrolled} older` : ''] })] }));
106
164
  }
107
165
  export function AgentPanel({ agent, logs, width, expanded = false, }) {
108
166
  return (_jsx(Box, { width: width, flexDirection: "column", children: expanded ? _jsx(AgentTranscript, { agent: agent, logs: logs }) : _jsx(AgentRow, { agent: agent, logs: logs, cols: 100 }) }));