@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
package/dist/index.js
CHANGED
|
@@ -101,8 +101,8 @@ if (headless) {
|
|
|
101
101
|
if (config.language)
|
|
102
102
|
setLang(config.language);
|
|
103
103
|
const ctl = new Controller(config, process.cwd());
|
|
104
|
-
// No human in the loop: commands are auto-approved
|
|
105
|
-
ctl.setSessionApprovalMode('
|
|
104
|
+
// No human in the loop: commands are auto-approved.
|
|
105
|
+
ctl.setSessionApprovalMode('yolo');
|
|
106
106
|
const provider = ctl.sessionProvider();
|
|
107
107
|
if (!provider || !provider.apiKey) {
|
|
108
108
|
console.error('Headless mode needs a configured provider + API key. Run `parallel` interactively once, or set PARALLEL_API_KEY.');
|
|
@@ -177,9 +177,11 @@ const useAltScreen = process.stdout.isTTY && process.env.PARALLEL_NO_ALT_SCREEN
|
|
|
177
177
|
const restoreTerminal = () => {
|
|
178
178
|
if (!useAltScreen)
|
|
179
179
|
return;
|
|
180
|
+
// Show cursor + leave alternate screen.
|
|
180
181
|
process.stdout.write('\x1b[?25h\x1b[?1049l');
|
|
181
182
|
};
|
|
182
183
|
if (useAltScreen) {
|
|
184
|
+
// Alternate screen + clear.
|
|
183
185
|
process.stdout.write('\x1b[?1049h\x1b[2J\x1b[3J\x1b[H');
|
|
184
186
|
process.once('exit', restoreTerminal);
|
|
185
187
|
process.once('SIGINT', () => {
|
package/dist/pricing.js
CHANGED
|
@@ -30,6 +30,33 @@ const BUILTIN = {
|
|
|
30
30
|
// Alibaba
|
|
31
31
|
'qwen2.5-coder': { input: 0.09, output: 0.09 },
|
|
32
32
|
'qwen-max': { input: 1.6, output: 6.4 },
|
|
33
|
+
// xAI (Grok)
|
|
34
|
+
'grok-4': { input: 4.0, output: 16.0 },
|
|
35
|
+
'grok-3-beta': { input: 3.0, output: 12.0 },
|
|
36
|
+
'grok-3-mini': { input: 0.55, output: 2.2 },
|
|
37
|
+
// Perplexity
|
|
38
|
+
'sonar-pro': { input: 3.0, output: 15.0 },
|
|
39
|
+
'sonar': { input: 1.0, output: 1.0 },
|
|
40
|
+
'sonar-reasoning': { input: 2.0, output: 16.0 },
|
|
41
|
+
// Cohere
|
|
42
|
+
'command-a': { input: 2.5, output: 10.0 },
|
|
43
|
+
'command-r-plus': { input: 2.5, output: 10.0 },
|
|
44
|
+
'command-r': { input: 0.5, output: 1.5 },
|
|
45
|
+
// DeepInfra
|
|
46
|
+
'llama-4-maverick': { input: 0.2, output: 0.6 }, // approximate
|
|
47
|
+
'wizardlm-2-8x22b': { input: 0.5, output: 0.5 }, // approximate
|
|
48
|
+
// Fireworks
|
|
49
|
+
'llama-4-scout': { input: 0.1, output: 0.3 }, // approximate
|
|
50
|
+
'mixtral-8x22b': { input: 0.9, output: 0.9 }, // approximate
|
|
51
|
+
// Cerebras
|
|
52
|
+
'llama-3.3-70b @cerebras': { input: 0.5, output: 1.5 }, // approximate
|
|
53
|
+
'llama-3.1-8b': { input: 0.05, output: 0.1 }, // approximate
|
|
54
|
+
// Novita
|
|
55
|
+
'deepseek-r1': { input: 2.0, output: 8.0 },
|
|
56
|
+
'deepseek-v3': { input: 1.25, output: 5.0 },
|
|
57
|
+
'llama-3.1-70b': { input: 0.35, output: 0.4 }, // approximate
|
|
58
|
+
// Hyperbolic
|
|
59
|
+
'qwen3-235b': { input: 0.5, output: 1.5 }, // approximate
|
|
33
60
|
// Local endpoints are free
|
|
34
61
|
'ollama': { input: 0, output: 0 },
|
|
35
62
|
};
|
package/dist/server.js
CHANGED
|
@@ -18,6 +18,7 @@ export function startSessionServer(ctl) {
|
|
|
18
18
|
}
|
|
19
19
|
const clients = new Set();
|
|
20
20
|
const infoFor = (ref) => ctl.board.getAgentByName(ref);
|
|
21
|
+
const spawnMode = (mode) => (mode === 'ask' || mode === 'plan' || mode === 'task' ? mode : 'task');
|
|
21
22
|
const send = (socket, msg) => {
|
|
22
23
|
try {
|
|
23
24
|
socket.write(JSON.stringify(msg) + '\n');
|
|
@@ -112,7 +113,7 @@ export function startSessionServer(ctl) {
|
|
|
112
113
|
// its own dedicated terminal then opens automatically.
|
|
113
114
|
const task = msg.text.trim();
|
|
114
115
|
if (task)
|
|
115
|
-
ctl.spawnAgent(task);
|
|
116
|
+
ctl.spawnAgent(task, undefined, undefined, undefined, undefined, undefined, spawnMode(msg.mode));
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
});
|
package/dist/ui/AgentPanel.js
CHANGED
|
@@ -1,25 +1,94 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState, useEffect } from 'react';
|
|
2
3
|
import { Box, Text } from 'ink';
|
|
3
|
-
import { Spinner } from './Spinner.js';
|
|
4
4
|
import { fmtCost } from '../pricing.js';
|
|
5
|
-
import {
|
|
5
|
+
import { elapsed, truncate } from './theme.js';
|
|
6
6
|
import { Md } from './Md.js';
|
|
7
|
-
import {
|
|
7
|
+
import { Spinner } from './Spinner.js';
|
|
8
|
+
import { Timeline } from './Timeline.js';
|
|
9
|
+
import { MARK, MODE, STATE_META, UI, ANIM } from './tokens.js';
|
|
8
10
|
export const KIND_COLOR = {
|
|
9
|
-
tool:
|
|
10
|
-
llm:
|
|
11
|
-
error:
|
|
12
|
-
note:
|
|
13
|
-
system:
|
|
14
|
-
info:
|
|
11
|
+
tool: UI.accent,
|
|
12
|
+
llm: UI.muted,
|
|
13
|
+
error: UI.danger,
|
|
14
|
+
note: UI.note,
|
|
15
|
+
system: UI.warn,
|
|
16
|
+
info: UI.text,
|
|
15
17
|
};
|
|
16
|
-
/** Thinking/commentary lines (kind 'llm') are dimmed + italic, à la Codex/Claude Code. */
|
|
17
18
|
export const KIND_DIM = { llm: true };
|
|
19
|
+
export function cleanHubSummary(text) {
|
|
20
|
+
return text
|
|
21
|
+
.replace(/^#{1,6}\s+/gm, '')
|
|
22
|
+
.replace(/\*\*(.*?)\*\*/g, '$1')
|
|
23
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
24
|
+
.replace(/^\s*[-*]\s+/gm, '')
|
|
25
|
+
.replace(/\s+/g, ' ')
|
|
26
|
+
.trim();
|
|
27
|
+
}
|
|
28
|
+
export function formatAgentTelemetry(agent) {
|
|
29
|
+
return `${elapsed(agent.startedAt)} · ${agent.cost === null ? '$-' : fmtCost(agent.cost)}`;
|
|
30
|
+
}
|
|
31
|
+
function ResultBlock({ agent, compact = false }) {
|
|
32
|
+
if (!agent.lastResult)
|
|
33
|
+
return null;
|
|
34
|
+
if (compact) {
|
|
35
|
+
return (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { color: UI.ok, children: [MARK.done, " "] }), _jsx(Text, { children: truncate(agent.lastResult, 110) })] }));
|
|
36
|
+
}
|
|
37
|
+
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 })] }));
|
|
38
|
+
}
|
|
39
|
+
const SPINNER_STATES = new Set(['thinking', 'working', 'listening', 'waiting']);
|
|
40
|
+
function spinnerColor(state) {
|
|
41
|
+
if (state === 'working')
|
|
42
|
+
return 'cyan';
|
|
43
|
+
return 'yellow'; // thinking, listening, waiting
|
|
44
|
+
}
|
|
45
|
+
function modeChar(mode) {
|
|
46
|
+
if (mode === 'ask')
|
|
47
|
+
return { char: '?', color: MODE.ask };
|
|
48
|
+
if (mode === 'plan')
|
|
49
|
+
return { char: '△', color: MODE.plan };
|
|
50
|
+
return null; // task = no mark
|
|
51
|
+
}
|
|
52
|
+
function agentDisplayName(agent) {
|
|
53
|
+
return agent.alias && agent.alias !== agent.name ? `${agent.alias} ${agent.name}` : agent.alias || agent.name;
|
|
54
|
+
}
|
|
55
|
+
export function AgentRow({ agent, logs, cols, }) {
|
|
56
|
+
const meta = STATE_META[agent.state];
|
|
57
|
+
// ── State transition pulse (Phase 5) ──
|
|
58
|
+
const prevState = useRef(agent.state);
|
|
59
|
+
const [pulse, setPulse] = useState(false);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (agent.state !== prevState.current) {
|
|
62
|
+
setPulse(true);
|
|
63
|
+
const timer = setTimeout(() => setPulse(false), ANIM.pulseMs);
|
|
64
|
+
prevState.current = agent.state;
|
|
65
|
+
return () => clearTimeout(timer);
|
|
66
|
+
}
|
|
67
|
+
}, [agent.state]);
|
|
68
|
+
// Pulse bumps the mark/spinner color to whiteBright for 400ms
|
|
69
|
+
const pulseColor = pulse ? 'whiteBright' : null;
|
|
70
|
+
const name = agentDisplayName(agent);
|
|
71
|
+
const mode = modeChar(agent.mode);
|
|
72
|
+
const taskMax = Math.max(10, cols - 18);
|
|
73
|
+
const line2Max = Math.max(10, cols - 2);
|
|
74
|
+
const telemetry = formatAgentTelemetry(agent);
|
|
75
|
+
// Line 2 content
|
|
76
|
+
let line2 = null;
|
|
77
|
+
if (agent.lastResult) {
|
|
78
|
+
line2 = { text: `✓ ${truncate(cleanHubSummary(agent.lastResult), line2Max)}`, color: UI.ok };
|
|
79
|
+
}
|
|
80
|
+
else if (agent.currentAction) {
|
|
81
|
+
line2 = { text: `▸ ${truncate(agent.currentAction, line2Max)}`, color: UI.accent };
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
line2 = { text: meta.label, color: meta.color };
|
|
85
|
+
}
|
|
86
|
+
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, _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 })] })] }));
|
|
87
|
+
}
|
|
88
|
+
export function AgentTranscript({ agent, logs, raw = false, scrolled = 0, }) {
|
|
89
|
+
const meta = STATE_META[agent.state];
|
|
90
|
+
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 })] }), _jsxs(Text, { color: UI.muted, wrap: "truncate-end", children: ["PgUp/PgDn scroll \u00B7 /raw toggles detail \u00B7 Esc returns", scrolled > 0 ? ` · ${scrolled} older` : ''] })] }));
|
|
91
|
+
}
|
|
18
92
|
export function AgentPanel({ agent, logs, width, expanded = false, }) {
|
|
19
|
-
|
|
20
|
-
const busy = agent.state === 'thinking' || agent.state === 'working' || agent.state === 'listening';
|
|
21
|
-
return (_jsx(Box, { width: width, paddingX: 0, children: _jsxs(Box, { borderStyle: agent.state === 'listening' ? 'double' : 'round', borderColor: agent.state === 'error' ? 'red' : agent.state === 'listening' ? 'cyanBright' : agent.color, flexDirection: "column", paddingX: 1, flexGrow: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsxs(Text, { color: agent.color, bold: true, children: ["\u25C6 ", agent.name, agent.alias && agent.alias !== agent.name ? _jsxs(Text, { color: "gray", children: [" @", agent.alias] }) : null, ' '] }), _jsxs(Text, { backgroundColor: st.color, color: "black", bold: true, children: [' ', st.icon, " ", stateLabel(agent.state), ' '] }), busy && (_jsxs(Text, { children: [' ', _jsx(Spinner, { color: agent.color })] }))] }), _jsxs(Text, { color: "gray", wrap: "truncate-end", children: [agent.specialist ? `🎓${agent.specialist} · ` : '', truncate(agent.model, 18), " \u00B7 ", elapsed(agent.startedAt), " \u00B7 ", agent.steps, " st \u00B7", ' ', Math.round((agent.tokensIn + agent.tokensOut) / 1000), "k \u00B7", ' ', agent.ctxPct !== undefined ? (_jsxs(Text, { color: agent.ctxPct >= 90 ? 'redBright' : agent.ctxPct >= 70 ? 'yellowBright' : 'gray', children: ["\u25D4", agent.ctxPct, "% \u00B7", ' '] })) : null, _jsx(Text, { color: "greenBright", children: agent.cost === null ? '$—' : fmtCost(agent.cost) })] })] }), _jsxs(Text, { color: "gray", wrap: expanded ? 'wrap' : 'truncate-end', children: ["\u25E6 ", expanded ? agent.task : truncate(agent.task, 120)] }), agent.claims && agent.claims.length > 0 ? (_jsxs(Text, { color: "yellowBright", wrap: "truncate-end", children: ["\uD83D\uDEA9 ", agent.claims.join(' ')] })) : null, agent.currentAction ? (_jsxs(Text, { color: agent.color, wrap: "truncate-end", children: ["\u25B8 ", truncate(agent.currentAction, 120)] })) : null, agent.lastResult ? (_jsxs(Box, { borderStyle: "single", borderColor: "gray", flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: "greenBright", bold: true, children: t('agent.summary') }), expanded || agent.state === 'done' ? (
|
|
22
|
-
// A finished agent's summary is the deliverable: show it ENTIRELY,
|
|
23
|
-
// wrapped and lightly formatted — never truncated.
|
|
24
|
-
_jsx(Md, { text: agent.lastResult })) : (_jsx(Text, { color: "white", wrap: "truncate-end", children: truncate(agent.lastResult, 260) }))] })) : null, _jsx(Box, { flexDirection: "column", marginTop: 0, children: logs.map((l, i) => (_jsx(Text, { color: KIND_COLOR[l.kind] ?? 'white', italic: KIND_DIM[l.kind] ?? false, wrap: "truncate-end", children: truncate(l.text, expanded ? 220 : 140) }, i))) })] }) }));
|
|
93
|
+
return (_jsx(Box, { width: width, flexDirection: "column", children: expanded ? _jsx(AgentTranscript, { agent: agent, logs: logs }) : _jsx(AgentRow, { agent: agent, logs: logs, cols: 100 }) }));
|
|
25
94
|
}
|