@spilno/herald-mcp 1.1.4 → 1.4.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.
- package/README.md +102 -60
- package/dist/index.d.ts +4 -2
- package/dist/index.js +648 -171
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,62 +2,121 @@
|
|
|
2
2
|
|
|
3
3
|
Herald MCP - AI-native interface to [CEDA](https://getceda.com) (Cognitive Event-Driven Architecture).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Dual-mode**: Natural chat for humans, MCP for AI agents.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
# Run directly (no install)
|
|
9
|
-
npx @spilno/herald-mcp
|
|
7
|
+
## Quick Start
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
```bash
|
|
12
10
|
npm install -g @spilno/herald-mcp
|
|
13
|
-
|
|
11
|
+
|
|
12
|
+
export HERALD_API_URL=https://getceda.com
|
|
13
|
+
|
|
14
|
+
herald-mcp chat
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That's it. Chat mode works out of the box.
|
|
18
|
+
|
|
19
|
+
## Chat Mode (Natural Conversation)
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
herald-mcp chat
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
You: I need a safety assessment module for construction sites
|
|
27
|
+
Herald: I've designed a Safety Assessment module with 4 sections...
|
|
28
|
+
|
|
29
|
+
You: Add OSHA compliance fields
|
|
30
|
+
Herald: Done. Added compliance checklist to Risk Evaluation...
|
|
31
|
+
|
|
32
|
+
You: Looks good, let's use it
|
|
33
|
+
Herald: Great! Module accepted and saved.
|
|
14
34
|
```
|
|
15
35
|
|
|
16
|
-
##
|
|
36
|
+
## Command Mode (Structured)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
herald-mcp predict "create safety assessment"
|
|
40
|
+
herald-mcp refine "add OSHA compliance"
|
|
41
|
+
herald-mcp resume
|
|
42
|
+
herald-mcp observe yes
|
|
43
|
+
herald-mcp new
|
|
44
|
+
herald-mcp health
|
|
45
|
+
herald-mcp stats
|
|
46
|
+
```
|
|
17
47
|
|
|
18
|
-
|
|
48
|
+
## Session Persistence
|
|
49
|
+
|
|
50
|
+
Sessions saved to `~/.herald/session` - resume anytime:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Start today
|
|
54
|
+
herald-mcp predict "create incident report module"
|
|
55
|
+
|
|
56
|
+
# Continue tomorrow
|
|
57
|
+
herald-mcp resume
|
|
58
|
+
herald-mcp refine "add witness statements section"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Environment Variables
|
|
19
62
|
|
|
20
63
|
| Variable | Required | Description |
|
|
21
64
|
|----------|----------|-------------|
|
|
22
|
-
| `HERALD_API_URL` | Yes | CEDA server URL
|
|
23
|
-
| `HERALD_API_TOKEN` | No | Bearer token for
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
65
|
+
| `HERALD_API_URL` | Yes | CEDA server URL |
|
|
66
|
+
| `HERALD_API_TOKEN` | No | Bearer token for auth |
|
|
67
|
+
| `HERALD_COMPANY` | No | Multi-tenant company context |
|
|
68
|
+
| `HERALD_PROJECT` | No | Multi-tenant project context |
|
|
69
|
+
| `HERALD_VAULT` | No | Offspring vault ID (spilno, goprint, disrupt) |
|
|
70
|
+
| `HERALD_OFFSPRING_CLOUD` | No | Set to "true" for cloud mode |
|
|
71
|
+
| `AEGIS_OFFSPRING_PATH` | No | Local path to offspring status files |
|
|
26
72
|
|
|
27
|
-
##
|
|
73
|
+
## Herald Context Sync (v1.3.0+)
|
|
28
74
|
|
|
29
|
-
Herald
|
|
75
|
+
Herald instances can communicate across contexts, sharing insights and synchronizing status:
|
|
30
76
|
|
|
31
|
-
|
|
32
|
-
|------|-------------|
|
|
33
|
-
| `herald_health` | Check CEDA system status |
|
|
34
|
-
| `herald_stats` | Get server statistics and loaded patterns |
|
|
35
|
-
| `herald_predict` | Generate prediction from natural language input |
|
|
36
|
-
| `herald_refine` | Refine existing prediction with additional requirements |
|
|
37
|
-
| `herald_session` | Get session history and state |
|
|
38
|
-
| `herald_observe` | Record prediction feedback for learning |
|
|
77
|
+
### Tools
|
|
39
78
|
|
|
40
|
-
|
|
79
|
+
| Tool | Purpose |
|
|
80
|
+
|------|---------|
|
|
81
|
+
| `herald_context_status` | Read status from Herald contexts across domains |
|
|
82
|
+
| `herald_share_insight` | Share a pattern insight with another context |
|
|
83
|
+
| `herald_query_insights` | Query accumulated insights on a topic |
|
|
41
84
|
|
|
42
|
-
|
|
85
|
+
### Local Mode (Default)
|
|
43
86
|
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
87
|
+
```bash
|
|
88
|
+
# Context files in local directory
|
|
89
|
+
export AEGIS_OFFSPRING_PATH=~/Documents/aegis_ceda/_offspring
|
|
90
|
+
export HERALD_VAULT=spilno # Which context this Herald serves
|
|
91
|
+
|
|
92
|
+
herald-mcp # MCP mode uses local files
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Cloud Mode
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Use CEDA API for context synchronization
|
|
99
|
+
export HERALD_API_URL=https://getceda.com
|
|
100
|
+
export HERALD_OFFSPRING_CLOUD=true
|
|
101
|
+
export HERALD_VAULT=spilno
|
|
102
|
+
|
|
103
|
+
herald-mcp # MCP mode calls CEDA API
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Herald-to-Herald Protocol
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
POST /api/herald/heartbeat # Herald reports its context status
|
|
110
|
+
GET /api/herald/contexts # Herald discovers sibling contexts
|
|
111
|
+
POST /api/herald/insight # Herald shares an insight
|
|
112
|
+
GET /api/herald/insights # Herald queries shared insights
|
|
56
113
|
```
|
|
57
114
|
|
|
58
|
-
##
|
|
115
|
+
## MCP Mode (for AI Agents)
|
|
59
116
|
|
|
60
|
-
|
|
117
|
+
When piped, Herald speaks JSON-RPC for Claude, Devin, and other AI agents.
|
|
118
|
+
|
|
119
|
+
### Claude Code (~/.claude.json)
|
|
61
120
|
|
|
62
121
|
```json
|
|
63
122
|
{
|
|
@@ -73,34 +132,17 @@ Add to `claude_desktop_config.json`:
|
|
|
73
132
|
}
|
|
74
133
|
```
|
|
75
134
|
|
|
76
|
-
|
|
135
|
+
### Devin MCP Marketplace
|
|
77
136
|
|
|
78
|
-
In Devin MCP marketplace:
|
|
79
137
|
- **Server Name**: Herald-CEDA
|
|
80
|
-
- **
|
|
81
|
-
- **Command**: `npx`
|
|
82
|
-
- **Arguments**: `@spilno/herald-mcp`
|
|
138
|
+
- **Command**: `npx @spilno/herald-mcp`
|
|
83
139
|
- **Environment**: `HERALD_API_URL=https://getceda.com`
|
|
84
140
|
|
|
85
|
-
##
|
|
86
|
-
|
|
87
|
-
Herald supports session-based conversations for iterative refinement:
|
|
141
|
+
## Architecture
|
|
88
142
|
|
|
89
143
|
```
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
2. herald_refine(sessionId, "add OSHA compliance fields")
|
|
94
|
-
→ Returns refined prediction
|
|
95
|
-
|
|
96
|
-
3. herald_refine(sessionId, "include corrective actions workflow")
|
|
97
|
-
→ Returns further refined prediction
|
|
98
|
-
|
|
99
|
-
4. herald_session(sessionId)
|
|
100
|
-
→ Returns full history of all turns
|
|
101
|
-
|
|
102
|
-
5. herald_observe(sessionId, accepted=true)
|
|
103
|
-
→ Records feedback for learning
|
|
144
|
+
Human ←→ Herald (Claude voice) ←→ CEDA (cognition)
|
|
145
|
+
Agent ←→ Herald (JSON-RPC) ←→ CEDA
|
|
104
146
|
```
|
|
105
147
|
|
|
106
148
|
## License
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Herald MCP -
|
|
3
|
+
* Herald MCP - AI-native interface to CEDA ecosystem
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Dual-mode:
|
|
6
|
+
* - CLI mode (TTY): Natural commands for humans
|
|
7
|
+
* - MCP mode (piped): JSON-RPC for AI agents
|
|
6
8
|
*/
|
|
7
9
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,219 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Herald MCP -
|
|
3
|
+
* Herald MCP - AI-native interface to CEDA ecosystem
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Dual-mode:
|
|
6
|
+
* - CLI mode (TTY): Natural commands for humans
|
|
7
|
+
* - MCP mode (piped): JSON-RPC for AI agents
|
|
6
8
|
*/
|
|
7
9
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
10
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
11
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
13
|
+
import { homedir } from "os";
|
|
14
|
+
import { join } from "path";
|
|
10
15
|
// Configuration - all sensitive values from environment only
|
|
11
16
|
const CEDA_API_URL = process.env.HERALD_API_URL;
|
|
12
|
-
const CEDA_API_TOKEN = process.env.HERALD_API_TOKEN;
|
|
13
|
-
const CEDA_API_USER = process.env.HERALD_API_USER;
|
|
14
|
-
const CEDA_API_PASS = process.env.HERALD_API_PASS;
|
|
15
|
-
|
|
17
|
+
const CEDA_API_TOKEN = process.env.HERALD_API_TOKEN;
|
|
18
|
+
const CEDA_API_USER = process.env.HERALD_API_USER;
|
|
19
|
+
const CEDA_API_PASS = process.env.HERALD_API_PASS;
|
|
20
|
+
// Multi-tenant context
|
|
21
|
+
const HERALD_COMPANY = process.env.HERALD_COMPANY || "default";
|
|
22
|
+
const HERALD_PROJECT = process.env.HERALD_PROJECT || "default";
|
|
23
|
+
const HERALD_USER = process.env.HERALD_USER || "default";
|
|
24
|
+
// Offspring vault context (for Avatar mode)
|
|
25
|
+
const HERALD_VAULT = process.env.HERALD_VAULT || ""; // e.g., "spilno", "goprint", "disrupt"
|
|
26
|
+
const AEGIS_OFFSPRING_PATH = process.env.AEGIS_OFFSPRING_PATH || join(homedir(), "Documents", "aegis_ceda", "_offspring");
|
|
27
|
+
// Cloud mode: Use CEDA API for offspring communication instead of local files
|
|
28
|
+
const OFFSPRING_CLOUD_MODE = process.env.HERALD_OFFSPRING_CLOUD === "true";
|
|
29
|
+
const VERSION = "1.3.0";
|
|
30
|
+
// Claude for Herald's voice - bundled key with limits, users can override
|
|
31
|
+
const HERALD_VOICE_KEY = "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";
|
|
32
|
+
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || HERALD_VOICE_KEY;
|
|
33
|
+
// Session persistence - context-isolated paths
|
|
34
|
+
// ~/.herald/{company}/{project}/{user}/session
|
|
35
|
+
function getHeraldDir() {
|
|
36
|
+
return join(homedir(), ".herald", HERALD_COMPANY, HERALD_PROJECT, HERALD_USER);
|
|
37
|
+
}
|
|
38
|
+
function getSessionFile() {
|
|
39
|
+
return join(getHeraldDir(), "session");
|
|
40
|
+
}
|
|
41
|
+
function ensureHeraldDir() {
|
|
42
|
+
const dir = getHeraldDir();
|
|
43
|
+
if (!existsSync(dir)) {
|
|
44
|
+
mkdirSync(dir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function saveSession(sessionId) {
|
|
48
|
+
ensureHeraldDir();
|
|
49
|
+
writeFileSync(getSessionFile(), sessionId, "utf-8");
|
|
50
|
+
}
|
|
51
|
+
function loadSession() {
|
|
52
|
+
const sessionFile = getSessionFile();
|
|
53
|
+
if (existsSync(sessionFile)) {
|
|
54
|
+
return readFileSync(sessionFile, "utf-8").trim();
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
function clearSession() {
|
|
59
|
+
const sessionFile = getSessionFile();
|
|
60
|
+
if (existsSync(sessionFile)) {
|
|
61
|
+
unlinkSync(sessionFile);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Context info for display
|
|
65
|
+
function getContextString() {
|
|
66
|
+
return `${HERALD_COMPANY}:${HERALD_PROJECT}:${HERALD_USER}`;
|
|
67
|
+
}
|
|
68
|
+
const HERALD_SYSTEM_PROMPT = `You are Herald, the voice of CEDA (Cognitive Event-Driven Architecture).
|
|
69
|
+
You help humans design module structures through natural conversation.
|
|
70
|
+
|
|
71
|
+
You have access to CEDA's cognitive capabilities:
|
|
72
|
+
- Predict: Generate structure predictions from requirements
|
|
73
|
+
- Refine: Improve predictions with additional requirements
|
|
74
|
+
- Session: Track conversation history
|
|
75
|
+
|
|
76
|
+
When users describe what they want, you:
|
|
77
|
+
1. Call CEDA to generate/refine predictions
|
|
78
|
+
2. Explain the results in natural language
|
|
79
|
+
3. Ask clarifying questions when needed
|
|
80
|
+
|
|
81
|
+
Keep responses concise and focused. You're a helpful assistant, not verbose.
|
|
82
|
+
When showing module structures, summarize the key sections and fields.`;
|
|
83
|
+
async function callClaude(systemPrompt, messages) {
|
|
84
|
+
// Convert to Anthropic format (separate system from messages)
|
|
85
|
+
const anthropicMessages = messages
|
|
86
|
+
.filter(m => m.role !== "system")
|
|
87
|
+
.map(m => ({ role: m.role, content: m.content }));
|
|
88
|
+
// Combine all system messages
|
|
89
|
+
const systemContent = messages
|
|
90
|
+
.filter(m => m.role === "system")
|
|
91
|
+
.map(m => m.content)
|
|
92
|
+
.join("\n\n");
|
|
93
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: {
|
|
96
|
+
"Content-Type": "application/json",
|
|
97
|
+
"x-api-key": ANTHROPIC_API_KEY,
|
|
98
|
+
"anthropic-version": "2023-06-01",
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify({
|
|
101
|
+
model: "claude-sonnet-4-20250514",
|
|
102
|
+
max_tokens: 1000,
|
|
103
|
+
system: systemPrompt + (systemContent ? "\n\n" + systemContent : ""),
|
|
104
|
+
messages: anthropicMessages.length > 0 ? anthropicMessages : [{ role: "user", content: "Hello" }],
|
|
105
|
+
}),
|
|
106
|
+
});
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
const error = await response.text();
|
|
109
|
+
return `Claude error: ${error}`;
|
|
110
|
+
}
|
|
111
|
+
const data = await response.json();
|
|
112
|
+
return data.content[0]?.text || "No response from Claude";
|
|
113
|
+
}
|
|
114
|
+
async function translateAndExecute(userInput, conversationHistory) {
|
|
115
|
+
const sessionId = loadSession();
|
|
116
|
+
// First, ask Claude to interpret the user's intent
|
|
117
|
+
const interpretSystemPrompt = `You interpret user requests for CEDA.
|
|
118
|
+
Respond with JSON only: {"action": "predict"|"refine"|"info"|"accept"|"reject", "input": "the user's requirement"}
|
|
119
|
+
- predict: User wants to create something new
|
|
120
|
+
- refine: User wants to modify/add to current design (requires active session)
|
|
121
|
+
- info: User is asking a question
|
|
122
|
+
- accept: User approves the current design
|
|
123
|
+
- reject: User rejects/wants to start over
|
|
124
|
+
|
|
125
|
+
Current session: ${sessionId || "none"}`;
|
|
126
|
+
const interpretation = await callClaude(interpretSystemPrompt, [
|
|
127
|
+
{ role: "user", content: userInput }
|
|
128
|
+
]);
|
|
129
|
+
let cedaResult = null;
|
|
130
|
+
let action = "info";
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(interpretation);
|
|
133
|
+
action = parsed.action;
|
|
134
|
+
const input = parsed.input;
|
|
135
|
+
if (action === "predict") {
|
|
136
|
+
cedaResult = await callCedaAPI("/api/predict", "POST", {
|
|
137
|
+
input,
|
|
138
|
+
config: { enableAutoFix: true, maxAutoFixAttempts: 3 },
|
|
139
|
+
});
|
|
140
|
+
if (cedaResult.sessionId) {
|
|
141
|
+
saveSession(cedaResult.sessionId);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else if (action === "refine" && sessionId) {
|
|
145
|
+
cedaResult = await callCedaAPI("/api/refine", "POST", {
|
|
146
|
+
sessionId,
|
|
147
|
+
refinement: input,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else if (action === "accept" && sessionId) {
|
|
151
|
+
cedaResult = await callCedaAPI("/api/feedback", "POST", {
|
|
152
|
+
sessionId,
|
|
153
|
+
accepted: true,
|
|
154
|
+
});
|
|
155
|
+
clearSession();
|
|
156
|
+
}
|
|
157
|
+
else if (action === "reject") {
|
|
158
|
+
clearSession();
|
|
159
|
+
cedaResult = { success: true, status: "Session cleared" };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Claude didn't return valid JSON, treat as info request
|
|
164
|
+
}
|
|
165
|
+
// Build response context
|
|
166
|
+
let responseContext = "";
|
|
167
|
+
if (cedaResult) {
|
|
168
|
+
responseContext = `\n\nCEDA ${action} result:\n${JSON.stringify(cedaResult, null, 2)}\n\nSummarize this naturally for the user.`;
|
|
169
|
+
}
|
|
170
|
+
// Ask Claude to formulate a natural response
|
|
171
|
+
const responseMessages = [
|
|
172
|
+
...conversationHistory,
|
|
173
|
+
{ role: "user", content: userInput },
|
|
174
|
+
];
|
|
175
|
+
return await callClaude(HERALD_SYSTEM_PROMPT + responseContext, responseMessages);
|
|
176
|
+
}
|
|
177
|
+
import * as readline from "readline";
|
|
178
|
+
async function runChatMode() {
|
|
179
|
+
// Key is bundled - chat works out of the box
|
|
180
|
+
const contextStr = getContextString();
|
|
181
|
+
console.log(`
|
|
182
|
+
Herald v${VERSION} - Chat Mode
|
|
183
|
+
Context: ${contextStr}
|
|
184
|
+
Type your requirements in natural language. Type 'exit' to quit.
|
|
185
|
+
──────────────────────────────────────────────────────────────
|
|
186
|
+
`);
|
|
187
|
+
const rl = readline.createInterface({
|
|
188
|
+
input: process.stdin,
|
|
189
|
+
output: process.stdout,
|
|
190
|
+
});
|
|
191
|
+
const conversationHistory = [];
|
|
192
|
+
const currentSession = loadSession();
|
|
193
|
+
if (currentSession) {
|
|
194
|
+
console.log(`Resuming session: ${currentSession}\n`);
|
|
195
|
+
}
|
|
196
|
+
const prompt = () => {
|
|
197
|
+
rl.question("You: ", async (input) => {
|
|
198
|
+
const trimmed = input.trim();
|
|
199
|
+
if (trimmed.toLowerCase() === "exit" || trimmed.toLowerCase() === "quit") {
|
|
200
|
+
console.log("\nGoodbye!");
|
|
201
|
+
rl.close();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (!trimmed) {
|
|
205
|
+
prompt();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
conversationHistory.push({ role: "user", content: trimmed });
|
|
209
|
+
const response = await translateAndExecute(trimmed, conversationHistory);
|
|
210
|
+
conversationHistory.push({ role: "assistant", content: response });
|
|
211
|
+
console.log(`\nHerald: ${response}\n`);
|
|
212
|
+
prompt();
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
prompt();
|
|
216
|
+
}
|
|
16
217
|
// Auth strategy: prefer Bearer token, fallback to Basic auth, allow none for demo
|
|
17
218
|
function getAuthHeader() {
|
|
18
219
|
if (CEDA_API_TOKEN) {
|
|
@@ -22,30 +223,43 @@ function getAuthHeader() {
|
|
|
22
223
|
const basicAuth = Buffer.from(`${CEDA_API_USER}:${CEDA_API_PASS}`).toString("base64");
|
|
23
224
|
return `Basic ${basicAuth}`;
|
|
24
225
|
}
|
|
25
|
-
return null;
|
|
226
|
+
return null;
|
|
26
227
|
}
|
|
27
228
|
async function callCedaAPI(endpoint, method = "GET", body) {
|
|
28
|
-
// Validate configuration
|
|
29
229
|
if (!CEDA_API_URL) {
|
|
30
230
|
return {
|
|
31
231
|
success: false,
|
|
32
|
-
error: "HERALD_API_URL not configured.
|
|
232
|
+
error: "HERALD_API_URL not configured. Run: export HERALD_API_URL=https://getceda.com"
|
|
33
233
|
};
|
|
34
234
|
}
|
|
35
|
-
|
|
235
|
+
// Add context params to URL for GET requests (except /health)
|
|
236
|
+
let url = `${CEDA_API_URL}${endpoint}`;
|
|
237
|
+
if (method === "GET" && endpoint.startsWith("/api/")) {
|
|
238
|
+
const separator = endpoint.includes("?") ? "&" : "?";
|
|
239
|
+
url += `${separator}company=${HERALD_COMPANY}&project=${HERALD_PROJECT}&user=${HERALD_USER}`;
|
|
240
|
+
}
|
|
36
241
|
const headers = {
|
|
37
242
|
"Content-Type": "application/json",
|
|
38
243
|
};
|
|
39
|
-
// Add auth header if configured (optional for demo)
|
|
40
244
|
const authHeader = getAuthHeader();
|
|
41
245
|
if (authHeader) {
|
|
42
246
|
headers["Authorization"] = authHeader;
|
|
43
247
|
}
|
|
248
|
+
// Add context to POST body
|
|
249
|
+
let enrichedBody = body;
|
|
250
|
+
if (method === "POST" && body && typeof body === "object") {
|
|
251
|
+
enrichedBody = {
|
|
252
|
+
...body,
|
|
253
|
+
company: HERALD_COMPANY,
|
|
254
|
+
project: HERALD_PROJECT,
|
|
255
|
+
user: HERALD_USER,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
44
258
|
try {
|
|
45
259
|
const response = await fetch(url, {
|
|
46
260
|
method,
|
|
47
261
|
headers,
|
|
48
|
-
body:
|
|
262
|
+
body: enrichedBody ? JSON.stringify(enrichedBody) : undefined,
|
|
49
263
|
});
|
|
50
264
|
if (!response.ok) {
|
|
51
265
|
return { success: false, error: `HTTP ${response.status}: ${response.statusText}` };
|
|
@@ -56,32 +270,181 @@ async function callCedaAPI(endpoint, method = "GET", body) {
|
|
|
56
270
|
return { success: false, error: `Connection failed: ${error}` };
|
|
57
271
|
}
|
|
58
272
|
}
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
273
|
+
// ============================================
|
|
274
|
+
// CLI MODE - Human-friendly commands
|
|
275
|
+
// ============================================
|
|
276
|
+
function printUsage() {
|
|
277
|
+
const currentSession = loadSession();
|
|
278
|
+
const contextStr = getContextString();
|
|
279
|
+
const sessionDir = getHeraldDir();
|
|
280
|
+
console.log(`
|
|
281
|
+
Herald MCP v${VERSION} - AI-native interface to CEDA
|
|
282
|
+
|
|
283
|
+
Context: ${contextStr}
|
|
284
|
+
Session: ${currentSession || "(none)"}
|
|
285
|
+
Path: ${sessionDir}
|
|
286
|
+
|
|
287
|
+
Usage:
|
|
288
|
+
herald-mcp <command> [options]
|
|
289
|
+
|
|
290
|
+
Commands:
|
|
291
|
+
chat Natural conversation mode (Claude voice)
|
|
292
|
+
predict "<signal>" Start new prediction (saves session)
|
|
293
|
+
refine "<text>" Refine current session
|
|
294
|
+
resume Show current session state
|
|
295
|
+
observe yes|no Record feedback & close session
|
|
296
|
+
new Clear session, start fresh
|
|
297
|
+
health Check CEDA system status
|
|
298
|
+
stats Get server statistics
|
|
299
|
+
|
|
300
|
+
Examples:
|
|
301
|
+
herald-mcp chat # Natural conversation
|
|
302
|
+
herald-mcp predict "create safety assessment" # Command mode
|
|
303
|
+
herald-mcp refine "add OSHA compliance"
|
|
304
|
+
herald-mcp observe yes
|
|
305
|
+
|
|
306
|
+
Environment:
|
|
307
|
+
HERALD_API_URL CEDA server URL (required)
|
|
308
|
+
HERALD_COMPANY Company context (default: default)
|
|
309
|
+
HERALD_PROJECT Project context (default: default)
|
|
310
|
+
HERALD_USER User context (default: default)
|
|
311
|
+
ANTHROPIC_API_KEY Claude key override (optional, bundled key available)
|
|
312
|
+
HERALD_API_TOKEN Bearer token (optional)
|
|
313
|
+
|
|
314
|
+
MCP Mode:
|
|
315
|
+
When piped, Herald speaks JSON-RPC for AI agents.
|
|
316
|
+
`);
|
|
317
|
+
}
|
|
318
|
+
function formatOutput(data) {
|
|
319
|
+
if (data.error) {
|
|
320
|
+
console.error(`Error: ${data.error}`);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
// Pretty print with session ID highlighted if present
|
|
324
|
+
if (data.sessionId) {
|
|
325
|
+
console.log(`\nSession: ${data.sessionId}\n`);
|
|
326
|
+
}
|
|
327
|
+
console.log(JSON.stringify(data, null, 2));
|
|
328
|
+
}
|
|
329
|
+
async function runCLI(args) {
|
|
330
|
+
const command = args[0]?.toLowerCase();
|
|
331
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
332
|
+
printUsage();
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (command === "--version" || command === "-v") {
|
|
336
|
+
console.log(`herald-mcp v${VERSION}`);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
switch (command) {
|
|
340
|
+
case "chat": {
|
|
341
|
+
await runChatMode();
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
case "health": {
|
|
345
|
+
const result = await callCedaAPI("/health");
|
|
346
|
+
formatOutput(result);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
case "stats": {
|
|
350
|
+
const result = await callCedaAPI("/api/stats");
|
|
351
|
+
formatOutput(result);
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
case "predict": {
|
|
355
|
+
const signal = args[1];
|
|
356
|
+
if (!signal) {
|
|
357
|
+
console.error("Error: Missing signal. Usage: herald-mcp predict \"<signal>\"");
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
const result = await callCedaAPI("/api/predict", "POST", {
|
|
361
|
+
input: signal,
|
|
362
|
+
config: { enableAutoFix: true, maxAutoFixAttempts: 3 },
|
|
363
|
+
});
|
|
364
|
+
// Save session for future commands
|
|
365
|
+
if (result.sessionId) {
|
|
366
|
+
saveSession(result.sessionId);
|
|
367
|
+
console.log(`\n✓ Session saved: ${result.sessionId}\n`);
|
|
368
|
+
}
|
|
369
|
+
formatOutput(result);
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
case "refine": {
|
|
373
|
+
const refinement = args[1];
|
|
374
|
+
if (!refinement) {
|
|
375
|
+
console.error("Error: Missing refinement. Usage: herald-mcp refine \"<refinement>\"");
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
const sessionId = loadSession();
|
|
379
|
+
if (!sessionId) {
|
|
380
|
+
console.error("Error: No active session. Run 'herald-mcp predict \"...\"' first.");
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
const result = await callCedaAPI("/api/refine", "POST", {
|
|
384
|
+
sessionId,
|
|
385
|
+
refinement,
|
|
386
|
+
});
|
|
387
|
+
formatOutput(result);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
case "resume":
|
|
391
|
+
case "session": {
|
|
392
|
+
const sessionId = args[1] || loadSession();
|
|
393
|
+
if (!sessionId) {
|
|
394
|
+
console.error("Error: No active session. Run 'herald-mcp predict \"...\"' first.");
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
397
|
+
const result = await callCedaAPI(`/api/session/${sessionId}`);
|
|
398
|
+
formatOutput(result);
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
case "observe": {
|
|
402
|
+
const accepted = args[1]?.toLowerCase();
|
|
403
|
+
if (!accepted) {
|
|
404
|
+
console.error("Error: Missing feedback. Usage: herald-mcp observe yes|no");
|
|
405
|
+
process.exit(1);
|
|
406
|
+
}
|
|
407
|
+
const sessionId = loadSession();
|
|
408
|
+
if (!sessionId) {
|
|
409
|
+
console.error("Error: No active session. Run 'herald-mcp predict \"...\"' first.");
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
const result = await callCedaAPI("/api/feedback", "POST", {
|
|
413
|
+
sessionId,
|
|
414
|
+
accepted: accepted === "yes" || accepted === "true" || accepted === "accept",
|
|
415
|
+
comment: args[2],
|
|
416
|
+
});
|
|
417
|
+
// Clear session after feedback
|
|
418
|
+
clearSession();
|
|
419
|
+
console.log("\n✓ Session closed.\n");
|
|
420
|
+
formatOutput(result);
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
case "new": {
|
|
424
|
+
clearSession();
|
|
425
|
+
console.log("✓ Session cleared. Ready for new prediction.");
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
default:
|
|
429
|
+
console.error(`Unknown command: ${command}`);
|
|
430
|
+
printUsage();
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// ============================================
|
|
435
|
+
// MCP MODE - JSON-RPC for AI agents
|
|
436
|
+
// ============================================
|
|
437
|
+
const server = new Server({ name: "herald", version: VERSION }, { capabilities: { tools: {} } });
|
|
69
438
|
const tools = [
|
|
70
439
|
{
|
|
71
440
|
name: "herald_health",
|
|
72
441
|
description: "Check Herald and CEDA system status",
|
|
73
|
-
inputSchema: {
|
|
74
|
-
type: "object",
|
|
75
|
-
properties: {},
|
|
76
|
-
},
|
|
442
|
+
inputSchema: { type: "object", properties: {} },
|
|
77
443
|
},
|
|
78
444
|
{
|
|
79
445
|
name: "herald_stats",
|
|
80
446
|
description: "Get CEDA server statistics and loaded patterns info",
|
|
81
|
-
inputSchema: {
|
|
82
|
-
type: "object",
|
|
83
|
-
properties: {},
|
|
84
|
-
},
|
|
447
|
+
inputSchema: { type: "object", properties: {} },
|
|
85
448
|
},
|
|
86
449
|
{
|
|
87
450
|
name: "herald_predict",
|
|
@@ -89,62 +452,35 @@ const tools = [
|
|
|
89
452
|
inputSchema: {
|
|
90
453
|
type: "object",
|
|
91
454
|
properties: {
|
|
92
|
-
signal: {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
},
|
|
96
|
-
context: {
|
|
97
|
-
type: "string",
|
|
98
|
-
description: "Additional context for better prediction",
|
|
99
|
-
},
|
|
100
|
-
session_id: {
|
|
101
|
-
type: "string",
|
|
102
|
-
description: "Session ID for multi-turn conversations (returned from previous predict/refine)",
|
|
103
|
-
},
|
|
104
|
-
participant: {
|
|
105
|
-
type: "string",
|
|
106
|
-
description: "Participant name (e.g., 'user', 'architect', 'compliance')",
|
|
107
|
-
},
|
|
455
|
+
signal: { type: "string", description: "Natural language input" },
|
|
456
|
+
context: { type: "string", description: "Additional context" },
|
|
457
|
+
session_id: { type: "string", description: "Session ID for multi-turn" },
|
|
458
|
+
participant: { type: "string", description: "Participant name" },
|
|
108
459
|
},
|
|
109
460
|
required: ["signal"],
|
|
110
461
|
},
|
|
111
462
|
},
|
|
112
463
|
{
|
|
113
464
|
name: "herald_refine",
|
|
114
|
-
description: "Refine an existing prediction with additional requirements.
|
|
465
|
+
description: "Refine an existing prediction with additional requirements.",
|
|
115
466
|
inputSchema: {
|
|
116
467
|
type: "object",
|
|
117
468
|
properties: {
|
|
118
|
-
session_id: {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
},
|
|
122
|
-
refinement: {
|
|
123
|
-
type: "string",
|
|
124
|
-
description: "Refinement instruction (e.g., 'make it OSHA compliant', 'add corrective actions section')",
|
|
125
|
-
},
|
|
126
|
-
context: {
|
|
127
|
-
type: "string",
|
|
128
|
-
description: "Additional context for refinement",
|
|
129
|
-
},
|
|
130
|
-
participant: {
|
|
131
|
-
type: "string",
|
|
132
|
-
description: "Participant name (e.g., 'user', 'architect', 'compliance')",
|
|
133
|
-
},
|
|
469
|
+
session_id: { type: "string", description: "Session ID from previous call" },
|
|
470
|
+
refinement: { type: "string", description: "Refinement instruction" },
|
|
471
|
+
context: { type: "string", description: "Additional context" },
|
|
472
|
+
participant: { type: "string", description: "Participant name" },
|
|
134
473
|
},
|
|
135
474
|
required: ["session_id", "refinement"],
|
|
136
475
|
},
|
|
137
476
|
},
|
|
138
477
|
{
|
|
139
478
|
name: "herald_session",
|
|
140
|
-
description: "Get session information including history
|
|
479
|
+
description: "Get session information including history",
|
|
141
480
|
inputSchema: {
|
|
142
481
|
type: "object",
|
|
143
482
|
properties: {
|
|
144
|
-
session_id: {
|
|
145
|
-
type: "string",
|
|
146
|
-
description: "Session ID to retrieve",
|
|
147
|
-
},
|
|
483
|
+
session_id: { type: "string", description: "Session ID to retrieve" },
|
|
148
484
|
},
|
|
149
485
|
required: ["session_id"],
|
|
150
486
|
},
|
|
@@ -155,149 +491,290 @@ const tools = [
|
|
|
155
491
|
inputSchema: {
|
|
156
492
|
type: "object",
|
|
157
493
|
properties: {
|
|
158
|
-
session_id: {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
},
|
|
162
|
-
accepted: {
|
|
163
|
-
type: "boolean",
|
|
164
|
-
description: "Was prediction accepted by user?",
|
|
165
|
-
},
|
|
166
|
-
feedback: {
|
|
167
|
-
type: "string",
|
|
168
|
-
description: "Optional user feedback/comment",
|
|
169
|
-
},
|
|
494
|
+
session_id: { type: "string", description: "Session ID" },
|
|
495
|
+
accepted: { type: "boolean", description: "Was prediction accepted?" },
|
|
496
|
+
feedback: { type: "string", description: "Optional feedback" },
|
|
170
497
|
},
|
|
171
498
|
required: ["session_id", "accepted"],
|
|
172
499
|
},
|
|
173
500
|
},
|
|
501
|
+
// === HERALD CONTEXT SYNC TOOLS ===
|
|
502
|
+
{
|
|
503
|
+
name: "herald_context_status",
|
|
504
|
+
description: "Get status from Herald contexts across domains. Returns session counts, active threads, blockers, and pending items for each context.",
|
|
505
|
+
inputSchema: {
|
|
506
|
+
type: "object",
|
|
507
|
+
properties: {
|
|
508
|
+
context: { type: "string", description: "Optional: specific context to query. Omit for all known contexts." },
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: "herald_share_insight",
|
|
514
|
+
description: "Share a pattern insight with another Herald context. Herald instances communicate through shared insights to propagate learned patterns across domains.",
|
|
515
|
+
inputSchema: {
|
|
516
|
+
type: "object",
|
|
517
|
+
properties: {
|
|
518
|
+
context: { type: "string", description: "Target context to share insight with" },
|
|
519
|
+
insight: { type: "string", description: "The insight to share" },
|
|
520
|
+
topic: { type: "string", description: "Optional topic/category for the insight" },
|
|
521
|
+
},
|
|
522
|
+
required: ["context", "insight"],
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
name: "herald_query_insights",
|
|
527
|
+
description: "Query Herald's accumulated insights on a topic. Herald draws from pattern knowledge shared across contexts. Use this when you need guidance on implementation decisions or domain patterns.",
|
|
528
|
+
inputSchema: {
|
|
529
|
+
type: "object",
|
|
530
|
+
properties: {
|
|
531
|
+
question: { type: "string", description: "What you need insight on" },
|
|
532
|
+
domain: { type: "string", description: "Optional domain context" },
|
|
533
|
+
},
|
|
534
|
+
required: ["question"],
|
|
535
|
+
},
|
|
536
|
+
},
|
|
174
537
|
];
|
|
175
|
-
|
|
176
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
177
|
-
tools,
|
|
178
|
-
}));
|
|
179
|
-
// Handle tool calls
|
|
538
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
180
539
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
181
540
|
const { name, arguments: args } = request.params;
|
|
541
|
+
let result;
|
|
182
542
|
switch (name) {
|
|
183
|
-
case "herald_health":
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
type: "text",
|
|
190
|
-
text: JSON.stringify(result, null, 2),
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
case "herald_stats": {
|
|
196
|
-
// Maps to: GET /api/stats
|
|
197
|
-
const result = await callCedaAPI("/api/stats");
|
|
198
|
-
return {
|
|
199
|
-
content: [
|
|
200
|
-
{
|
|
201
|
-
type: "text",
|
|
202
|
-
text: JSON.stringify(result, null, 2),
|
|
203
|
-
},
|
|
204
|
-
],
|
|
205
|
-
};
|
|
206
|
-
}
|
|
543
|
+
case "herald_health":
|
|
544
|
+
result = await callCedaAPI("/health");
|
|
545
|
+
break;
|
|
546
|
+
case "herald_stats":
|
|
547
|
+
result = await callCedaAPI("/api/stats");
|
|
548
|
+
break;
|
|
207
549
|
case "herald_predict": {
|
|
208
|
-
// Maps to: POST /api/predict
|
|
209
550
|
const params = args;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
{
|
|
213
|
-
type: "user_context",
|
|
214
|
-
value: params.context,
|
|
215
|
-
source: "herald_mcp",
|
|
216
|
-
}
|
|
217
|
-
] : [];
|
|
218
|
-
const result = await callCedaAPI("/api/predict", "POST", {
|
|
551
|
+
const context = params.context ? [{ type: "user_context", value: params.context, source: "herald_mcp" }] : [];
|
|
552
|
+
result = await callCedaAPI("/api/predict", "POST", {
|
|
219
553
|
input: params.signal,
|
|
220
554
|
context,
|
|
221
555
|
sessionId: params.session_id,
|
|
222
556
|
participant: params.participant,
|
|
223
|
-
config: {
|
|
224
|
-
enableAutoFix: true,
|
|
225
|
-
maxAutoFixAttempts: 3,
|
|
226
|
-
},
|
|
557
|
+
config: { enableAutoFix: true, maxAutoFixAttempts: 3 },
|
|
227
558
|
});
|
|
228
|
-
|
|
229
|
-
content: [
|
|
230
|
-
{
|
|
231
|
-
type: "text",
|
|
232
|
-
text: JSON.stringify(result, null, 2),
|
|
233
|
-
},
|
|
234
|
-
],
|
|
235
|
-
};
|
|
559
|
+
break;
|
|
236
560
|
}
|
|
237
561
|
case "herald_refine": {
|
|
238
|
-
// Maps to: POST /api/refine
|
|
239
562
|
const params = args;
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
{
|
|
243
|
-
type: "refinement_context",
|
|
244
|
-
value: params.context,
|
|
245
|
-
source: "herald_mcp",
|
|
246
|
-
}
|
|
247
|
-
] : [];
|
|
248
|
-
const result = await callCedaAPI("/api/refine", "POST", {
|
|
563
|
+
const context = params.context ? [{ type: "refinement_context", value: params.context, source: "herald_mcp" }] : [];
|
|
564
|
+
result = await callCedaAPI("/api/refine", "POST", {
|
|
249
565
|
sessionId: params.session_id,
|
|
250
566
|
refinement: params.refinement,
|
|
251
567
|
context,
|
|
252
568
|
participant: params.participant,
|
|
253
569
|
});
|
|
254
|
-
|
|
255
|
-
content: [
|
|
256
|
-
{
|
|
257
|
-
type: "text",
|
|
258
|
-
text: JSON.stringify(result, null, 2),
|
|
259
|
-
},
|
|
260
|
-
],
|
|
261
|
-
};
|
|
570
|
+
break;
|
|
262
571
|
}
|
|
263
572
|
case "herald_session": {
|
|
264
|
-
// Maps to: GET /api/session/:id
|
|
265
573
|
const params = args;
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
content: [
|
|
269
|
-
{
|
|
270
|
-
type: "text",
|
|
271
|
-
text: JSON.stringify(result, null, 2),
|
|
272
|
-
},
|
|
273
|
-
],
|
|
274
|
-
};
|
|
574
|
+
result = await callCedaAPI(`/api/session/${params.session_id}`);
|
|
575
|
+
break;
|
|
275
576
|
}
|
|
276
577
|
case "herald_observe": {
|
|
277
|
-
// Maps to: POST /api/feedback
|
|
278
578
|
const params = args;
|
|
279
|
-
|
|
280
|
-
sessionId: params.session_id,
|
|
579
|
+
result = await callCedaAPI("/api/feedback", "POST", {
|
|
580
|
+
sessionId: params.session_id,
|
|
281
581
|
accepted: params.accepted,
|
|
282
582
|
comment: params.feedback,
|
|
283
583
|
});
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
584
|
+
break;
|
|
585
|
+
}
|
|
586
|
+
// === HERALD CONTEXT SYNC HANDLERS ===
|
|
587
|
+
case "herald_context_status": {
|
|
588
|
+
const params = args;
|
|
589
|
+
const knownContexts = ["goprint", "disrupt", "spilno"];
|
|
590
|
+
const contextsToQuery = params.context ? [params.context] : knownContexts;
|
|
591
|
+
// Cloud mode: Call CEDA API (Herald-to-Herald protocol)
|
|
592
|
+
if (OFFSPRING_CLOUD_MODE && CEDA_API_URL) {
|
|
593
|
+
const contextParam = params.context ? `?context=${params.context}` : "";
|
|
594
|
+
result = await callCedaAPI(`/api/herald/contexts${contextParam}`);
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
const statuses = [];
|
|
598
|
+
for (const ctx of contextsToQuery) {
|
|
599
|
+
const statusPath = join(AEGIS_OFFSPRING_PATH, `${ctx}.md`);
|
|
600
|
+
if (!existsSync(statusPath)) {
|
|
601
|
+
statuses.push({
|
|
602
|
+
context: ctx,
|
|
603
|
+
status: "not_found",
|
|
604
|
+
sessionCount: 0,
|
|
605
|
+
lastOutcome: null,
|
|
606
|
+
activeThreads: [],
|
|
607
|
+
blockers: [],
|
|
608
|
+
pendingItems: [],
|
|
609
|
+
readyForSync: false,
|
|
610
|
+
});
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
const content = readFileSync(statusPath, "utf-8");
|
|
614
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
615
|
+
if (!frontmatterMatch) {
|
|
616
|
+
statuses.push({
|
|
617
|
+
context: ctx,
|
|
618
|
+
status: "invalid_format",
|
|
619
|
+
sessionCount: 0,
|
|
620
|
+
lastOutcome: null,
|
|
621
|
+
activeThreads: [],
|
|
622
|
+
blockers: [],
|
|
623
|
+
pendingItems: [],
|
|
624
|
+
readyForSync: false,
|
|
625
|
+
});
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
const yaml = frontmatterMatch[1];
|
|
629
|
+
const parseYamlArray = (key) => {
|
|
630
|
+
const match = yaml.match(new RegExp(`${key}:\\s*\\n((?:\\s+-[^\\n]+\\n?)+)`, "m"));
|
|
631
|
+
if (!match)
|
|
632
|
+
return [];
|
|
633
|
+
return match[1]
|
|
634
|
+
.split("\n")
|
|
635
|
+
.filter((line) => line.trim().startsWith("-"))
|
|
636
|
+
.map((line) => line.replace(/^\s*-\s*/, "").trim());
|
|
637
|
+
};
|
|
638
|
+
const parseYamlValue = (key) => {
|
|
639
|
+
const match = yaml.match(new RegExp(`^\\s*${key}:\\s*(.+)$`, "m"));
|
|
640
|
+
return match ? match[1].trim() : null;
|
|
641
|
+
};
|
|
642
|
+
statuses.push({
|
|
643
|
+
context: ctx,
|
|
644
|
+
status: "active",
|
|
645
|
+
sessionCount: parseInt(parseYamlValue("session_count") || "0", 10),
|
|
646
|
+
lastOutcome: parseYamlValue("outcome"),
|
|
647
|
+
activeThreads: parseYamlArray("active_threads"),
|
|
648
|
+
blockers: parseYamlArray("blockers"),
|
|
649
|
+
pendingItems: parseYamlArray("awaiting_aegis"), // Read from file but expose as pendingItems
|
|
650
|
+
readyForSync: parseYamlValue("ready_for_handoff") === "true",
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
const totalSessions = statuses.reduce((sum, s) => sum + s.sessionCount, 0);
|
|
654
|
+
const pendingCount = statuses.reduce((sum, s) => sum + s.pendingItems.length, 0);
|
|
655
|
+
result = {
|
|
656
|
+
success: true,
|
|
657
|
+
mode: "local",
|
|
658
|
+
statuses,
|
|
659
|
+
summary: {
|
|
660
|
+
totalSessions,
|
|
661
|
+
pendingItemsCount: pendingCount,
|
|
662
|
+
contextsQueried: contextsToQuery.length,
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
case "herald_share_insight": {
|
|
668
|
+
const params = args;
|
|
669
|
+
const validContexts = ["goprint", "disrupt", "spilno"];
|
|
670
|
+
if (!validContexts.includes(params.context)) {
|
|
671
|
+
result = { success: false, error: `Unknown context: ${params.context}. Valid: ${validContexts.join(", ")}` };
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
// Cloud mode: Call CEDA API (Herald-to-Herald protocol)
|
|
675
|
+
if (OFFSPRING_CLOUD_MODE && CEDA_API_URL) {
|
|
676
|
+
result = await callCedaAPI("/api/herald/insight", "POST", {
|
|
677
|
+
targetContext: params.context,
|
|
678
|
+
insight: params.insight,
|
|
679
|
+
topic: params.topic,
|
|
680
|
+
});
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
// Local mode: Write to filesystem
|
|
684
|
+
const insightPath = join(AEGIS_OFFSPRING_PATH, `${params.context}_insights.md`);
|
|
685
|
+
const entry = `---
|
|
686
|
+
timestamp: ${new Date().toISOString()}
|
|
687
|
+
from: herald
|
|
688
|
+
topic: ${params.topic || "general"}
|
|
689
|
+
---
|
|
690
|
+
|
|
691
|
+
${params.insight}
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
`;
|
|
695
|
+
let existingContent = "";
|
|
696
|
+
if (existsSync(insightPath)) {
|
|
697
|
+
existingContent = readFileSync(insightPath, "utf-8");
|
|
698
|
+
}
|
|
699
|
+
writeFileSync(insightPath, existingContent + entry);
|
|
700
|
+
result = {
|
|
701
|
+
success: true,
|
|
702
|
+
mode: "local",
|
|
703
|
+
context: params.context,
|
|
704
|
+
message: `Insight shared with ${params.context}`,
|
|
705
|
+
path: insightPath,
|
|
706
|
+
};
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
case "herald_query_insights": {
|
|
710
|
+
const params = args;
|
|
711
|
+
// Determine which context Herald is serving
|
|
712
|
+
const ctx = HERALD_VAULT || "default";
|
|
713
|
+
// Cloud mode: Call CEDA API (Herald-to-Herald protocol)
|
|
714
|
+
if (OFFSPRING_CLOUD_MODE && CEDA_API_URL) {
|
|
715
|
+
result = await callCedaAPI(`/api/herald/insights?context=${ctx}&query=${encodeURIComponent(params.question)}`);
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
// Local mode: Read from filesystem
|
|
719
|
+
const insightsPath = join(AEGIS_OFFSPRING_PATH, `${ctx}_insights.md`);
|
|
720
|
+
let relevantInsights = "";
|
|
721
|
+
if (existsSync(insightsPath)) {
|
|
722
|
+
const content = readFileSync(insightsPath, "utf-8");
|
|
723
|
+
const entries = content.split(/^---$/m).filter((e) => e.trim());
|
|
724
|
+
const keywords = params.question.toLowerCase().split(/\s+/);
|
|
725
|
+
for (const entry of entries) {
|
|
726
|
+
const entryLower = entry.toLowerCase();
|
|
727
|
+
if (keywords.some((kw) => kw.length > 3 && entryLower.includes(kw))) {
|
|
728
|
+
relevantInsights += entry.trim() + "\n\n";
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
let response = `**Herald's Insight**\n\nOn your query: "${params.question}"\n\n`;
|
|
733
|
+
if (relevantInsights) {
|
|
734
|
+
response += `From accumulated pattern knowledge:\n\n${relevantInsights}`;
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
response += `I don't have specific insights on this yet. Consider:\n`;
|
|
738
|
+
response += `1. Proceeding with your best judgment\n`;
|
|
739
|
+
response += `2. Recording your approach with herald_observe so I can learn\n`;
|
|
740
|
+
response += `3. The pattern will emerge through practice`;
|
|
741
|
+
}
|
|
742
|
+
result = {
|
|
743
|
+
success: true,
|
|
744
|
+
mode: "local",
|
|
745
|
+
context: ctx,
|
|
746
|
+
question: params.question,
|
|
747
|
+
response,
|
|
748
|
+
hasInsights: !!relevantInsights,
|
|
291
749
|
};
|
|
750
|
+
break;
|
|
292
751
|
}
|
|
293
752
|
default:
|
|
294
753
|
throw new Error(`Unknown tool: ${name}`);
|
|
295
754
|
}
|
|
755
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
296
756
|
});
|
|
297
|
-
|
|
298
|
-
async function main() {
|
|
757
|
+
async function runMCP() {
|
|
299
758
|
const transport = new StdioServerTransport();
|
|
300
759
|
await server.connect(transport);
|
|
301
760
|
console.error("Herald MCP server running on stdio");
|
|
302
761
|
}
|
|
762
|
+
// ============================================
|
|
763
|
+
// ENTRY POINT - Detect mode
|
|
764
|
+
// ============================================
|
|
765
|
+
async function main() {
|
|
766
|
+
const args = process.argv.slice(2);
|
|
767
|
+
// If we have CLI arguments, run CLI mode
|
|
768
|
+
if (args.length > 0) {
|
|
769
|
+
await runCLI(args);
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
// If stdin is a TTY (human at terminal), show help
|
|
773
|
+
if (process.stdin.isTTY) {
|
|
774
|
+
printUsage();
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
// Otherwise, run MCP server (AI agent calling via pipe)
|
|
778
|
+
await runMCP();
|
|
779
|
+
}
|
|
303
780
|
main().catch(console.error);
|