@denizokcu/haze 0.0.1 → 0.0.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/CHANGELOG.md +24 -0
- package/README.md +169 -70
- package/dist/cli/commands/chat.d.ts +4 -1
- package/dist/cli/commands/chat.js +606 -24
- package/dist/cli/commands/commands.d.ts +5 -0
- package/dist/cli/commands/commands.js +220 -11
- package/dist/cli/commands/formatters.d.ts +1 -0
- package/dist/cli/commands/formatters.js +23 -3
- package/dist/cli/commands/skills.d.ts +1 -1
- package/dist/cli/commands/skills.js +8 -5
- package/dist/cli/commands/streaming.d.ts +7 -1
- package/dist/cli/commands/streaming.js +533 -41
- package/dist/cli/index.js +5 -12
- package/dist/config/inputHistory.js +8 -0
- package/dist/config/paths.d.ts +0 -1
- package/dist/config/paths.js +0 -1
- package/dist/config/providers.d.ts +26 -0
- package/dist/config/providers.js +88 -0
- package/dist/config/settings.d.ts +9 -2
- package/dist/core/agent/compaction.d.ts +13 -0
- package/dist/core/agent/compaction.js +34 -0
- package/dist/core/agent/errors.d.ts +3 -0
- package/dist/core/agent/errors.js +13 -0
- package/dist/core/agent/events.d.ts +58 -0
- package/dist/core/agent/events.js +3 -0
- package/dist/core/goal/completionPolicy.d.ts +27 -0
- package/dist/core/goal/completionPolicy.js +67 -0
- package/dist/core/goal/requestClassifier.d.ts +6 -0
- package/dist/core/goal/requestClassifier.js +31 -0
- package/dist/core/goal/sessionGoal.d.ts +30 -0
- package/dist/core/goal/sessionGoal.js +88 -0
- package/dist/core/session/sessionStore.d.ts +37 -0
- package/dist/core/session/sessionStore.js +59 -0
- package/dist/llm/client.d.ts +1 -1
- package/dist/llm/client.js +6 -6
- package/dist/llm/hazeTools.d.ts +70 -0
- package/dist/llm/hazeTools.js +311 -97
- package/dist/llm/initPrompt.js +7 -5
- package/dist/llm/systemPrompt.js +25 -11
- package/dist/skills/SkillLoader.d.ts +12 -2
- package/dist/skills/SkillLoader.js +64 -18
- package/dist/skills/SkillRegistry.d.ts +1 -5
- package/dist/skills/SkillRegistry.js +10 -21
- package/dist/skills/builder/SkillBuilder.d.ts +31 -1
- package/dist/skills/builder/SkillBuilder.js +291 -20
- package/dist/skills/skillTools.d.ts +20 -0
- package/dist/skills/skillTools.js +25 -0
- package/dist/skills/types.d.ts +12 -51
- package/dist/ui/components/ErrorView.d.ts +2 -1
- package/dist/ui/components/Header.d.ts +4 -2
- package/dist/ui/components/Header.js +2 -2
- package/dist/ui/components/MarkdownText.d.ts +2 -1
- package/dist/ui/components/TextInput.d.ts +13 -2
- package/dist/ui/components/TextInput.js +125 -25
- package/dist/ui/theme.d.ts +2 -0
- package/dist/ui/theme.js +3 -1
- package/dist/utils/fs.d.ts +1 -0
- package/dist/utils/fs.js +10 -6
- package/examples/skills/files/SKILL.md +16 -0
- package/examples/skills/files/examples/file-editing.md +3 -0
- package/package.json +9 -9
- package/dist/skills/installer/SkillInstaller.d.ts +0 -1
- package/dist/skills/installer/SkillInstaller.js +0 -48
- package/dist/skills/manifestSchema.d.ts +0 -31
- package/dist/skills/manifestSchema.js +0 -23
- package/dist/tools/ToolExecutor.d.ts +0 -3
- package/dist/tools/ToolExecutor.js +0 -15
- package/dist/tools/types.d.ts +0 -9
- package/dist/tools/types.js +0 -1
- package/examples/skills/files/prompts/file_tasks.md +0 -1
- package/examples/skills/files/skill.yaml +0 -28
- package/examples/skills/files/tools/list_files.ts +0 -21
- package/examples/skills/files/tools/read_file.ts +0 -12
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type TextInputSuggestion = {
|
|
3
|
+
value: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
kind?: 'command' | 'skill' | 'provider' | 'model';
|
|
6
|
+
};
|
|
7
|
+
export declare function TextInput({ placeholder, disabled, mask, historyItems, recordHistory, suggestions, suggestionMode, submitOnEmpty, onHistoryAdd, onCancel, onEscape, onSubmit }: {
|
|
2
8
|
placeholder?: string;
|
|
3
9
|
disabled?: boolean;
|
|
4
10
|
mask?: boolean;
|
|
5
11
|
historyItems?: string[];
|
|
6
12
|
recordHistory?: boolean;
|
|
13
|
+
suggestions?: TextInputSuggestion[];
|
|
14
|
+
suggestionMode?: 'slash' | 'always';
|
|
15
|
+
submitOnEmpty?: boolean;
|
|
7
16
|
onHistoryAdd?: (value: string) => void;
|
|
17
|
+
onCancel?: () => void;
|
|
18
|
+
onEscape?: () => void;
|
|
8
19
|
onSubmit: (value: string) => void;
|
|
9
|
-
}):
|
|
20
|
+
}): React.JSX.Element;
|
|
@@ -1,13 +1,64 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { Text, useInput } from 'ink';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
4
|
import { theme } from '../theme.js';
|
|
5
|
-
|
|
5
|
+
const COMPACT_PASTE_MIN_LINES = 4;
|
|
6
|
+
function normalizeLineEndings(text) {
|
|
7
|
+
return text.replace(/\r\n|\r/g, '\n');
|
|
8
|
+
}
|
|
9
|
+
function lineCount(text) {
|
|
10
|
+
return normalizeLineEndings(text).split('\n').length;
|
|
11
|
+
}
|
|
12
|
+
function pastePlaceholder(block) {
|
|
13
|
+
return `[paste #${block.id} +${block.lineCount} lines]`;
|
|
14
|
+
}
|
|
15
|
+
function updatePasteBlocksForReplacement(blocks, start, end, insertedLength) {
|
|
16
|
+
const delta = insertedLength - (end - start);
|
|
17
|
+
return blocks.flatMap(block => {
|
|
18
|
+
const replacesInsideBlock = start < block.end && end > block.start;
|
|
19
|
+
const insertsInsideBlock = start === end && start > block.start && start < block.end;
|
|
20
|
+
if (replacesInsideBlock || insertsInsideBlock)
|
|
21
|
+
return [];
|
|
22
|
+
if (block.start >= end)
|
|
23
|
+
return [{ ...block, start: block.start + delta, end: block.end + delta }];
|
|
24
|
+
return [block];
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function displayCursorForValueCursor(blocks, valueCursor) {
|
|
28
|
+
let displayCursor = valueCursor;
|
|
29
|
+
for (const block of [...blocks].sort((a, b) => a.start - b.start)) {
|
|
30
|
+
const placeholderLength = pastePlaceholder(block).length;
|
|
31
|
+
const compactedLength = block.end - block.start - placeholderLength;
|
|
32
|
+
if (valueCursor <= block.start)
|
|
33
|
+
break;
|
|
34
|
+
if (valueCursor < block.end)
|
|
35
|
+
return block.start + placeholderLength;
|
|
36
|
+
displayCursor -= compactedLength;
|
|
37
|
+
}
|
|
38
|
+
return displayCursor;
|
|
39
|
+
}
|
|
40
|
+
function compactPasteBlocksForDisplay(value, blocks) {
|
|
41
|
+
if (blocks.length === 0)
|
|
42
|
+
return value;
|
|
43
|
+
let displayValue = '';
|
|
44
|
+
let offset = 0;
|
|
45
|
+
for (const block of [...blocks].sort((a, b) => a.start - b.start)) {
|
|
46
|
+
displayValue += value.slice(offset, block.start);
|
|
47
|
+
displayValue += pastePlaceholder(block);
|
|
48
|
+
offset = block.end;
|
|
49
|
+
}
|
|
50
|
+
displayValue += value.slice(offset);
|
|
51
|
+
return displayValue;
|
|
52
|
+
}
|
|
53
|
+
export function TextInput({ placeholder, disabled, mask, historyItems = [], recordHistory = true, suggestions = [], suggestionMode = 'slash', submitOnEmpty = false, onHistoryAdd, onCancel, onEscape, onSubmit }) {
|
|
6
54
|
const [value, setValue] = useState('');
|
|
7
55
|
const [cursor, setCursor] = useState(0);
|
|
56
|
+
const [pasteBlocks, setPasteBlocks] = useState([]);
|
|
57
|
+
const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(0);
|
|
8
58
|
const history = useRef(historyItems);
|
|
9
59
|
const historyIndex = useRef(null);
|
|
10
60
|
const draft = useRef('');
|
|
61
|
+
const nextPasteId = useRef(1);
|
|
11
62
|
useEffect(() => {
|
|
12
63
|
history.current = historyItems;
|
|
13
64
|
}, [historyItems]);
|
|
@@ -15,40 +66,83 @@ export function TextInput({ placeholder, disabled, mask, historyItems = [], reco
|
|
|
15
66
|
if (!disabled) {
|
|
16
67
|
setValue('');
|
|
17
68
|
setCursor(0);
|
|
69
|
+
setPasteBlocks([]);
|
|
70
|
+
setSelectedSuggestionIndex(0);
|
|
18
71
|
historyIndex.current = null;
|
|
19
72
|
draft.current = '';
|
|
73
|
+
nextPasteId.current = 1;
|
|
20
74
|
}
|
|
21
75
|
}, [disabled]);
|
|
22
|
-
function setInput(next, nextCursor = next.length) {
|
|
76
|
+
function setInput(next, nextCursor = next.length, nextPasteBlocks = []) {
|
|
23
77
|
setValue(next);
|
|
24
78
|
setCursor(Math.max(0, Math.min(nextCursor, next.length)));
|
|
79
|
+
setPasteBlocks(nextPasteBlocks);
|
|
80
|
+
setSelectedSuggestionIndex(0);
|
|
81
|
+
}
|
|
82
|
+
function replaceInput(start, end, inserted) {
|
|
83
|
+
const normalizedInserted = normalizeLineEndings(inserted);
|
|
84
|
+
const next = value.slice(0, start) + normalizedInserted + value.slice(end);
|
|
85
|
+
const insertedLineCount = lineCount(normalizedInserted);
|
|
86
|
+
const updatedPasteBlocks = updatePasteBlocksForReplacement(pasteBlocks, start, end, normalizedInserted.length);
|
|
87
|
+
const insertedPasteBlock = !mask && insertedLineCount >= COMPACT_PASTE_MIN_LINES
|
|
88
|
+
? [{ id: nextPasteId.current++, start, end: start + normalizedInserted.length, lineCount: insertedLineCount }]
|
|
89
|
+
: [];
|
|
90
|
+
setInput(next, start + normalizedInserted.length, [...updatedPasteBlocks, ...insertedPasteBlock]);
|
|
91
|
+
historyIndex.current = null;
|
|
25
92
|
}
|
|
26
93
|
function showHistory(index) {
|
|
27
94
|
historyIndex.current = index;
|
|
28
95
|
setInput(history.current[index] ?? '');
|
|
29
96
|
}
|
|
97
|
+
const suggestionQuery = !mask && (suggestionMode === 'always' || value.startsWith('/'))
|
|
98
|
+
? (suggestionMode === 'always' ? value : value.slice(1)).toLowerCase()
|
|
99
|
+
: undefined;
|
|
100
|
+
const filteredSuggestions = suggestionQuery == null ? [] : suggestions
|
|
101
|
+
.filter(suggestion => {
|
|
102
|
+
const suggestionValue = suggestionMode === 'always' ? suggestion.value : suggestion.value.slice(1);
|
|
103
|
+
return suggestionValue.toLowerCase().includes(suggestionQuery) || suggestion.description?.toLowerCase().includes(suggestionQuery);
|
|
104
|
+
})
|
|
105
|
+
.slice(0, 8);
|
|
106
|
+
const activeSuggestionIndex = Math.min(selectedSuggestionIndex, Math.max(0, filteredSuggestions.length - 1));
|
|
107
|
+
const activeSuggestion = filteredSuggestions[activeSuggestionIndex];
|
|
108
|
+
function submitValue(submitted, historyValue = submitted) {
|
|
109
|
+
if (recordHistory && historyValue) {
|
|
110
|
+
if (history.current[history.current.length - 1] !== historyValue)
|
|
111
|
+
history.current = [...history.current, historyValue];
|
|
112
|
+
onHistoryAdd?.(historyValue);
|
|
113
|
+
}
|
|
114
|
+
onSubmit(submitted);
|
|
115
|
+
}
|
|
30
116
|
useInput((input, key) => {
|
|
31
|
-
if (disabled)
|
|
117
|
+
if (disabled) {
|
|
118
|
+
if (key.escape)
|
|
119
|
+
onCancel?.();
|
|
32
120
|
return;
|
|
121
|
+
}
|
|
33
122
|
if (key.escape) {
|
|
34
123
|
setInput('');
|
|
35
124
|
historyIndex.current = null;
|
|
36
125
|
draft.current = '';
|
|
126
|
+
nextPasteId.current = 1;
|
|
127
|
+
onEscape?.();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (key.tab && activeSuggestion) {
|
|
131
|
+
setInput(activeSuggestion.value);
|
|
132
|
+
historyIndex.current = null;
|
|
37
133
|
return;
|
|
38
134
|
}
|
|
39
135
|
if (key.return) {
|
|
40
|
-
const
|
|
136
|
+
const shouldUseSuggestion = activeSuggestion && activeSuggestion.value !== value.trim() && (suggestionMode === 'always' || value.startsWith('/'));
|
|
137
|
+
const submitted = shouldUseSuggestion ? activeSuggestion.value : value.trim();
|
|
138
|
+
const submittedSuggestion = activeSuggestion?.value === submitted ? activeSuggestion : undefined;
|
|
139
|
+
const historyValue = submittedSuggestion && submittedSuggestion.kind !== 'command' ? '' : submitted;
|
|
41
140
|
setInput('');
|
|
42
141
|
historyIndex.current = null;
|
|
43
142
|
draft.current = '';
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
history.current = [...history.current, submitted];
|
|
48
|
-
onHistoryAdd?.(submitted);
|
|
49
|
-
}
|
|
50
|
-
onSubmit(submitted);
|
|
51
|
-
}
|
|
143
|
+
nextPasteId.current = 1;
|
|
144
|
+
if (submitted || submitOnEmpty)
|
|
145
|
+
submitValue(submitted, historyValue);
|
|
52
146
|
return;
|
|
53
147
|
}
|
|
54
148
|
if (key.leftArrow) {
|
|
@@ -60,6 +154,10 @@ export function TextInput({ placeholder, disabled, mask, historyItems = [], reco
|
|
|
60
154
|
return;
|
|
61
155
|
}
|
|
62
156
|
if (key.upArrow) {
|
|
157
|
+
if (filteredSuggestions.length > 0 && activeSuggestionIndex > 0) {
|
|
158
|
+
setSelectedSuggestionIndex(current => Math.max(0, current - 1));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
63
161
|
if (history.current.length === 0)
|
|
64
162
|
return;
|
|
65
163
|
if (historyIndex.current === null) {
|
|
@@ -72,6 +170,10 @@ export function TextInput({ placeholder, disabled, mask, historyItems = [], reco
|
|
|
72
170
|
return;
|
|
73
171
|
}
|
|
74
172
|
if (key.downArrow) {
|
|
173
|
+
if (filteredSuggestions.length > 0 && activeSuggestionIndex < filteredSuggestions.length - 1) {
|
|
174
|
+
setSelectedSuggestionIndex(current => Math.min(filteredSuggestions.length - 1, current + 1));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
75
177
|
if (historyIndex.current === null)
|
|
76
178
|
return;
|
|
77
179
|
if (historyIndex.current < history.current.length - 1) {
|
|
@@ -86,15 +188,13 @@ export function TextInput({ placeholder, disabled, mask, historyItems = [], reco
|
|
|
86
188
|
if (key.backspace) {
|
|
87
189
|
if (cursor === 0)
|
|
88
190
|
return;
|
|
89
|
-
|
|
90
|
-
historyIndex.current = null;
|
|
191
|
+
replaceInput(cursor - 1, cursor, '');
|
|
91
192
|
return;
|
|
92
193
|
}
|
|
93
194
|
if (key.delete) {
|
|
94
195
|
if (cursor >= value.length)
|
|
95
196
|
return;
|
|
96
|
-
|
|
97
|
-
historyIndex.current = null;
|
|
197
|
+
replaceInput(cursor, cursor + 1, '');
|
|
98
198
|
return;
|
|
99
199
|
}
|
|
100
200
|
if (key.ctrl && input === 'a') {
|
|
@@ -108,13 +208,13 @@ export function TextInput({ placeholder, disabled, mask, historyItems = [], reco
|
|
|
108
208
|
if (key.ctrl && input === 'c')
|
|
109
209
|
return;
|
|
110
210
|
if (input) {
|
|
111
|
-
|
|
112
|
-
historyIndex.current = null;
|
|
211
|
+
replaceInput(cursor, cursor, input);
|
|
113
212
|
}
|
|
114
213
|
});
|
|
115
|
-
const displayValue = mask ? '•'.repeat(value.length) : value;
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
|
|
214
|
+
const displayValue = mask ? '•'.repeat(value.length) : compactPasteBlocksForDisplay(value, pasteBlocks);
|
|
215
|
+
const displayCursor = mask ? cursor : displayCursorForValueCursor(pasteBlocks, cursor);
|
|
216
|
+
const beforeCursor = displayValue.slice(0, displayCursor);
|
|
217
|
+
const cursorChar = displayValue[displayCursor] ?? ' ';
|
|
218
|
+
const afterCursor = displayValue.slice(displayCursor + 1);
|
|
219
|
+
return _jsxs(Box, { flexDirection: "column", width: "100%", children: [filteredSuggestions.length > 0 && _jsx(Box, { flexDirection: "column", marginBottom: 1, children: filteredSuggestions.map((suggestion, index) => _jsxs(Text, { color: index === activeSuggestionIndex ? theme.success : theme.muted, wrap: "truncate-end", children: [index === activeSuggestionIndex ? '› ' : ' ', suggestion.value, _jsxs(Text, { color: theme.muted, children: [" ", suggestion.kind ?? 'command', suggestion.description ? ` — ${suggestion.description}` : ''] })] }, suggestion.value)) }), _jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: theme.purple, children: "\u203A " }), value.length === 0 ? _jsxs(_Fragment, { children: [_jsx(Text, { inverse: true, children: " " }), _jsxs(Text, { color: theme.muted, dimColor: true, children: [" ", placeholder ?? 'Type a message...'] })] }) : _jsxs(_Fragment, { children: [beforeCursor, _jsx(Text, { inverse: true, children: cursorChar }), afterCursor] })] })] });
|
|
120
220
|
}
|
package/dist/ui/theme.d.ts
CHANGED
package/dist/ui/theme.js
CHANGED
|
@@ -2,10 +2,12 @@ export const theme = {
|
|
|
2
2
|
purple: '#a78bfa',
|
|
3
3
|
deepPurple: '#6d28d9',
|
|
4
4
|
violet: '#8b5cf6',
|
|
5
|
+
blue: '#60a5fa',
|
|
5
6
|
muted: '#9ca3af',
|
|
6
7
|
danger: '#fb7185',
|
|
7
|
-
success: '#
|
|
8
|
+
success: '#39ff14',
|
|
8
9
|
warning: '#fbbf24',
|
|
10
|
+
orange: '#f59e0b',
|
|
9
11
|
codeBg: '#1f1633',
|
|
10
12
|
quoteBg: '#171127'
|
|
11
13
|
};
|
package/dist/utils/fs.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface WalkEntry {
|
|
|
8
8
|
export interface WalkOptions {
|
|
9
9
|
recursive?: boolean;
|
|
10
10
|
maxEntries?: number;
|
|
11
|
+
cursor?: string;
|
|
11
12
|
filter?: (entry: WalkEntry) => boolean | Promise<boolean>;
|
|
12
13
|
}
|
|
13
14
|
export declare function walkDir(root: string, options?: WalkOptions): Promise<WalkEntry[]>;
|
package/dist/utils/fs.js
CHANGED
|
@@ -2,10 +2,12 @@ import fs from 'fs-extra';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
const SKIP_ENTRIES = new Set(['node_modules', '.git']);
|
|
4
4
|
export async function walkDir(root, options = {}) {
|
|
5
|
-
const { recursive = false, maxEntries = Infinity, filter } = options;
|
|
5
|
+
const { recursive = false, maxEntries = Infinity, cursor, filter } = options;
|
|
6
6
|
const result = [];
|
|
7
|
+
let cursorSeen = cursor == null;
|
|
7
8
|
async function walk(dir) {
|
|
8
|
-
|
|
9
|
+
const entries = (await fs.readdir(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
10
|
+
for (const entry of entries) {
|
|
9
11
|
if (result.length >= maxEntries)
|
|
10
12
|
return;
|
|
11
13
|
if (SKIP_ENTRIES.has(entry.name))
|
|
@@ -19,10 +21,12 @@ export async function walkDir(root, options = {}) {
|
|
|
19
21
|
isDirectory: entry.isDirectory(),
|
|
20
22
|
isFile: entry.isFile(),
|
|
21
23
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (
|
|
24
|
+
const passesFilter = !filter || await filter(walkEntry);
|
|
25
|
+
if (passesFilter && cursorSeen)
|
|
26
|
+
result.push(walkEntry);
|
|
27
|
+
if (passesFilter && !cursorSeen && relativePath === cursor)
|
|
28
|
+
cursorSeen = true;
|
|
29
|
+
if (entry.isDirectory() && recursive && (!filter || passesFilter))
|
|
26
30
|
await walk(absolutePath);
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: files
|
|
3
|
+
description: Use when the user asks for a careful file-inspection or file-editing workflow.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use Haze's built-in file tools rather than shell commands for file discovery and edits.
|
|
7
|
+
|
|
8
|
+
Workflow:
|
|
9
|
+
1. Use `listFiles` for project discovery.
|
|
10
|
+
2. Use `readFile` before editing existing files.
|
|
11
|
+
3. Prefer `editFile` for small exact changes.
|
|
12
|
+
4. Use `replaceLines` when exact replacement is ambiguous.
|
|
13
|
+
5. Use `writeFile` only for new files or intentional complete rewrites.
|
|
14
|
+
|
|
15
|
+
References:
|
|
16
|
+
- examples/file-editing.md
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@denizokcu/haze",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "A pragmatic agentic CLI for building apps from the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"node": ">=20"
|
|
20
20
|
},
|
|
21
21
|
"bin": {
|
|
22
|
-
"haze": "
|
|
22
|
+
"haze": "bin/haze.js"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"bin",
|
|
@@ -44,15 +44,15 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@ai-sdk/openai": "3.0.67",
|
|
47
|
-
"@inquirer/prompts": "8.5.
|
|
48
|
-
"ai": "6.0.
|
|
47
|
+
"@inquirer/prompts": "8.5.2",
|
|
48
|
+
"ai": "6.0.194",
|
|
49
49
|
"cli-highlight": "2.1.11",
|
|
50
50
|
"commander": "15.0.0",
|
|
51
51
|
"fs-extra": "11.3.5",
|
|
52
52
|
"ink": "7.0.5",
|
|
53
53
|
"ink-spinner": "5.0.0",
|
|
54
54
|
"marked": "18.0.4",
|
|
55
|
-
"react": "19.2.
|
|
55
|
+
"react": "19.2.7",
|
|
56
56
|
"strip-ansi": "7.2.0",
|
|
57
57
|
"yaml": "2.9.0",
|
|
58
58
|
"zod": "4.4.3"
|
|
@@ -61,11 +61,11 @@
|
|
|
61
61
|
"@eslint/js": "^10.0.1",
|
|
62
62
|
"@types/fs-extra": "11.0.4",
|
|
63
63
|
"@types/node": "25.9.1",
|
|
64
|
-
"@types/react": "19.2.
|
|
64
|
+
"@types/react": "19.2.16",
|
|
65
65
|
"eslint": "^10.4.1",
|
|
66
|
-
"tsx": "4.22.
|
|
66
|
+
"tsx": "4.22.4",
|
|
67
67
|
"typescript": "6.0.3",
|
|
68
|
-
"typescript-eslint": "^8.60.
|
|
69
|
-
"vitest": "^4.1.
|
|
68
|
+
"typescript-eslint": "^8.60.1",
|
|
69
|
+
"vitest": "^4.1.8"
|
|
70
70
|
}
|
|
71
71
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function installSkill(spec: string): Promise<void>;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { spawnSync } from 'node:child_process';
|
|
5
|
-
import { confirm } from '@inquirer/prompts';
|
|
6
|
-
import { GLOBAL_SKILLS_DIR } from '../../config/paths.js';
|
|
7
|
-
import { loadSkill } from '../SkillLoader.js';
|
|
8
|
-
import { listFilesRecursive } from '../../utils/fs.js';
|
|
9
|
-
function repoUrl(spec) {
|
|
10
|
-
if (spec.startsWith('http'))
|
|
11
|
-
return spec;
|
|
12
|
-
if (spec.startsWith('github:'))
|
|
13
|
-
return `https://github.com/${spec.slice(7)}.git`;
|
|
14
|
-
if (/^[\w.-]+\/[\w.-]+$/.test(spec))
|
|
15
|
-
return `https://github.com/${spec}.git`;
|
|
16
|
-
return spec;
|
|
17
|
-
}
|
|
18
|
-
export async function installSkill(spec) {
|
|
19
|
-
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'haze-skill-'));
|
|
20
|
-
const url = repoUrl(spec);
|
|
21
|
-
const clone = spawnSync('git', ['clone', '--depth=1', url, tmp], { stdio: 'inherit' });
|
|
22
|
-
if (clone.status !== 0)
|
|
23
|
-
throw new Error('git clone failed');
|
|
24
|
-
const skill = await loadSkill(tmp, 'global');
|
|
25
|
-
if (!skill)
|
|
26
|
-
throw new Error('Repository does not contain a root skill.yaml');
|
|
27
|
-
console.log(`\nSkill: ${skill.manifest.name} ${skill.manifest.version}`);
|
|
28
|
-
console.log(skill.manifest.description);
|
|
29
|
-
console.log('\nFiles:');
|
|
30
|
-
for (const f of await listFilesRecursive(tmp))
|
|
31
|
-
console.log(` ${f}`);
|
|
32
|
-
const deps = skill.manifest.dependencies;
|
|
33
|
-
if (deps?.cli?.length)
|
|
34
|
-
console.log(`\nCLI dependencies: ${deps.cli.map(d => d.name).join(', ')}`);
|
|
35
|
-
if (deps?.env?.length)
|
|
36
|
-
console.log(`Env dependencies: ${deps.env.map(d => d.name).join(', ')}`);
|
|
37
|
-
const dest = path.join(GLOBAL_SKILLS_DIR, skill.manifest.name);
|
|
38
|
-
if (await fs.pathExists(dest))
|
|
39
|
-
console.log(`\nExisting skill will be replaced: ${dest}`);
|
|
40
|
-
const ok = await confirm({ message: 'Approve and activate this skill? It is code from the internet, regrettably.', default: false });
|
|
41
|
-
if (!ok)
|
|
42
|
-
return;
|
|
43
|
-
await fs.remove(dest);
|
|
44
|
-
await fs.ensureDir(path.dirname(dest));
|
|
45
|
-
await fs.copy(tmp, dest, { filter: src => !src.includes(`${path.sep}.git${path.sep}`) });
|
|
46
|
-
await fs.remove(path.join(dest, '.git'));
|
|
47
|
-
console.log(`Installed ${skill.manifest.name} to ${dest}`);
|
|
48
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
export declare const skillManifestSchema: z.ZodObject<{
|
|
3
|
-
name: z.ZodString;
|
|
4
|
-
version: z.ZodString;
|
|
5
|
-
description: z.ZodString;
|
|
6
|
-
author: z.ZodOptional<z.ZodString>;
|
|
7
|
-
homepage: z.ZodOptional<z.ZodString>;
|
|
8
|
-
dependencies: z.ZodOptional<z.ZodObject<{
|
|
9
|
-
cli: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
10
|
-
name: z.ZodString;
|
|
11
|
-
description: z.ZodOptional<z.ZodString>;
|
|
12
|
-
required: z.ZodOptional<z.ZodBoolean>;
|
|
13
|
-
}, z.core.$strip>>>;
|
|
14
|
-
env: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
15
|
-
name: z.ZodString;
|
|
16
|
-
description: z.ZodOptional<z.ZodString>;
|
|
17
|
-
required: z.ZodOptional<z.ZodBoolean>;
|
|
18
|
-
}, z.core.$strip>>>;
|
|
19
|
-
}, z.core.$strip>>;
|
|
20
|
-
tools: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
21
|
-
name: z.ZodString;
|
|
22
|
-
description: z.ZodString;
|
|
23
|
-
path: z.ZodString;
|
|
24
|
-
input: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
|
|
25
|
-
}, z.core.$strip>>>;
|
|
26
|
-
prompts: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
27
|
-
name: z.ZodString;
|
|
28
|
-
description: z.ZodOptional<z.ZodString>;
|
|
29
|
-
path: z.ZodString;
|
|
30
|
-
}, z.core.$strip>>>;
|
|
31
|
-
}, z.core.$strip>;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
const jsonSchema = z.lazy(() => z.object({
|
|
3
|
-
type: z.string().optional(),
|
|
4
|
-
required: z.array(z.string()).optional(),
|
|
5
|
-
properties: z.record(z.string(), jsonSchema).optional(),
|
|
6
|
-
items: jsonSchema.optional(),
|
|
7
|
-
description: z.string().optional(),
|
|
8
|
-
enum: z.array(z.unknown()).optional(),
|
|
9
|
-
additionalProperties: z.union([z.boolean(), jsonSchema]).optional()
|
|
10
|
-
}).passthrough());
|
|
11
|
-
export const skillManifestSchema = z.object({
|
|
12
|
-
name: z.string().min(1).regex(/^[a-zA-Z0-9_-]+$/),
|
|
13
|
-
version: z.string().min(1),
|
|
14
|
-
description: z.string().min(1),
|
|
15
|
-
author: z.string().optional(),
|
|
16
|
-
homepage: z.string().url().optional(),
|
|
17
|
-
dependencies: z.object({
|
|
18
|
-
cli: z.array(z.object({ name: z.string(), description: z.string().optional(), required: z.boolean().optional() })).optional(),
|
|
19
|
-
env: z.array(z.object({ name: z.string(), description: z.string().optional(), required: z.boolean().optional() })).optional()
|
|
20
|
-
}).optional(),
|
|
21
|
-
tools: z.array(z.object({ name: z.string().min(1), description: z.string().min(1), path: z.string().min(1), input: jsonSchema.optional() })).optional(),
|
|
22
|
-
prompts: z.array(z.object({ name: z.string().min(1), description: z.string().optional(), path: z.string().min(1) })).optional(),
|
|
23
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { pathToFileURL } from 'node:url';
|
|
2
|
-
export async function executeTool(tool, skill, input) {
|
|
3
|
-
try {
|
|
4
|
-
const context = { cwd: process.cwd(), skillDir: skill.dir };
|
|
5
|
-
const mod = await import(`${pathToFileURL(tool.absolutePath).href}?t=${Date.now()}`);
|
|
6
|
-
if (typeof mod.execute !== 'function') {
|
|
7
|
-
return { ok: false, message: 'Tool must export execute(input, context)' };
|
|
8
|
-
}
|
|
9
|
-
const result = await mod.execute(input ?? {}, context);
|
|
10
|
-
return result ?? { ok: true };
|
|
11
|
-
}
|
|
12
|
-
catch (error) {
|
|
13
|
-
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
14
|
-
}
|
|
15
|
-
}
|
package/dist/tools/types.d.ts
DELETED
package/dist/tools/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
For file tasks, prefer listing files before reading unknown paths. Never ask to read secrets unless the user explicitly requests it.
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
name: files
|
|
2
|
-
version: 0.1.0
|
|
3
|
-
description: Safe-ish file inspection tools for the current project.
|
|
4
|
-
dependencies: {}
|
|
5
|
-
tools:
|
|
6
|
-
- name: list_files
|
|
7
|
-
description: List files under a directory in the current project. Skips .git and node_modules.
|
|
8
|
-
path: tools/list_files.ts
|
|
9
|
-
input:
|
|
10
|
-
type: object
|
|
11
|
-
properties:
|
|
12
|
-
dir:
|
|
13
|
-
type: string
|
|
14
|
-
description: Directory to list, relative to the current working directory.
|
|
15
|
-
- name: read_file
|
|
16
|
-
description: Read a UTF-8 text file from the current project.
|
|
17
|
-
path: tools/read_file.ts
|
|
18
|
-
input:
|
|
19
|
-
type: object
|
|
20
|
-
required: [path]
|
|
21
|
-
properties:
|
|
22
|
-
path:
|
|
23
|
-
type: string
|
|
24
|
-
description: File path relative to the current working directory.
|
|
25
|
-
prompts:
|
|
26
|
-
- name: file_tasks
|
|
27
|
-
description: Guidance for file inspection tasks.
|
|
28
|
-
path: prompts/file_tasks.md
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
export async function execute(input: {dir?: string}, context: {cwd: string}) {
|
|
5
|
-
const root = path.resolve(context.cwd, input.dir ?? '.');
|
|
6
|
-
if (!root.startsWith(context.cwd)) return {ok: false, message: 'Refusing to list outside the current project.'};
|
|
7
|
-
const files: string[] = [];
|
|
8
|
-
async function walk(dir: string) {
|
|
9
|
-
for (const entry of await fs.readdir(dir)) {
|
|
10
|
-
if (entry === '.git' || entry === 'node_modules' || entry === 'dist') continue;
|
|
11
|
-
const full = path.join(dir, entry);
|
|
12
|
-
const rel = path.relative(context.cwd, full);
|
|
13
|
-
const stat = await fs.stat(full);
|
|
14
|
-
if (stat.isDirectory()) await walk(full);
|
|
15
|
-
else files.push(rel);
|
|
16
|
-
if (files.length >= 500) return;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
await walk(root);
|
|
20
|
-
return {ok: true, message: `Found ${files.length} files.`, data: files.sort()};
|
|
21
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
export async function execute(input: {path: string}, context: {cwd: string}) {
|
|
5
|
-
if (!input.path) return {ok: false, message: 'Missing path.'};
|
|
6
|
-
const full = path.resolve(context.cwd, input.path);
|
|
7
|
-
if (!full.startsWith(context.cwd)) return {ok: false, message: 'Refusing to read outside the current project.'};
|
|
8
|
-
const stat = await fs.stat(full);
|
|
9
|
-
if (!stat.isFile()) return {ok: false, message: 'Path is not a file.'};
|
|
10
|
-
if (stat.size > 200_000) return {ok: false, message: 'File is too large for the intentionally tiny attention span.'};
|
|
11
|
-
return {ok: true, message: `Read ${input.path}.`, data: await fs.readFile(full, 'utf8')};
|
|
12
|
-
}
|