@parallel-cli/parallel 0.3.3 → 0.4.1
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 +156 -82
- package/dist/agents/agent.js +24 -2
- package/dist/agents/tools.js +4 -2
- package/dist/commands.js +179 -135
- package/dist/config.js +79 -0
- package/dist/controller.js +58 -5
- package/dist/i18n.js +304 -40
- package/dist/index.js +4 -2
- package/dist/pricing.js +27 -0
- package/dist/server.js +2 -1
- package/dist/ui/AgentPanel.js +85 -16
- package/dist/ui/App.js +285 -86
- package/dist/ui/AttachApp.js +46 -21
- package/dist/ui/CommandInput.js +56 -15
- package/dist/ui/SettingsPanel.js +170 -55
- package/dist/ui/Timeline.js +60 -0
- package/dist/ui/Wizard.js +13 -6
- package/dist/ui/events.js +229 -0
- package/dist/ui/theme.js +5 -4
- package/dist/ui/tokens.js +77 -0
- package/dist/ui/views.js +9 -3
- package/package.json +2 -2
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { oneLine } from './tokens.js';
|
|
2
|
+
function cleanToolText(text) {
|
|
3
|
+
return oneLine(text)
|
|
4
|
+
.replace(/^[📖📁🔍✏🚩🧠🧩📢⏳❓✉✅↳]+\s*/u, '')
|
|
5
|
+
.trim();
|
|
6
|
+
}
|
|
7
|
+
function stripShellNoise(command) {
|
|
8
|
+
return command
|
|
9
|
+
.replace(/\s+2>&1\b/g, '')
|
|
10
|
+
.replace(/\s+\|\s*cat\b/g, '')
|
|
11
|
+
.trim();
|
|
12
|
+
}
|
|
13
|
+
function classify(log) {
|
|
14
|
+
const text = oneLine(log.text);
|
|
15
|
+
const cleaned = cleanToolText(log.text);
|
|
16
|
+
const lower = cleaned.toLowerCase();
|
|
17
|
+
if (log.kind === 'error')
|
|
18
|
+
return { agentId: log.agentId, kind: 'error', label: 'error', detail: text, ts: log.ts, seq: log.seq };
|
|
19
|
+
if (log.kind === 'tool_result') {
|
|
20
|
+
return { agentId: log.agentId, kind: 'command_output', label: 'output', detail: log.text.trim(), ts: log.ts, seq: log.seq };
|
|
21
|
+
}
|
|
22
|
+
if (log.kind === 'note')
|
|
23
|
+
return { agentId: log.agentId, kind: 'note', label: 'note', detail: cleaned || text, ts: log.ts, seq: log.seq };
|
|
24
|
+
if (log.kind === 'system')
|
|
25
|
+
return { agentId: log.agentId, kind: 'system', label: 'system', detail: cleaned || text, ts: log.ts, seq: log.seq };
|
|
26
|
+
if (log.kind === 'llm')
|
|
27
|
+
return { agentId: log.agentId, kind: 'thought', label: 'thinking', detail: cleaned.replace(/^✻\s*/, ''), ts: log.ts, seq: log.seq };
|
|
28
|
+
if (/^\$\s*/.test(cleaned)) {
|
|
29
|
+
return {
|
|
30
|
+
agentId: log.agentId,
|
|
31
|
+
kind: 'command',
|
|
32
|
+
label: 'run',
|
|
33
|
+
detail: stripShellNoise(cleaned.replace(/^\$\s*/, '')),
|
|
34
|
+
ts: log.ts,
|
|
35
|
+
seq: log.seq,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (/^(read|opened)\s+/i.test(cleaned)) {
|
|
39
|
+
return { agentId: log.agentId, kind: 'file', label: 'read', detail: cleaned.replace(/^(read|opened)\s+/i, ''), ts: log.ts, seq: log.seq };
|
|
40
|
+
}
|
|
41
|
+
if (/^(ls|list)\s+/i.test(cleaned)) {
|
|
42
|
+
return { agentId: log.agentId, kind: 'file', label: 'list', detail: cleaned.replace(/^(ls|list)\s+/i, ''), ts: log.ts, seq: log.seq };
|
|
43
|
+
}
|
|
44
|
+
if (/^(search)\s+/i.test(cleaned)) {
|
|
45
|
+
return { agentId: log.agentId, kind: 'file', label: 'search', detail: cleaned.replace(/^search\s+/i, ''), ts: log.ts, seq: log.seq };
|
|
46
|
+
}
|
|
47
|
+
if (/^(write|edit|patch|claim|claims?)\s+/i.test(cleaned)) {
|
|
48
|
+
const label = lower.startsWith('claim') ? 'claim' : lower.startsWith('write') ? 'write' : 'edit';
|
|
49
|
+
return { agentId: log.agentId, kind: 'file', label, detail: cleaned.replace(/^(write|edit|patch|claim|claims?)\s*/i, ''), ts: log.ts, seq: log.seq };
|
|
50
|
+
}
|
|
51
|
+
if (/^(run|exec|shell|npm|pnpm|yarn|git|node|npx)\b/i.test(cleaned)) {
|
|
52
|
+
return { agentId: log.agentId, kind: 'command', label: 'run', detail: stripShellNoise(cleaned), ts: log.ts, seq: log.seq };
|
|
53
|
+
}
|
|
54
|
+
if (lower.includes('approval') || lower.includes('approve')) {
|
|
55
|
+
return { agentId: log.agentId, kind: 'approval', label: 'approval', detail: cleaned || text, ts: log.ts, seq: log.seq };
|
|
56
|
+
}
|
|
57
|
+
if (lower.includes('ask') || lower.includes('question')) {
|
|
58
|
+
return { agentId: log.agentId, kind: 'question', label: 'question', detail: cleaned || text, ts: log.ts, seq: log.seq };
|
|
59
|
+
}
|
|
60
|
+
return { agentId: log.agentId, kind: log.kind === 'tool' ? 'tool' : 'system', label: log.kind === 'tool' ? 'tool' : 'info', detail: cleaned || text, ts: log.ts, seq: log.seq };
|
|
61
|
+
}
|
|
62
|
+
export function toUIEvents(logs) {
|
|
63
|
+
return logs.map(classify);
|
|
64
|
+
}
|
|
65
|
+
export function compactEvents(events) {
|
|
66
|
+
const out = [];
|
|
67
|
+
for (let i = 0; i < events.length; i++) {
|
|
68
|
+
const e = events[i];
|
|
69
|
+
if (e.kind !== 'file' || e.label !== 'read') {
|
|
70
|
+
out.push(e);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const reads = [e];
|
|
74
|
+
while (i + 1 < events.length && events[i + 1].kind === 'file' && events[i + 1].label === 'read') {
|
|
75
|
+
reads.push(events[++i]);
|
|
76
|
+
}
|
|
77
|
+
if (reads.length < 3) {
|
|
78
|
+
out.push(...reads);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const files = reads.flatMap((r) => r.detail.split(/\s+/).filter(Boolean));
|
|
82
|
+
out.push({
|
|
83
|
+
...e,
|
|
84
|
+
detail: `${files.slice(0, 5).join(', ')}${files.length > 5 ? ` +${files.length - 5}` : ''}`,
|
|
85
|
+
label: `read ${files.length}`,
|
|
86
|
+
ts: reads[reads.length - 1].ts,
|
|
87
|
+
seq: reads[reads.length - 1].seq,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
function commandCategory(command) {
|
|
93
|
+
if (/\b(test|build|tsc|lint|check|node\s+--test)\b/i.test(command))
|
|
94
|
+
return 'validate';
|
|
95
|
+
if (/\bgit\s+(add|commit|push|pull|fetch|merge|switch|checkout|branch)\b/i.test(command))
|
|
96
|
+
return 'publish';
|
|
97
|
+
if (/\bgit\s+(status|log|show|diff)\b/i.test(command))
|
|
98
|
+
return 'inspect';
|
|
99
|
+
if (/\b(find|pwd|ls|cat|sed|rg|grep|head|tail)\b/i.test(command))
|
|
100
|
+
return 'inspect';
|
|
101
|
+
return 'validate';
|
|
102
|
+
}
|
|
103
|
+
function categoryFor(e) {
|
|
104
|
+
if (e.kind === 'error')
|
|
105
|
+
return 'result';
|
|
106
|
+
if (e.kind === 'note' || e.kind === 'approval' || e.kind === 'question')
|
|
107
|
+
return 'coordinate';
|
|
108
|
+
if (e.kind === 'file') {
|
|
109
|
+
if (e.label === 'write' || e.label === 'edit' || e.label === 'claim')
|
|
110
|
+
return 'change';
|
|
111
|
+
return 'inspect';
|
|
112
|
+
}
|
|
113
|
+
if (e.kind === 'command') {
|
|
114
|
+
return commandCategory(e.detail);
|
|
115
|
+
}
|
|
116
|
+
return 'other';
|
|
117
|
+
}
|
|
118
|
+
function filesFrom(events) {
|
|
119
|
+
return events.flatMap((e) => e.detail.split(/[,\s]+/).filter(Boolean));
|
|
120
|
+
}
|
|
121
|
+
export function summarizeCommandOutput(output, command = '', maxLines = 6) {
|
|
122
|
+
const rawLines = output
|
|
123
|
+
.replace(/\r/g, '')
|
|
124
|
+
.split('\n')
|
|
125
|
+
.map((l) => l.trimEnd())
|
|
126
|
+
.filter((l) => l.trim().length > 0);
|
|
127
|
+
const status = /\b(error|failed|fatal|exit code: [1-9]|not found|denied)\b/i.test(output) ? 'error' : 'ok';
|
|
128
|
+
if (/npm\s+test|node\s+--test|test:pty|tsc|npm\s+run\s+build/i.test(command) && status === 'ok') {
|
|
129
|
+
const passed = rawLines.find((l) => /passed|pass|ok\b/i.test(l));
|
|
130
|
+
return { lines: [passed ? `Passed: ${passed.replace(/^#\s*/, '')}` : 'Passed'], hiddenLines: Math.max(0, rawLines.length - 1), status };
|
|
131
|
+
}
|
|
132
|
+
if (rawLines.length <= maxLines)
|
|
133
|
+
return { lines: rawLines.length > 0 ? rawLines : ['(no output, success)'], hiddenLines: 0, status };
|
|
134
|
+
const head = Math.max(1, Math.floor(maxLines / 2));
|
|
135
|
+
const tail = Math.max(1, maxLines - head);
|
|
136
|
+
return {
|
|
137
|
+
lines: [...rawLines.slice(0, head), ...rawLines.slice(rawLines.length - tail)],
|
|
138
|
+
hiddenLines: rawLines.length - maxLines,
|
|
139
|
+
status,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function narrationFor(category, previous) {
|
|
143
|
+
if (category === 'inspect' && previous === 'validate')
|
|
144
|
+
return 'Cette piste ne suffit pas, donc je reviens inspecter le projet pour confirmer l’état réel.';
|
|
145
|
+
if (category === 'inspect')
|
|
146
|
+
return 'Je vérifie l’état du projet et les fichiers concernés avant de conclure.';
|
|
147
|
+
if (category === 'change')
|
|
148
|
+
return 'Je modifie maintenant les fichiers ciblés en gardant le changement aussi petit que possible.';
|
|
149
|
+
if (category === 'validate')
|
|
150
|
+
return 'Je lance les validations locales pour vérifier que les changements tiennent techniquement.';
|
|
151
|
+
if (category === 'publish')
|
|
152
|
+
return 'Je prépare la synchronisation Git après avoir vérifié l’état local.';
|
|
153
|
+
if (category === 'coordinate')
|
|
154
|
+
return 'Je traite les échanges et les décisions nécessaires pour avancer proprement.';
|
|
155
|
+
if (category === 'result')
|
|
156
|
+
return 'Je vérifie le résultat final et les éventuelles erreurs importantes.';
|
|
157
|
+
return 'Je poursuis l’activité en cours.';
|
|
158
|
+
}
|
|
159
|
+
function pushSection(out, category, ts, seq) {
|
|
160
|
+
const prev = [...out].reverse().find((i) => i.kind !== 'section');
|
|
161
|
+
if (category === 'other')
|
|
162
|
+
return;
|
|
163
|
+
if (!prev) {
|
|
164
|
+
out.push({ kind: 'narration', category, label: 'narration', detail: narrationFor(category), ts, seq });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (prev.category === category)
|
|
168
|
+
return;
|
|
169
|
+
out.push({ kind: 'section', category, label: category, ts, seq });
|
|
170
|
+
out.push({ kind: 'narration', category, label: 'narration', detail: narrationFor(category, prev.category), ts, seq });
|
|
171
|
+
}
|
|
172
|
+
export function presentTimeline(logs, options = {}) {
|
|
173
|
+
const events = options.raw ? toUIEvents(logs) : toUIEvents(logs).filter((e) => e.kind !== 'thought');
|
|
174
|
+
const out = [];
|
|
175
|
+
for (let i = 0; i < events.length; i++) {
|
|
176
|
+
const e = events[i];
|
|
177
|
+
const category = categoryFor(e);
|
|
178
|
+
pushSection(out, category, e.ts, e.seq);
|
|
179
|
+
if (options.raw && e.kind === 'thought') {
|
|
180
|
+
out.push({ kind: 'thought', category, label: e.label, detail: e.detail, ts: e.ts, seq: e.seq });
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (e.kind === 'file') {
|
|
184
|
+
const group = [e];
|
|
185
|
+
while (i + 1 < events.length && events[i + 1].kind === 'file' && events[i + 1].label === e.label) {
|
|
186
|
+
group.push(events[++i]);
|
|
187
|
+
}
|
|
188
|
+
const files = filesFrom(group);
|
|
189
|
+
out.push({ kind: 'files', category, label: e.label, files, ts: group[group.length - 1].ts, seq: group[group.length - 1].seq });
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (e.kind === 'command') {
|
|
193
|
+
let output;
|
|
194
|
+
if (i + 1 < events.length && events[i + 1].kind === 'command_output') {
|
|
195
|
+
output = summarizeCommandOutput(events[++i].detail, e.detail, options.outputLines ?? 6);
|
|
196
|
+
}
|
|
197
|
+
out.push({
|
|
198
|
+
kind: 'command',
|
|
199
|
+
category,
|
|
200
|
+
label: 'run',
|
|
201
|
+
command: e.detail,
|
|
202
|
+
output: output?.lines,
|
|
203
|
+
hiddenLines: output?.hiddenLines,
|
|
204
|
+
status: output?.status,
|
|
205
|
+
ts: e.ts,
|
|
206
|
+
seq: e.seq,
|
|
207
|
+
});
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (e.kind === 'command_output' && options.raw) {
|
|
211
|
+
const output = summarizeCommandOutput(e.detail, '', options.outputLines ?? 8);
|
|
212
|
+
out.push({ kind: 'event', category, label: 'output', output: output.lines, hiddenLines: output.hiddenLines, status: output.status, ts: e.ts, seq: e.seq });
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
out.push({ kind: 'event', category, label: e.label, detail: e.detail, status: e.kind === 'error' ? 'error' : 'ok', ts: e.ts, seq: e.seq });
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
export function latestSignal(agent, events) {
|
|
220
|
+
if (agent.currentAction)
|
|
221
|
+
return agent.currentAction;
|
|
222
|
+
const last = [...events].reverse().find((e) => {
|
|
223
|
+
const item = e;
|
|
224
|
+
return item.kind !== 'thought' && Boolean(item.detail || item.command || item.files?.length);
|
|
225
|
+
});
|
|
226
|
+
if (last)
|
|
227
|
+
return last.command ? `run ${last.command}` : last.files?.length ? `${last.label} ${last.files[0]}` : `${last.label} ${last.detail}`;
|
|
228
|
+
return agent.lastResult ? 'result ready' : agent.task;
|
|
229
|
+
}
|
package/dist/ui/theme.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { t } from '../i18n.js';
|
|
2
|
+
import { STATE } from './tokens.js';
|
|
2
3
|
/** Strong visual cues: icon + label (i18n key) + color per state. */
|
|
3
4
|
export const STATE_LABEL = {
|
|
4
|
-
idle: { icon: '◇', labelKey: 'st.idle', color:
|
|
5
|
-
thinking: { icon: '🧠', labelKey: 'st.thinking', color:
|
|
5
|
+
idle: { icon: '◇', labelKey: 'st.idle', color: STATE.idle },
|
|
6
|
+
thinking: { icon: '🧠', labelKey: 'st.thinking', color: STATE.thinking },
|
|
6
7
|
listening: { icon: '👂', labelKey: 'st.listening', color: 'cyanBright' },
|
|
7
8
|
working: { icon: '🔨', labelKey: 'st.working', color: 'green' },
|
|
8
9
|
waiting: { icon: '✋', labelKey: 'st.waiting', color: 'magenta' },
|
|
9
10
|
paused: { icon: '⏸', labelKey: 'st.paused', color: 'blue' },
|
|
10
|
-
done: { icon: '✅', labelKey: 'st.done', color:
|
|
11
|
+
done: { icon: '✅', labelKey: 'st.done', color: STATE.done },
|
|
11
12
|
error: { icon: '✖', labelKey: 'st.error', color: 'red' },
|
|
12
|
-
stopped: { icon: '⏹', labelKey: 'st.stopped', color:
|
|
13
|
+
stopped: { icon: '⏹', labelKey: 'st.stopped', color: STATE.error },
|
|
13
14
|
};
|
|
14
15
|
export function stateLabel(state) {
|
|
15
16
|
return t(STATE_LABEL[state].labelKey);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export const MARK = {
|
|
2
|
+
active: '●',
|
|
3
|
+
idle: '◌',
|
|
4
|
+
done: '✓',
|
|
5
|
+
error: '!',
|
|
6
|
+
waiting: '?',
|
|
7
|
+
arrow: '▸',
|
|
8
|
+
};
|
|
9
|
+
export const UI = {
|
|
10
|
+
brand: 'cyanBright',
|
|
11
|
+
accent: 'cyan',
|
|
12
|
+
muted: 'gray',
|
|
13
|
+
text: 'white',
|
|
14
|
+
ok: 'greenBright',
|
|
15
|
+
warn: 'yellow',
|
|
16
|
+
danger: 'redBright',
|
|
17
|
+
note: 'magentaBright',
|
|
18
|
+
};
|
|
19
|
+
export const STATE_META = {
|
|
20
|
+
waiting: { mark: MARK.waiting, label: 'needs input', color: UI.warn, rank: 0 },
|
|
21
|
+
paused: { mark: MARK.waiting, label: 'paused', color: UI.warn, rank: 1 },
|
|
22
|
+
listening: { mark: MARK.active, label: 'listening', color: UI.accent, rank: 2 },
|
|
23
|
+
thinking: { mark: MARK.active, label: 'thinking', color: UI.accent, rank: 3 },
|
|
24
|
+
working: { mark: MARK.active, label: 'working', color: UI.accent, rank: 4 },
|
|
25
|
+
error: { mark: MARK.error, label: 'error', color: UI.danger, rank: 5 },
|
|
26
|
+
stopped: { mark: MARK.error, label: 'stopped', color: UI.danger, rank: 6 },
|
|
27
|
+
done: { mark: MARK.done, label: 'done', color: UI.ok, rank: 7 },
|
|
28
|
+
idle: { mark: MARK.idle, label: 'idle', color: UI.muted, rank: 8 },
|
|
29
|
+
};
|
|
30
|
+
// ─── Hub redesign tokens (Phase 0) ───────────────────────────────────────────
|
|
31
|
+
/** Brand colors — logotype, borders, focus indicator. */
|
|
32
|
+
export const BRAND = {
|
|
33
|
+
primary: 'cyanBright',
|
|
34
|
+
muted: 'cyan',
|
|
35
|
+
};
|
|
36
|
+
/** Semantic state colors mapped to agent states. */
|
|
37
|
+
export const STATE = {
|
|
38
|
+
working: 'cyan',
|
|
39
|
+
thinking: 'yellow',
|
|
40
|
+
listening: 'yellow',
|
|
41
|
+
done: 'greenBright',
|
|
42
|
+
error: 'redBright',
|
|
43
|
+
waiting: 'yellow',
|
|
44
|
+
idle: 'gray',
|
|
45
|
+
};
|
|
46
|
+
/** Mode indicator colors. `task` is undefined — it renders no mark. */
|
|
47
|
+
export const MODE = {
|
|
48
|
+
ask: 'yellow',
|
|
49
|
+
plan: 'blue',
|
|
50
|
+
task: undefined,
|
|
51
|
+
};
|
|
52
|
+
/** Chrome / UI element colors. */
|
|
53
|
+
export const CHROME = {
|
|
54
|
+
separator: 'gray',
|
|
55
|
+
muted: 'gray',
|
|
56
|
+
dim: 'dim',
|
|
57
|
+
};
|
|
58
|
+
/** Animation tokens. */
|
|
59
|
+
export const ANIM = {
|
|
60
|
+
spinner: 'dots',
|
|
61
|
+
pulseMs: 400,
|
|
62
|
+
spinnerIntervalMs: 80,
|
|
63
|
+
};
|
|
64
|
+
/** Plain 7-bit ASCII logotype — renders in every terminal. */
|
|
65
|
+
export const ASCII_LOGO = 'PARALLEL';
|
|
66
|
+
export function middleTruncate(text, max) {
|
|
67
|
+
if (text.length <= max)
|
|
68
|
+
return text;
|
|
69
|
+
if (max <= 3)
|
|
70
|
+
return text.slice(0, max);
|
|
71
|
+
const left = Math.ceil((max - 1) / 2);
|
|
72
|
+
const right = Math.floor((max - 1) / 2);
|
|
73
|
+
return `${text.slice(0, left)}…${text.slice(text.length - right)}`;
|
|
74
|
+
}
|
|
75
|
+
export function oneLine(text) {
|
|
76
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
77
|
+
}
|
package/dist/ui/views.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Box, Text, useInput, useStdout } from 'ink';
|
|
4
4
|
import * as Diff from 'diff';
|
|
5
|
-
import {
|
|
5
|
+
import { visibleCommands } from '../commands.js';
|
|
6
6
|
import { Controller } from '../controller.js';
|
|
7
7
|
import { fmtCost } from '../pricing.js';
|
|
8
8
|
import { STATE_LABEL, stateLabel, truncate } from './theme.js';
|
|
@@ -89,6 +89,12 @@ export function SessionsView({ projectRoot }) {
|
|
|
89
89
|
export function HelpView() {
|
|
90
90
|
// Intro (4) + title + blank lines (2) + footer (2) + border/input/status ≈ 16 rows of overhead.
|
|
91
91
|
const visible = useVisibleRows(16);
|
|
92
|
-
const
|
|
93
|
-
|
|
92
|
+
const commands = visibleCommands();
|
|
93
|
+
const { slice, above, below } = useScrollWindow(commands, visible, 'top');
|
|
94
|
+
const highlights = [
|
|
95
|
+
['Agent modes', ['/ask', '/task', '/plan']],
|
|
96
|
+
['Shell approvals', ['/approvals ask', '/approvals auto', '/approvals yolo']],
|
|
97
|
+
['Navigation', ['/focus', '/attach', '/raw', '/send']],
|
|
98
|
+
];
|
|
99
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: t('help.title') }), _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { bold: true, children: t('help.l1a') }), t('help.l1b'), _jsx(Text, { bold: true, children: t('help.l1c') }), "."] }), _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { bold: true, children: t('help.l2a') }), t('help.l2b'), _jsx(Text, { bold: true, children: t('help.l2c') }), t('help.l2d')] }), _jsx(Text, { wrap: "truncate-end", children: t('help.l3') }), _jsx(Text, { children: " " }), highlights.map(([label, names]) => (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { color: "cyan", bold: true, children: [label, ": "] }), _jsx(Text, { color: "gray", children: names.join(' ') })] }, label))), _jsx(Text, { color: "gray", wrap: "truncate-end", children: "Keyboard: Tab/\u2192 autocomplete \u00B7 Esc back/clear \u00B7 PgUp/PgDn scroll \u00B7 Ctrl+U clear \u00B7 Ctrl+V image" }), _jsx(Text, { children: " " }), _jsx(Above, { n: above }), slice.map((c) => (_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: "cyan", bold: true, children: c.name.padEnd(18) }), _jsx(Text, { color: "yellow", children: c.args.padEnd(24) }), _jsxs(Text, { color: "gray", children: [t(c.descKey), c.aliases?.length ? ` (= ${c.aliases.join(', ')})` : ''] })] }, c.name))), _jsx(Below, { n: below }), _jsx(Text, { children: " " }), _jsx(Text, { color: "gray", wrap: "truncate-end", children: t('help.states') }), _jsx(Text, { color: "gray", wrap: "truncate-end", children: t('help.keys') })] }));
|
|
94
100
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parallel-cli/parallel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Real-time multi-agent coding CLI with shared context, adaptive co-editing, dedicated agent terminals, and headless CI runs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsc && chmod +x dist/index.js",
|
|
41
41
|
"dev": "tsc --watch",
|
|
42
|
-
"test": "npm run build && node --test test/*.test.mjs && npm run test:pty",
|
|
42
|
+
"test": "npm run build && ( [ -d test ] && node --test test/*.test.mjs && npm run test:pty || echo 'test/ directory not found, skipping tests' )",
|
|
43
43
|
"test:pty": "sh test/pty.sh",
|
|
44
44
|
"start": "node dist/index.js",
|
|
45
45
|
"prepublishOnly": "npm run build"
|