glitool 1.0.0 → 2.0.0
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 +115 -45
- package/dist/agent.js +234 -37
- package/dist/agents/coder.js +46 -34
- package/dist/agents/debugger.js +111 -0
- package/dist/agents/explainer.js +2 -5
- package/dist/agents/git-agent.js +90 -0
- package/dist/agents/graph.js +214 -23
- package/dist/agents/judge.js +61 -0
- package/dist/agents/planner.js +31 -10
- package/dist/agents/planningAgent.js +41 -0
- package/dist/agents/refactorer.js +97 -0
- package/dist/agents/reviewer-agent.js +87 -0
- package/dist/agents/reviewer.js +6 -9
- package/dist/agents/types.js +1 -0
- package/dist/agents/validator.js +93 -0
- package/dist/agents/workflow.js +45 -0
- package/dist/auth.js +87 -0
- package/dist/commands/version.js +1 -0
- package/dist/config.js +4 -1
- package/dist/confirmHandler.js +4 -2
- package/dist/index.js +12 -25
- package/dist/llm/classifier.js +61 -0
- package/dist/llm/factory.js +50 -0
- package/dist/llm/router.js +235 -14
- package/dist/llm/telemetry.js +18 -0
- package/dist/logger.js +25 -0
- package/dist/processEvents.js +1 -0
- package/dist/tools/bashTool.js +90 -0
- package/dist/tools/editFileTool.js +14 -3
- package/dist/tools/index.js +3 -1
- package/dist/tools/listFilesTool.js +19 -21
- package/dist/tools/processRegistry.js +36 -0
- package/dist/tools/readBackgroundOutput.js +29 -0
- package/dist/tools/readFileTool.js +64 -9
- package/dist/tools/searchCodeTool.js +14 -4
- package/dist/tools/webFetchTool.js +45 -0
- package/dist/tools/writeFileTool.js +9 -6
- package/dist/trust/riskScorer.js +29 -2
- package/dist/ui/App.js +384 -47
- package/dist/ui/AuthFlow.js +76 -0
- package/dist/ui/ConfirmCard.js +53 -0
- package/dist/ui/EscalationCard.js +22 -0
- package/dist/ui/ExplainCard.js +5 -0
- package/dist/ui/Pipeline.js +37 -0
- package/dist/ui/ProcessTrace.js +79 -0
- package/dist/ui/RoleRow.js +16 -0
- package/dist/ui/RoleRow.test.js +8 -0
- package/dist/ui/SlashPalette.js +32 -0
- package/dist/ui/StatusBar.js +44 -0
- package/dist/ui/ToolLog.js +62 -0
- package/dist/ui/Welcome.js +11 -0
- package/dist/ui/renderMarkdown.js +41 -0
- package/dist/ui/symbols.js +19 -0
- package/dist/ui/tokens.js +13 -0
- package/dist/version.js +1 -0
- package/package.json +27 -20
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import { colors } from './tokens.js';
|
|
5
|
+
export const EscalationCard = ({ payload, onChoice }) => {
|
|
6
|
+
useInput((input) => {
|
|
7
|
+
const k = input.toLowerCase();
|
|
8
|
+
if (k === 'a')
|
|
9
|
+
onChoice('approve');
|
|
10
|
+
else if (k === 'c')
|
|
11
|
+
onChoice('correct');
|
|
12
|
+
else if (k === 'x')
|
|
13
|
+
onChoice('abort');
|
|
14
|
+
});
|
|
15
|
+
const planPreview = payload.plan.length > 400
|
|
16
|
+
? payload.plan.slice(0, 400) + '…'
|
|
17
|
+
: payload.plan;
|
|
18
|
+
const outputPreview = payload.finalOutput.length > 300
|
|
19
|
+
? payload.finalOutput.slice(0, 300) + '…'
|
|
20
|
+
: payload.finalOutput;
|
|
21
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: colors.amber, paddingX: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, color: colors.amber, children: `⚠ Escalation — Judge gave up after ${payload.trajectory.length} attempts` }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, children: "Original request:" }), _jsx(Text, { wrap: "wrap", children: payload.userMessage })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, children: "Plan:" }), _jsx(Text, { color: "white", wrap: "wrap", children: planPreview })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, children: "Judge trail:" }), payload.trajectory.map((t) => (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: ` #${t.iteration} ` }), _jsx(Text, { color: t.verdict === 'ok' ? 'green' : 'red', children: t.verdict.toUpperCase() }), _jsx(Text, { color: "gray", children: ` · ${t.failure_point ?? 'none'} · ` }), _jsx(Text, { wrap: "wrap", children: t.reason })] }, t.iteration)))] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.muted, children: "Current output (preview):" }), _jsx(Text, { wrap: "wrap", children: outputPreview })] }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, color: "green", children: "[a]" }), _jsx(Text, { children: ' approve as-is ' }), _jsx(Text, { bold: true, color: "yellow", children: "[c]" }), _jsx(Text, { children: ' give correction ' }), _jsx(Text, { bold: true, color: "red", children: "[x]" }), _jsx(Text, { children: ' abort' })] })] }));
|
|
22
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
|
+
import { colors } from "./tokens.js";
|
|
5
|
+
export const ExplainCard = ({ children, footer }) => (_jsxs(Box, { borderStyle: "single", borderColor: colors.amber, borderTop: false, borderRight: false, borderBottom: false, paddingLeft: 2, flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: colors.amber, bold: true, children: "\uD83D\uDCA1 EXPLAIN" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: typeof children === 'string' ? (_jsx(Text, { color: colors.ink2, wrap: "wrap", children: children })) : (children) }), footer && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, children: footer }) }))] }));
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState, useEffect } from "react";
|
|
3
|
+
import { colors } from "./tokens.js";
|
|
4
|
+
import { Box, Text } from "ink";
|
|
5
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
6
|
+
const AGENT_META = {
|
|
7
|
+
planner: { label: 'Planner', badge: 'P', color: colors.violet },
|
|
8
|
+
workflow: { label: 'Workflow', badge: 'W', color: colors.mustard },
|
|
9
|
+
coder: { label: 'Coder', badge: 'C', color: colors.amber },
|
|
10
|
+
validator: { label: 'Validator', badge: 'V', color: colors.sage },
|
|
11
|
+
judge: { label: 'Judge', badge: 'J', color: colors.rust },
|
|
12
|
+
};
|
|
13
|
+
function useSpinner() {
|
|
14
|
+
const [frame, setFrame] = useState(0);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const id = setInterval(() => setFrame(f => (f + 1) % SPINNER_FRAMES.length), 80);
|
|
17
|
+
return () => clearInterval(id);
|
|
18
|
+
}, []);
|
|
19
|
+
return SPINNER_FRAMES[frame];
|
|
20
|
+
}
|
|
21
|
+
const ActiveCard = ({ name, badge, color, detail, spinner }) => (_jsxs(Box, { borderStyle: "single", borderColor: color, paddingX: 1, marginBottom: 1, children: [_jsxs(Text, { color: color, bold: true, children: [badge, " "] }), _jsxs(Text, { color: "white", bold: true, children: [name, " "] }), _jsxs(Text, { color: color, children: [spinner, " "] }), _jsx(Text, { color: colors.muted, children: detail ?? 'working...' })] }));
|
|
22
|
+
const DoneRow = ({ agents }) => (_jsx(Box, { marginBottom: 1, paddingLeft: 1, children: agents.map((a, i) => (_jsxs(React.Fragment, { children: [_jsx(Text, { color: a.color, bold: true, children: a.badge }), _jsx(Text, { color: colors.sage, children: " \u2713 " }), i < agents.length - 1 && _jsx(Text, { color: colors.muted, children: "\u00B7 " })] }, a.label))) }));
|
|
23
|
+
export const Pipeline = ({ planner, workflow, coder, validator, judge }) => {
|
|
24
|
+
const spinner = useSpinner();
|
|
25
|
+
const states = [
|
|
26
|
+
['planner', planner],
|
|
27
|
+
['workflow', workflow],
|
|
28
|
+
['coder', coder],
|
|
29
|
+
['validator', validator],
|
|
30
|
+
['judge', judge],
|
|
31
|
+
];
|
|
32
|
+
const doneAgents = states
|
|
33
|
+
.filter(([, s]) => s.status === 'done')
|
|
34
|
+
.map(([key]) => AGENT_META[key]);
|
|
35
|
+
const activeEntry = states.find(([, s]) => s.status === 'active');
|
|
36
|
+
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [doneAgents.length > 0 && _jsx(DoneRow, { agents: doneAgents }), activeEntry && (_jsx(ActiveCard, { name: AGENT_META[activeEntry[0]].label, badge: AGENT_META[activeEntry[0]].badge, color: AGENT_META[activeEntry[0]].color, detail: activeEntry[1].detail, spinner: spinner }))] }));
|
|
37
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState, useEffect } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
const STAGE_COLOR = {
|
|
5
|
+
planner: '#c4732e',
|
|
6
|
+
coder: '#3e8a9a',
|
|
7
|
+
validator: '#6c5ab8',
|
|
8
|
+
judge: '#5a8c5a',
|
|
9
|
+
debugger: '#b54226',
|
|
10
|
+
reviewer: '#6c5ab8',
|
|
11
|
+
refactorer: '#3e8a9a',
|
|
12
|
+
git_agent: '#5a8c5a',
|
|
13
|
+
};
|
|
14
|
+
const STAGE_LABEL = {
|
|
15
|
+
planner: 'PLANNER',
|
|
16
|
+
coder: 'CODER',
|
|
17
|
+
validator: 'VALIDATOR',
|
|
18
|
+
judge: 'JUDGE',
|
|
19
|
+
debugger: 'DEBUGGER',
|
|
20
|
+
reviewer: 'REVIEWER',
|
|
21
|
+
refactorer: 'REFACTORER',
|
|
22
|
+
git_agent: 'GIT',
|
|
23
|
+
};
|
|
24
|
+
const TOOL_VERB = {
|
|
25
|
+
readFile: 'read',
|
|
26
|
+
writeFile: 'write',
|
|
27
|
+
editFile: 'edit',
|
|
28
|
+
searchCode: 'search',
|
|
29
|
+
listFiles: 'list',
|
|
30
|
+
bash: 'run',
|
|
31
|
+
webFetch: 'fetch',
|
|
32
|
+
};
|
|
33
|
+
export const ProcessTrace = ({ events, active }) => {
|
|
34
|
+
const SPINNER = ['◐', '◓', '◑', '◒'];
|
|
35
|
+
const [frame, setFrame] = useState(0);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!active)
|
|
38
|
+
return;
|
|
39
|
+
const id = setInterval(() => setFrame(f => (f + 1) % SPINNER.length), 150);
|
|
40
|
+
return () => clearInterval(id);
|
|
41
|
+
}, [active]);
|
|
42
|
+
if (events.length === 0)
|
|
43
|
+
return null;
|
|
44
|
+
const sections = [];
|
|
45
|
+
for (const ev of events) {
|
|
46
|
+
if (ev.type === 'stage_start') {
|
|
47
|
+
sections.push({ stage: ev.stage, items: [], done: false });
|
|
48
|
+
}
|
|
49
|
+
else if (ev.type === 'stage_done') {
|
|
50
|
+
if (sections.length > 0)
|
|
51
|
+
sections[sections.length - 1].done = true;
|
|
52
|
+
}
|
|
53
|
+
else if (ev.type === 'reasoning' || ev.type === 'tool') {
|
|
54
|
+
if (sections.length > 0) {
|
|
55
|
+
sections[sections.length - 1].items.push(ev);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: sections.map((sec, si) => {
|
|
60
|
+
const color = STAGE_COLOR[sec.stage];
|
|
61
|
+
const isLast = si === sections.length - 1;
|
|
62
|
+
const spinning = isLast && !sec.done && active;
|
|
63
|
+
const bullet = sec.done ? '●' : (spinning ? SPINNER[frame] : '○');
|
|
64
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: isLast ? 0 : 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: color, bold: true, children: [bullet, " ", STAGE_LABEL[sec.stage]] }), spinning && _jsx(Text, { color: color, children: " \u00B7\u00B7\u00B7" })] }), sec.items.map((item, ii) => {
|
|
65
|
+
if (item.type === 'reasoning') {
|
|
66
|
+
return item.text
|
|
67
|
+
.split('\n')
|
|
68
|
+
.filter(Boolean)
|
|
69
|
+
.slice(0, 4)
|
|
70
|
+
.map((line, li) => (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "#8a8170", italic: true, children: line.trim() }) }, `${ii}-${li}`)));
|
|
71
|
+
}
|
|
72
|
+
if (item.type === 'tool') {
|
|
73
|
+
const verb = TOOL_VERB[item.tool] ?? item.tool;
|
|
74
|
+
return (_jsxs(Box, { marginLeft: 2, children: [_jsxs(Text, { color: color, children: [" \u2713 ", verb] }), item.target ? (_jsxs(Text, { color: "#b3a994", children: [" ", item.target] })) : null] }, ii));
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
})] }, si));
|
|
78
|
+
}) }));
|
|
79
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { colors } from "./tokens.js";
|
|
5
|
+
const ROLE_COLOR = {
|
|
6
|
+
you: colors.amber,
|
|
7
|
+
glitool: colors.ink2,
|
|
8
|
+
tools: colors.violet,
|
|
9
|
+
graph: colors.violet,
|
|
10
|
+
plan: colors.sage,
|
|
11
|
+
note: colors.muted
|
|
12
|
+
};
|
|
13
|
+
export const RoleRow = ({ role, timestamp, children }) => {
|
|
14
|
+
const lable = timestamp ? `${role} . ${timestamp}` : role;
|
|
15
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: ROLE_COLOR[role], bold: true, children: lable.toUpperCase() }), _jsx(Box, { paddingLeft: 2, children: children })] }));
|
|
16
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { render, Box, Text } from 'ink';
|
|
4
|
+
import { RoleRow } from './RoleRow.js';
|
|
5
|
+
import { colors } from './tokens.js';
|
|
6
|
+
import { symbols } from './symbols.js';
|
|
7
|
+
const Demo = () => (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(RoleRow, { role: "you", timestamp: "14:08", children: _jsx(Text, { children: "where do we resolve the OpenAI key?" }) }), _jsx(RoleRow, { role: "glitool", children: _jsx(Text, { children: "The key is loaded from ~/.glitool/.env at startup." }) }), _jsx(RoleRow, { role: "tools", children: _jsxs(Text, { color: colors.sage, children: [symbols.done, " readFile \u2192 src/agent.ts"] }) }), _jsxs(RoleRow, { role: "plan", children: [_jsx(Text, { children: "1. Add /memory case" }), _jsx(Text, { children: "2. Spawn $EDITOR" }), _jsx(Text, { children: "3. Confirm before write" })] }), _jsx(RoleRow, { role: "note", children: _jsx(Text, { color: colors.muted, children: "tip: pass -e to enable explain mode" }) })] }));
|
|
8
|
+
render(_jsx(Demo, {}));
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { colors } from "./tokens.js";
|
|
5
|
+
export const SLASH_COMMANDS = [
|
|
6
|
+
{ cmd: '/help', desc: 'List commands and shortcuts' },
|
|
7
|
+
{ cmd: '/plan', desc: 'Force planning mode (complex tier)' },
|
|
8
|
+
{ cmd: '/coder', desc: 'Force coding mode (standard tier, graph)' },
|
|
9
|
+
{ cmd: '/quick', desc: 'Force quick chat (cheapest model)' },
|
|
10
|
+
{ cmd: '/explain', desc: 'Explanation only — no file edits' },
|
|
11
|
+
{ cmd: '/model', desc: 'Show or switch the active model' },
|
|
12
|
+
{ cmd: '/memory', desc: 'View or edit project memory and session summary' },
|
|
13
|
+
{ cmd: '/tools', desc: 'List enabled tools and their permissions' },
|
|
14
|
+
{ cmd: '/clear', desc: 'Clear current chat (keeps memory)' },
|
|
15
|
+
{ cmd: '/reset', desc: 'Clear chat and wipe memory + summary' },
|
|
16
|
+
{ cmd: '/exit', desc: 'Save summary and quit' },
|
|
17
|
+
];
|
|
18
|
+
export const SlashPalette = ({ items, selectedIndex }) => {
|
|
19
|
+
if (items.length === 0)
|
|
20
|
+
return null;
|
|
21
|
+
return (_jsxs(Box, { borderStyle: "single", borderColor: colors.line, flexDirection: "column", paddingX: 1, marginBottom: 1, children: [_jsx(Text, { color: colors.muted, children: "commands \u00B7 type to filter \u00B7 \u2191\u2193 navigate \u00B7 \u23CE run \u00B7 esc dismiss" }), items.map((item, i) => {
|
|
22
|
+
const isSelected = i === selectedIndex;
|
|
23
|
+
return (_jsxs(Box, { children: [_jsx(Box, { width: 2, children: _jsx(Text, { color: colors.amber, children: isSelected ? '›' : ' ' }) }), _jsx(Box, { width: 11, children: _jsx(Text, { color: isSelected ? colors.amber : colors.ink, bold: isSelected, children: item.cmd }) }), _jsx(Text, { color: isSelected ? colors.ink2 : colors.muted, children: item.desc })] }, item.cmd));
|
|
24
|
+
})] }));
|
|
25
|
+
};
|
|
26
|
+
export function filterCommands(input) {
|
|
27
|
+
if (!input.startsWith('/'))
|
|
28
|
+
return [];
|
|
29
|
+
if (input === '/')
|
|
30
|
+
return SLASH_COMMANDS;
|
|
31
|
+
return SLASH_COMMANDS.filter(c => c.cmd.startsWith(input));
|
|
32
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
|
+
import { colors } from "./tokens.js";
|
|
5
|
+
import { symbols } from "./symbols.js";
|
|
6
|
+
const STATE_COLOR = {
|
|
7
|
+
idle: colors.muted,
|
|
8
|
+
ready: colors.sage,
|
|
9
|
+
working: colors.amber,
|
|
10
|
+
awaiting: colors.mustard,
|
|
11
|
+
error: colors.rust,
|
|
12
|
+
};
|
|
13
|
+
const STATE_LABEL = {
|
|
14
|
+
idle: 'idle',
|
|
15
|
+
ready: 'ready',
|
|
16
|
+
working: 'working',
|
|
17
|
+
awaiting: 'awaiting confirm',
|
|
18
|
+
error: 'error',
|
|
19
|
+
};
|
|
20
|
+
function formatTokens(n) {
|
|
21
|
+
if (n < 1000)
|
|
22
|
+
return `${n} tokens`;
|
|
23
|
+
return `${(n / 1000).toFixed(1)}k tokens`;
|
|
24
|
+
}
|
|
25
|
+
function formatCost(c) {
|
|
26
|
+
return `$${c.toFixed(3)}`;
|
|
27
|
+
}
|
|
28
|
+
export const StatusBar = ({ state, detail, tier, anonLeft, model, tokens, cost }) => {
|
|
29
|
+
const dotColor = STATE_COLOR[state];
|
|
30
|
+
const stateLabel = STATE_LABEL[state];
|
|
31
|
+
const leftParts = [stateLabel];
|
|
32
|
+
if (detail)
|
|
33
|
+
leftParts.push(detail);
|
|
34
|
+
const rightParts = [];
|
|
35
|
+
if (tier) {
|
|
36
|
+
rightParts.push(`${tier}`);
|
|
37
|
+
}
|
|
38
|
+
else if (anonLeft !== undefined) {
|
|
39
|
+
rightParts.push(anonLeft > 0 ? `${anonLeft} free left` : 'sign up for more');
|
|
40
|
+
}
|
|
41
|
+
rightParts.push(model);
|
|
42
|
+
rightParts.push(formatTokens(tokens));
|
|
43
|
+
return (_jsxs(Box, { borderStyle: "single", borderColor: colors.line, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: dotColor, children: symbols.statusDot }), _jsx(Text, { color: colors.ink2, children: leftParts.join(' . ') })] }), _jsx(Box, { children: _jsx(Text, { color: colors.muted, children: rightParts.join(' · ') }) })] }));
|
|
44
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import React, { useEffect, useState } from "react";
|
|
4
|
+
import { spinnerFrames } from "./symbols.js";
|
|
5
|
+
import { colors } from "./tokens.js";
|
|
6
|
+
const TOOL_ICON = {
|
|
7
|
+
bash: '$',
|
|
8
|
+
writeFile: '✎',
|
|
9
|
+
editFile: '✎',
|
|
10
|
+
readFile: '≡',
|
|
11
|
+
listFiles: '≡',
|
|
12
|
+
searchCode: '⌕',
|
|
13
|
+
webFetch: '↗',
|
|
14
|
+
readBackgroundOutput: '⊙',
|
|
15
|
+
};
|
|
16
|
+
const TOOL_COLOR = {
|
|
17
|
+
bash: colors.mustard,
|
|
18
|
+
writeFile: colors.amber,
|
|
19
|
+
editFile: colors.amber,
|
|
20
|
+
readFile: colors.teal,
|
|
21
|
+
listFiles: colors.teal,
|
|
22
|
+
searchCode: colors.teal,
|
|
23
|
+
webFetch: colors.violet,
|
|
24
|
+
};
|
|
25
|
+
function formatTarget(tool, target) {
|
|
26
|
+
let value = target;
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(target);
|
|
29
|
+
value = parsed.command ?? parsed.filePath ?? parsed.pattern
|
|
30
|
+
?? parsed.query ?? parsed.url ?? target;
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
if (tool === 'bash') {
|
|
34
|
+
value = value.replace(/^npx\s+/, '').replace(/^node\s+/, '');
|
|
35
|
+
}
|
|
36
|
+
return value.length > 55 ? value.slice(0, 52) + '…' : value;
|
|
37
|
+
}
|
|
38
|
+
export const ToolLog = ({ entries }) => {
|
|
39
|
+
const [tick, setTick] = useState(0);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!entries.some(e => e.status === 'running'))
|
|
42
|
+
return;
|
|
43
|
+
const id = setInterval(() => setTick(t => t + 1), 120);
|
|
44
|
+
return () => clearInterval(id);
|
|
45
|
+
}, [entries]);
|
|
46
|
+
if (entries.length === 0)
|
|
47
|
+
return null;
|
|
48
|
+
return (_jsx(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: entries.map((e, i) => (_jsx(ToolLogRow, { entry: e, tick: tick }, i))) }));
|
|
49
|
+
};
|
|
50
|
+
const ToolLogRow = ({ entry, tick }) => {
|
|
51
|
+
const isRunning = entry.status === 'running';
|
|
52
|
+
const isDone = entry.status === 'done';
|
|
53
|
+
const spinner = spinnerFrames[tick % spinnerFrames.length];
|
|
54
|
+
const icon = isRunning ? spinner : isDone ? '✓' : '·';
|
|
55
|
+
const iconColor = isRunning ? colors.amber : isDone ? colors.sage : colors.muted;
|
|
56
|
+
const toolColor = TOOL_COLOR[entry.tool] ?? colors.muted2;
|
|
57
|
+
const toolIcon = TOOL_ICON[entry.tool] ?? '·';
|
|
58
|
+
const target = formatTarget(entry.tool, entry.target);
|
|
59
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: iconColor, children: [icon, " "] }), _jsxs(Text, { color: toolColor, children: [toolIcon, " "] }), _jsx(Text, { color: toolColor, dimColor: isDone, children: entry.tool }), _jsx(Text, { color: colors.muted, children: " \u00B7 " }), _jsx(Text, { color: isDone ? colors.muted : colors.muted2, children: target }), entry.stat
|
|
60
|
+
? _jsxs(Text, { color: colors.muted, children: [" ", entry.stat] })
|
|
61
|
+
: null] }));
|
|
62
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { colors } from "./tokens.js";
|
|
5
|
+
const InfoRow = ({ label, children }) => (_jsxs(Box, { children: [_jsx(Box, { children: _jsx(Text, { color: colors.muted }) }), _jsx(Box, { children: children })] }));
|
|
6
|
+
const Card = ({ title, badge, children }) => (_jsxs(Box, { borderStyle: "single", borderColor: colors.line, flexDirection: "column", paddingX: 1, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.muted, bold: true, children: title }), badge && _jsxs(Text, { color: colors.sage, children: [" [", badge, "]"] })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: children })] }));
|
|
7
|
+
export const Welcome = ({ name, version, workspace, runtime, sessionResumed = false, priorTurns = 0, }) => {
|
|
8
|
+
const subtitle = sessionResumed ? `AI coding assistant · session resumed · ${priorTurns} prior turns summarized` : 'AI coding assistant';
|
|
9
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Box, { width: 4, children: _jsx(Text, { color: colors.amber, bold: true, children: "g" }) }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Text, { color: colors.ink, bold: true, children: ["Welcome back, ", name, "."] }), _jsx(Text, { color: colors.muted, children: subtitle })] }), _jsxs(Text, { color: colors.muted, children: ["v", version] })] }), _jsxs(Card, { title: "Workspace", badge: "indexed", children: [_jsx(InfoRow, { label: "path", children: _jsx(Text, { color: colors.ink, children: workspace.path }) }), _jsx(InfoRow, { label: "branch", children: _jsx(Text, { color: colors.ink, children: workspace.branch }) }), _jsx(InfoRow, { label: "files", children: _jsxs(Text, { color: colors.ink, children: [workspace.files, workspace.loc ? ` · ${workspace.loc}` : ''] }) })] }), _jsxs(Card, { title: "Runtime", children: [_jsxs(InfoRow, { label: "model", children: [_jsx(Text, { color: colors.ink, children: runtime.model }), runtime.routerOn && _jsx(Text, { color: colors.amber, children: " [router on]" })] }), _jsx(InfoRow, { label: "tools", children: _jsxs(Text, { color: colors.ink, children: [runtime.toolsCount, " enabled"] }) }), _jsxs(InfoRow, { label: "explain", children: [_jsx(Text, { color: colors.ink, children: runtime.explainOn ? 'on' : 'off' }), !runtime.explainOn && _jsx(Text, { color: colors.muted, children: " \u00B7 pass -e to enable" })] })] }), _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(QuickHelp, { cmd: "/help", desc: "List commands and shortcuts" }), _jsx(QuickHelp, { cmd: "/model", desc: "Show or switch the active model" }), _jsx(QuickHelp, { cmd: "/tools", desc: "List available tools" }), _jsx(QuickHelp, { cmd: "/clear", desc: "Clear current session" }), _jsx(QuickHelp, { cmd: "/exit", desc: "Save summary and quit" })] })] }));
|
|
10
|
+
};
|
|
11
|
+
const QuickHelp = ({ cmd, desc }) => (_jsxs(Box, { children: [_jsx(Box, { width: 10, children: _jsx(Text, { color: colors.amber, bold: true, children: cmd }) }), _jsx(Text, { color: colors.muted, children: desc })] }));
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import chalk, {} from 'chalk';
|
|
2
|
+
const SEVERITY = {
|
|
3
|
+
CRITICAL: chalk.hex('#b54226').bold, // rust — danger
|
|
4
|
+
WARNING: chalk.hex('#c69a3a').bold, // mustard — caution
|
|
5
|
+
SUGGESTION: chalk.hex('#6c5ab8').bold, // violet — info
|
|
6
|
+
GOOD: chalk.hex('#5a8c5a').bold, // sage — success
|
|
7
|
+
SUMMARY: chalk.hex('#c4732e').bold, // amber — neutral header
|
|
8
|
+
ERROR: chalk.hex('#b54226').bold, // rust
|
|
9
|
+
NOTE: chalk.hex('#c69a3a').bold, // mustard
|
|
10
|
+
INFO: chalk.hex('#3e8a9a').bold, // teal
|
|
11
|
+
};
|
|
12
|
+
export function renderMarkdown(text) {
|
|
13
|
+
return text
|
|
14
|
+
// fenced code blocks ```...```
|
|
15
|
+
.replace(/```(?:\w+)?\n([\s\S]+?)```/gm, (_, code) => chalk.hex('#3e8a9a')(code.trimEnd()))
|
|
16
|
+
// ## H1/H2 → amber bold uppercase + padding
|
|
17
|
+
.replace(/^#{1,2} (.+)$/gm, (_, t) => '\n' + chalk.hex('#c4732e').bold(t.trim().toUpperCase()) + '\n')
|
|
18
|
+
// ### H3 → amber normal
|
|
19
|
+
.replace(/^### (.+)$/gm, (_, t) => chalk.hex('#c4732e')(t.trim()))
|
|
20
|
+
// **bold** → bold white
|
|
21
|
+
.replace(/\*\*(.+?)\*\*/g, (_, t) => chalk.bold.white(t))
|
|
22
|
+
// *italic* → muted italic
|
|
23
|
+
.replace(/\*([^*\n]+)\*/g, (_, t) => chalk.italic.hex('#8a8170')(t))
|
|
24
|
+
// `inline code` → teal
|
|
25
|
+
.replace(/`([^`\n]+)`/g, (_, t) => chalk.hex('#3e8a9a')(t))
|
|
26
|
+
// severity keywords on their own line (CRITICAL, WARNING etc)
|
|
27
|
+
.replace(/^(CRITICAL|WARNING|SUGGESTION|GOOD|SUMMARY|ERROR|NOTE|INFO)$/gm, (_, kw) => (SEVERITY[kw] ?? chalk.bold)(kw))
|
|
28
|
+
// severity keywords inline
|
|
29
|
+
.replace(/\b(CRITICAL|WARNING|SUGGESTION|GOOD|ERROR|NOTE|INFO)\b/g, (_, kw) => (SEVERITY[kw] ?? chalk.bold)(kw))
|
|
30
|
+
// file:line references → muted2 (readable but not distracting)
|
|
31
|
+
.replace(/\b([\w./\\-]+\.(ts|tsx|js|jsx|py|go|rs|json|md)):(\d+)(-\d+)?\b/g, (m) => chalk.hex('#b3a994')(m))
|
|
32
|
+
// --- horizontal rule → dim line
|
|
33
|
+
.replace(/^---+$/gm, chalk.hex('#8a8170')('─'.repeat(50)))
|
|
34
|
+
// numbered list → mustard number
|
|
35
|
+
.replace(/^(\d+)\. (.+)$/gm, (_, n, t) => ` ${chalk.hex('#c69a3a').bold(n + '.')} ${t}`)
|
|
36
|
+
// - bullet → amber arrow
|
|
37
|
+
.replace(/^[-*] (.+)$/gm, (_, t) => ` ${chalk.hex('#c4732e')('→')} ${t}`)
|
|
38
|
+
// _none_ → dimmed
|
|
39
|
+
.replace(/^_([^_\n]+)_$/gm, (_, t) => chalk.dim(t))
|
|
40
|
+
.trimEnd();
|
|
41
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const symbols = {
|
|
2
|
+
// input prefixes
|
|
3
|
+
prompt: '›',
|
|
4
|
+
confirmPrompt: '?',
|
|
5
|
+
// status
|
|
6
|
+
statusDot: '■',
|
|
7
|
+
// tool/agent state
|
|
8
|
+
done: '✓',
|
|
9
|
+
running: '●',
|
|
10
|
+
queued: '◌',
|
|
11
|
+
// diff
|
|
12
|
+
add: '+',
|
|
13
|
+
remove: '-',
|
|
14
|
+
// misc
|
|
15
|
+
arrow: '→',
|
|
16
|
+
warning: '!',
|
|
17
|
+
};
|
|
18
|
+
// Spinner frames for "running" state — cycle with setInterval(200ms)
|
|
19
|
+
export const spinnerFrames = ['◌', '◐', '●', '◑'];
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
"version": "
|
|
4
|
-
|
|
2
|
+
"name": "glitool",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "AI coding assistant for your terminal",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"glitool": "dist/index.js"
|
|
9
|
+
},
|
|
6
10
|
"scripts": {
|
|
7
11
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
12
|
"build": "tsc",
|
|
9
|
-
"dev": "tsx src/index.
|
|
13
|
+
"dev": "tsx src/index.tsx",
|
|
10
14
|
"prepublishOnly": "npm run build"
|
|
11
15
|
},
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
"keywords": ["ai", "cli", "coding-assistant", "openai", "terminal"],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai",
|
|
18
|
+
"cli",
|
|
19
|
+
"coding-assistant",
|
|
20
|
+
"openai",
|
|
21
|
+
"terminal"
|
|
22
|
+
],
|
|
20
23
|
"author": "Deep Sarkar <deep22sarkar@gmail.com>",
|
|
21
24
|
"license": "MIT",
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
"homepage": "https://github.com/deep9038/glitool",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/deep9038/glitool.git"
|
|
29
|
+
},
|
|
24
30
|
"dependencies": {
|
|
25
|
-
"@inquirer/prompts": "^8.4.1",
|
|
26
31
|
"@langchain/core": "^1.1.40",
|
|
27
32
|
"@langchain/langgraph": "^1.2.9",
|
|
28
33
|
"@langchain/ollama": "^1.2.6",
|
|
@@ -30,18 +35,20 @@
|
|
|
30
35
|
"chalk": "^5.6.2",
|
|
31
36
|
"commander": "^14.0.3",
|
|
32
37
|
"dotenv": "^17.4.2",
|
|
33
|
-
"
|
|
34
|
-
"ink
|
|
38
|
+
"fast-glob": "^3.3.3",
|
|
39
|
+
"ink": "^4.4.1",
|
|
40
|
+
"ink-text-input": "^5.0.1",
|
|
35
41
|
"ora": "^9.3.0",
|
|
36
|
-
"react": "^
|
|
42
|
+
"react": "^18.3.1",
|
|
43
|
+
"turndown": "^7.2.4"
|
|
37
44
|
},
|
|
38
45
|
"devDependencies": {
|
|
39
46
|
"@types/node": "^25.6.0",
|
|
40
|
-
"@types/react": "^
|
|
47
|
+
"@types/react": "^18.3.1",
|
|
48
|
+
"@types/turndown": "^5.0.6",
|
|
41
49
|
"tsx": "^4.21.0",
|
|
42
50
|
"typescript": "^6.0.2"
|
|
43
51
|
},
|
|
44
|
-
"type": "module",
|
|
45
52
|
"engines": {
|
|
46
53
|
"node": ">=18.0.0"
|
|
47
54
|
},
|