@parallel-cli/parallel 0.4.1 → 0.4.4
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/CHANGELOG.md +149 -0
- package/README.md +183 -195
- package/dist/agents/agent.js +2 -2
- package/dist/agents/tools.js +47 -5
- package/dist/commands.js +117 -18
- package/dist/config.js +248 -63
- package/dist/controller.js +48 -17
- package/dist/i18n.js +192 -44
- package/dist/index.js +8 -5
- package/dist/pricing.js +162 -54
- package/dist/ui/App.js +208 -102
- package/dist/ui/CommandInput.js +42 -17
- package/dist/ui/SettingsPanel.js +224 -34
- package/dist/ui/Wizard.js +33 -3
- package/dist/ui/views.js +53 -21
- package/package.json +10 -1
package/dist/ui/views.js
CHANGED
|
@@ -18,19 +18,27 @@ function useScrollWindow(items, visible, anchor = 'top') {
|
|
|
18
18
|
const s = Math.min(scroll, max);
|
|
19
19
|
const step = Math.max(1, visible - 1);
|
|
20
20
|
useInput((_input, key) => {
|
|
21
|
-
const towardsAnchor = (v) => Math.max(0, Math.min(v, max) -
|
|
22
|
-
const awayFromAnchor = (v) => Math.min(Math.min(v, max) +
|
|
21
|
+
const towardsAnchor = (v, amount = step) => Math.max(0, Math.min(v, max) - amount);
|
|
22
|
+
const awayFromAnchor = (v, amount = step) => Math.min(Math.min(v, max) + amount, max);
|
|
23
23
|
if (anchor === 'top') {
|
|
24
24
|
if (key.pageDown)
|
|
25
25
|
setScroll(awayFromAnchor);
|
|
26
26
|
if (key.pageUp)
|
|
27
27
|
setScroll(towardsAnchor);
|
|
28
|
+
if (key.downArrow)
|
|
29
|
+
setScroll((v) => awayFromAnchor(v, 1));
|
|
30
|
+
if (key.upArrow)
|
|
31
|
+
setScroll((v) => towardsAnchor(v, 1));
|
|
28
32
|
}
|
|
29
33
|
else {
|
|
30
34
|
if (key.pageUp)
|
|
31
35
|
setScroll(awayFromAnchor);
|
|
32
36
|
if (key.pageDown)
|
|
33
37
|
setScroll(towardsAnchor);
|
|
38
|
+
if (key.upArrow)
|
|
39
|
+
setScroll((v) => awayFromAnchor(v, 1));
|
|
40
|
+
if (key.downArrow)
|
|
41
|
+
setScroll((v) => towardsAnchor(v, 1));
|
|
34
42
|
}
|
|
35
43
|
});
|
|
36
44
|
const start = anchor === 'top' ? s : Math.max(0, items.length - visible - s);
|
|
@@ -44,20 +52,27 @@ function useVisibleRows(overhead, min = 6) {
|
|
|
44
52
|
const { stdout } = useStdout();
|
|
45
53
|
return Math.max(min, (stdout?.rows ?? 30) - overhead);
|
|
46
54
|
}
|
|
47
|
-
export function BoardView({ board }) {
|
|
55
|
+
export function BoardView({ board, bodyHeight }) {
|
|
48
56
|
const agents = [...board.agents.values()];
|
|
49
|
-
const
|
|
50
|
-
|
|
57
|
+
const fallbackVisible = useVisibleRows(12);
|
|
58
|
+
const visibleAgents = bodyHeight ? Math.max(1, Math.floor((bodyHeight - 7) / 3)) : fallbackVisible;
|
|
59
|
+
const { slice: agentSlice, above, below } = useScrollWindow(agents, visibleAgents, 'top');
|
|
60
|
+
const sideRows = bodyHeight ? Math.max(1, Math.floor((bodyHeight - visibleAgents - 5) / 2)) : 8;
|
|
61
|
+
const activities = [...board.fileActivity.values()].sort((a, b) => b.ts - a.ts).slice(0, sideRows);
|
|
62
|
+
const notes = board.notes.slice(-sideRows);
|
|
63
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: "yellow", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: t('board.title') }), _jsx(Text, { bold: true, children: t('board.agents') }), agents.length === 0 ? (_jsxs(Text, { color: "gray", children: [" ", t('board.none')] })) : (_jsxs(_Fragment, { children: [_jsx(Above, { n: above }), agentSlice.map((a) => (_jsxs(Text, { wrap: "truncate-end", children: [' ', _jsx(Text, { color: a.color, bold: true, children: a.name }), _jsxs(Text, { color: STATE_LABEL[a.state].color, children: [' ', STATE_LABEL[a.state].icon, " ", stateLabel(a.state)] }), _jsxs(Text, { color: "gray", children: [" ", truncate(a.currentAction || a.task, 110)] })] }, a.id))), _jsx(Below, { n: below })] })), _jsx(Text, { bold: true, children: t('board.activity') }), activities.length === 0 ? (_jsxs(Text, { color: "gray", children: [" ", t('board.noActivity')] })) : (activities.map((act) => (_jsxs(Text, { wrap: "truncate-end", children: [' ', "\u270F ", act.path, " ", _jsxs(Text, { color: "gray", children: ["\u2014 ", act.agentName, " (", act.op, ", ", Math.round((Date.now() - act.ts) / 1000), "s)"] })] }, act.path)))), _jsx(Text, { bold: true, children: t('board.notes') }), notes.map((n) => (_jsxs(Text, { wrap: "truncate-end", children: [' ', _jsxs(Text, { color: "magenta", children: [n.from, " \u2192 ", n.to] }), _jsxs(Text, { children: [": ", truncate(n.content, 140)] })] }, n.id)))] }));
|
|
51
64
|
}
|
|
52
|
-
export function NotesView({ board }) {
|
|
53
|
-
const
|
|
65
|
+
export function NotesView({ board, bodyHeight }) {
|
|
66
|
+
const fallbackVisible = useVisibleRows(7);
|
|
67
|
+
const visible = bodyHeight ? Math.max(3, bodyHeight - 4) : fallbackVisible;
|
|
54
68
|
const { slice, above, below } = useScrollWindow(board.notes, visible, 'bottom');
|
|
55
69
|
return (_jsxs(Box, { borderStyle: "round", borderColor: "magenta", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "magenta", children: t('notes.title') }), board.notes.length === 0 ? (_jsx(Text, { color: "gray", children: t('notes.empty') })) : (_jsxs(_Fragment, { children: [_jsx(Above, { n: above }), slice.map((n) => (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { color: "gray", children: [new Date(n.ts).toLocaleTimeString(), " "] }), _jsx(Text, { color: "magenta", bold: true, children: n.from }), _jsxs(Text, { color: "gray", children: [" \u2192 ", n.to, ": "] }), _jsx(Text, { children: truncate(n.content, 200) })] }, n.id))), _jsx(Below, { n: below })] }))] }));
|
|
56
70
|
}
|
|
57
|
-
export function DiffView({ board }) {
|
|
71
|
+
export function DiffView({ board, bodyHeight }) {
|
|
58
72
|
// Each change renders up to ~33 rows (header + 30 patch lines + spacing):
|
|
59
73
|
// window over WHOLE history, newest first, PgUp to walk back in time.
|
|
60
|
-
const
|
|
74
|
+
const fallbackRows = useVisibleRows(8, 18);
|
|
75
|
+
const rows = bodyHeight ? Math.max(8, bodyHeight - 4) : fallbackRows;
|
|
61
76
|
const perChange = Math.max(1, Math.floor(rows / 34));
|
|
62
77
|
const { slice: changes, above, below } = useScrollWindow(board.changes, perChange, 'bottom');
|
|
63
78
|
return (_jsxs(Box, { borderStyle: "round", borderColor: "green", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "green", children: t('diff.title', { total: board.changes.length }) }), board.changes.length === 0 ? (_jsx(Text, { color: "gray", children: t('diff.empty') })) : (_jsxs(_Fragment, { children: [_jsx(Above, { n: above }), changes.map((c) => {
|
|
@@ -67,28 +82,45 @@ export function DiffView({ board }) {
|
|
|
67
82
|
}), _jsx(Below, { n: below })] }))] }));
|
|
68
83
|
}
|
|
69
84
|
/** Financial view: live cost / steps / tokens per agent + session total. */
|
|
70
|
-
export function CostView({ board }) {
|
|
85
|
+
export function CostView({ board, bodyHeight }) {
|
|
71
86
|
const agents = [...board.agents.values()];
|
|
87
|
+
const fallbackVisible = useVisibleRows(8);
|
|
88
|
+
const visible = bodyHeight ? Math.max(3, bodyHeight - 7) : fallbackVisible;
|
|
89
|
+
const { slice, above, below } = useScrollWindow(agents, visible, 'top');
|
|
72
90
|
const total = agents.reduce((s, a) => s + (a.cost ?? 0), 0);
|
|
73
91
|
const unknown = agents.some((a) => a.cost === null);
|
|
74
|
-
return (_jsxs(Box, { borderStyle: "round", borderColor: "greenBright", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "greenBright", children: t('cost.title') }), agents.length === 0 ? (_jsx(Text, { color: "gray", children: t('cost.empty') })) : (_jsxs(_Fragment, { children: [
|
|
92
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: "greenBright", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "greenBright", children: t('cost.title') }), agents.length === 0 ? (_jsx(Text, { color: "gray", children: t('cost.empty') })) : (_jsxs(_Fragment, { children: [_jsx(Above, { n: above }), slice.map((a) => (_jsxs(Text, { wrap: "truncate-end", children: [' ', _jsx(Text, { color: a.color, bold: true, children: a.name.padEnd(12) }), _jsxs(Text, { color: "gray", children: [a.model.padEnd(24).slice(0, 24), " "] }), _jsxs(Text, { children: [String(a.steps).padStart(3), " steps "] }), _jsxs(Text, { color: "cyan", children: [String(Math.round(a.tokensIn / 1000)).padStart(5), "k in ", String(Math.round(a.tokensOut / 1000)).padStart(4), "k out", ' '] }), _jsx(Text, { color: "greenBright", bold: true, children: a.cost === null ? ' $—' : fmtCost(a.cost).padStart(8) }), a.cost === null ? _jsxs(Text, { color: "gray", children: [" ", t('cost.unknown')] }) : null] }, a.id))), _jsx(Below, { n: below }), _jsx(Text, { children: " " }), _jsxs(Text, { bold: true, children: [' ', t('cost.total'), " ", _jsx(Text, { color: "greenBright", children: fmtCost(total) }), unknown ? _jsxs(Text, { color: "gray", children: [" ", t('cost.partial')] }) : null] })] })), _jsx(Text, { color: "gray", children: t('cost.hint') })] }));
|
|
75
93
|
}
|
|
76
94
|
/** Skills catalog: user-authored markdown instructions agents can load. */
|
|
77
|
-
export function SkillsView({ skills }) {
|
|
78
|
-
|
|
95
|
+
export function SkillsView({ skills, bodyHeight }) {
|
|
96
|
+
const fallbackVisible = useVisibleRows(8);
|
|
97
|
+
const visible = bodyHeight ? Math.max(3, bodyHeight - 6) : fallbackVisible;
|
|
98
|
+
const { slice, above, below } = useScrollWindow(skills, visible, 'top');
|
|
99
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: "blueBright", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "blueBright", children: t('skills.title') }), skills.length === 0 ? (_jsx(Text, { color: "gray", children: t('skills.empty') })) : (_jsxs(_Fragment, { children: [_jsx(Above, { n: above }), slice.map((s) => (_jsxs(Text, { wrap: "truncate-end", children: [' ', _jsxs(Text, { color: "blueBright", bold: true, children: ["#", s.name.padEnd(16)] }), _jsxs(Text, { color: s.scope === 'global' ? 'yellow' : 'green', children: ["[", s.scope, "] "] }), _jsx(Text, { color: "gray", children: truncate(s.description || s.file, 100) })] }, s.file))), _jsx(Below, { n: below })] })), _jsx(Text, { children: " " }), _jsx(Text, { color: "gray", children: t('skills.hint1') }), _jsx(Text, { color: "gray", children: t('skills.hint2') })] }));
|
|
79
100
|
}
|
|
80
101
|
/** Specialists catalog: personas (role + optional pinned model). */
|
|
81
|
-
export function SpecialistsView({ specialists }) {
|
|
82
|
-
|
|
102
|
+
export function SpecialistsView({ specialists, bodyHeight }) {
|
|
103
|
+
const fallbackVisible = useVisibleRows(8);
|
|
104
|
+
const visible = bodyHeight ? Math.max(3, bodyHeight - 6) : fallbackVisible;
|
|
105
|
+
const { slice, above, below } = useScrollWindow(specialists, visible, 'top');
|
|
106
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: "magentaBright", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "magentaBright", children: t('spec.title') }), specialists.length === 0 ? (_jsx(Text, { color: "gray", children: t('spec.empty') })) : (_jsxs(_Fragment, { children: [_jsx(Above, { n: above }), slice.map((s) => (_jsxs(Text, { wrap: "truncate-end", children: [' ', _jsxs(Text, { color: "magentaBright", bold: true, children: ["\uD83C\uDF93", s.name.padEnd(16)] }), _jsxs(Text, { color: s.scope === 'global' ? 'yellow' : 'green', children: ["[", s.scope, "] "] }), s.model ? _jsxs(Text, { color: "cyan", children: [s.model, " "] }) : null, _jsx(Text, { color: "gray", children: truncate(s.description || s.file, 90) })] }, s.file))), _jsx(Below, { n: below })] })), _jsx(Text, { children: " " }), _jsx(Text, { color: "gray", children: t('spec.hint1') }), _jsx(Text, { color: "gray", children: t('spec.hint2') })] }));
|
|
83
107
|
}
|
|
84
108
|
/** Saved sessions: inspect available restore points; restore via /session. */
|
|
85
|
-
export function SessionsView({ projectRoot }) {
|
|
109
|
+
export function SessionsView({ projectRoot, bodyHeight }) {
|
|
86
110
|
const sessions = Controller.listSessions(projectRoot);
|
|
87
|
-
|
|
111
|
+
const fallbackVisible = useVisibleRows(7);
|
|
112
|
+
const visible = bodyHeight ? Math.max(3, bodyHeight - 5) : fallbackVisible;
|
|
113
|
+
const { slice, above, below } = useScrollWindow(sessions, visible, 'top');
|
|
114
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: "yellow", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: t('sessions.title') }), sessions.length === 0 ? (_jsx(Text, { color: "gray", children: t('sessions.empty') })) : (_jsxs(_Fragment, { children: [_jsx(Above, { n: above }), slice.map((s, i) => (_jsxs(Text, { wrap: "truncate-end", children: [' ', _jsxs(Text, { color: "yellow", bold: true, children: [String(sessions.indexOf(s) + 1).padStart(2), "."] }), ' ', _jsx(Text, { children: t('sessions.item', {
|
|
115
|
+
name: s.data.name ? `${s.data.name} · ` : '',
|
|
116
|
+
date: new Date(s.data.savedAt).toLocaleString(),
|
|
117
|
+
agents: s.data.agents.length,
|
|
118
|
+
}) }), _jsxs(Text, { color: "gray", children: [" ", s.data.agents.map((a) => a.name).join(', ').slice(0, 80)] })] }, s.file))), _jsx(Below, { n: below })] })), _jsx(Text, { children: " " }), _jsx(Text, { color: "gray", children: t('sessions.hint') })] }));
|
|
88
119
|
}
|
|
89
|
-
export function HelpView() {
|
|
90
|
-
//
|
|
91
|
-
const
|
|
120
|
+
export function HelpView({ bodyHeight }) {
|
|
121
|
+
// Fixed intro/highlight/footer rows consume about 12 lines inside the already-sized body.
|
|
122
|
+
const fallbackVisible = useVisibleRows(16);
|
|
123
|
+
const visible = bodyHeight ? Math.max(3, bodyHeight - 12) : fallbackVisible;
|
|
92
124
|
const commands = visibleCommands();
|
|
93
125
|
const { slice, above, below } = useScrollWindow(commands, visible, 'top');
|
|
94
126
|
const highlights = [
|
|
@@ -96,5 +128,5 @@ export function HelpView() {
|
|
|
96
128
|
['Shell approvals', ['/approvals ask', '/approvals auto', '/approvals yolo']],
|
|
97
129
|
['Navigation', ['/focus', '/attach', '/raw', '/send']],
|
|
98
130
|
];
|
|
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
|
|
131
|
+
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: \u2191/\u2193 or PgUp/PgDn scroll \u00B7 Tab/\u2192 autocomplete \u00B7 Esc back/clear \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') })] }));
|
|
100
132
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parallel-cli/parallel",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
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",
|
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
"real-time"
|
|
16
16
|
],
|
|
17
17
|
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Nil06/parallel-cli.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/Nil06/parallel-cli#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/Nil06/parallel-cli/issues"
|
|
25
|
+
},
|
|
18
26
|
"author": "Nil Amara",
|
|
19
27
|
"type": "module",
|
|
20
28
|
"bin": {
|
|
@@ -24,6 +32,7 @@
|
|
|
24
32
|
"files": [
|
|
25
33
|
"dist",
|
|
26
34
|
"README.md",
|
|
35
|
+
"CHANGELOG.md",
|
|
27
36
|
"LICENSE"
|
|
28
37
|
],
|
|
29
38
|
"publishConfig": {
|