glitool 2.0.3 → 2.1.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.
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React, { useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import TextInput from 'ink-text-input';
5
+ import { colors } from './tokens.js';
6
+ export const ClarificationCard = ({ questions, onAnswer, onSkip }) => {
7
+ const [currentIndex, setCurrentIndex] = useState(0);
8
+ const [collectedAnswers, setCollectedAnswers] = useState([]);
9
+ const [input, setInput] = useState('');
10
+ const [otherMode, setOtherMode] = useState(false);
11
+ const current = questions[currentIndex];
12
+ const options = current?.options;
13
+ const showOptions = !!options && options.length > 0 && !otherMode;
14
+ const otherIndex = options ? options.length + 1 : 0;
15
+ const isLast = currentIndex === questions.length - 1;
16
+ const advance = (answer) => {
17
+ const updated = [...collectedAnswers, answer];
18
+ if (isLast) {
19
+ const combined = questions
20
+ .map((q, i) => `Q${i + 1}: ${q.question}\nA: ${updated[i]}`)
21
+ .join('\n\n');
22
+ onAnswer(combined);
23
+ }
24
+ else {
25
+ setCollectedAnswers(updated);
26
+ setCurrentIndex(currentIndex + 1);
27
+ setInput('');
28
+ setOtherMode(false);
29
+ }
30
+ };
31
+ const handleSubmit = (value) => {
32
+ const trimmed = value.trim();
33
+ if (trimmed === 's' || trimmed === '/skip') {
34
+ onSkip();
35
+ return;
36
+ }
37
+ if (!trimmed)
38
+ return;
39
+ advance(trimmed);
40
+ };
41
+ // Single-keypress capture for the numbered-options view
42
+ useInput((ch) => {
43
+ if (!showOptions)
44
+ return;
45
+ if (ch === 's') {
46
+ onSkip();
47
+ return;
48
+ }
49
+ const n = parseInt(ch, 10);
50
+ if (isNaN(n))
51
+ return;
52
+ if (n >= 1 && n <= options.length) {
53
+ advance(options[n - 1]);
54
+ }
55
+ else if (n === otherIndex) {
56
+ setOtherMode(true);
57
+ }
58
+ }, { isActive: showOptions });
59
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, marginBottom: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: "yellow", children: " Clarification needed " }), _jsxs(Text, { color: colors.muted, children: [" ", currentIndex + 1, " / ", questions.length, " "] })] }), collectedAnswers.length > 0 && (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: collectedAnswers.map((ans, i) => (_jsxs(Box, { gap: 1, children: [_jsxs(Text, { dimColor: true, children: [questions[i].question.slice(0, 45), "..."] }), _jsx(Text, { color: colors.muted, children: "\u2192" }), _jsx(Text, { color: "green", children: ans })] }, i))) })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "yellow", children: current.question }) }), showOptions ? (_jsxs(Box, { flexDirection: "column", children: [options.map((opt, i) => (_jsxs(Box, { children: [_jsxs(Text, { color: "cyan", children: [" ", i + 1, " "] }), _jsxs(Text, { children: [" ", opt] })] }, i))), _jsxs(Box, { children: [_jsxs(Text, { color: "cyan", children: [" ", otherIndex, " "] }), _jsx(Text, { dimColor: true, children: " Other (type your own)" })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["press 1-", otherIndex, " \u00B7 's' to skip"] }) })] })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { dimColor: true, children: ["type 's' to skip \u00B7 ", isLast ? 'Enter to submit' : 'Enter for next question'] }), _jsxs(Box, { borderStyle: "round", borderColor: "green", paddingX: 1, marginTop: 1, children: [_jsx(Text, { bold: true, color: "green", children: " You: " }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: otherMode ? 'Your own answer...' : 'Your answer...' })] })] }))] }));
60
+ };
@@ -2,24 +2,20 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import React, { useState, useEffect } from 'react';
3
3
  import { Box, Text } from 'ink';
4
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',
5
+ coding: '#3e8a9a',
6
+ debugging: '#b54226',
7
+ refactoring: '#3e8a9a',
8
+ git: '#5a8c5a',
9
+ planning: '#c4732e',
10
+ review: '#6c5ab8',
13
11
  };
14
12
  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',
13
+ coding: 'CODING',
14
+ debugging: 'DEBUGGING',
15
+ refactoring: 'REFACTORING',
16
+ git: 'GIT',
17
+ planning: 'PLANNING',
18
+ review: 'REVIEW',
23
19
  };
24
20
  const TOOL_VERB = {
25
21
  readFile: 'read',
@@ -28,6 +28,16 @@ function formatCost(c) {
28
28
  export const StatusBar = ({ state, detail, tier, anonLeft, model, tokens, cost }) => {
29
29
  const dotColor = STATE_COLOR[state];
30
30
  const stateLabel = STATE_LABEL[state];
31
+ // Animate dot when working
32
+ const SPINNER_FRAMES = ['◐', '◓', '◑', '◒'];
33
+ const [frame, setFrame] = React.useState(0);
34
+ React.useEffect(() => {
35
+ if (state !== 'working')
36
+ return;
37
+ const id = setInterval(() => setFrame(f => (f + 1) % SPINNER_FRAMES.length), 150);
38
+ return () => clearInterval(id);
39
+ }, [state]);
40
+ const dotChar = state === 'working' ? SPINNER_FRAMES[frame] : symbols.statusDot;
31
41
  const leftParts = [stateLabel];
32
42
  if (detail)
33
43
  leftParts.push(detail);
@@ -40,5 +50,5 @@ export const StatusBar = ({ state, detail, tier, anonLeft, model, tokens, cost }
40
50
  }
41
51
  rightParts.push(model);
42
52
  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(' · ') }) })] }));
53
+ return (_jsxs(Box, { borderStyle: "single", borderColor: colors.line, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: dotColor, children: dotChar }), _jsxs(Text, { color: colors.ink2, children: [" ", leftParts.join(' . ')] })] }), _jsx(Box, { children: _jsx(Text, { color: colors.muted, children: rightParts.join(' · ') }) })] }));
44
54
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "glitool",
3
- "version": "2.0.3",
3
+ "version": "2.1.0",
4
4
  "description": "AI coding assistant for your terminal",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,22 +0,0 @@
1
- // REPLACE the whole file with:
2
- import { makeLlm } from '../llm/factory.js';
3
- import { SystemMessage, HumanMessage } from "@langchain/core/messages";
4
- export async function runReviewer(plan, coderOutput, userMessage, model) {
5
- const llm = makeLlm(model);
6
- const response = await llm.invoke([
7
- new SystemMessage(`You are a code reviewer. check if the coder's work correctly fulfills the user's request. Return valid JSON only:
8
- {
9
- "approved": true or false,
10
- "feedback": "what needs fixing if not approved, otherwise empty string",
11
- "finalResponse": "the final message to show the user summarizing what was done"
12
- }`),
13
- new HumanMessage(`User request: ${userMessage}\n\nPlan:\n${plan}\n\nWhat was done:\n${coderOutput}`)
14
- ]);
15
- try {
16
- const cleaned = response.content.replace(/```json|```/g, '').trim();
17
- return JSON.parse(cleaned);
18
- }
19
- catch {
20
- return { approved: true, feedback: '', finalResponse: coderOutput };
21
- }
22
- }
@@ -1,51 +0,0 @@
1
- import fs from 'fs';
2
- import { get } from 'http';
3
- import path from 'path';
4
- const SKIP_DIRS = ['node_modules', '.git', 'dist', 'build', '.next'];
5
- const TEXT_EXTENSIONS = ['.ts', '.js', '.json', '.md', '.txt', '.tsx', '.jsx', '.css', '.html', '.env'];
6
- const MAX_FILE_SIZE = 50 * 1024; // 50kb
7
- function getFiles(dir, prefix = '') {
8
- const entries = fs.readdirSync(dir, { withFileTypes: true });
9
- const lines = [];
10
- for (const entry of entries) {
11
- if (SKIP_DIRS.includes(entry.name))
12
- continue;
13
- const fullPath = path.join(dir, entry.name);
14
- if (entry.isDirectory()) {
15
- lines.push(`${prefix}${entry.name}/`);
16
- lines.push(...getFiles(fullPath, prefix + ' '));
17
- }
18
- else {
19
- lines.push(`${prefix}${entry.name}`);
20
- }
21
- }
22
- return lines;
23
- }
24
- function getFileContents(dir) {
25
- const entries = fs.readdirSync(dir, { withFileTypes: true });
26
- let result = '';
27
- for (const entry of entries) {
28
- if (SKIP_DIRS.includes(entry.name))
29
- continue;
30
- const fullPath = path.join(dir, entry.name);
31
- if (entry.isDirectory()) {
32
- result += getFileContents(fullPath);
33
- }
34
- else {
35
- const ext = path.extname(entry.name);
36
- if (!TEXT_EXTENSIONS.includes(ext))
37
- continue;
38
- const stats = fs.statSync(fullPath);
39
- if (stats.size > MAX_FILE_SIZE)
40
- continue;
41
- const content = fs.readFileSync(fullPath, 'utf-8');
42
- result += `\n--- File: ${fullPath} ---\n${content}\n`;
43
- }
44
- }
45
- return result;
46
- }
47
- export function getProjectContext(dir) {
48
- const structure = getFiles(dir).join('\n');
49
- const contents = getFileContents(dir);
50
- return `Folder structure:\n${structure}\n\nFile contents:${contents}`;
51
- }
@@ -1,61 +0,0 @@
1
- import { tool } from "@langchain/core/tools";
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { execSync } from "child_process";
5
- import { z } from 'zod';
6
- const SKIP_DIRS = ['node_modules', '.git', 'dist', 'build', '.next'];
7
- function getDependencyList() {
8
- const packageJsonPath = path.join(process.cwd(), 'package.json');
9
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
10
- return packageJson.dependencies || {};
11
- }
12
- function checkForOutdatedDependencies() {
13
- try {
14
- const result = execSync('npm outdated --json', { timeout: 10000, stdio: 'pipe' }).toString();
15
- return JSON.parse(result);
16
- }
17
- catch (error) {
18
- return {};
19
- }
20
- }
21
- function analyzeCodeQuality() {
22
- try {
23
- const result = execSync(`eslint . --format json`, { timeout: 10000, stdio: 'pipe' }).toString();
24
- return JSON.parse(result);
25
- }
26
- catch (error) {
27
- return { error: [], warnings: [] };
28
- }
29
- }
30
- function getFiles(dir) {
31
- const entries = fs.readdirSync(dir, { withFileTypes: true });
32
- const files = [];
33
- for (const entry of entries) {
34
- if (SKIP_DIRS.includes(entry.name))
35
- continue;
36
- const fullPath = path.join(dir, entry.name);
37
- if (entry.isDirectory()) {
38
- files.push(...getFiles(fullPath));
39
- }
40
- else {
41
- files.push(fullPath);
42
- }
43
- }
44
- return files;
45
- }
46
- export const analyzeProjectTool = tool(async () => {
47
- const dependencies = getDependencyList();
48
- const outdatedDependencies = checkForOutdatedDependencies();
49
- const codeQualityResults = analyzeCodeQuality();
50
- const projectFiles = getFiles(process.cwd());
51
- return {
52
- dependencies,
53
- outdatedDependencies,
54
- codeQualityResults,
55
- projectFiles
56
- };
57
- }, {
58
- name: 'analyzeProject',
59
- description: 'analyze the current project for dependencies, code quality , and project structure . useful for getting an overview of the project and identifying potential issues.',
60
- schema: z.object({})
61
- });