@parallel-cli/parallel 0.3.3
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/LICENSE +21 -0
- package/README.md +316 -0
- package/dist/agents/agent.js +518 -0
- package/dist/agents/tools.js +570 -0
- package/dist/commands.js +480 -0
- package/dist/config.js +163 -0
- package/dist/controller.js +703 -0
- package/dist/coordination/blackboard.js +225 -0
- package/dist/i18n.js +1087 -0
- package/dist/index.js +196 -0
- package/dist/llm/client.js +46 -0
- package/dist/pricing.js +76 -0
- package/dist/server.js +149 -0
- package/dist/skills.js +132 -0
- package/dist/types.js +1 -0
- package/dist/ui/AgentPanel.js +25 -0
- package/dist/ui/App.js +400 -0
- package/dist/ui/ApprovalPrompt.js +18 -0
- package/dist/ui/AttachApp.js +126 -0
- package/dist/ui/CommandInput.js +154 -0
- package/dist/ui/Md.js +40 -0
- package/dist/ui/QuestionPrompt.js +58 -0
- package/dist/ui/SettingsPanel.js +217 -0
- package/dist/ui/Spinner.js +12 -0
- package/dist/ui/Wizard.js +66 -0
- package/dist/ui/clipboard.js +36 -0
- package/dist/ui/theme.js +27 -0
- package/dist/ui/views.js +94 -0
- package/package.json +59 -0
package/dist/ui/views.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput, useStdout } from 'ink';
|
|
4
|
+
import * as Diff from 'diff';
|
|
5
|
+
import { COMMANDS } from '../commands.js';
|
|
6
|
+
import { Controller } from '../controller.js';
|
|
7
|
+
import { fmtCost } from '../pricing.js';
|
|
8
|
+
import { STATE_LABEL, stateLabel, truncate } from './theme.js';
|
|
9
|
+
import { t } from '../i18n.js';
|
|
10
|
+
/**
|
|
11
|
+
* PgUp/PgDn window over a list — the TUI runs in the alternate screen, so
|
|
12
|
+
* every long view needs its own scrolling. `anchor` decides what you see
|
|
13
|
+
* first: 'top' (docs like /help) or 'bottom' (live feeds like /notes).
|
|
14
|
+
*/
|
|
15
|
+
function useScrollWindow(items, visible, anchor = 'top') {
|
|
16
|
+
const [scroll, setScroll] = useState(0);
|
|
17
|
+
const max = Math.max(0, items.length - visible);
|
|
18
|
+
const s = Math.min(scroll, max);
|
|
19
|
+
const step = Math.max(1, visible - 1);
|
|
20
|
+
useInput((_input, key) => {
|
|
21
|
+
const towardsAnchor = (v) => Math.max(0, Math.min(v, max) - step);
|
|
22
|
+
const awayFromAnchor = (v) => Math.min(Math.min(v, max) + step, max);
|
|
23
|
+
if (anchor === 'top') {
|
|
24
|
+
if (key.pageDown)
|
|
25
|
+
setScroll(awayFromAnchor);
|
|
26
|
+
if (key.pageUp)
|
|
27
|
+
setScroll(towardsAnchor);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
if (key.pageUp)
|
|
31
|
+
setScroll(awayFromAnchor);
|
|
32
|
+
if (key.pageDown)
|
|
33
|
+
setScroll(towardsAnchor);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const start = anchor === 'top' ? s : Math.max(0, items.length - visible - s);
|
|
37
|
+
const slice = items.slice(start, start + visible);
|
|
38
|
+
return { slice, above: start, below: Math.max(0, items.length - start - slice.length) };
|
|
39
|
+
}
|
|
40
|
+
const Above = ({ n }) => (n > 0 ? _jsxs(Text, { color: "gray", children: ["\u25B2 ", n, " \u00B7 PgUp"] }) : null);
|
|
41
|
+
const Below = ({ n }) => (n > 0 ? _jsxs(Text, { color: "gray", children: ["\u25BC ", n, " \u00B7 PgDn"] }) : null);
|
|
42
|
+
/** Usable rows for a view's list, from the REAL terminal height. */
|
|
43
|
+
function useVisibleRows(overhead, min = 6) {
|
|
44
|
+
const { stdout } = useStdout();
|
|
45
|
+
return Math.max(min, (stdout?.rows ?? 30) - overhead);
|
|
46
|
+
}
|
|
47
|
+
export function BoardView({ board }) {
|
|
48
|
+
const agents = [...board.agents.values()];
|
|
49
|
+
const activities = [...board.fileActivity.values()].sort((a, b) => b.ts - a.ts).slice(0, 12);
|
|
50
|
+
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')] })) : (agents.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(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') }), board.notes.slice(-8).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
|
+
}
|
|
52
|
+
export function NotesView({ board }) {
|
|
53
|
+
const visible = useVisibleRows(7);
|
|
54
|
+
const { slice, above, below } = useScrollWindow(board.notes, visible, 'bottom');
|
|
55
|
+
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
|
+
}
|
|
57
|
+
export function DiffView({ board }) {
|
|
58
|
+
// Each change renders up to ~33 rows (header + 30 patch lines + spacing):
|
|
59
|
+
// window over WHOLE history, newest first, PgUp to walk back in time.
|
|
60
|
+
const rows = useVisibleRows(8, 18);
|
|
61
|
+
const perChange = Math.max(1, Math.floor(rows / 34));
|
|
62
|
+
const { slice: changes, above, below } = useScrollWindow(board.changes, perChange, 'bottom');
|
|
63
|
+
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) => {
|
|
64
|
+
const patch = Diff.createPatch(c.path, c.before, c.after, '', '', { context: 2 });
|
|
65
|
+
const lines = patch.split('\n').slice(4, 34);
|
|
66
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { bold: true, children: [_jsx(Text, { color: "cyan", children: c.path }), _jsxs(Text, { color: "gray", children: [' ', t('diff.by', { agent: c.agentName, time: new Date(c.ts).toLocaleTimeString() })] })] }), lines.map((l, i) => (_jsx(Text, { color: l.startsWith('+') ? 'green' : l.startsWith('-') ? 'red' : l.startsWith('@') ? 'cyan' : 'gray', wrap: "truncate-end", children: l || ' ' }, i))), patch.split('\n').length > 38 ? _jsx(Text, { color: "gray", children: t('diff.trunc') }) : null] }, c.id));
|
|
67
|
+
}), _jsx(Below, { n: below })] }))] }));
|
|
68
|
+
}
|
|
69
|
+
/** Financial view: live cost / steps / tokens per agent + session total. */
|
|
70
|
+
export function CostView({ board }) {
|
|
71
|
+
const agents = [...board.agents.values()];
|
|
72
|
+
const total = agents.reduce((s, a) => s + (a.cost ?? 0), 0);
|
|
73
|
+
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: [agents.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(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
|
+
}
|
|
76
|
+
/** Skills catalog: user-authored markdown instructions agents can load. */
|
|
77
|
+
export function SkillsView({ skills }) {
|
|
78
|
+
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') })) : (skills.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(Text, { children: " " }), _jsx(Text, { color: "gray", children: t('skills.hint1') }), _jsx(Text, { color: "gray", children: t('skills.hint2') })] }));
|
|
79
|
+
}
|
|
80
|
+
/** Specialists catalog: personas (role + optional pinned model). */
|
|
81
|
+
export function SpecialistsView({ specialists }) {
|
|
82
|
+
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') })) : (specialists.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(Text, { children: " " }), _jsx(Text, { color: "gray", children: t('spec.hint1') }), _jsx(Text, { color: "gray", children: t('spec.hint2') })] }));
|
|
83
|
+
}
|
|
84
|
+
/** Saved sessions: inspect available restore points; restore via /session. */
|
|
85
|
+
export function SessionsView({ projectRoot }) {
|
|
86
|
+
const sessions = Controller.listSessions(projectRoot);
|
|
87
|
+
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') })) : (sessions.map((s, i) => (_jsxs(Text, { wrap: "truncate-end", children: [' ', _jsxs(Text, { color: "yellow", bold: true, children: [String(i + 1).padStart(2), "."] }), ' ', _jsx(Text, { children: t('sessions.item', { date: new Date(s.data.savedAt).toLocaleString(), agents: s.data.agents.length }) }), _jsxs(Text, { color: "gray", children: [" ", s.data.agents.map((a) => a.name).join(', ').slice(0, 80)] })] }, s.file)))), _jsx(Text, { children: " " }), _jsx(Text, { color: "gray", children: t('sessions.hint') })] }));
|
|
88
|
+
}
|
|
89
|
+
export function HelpView() {
|
|
90
|
+
// Intro (4) + title + blank lines (2) + footer (2) + border/input/status ≈ 16 rows of overhead.
|
|
91
|
+
const visible = useVisibleRows(16);
|
|
92
|
+
const { slice, above, below } = useScrollWindow(COMMANDS, visible, 'top');
|
|
93
|
+
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: " " }), _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
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@parallel-cli/parallel",
|
|
3
|
+
"version": "0.3.3",
|
|
4
|
+
"description": "Real-time multi-agent coding CLI with shared context, adaptive co-editing, dedicated agent terminals, and headless CI runs.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"ai",
|
|
8
|
+
"agents",
|
|
9
|
+
"multi-agent",
|
|
10
|
+
"coding-assistant",
|
|
11
|
+
"terminal",
|
|
12
|
+
"ink",
|
|
13
|
+
"openai-compatible",
|
|
14
|
+
"deepseek",
|
|
15
|
+
"real-time"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "Nil Amara",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"bin": {
|
|
21
|
+
"parallel": "dist/index.js",
|
|
22
|
+
"prl": "dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"os": [
|
|
36
|
+
"linux",
|
|
37
|
+
"darwin"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
41
|
+
"dev": "tsc --watch",
|
|
42
|
+
"test": "npm run build && node --test test/*.test.mjs && npm run test:pty",
|
|
43
|
+
"test:pty": "sh test/pty.sh",
|
|
44
|
+
"start": "node dist/index.js",
|
|
45
|
+
"prepublishOnly": "npm run build"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"diff": "^5.2.0",
|
|
49
|
+
"ink": "^5.1.0",
|
|
50
|
+
"openai": "^4.77.0",
|
|
51
|
+
"react": "^18.3.1"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/diff": "^5.2.3",
|
|
55
|
+
"@types/node": "^18.19.0",
|
|
56
|
+
"@types/react": "^18.3.12",
|
|
57
|
+
"typescript": "^5.6.3"
|
|
58
|
+
}
|
|
59
|
+
}
|