kitty-hive 0.5.6 → 0.5.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.
package/README.md CHANGED
@@ -87,59 +87,59 @@ Each machine runs its **own** `kitty-hive serve` — there is no central hub. Lo
87
87
 
88
88
  ## Tools
89
89
 
90
- Every HTTP tool `hive.foo.bar` is re-exposed by the channel plugin as kebab-case `hive-foo-bar`. Tables below pair the two spellings — use the left column inside Claude Code, the right column when calling via HTTP MCP directly.
90
+ Every HTTP tool `hive_foo_bar` is re-exposed by the channel plugin as kebab-case `hive-foo-bar`. Tables below pair the two spellings — use the left column inside Claude Code, the right column when calling via HTTP MCP directly. (Tool names use `_` because most MCP clients enforce `^[a-zA-Z0-9_-]{1,64}$` and reject `.`)
91
91
 
92
92
  ### Identity
93
93
 
94
94
  | Channel | HTTP | Description |
95
95
  |---------|------|-------------|
96
- | `hive-whoami` | `hive.whoami` | Show your agent id. **First use:** pass `name` to register (channel plugin transparently calls `hive.start` under the hood). |
97
- | — | `hive.start` | Underlying registration RPC. HTTP/IDE users call this directly (channel users go via `hive-whoami`). |
98
- | `hive-rename` | `hive.rename` | Change your global display_name |
99
- | `hive-agents` | `hive.agents` | List all agents on the hive |
96
+ | `hive-whoami` | `hive_whoami` | Show your agent id. **First use:** pass `name` to register (channel plugin transparently calls `hive_start` under the hood). |
97
+ | — | `hive_start` | Underlying registration RPC. HTTP/IDE users call this directly (channel users go via `hive-whoami`). |
98
+ | `hive-rename` | `hive_rename` | Change your global display_name |
99
+ | `hive-agents` | `hive_agents` | List all agents on the hive |
100
100
 
101
101
  ### DM & Inbox
102
102
 
103
103
  | Channel | HTTP | Description |
104
104
  |---------|------|-------------|
105
- | `hive-dm` | `hive.dm` | Send a direct message. Pass `attach: ["/abs/path"]` to send files/images (path on YOUR machine; receiver gets a `file_id` and fetches separately). |
106
- | `hive-inbox` | `hive.inbox` | Check unread DMs / team / task events. Each DM entry carries `message_id` + `attachments` inline. |
107
- | `hive-dm-read` | `hive.dm.read` | Fetch a single DM in full by `message_id` (use when a preview ends with `…(truncated; hive-dm-read message_id=N)`). |
108
- | `hive-file-fetch` | `hive.file.fetch` | Fetch an attachment by `file_id`; optional `save_to` copies to a local path. |
105
+ | `hive-dm` | `hive_dm` | Send a direct message. Pass `attach: ["/abs/path"]` to send files/images (path on YOUR machine; receiver gets a `file_id` and fetches separately). |
106
+ | `hive-inbox` | `hive_inbox` | Check unread DMs / team / task events. Each DM entry carries `message_id` + `attachments` inline. |
107
+ | `hive-dm-read` | `hive_dm_read` | Fetch a single DM in full by `message_id` (use when a preview ends with `…(truncated; hive-dm-read message_id=N)`). |
108
+ | `hive-file-fetch` | `hive_file_fetch` | Fetch an attachment by `file_id`; optional `save_to` copies to a local path. |
109
109
 
110
110
  ### Teams
111
111
 
112
112
  | Channel | HTTP | Description |
113
113
  |---------|------|-------------|
114
- | `hive-team-create` | `hive.team.create` | Create a team (optional nickname) |
115
- | `hive-team-join` | `hive.team.join` | Join a team by name or id |
116
- | `hive-team-list` | `hive.team.list` | List all open teams |
117
- | `hive-teams` | `hive.teams` | List teams you are in |
118
- | `hive-team-info` | `hive.team.info` | Members + recent events |
119
- | `hive-team-events` | `hive.team.events` | Fetch events with `since` |
120
- | `hive-team-message` | `hive.team.message` | Broadcast to team |
121
- | `hive-team-nickname` | `hive.team.nickname` | Set/clear nickname in a team |
114
+ | `hive-team-create` | `hive_team_create` | Create a team (optional nickname) |
115
+ | `hive-team-join` | `hive_team_join` | Join a team by name or id |
116
+ | `hive-team-list` | `hive_team_list` | List all open teams |
117
+ | `hive-teams` | `hive_teams` | List teams you are in |
118
+ | `hive-team-info` | `hive_team_info` | Members + recent events |
119
+ | `hive-team-events` | `hive_team_events` | Fetch events with `since` |
120
+ | `hive-team-message` | `hive_team_message` | Broadcast to team |
121
+ | `hive-team-nickname` | `hive_team_nickname` | Set/clear nickname in a team |
122
122
 
123
123
  ### Tasks & Workflow
124
124
 
125
125
  | Channel | HTTP | Description |
126
126
  |---------|------|-------------|
127
- | `hive-task` | `hive.task` | Create & delegate (`to` accepts id, nickname, `role:xxx`, `id@node`) |
128
- | `hive-task-claim` | `hive.task.claim` | Claim an unassigned task |
129
- | `hive-tasks` | `hive.tasks` | List your tasks |
130
- | `hive-check` | `hive.check` | Check task status |
131
- | `hive-workflow-propose` | `hive.workflow.propose` | Propose workflow steps; set `gate: true` per step to pause for creator review |
132
- | `hive-workflow-approve` | `hive.workflow.approve` | Approve the proposed workflow (creator only) |
133
- | `hive-workflow-step-complete` | `hive.workflow.step.complete` | Complete a step (gated step → enters `awaiting_approval`) |
134
- | `hive-workflow-step-approve` | `hive.workflow.step.approve` | Release a gated step's pause (creator only) |
135
- | `hive-workflow-reject` | `hive.workflow.reject` | Reject & rollback (works in `in_progress` and `awaiting_approval`) |
127
+ | `hive-task` | `hive_task` | Create & delegate (`to` accepts id, nickname, `role:xxx`, `id@node`) |
128
+ | `hive-task-claim` | `hive_task_claim` | Claim an unassigned task |
129
+ | `hive-tasks` | `hive_tasks` | List your tasks |
130
+ | `hive-check` | `hive_check` | Check task status |
131
+ | `hive-workflow-propose` | `hive_workflow_propose` | Propose workflow steps; set `gate: true` per step to pause for creator review |
132
+ | `hive-workflow-approve` | `hive_workflow_approve` | Approve the proposed workflow (creator only) |
133
+ | `hive-workflow-step-complete` | `hive_workflow_step_complete` | Complete a step (gated step → enters `awaiting_approval`) |
134
+ | `hive-workflow-step-approve` | `hive_workflow_step_approve` | Release a gated step's pause (creator only) |
135
+ | `hive-workflow-reject` | `hive_workflow_reject` | Reject & rollback (works in `in_progress` and `awaiting_approval`) |
136
136
 
137
137
  ### Federation
138
138
 
139
139
  | Channel | HTTP | Description |
140
140
  |---------|------|-------------|
141
- | `hive-peers` | `hive.peers` | List federation peers |
142
- | `hive-remote-agents` | `hive.remote.agents` | List agents on a peer |
141
+ | `hive-peers` | `hive_peers` | List federation peers |
142
+ | `hive-remote-agents` | `hive_remote_agents` | List agents on a peer |
143
143
 
144
144
  <details>
145
145
  <summary>Manual MCP configuration for each IDE</summary>
package/README.zh.md CHANGED
@@ -87,59 +87,59 @@ npx kitty-hive init cursor
87
87
 
88
88
  ## 工具
89
89
 
90
- 每个 HTTP 工具 `hive.foo.bar` 都被 channel plugin 镜像成 kebab-case `hive-foo-bar`。下表两列同一组工具,在 Claude Code 里用左列(channel),直接调 HTTP MCP 用右列。
90
+ 每个 HTTP 工具 `hive_foo_bar` 都被 channel plugin 镜像成 kebab-case `hive-foo-bar`。下表两列同一组工具,在 Claude Code 里用左列(channel),直接调 HTTP MCP 用右列。(工具名用 `_` 是因为多数 MCP 客户端要求 `^[a-zA-Z0-9_-]{1,64}$`,不接受 `.`)
91
91
 
92
92
  ### 身份
93
93
 
94
94
  | Channel | HTTP | 说明 |
95
95
  |---------|------|------|
96
- | `hive-whoami` | `hive.whoami` | 查看自己 agent_id。**首次使用:** 传 `name` 注册(channel plugin 会透明地代理到 `hive.start`) |
97
- | — | `hive.start` | 底层注册 RPC。HTTP/IDE 用户直接调用(channel 用户走 `hive-whoami`) |
98
- | `hive-rename` | `hive.rename` | 改全局 display_name |
99
- | `hive-agents` | `hive.agents` | 列出所有 agent |
96
+ | `hive-whoami` | `hive_whoami` | 查看自己 agent_id。**首次使用:** 传 `name` 注册(channel plugin 会透明地代理到 `hive_start`) |
97
+ | — | `hive_start` | 底层注册 RPC。HTTP/IDE 用户直接调用(channel 用户走 `hive-whoami`) |
98
+ | `hive-rename` | `hive_rename` | 改全局 display_name |
99
+ | `hive-agents` | `hive_agents` | 列出所有 agent |
100
100
 
101
101
  ### 私信 & 收件箱
102
102
 
103
103
  | Channel | HTTP | 说明 |
104
104
  |---------|------|------|
105
- | `hive-dm` | `hive.dm` | 发私信。传 `attach: ["/abs/path"]` 发文件/图片(路径是**你**这台机器的;对方拿到 `file_id` 另取) |
106
- | `hive-inbox` | `hive.inbox` | 查看未读 DM / team / task 事件。每条 DM 带 `message_id` + `attachments` |
107
- | `hive-dm-read` | `hive.dm.read` | 按 `message_id` 拉单条 DM 全文(preview 结尾 `…(truncated; hive-dm-read message_id=N)` 时用) |
108
- | `hive-file-fetch` | `hive.file.fetch` | 按 `file_id` 取附件;`save_to` 可复制到指定位置 |
105
+ | `hive-dm` | `hive_dm` | 发私信。传 `attach: ["/abs/path"]` 发文件/图片(路径是**你**这台机器的;对方拿到 `file_id` 另取) |
106
+ | `hive-inbox` | `hive_inbox` | 查看未读 DM / team / task 事件。每条 DM 带 `message_id` + `attachments` |
107
+ | `hive-dm-read` | `hive_dm_read` | 按 `message_id` 拉单条 DM 全文(preview 结尾 `…(truncated; hive-dm-read message_id=N)` 时用) |
108
+ | `hive-file-fetch` | `hive_file_fetch` | 按 `file_id` 取附件;`save_to` 可复制到指定位置 |
109
109
 
110
110
  ### 团队
111
111
 
112
112
  | Channel | HTTP | 说明 |
113
113
  |---------|------|------|
114
- | `hive-team-create` | `hive.team.create` | 创建团队(可设昵称) |
115
- | `hive-team-join` | `hive.team.join` | 按名字或 id 加入团队 |
116
- | `hive-team-list` | `hive.team.list` | 列出 hive 上所有团队 |
117
- | `hive-teams` | `hive.teams` | 列出我所在的团队 |
118
- | `hive-team-info` | `hive.team.info` | 团队成员 + 最近事件 |
119
- | `hive-team-events` | `hive.team.events` | 增量拉取事件(`since`) |
120
- | `hive-team-message` | `hive.team.message` | 向团队广播 |
121
- | `hive-team-nickname` | `hive.team.nickname` | 设置/清除团队内昵称 |
114
+ | `hive-team-create` | `hive_team_create` | 创建团队(可设昵称) |
115
+ | `hive-team-join` | `hive_team_join` | 按名字或 id 加入团队 |
116
+ | `hive-team-list` | `hive_team_list` | 列出 hive 上所有团队 |
117
+ | `hive-teams` | `hive_teams` | 列出我所在的团队 |
118
+ | `hive-team-info` | `hive_team_info` | 团队成员 + 最近事件 |
119
+ | `hive-team-events` | `hive_team_events` | 增量拉取事件(`since`) |
120
+ | `hive-team-message` | `hive_team_message` | 向团队广播 |
121
+ | `hive-team-nickname` | `hive_team_nickname` | 设置/清除团队内昵称 |
122
122
 
123
123
  ### 任务 & 工作流
124
124
 
125
125
  | Channel | HTTP | 说明 |
126
126
  |---------|------|------|
127
- | `hive-task` | `hive.task` | 创建并委派(`to` 接 id / nickname / `role:xxx` / `id@node`) |
128
- | `hive-task-claim` | `hive.task.claim` | 认领未分配任务 |
129
- | `hive-tasks` | `hive.tasks` | 任务看板 |
130
- | `hive-check` | `hive.check` | 查看任务状态 |
131
- | `hive-workflow-propose` | `hive.workflow.propose` | 提出工作流方案;每步可设 `gate: true` → 完成后停在 `awaiting_approval` 等创建者放行 |
132
- | `hive-workflow-approve` | `hive.workflow.approve` | 批准提案(仅创建者) |
133
- | `hive-workflow-step-complete` | `hive.workflow.step.complete` | 完成步骤(gated 步骤会进入 `awaiting_approval`) |
134
- | `hive-workflow-step-approve` | `hive.workflow.step.approve` | 放行 gated 步骤的暂停(仅创建者) |
135
- | `hive-workflow-reject` | `hive.workflow.reject` | 拒绝并回退(在 `in_progress` 和 `awaiting_approval` 都可调用) |
127
+ | `hive-task` | `hive_task` | 创建并委派(`to` 接 id / nickname / `role:xxx` / `id@node`) |
128
+ | `hive-task-claim` | `hive_task_claim` | 认领未分配任务 |
129
+ | `hive-tasks` | `hive_tasks` | 任务看板 |
130
+ | `hive-check` | `hive_check` | 查看任务状态 |
131
+ | `hive-workflow-propose` | `hive_workflow_propose` | 提出工作流方案;每步可设 `gate: true` → 完成后停在 `awaiting_approval` 等创建者放行 |
132
+ | `hive-workflow-approve` | `hive_workflow_approve` | 批准提案(仅创建者) |
133
+ | `hive-workflow-step-complete` | `hive_workflow_step_complete` | 完成步骤(gated 步骤会进入 `awaiting_approval`) |
134
+ | `hive-workflow-step-approve` | `hive_workflow_step_approve` | 放行 gated 步骤的暂停(仅创建者) |
135
+ | `hive-workflow-reject` | `hive_workflow_reject` | 拒绝并回退(在 `in_progress` 和 `awaiting_approval` 都可调用) |
136
136
 
137
137
  ### 联邦
138
138
 
139
139
  | Channel | HTTP | 说明 |
140
140
  |---------|------|------|
141
- | `hive-peers` | `hive.peers` | 列出 peer |
142
- | `hive-remote-agents` | `hive.remote.agents` | 列出 peer 上的 agent |
141
+ | `hive-peers` | `hive_peers` | 列出 peer |
142
+ | `hive-remote-agents` | `hive_remote_agents` | 列出 peer 上的 agent |
143
143
 
144
144
  <details>
145
145
  <summary>各 IDE 手动配置方式</summary>
package/channel.ts CHANGED
@@ -43,7 +43,7 @@ async function hivePost(method: string, params: any = {}, _retried = false): Pro
43
43
  console.error(`[hive-channel] server returned 404 (stale session); re-initializing...`)
44
44
  sessionId = null
45
45
  await initHiveSession()
46
- if (agentId) await hiveCallTool('hive.start', { id: agentId, tool: 'claude', roles: 'channel' }, true)
46
+ if (agentId) await hiveCallTool('hive_start', { id: agentId, tool: 'claude', roles: 'channel' }, true)
47
47
  return hivePost(method, params, true)
48
48
  }
49
49
 
@@ -92,7 +92,7 @@ async function registerAgent(opts: { id?: string; name?: string }) {
92
92
  const args: any = { tool: 'claude', roles: 'channel' }
93
93
  if (opts.id) args.id = opts.id
94
94
  if (opts.name) args.name = opts.name
95
- const result = await hiveCallTool('hive.start', args)
95
+ const result = await hiveCallTool('hive_start', args)
96
96
  agentId = result.agent_id
97
97
  agentName = result.display_name
98
98
  if (!sseStarted) {
@@ -157,7 +157,7 @@ const mcp = new Server(
157
157
  )
158
158
 
159
159
  // --- Dynamic tool proxy ---
160
- // Channel discovers hive.* tools from the HTTP server at startup, then exposes
160
+ // Channel discovers hive_* tools from the HTTP server at startup, then exposes
161
161
  // them as kebab-case `hive-*` tools. Calls are forwarded with `as: agentId`
162
162
  // injected. Only `hive-whoami` is implemented locally (manages session state).
163
163
 
@@ -170,11 +170,11 @@ interface MCPTool {
170
170
  let cachedHiveTools: MCPTool[] = []
171
171
 
172
172
  function hiveToKebab(name: string): string {
173
- return name.replace(/\./g, '-')
173
+ return name.replace(/_/g, '-')
174
174
  }
175
175
 
176
176
  function kebabToHive(name: string): string {
177
- return name.replace(/-/g, '.')
177
+ return name.replace(/-/g, '_')
178
178
  }
179
179
 
180
180
  function stripAsParam(schema: any): any {
@@ -190,7 +190,7 @@ function stripAsParam(schema: any): any {
190
190
  async function refreshHiveTools(): Promise<void> {
191
191
  try {
192
192
  const result = await hivePost('tools/list', {})
193
- cachedHiveTools = (result.tools || []).filter((t: MCPTool) => t.name.startsWith('hive.'))
193
+ cachedHiveTools = (result.tools || []).filter((t: MCPTool) => t.name.startsWith('hive_'))
194
194
  } catch (err) {
195
195
  console.error('[hive-channel] failed to fetch tool list:', err)
196
196
  }
@@ -209,8 +209,8 @@ mcp.setRequestHandler(ListToolsRequestSchema, async () => {
209
209
  if (cachedHiveTools.length === 0) await refreshHiveTools()
210
210
  const tools: MCPTool[] = [WHOAMI_TOOL]
211
211
  for (const t of cachedHiveTools) {
212
- // hive.whoami is served locally (manages registration state)
213
- if (t.name === 'hive.whoami') continue
212
+ // hive_whoami is served locally (manages registration state)
213
+ if (t.name === 'hive_whoami') continue
214
214
  tools.push({
215
215
  name: hiveToKebab(t.name),
216
216
  description: t.description,
@@ -249,7 +249,7 @@ mcp.setRequestHandler(CallToolRequestSchema, async (req) => {
249
249
  }
250
250
  }
251
251
 
252
- // Proxy all other hive-* tools to hive.* with `as: agentId` injected
252
+ // Proxy all other hive-* tools to hive_* with `as: agentId` injected
253
253
  if (!name.startsWith('hive-')) throw new Error(`Unknown tool: ${name}`)
254
254
  const hiveName = kebabToHive(name)
255
255
  const result = await hiveCallTool(hiveName, { as: agentId, ...args })
@@ -285,7 +285,7 @@ async function listenSSE() {
285
285
  console.error(`[hive-channel] SSE connect failed: ${res.status}, re-registering...`)
286
286
  try {
287
287
  await initHiveSession()
288
- if (agentId) await hiveCallTool('hive.start', { id: agentId, tool: 'claude', roles: 'channel' })
288
+ if (agentId) await hiveCallTool('hive_start', { id: agentId, tool: 'claude', roles: 'channel' })
289
289
  } catch (e) {
290
290
  console.error(`[hive-channel] re-register failed:`, e)
291
291
  }
package/dist/auth.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { getAgentById, touchAgent } from './db.js';
3
3
  import { sessionAgents } from './sessions.js';
4
- export const asParam = z.string().optional().describe('Your agent id (from hive.start). Optional — session binding takes precedence.');
4
+ export const asParam = z.string().optional().describe('Your agent id (from hive_start). Optional — session binding takes precedence.');
5
5
  export function resolveAgent(extra, asValue) {
6
6
  // 1) Session binding (preferred)
7
7
  const sessionId = extra?.sessionId;
@@ -27,7 +27,7 @@ export function resolveAgent(extra, asValue) {
27
27
  }
28
28
  export function authError() {
29
29
  return {
30
- content: [{ type: 'text', text: 'Error: Not authenticated. Call hive.start first.' }],
30
+ content: [{ type: 'text', text: 'Error: Not authenticated. Call hive_start first.' }],
31
31
  isError: true,
32
32
  };
33
33
  }
package/dist/index.js CHANGED
@@ -186,7 +186,7 @@ async function cmdInit() {
186
186
  console.log(` ${t.padEnd(12)} ${path}`);
187
187
  }
188
188
  }
189
- console.log(`\n Agent registers via hive.start when first used.`);
189
+ console.log(`\n Agent registers via hive_start when first used.`);
190
190
  }
191
191
  function relativeTime(iso) {
192
192
  if (!iso)
@@ -34,7 +34,7 @@ function listLocalAgents(db) {
34
34
  export async function pickLocalAgent(db, message) {
35
35
  const agents = listLocalAgents(db);
36
36
  if (agents.length === 0) {
37
- console.log('No local agents registered yet. (Agents register on first hive.start.)');
37
+ console.log('No local agents registered yet. (Agents register on first hive_start.)');
38
38
  process.exit(1);
39
39
  }
40
40
  if (agents.length === 1)
@@ -51,7 +51,7 @@ export async function pickLocalAgent(db, message) {
51
51
  export async function pickLocalAgents(db, message, initial = []) {
52
52
  const agents = listLocalAgents(db);
53
53
  if (agents.length === 0) {
54
- console.log('No local agents to expose. Register one first via hive.start.');
54
+ console.log('No local agents to expose. Register one first via hive_start.');
55
55
  return [];
56
56
  }
57
57
  return askMultiselect({
@@ -4,7 +4,7 @@ import { renameAgent, listAllAgents, getAgentsByName } from '../db.js';
4
4
  import { asParam, authError, resolveAgent } from '../auth.js';
5
5
  import { bindSession } from '../sessions.js';
6
6
  export function registerAgentTools(mcp) {
7
- mcp.tool('hive.start', 'Register or reconnect as an agent. Returns your agent_id (used for cross-team addressing). The MCP session is automatically bound to the returned agent so push notifications target the right session. (Channel-plugin users: prefer hive-whoami for first-time registration; this is the underlying server tool.)', {
7
+ mcp.tool('hive_start', 'Register or reconnect as an agent. Returns your agent_id (used for cross-team addressing). The MCP session is automatically bound to the returned agent so push notifications target the right session. (Channel-plugin users: prefer hive-whoami for first-time registration; this is the underlying server tool.)', {
8
8
  id: z.string().optional().describe('Agent id to reconnect to (exact match). Errors if not found.'),
9
9
  name: z.string().optional().describe('Display name (random if omitted). Reuses latest existing agent with this name.'),
10
10
  roles: z.string().optional().describe('Comma-separated roles: ux,frontend,backend'),
@@ -16,11 +16,11 @@ export function registerAgentTools(mcp) {
16
16
  bindSession(extra.sessionId, result.agent_id);
17
17
  }
18
18
  else {
19
- console.warn(`[hive.start] WARNING: no sessionId in extra (stateless?). agent=${result.agent_id} will NOT receive push.`);
19
+ console.warn(`[hive_start] WARNING: no sessionId in extra (stateless?). agent=${result.agent_id} will NOT receive push.`);
20
20
  }
21
21
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
22
22
  });
23
- mcp.tool('hive.whoami', 'Show the agent currently bound to this session (or via `as`).', { as: asParam }, async (params, extra) => {
23
+ mcp.tool('hive_whoami', 'Show the agent currently bound to this session (or via `as`).', { as: asParam }, async (params, extra) => {
24
24
  const agent = resolveAgent(extra, params.as);
25
25
  if (!agent)
26
26
  return authError();
@@ -38,7 +38,7 @@ export function registerAgentTools(mcp) {
38
38
  }],
39
39
  };
40
40
  });
41
- mcp.tool('hive.rename', 'Change your global display_name (display only — addressing uses id).', {
41
+ mcp.tool('hive_rename', 'Change your global display_name (display only — addressing uses id).', {
42
42
  as: asParam,
43
43
  name: z.string().describe('New display name'),
44
44
  }, async (params, extra) => {
@@ -49,7 +49,7 @@ export function registerAgentTools(mcp) {
49
49
  renameAgent(agent.id, params.name);
50
50
  return { content: [{ type: 'text', text: JSON.stringify({ agent_id: agent.id, old_name: oldName, new_name: params.name }) }] };
51
51
  });
52
- mcp.tool('hive.agents', 'List all known agents on this hive. Use this to find agent ids for cross-team DM/task.', {
52
+ mcp.tool('hive_agents', 'List all known agents on this hive. Use this to find agent ids for cross-team DM/task.', {
53
53
  active_only: z.boolean().optional().describe('Only show active agents (default false)'),
54
54
  name: z.string().optional().describe('Filter by display_name (may match multiple)'),
55
55
  }, async (params) => {
@@ -9,9 +9,9 @@ import { getFilePath, getFileMeta } from '../files.js';
9
9
  import { buildDMPreview, CHANNEL_PREVIEW_LEN } from '../preview.js';
10
10
  import * as db from '../db.js';
11
11
  export function registerDMTools(mcp) {
12
- mcp.tool('hive.dm', 'Send a direct message to another agent. `to` accepts agent id, team-nickname (within your teams), display_name (only if unambiguous), or "id@node" for federation. ' +
12
+ mcp.tool('hive_dm', 'Send a direct message to another agent. `to` accepts agent id, team-nickname (within your teams), display_name (only if unambiguous), or "id@node" for federation. ' +
13
13
  'IMPORTANT: any file path you mention in `content` is local-to-YOUR-machine ONLY — the receiver cannot read it (they may be on a different OS). ' +
14
- 'To actually share a file, pass `attach: [absolute path on YOUR disk]`; hive copies the bytes into storage (replicating across federation if needed) and the receiver gets a `file_id` they fetch via `hive.file.fetch`. ' +
14
+ 'To actually share a file, pass `attach: [absolute path on YOUR disk]`; hive copies the bytes into storage (replicating across federation if needed) and the receiver gets a `file_id` they fetch via `hive_file_fetch`. ' +
15
15
  'Use `attach` for screenshots, PDFs, CSVs, logs, pasted-image temp files, anything binary.', {
16
16
  as: asParam,
17
17
  to: z.string().describe('Target: agent id, team-nickname, display_name, or "id@node"'),
@@ -37,7 +37,7 @@ export function registerDMTools(mcp) {
37
37
  }
38
38
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
39
39
  });
40
- mcp.tool('hive.dm.read', 'Fetch a single DM in full by message_id. Use whenever a preview contains a `[hive note]` paragraph mentioning hive-dm-read — the visible text is only the first 200/2000 characters. Returns the full content, attachments, sender, and timestamp.', { message_id: z.number().describe('Message id — the integer N from the truncation hint, from `message_id` in hive-inbox latest entries, or from the `message_id` meta field on channel notifications') }, async (params) => {
40
+ mcp.tool('hive_dm_read', 'Fetch a single DM in full by message_id. Use whenever a preview contains a `[hive note]` paragraph mentioning hive-dm-read — the visible text is only the first 200/2000 characters. Returns the full content, attachments, sender, and timestamp.', { message_id: z.number().describe('Message id — the integer N from the truncation hint, from `message_id` in hive-inbox latest entries, or from the `message_id` meta field on channel notifications') }, async (params) => {
41
41
  const msg = db.getDMById(params.message_id);
42
42
  if (!msg)
43
43
  return { content: [{ type: 'text', text: JSON.stringify({ error: `Message not found: ${params.message_id}` }) }], isError: true };
@@ -62,7 +62,7 @@ export function registerDMTools(mcp) {
62
62
  }],
63
63
  };
64
64
  });
65
- mcp.tool('hive.file.fetch', 'Fetch an attachment by file_id (from a DM you received). Returns a `path` on the hive node serving you (your local hive when running locally; the receiver\'s hive in federated setups — already replicated). Pass `save_to` to copy it to a path of your choice (a trailing "/" treats it as a directory and keeps the original filename).', {
65
+ mcp.tool('hive_file_fetch', 'Fetch an attachment by file_id (from a DM you received). Returns a `path` on the hive node serving you (your local hive when running locally; the receiver\'s hive in federated setups — already replicated). Pass `save_to` to copy it to a path of your choice (a trailing "/" treats it as a directory and keeps the original filename).', {
66
66
  file_id: z.string().describe('Attachment file_id (from hive-inbox latest entry attachments, or from a channel notification)'),
67
67
  save_to: z.string().optional().describe('Optional destination path; pass a trailing "/" to copy into a directory keeping the original filename.'),
68
68
  }, async (params) => {
@@ -82,7 +82,7 @@ export function registerDMTools(mcp) {
82
82
  }
83
83
  return { content: [{ type: 'text', text: JSON.stringify({ ...meta, path: sourcePath, saved_to: saved }) }] };
84
84
  });
85
- mcp.tool('hive.inbox', 'Check unread DMs, team events, and task events. Marks returned items as read.', { as: asParam }, async (params, extra) => {
85
+ mcp.tool('hive_inbox', 'Check unread DMs, team events, and task events. Marks returned items as read.', { as: asParam }, async (params, extra) => {
86
86
  const agent = resolveAgent(extra, params.as);
87
87
  if (!agent)
88
88
  return authError();
@@ -16,7 +16,7 @@ async function fetchRemoteAgents(peer) {
16
16
  return res.json();
17
17
  }
18
18
  export function registerFederationTools(mcp) {
19
- mcp.tool('hive.peers', 'List all known federation peers (each entry includes name, status active/inactive, exposed agents, last_seen).', {}, async () => {
19
+ mcp.tool('hive_peers', 'List all known federation peers (each entry includes name, status active/inactive, exposed agents, last_seen).', {}, async () => {
20
20
  const peers = db.listPeers();
21
21
  const result = peers.map(p => ({
22
22
  name: p.name, node: p.node_name, url: p.url, status: p.status,
@@ -24,7 +24,7 @@ export function registerFederationTools(mcp) {
24
24
  }));
25
25
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
26
26
  });
27
- mcp.tool('hive.remote.agents', 'List agents that a remote peer has exposed to us. Cached 5 minutes; pass fresh=true to bypass. After getting an agent id from here, address them in DM/task as "<agent_id>@<peer-name>".', {
27
+ mcp.tool('hive_remote_agents', 'List agents that a remote peer has exposed to us. Cached 5 minutes; pass fresh=true to bypass. After getting an agent id from here, address them in DM/task as "<agent_id>@<peer-name>".', {
28
28
  peer: z.string().describe('Peer name (as shown by hive-peers)'),
29
29
  fresh: z.boolean().optional().describe('Bypass the 5-minute cache and re-query the peer'),
30
30
  }, async (params) => {
@@ -19,19 +19,19 @@ export function createMcpServer() {
19
19
  'kitty-hive is a multi-agent collaboration server.',
20
20
  '',
21
21
  '## Identity',
22
- '- agent_id (ULID) is your stable cross-team handle. Get it from hive.start.',
22
+ '- agent_id (ULID) is your stable cross-team handle. Get it from hive_start.',
23
23
  '- display_name is for display only, not unique.',
24
- '- Inside a team, members can have unique nicknames (set with hive.team.nickname).',
24
+ '- Inside a team, members can have unique nicknames (set with hive_team_nickname).',
25
25
  '',
26
26
  '## Addressing',
27
27
  '- DM and task `to`: prefer agent_id. team-nickname or display_name also accepted if unambiguous.',
28
28
  '- Federation: use "id@node" or "nickname@node".',
29
29
  '',
30
30
  '## Workflow rules',
31
- '- When you receive a task, propose a workflow (hive.workflow.propose) before starting.',
32
- '- The creator approves (hive.workflow.approve). NEVER auto-approve — show the proposal to the user.',
33
- '- Mark each step with hive.workflow.step.complete.',
34
- '- Claim unassigned tasks with hive.task.claim.',
31
+ '- When you receive a task, propose a workflow (hive_workflow_propose) before starting.',
32
+ '- The creator approves (hive_workflow_approve). NEVER auto-approve — show the proposal to the user.',
33
+ '- Mark each step with hive_workflow_step_complete.',
34
+ '- Claim unassigned tasks with hive_task_claim.',
35
35
  '',
36
36
  '## Artifacts',
37
37
  'Use ~/.kitty-hive/artifacts/<task_id>/ for cross-agent file exchange.',
@@ -47,7 +47,7 @@ export function createMcpServer() {
47
47
  const sessionId = extra?.sessionId;
48
48
  const agentId = sessionId ? sessionAgents.get(sessionId) : undefined;
49
49
  if (!agentId) {
50
- return { contents: [{ uri: uri.href, text: '{"error":"Not authenticated. Call hive.start first."}' }] };
50
+ return { contents: [{ uri: uri.href, text: '{"error":"Not authenticated. Call hive_start first."}' }] };
51
51
  }
52
52
  const unread = getUnreadForAgent(agentId);
53
53
  return {
@@ -4,7 +4,7 @@ import { asParam, authError, resolveAgent } from '../auth.js';
4
4
  import { notifyAgents, notifyTaskParticipants } from '../sessions.js';
5
5
  import * as db from '../db.js';
6
6
  export function registerTaskTools(mcp) {
7
- mcp.tool('hive.task', 'Create a task and (optionally) delegate. Omit `to` to create an unassigned task that anyone can claim.', {
7
+ mcp.tool('hive_task', 'Create a task and (optionally) delegate. Omit `to` to create an unassigned task that anyone can claim.', {
8
8
  as: asParam,
9
9
  to: z.string().optional().describe('Target. Accepts: agent id (always works) · team-nickname (only resolved within teams you both belong to) · display_name (only if globally unambiguous) · "role:xxx" (picks an active agent with that role) · "id@<peer-name>" for federation.'),
10
10
  title: z.string().describe('Task title'),
@@ -22,7 +22,7 @@ export function registerTaskTools(mcp) {
22
22
  }
23
23
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
24
24
  });
25
- mcp.tool('hive.task.claim', 'Claim an unassigned task (status "created", no assignee).', { as: asParam, task_id: z.string().describe('Task id') }, async (params, extra) => {
25
+ mcp.tool('hive_task_claim', 'Claim an unassigned task (status "created", no assignee).', { as: asParam, task_id: z.string().describe('Task id') }, async (params, extra) => {
26
26
  const agent = resolveAgent(extra, params.as);
27
27
  if (!agent)
28
28
  return authError();
@@ -33,7 +33,7 @@ export function registerTaskTools(mcp) {
33
33
  }));
34
34
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
35
35
  });
36
- mcp.tool('hive.tasks', 'List tasks you created or are assigned to.', {
36
+ mcp.tool('hive_tasks', 'List tasks you created or are assigned to.', {
37
37
  as: asParam,
38
38
  status: z.string().optional().describe('Filter by status. Valid values: created, proposing, approved, in_progress, awaiting_approval, completed, failed, canceled.'),
39
39
  }, async (params, extra) => {
@@ -51,11 +51,11 @@ export function registerTaskTools(mcp) {
51
51
  }));
52
52
  return { content: [{ type: 'text', text: JSON.stringify(board, null, 2) }] };
53
53
  });
54
- mcp.tool('hive.check', 'Check the current state of a task by id.', { task_id: z.string().describe('Task id') }, async (params) => {
54
+ mcp.tool('hive_check', 'Check the current state of a task by id.', { task_id: z.string().describe('Task id') }, async (params) => {
55
55
  const result = handleCheck(params);
56
56
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
57
57
  });
58
- mcp.tool('hive.workflow.propose', 'Propose a workflow for a task. Creator must approve before steps start. ' +
58
+ mcp.tool('hive_workflow_propose', 'Propose a workflow for a task. Creator must approve before steps start. ' +
59
59
  'For multi-phase workflows where the user (creator) will want to review the output between phases, ' +
60
60
  'set `gate: true` on each phase — the task then pauses in status `awaiting_approval` after each gated step ' +
61
61
  'until the creator calls hive-workflow-step-approve. Default (no gate) auto-advances to the next step.', {
@@ -81,7 +81,7 @@ export function registerTaskTools(mcp) {
81
81
  }));
82
82
  return { content: [{ type: 'text', text: JSON.stringify({ task_id: params.task_id, status: 'proposing', steps: params.workflow.length }) }] };
83
83
  });
84
- mcp.tool('hive.workflow.approve', 'Approve a proposed workflow (creator only). Starts step 1.', { as: asParam, task_id: z.string().describe('Task id') }, async (params, extra) => {
84
+ mcp.tool('hive_workflow_approve', 'Approve a proposed workflow (creator only). Starts step 1.', { as: asParam, task_id: z.string().describe('Task id') }, async (params, extra) => {
85
85
  const agent = resolveAgent(extra, params.as);
86
86
  if (!agent)
87
87
  return authError();
@@ -92,7 +92,7 @@ export function registerTaskTools(mcp) {
92
92
  }));
93
93
  return { content: [{ type: 'text', text: JSON.stringify({ task_id: params.task_id, status: 'approved', action }) }] };
94
94
  });
95
- mcp.tool('hive.workflow.step.complete', 'Mark your part of the current step as complete. If the step has `gate: true` and you were the last assignee, the task enters `awaiting_approval` instead of auto-advancing — the creator must call hive-workflow-step-approve to release the gate.', {
95
+ mcp.tool('hive_workflow_step_complete', 'Mark your part of the current step as complete. If the step has `gate: true` and you were the last assignee, the task enters `awaiting_approval` instead of auto-advancing — the creator must call hive-workflow-step-approve to release the gate.', {
96
96
  as: asParam,
97
97
  task_id: z.string().describe('Task id'),
98
98
  step: z.number().describe('Step number'),
@@ -126,7 +126,7 @@ export function registerTaskTools(mcp) {
126
126
  }));
127
127
  return { content: [{ type: 'text', text: JSON.stringify({ task_id: params.task_id, action: action || 'waiting' }) }] };
128
128
  });
129
- mcp.tool('hive.workflow.step.approve', 'Release a gated step\'s `awaiting_approval` pause. Creator-only. ' +
129
+ mcp.tool('hive_workflow_step_approve', 'Release a gated step\'s `awaiting_approval` pause. Creator-only. ' +
130
130
  'Call this after reviewing the output of a step that was proposed with `gate: true`. ' +
131
131
  'Advances to the next step (or completes the task if it was the last step).', {
132
132
  as: asParam,
@@ -145,7 +145,7 @@ export function registerTaskTools(mcp) {
145
145
  }));
146
146
  return { content: [{ type: 'text', text: JSON.stringify({ task_id: params.task_id, action }) }] };
147
147
  });
148
- mcp.tool('hive.workflow.reject', 'Reject the current step. Sends task back to a previous step.', {
148
+ mcp.tool('hive_workflow_reject', 'Reject the current step. Sends task back to a previous step.', {
149
149
  as: asParam,
150
150
  task_id: z.string().describe('Task id'),
151
151
  step: z.number().describe('Step being rejected'),
@@ -3,7 +3,7 @@ import { handleTeamCreate, handleTeamJoin, handleTeamList, handleTeamInfo, handl
3
3
  import { asParam, authError, resolveAgent } from '../auth.js';
4
4
  import { notifyTeamMembers } from '../sessions.js';
5
5
  export function registerTeamTools(mcp) {
6
- mcp.tool('hive.team.create', 'Create a new team. Optionally set your nickname in this team.', {
6
+ mcp.tool('hive_team_create', 'Create a new team. Optionally set your nickname in this team.', {
7
7
  as: asParam,
8
8
  name: z.string().describe('Team name (must be globally unique)'),
9
9
  nickname: z.string().optional().describe('Your nickname in this team (must be unique within team)'),
@@ -14,7 +14,7 @@ export function registerTeamTools(mcp) {
14
14
  const result = handleTeamCreate(agent.id, { name: params.name, nickname: params.nickname });
15
15
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
16
16
  });
17
- mcp.tool('hive.team.join', 'Join an existing team by name or id.', {
17
+ mcp.tool('hive_team_join', 'Join an existing team by name or id.', {
18
18
  as: asParam,
19
19
  team_id: z.string().optional().describe('Team id'),
20
20
  name: z.string().optional().describe('Team name'),
@@ -31,11 +31,11 @@ export function registerTeamTools(mcp) {
31
31
  }));
32
32
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
33
33
  });
34
- mcp.tool('hive.team.list', 'List all teams on this hive (with their members).', {}, async () => {
34
+ mcp.tool('hive_team_list', 'List all teams on this hive (with their members).', {}, async () => {
35
35
  const result = handleTeamList();
36
36
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
37
37
  });
38
- mcp.tool('hive.team.info', 'Get detailed info about a team you are in (members with nicknames + recent events).', {
38
+ mcp.tool('hive_team_info', 'Get detailed info about a team you are in (members with nicknames + recent events).', {
39
39
  as: asParam,
40
40
  team_id: z.string().describe('Team id'),
41
41
  }, async (params, extra) => {
@@ -45,7 +45,7 @@ export function registerTeamTools(mcp) {
45
45
  const result = handleTeamInfo(agent.id, { team_id: params.team_id });
46
46
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
47
47
  });
48
- mcp.tool('hive.team.events', 'Fetch events from a team. Use "since" for incremental polling.', {
48
+ mcp.tool('hive_team_events', 'Fetch events from a team. Use "since" for incremental polling.', {
49
49
  as: asParam,
50
50
  team_id: z.string().describe('Team id'),
51
51
  since: z.number().optional().describe('Return events after this seq number'),
@@ -57,7 +57,7 @@ export function registerTeamTools(mcp) {
57
57
  const result = handleTeamEvents(agent.id, { team_id: params.team_id, since: params.since, limit: params.limit });
58
58
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
59
59
  });
60
- mcp.tool('hive.team.message', 'Post a message to a team (broadcasts to all members).', {
60
+ mcp.tool('hive_team_message', 'Post a message to a team (broadcasts to all members).', {
61
61
  as: asParam,
62
62
  team_id: z.string().describe('Team id'),
63
63
  content: z.string().describe('Message content'),
@@ -73,7 +73,7 @@ export function registerTeamTools(mcp) {
73
73
  }));
74
74
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
75
75
  });
76
- mcp.tool('hive.team.nickname', 'Set or change your nickname within a team. Pass null to clear.', {
76
+ mcp.tool('hive_team_nickname', 'Set or change your nickname within a team. Pass null to clear.', {
77
77
  as: asParam,
78
78
  team_id: z.string().describe('Team id'),
79
79
  nickname: z.string().nullable().describe('New nickname (or null to clear)'),
@@ -84,7 +84,7 @@ export function registerTeamTools(mcp) {
84
84
  const result = handleTeamNickname(agent.id, { team_id: params.team_id, nickname: params.nickname });
85
85
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
86
86
  });
87
- mcp.tool('hive.teams', 'List teams you are a member of (with your nickname in each).', { as: asParam }, async (params, extra) => {
87
+ mcp.tool('hive_teams', 'List teams you are a member of (with your nickname in each).', { as: asParam }, async (params, extra) => {
88
88
  const agent = resolveAgent(extra, params.as);
89
89
  if (!agent)
90
90
  return authError();
package/dist/server.js CHANGED
@@ -68,7 +68,7 @@ export async function startServer(port, dbPath) {
68
68
  const sid = req.headers['mcp-session-id'];
69
69
  const method = body?.method || (Array.isArray(body) ? `batch[${body.length}]` : 'unknown');
70
70
  const tool = body?.params?.name || '';
71
- const isHeartbeat = tool === 'hive.inbox' || method === 'notifications/initialized';
71
+ const isHeartbeat = tool === 'hive_inbox' || method === 'notifications/initialized';
72
72
  if (!isHeartbeat) {
73
73
  log('info', `[rpc] method=${method} sid=${sid || 'none'} tool=${tool || '-'}`);
74
74
  }
@@ -188,7 +188,7 @@ export async function handleTaskClaimAsync(taskId, agentId) {
188
188
  await forwardTaskEvent(task, agentId, 'task-claim', {});
189
189
  return out;
190
190
  }
191
- // --- hive.task.claim ---
191
+ // --- hive_task_claim ---
192
192
  export function handleTaskClaim(taskId, agentId) {
193
193
  const task = getTaskById(taskId);
194
194
  if (!task)
@@ -1,5 +1,5 @@
1
1
  import { createTeam, getTeamById, getTeamByName, listTeams, getAgentTeams, addTeamMember, isTeamMember, getTeamMembers, getTeamMember, setTeamNickname, appendTeamEvent, getTeamEvents, getLatestTeamEvents, getAgentById, } from '../db.js';
2
- // --- hive.team.create ---
2
+ // --- hive_team_create ---
3
3
  export function handleTeamCreate(actorId, input) {
4
4
  const existing = getTeamByName(input.name);
5
5
  if (existing)
@@ -9,7 +9,7 @@ export function handleTeamCreate(actorId, input) {
9
9
  appendTeamEvent(team.id, 'join', actorId, { nickname: input.nickname ?? null });
10
10
  return { team_id: team.id, name: team.name };
11
11
  }
12
- // --- hive.team.join ---
12
+ // --- hive_team_join ---
13
13
  export function handleTeamJoin(actorId, input) {
14
14
  let team;
15
15
  if (input.team_id)
@@ -64,7 +64,7 @@ export function handleTeamInfo(actorId, input) {
64
64
  latest_events: getLatestTeamEvents(input.team_id, 10),
65
65
  };
66
66
  }
67
- // --- hive.team.events ---
67
+ // --- hive_team_events ---
68
68
  export function handleTeamEvents(actorId, input) {
69
69
  if (!isTeamMember(input.team_id, actorId))
70
70
  throw new Error('Not a member of this team');
@@ -75,14 +75,14 @@ export function handleTeamEvents(actorId, input) {
75
75
  events.pop();
76
76
  return { events, has_more: hasMore };
77
77
  }
78
- // --- hive.team.message ---
78
+ // --- hive_team_message ---
79
79
  export function handleTeamMessage(actorId, input) {
80
80
  if (!isTeamMember(input.team_id, actorId))
81
81
  throw new Error('Not a member of this team');
82
82
  const event = appendTeamEvent(input.team_id, 'message', actorId, { content: input.content });
83
83
  return { team_id: input.team_id, event_id: event.id, seq: event.seq };
84
84
  }
85
- // --- hive.team.nickname ---
85
+ // --- hive_team_nickname ---
86
86
  export function handleTeamNickname(actorId, input) {
87
87
  if (!isTeamMember(input.team_id, actorId))
88
88
  throw new Error('Not a member of this team');
@@ -98,7 +98,7 @@ export function handleTeamNickname(actorId, input) {
98
98
  });
99
99
  return { team_id: input.team_id, nickname: input.nickname, old_nickname: member.nickname };
100
100
  }
101
- // --- hive.team.list (mine) ---
101
+ // --- hive_team_list (mine) ---
102
102
  export function handleMyTeams(actorId) {
103
103
  const teams = getAgentTeams(actorId, true);
104
104
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kitty-hive",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "MCP server for multi-agent collaboration — DM, tasks, workflow, federation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",