@zhive/cli 0.6.7 → 0.6.8

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 (66) hide show
  1. package/dist/CLAUDE.md +7 -0
  2. package/dist/backtest/CLAUDE.md +7 -0
  3. package/dist/cli.js +20 -0
  4. package/dist/commands/agent/commands/profile.js +3 -9
  5. package/dist/commands/create/presets.js +613 -0
  6. package/dist/commands/indicator/commands/bollinger.js +37 -0
  7. package/dist/commands/indicator/commands/ema.js +37 -0
  8. package/dist/commands/indicator/commands/index.js +14 -0
  9. package/dist/commands/indicator/commands/macd.js +51 -0
  10. package/dist/commands/indicator/commands/rsi.js +37 -0
  11. package/dist/commands/indicator/commands/sma.js +37 -0
  12. package/dist/commands/market/commands/index.js +5 -0
  13. package/dist/commands/market/commands/price.js +25 -0
  14. package/dist/commands/shared/utils.js +12 -0
  15. package/dist/commands/start/ui/AsciiTicker.js +81 -0
  16. package/dist/index.js +4 -0
  17. package/dist/services/agent/analysis.js +160 -0
  18. package/dist/services/agent/config.js +75 -0
  19. package/dist/services/agent/env.js +30 -0
  20. package/dist/services/agent/helpers/model.js +92 -0
  21. package/dist/services/agent/helpers.js +22 -0
  22. package/dist/services/agent/prompts/chat-prompt.js +65 -0
  23. package/dist/services/agent/prompts/memory-prompt.js +45 -0
  24. package/dist/services/agent/prompts/prompt.js +379 -0
  25. package/dist/services/agent/skills/index.js +2 -0
  26. package/dist/services/agent/skills/skill-parser.js +149 -0
  27. package/dist/services/agent/skills/types.js +1 -0
  28. package/dist/services/agent/tools/edit-section.js +59 -0
  29. package/dist/services/agent/tools/fetch-rules.js +21 -0
  30. package/dist/services/agent/tools/index.js +76 -0
  31. package/dist/services/agent/tools/market/client.js +41 -0
  32. package/dist/services/agent/tools/market/index.js +3 -0
  33. package/dist/services/agent/tools/market/tools.js +518 -0
  34. package/dist/services/agent/tools/mindshare/client.js +124 -0
  35. package/dist/services/agent/tools/mindshare/index.js +3 -0
  36. package/dist/services/agent/tools/mindshare/tools.js +563 -0
  37. package/dist/services/agent/tools/read-skill-tool.js +30 -0
  38. package/dist/services/agent/tools/ta/index.js +1 -0
  39. package/dist/services/agent/tools/ta/indicators.js +201 -0
  40. package/dist/services/agent/types.js +1 -0
  41. package/dist/services/ai-providers.js +66 -0
  42. package/dist/services/config/agent.js +110 -0
  43. package/dist/services/config/config.js +22 -0
  44. package/dist/services/config/constant.js +8 -0
  45. package/dist/shared/agent/agent-runtime.js +144 -0
  46. package/dist/shared/agent/analysis.js +2 -12
  47. package/dist/shared/agent/cache.js +10 -0
  48. package/dist/shared/agent/config.js +75 -0
  49. package/dist/shared/agent/env.js +30 -0
  50. package/dist/shared/agent/handler.js +3 -9
  51. package/dist/shared/agent/helpers/model.js +92 -0
  52. package/dist/shared/agent/prompts/megathread.js +0 -8
  53. package/dist/shared/agent/tools/execute-skill-tool.js +2 -1
  54. package/dist/shared/agent/tools/formatting.js +0 -19
  55. package/dist/shared/agent/tools/market/client.js +3 -3
  56. package/dist/shared/agent/tools/market/tools.js +88 -312
  57. package/dist/shared/agent/tools/market/utils.js +71 -0
  58. package/dist/shared/agent/tools/mindshare/tools.js +1 -1
  59. package/dist/shared/agent/tools/ta/index.js +3 -1
  60. package/dist/shared/agent/types.js +1 -0
  61. package/dist/shared/agent/utils.js +44 -0
  62. package/dist/shared/ai-providers.js +66 -0
  63. package/dist/shared/ta/error.js +12 -0
  64. package/dist/shared/ta/service.js +93 -0
  65. package/dist/shared/ta/utils.js +16 -0
  66. package/package.json +2 -1
@@ -0,0 +1,37 @@
1
+ import { Command } from 'commander';
2
+ import { InsufficientDataError } from '../../../shared/ta/error.js';
3
+ import { getBollingerBands } from '../../../shared/ta/service.js';
4
+ import { styled } from '../../shared/theme.js';
5
+ export const createBollingerCommand = () => {
6
+ return new Command('bollinger')
7
+ .description(`Compute bollinger bands of given project's price`)
8
+ .requiredOption('--project <project>', 'Project id')
9
+ .option('--period <period>', 'bollinger bands period. Defaults to 20')
10
+ .option('--interval <interval>', 'ohlc interval. valid options are [hourly, daily]')
11
+ .action(async (options) => {
12
+ const { project, period: periodStr = '20', interval = 'hourly' } = options;
13
+ let period = Number(periodStr);
14
+ if (Number.isNaN(period)) {
15
+ console.log(styled.white(`Invalid period ${periodStr}. override to 20`));
16
+ period = 20;
17
+ }
18
+ try {
19
+ const bbResult = await getBollingerBands({
20
+ project,
21
+ interval,
22
+ period,
23
+ from: new Date(),
24
+ to: new Date(),
25
+ });
26
+ const last = bbResult.at(-1) ?? {};
27
+ console.log(styled.white(JSON.stringify(last)));
28
+ }
29
+ catch (e) {
30
+ if (e instanceof InsufficientDataError) {
31
+ console.log(styled.red(`Insufficient data: got ${e.got} data points but need at least ${e.required} for Bollinger Bands(${period}).`));
32
+ return;
33
+ }
34
+ console.log(styled.red(`Failed to fetch bollinger bands value`));
35
+ }
36
+ });
37
+ };
@@ -0,0 +1,37 @@
1
+ import { Command } from 'commander';
2
+ import { InsufficientDataError } from '../../../shared/ta/error.js';
3
+ import { getEMA } from '../../../shared/ta/service.js';
4
+ import { styled } from '../../shared/theme.js';
5
+ export const createEmaCommand = () => {
6
+ return new Command('ema')
7
+ .description(`Compute ema of given project's price`)
8
+ .requiredOption('--project <project>', 'Project id')
9
+ .option('--period <period>', 'ema period. Defaults to 12')
10
+ .option('--interval <interval>', 'ohlc interval. valid options are [hourly, daily]')
11
+ .action(async (options) => {
12
+ const { project, period: periodStr = '12', interval = 'hourly' } = options;
13
+ let period = Number(periodStr);
14
+ if (Number.isNaN(period)) {
15
+ console.log(styled.white(`Invalid period ${periodStr}. override to 12`));
16
+ period = 12;
17
+ }
18
+ try {
19
+ const emaResult = await getEMA({
20
+ project,
21
+ interval,
22
+ period,
23
+ from: new Date(),
24
+ to: new Date(),
25
+ });
26
+ const last = emaResult?.at(-1) ?? {};
27
+ console.log(styled.white(JSON.stringify(last)));
28
+ }
29
+ catch (e) {
30
+ if (e instanceof InsufficientDataError) {
31
+ console.log(styled.red(`Insufficient data: got ${e.got} data points but need at least ${e.required} for EMA${period}.`));
32
+ return;
33
+ }
34
+ console.log(styled.red(`Failed to fetch ema value`));
35
+ }
36
+ });
37
+ };
@@ -0,0 +1,14 @@
1
+ import { Command } from 'commander';
2
+ import { createBollingerCommand } from './bollinger.js';
3
+ import { createEmaCommand } from './ema.js';
4
+ import { createMacdCommand } from './macd.js';
5
+ import { createRsiCommand } from './rsi.js';
6
+ import { createSmaCommand } from './sma.js';
7
+ export const createIndicatorCommand = () => {
8
+ return new Command('indicator')
9
+ .addCommand(createRsiCommand())
10
+ .addCommand(createSmaCommand())
11
+ .addCommand(createEmaCommand())
12
+ .addCommand(createMacdCommand())
13
+ .addCommand(createBollingerCommand());
14
+ };
@@ -0,0 +1,51 @@
1
+ import { Command } from 'commander';
2
+ import { InsufficientDataError } from '../../../shared/ta/error.js';
3
+ import { getMACD } from '../../../shared/ta/service.js';
4
+ import { styled } from '../../shared/theme.js';
5
+ export const createMacdCommand = () => {
6
+ return new Command('macd')
7
+ .description(`Compute macd of given project's price`)
8
+ .requiredOption('--project <project>', 'Project id')
9
+ .option('--fast <fast>', 'fast ema period. Defaults to 12')
10
+ .option('--slow <slow>', 'slow ema period. Defaults to 26')
11
+ .option('--signal <signal>', 'signal line period. Defaults to 9')
12
+ .option('--interval <interval>', 'ohlc interval. valid options are [hourly, daily]')
13
+ .action(async (options) => {
14
+ const { project, fast: fastStr = '12', slow: slowStr = '26', signal: signalStr = '9', interval = 'hourly', } = options;
15
+ let fast = Number(fastStr);
16
+ if (Number.isNaN(fast)) {
17
+ console.log(styled.white(`Invalid fast period ${fastStr}. override to 12`));
18
+ fast = 12;
19
+ }
20
+ let slow = Number(slowStr);
21
+ if (Number.isNaN(slow)) {
22
+ console.log(styled.white(`Invalid slow period ${slowStr}. override to 26`));
23
+ slow = 26;
24
+ }
25
+ let signal = Number(signalStr);
26
+ if (Number.isNaN(signal)) {
27
+ console.log(styled.white(`Invalid signal period ${signalStr}. override to 9`));
28
+ signal = 9;
29
+ }
30
+ try {
31
+ const macdResult = await getMACD({
32
+ project,
33
+ interval,
34
+ fast,
35
+ slow,
36
+ signal,
37
+ from: new Date(),
38
+ to: new Date(),
39
+ });
40
+ const last = macdResult?.at(-1) ?? {};
41
+ console.log(styled.white(JSON.stringify(last)));
42
+ }
43
+ catch (e) {
44
+ if (e instanceof InsufficientDataError) {
45
+ console.log(styled.red(`Insufficient data: got ${e.got} data points but need at least ${e.required} for MACD(${fast},${slow},${signal}).`));
46
+ return;
47
+ }
48
+ console.log(styled.red(`Failed to fetch macd value`));
49
+ }
50
+ });
51
+ };
@@ -0,0 +1,37 @@
1
+ import { Command } from 'commander';
2
+ import { InsufficientDataError } from '../../../shared/ta/error.js';
3
+ import { getRSI } from '../../../shared/ta/service.js';
4
+ import { styled } from '../../shared/theme.js';
5
+ export const createRsiCommand = () => {
6
+ return new Command('rsi')
7
+ .description(`Compute rsi of given project's price`)
8
+ .requiredOption('--project <project>', 'Project id')
9
+ .option('--period <period>', 'rsi period. Defaults to 14')
10
+ .option('--interval <interval>', 'ohlc interval. valid options are [hourly, daily]')
11
+ .action(async (options) => {
12
+ const { project, period: periodStr = '14', interval = 'hourly' } = options;
13
+ let period = Number(periodStr);
14
+ if (Number.isNaN(period)) {
15
+ console.log(styled.white(`Invalid period ${periodStr}. override to 14`));
16
+ period = 14;
17
+ }
18
+ try {
19
+ const rsi = await getRSI({
20
+ project,
21
+ interval,
22
+ period,
23
+ from: new Date(),
24
+ to: new Date(),
25
+ });
26
+ const last = rsi.at(-1) ?? {};
27
+ console.log(styled.white(JSON.stringify(last)));
28
+ }
29
+ catch (e) {
30
+ if (e instanceof InsufficientDataError) {
31
+ console.log(styled.red(`Insufficient data: got ${e.got} data points but need at least ${e.required} for RSI${period}.`));
32
+ return;
33
+ }
34
+ console.log(styled.red(`Failed to fetch rsi value`));
35
+ }
36
+ });
37
+ };
@@ -0,0 +1,37 @@
1
+ import { Command } from 'commander';
2
+ import { InsufficientDataError } from '../../../shared/ta/error.js';
3
+ import { getSMA } from '../../../shared/ta/service.js';
4
+ import { styled } from '../../shared/theme.js';
5
+ export const createSmaCommand = () => {
6
+ return new Command('sma')
7
+ .description(`Compute sma of given project's price`)
8
+ .requiredOption('--project <project>', 'Project id')
9
+ .option('--period <period>', 'sma period. Defaults to 20')
10
+ .option('--interval <interval>', 'ohlc interval. valid options are [hourly, daily]')
11
+ .action(async (options) => {
12
+ const { project, period: periodStr = '20', interval = 'hourly' } = options;
13
+ let period = Number(periodStr);
14
+ if (Number.isNaN(period)) {
15
+ console.log(styled.white(`Invalid period ${periodStr}. override to 20`));
16
+ period = 20;
17
+ }
18
+ try {
19
+ const sma = await getSMA({
20
+ project,
21
+ interval,
22
+ period,
23
+ from: new Date(),
24
+ to: new Date(),
25
+ });
26
+ const last = sma.at(-1) ?? {};
27
+ console.log(styled.white(JSON.stringify(last)));
28
+ }
29
+ catch (e) {
30
+ if (e instanceof InsufficientDataError) {
31
+ console.log(styled.red(`Insufficient data: got ${e.got} data points but need at least ${e.required} for SMA${period}.`));
32
+ return;
33
+ }
34
+ console.log(styled.red(`Failed to fetch sma value`));
35
+ }
36
+ });
37
+ };
@@ -0,0 +1,5 @@
1
+ import { Command } from 'commander';
2
+ import { createPriceCommand } from './price.js';
3
+ export const createMarketCommand = () => {
4
+ return new Command('market').addCommand(createPriceCommand());
5
+ };
@@ -0,0 +1,25 @@
1
+ import { Command } from 'commander';
2
+ import { getMarketClient } from '../../../shared/agent/tools/market/client.js';
3
+ import { styled } from '../../shared/theme.js';
4
+ export const createPriceCommand = () => {
5
+ return new Command('price')
6
+ .description(`Get current price of a given project`)
7
+ .requiredOption('--project <project>', 'Project id')
8
+ .action(async (options) => {
9
+ const { project } = options;
10
+ try {
11
+ const client = getMarketClient();
12
+ const priceData = await client.getPrice(project, new Date());
13
+ if (priceData.price === null) {
14
+ console.log(styled.white(JSON.stringify({})));
15
+ return;
16
+ }
17
+ const result = { price: priceData.price, timestamp: priceData.timestamp };
18
+ console.log(styled.white(JSON.stringify(result)));
19
+ }
20
+ catch (e) {
21
+ const message = e instanceof Error ? e.message : 'Failed to fetch price';
22
+ console.log(styled.red(message));
23
+ }
24
+ });
25
+ };
@@ -0,0 +1,12 @@
1
+ import { scanAgents } from '../../shared/config/agent.js';
2
+ import { styled, symbols } from './theme.js';
3
+ export const printAgentNotFoundHelper = async (agentName) => {
4
+ const agents = await scanAgents();
5
+ if (agents.length === 0) {
6
+ console.error(styled.red(`${symbols.cross} No agents found. Create one with: npx @zhive/cli@latest create`));
7
+ }
8
+ else {
9
+ const availableNames = agents.map((a) => a.name).join(', ');
10
+ console.error(styled.red(`${symbols.cross} Agent "${agentName}" not found. Available agents: ${availableNames}`));
11
+ }
12
+ };
@@ -0,0 +1,81 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { animation, colors } from '../../shared/theme.js';
5
+ function buildTickerChars(step) {
6
+ const stepStr = String(step).padStart(2, '0');
7
+ const digits = stepStr.split('');
8
+ return animation.HEX_CHARS + digits.join('') + '\u25AA\u25AB\u2591\u2592';
9
+ }
10
+ function buildRow(cols, frame, rowIndex, tickerChars) {
11
+ const segments = [];
12
+ const isSecondRow = rowIndex === 1;
13
+ const scrollSpeed = isSecondRow ? 3 : 2;
14
+ const direction = isSecondRow ? -1 : 1;
15
+ const sinFreq = isSecondRow ? 0.4 : 0.3;
16
+ const sinPhase = isSecondRow ? -0.4 : 0.6;
17
+ const wrapLen = cols * 2;
18
+ for (let c = 0; c < cols; c++) {
19
+ const scrolledC = direction === 1
20
+ ? (c + frame * scrollSpeed) % wrapLen
21
+ : (cols - c + frame * scrollSpeed) % wrapLen;
22
+ const charIdx = scrolledC % tickerChars.length;
23
+ const char = tickerChars[charIdx];
24
+ const isHex = char === '\u2B21' || char === '\u2B22';
25
+ const pulseHit = Math.sin((c + frame * sinPhase) * sinFreq) > 0.5;
26
+ // Edge fade: dim the outermost 4 columns
27
+ const edgeDist = Math.min(c, cols - 1 - c);
28
+ if (edgeDist < 2) {
29
+ segments.push({ char: '\u00B7', color: colors.grayDim });
30
+ continue;
31
+ }
32
+ if (edgeDist < 4) {
33
+ segments.push({ char, color: colors.grayDim });
34
+ continue;
35
+ }
36
+ if (pulseHit && isHex) {
37
+ segments.push({ char, color: colors.honey });
38
+ }
39
+ else if (pulseHit) {
40
+ segments.push({ char, color: colors.green });
41
+ }
42
+ else {
43
+ segments.push({ char, color: colors.grayDim });
44
+ }
45
+ }
46
+ return segments;
47
+ }
48
+ function renderSegments(segments) {
49
+ const elements = [];
50
+ let runColor = segments[0]?.color ?? colors.grayDim;
51
+ let runChars = '';
52
+ for (let i = 0; i < segments.length; i++) {
53
+ const seg = segments[i];
54
+ if (seg.color === runColor) {
55
+ runChars += seg.char;
56
+ }
57
+ else {
58
+ elements.push(_jsx(Text, { color: runColor, children: runChars }, `${elements.length}`));
59
+ runColor = seg.color;
60
+ runChars = seg.char;
61
+ }
62
+ }
63
+ if (runChars.length > 0) {
64
+ elements.push(_jsx(Text, { color: runColor, children: runChars }, `${elements.length}`));
65
+ }
66
+ return elements;
67
+ }
68
+ export function AsciiTicker({ rows = 1, step = 1 }) {
69
+ const [frame, setFrame] = useState(0);
70
+ const cols = process.stdout.columns || 60;
71
+ const tickerChars = buildTickerChars(step);
72
+ useEffect(() => {
73
+ const timer = setInterval(() => {
74
+ setFrame((prev) => prev + 1);
75
+ }, animation.TICK_MS);
76
+ return () => {
77
+ clearInterval(timer);
78
+ };
79
+ }, []);
80
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: renderSegments(buildRow(cols, frame, 0, tickerChars)) }), rows === 2 && _jsx(Text, { children: renderSegments(buildRow(cols, frame, 1, tickerChars)) })] }));
81
+ }
package/dist/index.js CHANGED
@@ -10,6 +10,8 @@ import { createStartAllCommand } from './commands/start-all/commands/index.js';
10
10
  import { createRunCommand } from './commands/run/commands/index.js';
11
11
  import { createMigrateTemplatesCommand } from './commands/migrate-templates/commands/index.js';
12
12
  import { createDoctorCommand } from './commands/doctor/commands/index.js';
13
+ import { createIndicatorCommand } from './commands/indicator/commands/index.js';
14
+ import { createMarketCommand } from './commands/market/commands/index.js';
13
15
  const require = createRequire(import.meta.url);
14
16
  const packageJson = require('../package.json');
15
17
  const program = new Command();
@@ -23,6 +25,8 @@ program.addCommand(createStartAllCommand());
23
25
  program.addCommand(createRunCommand());
24
26
  program.addCommand(createMigrateTemplatesCommand());
25
27
  program.addCommand(createDoctorCommand());
28
+ program.addCommand(createIndicatorCommand());
29
+ program.addCommand(createMarketCommand());
26
30
  // Show help with exit code 0 when no arguments provided
27
31
  const args = process.argv.slice(2);
28
32
  if (args.length === 0) {
@@ -0,0 +1,160 @@
1
+ import { generateText, Output, ToolLoopAgent } from 'ai';
2
+ import { z } from 'zod';
3
+ import { buildAnalystPrompt, buildMegathreadPrompt } from './prompts/prompt.js';
4
+ import { loadMemory, saveMemory, getMemoryLineCount, MEMORY_SOFT_LIMIT, } from '@hive-org/sdk';
5
+ import { buildMemoryExtractionPrompt } from './prompts/memory-prompt.js';
6
+ import { stripCodeFences } from './helpers.js';
7
+ import { getModel } from './helpers/model.js';
8
+ // ─── Cache Helpers ─────────────────────────────────
9
+ function cacheableSystem(content) {
10
+ const message = {
11
+ role: 'system',
12
+ content,
13
+ providerOptions: {
14
+ anthropic: { cacheControl: { type: 'ephemeral' } },
15
+ },
16
+ };
17
+ return message;
18
+ }
19
+ // ─── Prediction Schema ──────────────────────────────
20
+ export const predictionSchema = z.object({
21
+ skip: z
22
+ .boolean()
23
+ .describe('true if this signal is outside your expertise or you have no strong take. false if you want to comment.'),
24
+ summary: z
25
+ .string()
26
+ .min(1)
27
+ .max(2000)
28
+ .nullable()
29
+ .describe('Your CT-style take on this signal. Short, punchy, in character. Think tweet, not essay. null if skipping.'),
30
+ conviction: z
31
+ .number()
32
+ .nullable()
33
+ .describe('Predicted percent price change over the next 3 hours, up to two 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. NEVER use 0 — always commit to a directional lean, even if small (e.g. ±0.1). null if skipping. VARY your predictions — do NOT default to the same number repeatedly.'),
34
+ });
35
+ const megathreadPredictionSchema = z.object({
36
+ skip: z
37
+ .boolean()
38
+ .describe('Only true if this project is outside the expertise list defined in your STRATEGY.md.'),
39
+ summary: z
40
+ .string()
41
+ .min(1)
42
+ .max(2000)
43
+ .nullable()
44
+ .describe('Your CT-style take on this project. Short, punchy, in character. Think tweet, not essay. null if skipping.'),
45
+ conviction: z
46
+ .number()
47
+ .nullable()
48
+ .describe('Predicted percent price change over the conviction window described in your instructions, up to two decimal places. 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. NEVER use 0 — always commit to a directional lean, even if small (e.g. ±0.1). null if skipping. VARY your predictions — do NOT default to the same number repeatedly.'),
49
+ });
50
+ function buildUsage(res) {
51
+ const toolCalls = res.steps.flatMap((s) => s.toolCalls);
52
+ const toolNames = toolCalls.map((tc) => tc.toolName);
53
+ const toolResults = res.steps
54
+ .flatMap((s) => s.toolResults)
55
+ .map((tr) => ({
56
+ toolName: tr.toolName,
57
+ result: String(tr.output),
58
+ }));
59
+ const usage = {
60
+ inputTokens: res.totalUsage.inputTokens ?? 0,
61
+ outputTokens: res.totalUsage.outputTokens ?? 0,
62
+ cacheReadTokens: res.totalUsage.inputTokenDetails?.cacheReadTokens ?? 0,
63
+ cacheWriteTokens: res.totalUsage.inputTokenDetails?.cacheWriteTokens ?? 0,
64
+ toolCalls: toolCalls.length,
65
+ toolNames,
66
+ toolResults,
67
+ };
68
+ return usage;
69
+ }
70
+ // ─── Signal Analysis ────────────────────────────────
71
+ export async function processSignalAndSummarize(thread, recentComments, memory, soulContent, strategyContent, tools = {}, availableSkills) {
72
+ const promptOptions = {
73
+ threadText: thread.text,
74
+ projectId: thread.project_id,
75
+ timestamp: thread.timestamp,
76
+ priceOnFetch: thread.price_on_fetch,
77
+ citations: thread.citations,
78
+ recentPosts: recentComments,
79
+ memory,
80
+ availableSkills,
81
+ };
82
+ const { system, prompt } = buildAnalystPrompt(soulContent, strategyContent, promptOptions);
83
+ const model = await getModel();
84
+ const agent = new ToolLoopAgent({
85
+ model,
86
+ instructions: cacheableSystem(system),
87
+ output: Output.object({ schema: predictionSchema }),
88
+ tools,
89
+ });
90
+ const res = await agent.generate({ prompt });
91
+ const usage = buildUsage(res);
92
+ const { output } = res;
93
+ if (!output) {
94
+ return { skip: true, summary: '', conviction: 0, usage };
95
+ }
96
+ const prediction = output;
97
+ const skip = prediction.skip ?? false;
98
+ const summary = prediction.summary ?? '';
99
+ const conviction = prediction.conviction ?? 0;
100
+ return { skip, summary, conviction, usage };
101
+ }
102
+ // ─── Megathread Round Analysis ──────────────────────
103
+ export async function processMegathreadRound(projectId, durationMs, recentComments, memory, soulContent, strategyContent, tools = {}, availableSkills, priceAtStart, currentPrice) {
104
+ const promptOptions = {
105
+ projectId,
106
+ durationMs,
107
+ priceAtStart,
108
+ currentPrice,
109
+ recentPosts: recentComments,
110
+ memory,
111
+ availableSkills,
112
+ };
113
+ const { system, prompt } = buildMegathreadPrompt(soulContent, strategyContent, promptOptions);
114
+ const model = await getModel();
115
+ const agent = new ToolLoopAgent({
116
+ model,
117
+ instructions: cacheableSystem(system),
118
+ output: Output.object({ schema: megathreadPredictionSchema }),
119
+ tools,
120
+ });
121
+ const res = await agent.generate({ prompt });
122
+ const usage = buildUsage(res);
123
+ const { output } = res;
124
+ if (!output) {
125
+ return { skip: true, summary: '', conviction: 0, usage };
126
+ }
127
+ const prediction = output;
128
+ const skip = prediction.skip ?? false;
129
+ const summary = prediction.summary ?? '';
130
+ const conviction = prediction.conviction ?? 0;
131
+ return { skip, summary, conviction, usage };
132
+ }
133
+ // ─── Memory Extraction ──────────────────────────────
134
+ export async function extractAndSaveMemory(sessionMessages) {
135
+ const currentMemory = await loadMemory();
136
+ const lineCount = getMemoryLineCount(currentMemory);
137
+ if (sessionMessages.length === 0 && lineCount <= MEMORY_SOFT_LIMIT) {
138
+ return null;
139
+ }
140
+ const prompt = buildMemoryExtractionPrompt({
141
+ currentMemory,
142
+ sessionMessages,
143
+ lineCount,
144
+ });
145
+ try {
146
+ const model = await getModel();
147
+ const { text } = await generateText({
148
+ model,
149
+ prompt,
150
+ });
151
+ const cleaned = stripCodeFences(text);
152
+ await saveMemory(cleaned);
153
+ return cleaned;
154
+ }
155
+ catch (err) {
156
+ const raw = err instanceof Error ? err.message : String(err);
157
+ console.error(`[Memory] Failed to extract memory: ${raw.slice(0, 200)}`);
158
+ return null;
159
+ }
160
+ }
@@ -0,0 +1,75 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ async function loadMarkdownFile(filename) {
4
+ const filePath = path.join(process.cwd(), filename);
5
+ const content = await fs.readFile(filePath, 'utf-8');
6
+ return content;
7
+ }
8
+ function extractField(content, pattern) {
9
+ const match = content.match(pattern);
10
+ if (match === null) {
11
+ return null;
12
+ }
13
+ const value = match[1].trim();
14
+ return value;
15
+ }
16
+ const VALID_SENTIMENTS = [
17
+ 'very-bullish',
18
+ 'bullish',
19
+ 'neutral',
20
+ 'bearish',
21
+ 'very-bearish',
22
+ ];
23
+ const VALID_TIMEFRAMES = ['1h', '4h', '24h'];
24
+ function parseSentiment(raw) {
25
+ if (raw !== null && VALID_SENTIMENTS.includes(raw)) {
26
+ return raw;
27
+ }
28
+ return 'neutral';
29
+ }
30
+ function parseSectors(raw) {
31
+ if (raw === null || raw.trim() === '') {
32
+ return [];
33
+ }
34
+ const sectors = raw
35
+ .split(',')
36
+ .map((s) => s.trim())
37
+ .filter((s) => s.length > 0);
38
+ return sectors;
39
+ }
40
+ function parseTimeframes(raw) {
41
+ if (raw === null || raw.trim() === '') {
42
+ return ['1h', '4h', '24h'];
43
+ }
44
+ const parsed = raw
45
+ .split(',')
46
+ .map((t) => t.trim())
47
+ .filter((t) => VALID_TIMEFRAMES.includes(t));
48
+ if (parsed.length === 0) {
49
+ return ['1h', '4h', '24h'];
50
+ }
51
+ return parsed;
52
+ }
53
+ export async function loadAgentConfig() {
54
+ const soulContent = await loadMarkdownFile('SOUL.md');
55
+ const strategyContent = await loadMarkdownFile('STRATEGY.md');
56
+ const name = extractField(soulContent, /^#\s+Agent:\s+(.+)$/m);
57
+ if (name === null) {
58
+ throw new Error('Could not parse agent name from SOUL.md. Expected "# Agent: <name>" as the first heading.');
59
+ }
60
+ const avatarUrl = extractField(soulContent, /^## Avatar\s*\n+(https?:\/\/.+)$/m);
61
+ if (avatarUrl === null) {
62
+ throw new Error('Could not parse avatar URL from SOUL.md. Expected a valid URL under "## Avatar".');
63
+ }
64
+ const bioRaw = extractField(soulContent, /^## Bio\s*\n+(.+)$/m);
65
+ const bio = bioRaw ?? null;
66
+ const sentimentRaw = extractField(strategyContent, /^-\s+Bias:\s+(.+)$/m);
67
+ const sectorsRaw = extractField(strategyContent, /^-\s+Sectors:\s+(.+)$/m);
68
+ const timeframesRaw = extractField(strategyContent, /^-\s+Active timeframes:\s+(.+)$/m);
69
+ const agentProfile = {
70
+ sentiment: parseSentiment(sentimentRaw),
71
+ sectors: parseSectors(sectorsRaw),
72
+ timeframes: parseTimeframes(timeframesRaw),
73
+ };
74
+ return { name, bio, avatarUrl, soulContent, strategyContent, agentProfile };
75
+ }
@@ -0,0 +1,30 @@
1
+ import { readFileSync } from 'fs';
2
+ import { AI_PROVIDER_ENV_VARS } from '../ai-providers.js';
3
+ let _agentProviderKeys = new Set();
4
+ /**
5
+ * Provider env-var names declared in the agent's .env file.
6
+ * Used by getModel() to prioritize the agent's chosen provider
7
+ * over keys inherited from the shell.
8
+ */
9
+ export function getAgentProviderKeys() {
10
+ return _agentProviderKeys;
11
+ }
12
+ /**
13
+ * Load the agent's .env with provider-key priority.
14
+ *
15
+ * 1. Parse .env to discover which provider keys the agent declared.
16
+ * 2. Load .env with override so the agent's values win for the same key.
17
+ * 3. getModel() uses getAgentProviderKeys() to check those providers first,
18
+ * falling back to shell-inherited keys if the agent has none.
19
+ */
20
+ export async function loadAgentEnv() {
21
+ try {
22
+ const content = readFileSync('.env', 'utf-8');
23
+ _agentProviderKeys = new Set(AI_PROVIDER_ENV_VARS.filter((key) => new RegExp(`^${key}=`, 'm').test(content)));
24
+ }
25
+ catch {
26
+ _agentProviderKeys = new Set();
27
+ }
28
+ const { config } = await import('dotenv');
29
+ config({ override: true });
30
+ }