@denizokcu/haze 0.0.1 → 0.0.2

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +114 -69
  3. package/dist/cli/commands/chat.d.ts +1 -0
  4. package/dist/cli/commands/chat.js +203 -11
  5. package/dist/cli/commands/commands.js +130 -6
  6. package/dist/cli/commands/formatters.d.ts +1 -0
  7. package/dist/cli/commands/formatters.js +18 -1
  8. package/dist/cli/commands/skills.d.ts +1 -1
  9. package/dist/cli/commands/skills.js +8 -5
  10. package/dist/cli/commands/streaming.d.ts +2 -0
  11. package/dist/cli/commands/streaming.js +424 -39
  12. package/dist/cli/index.js +1 -11
  13. package/dist/config/paths.d.ts +0 -1
  14. package/dist/config/paths.js +0 -1
  15. package/dist/llm/client.js +1 -1
  16. package/dist/llm/hazeTools.d.ts +32 -0
  17. package/dist/llm/hazeTools.js +136 -26
  18. package/dist/llm/initPrompt.js +2 -2
  19. package/dist/llm/systemPrompt.js +23 -9
  20. package/dist/skills/SkillLoader.d.ts +12 -2
  21. package/dist/skills/SkillLoader.js +64 -18
  22. package/dist/skills/SkillRegistry.d.ts +1 -5
  23. package/dist/skills/SkillRegistry.js +10 -21
  24. package/dist/skills/builder/SkillBuilder.d.ts +25 -1
  25. package/dist/skills/builder/SkillBuilder.js +169 -20
  26. package/dist/skills/skillTools.d.ts +20 -0
  27. package/dist/skills/skillTools.js +25 -0
  28. package/dist/skills/types.d.ts +12 -51
  29. package/dist/ui/components/Header.d.ts +2 -1
  30. package/dist/ui/components/Header.js +12 -2
  31. package/dist/ui/components/TextInput.d.ts +8 -1
  32. package/dist/ui/components/TextInput.js +29 -14
  33. package/dist/ui/theme.d.ts +1 -0
  34. package/dist/ui/theme.js +1 -0
  35. package/dist/utils/fs.d.ts +1 -0
  36. package/dist/utils/fs.js +10 -6
  37. package/examples/skills/files/SKILL.md +16 -0
  38. package/examples/skills/files/examples/file-editing.md +3 -0
  39. package/package.json +2 -2
  40. package/dist/skills/installer/SkillInstaller.d.ts +0 -1
  41. package/dist/skills/installer/SkillInstaller.js +0 -48
  42. package/dist/skills/manifestSchema.d.ts +0 -31
  43. package/dist/skills/manifestSchema.js +0 -23
  44. package/dist/tools/ToolExecutor.d.ts +0 -3
  45. package/dist/tools/ToolExecutor.js +0 -15
  46. package/dist/tools/types.d.ts +0 -9
  47. package/dist/tools/types.js +0 -1
  48. package/examples/skills/files/prompts/file_tasks.md +0 -1
  49. package/examples/skills/files/skill.yaml +0 -28
  50. package/examples/skills/files/tools/list_files.ts +0 -21
  51. package/examples/skills/files/tools/read_file.ts +0 -12
@@ -1,10 +1,131 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'node:path';
1
3
  import { buildInitPrompt } from '../../llm/initPrompt.js';
2
- import { updateSettings } from '../../config/settings.js';
4
+ function skillHelp() {
5
+ return [
6
+ 'Skill commands:',
7
+ '/skill create <description>',
8
+ ' Create a Markdown skill in ~/.haze/skills.',
9
+ '/skill list',
10
+ ' List installed skills.',
11
+ '/skill info <name>',
12
+ ' Show a skill description and path.',
13
+ '/skill validate <name-or-dir>',
14
+ ' Validate a skill directory containing SKILL.md.',
15
+ '/skill remove <name> --yes',
16
+ ' Remove an installed skill. Requires --yes because it deletes files.',
17
+ ].join('\n');
18
+ }
19
+ async function handleSkillCommand(value, ctx) {
20
+ const parts = value.trim().split(/\s+/).filter(Boolean);
21
+ const subcommand = parts[1];
22
+ if (!subcommand || subcommand === 'help') {
23
+ ctx.addSystemMessage(skillHelp());
24
+ return 'handled';
25
+ }
26
+ if (subcommand === 'create') {
27
+ const description = parts.slice(2).join(' ');
28
+ if (!description) {
29
+ ctx.addSystemMessage('Usage: /skill create <description>');
30
+ return 'handled';
31
+ }
32
+ const { createSkill } = await import('../../skills/builder/SkillBuilder.js');
33
+ const result = await createSkill(description);
34
+ ctx.addSystemMessage(`Created skill ${result.name} at ${result.file}. Edit SKILL.md to refine its workflow.`);
35
+ return 'handled';
36
+ }
37
+ if (subcommand === 'list') {
38
+ const { loadSkillRegistry } = await import('../../skills/SkillRegistry.js');
39
+ const registry = await loadSkillRegistry();
40
+ const skills = [...registry.skills.values()];
41
+ ctx.addSystemMessage(skills.length === 0
42
+ ? 'No installed skills found.'
43
+ : ['Installed skills:', ...skills.map(s => `- ${s.name} — ${s.description}`)].join('\n'));
44
+ return 'handled';
45
+ }
46
+ if (subcommand === 'info') {
47
+ const name = parts[2];
48
+ if (!name) {
49
+ ctx.addSystemMessage('Usage: /skill info <name>');
50
+ return 'handled';
51
+ }
52
+ const { loadSkillRegistry } = await import('../../skills/SkillRegistry.js');
53
+ const registry = await loadSkillRegistry();
54
+ const skill = registry.skills.get(name);
55
+ if (!skill) {
56
+ ctx.addSystemMessage(`No skill named ${name}`);
57
+ return 'handled';
58
+ }
59
+ ctx.addSystemMessage([
60
+ `${skill.name}`,
61
+ skill.description,
62
+ '',
63
+ `References: ${skill.references.length}`,
64
+ `Path: ${skill.dir}`,
65
+ ].join('\n'));
66
+ return 'handled';
67
+ }
68
+ if (subcommand === 'validate') {
69
+ const target = parts[2];
70
+ if (!target) {
71
+ ctx.addSystemMessage('Usage: /skill validate <name-or-dir>');
72
+ return 'handled';
73
+ }
74
+ const { GLOBAL_SKILLS_DIR } = await import('../../config/paths.js');
75
+ const { loadSkill } = await import('../../skills/SkillLoader.js');
76
+ const direct = path.resolve(target);
77
+ const dir = await fs.pathExists(path.join(direct, 'SKILL.md')) ? direct : path.join(GLOBAL_SKILLS_DIR, target);
78
+ const skill = await loadSkill(dir, 'global');
79
+ ctx.addSystemMessage(skill ? `Valid: ${skill.name}` : 'No SKILL.md found');
80
+ return 'handled';
81
+ }
82
+ if (subcommand === 'remove') {
83
+ const name = parts[2];
84
+ if (!name || !parts.includes('--yes')) {
85
+ ctx.addSystemMessage('Usage: /skill remove <name> --yes');
86
+ return 'handled';
87
+ }
88
+ const { loadSkillRegistry } = await import('../../skills/SkillRegistry.js');
89
+ const registry = await loadSkillRegistry();
90
+ const skill = registry.skills.get(name);
91
+ if (!skill) {
92
+ ctx.addSystemMessage(`No skill named ${name}`);
93
+ return 'handled';
94
+ }
95
+ await fs.remove(skill.dir);
96
+ ctx.addSystemMessage(`Removed ${name} from ${skill.dir}.`);
97
+ return 'handled';
98
+ }
99
+ ctx.addSystemMessage(`Unknown skill command: ${parts[0]} ${subcommand}\n\n${skillHelp()}`);
100
+ return 'handled';
101
+ }
3
102
  export async function handleSlashCommand(value, ctx) {
4
103
  if (value === '/exit' || value === '/quit')
5
104
  return 'exit';
6
105
  if (value === '/help') {
7
- ctx.addSystemMessage('Commands: /login, /model <name>, /model, /settings, /init, /clear, /exit');
106
+ ctx.addSystemMessage([
107
+ 'Commands:',
108
+ '/help',
109
+ ' Show all available slash commands and what they do.',
110
+ '/login',
111
+ ' Save an OpenRouter API key to ~/.haze/settings.json.',
112
+ '/model',
113
+ ' Prompt for an OpenRouter model name to use for future chats.',
114
+ '/model <name>',
115
+ ' Set the OpenRouter model directly, for example x-ai/grok-build-0.1.',
116
+ '/settings',
117
+ ' Show the configured provider, model, API key status, and loaded context files.',
118
+ '/skill help',
119
+ ' Manage Markdown skills from inside the Haze app.',
120
+ '/init',
121
+ ' Inspect the current workspace and create or update AGENTS.md project instructions.',
122
+ '/clear',
123
+ ' Clear the current chat conversation history.',
124
+ '/exit',
125
+ ' Exit Haze.',
126
+ '/quit',
127
+ ' Exit Haze.',
128
+ ].join('\n'));
8
129
  return 'handled';
9
130
  }
10
131
  if (value === '/clear') {
@@ -23,14 +144,13 @@ export async function handleSlashCommand(value, ctx) {
23
144
  }
24
145
  if (value === '/model') {
25
146
  ctx.setMode('model');
26
- ctx.addSystemMessage('Enter an OpenRouter model name, e.g. openai/gpt-4o-mini or anthropic/claude-3.5-sonnet.');
147
+ ctx.addSystemMessage('Enter an OpenRouter model name, e.g. x-ai/grok-build-0.1 or anthropic/claude-3.5-sonnet.');
27
148
  return 'handled';
28
149
  }
29
150
  if (value.startsWith('/model ')) {
30
151
  const modelName = value.slice('/model '.length).trim();
31
- const next = await updateSettings({ model: modelName });
32
- ctx.updateSettings(next);
33
- ctx.addSystemMessage(`Model set to ${modelName}.`);
152
+ await ctx.updateSettings({ model: modelName });
153
+ ctx.addSystemMessage(`Model set to ${modelName}. Saved to ~/.haze/settings.json and will be used until you set a new model.`);
34
154
  return 'handled';
35
155
  }
36
156
  if (value === '/init') {
@@ -38,6 +158,10 @@ export async function handleSlashCommand(value, ctx) {
38
158
  await ctx.refreshContextFiles();
39
159
  return 'handled';
40
160
  }
161
+ if (value === '/skill' || value.startsWith('/skill ') || value === '/skills' || value.startsWith('/skills ')) {
162
+ const normalized = value.replace(/^\/skills\b/, '/skill');
163
+ return await handleSkillCommand(normalized, ctx);
164
+ }
41
165
  if (value.startsWith('/')) {
42
166
  ctx.addSystemMessage(`Unknown command: ${value}. Bold start.`);
43
167
  return 'handled';
@@ -6,3 +6,4 @@ export declare function toolResultSummary(event: {
6
6
  error?: unknown;
7
7
  }): string;
8
8
  export declare function formatSeconds(milliseconds: number): string;
9
+ export declare function toolOutputDetails(value: unknown): string;
@@ -17,7 +17,7 @@ export function toolCallSummary(toolName, input) {
17
17
  const data = input;
18
18
  if (toolName === 'bash' && typeof data?.command === 'string') {
19
19
  const timeout = typeof data.timeoutSeconds === 'number' ? ` (timeout ${data.timeoutSeconds}s)` : '';
20
- return `$ ${data.command}${timeout}`;
20
+ return `bash $ ${data.command}${timeout}`;
21
21
  }
22
22
  if (toolName === 'listFiles' && typeof data?.path === 'string')
23
23
  return `listFiles ${data.path}`;
@@ -35,6 +35,8 @@ export function toolResultSummary(event) {
35
35
  if (!event.success)
36
36
  return `failed: ${compact(event.error)}`;
37
37
  const output = event.output;
38
+ if (output?.duplicateSkipped === true)
39
+ return 'skipped duplicate';
38
40
  if (typeof output?.code === 'number')
39
41
  return `exited with code ${output.code}`;
40
42
  if (typeof output?.ok === 'boolean')
@@ -44,3 +46,18 @@ export function toolResultSummary(event) {
44
46
  export function formatSeconds(milliseconds) {
45
47
  return `${(milliseconds / 1000).toFixed(1)}s`;
46
48
  }
49
+ export function toolOutputDetails(value) {
50
+ if (!value || typeof value !== 'object')
51
+ return '';
52
+ const output = value;
53
+ const stdout = output.stdout?.text?.trim();
54
+ const stderr = output.stderr?.text?.trim();
55
+ const parts = [
56
+ stdout ? `stdout:\n${compact(stdout, 1200)}` : '',
57
+ stderr ? `stderr:\n${compact(stderr, 1200)}` : '',
58
+ ].filter(Boolean);
59
+ if (parts.length === 0)
60
+ return '';
61
+ const truncated = output.stdout?.truncated || output.stderr?.truncated;
62
+ return `${parts.join('\n\n')}${truncated ? '\n… output truncated' : ''}`;
63
+ }
@@ -1,4 +1,4 @@
1
1
  export declare function listSkills(): Promise<void>;
2
2
  export declare function infoSkill(name: string): Promise<void>;
3
3
  export declare function removeSkill(name: string): Promise<void>;
4
- export declare function validateSkill(dir: string): Promise<void>;
4
+ export declare function validateSkill(target: string): Promise<void>;
@@ -3,19 +3,20 @@ import { render, Box, Text } from 'ink';
3
3
  import fs from 'fs-extra';
4
4
  import path from 'node:path';
5
5
  import { confirm } from '@inquirer/prompts';
6
+ import { GLOBAL_SKILLS_DIR } from '../../config/paths.js';
6
7
  import { loadSkillRegistry } from '../../skills/SkillRegistry.js';
7
8
  import { Header } from '../../ui/components/Header.js';
8
9
  import { theme } from '../../ui/theme.js';
9
10
  export async function listSkills() {
10
11
  const registry = await loadSkillRegistry();
11
- render(_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { subtitle: "Installed skills" }), [...registry.skills.values()].map(s => _jsxs(Text, { children: [_jsx(Text, { color: theme.purple, children: s.manifest.name }), " ", s.manifest.version, " \u2014 ", s.manifest.description, " (", s.source, ")"] }, s.manifest.name))] }));
12
+ render(_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { subtitle: "Installed skills" }), [...registry.skills.values()].map(s => _jsxs(Text, { children: [_jsx(Text, { color: theme.purple, children: s.name }), " \u2014 ", s.description] }, s.name))] }));
12
13
  }
13
14
  export async function infoSkill(name) {
14
15
  const registry = await loadSkillRegistry();
15
16
  const skill = registry.skills.get(name);
16
17
  if (!skill)
17
18
  throw new Error(`No skill named ${name}`);
18
- render(_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { subtitle: `Skill: ${name}` }), _jsx(Text, { children: skill.manifest.description }), _jsx(Text, { color: theme.violet, children: "Tools" }), skill.tools.map(t => _jsxs(Text, { children: [" ", t.id, ": ", t.description] }, t.id)), _jsx(Text, { color: theme.violet, children: "Path" }), _jsx(Text, { children: skill.dir })] }));
19
+ render(_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { subtitle: `Skill: ${name}` }), _jsx(Text, { children: skill.description }), _jsx(Text, { color: theme.violet, children: "References" }), skill.references.map(r => _jsxs(Text, { children: [" ", r.path] }, r.path)), _jsx(Text, { color: theme.violet, children: "Path" }), _jsx(Text, { children: skill.dir })] }));
19
20
  }
20
21
  export async function removeSkill(name) {
21
22
  const registry = await loadSkillRegistry();
@@ -28,8 +29,10 @@ export async function removeSkill(name) {
28
29
  await fs.remove(skill.dir);
29
30
  console.log(`Removed ${name}. A rare case of subtraction as progress.`);
30
31
  }
31
- export async function validateSkill(dir) {
32
+ export async function validateSkill(target) {
32
33
  const { loadSkill } = await import('../../skills/SkillLoader.js');
33
- const skill = await loadSkill(path.resolve(dir), 'local');
34
- console.log(skill ? `Valid: ${skill.manifest.name}` : 'No skill.yaml found');
34
+ const direct = path.resolve(target);
35
+ const dir = await fs.pathExists(path.join(direct, 'SKILL.md')) ? direct : path.join(GLOBAL_SKILLS_DIR, target);
36
+ const skill = await loadSkill(dir, 'global');
37
+ console.log(skill ? `Valid: ${skill.name}` : 'No SKILL.md found');
35
38
  }
@@ -5,6 +5,7 @@ export type Message = {
5
5
  role: 'system' | 'user' | 'assistant' | 'tool';
6
6
  text: string;
7
7
  streaming?: boolean;
8
+ hidden?: boolean;
8
9
  };
9
10
  export interface StreamCallbacks {
10
11
  addMessage: (msg: Message) => void;
@@ -15,5 +16,6 @@ export interface StreamCallbacks {
15
16
  getConversation: () => ModelMessage[];
16
17
  getLastAssistantText: () => string;
17
18
  setLastAssistantText: (text: string) => void;
19
+ setAbortController?: (controller: AbortController | null) => void;
18
20
  }
19
21
  export declare function runAgentTurn(value: string, displayValue: string | undefined, contextFiles: ContextFile[], callbacks: StreamCallbacks): Promise<void>;