@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,131 @@
1
+ import { spawn } from 'child_process';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ const FORCE_KILL_TIMEOUT_MS = 5_000;
5
+ const SHUTDOWN_TIMEOUT_MS = 10_000;
6
+ const ABSOLUTE_TIMEOUT_MS = FORCE_KILL_TIMEOUT_MS + 2_000;
7
+ export class AgentProcessManager {
8
+ constructor() {
9
+ this._agents = new Map();
10
+ this._agentsDir = path.join(os.homedir(), '.hive', 'agents');
11
+ }
12
+ spawnAll(discovered) {
13
+ for (const agent of discovered) {
14
+ this._agents.set(agent.name, {
15
+ name: agent.name,
16
+ status: 'spawning',
17
+ exitCode: null,
18
+ child: null,
19
+ });
20
+ this._spawnPiped(agent.name);
21
+ }
22
+ }
23
+ getStates() {
24
+ const states = [];
25
+ for (const agent of this._agents.values()) {
26
+ states.push({
27
+ name: agent.name,
28
+ status: agent.status,
29
+ exitCode: agent.exitCode,
30
+ });
31
+ }
32
+ return states;
33
+ }
34
+ async stopAgent(name) {
35
+ const agent = this._agents.get(name);
36
+ if (!agent?.child) {
37
+ return;
38
+ }
39
+ const child = agent.child;
40
+ const exitPromise = new Promise((resolve) => {
41
+ if (child.exitCode !== null) {
42
+ resolve();
43
+ return;
44
+ }
45
+ child.on('exit', () => resolve());
46
+ // Absolute safety timeout in case SIGKILL is not enough
47
+ setTimeout(() => resolve(), ABSOLUTE_TIMEOUT_MS);
48
+ });
49
+ child.kill('SIGTERM');
50
+ const forceKillTimer = setTimeout(() => {
51
+ child.kill('SIGKILL');
52
+ }, FORCE_KILL_TIMEOUT_MS);
53
+ await exitPromise;
54
+ clearTimeout(forceKillTimer);
55
+ agent.child = null;
56
+ }
57
+ respawnPiped(name) {
58
+ const agent = this._agents.get(name);
59
+ if (!agent) {
60
+ return;
61
+ }
62
+ agent.status = 'spawning';
63
+ agent.exitCode = null;
64
+ agent.child = null;
65
+ this._spawnPiped(name);
66
+ }
67
+ async shutdownAll() {
68
+ const children = [];
69
+ for (const agent of this._agents.values()) {
70
+ if (agent.child) {
71
+ children.push(agent.child);
72
+ }
73
+ }
74
+ if (children.length === 0) {
75
+ return;
76
+ }
77
+ const waitForAll = children.map((child) => new Promise((resolve) => {
78
+ if (child.exitCode !== null) {
79
+ resolve();
80
+ return;
81
+ }
82
+ child.on('exit', () => resolve());
83
+ // Absolute safety timeout
84
+ setTimeout(() => resolve(), SHUTDOWN_TIMEOUT_MS + 2_000);
85
+ }));
86
+ for (const child of children) {
87
+ child.kill('SIGTERM');
88
+ }
89
+ const forceKillTimer = setTimeout(() => {
90
+ for (const agent of this._agents.values()) {
91
+ if (agent.child && agent.child.exitCode === null) {
92
+ agent.child.kill('SIGKILL');
93
+ }
94
+ }
95
+ }, SHUTDOWN_TIMEOUT_MS);
96
+ await Promise.all(waitForAll);
97
+ clearTimeout(forceKillTimer);
98
+ }
99
+ _spawnPiped(name) {
100
+ const agentDir = path.join(this._agentsDir, name);
101
+ const child = spawn('npm', ['start'], {
102
+ cwd: agentDir,
103
+ stdio: ['ignore', 'pipe', 'pipe'],
104
+ });
105
+ const agent = this._agents.get(name);
106
+ if (!agent) {
107
+ return;
108
+ }
109
+ agent.child = child;
110
+ // Transition to 'running' once the OS has spawned the process
111
+ child.on('spawn', () => {
112
+ if (agent.status === 'spawning') {
113
+ agent.status = 'running';
114
+ }
115
+ });
116
+ // Drain stdout/stderr to prevent buffer blocking
117
+ child.stdout?.resume();
118
+ child.stderr?.resume();
119
+ child.on('error', () => {
120
+ agent.status = 'errored';
121
+ agent.exitCode = null;
122
+ agent.child = null;
123
+ });
124
+ child.on('exit', (code) => {
125
+ const exitCode = code ?? 1;
126
+ agent.status = exitCode === 0 ? 'exited' : 'errored';
127
+ agent.exitCode = exitCode;
128
+ agent.child = null;
129
+ });
130
+ }
131
+ }
@@ -0,0 +1,88 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text, useApp, useInput } from 'ink';
4
+ import { colors, symbols, border } from '../theme.js';
5
+ const STATUS_COLORS = {
6
+ spawning: colors.honey,
7
+ running: colors.green,
8
+ exited: colors.grayDim,
9
+ errored: colors.red,
10
+ };
11
+ const STATUS_SYMBOLS = {
12
+ spawning: symbols.diamondOpen,
13
+ running: symbols.dot,
14
+ exited: '\u25CB', // ○
15
+ errored: symbols.cross,
16
+ };
17
+ const POLL_INTERVAL_MS = 1_000;
18
+ const STOPPABLE = new Set(['running', 'spawning']);
19
+ const STARTABLE = new Set(['exited', 'errored']);
20
+ export function Dashboard({ manager }) {
21
+ const { exit } = useApp();
22
+ const [agents, setAgents] = useState(manager.getStates());
23
+ const [selectedIndex, setSelectedIndex] = useState(0);
24
+ useEffect(() => {
25
+ const timer = setInterval(() => {
26
+ setAgents(manager.getStates());
27
+ }, POLL_INTERVAL_MS);
28
+ return () => clearInterval(timer);
29
+ }, [manager]);
30
+ // Clamp selectedIndex when agents list changes
31
+ useEffect(() => {
32
+ const maxIndex = Math.max(agents.length - 1, 0);
33
+ setSelectedIndex((prev) => Math.min(prev, maxIndex));
34
+ }, [agents.length]);
35
+ useInput((_input, key) => {
36
+ if (key.upArrow) {
37
+ setSelectedIndex((prev) => {
38
+ const max = agents.length - 1;
39
+ return prev > 0 ? prev - 1 : max;
40
+ });
41
+ }
42
+ else if (key.downArrow) {
43
+ setSelectedIndex((prev) => {
44
+ const max = agents.length - 1;
45
+ return prev < max ? prev + 1 : 0;
46
+ });
47
+ }
48
+ else if (_input === ' ' || key.return) {
49
+ const agent = agents[selectedIndex];
50
+ if (!agent) {
51
+ return;
52
+ }
53
+ if (STOPPABLE.has(agent.status)) {
54
+ void manager.stopAgent(agent.name);
55
+ }
56
+ else if (STARTABLE.has(agent.status)) {
57
+ manager.respawnPiped(agent.name);
58
+ }
59
+ }
60
+ else if (key.ctrl && _input === 'c') {
61
+ exit();
62
+ }
63
+ });
64
+ const runningCount = agents.filter((a) => a.status === 'running').length;
65
+ const spawningCount = agents.filter((a) => a.status === 'spawning').length;
66
+ const stoppedCount = agents.filter((a) => a.status === 'exited' || a.status === 'errored').length;
67
+ const statusParts = [];
68
+ if (runningCount > 0) {
69
+ statusParts.push(`${runningCount} running`);
70
+ }
71
+ if (spawningCount > 0) {
72
+ statusParts.push(`${spawningCount} starting`);
73
+ }
74
+ if (stoppedCount > 0) {
75
+ statusParts.push(`${stoppedCount} stopped`);
76
+ }
77
+ const statusLabel = statusParts.length > 0 ? statusParts.join(', ') : 'no agents running';
78
+ return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.honey, children: [symbols.hive, " "] }), _jsx(Text, { color: colors.white, bold: true, children: "Hive Swarm" }), _jsxs(Text, { color: colors.gray, children: [' ', border.horizontal, " ", agents.length, " agent", agents.length !== 1 ? 's' : '', ' ', border.horizontal, " ", statusLabel] })] }), agents.map((agent, index) => {
79
+ const isSelected = index === selectedIndex;
80
+ const statusColor = STATUS_COLORS[agent.status];
81
+ const statusSymbol = STATUS_SYMBOLS[agent.status];
82
+ const isAlive = agent.status === 'running' || agent.status === 'spawning';
83
+ const statusText = agent.status === 'exited' || agent.status === 'errored'
84
+ ? `${agent.status} (${agent.exitCode})`
85
+ : agent.status;
86
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? colors.honey : undefined, children: isSelected ? `${symbols.diamond} ` : ' ' }), _jsxs(Text, { color: statusColor, children: [statusSymbol, " "] }), _jsx(Text, { color: isAlive ? colors.white : colors.grayDim, children: agent.name.padEnd(20) }), _jsx(Text, { color: statusColor, children: statusText })] }, agent.name));
87
+ }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.grayDim, children: [symbols.arrow, " ", '\u2191\u2193', " navigate ", ' ', " space/enter stop/start ", ' ', " ctrl+c quit"] }) })] }));
88
+ }
@@ -0,0 +1,101 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ const HEADLESS_MARKER = 'Input Bar — only when stdin is a real TTY';
4
+ /**
5
+ * Patches an agent's index.tsx so that TextInput is only rendered when
6
+ * stdin is a real TTY. Without this, Ink's setRawMode(true) throws
7
+ * when the agent is spawned with piped stdio.
8
+ *
9
+ * Idempotent — skips if already patched.
10
+ */
11
+ export async function ensureHeadlessSupport(agentDir) {
12
+ const filePath = path.join(agentDir, 'index.tsx');
13
+ const exists = await fs.pathExists(filePath);
14
+ if (!exists) {
15
+ return;
16
+ }
17
+ let content = await fs.readFile(filePath, 'utf-8');
18
+ let dirty = false;
19
+ // 1. Ensure isInteractive flag exists after useAgent() destructuring
20
+ const INTERACTIVE_DECL = 'const isInteractive = process.stdin.isTTY === true;';
21
+ if (!content.includes(INTERACTIVE_DECL)) {
22
+ const useAgentClose = '} = useAgent();';
23
+ const hookInsertIndex = content.indexOf(useAgentClose);
24
+ if (hookInsertIndex === -1) {
25
+ return;
26
+ }
27
+ const insertAfter = hookInsertIndex + useAgentClose.length;
28
+ const interactiveFlag = `\n\n // When stdin is not a TTY (piped by hive-cli start), skip interactive input\n ${INTERACTIVE_DECL}`;
29
+ content = content.slice(0, insertAfter) + interactiveFlag + content.slice(insertAfter);
30
+ dirty = true;
31
+ }
32
+ // 2. Wrap TextInput in isInteractive guard (if not already done)
33
+ if (content.includes(HEADLESS_MARKER)) {
34
+ // Input Bar already replaced — just write if we added the variable
35
+ if (dirty) {
36
+ await fs.writeFile(filePath, content, 'utf-8');
37
+ }
38
+ return;
39
+ }
40
+ if (!content.includes('<TextInput')) {
41
+ if (dirty) {
42
+ await fs.writeFile(filePath, content, 'utf-8');
43
+ }
44
+ return;
45
+ }
46
+ const inputBarStart = content.indexOf('{/* Input Bar */}');
47
+ if (inputBarStart === -1) {
48
+ if (dirty) {
49
+ await fs.writeFile(filePath, content, 'utf-8');
50
+ }
51
+ return;
52
+ }
53
+ // Find the closing </Box> of the bottom border (3 Box elements after Input Bar comment)
54
+ let pos = inputBarStart;
55
+ let boxCloseCount = 0;
56
+ const boxCloseTag = '</Box>';
57
+ while (boxCloseCount < 3 && pos < content.length) {
58
+ const nextClose = content.indexOf(boxCloseTag, pos);
59
+ if (nextClose === -1) {
60
+ break;
61
+ }
62
+ pos = nextClose + boxCloseTag.length;
63
+ boxCloseCount++;
64
+ }
65
+ if (boxCloseCount === 3) {
66
+ const inputBarEnd = pos;
67
+ const replacement = `{/* Input Bar — only when stdin is a real TTY */}
68
+ <Box>
69
+ <Text color="gray">
70
+ {isInteractive ? border.teeLeft : border.bottomLeft}
71
+ {border.horizontal.repeat(boxWidth - 2)}
72
+ {isInteractive ? border.teeRight : border.bottomRight}
73
+ </Text>
74
+ </Box>
75
+ {isInteractive && (
76
+ <>
77
+ <Box paddingLeft={1}>
78
+ <Text color={colors.honey}>{symbols.arrow} </Text>
79
+ <TextInput
80
+ value={input}
81
+ onChange={setInput}
82
+ onSubmit={(val) => {
83
+ setInput('');
84
+ void handleChatSubmit(val);
85
+ }}
86
+ placeholder={chatStreaming ? 'thinking...' : \`chat with \${agentName} agent...\`}
87
+ />
88
+ </Box>
89
+ <Box>
90
+ <Text color="gray">
91
+ {border.bottomLeft}
92
+ {border.horizontal.repeat(boxWidth - 2)}
93
+ {border.bottomRight}
94
+ </Text>
95
+ </Box>
96
+ </>
97
+ )}`;
98
+ content = content.slice(0, inputBarStart) + replacement + content.slice(inputBarEnd);
99
+ }
100
+ await fs.writeFile(filePath, content, 'utf-8');
101
+ }
@@ -0,0 +1,142 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
+ const TEMPLATES_DIR = path.resolve(__dirname, '../../templates');
6
+ const MANAGED_MARKER = 'HIVE_MANAGED';
7
+ const INTERACTIVE_GUARD_MARKER = 'Input Bar — only when stdin is a real TTY';
8
+ /**
9
+ * Patches an existing agent to support:
10
+ * 1. Headless mode — skip TextInput when stdin is not a TTY (piped by dashboard)
11
+ * 2. Managed mode — Escape exits back to dashboard when HIVE_MANAGED=1
12
+ *
13
+ * Idempotent — skips if already patched.
14
+ */
15
+ export async function ensureManagedMode(agentDir) {
16
+ await patchProcessLifecycle(agentDir);
17
+ await patchIndexTsx(agentDir);
18
+ }
19
+ async function patchProcessLifecycle(agentDir) {
20
+ const filePath = path.join(agentDir, 'process-lifecycle.ts');
21
+ const exists = await fs.pathExists(filePath);
22
+ if (!exists) {
23
+ return;
24
+ }
25
+ const content = await fs.readFile(filePath, 'utf-8');
26
+ // Already patched if gracefulShutdown is exported
27
+ if (content.includes('export const gracefulShutdown')) {
28
+ return;
29
+ }
30
+ // Copy the template version (it has the export + exitCode param)
31
+ const templatePath = path.join(TEMPLATES_DIR, 'process-lifecycle.ts');
32
+ const templateExists = await fs.pathExists(templatePath);
33
+ if (!templateExists) {
34
+ return;
35
+ }
36
+ await fs.copyFile(templatePath, filePath);
37
+ }
38
+ async function patchIndexTsx(agentDir) {
39
+ const filePath = path.join(agentDir, 'index.tsx');
40
+ const exists = await fs.pathExists(filePath);
41
+ if (!exists) {
42
+ return;
43
+ }
44
+ let content = await fs.readFile(filePath, 'utf-8');
45
+ // Already fully patched
46
+ if (content.includes(MANAGED_MARKER) && content.includes(INTERACTIVE_GUARD_MARKER)) {
47
+ return;
48
+ }
49
+ // 1. Add useInput to ink import if not present
50
+ // Existing pattern: "import { render, Box, Text } from 'ink';"
51
+ // Target: "import { render, Box, Text, useInput } from 'ink';"
52
+ if (!content.includes('useInput')) {
53
+ content = content.replace(/\s*}\s*from\s*'ink'/, ", useInput } from 'ink'");
54
+ }
55
+ // 2. Add gracefulShutdown import from process-lifecycle if not present
56
+ if (!content.includes('gracefulShutdown')) {
57
+ content = content.replace(/import\s*\{\s*setupProcessLifecycle\s*\}\s*from\s*'\.\/process-lifecycle'/, "import { setupProcessLifecycle, gracefulShutdown } from './process-lifecycle'");
58
+ }
59
+ // 3. Add isInteractive + managed mode hooks after useAgent() destructuring
60
+ if (!content.includes(MANAGED_MARKER)) {
61
+ const useAgentClose = '} = useAgent();';
62
+ const hookInsertIndex = content.indexOf(useAgentClose);
63
+ if (hookInsertIndex === -1) {
64
+ return;
65
+ }
66
+ const insertAfter = hookInsertIndex + useAgentClose.length;
67
+ const hooks = `
68
+
69
+ // When stdin is not a TTY (piped by hive-cli start), skip interactive input
70
+ const isInteractive = process.stdin.isTTY === true;
71
+
72
+ // When managed by hive-cli start, Escape exits back to dashboard
73
+ const isManaged = process.env.HIVE_MANAGED === '1';
74
+
75
+ useInput((_input, key) => {
76
+ if (isManaged && key.escape) {
77
+ void gracefulShutdown(0);
78
+ }
79
+ }, { isActive: isManaged });`;
80
+ content = content.slice(0, insertAfter) + hooks + content.slice(insertAfter);
81
+ }
82
+ // 4. Wrap TextInput in isInteractive guard
83
+ // Replace the input bar section to conditionally render TextInput
84
+ if (!content.includes(INTERACTIVE_GUARD_MARKER) && content.includes('<TextInput')) {
85
+ // Find the Input Bar comment and replace the entire block
86
+ const inputBarStart = content.indexOf('{/* Input Bar */}');
87
+ if (inputBarStart === -1) {
88
+ // Fallback: just wrap the TextInput line
89
+ await fs.writeFile(filePath, content, 'utf-8');
90
+ return;
91
+ }
92
+ // Find the closing </Box> of the bottom border (3 Box elements after Input Bar comment)
93
+ // Pattern: <Box> separator </Box> <Box> TextInput </Box> <Box> bottom border </Box>
94
+ let pos = inputBarStart;
95
+ let boxCloseCount = 0;
96
+ const boxCloseTag = '</Box>';
97
+ while (boxCloseCount < 3 && pos < content.length) {
98
+ const nextClose = content.indexOf(boxCloseTag, pos);
99
+ if (nextClose === -1) {
100
+ break;
101
+ }
102
+ pos = nextClose + boxCloseTag.length;
103
+ boxCloseCount++;
104
+ }
105
+ if (boxCloseCount === 3) {
106
+ const inputBarEnd = pos;
107
+ const replacement = `{/* Input Bar — only when stdin is a real TTY */}
108
+ <Box>
109
+ <Text color="gray">
110
+ {isInteractive ? border.teeLeft : border.bottomLeft}
111
+ {border.horizontal.repeat(boxWidth - 2)}
112
+ {isInteractive ? border.teeRight : border.bottomRight}
113
+ </Text>
114
+ </Box>
115
+ {isInteractive && (
116
+ <>
117
+ <Box paddingLeft={1}>
118
+ <Text color={colors.honey}>{symbols.arrow} </Text>
119
+ <TextInput
120
+ value={input}
121
+ onChange={setInput}
122
+ onSubmit={(val) => {
123
+ setInput('');
124
+ void handleChatSubmit(val);
125
+ }}
126
+ placeholder={chatStreaming ? 'thinking...' : \`chat with \${agentName} agent...\`}
127
+ />
128
+ </Box>
129
+ <Box>
130
+ <Text color="gray">
131
+ {border.bottomLeft}
132
+ {border.horizontal.repeat(boxWidth - 2)}
133
+ {border.bottomRight}
134
+ </Text>
135
+ </Box>
136
+ </>
137
+ )}`;
138
+ content = content.slice(0, inputBarStart) + replacement + content.slice(inputBarEnd);
139
+ }
140
+ }
141
+ await fs.writeFile(filePath, content, 'utf-8');
142
+ }
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { render } from 'ink';
3
+ import { showWelcome } from '../create/welcome.js';
4
+ import { scanAgents } from '../agents.js';
5
+ import { symbols, styled } from '../theme.js';
6
+ import { AgentProcessManager } from './AgentProcessManager.js';
7
+ import { Dashboard } from './Dashboard.js';
8
+ export async function startCommand() {
9
+ // Run welcome animation and scan agents in parallel
10
+ const results = await Promise.all([showWelcome(), scanAgents()]);
11
+ const discovered = results[1];
12
+ if (discovered.length === 0) {
13
+ console.log(`\n ${styled.honey(symbols.hive)} ${styled.red('No agents found in ~/.hive/agents/')}\n`);
14
+ console.log(` ${styled.gray('Create agents with:')} ${styled.white('npx @hive-org/cli@latest create')}\n`);
15
+ return;
16
+ }
17
+ const manager = new AgentProcessManager();
18
+ manager.spawnAll(discovered);
19
+ const { waitUntilExit } = render(React.createElement(Dashboard, { manager }));
20
+ await waitUntilExit();
21
+ await manager.shutdownAll();
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hive-org/cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "CLI for bootstrapping Hive AI Agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,7 +10,6 @@
10
10
  },
11
11
  "files": [
12
12
  "dist",
13
- "templates",
14
13
  "README.md"
15
14
  ],
16
15
  "publishConfig": {
@@ -28,16 +27,18 @@
28
27
  "@ai-sdk/google": "^3.0.0",
29
28
  "@ai-sdk/openai": "^3.0.25",
30
29
  "@ai-sdk/xai": "^3.0.0",
31
- "@hive-org/sdk": "^0.0.9",
30
+ "@hive-org/sdk": "^0.0.12",
32
31
  "@openrouter/ai-sdk-provider": "^0.4.0",
33
- "ai": "^6.0.0",
32
+ "ai": "^6.0.71",
34
33
  "axios": "^1.6.0",
35
34
  "chalk": "^5.3.0",
35
+ "dotenv": "^16.0.0",
36
36
  "fs-extra": "^11.2.0",
37
37
  "ink": "^5.1.0",
38
38
  "ink-select-input": "^6.0.0",
39
39
  "ink-text-input": "^6.0.0",
40
- "react": "^18.3.1"
40
+ "react": "^18.3.1",
41
+ "zod": "^4.0.0"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@types/fs-extra": "^11.0.4",
@@ -1,103 +0,0 @@
1
- import { openai } from '@ai-sdk/openai';
2
- import { generateText, Output } from 'ai';
3
- import { z } from 'zod';
4
- import type { Conviction, ThreadDto } from './hive/objects';
5
- import { buildAnalystPrompt } from './prompt';
6
- import type { BuildPromptOptions } from './prompt';
7
- import type { ChatMessage } from './chat-prompt';
8
- import { loadMemory, saveMemory, getMemoryLineCount, MEMORY_SOFT_LIMIT } from './hive/memory';
9
- import { buildMemoryExtractionPrompt } from './memory-prompt';
10
- import { stripCodeFences } from './helpers';
11
-
12
- // ─── Prediction Schema ──────────────────────────────
13
-
14
- export const predictionSchema = z.object({
15
- skip: z
16
- .boolean()
17
- .describe(
18
- 'true if this signal is outside your expertise or you have no strong take. false if you want to comment.',
19
- ),
20
- summary: z
21
- .string()
22
- .min(1)
23
- .max(280)
24
- .nullable()
25
- .describe(
26
- 'Your CT-style take on this signal. Short, punchy, in character. Think tweet, not essay. null if skipping.',
27
- ),
28
- conviction: z
29
- .number()
30
- .nullable()
31
- .describe(
32
- 'Predicted percent price change over the next 3 hours, up to one decimal place. Use the FULL range based on signal strength: tiny signals ±0.1-1.0, moderate ±1.5-5.0, strong ±5.0-12.0, extreme ±12.0-25.0. Negative for bearish. 0 if neutral. null if skipping. VARY your predictions — do NOT default to the same number repeatedly.',
33
- ),
34
- });
35
-
36
- type Prediction = z.infer<typeof predictionSchema>;
37
-
38
- // ─── Signal Analysis ────────────────────────────────
39
-
40
- export async function processSignalAndSummarize(
41
- thread: ThreadDto,
42
- recentComments: readonly string[],
43
- memory: string,
44
- ): Promise<{ skip: boolean; summary: string; conviction: Conviction }> {
45
- const promptOptions: BuildPromptOptions = {
46
- threadText: thread.text,
47
- projectId: thread.project_id,
48
- timestamp: thread.timestamp,
49
- priceOnFetch: thread.price_on_fetch,
50
- citations: thread.citations,
51
- recentPosts: recentComments,
52
- memory,
53
- };
54
- const prompt = await buildAnalystPrompt(promptOptions);
55
-
56
- const { output } = await generateText({
57
- model: openai('gpt-4o'),
58
- output: Output.object({ schema: predictionSchema }),
59
- prompt,
60
- });
61
-
62
- if (!output) {
63
- return { skip: true, summary: '', conviction: 0 };
64
- }
65
-
66
- const prediction = output as Prediction;
67
- const skip = prediction.skip ?? false;
68
- const summary = prediction.summary ?? '';
69
- const conviction: Conviction = prediction.conviction ?? 0;
70
-
71
- return { skip, summary, conviction };
72
- }
73
-
74
- // ─── Memory Extraction ──────────────────────────────
75
-
76
- export async function extractAndSaveMemory(sessionMessages: ChatMessage[]): Promise<string | null> {
77
- const currentMemory = await loadMemory();
78
- const lineCount = getMemoryLineCount(currentMemory);
79
-
80
- if (sessionMessages.length === 0 && lineCount <= MEMORY_SOFT_LIMIT) {
81
- return null;
82
- }
83
-
84
- const prompt = buildMemoryExtractionPrompt({
85
- currentMemory,
86
- sessionMessages,
87
- lineCount,
88
- });
89
-
90
- try {
91
- const { text } = await generateText({
92
- model: openai('gpt-4o'),
93
- prompt,
94
- });
95
- const cleaned = stripCodeFences(text);
96
- await saveMemory(cleaned);
97
- return cleaned;
98
- } catch (err: unknown) {
99
- const raw = err instanceof Error ? err.message : String(err);
100
- console.error(`[Memory] Failed to extract memory: ${raw.slice(0, 200)}`);
101
- return null;
102
- }
103
- }