@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.
Files changed (47) hide show
  1. package/dist/agent/analysis.js +78 -0
  2. package/dist/agent/app.js +32 -0
  3. package/dist/agent/chat-prompt.js +63 -0
  4. package/dist/agent/components/AsciiTicker.js +81 -0
  5. package/dist/agent/components/HoneycombBoot.js +270 -0
  6. package/dist/agent/components/Spinner.js +37 -0
  7. package/dist/agent/config.js +52 -0
  8. package/dist/agent/edit-section.js +59 -0
  9. package/dist/agent/fetch-rules.js +21 -0
  10. package/dist/agent/helpers.js +22 -0
  11. package/dist/agent/hooks/useAgent.js +269 -0
  12. package/{templates/memory-prompt.ts → dist/agent/memory-prompt.js} +17 -32
  13. package/dist/agent/model.js +63 -0
  14. package/dist/agent/objects.js +1 -0
  15. package/dist/agent/process-lifecycle.js +56 -0
  16. package/{templates/prompt.ts → dist/agent/prompt.js} +18 -47
  17. package/dist/agent/theme.js +37 -0
  18. package/dist/agent/types.js +1 -0
  19. package/dist/agents.js +30 -21
  20. package/dist/ai-providers.js +0 -13
  21. package/dist/create/generate.js +10 -120
  22. package/dist/index.js +27 -4
  23. package/dist/migrate-templates/MigrateApp.js +131 -0
  24. package/dist/migrate-templates/migrate.js +86 -0
  25. package/dist/start/AgentProcessManager.js +131 -0
  26. package/dist/start/Dashboard.js +88 -0
  27. package/dist/start/patch-headless.js +101 -0
  28. package/dist/start/patch-managed-mode.js +142 -0
  29. package/dist/start/start-command.js +22 -0
  30. package/package.json +6 -5
  31. package/templates/analysis.ts +0 -103
  32. package/templates/chat-prompt.ts +0 -94
  33. package/templates/components/AsciiTicker.tsx +0 -113
  34. package/templates/components/HoneycombBoot.tsx +0 -348
  35. package/templates/components/Spinner.tsx +0 -64
  36. package/templates/edit-section.ts +0 -64
  37. package/templates/fetch-rules.ts +0 -23
  38. package/templates/helpers.ts +0 -22
  39. package/templates/hive/agent.ts +0 -2
  40. package/templates/hive/config.ts +0 -96
  41. package/templates/hive/memory.ts +0 -1
  42. package/templates/hive/objects.ts +0 -26
  43. package/templates/hooks/useAgent.ts +0 -337
  44. package/templates/index.tsx +0 -257
  45. package/templates/process-lifecycle.ts +0 -66
  46. package/templates/theme.ts +0 -40
  47. 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
- let provider = 'unknown';
22
- const envPath = path.join(agentsDir, entry.name, '.env');
23
- const envExists = await fs.pathExists(envPath);
24
- if (envExists) {
25
- const envContent = await fs.readFile(envPath, 'utf-8');
26
- if (envContent.includes('OPENAI_API_KEY')) {
27
- provider = 'OpenAI';
28
- }
29
- else if (envContent.includes('ANTHROPIC_API_KEY')) {
30
- provider = 'Anthropic';
31
- }
32
- else if (envContent.includes('GOOGLE_GENERATIVE_AI_API_KEY')) {
33
- provider = 'Google';
34
- }
35
- else if (envContent.includes('XAI_API_KEY')) {
36
- provider = 'xAI';
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
- else if (envContent.includes('OPENROUTER_API_KEY')) {
39
- provider = 'OpenRouter';
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 agents;
54
+ return 'unknown';
46
55
  }
@@ -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
- }
@@ -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. Initialize npm project
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
- // 6. Write .env
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
- // 7. Copy prompt.ts
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 packageJsonPath = path.join(projectDir, 'package.json');
124
- const packageJson = await fs.readJson(packageJsonPath);
125
- packageJson.type = 'module';
126
- packageJson.scripts = {
127
- ...packageJson.scripts,
128
- start: 'tsx index.tsx',
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, 'tsconfig.json'), tsConfig, { spaces: 2 });
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 { StartApp } from './start/StartApp.js';
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
- const { waitUntilExit } = render(React.createElement(StartApp));
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
+ }