@hybridaione/hybridclaw 0.1.5

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 (196) hide show
  1. package/.env.example +14 -0
  2. package/.github/workflows/pages.yml +41 -0
  3. package/AGENTS.md +24 -0
  4. package/CHANGELOG.md +113 -0
  5. package/LICENSE +21 -0
  6. package/README.md +226 -0
  7. package/SECURITY.md +67 -0
  8. package/config.example.json +62 -0
  9. package/container/Dockerfile +23 -0
  10. package/container/package-lock.json +246 -0
  11. package/container/package.json +17 -0
  12. package/container/src/hybridai-client.ts +38 -0
  13. package/container/src/index.ts +198 -0
  14. package/container/src/ipc.ts +37 -0
  15. package/container/src/tools.ts +1008 -0
  16. package/container/src/types.ts +74 -0
  17. package/container/src/web-fetch.ts +389 -0
  18. package/container/tsconfig.json +16 -0
  19. package/dist/agent.d.ts +3 -0
  20. package/dist/agent.d.ts.map +1 -0
  21. package/dist/agent.js +21 -0
  22. package/dist/agent.js.map +1 -0
  23. package/dist/cli.d.ts +3 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +32 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/config.d.ts +38 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +107 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/container-runner.d.ts +11 -0
  32. package/dist/container-runner.d.ts.map +1 -0
  33. package/dist/container-runner.js +246 -0
  34. package/dist/container-runner.js.map +1 -0
  35. package/dist/conversation.d.ts +18 -0
  36. package/dist/conversation.d.ts.map +1 -0
  37. package/dist/conversation.js +29 -0
  38. package/dist/conversation.js.map +1 -0
  39. package/dist/db.d.ts +29 -0
  40. package/dist/db.d.ts.map +1 -0
  41. package/dist/db.js +205 -0
  42. package/dist/db.js.map +1 -0
  43. package/dist/discord.d.ts +17 -0
  44. package/dist/discord.d.ts.map +1 -0
  45. package/dist/discord.js +115 -0
  46. package/dist/discord.js.map +1 -0
  47. package/dist/env.d.ts +6 -0
  48. package/dist/env.d.ts.map +1 -0
  49. package/dist/env.js +36 -0
  50. package/dist/env.js.map +1 -0
  51. package/dist/gateway-client.d.ts +8 -0
  52. package/dist/gateway-client.d.ts.map +1 -0
  53. package/dist/gateway-client.js +57 -0
  54. package/dist/gateway-client.js.map +1 -0
  55. package/dist/gateway-service.d.ts +23 -0
  56. package/dist/gateway-service.d.ts.map +1 -0
  57. package/dist/gateway-service.js +360 -0
  58. package/dist/gateway-service.js.map +1 -0
  59. package/dist/gateway-types.d.ts +40 -0
  60. package/dist/gateway-types.d.ts.map +1 -0
  61. package/dist/gateway-types.js +6 -0
  62. package/dist/gateway-types.js.map +1 -0
  63. package/dist/gateway.d.ts +2 -0
  64. package/dist/gateway.d.ts.map +1 -0
  65. package/dist/gateway.js +138 -0
  66. package/dist/gateway.js.map +1 -0
  67. package/dist/hatch.d.ts +7 -0
  68. package/dist/hatch.d.ts.map +1 -0
  69. package/dist/hatch.js +99 -0
  70. package/dist/hatch.js.map +1 -0
  71. package/dist/health.d.ts +2 -0
  72. package/dist/health.d.ts.map +1 -0
  73. package/dist/health.js +169 -0
  74. package/dist/health.js.map +1 -0
  75. package/dist/heartbeat.d.ts +3 -0
  76. package/dist/heartbeat.d.ts.map +1 -0
  77. package/dist/heartbeat.js +103 -0
  78. package/dist/heartbeat.js.map +1 -0
  79. package/dist/hybridai-bots.d.ts +5 -0
  80. package/dist/hybridai-bots.d.ts.map +1 -0
  81. package/dist/hybridai-bots.js +34 -0
  82. package/dist/hybridai-bots.js.map +1 -0
  83. package/dist/index.d.ts +2 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +60 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/ipc.d.ts +33 -0
  88. package/dist/ipc.d.ts.map +1 -0
  89. package/dist/ipc.js +142 -0
  90. package/dist/ipc.js.map +1 -0
  91. package/dist/logger.d.ts +3 -0
  92. package/dist/logger.d.ts.map +1 -0
  93. package/dist/logger.js +21 -0
  94. package/dist/logger.js.map +1 -0
  95. package/dist/mount-security.d.ts +28 -0
  96. package/dist/mount-security.d.ts.map +1 -0
  97. package/dist/mount-security.js +187 -0
  98. package/dist/mount-security.js.map +1 -0
  99. package/dist/onboarding.d.ts +7 -0
  100. package/dist/onboarding.d.ts.map +1 -0
  101. package/dist/onboarding.js +445 -0
  102. package/dist/onboarding.js.map +1 -0
  103. package/dist/prompt-hooks.d.ts +17 -0
  104. package/dist/prompt-hooks.d.ts.map +1 -0
  105. package/dist/prompt-hooks.js +83 -0
  106. package/dist/prompt-hooks.js.map +1 -0
  107. package/dist/runtime-config.d.ts +78 -0
  108. package/dist/runtime-config.d.ts.map +1 -0
  109. package/dist/runtime-config.js +471 -0
  110. package/dist/runtime-config.js.map +1 -0
  111. package/dist/scheduled-task-runner.d.ts +11 -0
  112. package/dist/scheduled-task-runner.d.ts.map +1 -0
  113. package/dist/scheduled-task-runner.js +16 -0
  114. package/dist/scheduled-task-runner.js.map +1 -0
  115. package/dist/scheduler.d.ts +11 -0
  116. package/dist/scheduler.d.ts.map +1 -0
  117. package/dist/scheduler.js +165 -0
  118. package/dist/scheduler.js.map +1 -0
  119. package/dist/session-maintenance.d.ts +9 -0
  120. package/dist/session-maintenance.d.ts.map +1 -0
  121. package/dist/session-maintenance.js +168 -0
  122. package/dist/session-maintenance.js.map +1 -0
  123. package/dist/session-transcripts.d.ts +11 -0
  124. package/dist/session-transcripts.d.ts.map +1 -0
  125. package/dist/session-transcripts.js +32 -0
  126. package/dist/session-transcripts.js.map +1 -0
  127. package/dist/side-effects.d.ts +3 -0
  128. package/dist/side-effects.d.ts.map +1 -0
  129. package/dist/side-effects.js +30 -0
  130. package/dist/side-effects.js.map +1 -0
  131. package/dist/skills.d.ts +32 -0
  132. package/dist/skills.d.ts.map +1 -0
  133. package/dist/skills.js +376 -0
  134. package/dist/skills.js.map +1 -0
  135. package/dist/tui.d.ts +2 -0
  136. package/dist/tui.d.ts.map +1 -0
  137. package/dist/tui.js +305 -0
  138. package/dist/tui.js.map +1 -0
  139. package/dist/types.d.ts +132 -0
  140. package/dist/types.d.ts.map +1 -0
  141. package/dist/types.js +3 -0
  142. package/dist/types.js.map +1 -0
  143. package/dist/workspace.d.ts +25 -0
  144. package/dist/workspace.d.ts.map +1 -0
  145. package/dist/workspace.js +154 -0
  146. package/dist/workspace.js.map +1 -0
  147. package/docs/chat.html +929 -0
  148. package/docs/hai_logo_free.png +0 -0
  149. package/docs/hero.png +0 -0
  150. package/docs/index.html +1213 -0
  151. package/package.json +34 -0
  152. package/skills/current-time/SKILL.md +26 -0
  153. package/skills/iss-position/SKILL.md +46 -0
  154. package/skills/iss-position/agents/openai.yaml +3 -0
  155. package/skills/iss-position/scripts/get_iss_position.py +107 -0
  156. package/skills/repo-orientation/SKILL.md +74 -0
  157. package/src/agent.ts +35 -0
  158. package/src/cli.ts +35 -0
  159. package/src/config.ts +137 -0
  160. package/src/container-runner.ts +305 -0
  161. package/src/conversation.ts +49 -0
  162. package/src/db.ts +290 -0
  163. package/src/discord.ts +156 -0
  164. package/src/env.ts +36 -0
  165. package/src/gateway-client.ts +73 -0
  166. package/src/gateway-service.ts +456 -0
  167. package/src/gateway-types.ts +47 -0
  168. package/src/gateway.ts +199 -0
  169. package/src/health.ts +189 -0
  170. package/src/heartbeat.ts +121 -0
  171. package/src/hybridai-bots.ts +48 -0
  172. package/src/ipc.ts +163 -0
  173. package/src/logger.ts +26 -0
  174. package/src/mount-security.ts +216 -0
  175. package/src/onboarding.ts +569 -0
  176. package/src/prompt-hooks.ts +113 -0
  177. package/src/runtime-config.ts +588 -0
  178. package/src/scheduled-task-runner.ts +26 -0
  179. package/src/scheduler.ts +196 -0
  180. package/src/session-maintenance.ts +263 -0
  181. package/src/session-transcripts.ts +45 -0
  182. package/src/side-effects.ts +41 -0
  183. package/src/skills.ts +429 -0
  184. package/src/tui.ts +327 -0
  185. package/src/types.ts +135 -0
  186. package/src/workspace.ts +171 -0
  187. package/templates/AGENTS.md +104 -0
  188. package/templates/BOOT.md +3 -0
  189. package/templates/BOOTSTRAP.md +54 -0
  190. package/templates/HEARTBEAT.md +5 -0
  191. package/templates/IDENTITY.md +18 -0
  192. package/templates/MEMORY.md +19 -0
  193. package/templates/SOUL.md +36 -0
  194. package/templates/TOOLS.md +22 -0
  195. package/templates/USER.md +17 -0
  196. package/tsconfig.json +18 -0
package/src/discord.ts ADDED
@@ -0,0 +1,156 @@
1
+ import {
2
+ Client,
3
+ GatewayIntentBits,
4
+ type Message as DiscordMessage,
5
+ Partials,
6
+ } from 'discord.js';
7
+
8
+ import { DISCORD_PREFIX, DISCORD_TOKEN } from './config.js';
9
+ import { logger } from './logger.js';
10
+
11
+ export type ReplyFn = (content: string) => Promise<void>;
12
+
13
+ export type MessageHandler = (
14
+ sessionId: string,
15
+ guildId: string | null,
16
+ channelId: string,
17
+ userId: string,
18
+ username: string,
19
+ content: string,
20
+ reply: ReplyFn,
21
+ ) => Promise<void>;
22
+
23
+ export type CommandHandler = (
24
+ sessionId: string,
25
+ guildId: string | null,
26
+ channelId: string,
27
+ args: string[],
28
+ reply: ReplyFn,
29
+ ) => Promise<void>;
30
+
31
+ let client: Client;
32
+ let messageHandler: MessageHandler;
33
+ let commandHandler: CommandHandler;
34
+
35
+ /**
36
+ * Format an agent response as plain text.
37
+ * Appends a subtle tools line if any tools were used.
38
+ */
39
+ export function buildResponseText(text: string, toolsUsed?: string[]): string {
40
+ let body = text.slice(0, 2000);
41
+ if (toolsUsed && toolsUsed.length > 0) {
42
+ const toolsLine = `\n*Tools: ${toolsUsed.join(', ')}*`;
43
+ body = text.slice(0, 2000 - toolsLine.length) + toolsLine;
44
+ }
45
+ return body;
46
+ }
47
+
48
+ export function formatInfo(title: string, body: string): string {
49
+ return `**${title}**\n${body}`.slice(0, 2000);
50
+ }
51
+
52
+ export function formatError(title: string, detail: string): string {
53
+ return `**${title}:** ${detail}`.slice(0, 2000);
54
+ }
55
+
56
+ function getSessionId(msg: DiscordMessage): string {
57
+ return msg.guild ? `${msg.guild.id}:${msg.channelId}` : `dm:${msg.author.id}`;
58
+ }
59
+
60
+ function isTrigger(msg: DiscordMessage): boolean {
61
+ if (client.user && msg.mentions.has(client.user)) return true;
62
+ if (msg.content.startsWith(DISCORD_PREFIX)) return true;
63
+ if (!msg.guild) return true;
64
+ return false;
65
+ }
66
+
67
+ function parseCommand(content: string): { isCommand: boolean; command: string; args: string[] } {
68
+ let text = content;
69
+
70
+ if (client.user) {
71
+ text = text.replace(new RegExp(`<@!?${client.user.id}>`, 'g'), '').trim();
72
+ }
73
+
74
+ if (text.startsWith(DISCORD_PREFIX)) {
75
+ text = text.slice(DISCORD_PREFIX.length).trim();
76
+ }
77
+
78
+ const parts = text.split(/\s+/);
79
+ const subcommands = ['bot', 'rag', 'model', 'status', 'sessions', 'audit', 'schedule', 'clear', 'help'];
80
+
81
+ if (parts.length > 0 && subcommands.includes(parts[0].toLowerCase())) {
82
+ return { isCommand: true, command: parts[0].toLowerCase(), args: parts.slice(1) };
83
+ }
84
+
85
+ return { isCommand: false, command: '', args: [] };
86
+ }
87
+
88
+ export function initDiscord(onMessage: MessageHandler, onCommand: CommandHandler): Client {
89
+ messageHandler = onMessage;
90
+ commandHandler = onCommand;
91
+
92
+ client = new Client({
93
+ intents: [
94
+ GatewayIntentBits.Guilds,
95
+ GatewayIntentBits.GuildMessages,
96
+ GatewayIntentBits.MessageContent,
97
+ GatewayIntentBits.DirectMessages,
98
+ ],
99
+ partials: [Partials.Channel],
100
+ });
101
+
102
+ client.on('clientReady', () => {
103
+ logger.info({ user: client.user?.tag }, 'Discord bot connected');
104
+ });
105
+
106
+ client.on('messageCreate', async (msg: DiscordMessage) => {
107
+ if (msg.author.bot) return;
108
+ if (!isTrigger(msg)) return;
109
+
110
+ const sessionId = getSessionId(msg);
111
+ const guildId = msg.guild?.id || null;
112
+ const channelId = msg.channelId;
113
+
114
+ const reply: ReplyFn = async (text) => {
115
+ await msg.reply(text);
116
+ };
117
+
118
+ // Clean content (remove mention/prefix)
119
+ let content = msg.content;
120
+ if (client.user) {
121
+ content = content.replace(new RegExp(`<@!?${client.user.id}>`, 'g'), '').trim();
122
+ }
123
+ if (content.startsWith(DISCORD_PREFIX)) {
124
+ content = content.slice(DISCORD_PREFIX.length).trim();
125
+ }
126
+
127
+ const parsed = parseCommand(msg.content);
128
+
129
+ if (parsed.isCommand) {
130
+ await commandHandler(sessionId, guildId, channelId, [parsed.command, ...parsed.args], reply);
131
+ } else {
132
+ if (!content) {
133
+ await reply('How can I help? Send me a message or try `!claw help`.');
134
+ return;
135
+ }
136
+ if ('sendTyping' in msg.channel) await msg.channel.sendTyping();
137
+ await messageHandler(sessionId, guildId, channelId, msg.author.id, msg.author.username, content, reply);
138
+ }
139
+ });
140
+
141
+ if (!DISCORD_TOKEN) {
142
+ throw new Error('DISCORD_TOKEN is required to start the Discord bot');
143
+ }
144
+ client.login(DISCORD_TOKEN);
145
+ return client;
146
+ }
147
+
148
+ /**
149
+ * Send a message to a channel by ID (used by scheduler).
150
+ */
151
+ export async function sendToChannel(channelId: string, text: string): Promise<void> {
152
+ const channel = await client.channels.fetch(channelId);
153
+ if (channel && 'send' in channel) {
154
+ await (channel as unknown as { send: (text: string) => Promise<void> }).send(text);
155
+ }
156
+ }
package/src/env.ts ADDED
@@ -0,0 +1,36 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Load environment variables from <cwd>/.env with simple KEY=VALUE parsing.
6
+ * Existing process.env values win over file values.
7
+ */
8
+ export function loadEnvFile(cwd: string = process.cwd()): void {
9
+ const envPath = path.join(cwd, '.env');
10
+ if (!fs.existsSync(envPath)) return;
11
+
12
+ const content = fs.readFileSync(envPath, 'utf-8');
13
+ for (const line of content.split('\n')) {
14
+ const trimmed = line.trim();
15
+ if (!trimmed || trimmed.startsWith('#')) continue;
16
+
17
+ const eqIdx = trimmed.indexOf('=');
18
+ if (eqIdx === -1) continue;
19
+
20
+ const key = trimmed.slice(0, eqIdx).trim();
21
+ let val = trimmed.slice(eqIdx + 1).trim();
22
+
23
+ // Strip inline comments (# ...) unless the value is quoted.
24
+ if (!val.startsWith('"') && !val.startsWith("'")) {
25
+ const hashIdx = val.indexOf('#');
26
+ if (hashIdx !== -1) val = val.slice(0, hashIdx).trim();
27
+ } else {
28
+ // Remove surrounding quotes.
29
+ val = val.slice(1, -1);
30
+ }
31
+
32
+ if (!process.env[key]) {
33
+ process.env[key] = val;
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,73 @@
1
+ import { GATEWAY_API_TOKEN, GATEWAY_BASE_URL } from './config.js';
2
+ import {
3
+ renderGatewayCommand,
4
+ type GatewayChatRequestBody,
5
+ type GatewayChatResult,
6
+ type GatewayCommandRequest,
7
+ type GatewayCommandResult,
8
+ type GatewayStatus,
9
+ } from './gateway-types.js';
10
+ export { renderGatewayCommand };
11
+ export type { GatewayChatResult, GatewayCommandResult, GatewayStatus };
12
+ export type GatewayChatRequest = GatewayChatRequestBody;
13
+
14
+ function gatewayUrl(pathname: string): string {
15
+ const base = GATEWAY_BASE_URL.replace(/\/+$/, '');
16
+ const path = pathname.startsWith('/') ? pathname : `/${pathname}`;
17
+ return `${base}${path}`;
18
+ }
19
+
20
+ function authHeaders(): Record<string, string> {
21
+ if (!GATEWAY_API_TOKEN) return {};
22
+ return { Authorization: `Bearer ${GATEWAY_API_TOKEN}` };
23
+ }
24
+
25
+ async function requestJson<T>(pathname: string, init: RequestInit): Promise<T> {
26
+ let response: Response;
27
+ try {
28
+ response = await fetch(gatewayUrl(pathname), init);
29
+ } catch (err) {
30
+ const detail = err instanceof Error ? err.message : String(err);
31
+ throw new Error(`Gateway request failed (${GATEWAY_BASE_URL}): ${detail}`);
32
+ }
33
+
34
+ const payload = await response.json().catch(() => ({}));
35
+ if (!response.ok) {
36
+ const message = typeof payload.error === 'string' ? payload.error : `${response.status} ${response.statusText}`;
37
+ throw new Error(`Gateway error: ${message}`);
38
+ }
39
+ return payload as T;
40
+ }
41
+
42
+ export async function gatewayCommand(params: GatewayCommandRequest): Promise<GatewayCommandResult> {
43
+ return requestJson<GatewayCommandResult>('/api/command', {
44
+ method: 'POST',
45
+ headers: {
46
+ 'Content-Type': 'application/json',
47
+ ...authHeaders(),
48
+ },
49
+ body: JSON.stringify(params),
50
+ });
51
+ }
52
+
53
+ export async function gatewayChat(
54
+ params: GatewayChatRequest,
55
+ signal?: AbortSignal,
56
+ ): Promise<GatewayChatResult> {
57
+ return requestJson<GatewayChatResult>('/api/chat', {
58
+ method: 'POST',
59
+ headers: {
60
+ 'Content-Type': 'application/json',
61
+ ...authHeaders(),
62
+ },
63
+ body: JSON.stringify(params),
64
+ signal,
65
+ });
66
+ }
67
+
68
+ export async function gatewayStatus(): Promise<GatewayStatus> {
69
+ return requestJson<GatewayStatus>('/api/status', {
70
+ method: 'GET',
71
+ headers: authHeaders(),
72
+ });
73
+ }