@hive-org/cli 0.0.6 → 0.0.7
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/dist/agent/analysis.js +78 -0
- package/dist/agent/app.js +32 -0
- package/dist/agent/chat-prompt.js +63 -0
- package/dist/agent/components/AsciiTicker.js +81 -0
- package/dist/agent/components/HoneycombBoot.js +270 -0
- package/dist/agent/components/Spinner.js +37 -0
- package/dist/agent/config.js +52 -0
- package/dist/agent/edit-section.js +59 -0
- package/dist/agent/fetch-rules.js +21 -0
- package/dist/agent/helpers.js +22 -0
- package/dist/agent/hooks/useAgent.js +269 -0
- package/{templates/memory-prompt.ts → dist/agent/memory-prompt.js} +17 -32
- package/dist/agent/model.js +63 -0
- package/dist/agent/objects.js +1 -0
- package/dist/agent/process-lifecycle.js +56 -0
- package/{templates/prompt.ts → dist/agent/prompt.js} +18 -47
- package/dist/agent/theme.js +37 -0
- package/dist/agent/types.js +1 -0
- package/dist/agents.js +30 -21
- package/dist/ai-providers.js +0 -13
- package/dist/create/generate.js +10 -120
- package/dist/index.js +27 -4
- package/dist/migrate-templates/MigrateApp.js +131 -0
- package/dist/migrate-templates/migrate.js +86 -0
- package/dist/start/AgentProcessManager.js +131 -0
- package/dist/start/Dashboard.js +88 -0
- package/dist/start/patch-headless.js +101 -0
- package/dist/start/patch-managed-mode.js +142 -0
- package/dist/start/start-command.js +22 -0
- package/package.json +6 -5
- package/templates/analysis.ts +0 -103
- package/templates/chat-prompt.ts +0 -94
- package/templates/components/AsciiTicker.tsx +0 -113
- package/templates/components/HoneycombBoot.tsx +0 -348
- package/templates/components/Spinner.tsx +0 -64
- package/templates/edit-section.ts +0 -64
- package/templates/fetch-rules.ts +0 -23
- package/templates/helpers.ts +0 -22
- package/templates/hive/agent.ts +0 -2
- package/templates/hive/config.ts +0 -96
- package/templates/hive/memory.ts +0 -1
- package/templates/hive/objects.ts +0 -26
- package/templates/hooks/useAgent.ts +0 -337
- package/templates/index.tsx +0 -257
- package/templates/process-lifecycle.ts +0 -66
- package/templates/theme.ts +0 -40
- package/templates/types.ts +0 -23
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const colors = {
|
|
2
|
+
honey: '#F5A623',
|
|
3
|
+
honeyDark: '#D4891A',
|
|
4
|
+
white: '#FFFFFF',
|
|
5
|
+
gray: '#888888',
|
|
6
|
+
grayDim: '#555555',
|
|
7
|
+
green: '#4CAF50',
|
|
8
|
+
red: '#F44336',
|
|
9
|
+
cyan: '#00BCD4',
|
|
10
|
+
};
|
|
11
|
+
export const symbols = {
|
|
12
|
+
hive: '\u2B21', // ⬡
|
|
13
|
+
diamond: '\u25C6', // ◆
|
|
14
|
+
diamondOpen: '\u25C7', // ◇
|
|
15
|
+
dot: '\u25CF', // ●
|
|
16
|
+
check: '\u2713', // ✓
|
|
17
|
+
cross: '\u2717', // ✗
|
|
18
|
+
arrow: '\u203A', // ›
|
|
19
|
+
circle: '\u25CB', // ○
|
|
20
|
+
};
|
|
21
|
+
export const border = {
|
|
22
|
+
horizontal: '\u2500', // ─
|
|
23
|
+
vertical: '\u2502', // │
|
|
24
|
+
topLeft: '\u250C', // ┌
|
|
25
|
+
topRight: '\u2510', // ┐
|
|
26
|
+
bottomLeft: '\u2514', // └
|
|
27
|
+
bottomRight: '\u2518', // ┘
|
|
28
|
+
teeLeft: '\u251C', // ├
|
|
29
|
+
teeRight: '\u2524', // ┤
|
|
30
|
+
};
|
|
31
|
+
export const animation = {
|
|
32
|
+
DATA_CHARS: '01\u25AA\u25AB\u2591\u2592',
|
|
33
|
+
HEX_CHARS: '\u2B21\u2B22',
|
|
34
|
+
TICK_MS: 120,
|
|
35
|
+
HEX_W: 8,
|
|
36
|
+
HEX_H: 4,
|
|
37
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/agents.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
+
import { AI_PROVIDERS } from './ai-providers.js';
|
|
4
5
|
export async function scanAgents() {
|
|
5
6
|
const agentsDir = path.join(os.homedir(), '.hive', 'agents');
|
|
6
7
|
const exists = await fs.pathExists(agentsDir);
|
|
@@ -18,29 +19,37 @@ export async function scanAgents() {
|
|
|
18
19
|
if (!soulExists) {
|
|
19
20
|
continue;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
const agentDir = path.join(agentsDir, entry.name);
|
|
23
|
+
const provider = await detectProvider(agentDir);
|
|
24
|
+
const stat = await fs.stat(soulPath);
|
|
25
|
+
agents.push({ name: entry.name, dir: agentDir, provider, created: stat.birthtime });
|
|
26
|
+
}
|
|
27
|
+
return agents;
|
|
28
|
+
}
|
|
29
|
+
async function detectProvider(agentDir) {
|
|
30
|
+
// Try old-style detection: check package.json dependencies
|
|
31
|
+
const pkgPath = path.join(agentDir, 'package.json');
|
|
32
|
+
const pkgExists = await fs.pathExists(pkgPath);
|
|
33
|
+
if (pkgExists) {
|
|
34
|
+
const pkg = await fs.readJson(pkgPath);
|
|
35
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
36
|
+
for (const provider of AI_PROVIDERS) {
|
|
37
|
+
if (deps[provider.package]) {
|
|
38
|
+
return provider.label;
|
|
37
39
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// New-style detection: check .env for provider API keys
|
|
43
|
+
const envPath = path.join(agentDir, '.env');
|
|
44
|
+
const envExists = await fs.pathExists(envPath);
|
|
45
|
+
if (envExists) {
|
|
46
|
+
const envContent = await fs.readFile(envPath, 'utf-8');
|
|
47
|
+
for (const provider of AI_PROVIDERS) {
|
|
48
|
+
const pattern = new RegExp(`^${provider.envVar}=.+`, 'm');
|
|
49
|
+
if (pattern.test(envContent)) {
|
|
50
|
+
return provider.label;
|
|
40
51
|
}
|
|
41
52
|
}
|
|
42
|
-
const stat = await fs.stat(soulPath);
|
|
43
|
-
agents.push({ name: entry.name, provider, created: stat.birthtime });
|
|
44
53
|
}
|
|
45
|
-
return
|
|
54
|
+
return 'unknown';
|
|
46
55
|
}
|
package/dist/ai-providers.js
CHANGED
|
@@ -47,16 +47,3 @@ export function getProvider(id) {
|
|
|
47
47
|
}
|
|
48
48
|
return provider;
|
|
49
49
|
}
|
|
50
|
-
/** Regex: default provider import line (and trailing newline). */
|
|
51
|
-
const DEFAULT_IMPORT_LINE_REGEX = /import\s*\{\s*openai\s*\}\s*from\s*['"]@ai-sdk\/openai['"]\s*;\s*\n/;
|
|
52
|
-
/** Regex: default model call in generateText() — global to catch all occurrences. */
|
|
53
|
-
const DEFAULT_MODEL_CALL_REGEX = /openai\s*\(\s*['"]gpt-4o['"]\s*\)/g;
|
|
54
|
-
/**
|
|
55
|
-
* Replace the default (OpenAI) provider and model call in the example template with the chosen provider.
|
|
56
|
-
*/
|
|
57
|
-
export function applyAdapterToChatTemplate(template, provider) {
|
|
58
|
-
const importReplacement = provider.importLine + '\n';
|
|
59
|
-
let out = template.replace(DEFAULT_IMPORT_LINE_REGEX, importReplacement);
|
|
60
|
-
out = out.replaceAll(DEFAULT_MODEL_CALL_REGEX, provider.modelCall);
|
|
61
|
-
return out;
|
|
62
|
-
}
|
package/dist/create/generate.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
-
import { exec } from 'child_process';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
7
|
-
import { applyAdapterToChatTemplate } from '../ai-providers.js';
|
|
8
|
-
const execAsync = promisify(exec);
|
|
9
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
4
|
export async function scaffoldProject(projectName, provider, apiKey, soulContent, strategyContent, callbacks) {
|
|
11
5
|
// Validate project name to prevent path traversal / command injection
|
|
12
6
|
if (!/^[a-zA-Z0-9_-]+$/.test(projectName)) {
|
|
@@ -29,17 +23,7 @@ export async function scaffoldProject(projectName, provider, apiKey, soulContent
|
|
|
29
23
|
// 1. Create directory
|
|
30
24
|
callbacks.onStep('Creating project directory');
|
|
31
25
|
await fs.ensureDir(projectDir);
|
|
32
|
-
// 2.
|
|
33
|
-
callbacks.onStep('Initializing project');
|
|
34
|
-
await execAsync('npm init -y', { cwd: projectDir });
|
|
35
|
-
// 4. Install hive bootstrap
|
|
36
|
-
callbacks.onStep('Installing Hive SDK');
|
|
37
|
-
const templatesDir = path.resolve(__dirname, '../../templates');
|
|
38
|
-
const hiveTemplatesDir = path.join(templatesDir, 'hive');
|
|
39
|
-
const hiveDir = path.join(projectDir, 'hive');
|
|
40
|
-
await fs.ensureDir(hiveDir);
|
|
41
|
-
await fs.copy(hiveTemplatesDir, hiveDir);
|
|
42
|
-
// 5. Write SOUL.md and STRATEGY.md
|
|
26
|
+
// 2. Write SOUL.md and STRATEGY.md
|
|
43
27
|
callbacks.onStep('Writing personality files');
|
|
44
28
|
await fs.writeFile(path.join(projectDir, 'SOUL.md'), soulContent, 'utf-8');
|
|
45
29
|
await fs.writeFile(path.join(projectDir, 'STRATEGY.md'), strategyContent, 'utf-8');
|
|
@@ -58,116 +42,22 @@ export async function scaffoldProject(projectName, provider, apiKey, soulContent
|
|
|
58
42
|
(none yet)
|
|
59
43
|
`;
|
|
60
44
|
await fs.writeFile(path.join(projectDir, 'MEMORY.md'), seedMemory, 'utf-8');
|
|
61
|
-
//
|
|
45
|
+
// 3. Write .env
|
|
62
46
|
callbacks.onStep('Writing environment file');
|
|
63
47
|
const envContent = `HIVE_API_URL="https://hive-backend.z3n.dev"
|
|
64
48
|
${provider.envVar}="${apiKey}"
|
|
65
49
|
`;
|
|
66
50
|
await fs.writeFile(path.join(projectDir, '.env'), envContent, { encoding: 'utf-8', mode: 0o600 });
|
|
67
|
-
//
|
|
68
|
-
callbacks.onStep('Creating agent files');
|
|
69
|
-
const promptPath = path.join(templatesDir, 'prompt.ts');
|
|
70
|
-
const promptExists = await fs.pathExists(promptPath);
|
|
71
|
-
if (promptExists) {
|
|
72
|
-
const promptFileContent = await fs.readFile(promptPath, 'utf-8');
|
|
73
|
-
await fs.writeFile(path.join(projectDir, 'prompt.ts'), promptFileContent, 'utf-8');
|
|
74
|
-
}
|
|
75
|
-
// 8. Copy and adapt index.tsx (Ink TUI agent — no model calls, just UI shell)
|
|
76
|
-
const tuiTemplatePath = path.join(templatesDir, 'index.tsx');
|
|
77
|
-
const tuiTemplate = await fs.readFile(tuiTemplatePath, 'utf-8');
|
|
78
|
-
await fs.writeFile(path.join(projectDir, 'index.tsx'), tuiTemplate, 'utf-8');
|
|
79
|
-
// Copy and adapt analysis.ts (contains model calls)
|
|
80
|
-
const analysisTemplate = await fs.readFile(path.join(templatesDir, 'analysis.ts'), 'utf-8');
|
|
81
|
-
const analysisContent = applyAdapterToChatTemplate(analysisTemplate, provider);
|
|
82
|
-
await fs.writeFile(path.join(projectDir, 'analysis.ts'), analysisContent, 'utf-8');
|
|
83
|
-
// Copy process-lifecycle.ts
|
|
84
|
-
const lifecycleContent = await fs.readFile(path.join(templatesDir, 'process-lifecycle.ts'), 'utf-8');
|
|
85
|
-
await fs.writeFile(path.join(projectDir, 'process-lifecycle.ts'), lifecycleContent, 'utf-8');
|
|
86
|
-
// Copy hooks/ directory and adapt useAgent.ts (contains model calls)
|
|
87
|
-
const hooksTemplatesDir = path.join(templatesDir, 'hooks');
|
|
88
|
-
const hooksDir = path.join(projectDir, 'hooks');
|
|
89
|
-
await fs.ensureDir(hooksDir);
|
|
90
|
-
const useAgentTemplate = await fs.readFile(path.join(hooksTemplatesDir, 'useAgent.ts'), 'utf-8');
|
|
91
|
-
const useAgentContent = applyAdapterToChatTemplate(useAgentTemplate, provider);
|
|
92
|
-
await fs.writeFile(path.join(hooksDir, 'useAgent.ts'), useAgentContent, 'utf-8');
|
|
93
|
-
// Copy theme.ts, types.ts, helpers.ts
|
|
94
|
-
const themeContent = await fs.readFile(path.join(templatesDir, 'theme.ts'), 'utf-8');
|
|
95
|
-
await fs.writeFile(path.join(projectDir, 'theme.ts'), themeContent, 'utf-8');
|
|
96
|
-
const typesContent = await fs.readFile(path.join(templatesDir, 'types.ts'), 'utf-8');
|
|
97
|
-
await fs.writeFile(path.join(projectDir, 'types.ts'), typesContent, 'utf-8');
|
|
98
|
-
const helpersContent = await fs.readFile(path.join(templatesDir, 'helpers.ts'), 'utf-8');
|
|
99
|
-
await fs.writeFile(path.join(projectDir, 'helpers.ts'), helpersContent, 'utf-8');
|
|
100
|
-
// Copy components/ directory
|
|
101
|
-
const componentsTemplatesDir = path.join(templatesDir, 'components');
|
|
102
|
-
const componentsDir = path.join(projectDir, 'components');
|
|
103
|
-
await fs.ensureDir(componentsDir);
|
|
104
|
-
await fs.copy(componentsTemplatesDir, componentsDir);
|
|
105
|
-
// Copy chat-prompt.ts
|
|
106
|
-
const chatPromptPath = path.join(templatesDir, 'chat-prompt.ts');
|
|
107
|
-
const chatPromptContent = await fs.readFile(chatPromptPath, 'utf-8');
|
|
108
|
-
await fs.writeFile(path.join(projectDir, 'chat-prompt.ts'), chatPromptContent, 'utf-8');
|
|
109
|
-
// Copy memory-prompt.ts
|
|
110
|
-
const memoryPromptPath = path.join(templatesDir, 'memory-prompt.ts');
|
|
111
|
-
const memoryPromptContent = await fs.readFile(memoryPromptPath, 'utf-8');
|
|
112
|
-
await fs.writeFile(path.join(projectDir, 'memory-prompt.ts'), memoryPromptContent, 'utf-8');
|
|
113
|
-
// Copy edit-section.ts
|
|
114
|
-
const editSectionPath = path.join(templatesDir, 'edit-section.ts');
|
|
115
|
-
const editSectionContent = await fs.readFile(editSectionPath, 'utf-8');
|
|
116
|
-
await fs.writeFile(path.join(projectDir, 'edit-section.ts'), editSectionContent, 'utf-8');
|
|
117
|
-
// Copy fetch-rules.ts
|
|
118
|
-
const fetchRulesPath = path.join(templatesDir, 'fetch-rules.ts');
|
|
119
|
-
const fetchRulesContent = await fs.readFile(fetchRulesPath, 'utf-8');
|
|
120
|
-
await fs.writeFile(path.join(projectDir, 'fetch-rules.ts'), fetchRulesContent, 'utf-8');
|
|
121
|
-
// 9. Configure package.json
|
|
51
|
+
// 4. Write minimal package.json — no deps needed, npx fetches @hive-org/agent@latest on every run
|
|
122
52
|
callbacks.onStep('Configuring project');
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
};
|
|
130
|
-
packageJson.dependencies = {
|
|
131
|
-
...packageJson.dependencies,
|
|
132
|
-
'@hive-org/sdk': 'latest',
|
|
133
|
-
dotenv: '^16.0.0',
|
|
134
|
-
ai: '^6.0.71',
|
|
135
|
-
chalk: '^5.4.1',
|
|
136
|
-
zod: '^4.0.0',
|
|
137
|
-
[provider.package]: '^3.0.0',
|
|
138
|
-
ink: '^5.1.0',
|
|
139
|
-
'ink-text-input': '^6.0.0',
|
|
140
|
-
react: '^18.3.1',
|
|
141
|
-
};
|
|
142
|
-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
143
|
-
// 10. Write tsconfig.json
|
|
144
|
-
const tsConfig = {
|
|
145
|
-
compilerOptions: {
|
|
146
|
-
target: 'ES2020',
|
|
147
|
-
module: 'ESNext',
|
|
148
|
-
moduleResolution: 'bundler',
|
|
149
|
-
jsx: 'react-jsx',
|
|
150
|
-
outDir: './dist',
|
|
151
|
-
rootDir: '.',
|
|
152
|
-
strict: true,
|
|
153
|
-
esModuleInterop: true,
|
|
154
|
-
skipLibCheck: true,
|
|
155
|
-
baseUrl: '.',
|
|
156
|
-
paths: {
|
|
157
|
-
'@/*': ['./*'],
|
|
158
|
-
},
|
|
53
|
+
const packageJson = {
|
|
54
|
+
name: `hive-agent-${projectName}`,
|
|
55
|
+
private: true,
|
|
56
|
+
type: 'module',
|
|
57
|
+
scripts: {
|
|
58
|
+
start: 'npx @hive-org/cli@latest run',
|
|
159
59
|
},
|
|
160
60
|
};
|
|
161
|
-
await fs.writeJson(path.join(projectDir, '
|
|
162
|
-
// 11. Install dependencies
|
|
163
|
-
callbacks.onStep('Installing dependencies');
|
|
164
|
-
const adapterInstall = `ai ${provider.package}`;
|
|
165
|
-
try {
|
|
166
|
-
await execAsync(`npm install --save-dev typescript @types/node @types/react tsx && npm install ${adapterInstall}`, { cwd: projectDir });
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
callbacks.onError(`Failed to install dependencies. Run manually:\n cd ${normalizedProjectDir}\n npm install --save-dev typescript @types/node @types/react tsx && npm install ${adapterInstall}`);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
61
|
+
await fs.writeJson(path.join(projectDir, 'package.json'), packageJson, { spaces: 2 });
|
|
172
62
|
callbacks.onDone(normalizedProjectDir);
|
|
173
63
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { render } from 'ink';
|
|
|
4
4
|
import React from 'react';
|
|
5
5
|
import { CreateApp } from './create/CreateApp.js';
|
|
6
6
|
import { ListApp } from './list/ListApp.js';
|
|
7
|
-
import {
|
|
7
|
+
import { startCommand } from './start/start-command.js';
|
|
8
8
|
import { showWelcome } from './create/welcome.js';
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
10
10
|
const pkg = require('../package.json');
|
|
@@ -14,6 +14,8 @@ Usage:
|
|
|
14
14
|
npx @hive-org/cli@latest create [agent-name] Scaffold a new Hive agent
|
|
15
15
|
npx @hive-org/cli@latest list List existing agents
|
|
16
16
|
npx @hive-org/cli@latest start Start all agents
|
|
17
|
+
npx @hive-org/cli@latest run Run agent in current directory
|
|
18
|
+
npx @hive-org/cli@latest migrate-templates Migrate old-style agents
|
|
17
19
|
npx @hive-org/cli@latest --help Show this help message
|
|
18
20
|
npx @hive-org/cli@latest --version Print version
|
|
19
21
|
|
|
@@ -21,7 +23,8 @@ Examples:
|
|
|
21
23
|
npx @hive-org/cli@latest create alpha Creates ~/.hive/agents/alpha/
|
|
22
24
|
npx @hive-org/cli@latest create Interactive setup
|
|
23
25
|
npx @hive-org/cli@latest list Show all agents
|
|
24
|
-
npx @hive-org/cli@latest start Launch all agents as child processes
|
|
26
|
+
npx @hive-org/cli@latest start Launch all agents as child processes
|
|
27
|
+
npx @hive-org/cli@latest run Run agent from cwd (reads SOUL.md, .env)`;
|
|
25
28
|
const command = process.argv[2];
|
|
26
29
|
const arg = process.argv[3];
|
|
27
30
|
if (command === '--version' || command === '-v') {
|
|
@@ -37,8 +40,7 @@ if (command === 'list') {
|
|
|
37
40
|
await waitUntilExit();
|
|
38
41
|
}
|
|
39
42
|
else if (command === 'start') {
|
|
40
|
-
|
|
41
|
-
await waitUntilExit();
|
|
43
|
+
await startCommand();
|
|
42
44
|
}
|
|
43
45
|
else if (command === 'create') {
|
|
44
46
|
const projectName = arg;
|
|
@@ -46,6 +48,27 @@ else if (command === 'create') {
|
|
|
46
48
|
const { waitUntilExit } = render(React.createElement(CreateApp, { initialName: projectName }));
|
|
47
49
|
await waitUntilExit();
|
|
48
50
|
}
|
|
51
|
+
else if (command === 'migrate-templates') {
|
|
52
|
+
await showWelcome();
|
|
53
|
+
const { MigrateApp } = await import('./migrate-templates/MigrateApp.js');
|
|
54
|
+
const { waitUntilExit } = render(React.createElement(MigrateApp));
|
|
55
|
+
await waitUntilExit();
|
|
56
|
+
}
|
|
57
|
+
else if (command === 'run') {
|
|
58
|
+
const fs = await import('fs');
|
|
59
|
+
const path = await import('path');
|
|
60
|
+
const soulPath = path.join(process.cwd(), 'SOUL.md');
|
|
61
|
+
if (!fs.existsSync(soulPath)) {
|
|
62
|
+
console.error('No SOUL.md found in current directory. Run this command from an agent directory.');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
await import('dotenv/config');
|
|
66
|
+
const { setupProcessLifecycle } = await import('./agent/process-lifecycle.js');
|
|
67
|
+
const { App } = await import('./agent/app.js');
|
|
68
|
+
setupProcessLifecycle();
|
|
69
|
+
const { waitUntilExit } = render(React.createElement(App));
|
|
70
|
+
await waitUntilExit();
|
|
71
|
+
}
|
|
49
72
|
else {
|
|
50
73
|
console.error(`Unknown command: ${command}\n`);
|
|
51
74
|
console.log(HELP_TEXT);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
3
|
+
import { Box, Text, useInput, useApp } from 'ink';
|
|
4
|
+
import { scanAgents } from '../agents.js';
|
|
5
|
+
import { colors, symbols, styled, border } from '../theme.js';
|
|
6
|
+
import { isOldStyleAgent, migrateAgent } from './migrate.js';
|
|
7
|
+
export function MigrateApp() {
|
|
8
|
+
const { exit } = useApp();
|
|
9
|
+
const [phase, setPhase] = useState('scanning');
|
|
10
|
+
const [agents, setAgents] = useState([]);
|
|
11
|
+
const [cursor, setCursor] = useState(0);
|
|
12
|
+
const [results, setResults] = useState([]);
|
|
13
|
+
const [currentStep, setCurrentStep] = useState('');
|
|
14
|
+
const [currentAgent, setCurrentAgent] = useState('');
|
|
15
|
+
// ─── Scan phase ────────────────────────────────────
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const scan = async () => {
|
|
18
|
+
const discovered = await scanAgents();
|
|
19
|
+
if (discovered.length === 0) {
|
|
20
|
+
setPhase('done');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const selectableAgents = discovered.map((info) => {
|
|
24
|
+
const oldStyle = isOldStyleAgent(info.dir);
|
|
25
|
+
return {
|
|
26
|
+
info,
|
|
27
|
+
selected: oldStyle,
|
|
28
|
+
isOldStyle: oldStyle,
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
const hasOldStyle = selectableAgents.some((a) => a.isOldStyle);
|
|
32
|
+
if (!hasOldStyle) {
|
|
33
|
+
setResults(selectableAgents.map((a) => ({
|
|
34
|
+
name: a.info.name,
|
|
35
|
+
success: true,
|
|
36
|
+
error: 'Already migrated',
|
|
37
|
+
})));
|
|
38
|
+
setPhase('done');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
setAgents(selectableAgents);
|
|
42
|
+
setPhase('selecting');
|
|
43
|
+
};
|
|
44
|
+
scan().catch((err) => {
|
|
45
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
46
|
+
setResults([{ name: 'scan', success: false, error: message }]);
|
|
47
|
+
setPhase('done');
|
|
48
|
+
});
|
|
49
|
+
}, []);
|
|
50
|
+
// ─── Keyboard input (selecting phase) ──────────────
|
|
51
|
+
useInput((input, key) => {
|
|
52
|
+
if (phase !== 'selecting')
|
|
53
|
+
return;
|
|
54
|
+
const oldStyleAgents = agents.filter((a) => a.isOldStyle);
|
|
55
|
+
if (key.upArrow) {
|
|
56
|
+
setCursor((prev) => Math.max(0, prev - 1));
|
|
57
|
+
}
|
|
58
|
+
else if (key.downArrow) {
|
|
59
|
+
setCursor((prev) => Math.min(oldStyleAgents.length - 1, prev + 1));
|
|
60
|
+
}
|
|
61
|
+
else if (input === ' ') {
|
|
62
|
+
// Toggle selection
|
|
63
|
+
const targetName = oldStyleAgents[cursor]?.info.name;
|
|
64
|
+
if (targetName) {
|
|
65
|
+
setAgents((prev) => prev.map((a) => a.info.name === targetName ? { ...a, selected: !a.selected } : a));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (key.return) {
|
|
69
|
+
const selected = agents.filter((a) => a.selected && a.isOldStyle);
|
|
70
|
+
if (selected.length > 0) {
|
|
71
|
+
setPhase('migrating');
|
|
72
|
+
runMigrations(selected);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (input === 'q' || key.escape) {
|
|
76
|
+
exit();
|
|
77
|
+
}
|
|
78
|
+
}, { isActive: phase === 'selecting' });
|
|
79
|
+
// ─── Migrate phase ─────────────────────────────────
|
|
80
|
+
const runMigrations = useCallback(async (selected) => {
|
|
81
|
+
const migrateResults = [];
|
|
82
|
+
for (const agent of selected) {
|
|
83
|
+
setCurrentAgent(agent.info.name);
|
|
84
|
+
setCurrentStep('Starting migration');
|
|
85
|
+
const result = await migrateAgent(agent.info.dir, agent.info.name, (step) => {
|
|
86
|
+
setCurrentStep(step);
|
|
87
|
+
});
|
|
88
|
+
migrateResults.push(result);
|
|
89
|
+
setResults([...migrateResults]);
|
|
90
|
+
}
|
|
91
|
+
setCurrentAgent('');
|
|
92
|
+
setCurrentStep('');
|
|
93
|
+
setPhase('done');
|
|
94
|
+
}, []);
|
|
95
|
+
// ─── Done phase — exit after a short delay ─────────
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (phase === 'done') {
|
|
98
|
+
const timer = setTimeout(() => {
|
|
99
|
+
exit();
|
|
100
|
+
}, 1500);
|
|
101
|
+
return () => {
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}, [phase, exit]);
|
|
106
|
+
// ─── Render ────────────────────────────────────────
|
|
107
|
+
const termWidth = process.stdout.columns || 60;
|
|
108
|
+
if (phase === 'scanning') {
|
|
109
|
+
return (_jsx(Box, { flexDirection: "column", paddingLeft: 1, children: _jsxs(Text, { color: colors.honey, children: [symbols.hive, " Scanning agents..."] }) }));
|
|
110
|
+
}
|
|
111
|
+
if (phase === 'selecting') {
|
|
112
|
+
const oldStyleAgents = agents.filter((a) => a.isOldStyle);
|
|
113
|
+
const newStyleAgents = agents.filter((a) => !a.isOldStyle);
|
|
114
|
+
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsxs(Text, { color: colors.honey, bold: true, children: [symbols.hive, " Migrate agents to @hive-org/cli"] }), _jsx(Text, { color: "gray", children: border.horizontal.repeat(termWidth - 4) }), _jsxs(Text, { color: "gray", children: ["Use ", styled.white('↑↓'), " to navigate, ", styled.white('space'), " to toggle, ", styled.white('enter'), " to confirm"] }), _jsx(Text, { children: " " }), oldStyleAgents.map((agent, i) => {
|
|
115
|
+
const isCursor = i === cursor;
|
|
116
|
+
const prefix = agent.selected ? symbols.check : symbols.diamondOpen;
|
|
117
|
+
const prefixColor = agent.selected ? colors.green : colors.gray;
|
|
118
|
+
const nameColor = isCursor ? 'white' : 'gray';
|
|
119
|
+
const cursorChar = isCursor ? symbols.arrow : ' ';
|
|
120
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: colors.honey, children: [cursorChar, " "] }), _jsxs(Text, { color: prefixColor, children: [prefix, " "] }), _jsx(Text, { color: nameColor, bold: isCursor, children: agent.info.name }), _jsxs(Text, { color: "gray", children: [" (", agent.info.provider, ")"] })] }, agent.info.name));
|
|
121
|
+
}), newStyleAgents.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "gray", children: "Already migrated:" }), newStyleAgents.map((agent) => (_jsx(Box, { children: _jsxs(Text, { color: "gray", children: [' ', symbols.check, " ", agent.info.name] }) }, agent.info.name)))] })), _jsx(Text, { children: " " }), _jsx(Text, { color: "gray", children: styled.dim('q/esc to cancel') })] }));
|
|
122
|
+
}
|
|
123
|
+
if (phase === 'migrating') {
|
|
124
|
+
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsxs(Text, { color: colors.honey, bold: true, children: [symbols.hive, " Migrating agents..."] }), _jsx(Text, { color: "gray", children: border.horizontal.repeat(termWidth - 4) }), results.map((r) => (_jsx(Box, { children: _jsxs(Text, { color: r.success ? colors.green : colors.red, children: [r.success ? symbols.check : symbols.cross, " ", r.name] }) }, r.name))), currentAgent && (_jsx(Box, { children: _jsxs(Text, { color: colors.honey, children: [symbols.diamond, " ", currentAgent, ": ", currentStep] }) }))] }));
|
|
125
|
+
}
|
|
126
|
+
// phase === 'done'
|
|
127
|
+
const successCount = results.filter((r) => r.success && !r.error).length;
|
|
128
|
+
const alreadyNew = results.filter((r) => r.error === 'Already migrated').length;
|
|
129
|
+
const failCount = results.filter((r) => !r.success).length;
|
|
130
|
+
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsxs(Text, { color: colors.honey, bold: true, children: [symbols.hive, " Migration complete"] }), _jsx(Text, { color: "gray", children: border.horizontal.repeat(termWidth - 4) }), results.map((r) => (_jsxs(Box, { children: [r.success && !r.error && (_jsxs(Text, { color: colors.green, children: [symbols.check, " ", r.name, " \u2014 migrated"] })), r.error === 'Already migrated' && (_jsxs(Text, { color: "gray", children: [symbols.check, " ", r.name, " \u2014 already migrated"] })), !r.success && r.error !== 'Already migrated' && (_jsxs(Text, { color: colors.red, children: [symbols.cross, " ", r.name, " \u2014 ", r.error] }))] }, r.name))), agents.length === 0 && results.length === 0 && (_jsx(Text, { color: "gray", children: "No agents found in ~/.hive/agents/" })), _jsx(Text, { children: " " }), successCount > 0 && (_jsxs(Text, { color: "gray", children: ["Agents now run via @hive-org/cli. ", styled.white('npx @hive-org/cli@latest run'), " always uses the latest version."] }))] }));
|
|
131
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/** Files scaffolded by the old `hive create` that are now in @hive-org/cli */
|
|
4
|
+
const OLD_FILES = [
|
|
5
|
+
'index.tsx',
|
|
6
|
+
'analysis.ts',
|
|
7
|
+
'prompt.ts',
|
|
8
|
+
'chat-prompt.ts',
|
|
9
|
+
'memory-prompt.ts',
|
|
10
|
+
'edit-section.ts',
|
|
11
|
+
'fetch-rules.ts',
|
|
12
|
+
'helpers.ts',
|
|
13
|
+
'theme.ts',
|
|
14
|
+
'types.ts',
|
|
15
|
+
'process-lifecycle.ts',
|
|
16
|
+
'tsconfig.json',
|
|
17
|
+
];
|
|
18
|
+
/** Directories scaffolded by the old `hive create` */
|
|
19
|
+
const OLD_DIRS = ['hooks', 'components', 'hive'];
|
|
20
|
+
export function isOldStyleAgent(agentDir) {
|
|
21
|
+
const indexPath = path.join(agentDir, 'index.tsx');
|
|
22
|
+
return fs.pathExistsSync(indexPath);
|
|
23
|
+
}
|
|
24
|
+
export async function migrateAgent(agentDir, name, onStep) {
|
|
25
|
+
try {
|
|
26
|
+
// 1. Verify it's a valid agent
|
|
27
|
+
const soulPath = path.join(agentDir, 'SOUL.md');
|
|
28
|
+
const soulExists = await fs.pathExists(soulPath);
|
|
29
|
+
if (!soulExists) {
|
|
30
|
+
return { name, success: false, error: 'No SOUL.md found — not a valid agent' };
|
|
31
|
+
}
|
|
32
|
+
// 2. Delete old scaffolded files
|
|
33
|
+
onStep('Removing old runtime files');
|
|
34
|
+
for (const file of OLD_FILES) {
|
|
35
|
+
const filePath = path.join(agentDir, file);
|
|
36
|
+
const exists = await fs.pathExists(filePath);
|
|
37
|
+
if (exists) {
|
|
38
|
+
await fs.remove(filePath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// 3. Delete old directories
|
|
42
|
+
onStep('Removing old directories');
|
|
43
|
+
for (const dir of OLD_DIRS) {
|
|
44
|
+
const dirPath = path.join(agentDir, dir);
|
|
45
|
+
const exists = await fs.pathExists(dirPath);
|
|
46
|
+
if (exists) {
|
|
47
|
+
await fs.remove(dirPath);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// 4. Rewrite package.json
|
|
51
|
+
onStep('Rewriting package.json');
|
|
52
|
+
const pkgPath = path.join(agentDir, 'package.json');
|
|
53
|
+
const pkgExists = await fs.pathExists(pkgPath);
|
|
54
|
+
let pkgName = `hive-agent-${name}`;
|
|
55
|
+
if (pkgExists) {
|
|
56
|
+
const oldPkg = await fs.readJson(pkgPath);
|
|
57
|
+
pkgName = oldPkg.name ?? pkgName;
|
|
58
|
+
}
|
|
59
|
+
const newPkg = {
|
|
60
|
+
name: pkgName,
|
|
61
|
+
private: true,
|
|
62
|
+
type: 'module',
|
|
63
|
+
scripts: {
|
|
64
|
+
start: 'npx @hive-org/cli@latest run',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
await fs.writeJson(pkgPath, newPkg, { spaces: 2 });
|
|
68
|
+
// 5. Remove old node_modules and lock files (no longer needed)
|
|
69
|
+
onStep('Cleaning up old dependencies');
|
|
70
|
+
const nodeModulesPath = path.join(agentDir, 'node_modules');
|
|
71
|
+
const nodeModulesExists = await fs.pathExists(nodeModulesPath);
|
|
72
|
+
if (nodeModulesExists) {
|
|
73
|
+
await fs.remove(nodeModulesPath);
|
|
74
|
+
}
|
|
75
|
+
const lockPath = path.join(agentDir, 'package-lock.json');
|
|
76
|
+
const lockExists = await fs.pathExists(lockPath);
|
|
77
|
+
if (lockExists) {
|
|
78
|
+
await fs.remove(lockPath);
|
|
79
|
+
}
|
|
80
|
+
return { name, success: true };
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
84
|
+
return { name, success: false, error: message.slice(0, 200) };
|
|
85
|
+
}
|
|
86
|
+
}
|