@haystackeditor/cli 0.8.0 ā 0.8.1
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 +59 -12
- package/dist/assets/hooks/agent-context/detect.ts +136 -0
- package/dist/assets/hooks/agent-context/format.ts +99 -0
- package/dist/assets/hooks/agent-context/index.ts +39 -0
- package/dist/assets/hooks/agent-context/parsers/claude.ts +253 -0
- package/dist/assets/hooks/agent-context/parsers/gemini.ts +155 -0
- package/dist/assets/hooks/agent-context/parsers/opencode.ts +174 -0
- package/dist/assets/hooks/agent-context/tsconfig.json +13 -0
- package/dist/assets/hooks/agent-context/types.ts +58 -0
- package/dist/assets/hooks/llm-rules-template.md +35 -0
- package/dist/assets/hooks/package.json +11 -0
- package/dist/assets/hooks/scripts/commit-msg.sh +4 -0
- package/dist/assets/hooks/scripts/post-commit.sh +4 -0
- package/dist/assets/hooks/scripts/pre-commit.sh +92 -0
- package/dist/assets/hooks/scripts/pre-push.sh +5 -0
- package/dist/assets/hooks/scripts/prepare-commit-msg.sh +3 -0
- package/dist/assets/hooks/truncation-checker/ast-analyzer.ts +528 -0
- package/dist/assets/hooks/truncation-checker/index.ts +595 -0
- package/dist/assets/hooks/truncation-checker/tsconfig.json +13 -0
- package/dist/commands/config.d.ts +14 -0
- package/dist/commands/config.js +89 -0
- package/dist/commands/hooks.d.ts +17 -0
- package/dist/commands/hooks.js +269 -0
- package/dist/commands/skills.d.ts +8 -0
- package/dist/commands/skills.js +215 -0
- package/dist/index.js +86 -1
- package/dist/utils/hooks.d.ts +26 -0
- package/dist/utils/hooks.js +226 -0
- package/dist/utils/skill.d.ts +1 -1
- package/dist/utils/skill.js +401 -1
- package/package.json +2 -2
package/dist/commands/config.js
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { loadToken } from './login.js';
|
|
6
6
|
const API_BASE = 'https://haystackeditor.com/api/preferences';
|
|
7
|
+
const AGENTIC_TOOL_LABELS = {
|
|
8
|
+
'opencode': 'OpenCode (Haystack billing)',
|
|
9
|
+
'claude-code': 'Claude Code (your Claude Max subscription)',
|
|
10
|
+
'codex': 'Codex CLI (your ChatGPT subscription)',
|
|
11
|
+
};
|
|
7
12
|
async function requireAuth() {
|
|
8
13
|
const token = await loadToken();
|
|
9
14
|
if (!token) {
|
|
@@ -131,3 +136,87 @@ export async function handleSandbox(action) {
|
|
|
131
136
|
process.exit(1);
|
|
132
137
|
}
|
|
133
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Get current agentic tool setting
|
|
141
|
+
*/
|
|
142
|
+
export async function getAgenticToolStatus() {
|
|
143
|
+
const token = await requireAuth();
|
|
144
|
+
console.log(chalk.dim('\nFetching preferences...\n'));
|
|
145
|
+
try {
|
|
146
|
+
const response = await apiRequest('GET', token);
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
if (response.status === 401) {
|
|
149
|
+
console.error(chalk.red('Session expired. Run `haystack login` again.\n'));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`Failed to get preferences: ${response.status}`);
|
|
153
|
+
}
|
|
154
|
+
const data = await response.json();
|
|
155
|
+
const currentTool = data.agentic_tool || 'opencode';
|
|
156
|
+
console.log(chalk.bold('Agentic tool:'), chalk.cyan(AGENTIC_TOOL_LABELS[currentTool]));
|
|
157
|
+
console.log();
|
|
158
|
+
console.log(chalk.dim('Available options:'));
|
|
159
|
+
for (const [key, label] of Object.entries(AGENTIC_TOOL_LABELS)) {
|
|
160
|
+
const marker = key === currentTool ? chalk.green(' ā ') : ' ';
|
|
161
|
+
console.log(`${marker}${chalk.dim(key)} - ${label}`);
|
|
162
|
+
}
|
|
163
|
+
console.log();
|
|
164
|
+
console.log(chalk.dim('Change with: haystack config agentic-tool <tool>'));
|
|
165
|
+
console.log();
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Set agentic tool preference
|
|
174
|
+
*/
|
|
175
|
+
export async function setAgenticTool(tool) {
|
|
176
|
+
const token = await requireAuth();
|
|
177
|
+
console.log(chalk.dim(`\nSetting agentic tool to ${tool}...`));
|
|
178
|
+
try {
|
|
179
|
+
const response = await apiRequest('PUT', token, { agentic_tool: tool });
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
if (response.status === 401) {
|
|
182
|
+
console.error(chalk.red('Session expired. Run `haystack login` again.\n'));
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
const error = await response.json().catch(() => ({}));
|
|
186
|
+
throw new Error(error.error || `Failed to update preferences: ${response.status}`);
|
|
187
|
+
}
|
|
188
|
+
console.log(chalk.green(`\nAgentic tool set to: ${AGENTIC_TOOL_LABELS[tool]}\n`));
|
|
189
|
+
if (tool === 'claude-code') {
|
|
190
|
+
console.log(chalk.dim('Note: Requires Claude Max subscription.'));
|
|
191
|
+
console.log(chalk.dim('Connect your account in the dashboard settings.\n'));
|
|
192
|
+
}
|
|
193
|
+
else if (tool === 'codex') {
|
|
194
|
+
console.log(chalk.dim('Note: Requires ChatGPT Plus, Pro, or Team subscription.'));
|
|
195
|
+
console.log(chalk.dim('Connect your account in the dashboard settings.\n'));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Handle agentic-tool subcommand
|
|
205
|
+
*/
|
|
206
|
+
export async function handleAgenticTool(tool) {
|
|
207
|
+
const validTools = ['opencode', 'claude-code', 'codex'];
|
|
208
|
+
if (!tool || tool === 'status') {
|
|
209
|
+
return getAgenticToolStatus();
|
|
210
|
+
}
|
|
211
|
+
const normalizedTool = tool.toLowerCase();
|
|
212
|
+
if (!validTools.includes(normalizedTool)) {
|
|
213
|
+
console.error(chalk.red(`\nUnknown tool: ${tool}`));
|
|
214
|
+
console.log('\nValid options:');
|
|
215
|
+
for (const [key, label] of Object.entries(AGENTIC_TOOL_LABELS)) {
|
|
216
|
+
console.log(` ${chalk.cyan(key)} - ${label}`);
|
|
217
|
+
}
|
|
218
|
+
console.log('\nUsage: haystack config agentic-tool [opencode|claude-code|codex|status]\n');
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
return setAgenticTool(normalizedTool);
|
|
222
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* haystack hooks - Install and manage git hooks for AI agent quality checks
|
|
3
|
+
*
|
|
4
|
+
* Installs:
|
|
5
|
+
* - Entire CLI binary (session tracking, powered by https://entire.dev)
|
|
6
|
+
* - Pre-commit hook (agent detection, truncation checking, LLM rules review)
|
|
7
|
+
* - Git hooks for session logging (commit-msg, post-commit, pre-push, prepare-commit-msg)
|
|
8
|
+
*/
|
|
9
|
+
interface HooksInstallOptions {
|
|
10
|
+
version?: string;
|
|
11
|
+
force?: boolean;
|
|
12
|
+
skipEntire?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function hooksInstall(options: HooksInstallOptions): Promise<void>;
|
|
15
|
+
export declare function hooksStatus(): Promise<void>;
|
|
16
|
+
export declare function hooksUpdate(): Promise<void>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* haystack hooks - Install and manage git hooks for AI agent quality checks
|
|
3
|
+
*
|
|
4
|
+
* Installs:
|
|
5
|
+
* - Entire CLI binary (session tracking, powered by https://entire.dev)
|
|
6
|
+
* - Pre-commit hook (agent detection, truncation checking, LLM rules review)
|
|
7
|
+
* - Git hooks for session logging (commit-msg, post-commit, pre-push, prepare-commit-msg)
|
|
8
|
+
*/
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { existsSync, accessSync, constants } from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import { execSync } from 'child_process';
|
|
13
|
+
import { findGitRoot, ENTIRE_DEFAULT_VERSION, ENTIRE_BIN_PATH, getInstalledEntireVersion, getLatestEntireVersion, downloadEntireBinary, copyHookFiles, installHookDeps, copyLlmRulesTemplate, createEntireConfig, updateGitignore, } from '../utils/hooks.js';
|
|
14
|
+
export async function hooksInstall(options) {
|
|
15
|
+
console.log(chalk.cyan('\nHaystack Hooks Setup\n'));
|
|
16
|
+
// Step 1: Validate git repo
|
|
17
|
+
const gitRoot = findGitRoot();
|
|
18
|
+
if (!gitRoot) {
|
|
19
|
+
console.error(chalk.red('Not a git repository. Run this from inside a git repo.\n'));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const hooksDir = path.join(gitRoot, 'hooks');
|
|
23
|
+
// Step 2: Check for existing installation
|
|
24
|
+
if (existsSync(hooksDir) && !options.force) {
|
|
25
|
+
console.error(chalk.yellow('hooks/ directory already exists. Use --force to overwrite.\n'));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
if (existsSync(hooksDir) && options.force) {
|
|
29
|
+
console.log(chalk.yellow('Overwriting existing hooks...'));
|
|
30
|
+
}
|
|
31
|
+
// Step 3: Download Entire binary
|
|
32
|
+
if (!options.skipEntire) {
|
|
33
|
+
const version = options.version || ENTIRE_DEFAULT_VERSION;
|
|
34
|
+
const installedVersion = await getInstalledEntireVersion();
|
|
35
|
+
if (installedVersion === version) {
|
|
36
|
+
console.log(chalk.green(`ā Entire CLI v${version} already installed`));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.log(chalk.dim(`Downloading Entire CLI v${version}...`));
|
|
40
|
+
try {
|
|
41
|
+
await downloadEntireBinary(version);
|
|
42
|
+
console.log(chalk.green(`ā Entire CLI v${version} installed to ~/.haystack/bin/entire`));
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
46
|
+
console.error(chalk.red(`Failed to download Entire CLI: ${message}\n`));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log(chalk.dim('Skipping Entire CLI download (--skip-entire)'));
|
|
53
|
+
}
|
|
54
|
+
// Step 4: Copy hook files
|
|
55
|
+
try {
|
|
56
|
+
await copyHookFiles(hooksDir);
|
|
57
|
+
console.log(chalk.green('ā Hook scripts installed to hooks/'));
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
61
|
+
console.error(chalk.red(`Failed to copy hook files: ${message}\n`));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
// Step 5: Install hook dependencies
|
|
65
|
+
console.log(chalk.dim('Installing hook dependencies (tree-sitter)...'));
|
|
66
|
+
try {
|
|
67
|
+
await installHookDeps(hooksDir);
|
|
68
|
+
console.log(chalk.green('ā Hook dependencies installed'));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
console.log(chalk.yellow('ā Failed to install hook dependencies. Run `npm install` in hooks/ manually.'));
|
|
72
|
+
}
|
|
73
|
+
// Step 6: Configure git hooks path
|
|
74
|
+
try {
|
|
75
|
+
execSync('git config core.hooksPath hooks', { cwd: gitRoot, stdio: 'pipe' });
|
|
76
|
+
console.log(chalk.green('ā Git configured to use hooks/ directory'));
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
80
|
+
console.error(chalk.red(`Failed to set git hooks path: ${message}\n`));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
// Step 7: Create .entire/ configuration
|
|
84
|
+
try {
|
|
85
|
+
await createEntireConfig(gitRoot);
|
|
86
|
+
console.log(chalk.green('ā Created .entire/settings.json'));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
console.log(chalk.yellow('ā Failed to create .entire/ configuration'));
|
|
90
|
+
}
|
|
91
|
+
// Step 8: Create LLM_RULES.md if needed
|
|
92
|
+
try {
|
|
93
|
+
const created = await copyLlmRulesTemplate(gitRoot);
|
|
94
|
+
if (created) {
|
|
95
|
+
console.log(chalk.green('ā Created LLM_RULES.md (customize this for your project)'));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log(chalk.dim(' LLM_RULES.md already exists'));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
console.log(chalk.yellow('ā Failed to create LLM_RULES.md'));
|
|
103
|
+
}
|
|
104
|
+
// Step 9: Update .gitignore
|
|
105
|
+
try {
|
|
106
|
+
const updated = await updateGitignore(gitRoot);
|
|
107
|
+
if (updated) {
|
|
108
|
+
console.log(chalk.green('ā Updated .gitignore'));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
console.log(chalk.yellow('ā Failed to update .gitignore'));
|
|
113
|
+
}
|
|
114
|
+
// Summary
|
|
115
|
+
console.log(chalk.green('\nā Haystack hooks installed!\n'));
|
|
116
|
+
console.log(chalk.dim('Hooks installed:'));
|
|
117
|
+
console.log(chalk.dim(' pre-commit Agent detection, truncation check, LLM rules review'));
|
|
118
|
+
console.log(chalk.dim(' commit-msg Session tracking (powered by Entire)'));
|
|
119
|
+
console.log(chalk.dim(' post-commit Session condensing (powered by Entire)'));
|
|
120
|
+
console.log(chalk.dim(' pre-push Session log push (powered by Entire)'));
|
|
121
|
+
console.log(chalk.dim(' prepare-commit-msg Commit preparation (powered by Entire)'));
|
|
122
|
+
console.log('');
|
|
123
|
+
console.log(chalk.dim('Files to commit:'));
|
|
124
|
+
console.log(chalk.dim(' hooks/ Hook scripts and TypeScript modules'));
|
|
125
|
+
console.log(chalk.dim(' .entire/settings.json'));
|
|
126
|
+
console.log(chalk.dim(' .entire/.gitignore'));
|
|
127
|
+
console.log(chalk.dim(' LLM_RULES.md Customize this for your project'));
|
|
128
|
+
console.log('');
|
|
129
|
+
}
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// haystack hooks status
|
|
132
|
+
// ============================================================================
|
|
133
|
+
export async function hooksStatus() {
|
|
134
|
+
const gitRoot = findGitRoot();
|
|
135
|
+
if (!gitRoot) {
|
|
136
|
+
console.error(chalk.red('Not a git repository.\n'));
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
console.log(chalk.cyan('\nHaystack Hooks Status\n'));
|
|
140
|
+
const hooksDir = path.join(gitRoot, 'hooks');
|
|
141
|
+
// Check hooks directory
|
|
142
|
+
if (!existsSync(hooksDir)) {
|
|
143
|
+
console.log(chalk.red('ā hooks/ directory not found'));
|
|
144
|
+
console.log(chalk.dim(' Run: haystack hooks install\n'));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
console.log(chalk.green('ā hooks/ directory exists'));
|
|
148
|
+
// Check each hook script
|
|
149
|
+
const requiredHooks = [
|
|
150
|
+
'pre-commit',
|
|
151
|
+
'commit-msg',
|
|
152
|
+
'post-commit',
|
|
153
|
+
'pre-push',
|
|
154
|
+
'prepare-commit-msg',
|
|
155
|
+
];
|
|
156
|
+
for (const hook of requiredHooks) {
|
|
157
|
+
const hookPath = path.join(hooksDir, hook);
|
|
158
|
+
if (existsSync(hookPath)) {
|
|
159
|
+
try {
|
|
160
|
+
accessSync(hookPath, constants.X_OK);
|
|
161
|
+
console.log(chalk.green(`ā ${hook}`));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
console.log(chalk.yellow(`ā ${hook} (not executable)`));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.log(chalk.red(`ā ${hook} missing`));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Check TypeScript modules
|
|
172
|
+
const agentContextDir = path.join(hooksDir, 'agent-context');
|
|
173
|
+
const truncationDir = path.join(hooksDir, 'truncation-checker');
|
|
174
|
+
console.log(existsSync(agentContextDir)
|
|
175
|
+
? chalk.green('ā agent-context/ module')
|
|
176
|
+
: chalk.red('ā agent-context/ missing'));
|
|
177
|
+
console.log(existsSync(truncationDir)
|
|
178
|
+
? chalk.green('ā truncation-checker/ module')
|
|
179
|
+
: chalk.red('ā truncation-checker/ missing'));
|
|
180
|
+
// Check hook dependencies
|
|
181
|
+
const nodeModules = path.join(hooksDir, 'node_modules');
|
|
182
|
+
console.log(existsSync(nodeModules)
|
|
183
|
+
? chalk.green('ā Hook dependencies installed')
|
|
184
|
+
: chalk.yellow('ā Hook dependencies not installed (run npm install in hooks/)'));
|
|
185
|
+
// Check git core.hooksPath
|
|
186
|
+
try {
|
|
187
|
+
const hooksPath = execSync('git config core.hooksPath', {
|
|
188
|
+
cwd: gitRoot,
|
|
189
|
+
encoding: 'utf-8',
|
|
190
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
191
|
+
}).trim();
|
|
192
|
+
if (hooksPath === 'hooks') {
|
|
193
|
+
console.log(chalk.green('ā Git core.hooksPath = hooks'));
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
console.log(chalk.yellow(`ā Git core.hooksPath = ${hooksPath} (expected: hooks)`));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
console.log(chalk.red('ā Git core.hooksPath not set'));
|
|
201
|
+
}
|
|
202
|
+
// Check Entire binary
|
|
203
|
+
const installedVersion = await getInstalledEntireVersion();
|
|
204
|
+
if (installedVersion) {
|
|
205
|
+
console.log(chalk.green(`ā Entire CLI v${installedVersion}`));
|
|
206
|
+
}
|
|
207
|
+
else if (existsSync(ENTIRE_BIN_PATH)) {
|
|
208
|
+
console.log(chalk.yellow('ā Entire CLI binary exists but failed to run'));
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
console.log(chalk.yellow('ā Entire CLI not installed (~/.haystack/bin/entire)'));
|
|
212
|
+
}
|
|
213
|
+
// Check .entire/settings.json
|
|
214
|
+
const settingsPath = path.join(gitRoot, '.entire', 'settings.json');
|
|
215
|
+
if (existsSync(settingsPath)) {
|
|
216
|
+
console.log(chalk.green('ā .entire/settings.json'));
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
console.log(chalk.yellow('ā .entire/settings.json not found'));
|
|
220
|
+
}
|
|
221
|
+
// Check LLM_RULES.md
|
|
222
|
+
const rulesPath = path.join(gitRoot, 'LLM_RULES.md');
|
|
223
|
+
if (existsSync(rulesPath)) {
|
|
224
|
+
console.log(chalk.green('ā LLM_RULES.md'));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
console.log(chalk.yellow('ā LLM_RULES.md not found'));
|
|
228
|
+
}
|
|
229
|
+
console.log('');
|
|
230
|
+
}
|
|
231
|
+
// ============================================================================
|
|
232
|
+
// haystack hooks update
|
|
233
|
+
// ============================================================================
|
|
234
|
+
export async function hooksUpdate() {
|
|
235
|
+
console.log(chalk.cyan('\nUpdating Entire CLI...\n'));
|
|
236
|
+
// Check current version
|
|
237
|
+
const currentVersion = await getInstalledEntireVersion();
|
|
238
|
+
if (currentVersion) {
|
|
239
|
+
console.log(chalk.dim(`Current version: v${currentVersion}`));
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
console.log(chalk.dim('Entire CLI not currently installed.'));
|
|
243
|
+
}
|
|
244
|
+
// Fetch latest version
|
|
245
|
+
let latestVersion;
|
|
246
|
+
try {
|
|
247
|
+
latestVersion = await getLatestEntireVersion();
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
251
|
+
console.error(chalk.red(`Failed to check for updates: ${message}\n`));
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
console.log(chalk.dim(`Latest version: v${latestVersion}`));
|
|
255
|
+
if (currentVersion === latestVersion) {
|
|
256
|
+
console.log(chalk.green('\nā Already up to date.\n'));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// Download and install
|
|
260
|
+
try {
|
|
261
|
+
await downloadEntireBinary(latestVersion);
|
|
262
|
+
console.log(chalk.green(`\nā Updated Entire CLI to v${latestVersion}\n`));
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
266
|
+
console.error(chalk.red(`Failed to update: ${message}\n`));
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills command - Registers the haystack-verify MCP server with coding CLIs
|
|
3
|
+
* Supports: Claude Code, Codex CLI, Cursor, and manual setup
|
|
4
|
+
*/
|
|
5
|
+
export declare function installSkills(options: {
|
|
6
|
+
cli?: string;
|
|
7
|
+
}): Promise<void>;
|
|
8
|
+
export declare function listSkills(): Promise<void>;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills command - Registers the haystack-verify MCP server with coding CLIs
|
|
3
|
+
* Supports: Claude Code, Codex CLI, Cursor, and manual setup
|
|
4
|
+
*/
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
const CLI_CONFIGS = {
|
|
11
|
+
claude: {
|
|
12
|
+
name: 'claude',
|
|
13
|
+
displayName: 'Claude Code',
|
|
14
|
+
checkCommand: 'which claude',
|
|
15
|
+
installCommand: 'claude mcp add haystack-verify -- npx @haystackeditor/verify',
|
|
16
|
+
},
|
|
17
|
+
codex: {
|
|
18
|
+
name: 'codex',
|
|
19
|
+
displayName: 'Codex CLI',
|
|
20
|
+
checkCommand: 'which codex',
|
|
21
|
+
installCommand: 'codex mcp add haystack-verify -- npx @haystackeditor/verify',
|
|
22
|
+
},
|
|
23
|
+
cursor: {
|
|
24
|
+
name: 'cursor',
|
|
25
|
+
displayName: 'Cursor',
|
|
26
|
+
checkCommand: '', // Cursor is always "available" - we check config file
|
|
27
|
+
installCommand: '', // We modify config directly
|
|
28
|
+
configPath: join(homedir(), '.cursor', 'mcp.json'),
|
|
29
|
+
},
|
|
30
|
+
manual: {
|
|
31
|
+
name: 'manual',
|
|
32
|
+
displayName: 'Manual Setup',
|
|
33
|
+
checkCommand: '',
|
|
34
|
+
installCommand: '',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
function detectAvailableCLIs() {
|
|
38
|
+
const available = [];
|
|
39
|
+
// Check Claude Code
|
|
40
|
+
try {
|
|
41
|
+
execSync('which claude', { stdio: 'ignore' });
|
|
42
|
+
available.push('claude');
|
|
43
|
+
}
|
|
44
|
+
catch { }
|
|
45
|
+
// Check Codex CLI
|
|
46
|
+
try {
|
|
47
|
+
execSync('which codex', { stdio: 'ignore' });
|
|
48
|
+
available.push('codex');
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
// Check Cursor (config-based)
|
|
52
|
+
const cursorConfigDir = join(homedir(), '.cursor');
|
|
53
|
+
if (existsSync(cursorConfigDir)) {
|
|
54
|
+
available.push('cursor');
|
|
55
|
+
}
|
|
56
|
+
return available;
|
|
57
|
+
}
|
|
58
|
+
function installForClaude() {
|
|
59
|
+
const config = CLI_CONFIGS.claude;
|
|
60
|
+
console.log(chalk.gray(`Running: ${config.installCommand}\n`));
|
|
61
|
+
try {
|
|
62
|
+
execSync(config.installCommand, { stdio: 'inherit' });
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function installForCodex() {
|
|
70
|
+
const config = CLI_CONFIGS.codex;
|
|
71
|
+
console.log(chalk.gray(`Running: ${config.installCommand}\n`));
|
|
72
|
+
try {
|
|
73
|
+
execSync(config.installCommand, { stdio: 'inherit' });
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function installForCursor() {
|
|
81
|
+
const configPath = CLI_CONFIGS.cursor.configPath;
|
|
82
|
+
const configDir = join(homedir(), '.cursor');
|
|
83
|
+
console.log(chalk.gray(`Updating: ${configPath}\n`));
|
|
84
|
+
try {
|
|
85
|
+
// Ensure directory exists
|
|
86
|
+
if (!existsSync(configDir)) {
|
|
87
|
+
mkdirSync(configDir, { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
// Read or create config
|
|
90
|
+
let config = {};
|
|
91
|
+
if (existsSync(configPath)) {
|
|
92
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
93
|
+
config = JSON.parse(content);
|
|
94
|
+
}
|
|
95
|
+
// Add haystack-verify server
|
|
96
|
+
config.mcpServers = config.mcpServers || {};
|
|
97
|
+
config.mcpServers['haystack-verify'] = {
|
|
98
|
+
command: 'npx',
|
|
99
|
+
args: ['@haystackeditor/verify'],
|
|
100
|
+
};
|
|
101
|
+
// Write config
|
|
102
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error(chalk.red(`Failed to update Cursor config: ${error}`));
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function showManualInstructions() {
|
|
111
|
+
console.log(chalk.white('\nš Manual Setup Instructions\n'));
|
|
112
|
+
console.log(chalk.cyan('Claude Code:'));
|
|
113
|
+
console.log(chalk.gray(' claude mcp add haystack-verify -- npx @haystackeditor/verify\n'));
|
|
114
|
+
console.log(chalk.cyan('Codex CLI:'));
|
|
115
|
+
console.log(chalk.gray(' codex mcp add haystack-verify -- npx @haystackeditor/verify\n'));
|
|
116
|
+
console.log(chalk.cyan('Cursor:'));
|
|
117
|
+
console.log(chalk.gray(' Add to ~/.cursor/mcp.json:'));
|
|
118
|
+
console.log(chalk.gray(` {
|
|
119
|
+
"mcpServers": {
|
|
120
|
+
"haystack-verify": {
|
|
121
|
+
"command": "npx",
|
|
122
|
+
"args": ["@haystackeditor/verify"]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}\n`));
|
|
126
|
+
console.log(chalk.cyan('VS Code + Continue:'));
|
|
127
|
+
console.log(chalk.gray(' Add to .continue/config.json or settings\n'));
|
|
128
|
+
}
|
|
129
|
+
export async function installSkills(options) {
|
|
130
|
+
console.log(chalk.cyan('\nš¦ Installing Haystack skills...\n'));
|
|
131
|
+
// If CLI specified, use it directly
|
|
132
|
+
if (options.cli) {
|
|
133
|
+
const cli = options.cli.toLowerCase();
|
|
134
|
+
if (cli === 'manual') {
|
|
135
|
+
showManualInstructions();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (!['claude', 'codex', 'cursor'].includes(cli)) {
|
|
139
|
+
console.log(chalk.red(`Unknown CLI: ${options.cli}`));
|
|
140
|
+
console.log(chalk.gray('Supported: claude, codex, cursor, manual'));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
const success = await installForCLI(cli);
|
|
144
|
+
if (success) {
|
|
145
|
+
showSuccessMessage(cli);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// Auto-detect available CLIs
|
|
150
|
+
const available = detectAvailableCLIs();
|
|
151
|
+
if (available.length === 0) {
|
|
152
|
+
console.log(chalk.yellow('No supported coding CLI detected.\n'));
|
|
153
|
+
showManualInstructions();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (available.length === 1) {
|
|
157
|
+
// Only one CLI available, use it
|
|
158
|
+
const cli = available[0];
|
|
159
|
+
console.log(chalk.gray(`Detected: ${CLI_CONFIGS[cli].displayName}\n`));
|
|
160
|
+
const success = await installForCLI(cli);
|
|
161
|
+
if (success) {
|
|
162
|
+
showSuccessMessage(cli);
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Multiple CLIs available - install for all
|
|
167
|
+
console.log(chalk.gray(`Detected: ${available.map(c => CLI_CONFIGS[c].displayName).join(', ')}\n`));
|
|
168
|
+
for (const cli of available) {
|
|
169
|
+
console.log(chalk.white(`\nInstalling for ${CLI_CONFIGS[cli].displayName}...`));
|
|
170
|
+
await installForCLI(cli);
|
|
171
|
+
}
|
|
172
|
+
console.log(chalk.green('\nā
Haystack skills installed!\n'));
|
|
173
|
+
console.log(chalk.white('Run one of these in your coding CLI:'));
|
|
174
|
+
console.log(chalk.cyan(' /setup-haystack'));
|
|
175
|
+
console.log();
|
|
176
|
+
}
|
|
177
|
+
async function installForCLI(cli) {
|
|
178
|
+
switch (cli) {
|
|
179
|
+
case 'claude':
|
|
180
|
+
return installForClaude();
|
|
181
|
+
case 'codex':
|
|
182
|
+
return installForCodex();
|
|
183
|
+
case 'cursor':
|
|
184
|
+
return installForCursor();
|
|
185
|
+
default:
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function showSuccessMessage(cli) {
|
|
190
|
+
const config = CLI_CONFIGS[cli];
|
|
191
|
+
console.log(chalk.green('\nā
Haystack skills installed!\n'));
|
|
192
|
+
console.log(chalk.white('Available skills:'));
|
|
193
|
+
console.log(chalk.cyan(' /setup-haystack ') + chalk.gray('- Create .haystack.json with AI assistance'));
|
|
194
|
+
console.log(chalk.cyan(' /prepare-haystack ') + chalk.gray('- Add aria-labels and data-testid attributes'));
|
|
195
|
+
console.log(chalk.cyan(' /setup-haystack-secrets ') + chalk.gray('- Configure API keys and secrets'));
|
|
196
|
+
console.log();
|
|
197
|
+
console.log(chalk.white(`Open ${config.displayName} and run:`));
|
|
198
|
+
console.log(chalk.cyan(' /setup-haystack'));
|
|
199
|
+
console.log();
|
|
200
|
+
}
|
|
201
|
+
export async function listSkills() {
|
|
202
|
+
console.log(chalk.white('\nAvailable Haystack skills:\n'));
|
|
203
|
+
console.log(chalk.cyan(' /setup-haystack ') + chalk.gray('- Master setup - creates .haystack.json'));
|
|
204
|
+
console.log(chalk.cyan(' /prepare-haystack ') + chalk.gray('- Add accessibility attributes'));
|
|
205
|
+
console.log(chalk.cyan(' /setup-haystack-secrets ') + chalk.gray('- Configure secrets for sandboxes'));
|
|
206
|
+
console.log();
|
|
207
|
+
const available = detectAvailableCLIs();
|
|
208
|
+
if (available.length > 0) {
|
|
209
|
+
console.log(chalk.gray('Detected CLIs: ') + chalk.white(available.map(c => CLI_CONFIGS[c].displayName).join(', ')));
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
console.log(chalk.gray('No coding CLI detected. Run: ') + chalk.white('haystack skills install --cli manual'));
|
|
213
|
+
}
|
|
214
|
+
console.log();
|
|
215
|
+
}
|