@zhive/cli 0.5.0

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 (189) hide show
  1. package/README.md +118 -0
  2. package/dist/agent/analysis.js +160 -0
  3. package/dist/agent/app.js +122 -0
  4. package/dist/agent/chat-prompt.js +65 -0
  5. package/dist/agent/commands/registry.js +12 -0
  6. package/dist/agent/components/AsciiTicker.js +81 -0
  7. package/dist/agent/components/CommandInput.js +65 -0
  8. package/dist/agent/components/HoneycombBoot.js +291 -0
  9. package/dist/agent/components/Spinner.js +37 -0
  10. package/dist/agent/config.js +75 -0
  11. package/dist/agent/edit-section.js +59 -0
  12. package/dist/agent/fetch-rules.js +21 -0
  13. package/dist/agent/helpers.js +22 -0
  14. package/dist/agent/hooks/useAgent.js +480 -0
  15. package/dist/agent/memory-prompt.js +47 -0
  16. package/dist/agent/model.js +92 -0
  17. package/dist/agent/objects.js +1 -0
  18. package/dist/agent/process-lifecycle.js +18 -0
  19. package/dist/agent/prompt.js +353 -0
  20. package/dist/agent/run-headless.js +189 -0
  21. package/dist/agent/skills/index.js +2 -0
  22. package/dist/agent/skills/skill-parser.js +149 -0
  23. package/dist/agent/skills/types.js +1 -0
  24. package/dist/agent/theme.js +41 -0
  25. package/dist/agent/tools/index.js +76 -0
  26. package/dist/agent/tools/market/client.js +41 -0
  27. package/dist/agent/tools/market/index.js +3 -0
  28. package/dist/agent/tools/market/tools.js +518 -0
  29. package/dist/agent/tools/mindshare/client.js +124 -0
  30. package/dist/agent/tools/mindshare/index.js +3 -0
  31. package/dist/agent/tools/mindshare/tools.js +563 -0
  32. package/dist/agent/tools/read-skill-tool.js +30 -0
  33. package/dist/agent/tools/ta/index.js +1 -0
  34. package/dist/agent/tools/ta/indicators.js +201 -0
  35. package/dist/agent/types.js +1 -0
  36. package/dist/agents.js +110 -0
  37. package/dist/ai-providers.js +66 -0
  38. package/dist/avatar.js +34 -0
  39. package/dist/backtest/default-backtest-data.js +200 -0
  40. package/dist/backtest/fetch.js +41 -0
  41. package/dist/backtest/import.js +106 -0
  42. package/dist/backtest/index.js +10 -0
  43. package/dist/backtest/results.js +113 -0
  44. package/dist/backtest/runner.js +134 -0
  45. package/dist/backtest/storage.js +11 -0
  46. package/dist/backtest/types.js +1 -0
  47. package/dist/commands/create/ai-generate.js +126 -0
  48. package/dist/commands/create/commands/index.js +10 -0
  49. package/dist/commands/create/generate.js +73 -0
  50. package/dist/commands/create/presets/data.js +225 -0
  51. package/dist/commands/create/presets/formatting.js +81 -0
  52. package/dist/commands/create/presets/index.js +3 -0
  53. package/dist/commands/create/presets/options.js +307 -0
  54. package/dist/commands/create/presets/types.js +1 -0
  55. package/dist/commands/create/presets.js +613 -0
  56. package/dist/commands/create/ui/CreateApp.js +172 -0
  57. package/dist/commands/create/ui/steps/ApiKeyStep.js +89 -0
  58. package/dist/commands/create/ui/steps/AvatarStep.js +16 -0
  59. package/dist/commands/create/ui/steps/DoneStep.js +14 -0
  60. package/dist/commands/create/ui/steps/IdentityStep.js +125 -0
  61. package/dist/commands/create/ui/steps/NameStep.js +148 -0
  62. package/dist/commands/create/ui/steps/ScaffoldStep.js +59 -0
  63. package/dist/commands/create/ui/steps/SoulStep.js +21 -0
  64. package/dist/commands/create/ui/steps/StrategyStep.js +20 -0
  65. package/dist/commands/create/ui/steps/StreamingGenerationStep.js +56 -0
  66. package/dist/commands/create/ui/validation.js +34 -0
  67. package/dist/commands/create/validate-api-key.js +27 -0
  68. package/dist/commands/install.js +50 -0
  69. package/dist/commands/list/commands/index.js +7 -0
  70. package/dist/commands/list/ui/ListApp.js +79 -0
  71. package/dist/commands/migrate-templates/commands/index.js +9 -0
  72. package/dist/commands/migrate-templates/migrate.js +87 -0
  73. package/dist/commands/migrate-templates/ui/MigrateApp.js +132 -0
  74. package/dist/commands/run/commands/index.js +17 -0
  75. package/dist/commands/run/run-headless.js +111 -0
  76. package/dist/commands/shared/theme.js +57 -0
  77. package/dist/commands/shared/welcome.js +304 -0
  78. package/dist/commands/start/commands/backtest.js +35 -0
  79. package/dist/commands/start/commands/index.js +62 -0
  80. package/dist/commands/start/commands/prediction.js +73 -0
  81. package/dist/commands/start/commands/skills.js +44 -0
  82. package/dist/commands/start/commands/skills.test.js +140 -0
  83. package/dist/commands/start/hooks/types.js +1 -0
  84. package/dist/commands/start/hooks/useAgent.js +177 -0
  85. package/dist/commands/start/hooks/useChat.js +266 -0
  86. package/dist/commands/start/hooks/usePollActivity.js +45 -0
  87. package/dist/commands/start/hooks/utils.js +152 -0
  88. package/dist/commands/start/services/backtest/default-backtest-data.js +200 -0
  89. package/dist/commands/start/services/backtest/fetch.js +42 -0
  90. package/dist/commands/start/services/backtest/import.js +109 -0
  91. package/dist/commands/start/services/backtest/index.js +10 -0
  92. package/dist/commands/start/services/backtest/results.js +113 -0
  93. package/dist/commands/start/services/backtest/runner.js +103 -0
  94. package/dist/commands/start/services/backtest/storage.js +11 -0
  95. package/dist/commands/start/services/backtest/types.js +1 -0
  96. package/dist/commands/start/services/command-registry.js +13 -0
  97. package/dist/commands/start/ui/AsciiTicker.js +81 -0
  98. package/dist/commands/start/ui/CommandInput.js +65 -0
  99. package/dist/commands/start/ui/HoneycombBoot.js +291 -0
  100. package/dist/commands/start/ui/PollText.js +23 -0
  101. package/dist/commands/start/ui/PredictionsPanel.js +88 -0
  102. package/dist/commands/start/ui/SelectAgentApp.js +93 -0
  103. package/dist/commands/start/ui/Spinner.js +29 -0
  104. package/dist/commands/start/ui/SpinnerContext.js +20 -0
  105. package/dist/commands/start/ui/app.js +36 -0
  106. package/dist/commands/start-all/AgentProcessManager.js +98 -0
  107. package/dist/commands/start-all/commands/index.js +24 -0
  108. package/dist/commands/start-all/ui/Dashboard.js +91 -0
  109. package/dist/components/AsciiTicker.js +81 -0
  110. package/dist/components/CharacterSummaryCard.js +33 -0
  111. package/dist/components/CodeBlock.js +11 -0
  112. package/dist/components/ColoredStats.js +18 -0
  113. package/dist/components/Header.js +10 -0
  114. package/dist/components/HoneycombLoader.js +190 -0
  115. package/dist/components/InputGuard.js +6 -0
  116. package/dist/components/MultiSelectPrompt.js +45 -0
  117. package/dist/components/SelectPrompt.js +20 -0
  118. package/dist/components/Spinner.js +16 -0
  119. package/dist/components/StepIndicator.js +31 -0
  120. package/dist/components/StreamingText.js +50 -0
  121. package/dist/components/TextPrompt.js +28 -0
  122. package/dist/components/stdout-spinner.js +48 -0
  123. package/dist/config.js +28 -0
  124. package/dist/create/CreateApp.js +153 -0
  125. package/dist/create/ai-generate.js +147 -0
  126. package/dist/create/generate.js +73 -0
  127. package/dist/create/steps/ApiKeyStep.js +97 -0
  128. package/dist/create/steps/AvatarStep.js +16 -0
  129. package/dist/create/steps/BioStep.js +14 -0
  130. package/dist/create/steps/DoneStep.js +14 -0
  131. package/dist/create/steps/IdentityStep.js +163 -0
  132. package/dist/create/steps/NameStep.js +71 -0
  133. package/dist/create/steps/ScaffoldStep.js +58 -0
  134. package/dist/create/steps/SoulStep.js +58 -0
  135. package/dist/create/steps/StrategyStep.js +58 -0
  136. package/dist/create/validate-api-key.js +47 -0
  137. package/dist/create/welcome.js +304 -0
  138. package/dist/index.js +60 -0
  139. package/dist/list/ListApp.js +79 -0
  140. package/dist/load-agent-env.js +30 -0
  141. package/dist/migrate-templates/MigrateApp.js +131 -0
  142. package/dist/migrate-templates/migrate.js +86 -0
  143. package/dist/presets.js +613 -0
  144. package/dist/shared/agent/agent-runtime.js +144 -0
  145. package/dist/shared/agent/analysis.js +171 -0
  146. package/dist/shared/agent/helpers.js +1 -0
  147. package/dist/shared/agent/prompts/chat-prompt.js +60 -0
  148. package/dist/shared/agent/prompts/megathread.js +202 -0
  149. package/dist/shared/agent/prompts/memory-prompt.js +47 -0
  150. package/dist/shared/agent/prompts/prompt.js +18 -0
  151. package/dist/shared/agent/skills/index.js +2 -0
  152. package/dist/shared/agent/skills/skill-parser.js +167 -0
  153. package/dist/shared/agent/skills/skill-parser.test.js +190 -0
  154. package/dist/shared/agent/skills/types.js +1 -0
  155. package/dist/shared/agent/tools/edit-section.js +60 -0
  156. package/dist/shared/agent/tools/execute-skill-tool.js +134 -0
  157. package/dist/shared/agent/tools/fetch-rules.js +22 -0
  158. package/dist/shared/agent/tools/formatting.js +48 -0
  159. package/dist/shared/agent/tools/index.js +87 -0
  160. package/dist/shared/agent/tools/market/client.js +41 -0
  161. package/dist/shared/agent/tools/market/index.js +3 -0
  162. package/dist/shared/agent/tools/market/tools.js +497 -0
  163. package/dist/shared/agent/tools/mindshare/client.js +124 -0
  164. package/dist/shared/agent/tools/mindshare/index.js +3 -0
  165. package/dist/shared/agent/tools/mindshare/tools.js +167 -0
  166. package/dist/shared/agent/tools/read-skill-tool.js +30 -0
  167. package/dist/shared/agent/tools/ta/index.js +1 -0
  168. package/dist/shared/agent/tools/ta/indicators.js +201 -0
  169. package/dist/shared/agent/types.js +1 -0
  170. package/dist/shared/agent/utils.js +43 -0
  171. package/dist/shared/config/agent.js +177 -0
  172. package/dist/shared/config/ai-providers.js +156 -0
  173. package/dist/shared/config/config.js +22 -0
  174. package/dist/shared/config/constant.js +8 -0
  175. package/dist/shared/config/env-loader.js +30 -0
  176. package/dist/shared/types.js +1 -0
  177. package/dist/start/AgentProcessManager.js +98 -0
  178. package/dist/start/Dashboard.js +92 -0
  179. package/dist/start/SelectAgentApp.js +81 -0
  180. package/dist/start/StartApp.js +189 -0
  181. package/dist/start/patch-headless.js +101 -0
  182. package/dist/start/patch-managed-mode.js +142 -0
  183. package/dist/start/start-command.js +24 -0
  184. package/dist/theme.js +54 -0
  185. package/package.json +68 -0
  186. package/templates/components/HoneycombBoot.tsx +343 -0
  187. package/templates/fetch-rules.ts +23 -0
  188. package/templates/skills/mindshare/SKILL.md +197 -0
  189. package/templates/skills/ta/SKILL.md +179 -0
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback } from 'react';
3
+ import { streamStrategy } from '../../ai-generate.js';
4
+ import { StreamingGenerationStep } from './StreamingGenerationStep.js';
5
+ export function StrategyStep({ providerId, apiKey, agentName, bio, personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes, onComplete, }) {
6
+ const createStream = useCallback((feedback) => streamStrategy(providerId, apiKey, agentName, bio, personality, tone, voiceStyle, tradingStyle, sectors, sentiment, timeframes, feedback), [
7
+ providerId,
8
+ apiKey,
9
+ agentName,
10
+ bio,
11
+ personality,
12
+ tone,
13
+ voiceStyle,
14
+ tradingStyle,
15
+ sectors,
16
+ sentiment,
17
+ timeframes,
18
+ ]);
19
+ return (_jsx(StreamingGenerationStep, { title: "STRATEGY.md", createStream: createStream, onComplete: onComplete }));
20
+ }
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useCallback } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { StreamingText } from '../../../../components/StreamingText.js';
5
+ import { TextPrompt } from '../../../../components/TextPrompt.js';
6
+ import { CodeBlock } from '../../../../components/CodeBlock.js';
7
+ import { Spinner } from '../../../../components/Spinner.js';
8
+ import { colors, symbols } from '../../../shared/theme.js';
9
+ export function StreamingGenerationStep({ title, createStream, onComplete, }) {
10
+ const [phase, setPhase] = useState('streaming');
11
+ const [draft, setDraft] = useState('');
12
+ const [errorMessage, setErrorMessage] = useState('');
13
+ const [feedbackCount, setFeedbackCount] = useState(0);
14
+ const [currentStream, setCurrentStream] = useState(() => createStream());
15
+ const handleStreamComplete = useCallback((fullText) => {
16
+ const trimmed = fullText.trim();
17
+ if (trimmed.length === 0) {
18
+ setErrorMessage('LLM returned empty content. Try regenerating.');
19
+ setPhase('error');
20
+ return;
21
+ }
22
+ setDraft(trimmed);
23
+ setPhase('review');
24
+ }, []);
25
+ const handleStreamError = useCallback((error) => {
26
+ setErrorMessage(error);
27
+ setPhase('error');
28
+ }, []);
29
+ const handleAccept = useCallback(() => {
30
+ onComplete(draft);
31
+ }, [draft, onComplete]);
32
+ const handleRetry = useCallback(() => {
33
+ setFeedbackCount((prev) => prev + 1);
34
+ setPhase('streaming');
35
+ setDraft('');
36
+ setErrorMessage('');
37
+ const newStream = createStream();
38
+ setCurrentStream(newStream);
39
+ }, [createStream]);
40
+ const handleFeedback = useCallback((feedback) => {
41
+ setFeedbackCount((prev) => prev + 1);
42
+ setPhase('streaming');
43
+ setDraft('');
44
+ setErrorMessage('');
45
+ const newStream = createStream(feedback);
46
+ setCurrentStream(newStream);
47
+ }, [createStream]);
48
+ return (_jsxs(Box, { flexDirection: "column", children: [phase === 'streaming' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Spinner, { label: feedbackCount > 0 ? `Regenerating ${title}...` : `Generating ${title}...` }) }), _jsx(StreamingText, { stream: currentStream, title: title, onComplete: handleStreamComplete, onError: handleStreamError })] })), phase === 'error' && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.red, children: [symbols.cross, " "] }), _jsxs(Text, { color: colors.white, children: ["Failed to generate ", title] })] }), _jsx(Box, { marginLeft: 2, marginBottom: 1, children: _jsx(Text, { color: colors.red, children: errorMessage }) }), _jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: colors.gray, children: ["Press", ' ', _jsx(Text, { color: colors.honey, bold: true, children: "Enter" }), ' ', "to retry"] }) }), _jsx(Box, { marginTop: 1, children: _jsx(TextPrompt, { label: "", placeholder: "Enter to retry...", onSubmit: () => handleRetry() }) })] })), phase === 'review' && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: colors.green, children: [symbols.check, " "] }), _jsxs(Text, { color: colors.white, children: [title, " draft ready"] })] }), _jsx(CodeBlock, { title: title, children: draft }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.gray, children: ["Press", ' ', _jsx(Text, { color: colors.honey, bold: true, children: "Enter" }), ' ', "to accept ", symbols.dot, " Type feedback to regenerate"] }) }), _jsx(Box, { marginTop: 1, children: _jsx(TextPrompt, { label: "", placeholder: "Enter to accept, or type feedback...", onSubmit: (val) => {
49
+ if (!val) {
50
+ handleAccept();
51
+ }
52
+ else {
53
+ handleFeedback(val);
54
+ }
55
+ } }) })] }))] }));
56
+ }
@@ -0,0 +1,34 @@
1
+ export function required(fieldName) {
2
+ return (val) => (val ? true : `${fieldName} is required`);
3
+ }
4
+ export function maxLength(max) {
5
+ return (val) => val.length > max ? `Must be ${max} characters or less (currently ${val.length})` : true;
6
+ }
7
+ export function agentName(val) {
8
+ if (!val)
9
+ return 'Agent name is required';
10
+ if (val.length < 3)
11
+ return 'Agent name must be at least 3 characters';
12
+ if (val.length > 20)
13
+ return 'Agent name must be 20 characters or less';
14
+ if (!/^[a-zA-Z0-9-_]+$/.test(val))
15
+ return 'Only letters, numbers, hyphens, and underscores allowed';
16
+ return true;
17
+ }
18
+ export function apiKey(val) {
19
+ if (!val)
20
+ return 'API key is required';
21
+ if (val.length < 10)
22
+ return 'API key seems too short';
23
+ return true;
24
+ }
25
+ export function compose(...fns) {
26
+ return (val) => {
27
+ for (const fn of fns) {
28
+ const result = fn(val);
29
+ if (result !== true)
30
+ return result;
31
+ }
32
+ return true;
33
+ };
34
+ }
@@ -0,0 +1,27 @@
1
+ import { generateText } from 'ai';
2
+ import { buildLanguageModel } from '../../shared/config/ai-providers.js';
3
+ import { extractErrorMessage } from '../../shared/agent/utils.js';
4
+ /**
5
+ * Make a lightweight test call to validate the user's API key.
6
+ * Returns true if the key works, or an error message string on failure.
7
+ */
8
+ export async function validateApiKey(providerId, apiKey) {
9
+ try {
10
+ const model = buildLanguageModel(providerId, apiKey, 'validation');
11
+ await generateText({
12
+ model,
13
+ prompt: 'Say "ok"',
14
+ maxOutputTokens: 16,
15
+ });
16
+ return true;
17
+ }
18
+ catch (err) {
19
+ const message = extractErrorMessage(err);
20
+ if (message.includes('401') ||
21
+ message.includes('Unauthorized') ||
22
+ message.includes('invalid')) {
23
+ return 'Invalid API key. Please check and try again.';
24
+ }
25
+ return `API validation failed: ${message.slice(0, 120)}`;
26
+ }
27
+ }
@@ -0,0 +1,50 @@
1
+ import chalk from 'chalk';
2
+ import { exec } from 'child_process';
3
+ import { promisify } from 'util';
4
+ import fs from 'fs-extra';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ const execAsync = promisify(exec);
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ export async function install(targetDir = process.cwd(), options = {}) {
10
+ const packageJsonPath = path.join(targetDir, 'package.json');
11
+ if (!fs.existsSync(packageJsonPath)) {
12
+ throw new Error('No package.json found. Please run this command in the root of your project.');
13
+ }
14
+ console.log(chalk.blue('Installing Hive bootstrap...'));
15
+ const dir = path.join(targetDir, 'hive');
16
+ await fs.ensureDir(dir);
17
+ const templatesDir = path.resolve(__dirname, '../../templates/hive');
18
+ if (!fs.existsSync(templatesDir)) {
19
+ throw new Error(`Templates directory not found at ${templatesDir}`);
20
+ }
21
+ await fs.copy(templatesDir, dir);
22
+ console.log(chalk.green('\u2713 Bootstrap code copied to hive/'));
23
+ // Hive dependencies: @hive-org/sdk (and dotenv if missing)
24
+ console.log(chalk.blue('Installing dependencies...'));
25
+ try {
26
+ const packageJson = await fs.readJson(packageJsonPath);
27
+ const deps = packageJson.dependencies ?? {};
28
+ const hasSdk = deps['@hive-org/sdk'];
29
+ const hasDotenv = deps['dotenv'];
30
+ const toInstall = [];
31
+ if (!hasSdk) {
32
+ toInstall.push('@hive-org/sdk@*');
33
+ }
34
+ if (!hasDotenv) {
35
+ toInstall.push('dotenv');
36
+ }
37
+ if (toInstall.length > 0) {
38
+ await execAsync(`pnpm add ${toInstall.join(' ')}`, { cwd: targetDir });
39
+ console.log(chalk.green('\u2713 Dependencies installed'));
40
+ }
41
+ else {
42
+ console.log(chalk.gray('Dependencies already installed.'));
43
+ }
44
+ }
45
+ catch (e) {
46
+ const message = e instanceof Error ? e.message : String(e);
47
+ console.error(chalk.yellow("Warning: Failed to install dependencies automatically. Please run 'pnpm add @hive-org/sdk dotenv' manually."));
48
+ }
49
+ console.log(chalk.green('\nHive installed successfully!'));
50
+ }
@@ -0,0 +1,7 @@
1
+ import { render } from 'ink';
2
+ import React from 'react';
3
+ import { ListApp } from '../ui/ListApp.js';
4
+ export const listCommand = async () => {
5
+ const { waitUntilExit } = render(React.createElement(ListApp));
6
+ await waitUntilExit();
7
+ };
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text, useApp } from 'ink';
4
+ import { colors, symbols, border } from '../../shared/theme.js';
5
+ import { scanAgents, fetchBulkStats, sortByHoney, } from '../../../shared/config/agent.js';
6
+ const COL = {
7
+ name: 0,
8
+ honey: 8,
9
+ wax: 8,
10
+ winRate: 10,
11
+ confidence: 8,
12
+ provider: 0,
13
+ created: 14,
14
+ };
15
+ function cell(text, width) {
16
+ return ` ${text}`.padEnd(width);
17
+ }
18
+ function formatDate(date) {
19
+ const formatted = date.toLocaleDateString('en-US', {
20
+ year: 'numeric',
21
+ month: 'short',
22
+ day: 'numeric',
23
+ });
24
+ return formatted;
25
+ }
26
+ export function ListApp() {
27
+ const { exit } = useApp();
28
+ const [rows, setRows] = useState(null);
29
+ useEffect(() => {
30
+ const load = async () => {
31
+ const agents = await scanAgents();
32
+ if (agents.length === 0) {
33
+ setRows([]);
34
+ return;
35
+ }
36
+ const names = agents.map((a) => a.name);
37
+ const statsMap = await fetchBulkStats(names);
38
+ const agentRows = agents.map((info) => ({
39
+ info,
40
+ stats: statsMap.get(info.name) ?? null,
41
+ }));
42
+ const sortedRows = sortByHoney(agentRows);
43
+ setRows(sortedRows);
44
+ };
45
+ void load();
46
+ }, []);
47
+ useEffect(() => {
48
+ if (rows !== null) {
49
+ exit();
50
+ }
51
+ }, [rows]);
52
+ if (rows === null) {
53
+ return (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: colors.gray, children: "Scanning agents..." }) }));
54
+ }
55
+ if (rows.length === 0) {
56
+ 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: "No agents found" })] }), _jsxs(Text, { color: colors.gray, children: ["Create one with: ", _jsx(Text, { color: colors.white, children: "npx @zhive/cli@latest create" })] })] }));
57
+ }
58
+ const nameW = Math.max(COL.name, ...rows.map((r) => r.info.name.length)) + 2;
59
+ const providerW = Math.max(COL.provider, ...rows.map((r) => r.info.provider.length)) + 2;
60
+ const honeyW = COL.honey;
61
+ const waxW = COL.wax;
62
+ const winRateW = COL.winRate;
63
+ const confidenceW = COL.confidence;
64
+ const createdW = COL.created;
65
+ const sep = border.horizontal;
66
+ const totalWidth = nameW + 1 + honeyW + 1 + waxW + 1 + winRateW + 1 + confidenceW + 1 + providerW + 1 + createdW;
67
+ const topBorder = `${border.topLeft}${sep.repeat(totalWidth)}${border.topRight}`;
68
+ const midBorder = `${border.teeLeft}${sep.repeat(totalWidth)}${border.teeRight}`;
69
+ const botBorder = `${border.bottomLeft}${sep.repeat(totalWidth)}${border.bottomRight}`;
70
+ const v = border.vertical;
71
+ 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: "Your Hive Agents" }), _jsxs(Text, { color: colors.grayDim, children: [" (", rows.length, ")"] })] }), _jsx(Box, { children: _jsx(Text, { color: colors.honey, children: topBorder }) }), _jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Name', nameW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Honey', honeyW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Wax', waxW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Win Rate', winRateW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Conf', confidenceW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Provider', providerW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, bold: true, children: cell('Created', createdW) }), _jsx(Text, { color: colors.honey, children: v })] }), _jsx(Box, { children: _jsx(Text, { color: colors.honey, children: midBorder }) }), rows.map((row) => {
72
+ const s = row.stats;
73
+ const honeyText = s !== null ? String(Math.floor(s.honey)) : '-';
74
+ const waxText = s !== null ? String(Math.floor(s.wax)) : '-';
75
+ const winRateText = s !== null ? `${(s.win_rate * 100).toFixed(2)}%` : '-';
76
+ const confidenceText = s !== null ? s.confidence.toFixed(2) : '-';
77
+ return (_jsxs(Box, { children: [_jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.white, children: cell(row.info.name, nameW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.honey, children: cell(honeyText, honeyW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.wax, children: cell(waxText, waxW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.green, children: cell(winRateText, winRateW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.cyan, children: cell(confidenceText, confidenceW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.gray, children: cell(row.info.provider, providerW) }), _jsx(Text, { color: colors.honey, children: v }), _jsx(Text, { color: colors.grayDim, children: cell(formatDate(row.info.created), createdW) }), _jsx(Text, { color: colors.honey, children: v })] }, row.info.name));
78
+ }), _jsx(Box, { children: _jsx(Text, { color: colors.honey, children: botBorder }) })] }));
79
+ }
@@ -0,0 +1,9 @@
1
+ import { render } from 'ink';
2
+ import React from 'react';
3
+ import { showWelcome } from '../../shared/welcome.js';
4
+ import { MigrateApp } from '../ui/MigrateApp.js';
5
+ export const migrateTemplateCommand = async () => {
6
+ await showWelcome();
7
+ const { waitUntilExit } = render(React.createElement(MigrateApp));
8
+ await waitUntilExit();
9
+ };
@@ -0,0 +1,87 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { extractErrorMessage } from '../../shared/agent/utils.js';
4
+ /** Files scaffolded by the old `hive create` that are now in @zhive/cli */
5
+ const OLD_FILES = [
6
+ 'index.tsx',
7
+ 'analysis.ts',
8
+ 'prompt.ts',
9
+ 'chat-prompt.ts',
10
+ 'memory-prompt.ts',
11
+ 'edit-section.ts',
12
+ 'fetch-rules.ts',
13
+ 'helpers.ts',
14
+ 'theme.ts',
15
+ 'types.ts',
16
+ 'process-lifecycle.ts',
17
+ 'tsconfig.json',
18
+ ];
19
+ /** Directories scaffolded by the old `hive create` */
20
+ const OLD_DIRS = ['hooks', 'components', 'hive'];
21
+ export function isOldStyleAgent(agentDir) {
22
+ const indexPath = path.join(agentDir, 'index.tsx');
23
+ return fs.pathExistsSync(indexPath);
24
+ }
25
+ export async function migrateAgent(agentDir, name, onStep) {
26
+ try {
27
+ // 1. Verify it's a valid agent
28
+ const soulPath = path.join(agentDir, 'SOUL.md');
29
+ const soulExists = await fs.pathExists(soulPath);
30
+ if (!soulExists) {
31
+ return { name, success: false, error: 'No SOUL.md found — not a valid agent' };
32
+ }
33
+ // 2. Delete old scaffolded files
34
+ onStep('Removing old runtime files');
35
+ for (const file of OLD_FILES) {
36
+ const filePath = path.join(agentDir, file);
37
+ const exists = await fs.pathExists(filePath);
38
+ if (exists) {
39
+ await fs.remove(filePath);
40
+ }
41
+ }
42
+ // 3. Delete old directories
43
+ onStep('Removing old directories');
44
+ for (const dir of OLD_DIRS) {
45
+ const dirPath = path.join(agentDir, dir);
46
+ const exists = await fs.pathExists(dirPath);
47
+ if (exists) {
48
+ await fs.remove(dirPath);
49
+ }
50
+ }
51
+ // 4. Rewrite package.json
52
+ onStep('Rewriting package.json');
53
+ const pkgPath = path.join(agentDir, 'package.json');
54
+ const pkgExists = await fs.pathExists(pkgPath);
55
+ let pkgName = `hive-agent-${name}`;
56
+ if (pkgExists) {
57
+ const oldPkg = await fs.readJson(pkgPath);
58
+ pkgName = oldPkg.name ?? pkgName;
59
+ }
60
+ const newPkg = {
61
+ name: pkgName,
62
+ private: true,
63
+ type: 'module',
64
+ scripts: {
65
+ start: 'npx @zhive/cli@latest start',
66
+ },
67
+ };
68
+ await fs.writeJson(pkgPath, newPkg, { spaces: 2 });
69
+ // 5. Remove old node_modules and lock files (no longer needed)
70
+ onStep('Cleaning up old dependencies');
71
+ const nodeModulesPath = path.join(agentDir, 'node_modules');
72
+ const nodeModulesExists = await fs.pathExists(nodeModulesPath);
73
+ if (nodeModulesExists) {
74
+ await fs.remove(nodeModulesPath);
75
+ }
76
+ const lockPath = path.join(agentDir, 'package-lock.json');
77
+ const lockExists = await fs.pathExists(lockPath);
78
+ if (lockExists) {
79
+ await fs.remove(lockPath);
80
+ }
81
+ return { name, success: true };
82
+ }
83
+ catch (err) {
84
+ const message = extractErrorMessage(err);
85
+ return { name, success: false, error: message.slice(0, 200) };
86
+ }
87
+ }
@@ -0,0 +1,132 @@
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 '../../../shared/config/agent.js';
5
+ import { colors, symbols, styled, border } from '../../shared/theme.js';
6
+ import { isOldStyleAgent, migrateAgent } from '../migrate.js';
7
+ import { extractErrorMessage } from '../../../shared/agent/utils.js';
8
+ export function MigrateApp() {
9
+ const { exit } = useApp();
10
+ const [phase, setPhase] = useState('scanning');
11
+ const [agents, setAgents] = useState([]);
12
+ const [cursor, setCursor] = useState(0);
13
+ const [results, setResults] = useState([]);
14
+ const [currentStep, setCurrentStep] = useState('');
15
+ const [currentAgent, setCurrentAgent] = useState('');
16
+ // ─── Scan phase ────────────────────────────────────
17
+ useEffect(() => {
18
+ const scan = async () => {
19
+ const discovered = await scanAgents();
20
+ if (discovered.length === 0) {
21
+ setPhase('done');
22
+ return;
23
+ }
24
+ const selectableAgents = discovered.map((info) => {
25
+ const oldStyle = isOldStyleAgent(info.dir);
26
+ return {
27
+ info,
28
+ selected: oldStyle,
29
+ isOldStyle: oldStyle,
30
+ };
31
+ });
32
+ const hasOldStyle = selectableAgents.some((a) => a.isOldStyle);
33
+ if (!hasOldStyle) {
34
+ setResults(selectableAgents.map((a) => ({
35
+ name: a.info.name,
36
+ success: true,
37
+ error: 'Already migrated',
38
+ })));
39
+ setPhase('done');
40
+ return;
41
+ }
42
+ setAgents(selectableAgents);
43
+ setPhase('selecting');
44
+ };
45
+ scan().catch((err) => {
46
+ const message = extractErrorMessage(err);
47
+ setResults([{ name: 'scan', success: false, error: message }]);
48
+ setPhase('done');
49
+ });
50
+ }, []);
51
+ // ─── Keyboard input (selecting phase) ──────────────
52
+ useInput((input, key) => {
53
+ if (phase !== 'selecting')
54
+ return;
55
+ const oldStyleAgents = agents.filter((a) => a.isOldStyle);
56
+ if (key.upArrow) {
57
+ setCursor((prev) => Math.max(0, prev - 1));
58
+ }
59
+ else if (key.downArrow) {
60
+ setCursor((prev) => Math.min(oldStyleAgents.length - 1, prev + 1));
61
+ }
62
+ else if (input === ' ') {
63
+ // Toggle selection
64
+ const targetName = oldStyleAgents[cursor]?.info.name;
65
+ if (targetName) {
66
+ setAgents((prev) => prev.map((a) => (a.info.name === targetName ? { ...a, selected: !a.selected } : a)));
67
+ }
68
+ }
69
+ else if (key.return) {
70
+ const selected = agents.filter((a) => a.selected && a.isOldStyle);
71
+ if (selected.length > 0) {
72
+ setPhase('migrating');
73
+ runMigrations(selected);
74
+ }
75
+ }
76
+ else if (input === 'q' || key.escape) {
77
+ exit();
78
+ }
79
+ }, { isActive: phase === 'selecting' });
80
+ // ─── Migrate phase ─────────────────────────────────
81
+ const runMigrations = useCallback(async (selected) => {
82
+ const migrateResults = [];
83
+ for (const agent of selected) {
84
+ setCurrentAgent(agent.info.name);
85
+ setCurrentStep('Starting migration');
86
+ const result = await migrateAgent(agent.info.dir, agent.info.name, (step) => {
87
+ setCurrentStep(step);
88
+ });
89
+ migrateResults.push(result);
90
+ setResults([...migrateResults]);
91
+ }
92
+ setCurrentAgent('');
93
+ setCurrentStep('');
94
+ setPhase('done');
95
+ }, []);
96
+ // ─── Done phase — exit after a short delay ─────────
97
+ useEffect(() => {
98
+ if (phase === 'done') {
99
+ const timer = setTimeout(() => {
100
+ exit();
101
+ }, 1500);
102
+ return () => {
103
+ clearTimeout(timer);
104
+ };
105
+ }
106
+ }, [phase, exit]);
107
+ // ─── Render ────────────────────────────────────────
108
+ const termWidth = process.stdout.columns || 60;
109
+ if (phase === 'scanning') {
110
+ return (_jsx(Box, { flexDirection: "column", paddingLeft: 1, children: _jsxs(Text, { color: colors.honey, children: [symbols.hive, " Scanning agents..."] }) }));
111
+ }
112
+ if (phase === 'selecting') {
113
+ const oldStyleAgents = agents.filter((a) => a.isOldStyle);
114
+ const newStyleAgents = agents.filter((a) => !a.isOldStyle);
115
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsxs(Text, { color: colors.honey, bold: true, children: [symbols.hive, " Migrate agents to @zhive/cli"] }), _jsx(Text, { color: colors.gray, children: border.horizontal.repeat(termWidth - 4) }), _jsxs(Text, { color: colors.gray, children: ["Use ", styled.white('↑↓'), " to navigate, ", styled.white('space'), " to toggle,", ' ', styled.white('enter'), " to confirm"] }), _jsx(Text, { children: " " }), oldStyleAgents.map((agent, i) => {
116
+ const isCursor = i === cursor;
117
+ const prefix = agent.selected ? symbols.check : symbols.diamondOpen;
118
+ const prefixColor = agent.selected ? colors.green : colors.gray;
119
+ const nameColor = isCursor ? colors.white : colors.gray;
120
+ const cursorChar = isCursor ? symbols.arrow : ' ';
121
+ 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: colors.gray, children: [" (", agent.info.provider, ")"] })] }, agent.info.name));
122
+ }), newStyleAgents.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: colors.gray, children: "Already migrated:" }), newStyleAgents.map((agent) => (_jsx(Box, { children: _jsxs(Text, { color: colors.gray, children: [' ', symbols.check, " ", agent.info.name] }) }, agent.info.name)))] })), _jsx(Text, { children: " " }), _jsx(Text, { color: colors.gray, children: styled.dim('q/esc to cancel') })] }));
123
+ }
124
+ if (phase === 'migrating') {
125
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsxs(Text, { color: colors.honey, bold: true, children: [symbols.hive, " Migrating agents..."] }), _jsx(Text, { color: colors.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] }) }))] }));
126
+ }
127
+ // phase === 'done'
128
+ const successCount = results.filter((r) => r.success && !r.error).length;
129
+ const alreadyNew = results.filter((r) => r.error === 'Already migrated').length;
130
+ const failCount = results.filter((r) => !r.success).length;
131
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsxs(Text, { color: colors.honey, bold: true, children: [symbols.hive, " Migration complete"] }), _jsx(Text, { color: colors.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: colors.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: colors.gray, children: "No agents found in ~/.hive/agents/" })), _jsx(Text, { children: " " }), successCount > 0 && (_jsxs(Text, { color: colors.gray, children: ["Agents now run via @zhive/cli. ", styled.white('npx @zhive/cli@latest start'), " always uses the latest version."] }))] }));
132
+ }
@@ -0,0 +1,17 @@
1
+ import { access } from 'fs/promises';
2
+ import { join } from 'path';
3
+ import { runHeadless } from '../run-headless.js';
4
+ import { loadAgentEnv } from '../../../shared/config/env-loader.js';
5
+ export const runCommand = async () => {
6
+ // Headless agent run — no TUI, just console output.
7
+ // Used by start-all to spawn agents as child processes.
8
+ const isAgentDir = await access(join(process.cwd(), 'SOUL.md'))
9
+ .then(() => true)
10
+ .catch(() => false);
11
+ if (!isAgentDir) {
12
+ console.error('Error: "run" must be called from an agent directory (with SOUL.md)');
13
+ process.exit(1);
14
+ }
15
+ await loadAgentEnv();
16
+ await runHeadless();
17
+ };
@@ -0,0 +1,111 @@
1
+ import { HiveAgent } from '@zhive/sdk';
2
+ import { initializeAgentRuntime, createMegathreadRoundHandler, } from '../../shared/agent/agent-runtime.js';
3
+ import { HIVE_API_URL, HIVE_FRONTEND_URL } from '../../shared/config/constant.js';
4
+ import { resolveModelInfo } from '../../shared/config/ai-providers.js';
5
+ import { formatTokenCount, formatTokenUsage } from '../../shared/agent/utils.js';
6
+ function formatUsageLine(usage) {
7
+ const { input, output, tools } = formatTokenUsage(usage);
8
+ const toolSuffix = tools !== null ? ` \u00b7 ${tools}` : '';
9
+ const line = ` ${input} \u00b7 ${output}${toolSuffix}`;
10
+ return line;
11
+ }
12
+ function timestamp() {
13
+ const now = new Date();
14
+ const hours = String(now.getHours()).padStart(2, '0');
15
+ const minutes = String(now.getMinutes()).padStart(2, '0');
16
+ const seconds = String(now.getSeconds()).padStart(2, '0');
17
+ const ts = `${hours}:${minutes}:${seconds}`;
18
+ return ts;
19
+ }
20
+ export async function runHeadless() {
21
+ const runtime = await initializeAgentRuntime();
22
+ const { config } = runtime;
23
+ let predictionCount = 0;
24
+ let totalInputTokens = 0;
25
+ let totalOutputTokens = 0;
26
+ let totalToolCalls = 0;
27
+ const consoleReporter = {
28
+ onRoundStart(round, timeframe) {
29
+ console.log(`[${timestamp()}] megathread c/${round.projectId} · ${timeframe} round`);
30
+ },
31
+ onPriceInfo(priceAtStart, currentPrice) {
32
+ if (currentPrice !== undefined) {
33
+ const changePercent = ((currentPrice - priceAtStart) / priceAtStart) * 100;
34
+ const sign = changePercent >= 0 ? '+' : '';
35
+ console.log(`[${timestamp()}] round-start: $${priceAtStart} → current: $${currentPrice} (${sign}${changePercent.toFixed(2)}%)`);
36
+ }
37
+ else {
38
+ console.log(`[${timestamp()}] round-start price: $${priceAtStart}`);
39
+ }
40
+ },
41
+ onScreenResult(round, result) {
42
+ const verdict = result.engage ? 'engage' : 'skip';
43
+ console.log(`[${timestamp()}] screen c/${round.projectId}: ${verdict} — ${result.reason}`);
44
+ totalInputTokens += result.usage.inputTokens;
45
+ totalOutputTokens += result.usage.outputTokens;
46
+ },
47
+ onResearching(projectId) {
48
+ console.log(`[${timestamp()}] researching c/${projectId}...`);
49
+ },
50
+ onToolsUsed(toolNames, callCount) {
51
+ if (callCount > 0) {
52
+ console.log(` tools: ${toolNames.join(', ')} (${callCount} calls)`);
53
+ }
54
+ else {
55
+ console.log(` tools: none`);
56
+ }
57
+ },
58
+ onSkipped(round, usage) {
59
+ totalInputTokens += usage.inputTokens;
60
+ totalOutputTokens += usage.outputTokens;
61
+ totalToolCalls += usage.toolCalls;
62
+ console.log(`[${timestamp()}] skipped c/${round.projectId} — outside expertise`);
63
+ console.log(formatUsageLine(usage));
64
+ },
65
+ onPosted(round, conviction, summary, timeframe, usage) {
66
+ predictionCount += 1;
67
+ totalInputTokens += usage.inputTokens;
68
+ totalOutputTokens += usage.outputTokens;
69
+ totalToolCalls += usage.toolCalls;
70
+ const sign = conviction >= 0 ? '+' : '';
71
+ console.log(`[${timestamp()}] megathread c/${round.projectId} [${sign}${conviction}%] "${summary}" (${predictionCount} total)`);
72
+ console.log(formatUsageLine(usage));
73
+ const url = `${HIVE_FRONTEND_URL}/c/${round.projectId}/megathread/${timeframe}`;
74
+ console.log(` ${url}`);
75
+ },
76
+ onError(round, message) {
77
+ console.error(`[${timestamp()}] error c/${round.projectId}: ${message}`);
78
+ },
79
+ };
80
+ // eslint-disable-next-line prefer-const
81
+ let agent;
82
+ const roundHandler = createMegathreadRoundHandler(() => agent, runtime, consoleReporter);
83
+ agent = new HiveAgent(HIVE_API_URL, {
84
+ name: config.name,
85
+ avatarUrl: config.avatarUrl,
86
+ bio: config.bio ?? undefined,
87
+ agentProfile: config.agentProfile,
88
+ onPollEmpty: () => {
89
+ console.log(`[${timestamp()}] idle — no new rounds`);
90
+ },
91
+ onNewMegathreadRound: roundHandler,
92
+ });
93
+ const shutdown = async () => {
94
+ console.log(`[${config.name}] shutting down...`);
95
+ const sessionTotal = totalInputTokens + totalOutputTokens;
96
+ const toolInfo = totalToolCalls > 0 ? ` \u00b7 ${totalToolCalls} tool calls` : '';
97
+ console.log(`[${config.name}] session — input: ${formatTokenCount(totalInputTokens)} \u00b7 output: ${formatTokenCount(totalOutputTokens)} (${formatTokenCount(sessionTotal)} total)${toolInfo}`);
98
+ await agent.stop();
99
+ process.exit(0);
100
+ };
101
+ process.on('SIGINT', () => void shutdown());
102
+ process.on('SIGTERM', () => void shutdown());
103
+ await agent.start();
104
+ const modelInfoResult = resolveModelInfo();
105
+ console.log(`[${config.name}] ${modelInfoResult.modelId} \u00d7 zData`);
106
+ const { agentProfile } = config;
107
+ const sectorsDisplay = agentProfile.sectors.length > 0 ? agentProfile.sectors.join(', ') : 'all';
108
+ const timeframesDisplay = agentProfile.timeframes.join(', ');
109
+ console.log(`[${config.name}] sectors: ${sectorsDisplay} \u00b7 timeframes: ${timeframesDisplay}`);
110
+ console.log(`[${config.name}] agent online \u2014 "${config.bio ?? ''}"`);
111
+ }