agentfeed 0.1.7 → 0.1.10
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/bin/mcp-server.js +21 -0
- package/dist/agent-registry-store.d.ts +11 -0
- package/dist/agent-registry-store.js +31 -0
- package/dist/api-client.d.ts +11 -5
- package/dist/api-client.js +47 -6
- package/dist/backends/claude.d.ts +12 -0
- package/dist/backends/claude.js +92 -0
- package/dist/backends/codex.d.ts +13 -0
- package/dist/backends/codex.js +69 -0
- package/dist/backends/gemini.d.ts +11 -0
- package/dist/backends/gemini.js +102 -0
- package/dist/backends/index.d.ts +6 -0
- package/dist/backends/index.js +16 -0
- package/dist/backends/types.d.ts +23 -0
- package/dist/backends/types.js +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +129 -0
- package/dist/follow-store.d.ts +1 -0
- package/dist/follow-store.js +12 -0
- package/dist/index.js +157 -194
- package/dist/invoker.d.ts +6 -5
- package/dist/invoker.js +87 -60
- package/dist/mcp-server.d.ts +8 -0
- package/dist/mcp-server.js +230 -0
- package/dist/post-session-store.d.ts +20 -0
- package/dist/post-session-store.js +72 -0
- package/dist/processor.d.ts +21 -0
- package/dist/processor.js +168 -0
- package/dist/queue-store.js +2 -2
- package/dist/scanner.d.ts +3 -2
- package/dist/scanner.js +53 -25
- package/dist/session-store.d.ts +1 -0
- package/dist/session-store.js +3 -0
- package/dist/sse-client.d.ts +1 -1
- package/dist/sse-client.js +12 -4
- package/dist/trigger.d.ts +3 -2
- package/dist/trigger.js +82 -47
- package/dist/types.d.ts +24 -1
- package/dist/utils.d.ts +7 -0
- package/dist/utils.js +13 -3
- package/package.json +16 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { AgentFeedClient } from "../dist/api-client.js";
|
|
4
|
+
import { startMCPServer } from "../dist/mcp-server.js";
|
|
5
|
+
|
|
6
|
+
const serverUrl = process.env.AGENTFEED_BASE_URL?.replace(/\/api$/, "") || "http://localhost:3000";
|
|
7
|
+
const apiKey = process.env.AGENTFEED_API_KEY;
|
|
8
|
+
|
|
9
|
+
if (!apiKey) {
|
|
10
|
+
console.error("AGENTFEED_API_KEY environment variable is required");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const client = new AgentFeedClient(serverUrl, apiKey);
|
|
15
|
+
|
|
16
|
+
// Set agent ID if provided
|
|
17
|
+
if (process.env.AGENTFEED_AGENT_ID) {
|
|
18
|
+
client["_agentId"] = process.env.AGENTFEED_AGENT_ID;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
startMCPServer({ client, serverUrl });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PersistentStore } from "./persistent-store.js";
|
|
2
|
+
export declare class AgentRegistryStore extends PersistentStore {
|
|
3
|
+
private map;
|
|
4
|
+
constructor(filePath?: string);
|
|
5
|
+
protected serialize(): string;
|
|
6
|
+
protected deserialize(raw: string): void;
|
|
7
|
+
get(name: string): string | undefined;
|
|
8
|
+
set(name: string, id: string): void;
|
|
9
|
+
delete(name: string): void;
|
|
10
|
+
getAllIds(): Set<string>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PersistentStore } from "./persistent-store.js";
|
|
2
|
+
export class AgentRegistryStore extends PersistentStore {
|
|
3
|
+
map = new Map();
|
|
4
|
+
constructor(filePath) {
|
|
5
|
+
super("agent-registry.json", filePath);
|
|
6
|
+
this.load();
|
|
7
|
+
}
|
|
8
|
+
serialize() {
|
|
9
|
+
return JSON.stringify(Object.fromEntries(this.map), null, 2);
|
|
10
|
+
}
|
|
11
|
+
deserialize(raw) {
|
|
12
|
+
const data = JSON.parse(raw);
|
|
13
|
+
for (const [k, v] of Object.entries(data)) {
|
|
14
|
+
this.map.set(k, v);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
get(name) {
|
|
18
|
+
return this.map.get(name);
|
|
19
|
+
}
|
|
20
|
+
set(name, id) {
|
|
21
|
+
this.map.set(name, id);
|
|
22
|
+
this.save();
|
|
23
|
+
}
|
|
24
|
+
delete(name) {
|
|
25
|
+
this.map.delete(name);
|
|
26
|
+
this.save();
|
|
27
|
+
}
|
|
28
|
+
getAllIds() {
|
|
29
|
+
return new Set(this.map.values());
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/api-client.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import type { AgentInfo, FeedItem, FeedCommentItem, CommentItem, PostItem, PaginatedResponse } from "./types.js";
|
|
1
|
+
import type { AgentConfig, AgentInfo, FeedItem, FeedCommentItem, CommentItem, PostItem, PaginatedResponse } from "./types.js";
|
|
2
2
|
export declare class AgentFeedClient {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
readonly baseUrl: string;
|
|
4
|
+
readonly apiKey: string;
|
|
5
|
+
private _agentId;
|
|
5
6
|
constructor(baseUrl: string, apiKey: string);
|
|
7
|
+
get agentId(): string | undefined;
|
|
8
|
+
setDefaultAgentId(id: string): void;
|
|
6
9
|
private request;
|
|
7
|
-
|
|
10
|
+
registerAgent(name: string, type?: string): Promise<AgentInfo>;
|
|
11
|
+
register(name: string, type?: string): Promise<AgentInfo>;
|
|
8
12
|
getSkillMd(): Promise<string>;
|
|
9
13
|
getFeeds(): Promise<FeedItem[]>;
|
|
10
14
|
getFeedPosts(feedId: string, options?: {
|
|
@@ -23,5 +27,7 @@ export declare class AgentFeedClient {
|
|
|
23
27
|
status: "thinking" | "idle";
|
|
24
28
|
feed_id: string;
|
|
25
29
|
post_id: string;
|
|
26
|
-
}): Promise<void>;
|
|
30
|
+
}, agentId?: string): Promise<void>;
|
|
31
|
+
getAgentConfig(agentId: string): Promise<AgentConfig>;
|
|
32
|
+
reportSession(sessionName: string, claudeSessionId: string, agentId?: string): Promise<void>;
|
|
27
33
|
}
|
package/dist/api-client.js
CHANGED
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
export class AgentFeedClient {
|
|
2
2
|
baseUrl;
|
|
3
3
|
apiKey;
|
|
4
|
+
_agentId;
|
|
4
5
|
constructor(baseUrl, apiKey) {
|
|
5
6
|
this.baseUrl = baseUrl;
|
|
6
7
|
this.apiKey = apiKey;
|
|
7
8
|
}
|
|
9
|
+
get agentId() {
|
|
10
|
+
return this._agentId;
|
|
11
|
+
}
|
|
12
|
+
setDefaultAgentId(id) {
|
|
13
|
+
this._agentId = id;
|
|
14
|
+
}
|
|
8
15
|
async request(path, options) {
|
|
16
|
+
const effectiveAgentId = options?.agentId ?? this._agentId;
|
|
17
|
+
const headers = {
|
|
18
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
19
|
+
};
|
|
20
|
+
if (effectiveAgentId) {
|
|
21
|
+
headers["X-Agent-Id"] = effectiveAgentId;
|
|
22
|
+
}
|
|
23
|
+
// Strip custom agentId from options before passing to fetch
|
|
24
|
+
const { agentId: _, ...fetchOptions } = options ?? {};
|
|
9
25
|
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
10
|
-
...
|
|
26
|
+
...fetchOptions,
|
|
11
27
|
headers: {
|
|
12
|
-
|
|
13
|
-
...
|
|
28
|
+
...headers,
|
|
29
|
+
...fetchOptions?.headers,
|
|
14
30
|
},
|
|
15
31
|
});
|
|
16
32
|
if (!res.ok) {
|
|
@@ -18,8 +34,18 @@ export class AgentFeedClient {
|
|
|
18
34
|
}
|
|
19
35
|
return res.json();
|
|
20
36
|
}
|
|
21
|
-
async
|
|
22
|
-
|
|
37
|
+
async registerAgent(name, type) {
|
|
38
|
+
const result = await this.request("/api/agents/register", {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: { "Content-Type": "application/json" },
|
|
41
|
+
body: JSON.stringify({ name, type, cwd: process.cwd() }),
|
|
42
|
+
});
|
|
43
|
+
return { id: result.id, name: result.name, type: type ?? "api" };
|
|
44
|
+
}
|
|
45
|
+
async register(name, type) {
|
|
46
|
+
const agent = await this.registerAgent(name, type);
|
|
47
|
+
this._agentId = agent.id;
|
|
48
|
+
return agent;
|
|
23
49
|
}
|
|
24
50
|
async getSkillMd() {
|
|
25
51
|
const res = await fetch(`${this.baseUrl}/skill.md`);
|
|
@@ -58,12 +84,13 @@ export class AgentFeedClient {
|
|
|
58
84
|
const qs = params.toString();
|
|
59
85
|
return this.request(`/api/posts/${postId}/comments${qs ? `?${qs}` : ""}`);
|
|
60
86
|
}
|
|
61
|
-
async setAgentStatus(params) {
|
|
87
|
+
async setAgentStatus(params, agentId) {
|
|
62
88
|
try {
|
|
63
89
|
await this.request("/api/agents/status", {
|
|
64
90
|
method: "POST",
|
|
65
91
|
headers: { "Content-Type": "application/json" },
|
|
66
92
|
body: JSON.stringify(params),
|
|
93
|
+
agentId,
|
|
67
94
|
});
|
|
68
95
|
}
|
|
69
96
|
catch (err) {
|
|
@@ -71,4 +98,18 @@ export class AgentFeedClient {
|
|
|
71
98
|
console.warn("Failed to set agent status:", err);
|
|
72
99
|
}
|
|
73
100
|
}
|
|
101
|
+
async getAgentConfig(agentId) {
|
|
102
|
+
return this.request(`/api/agents/${agentId}/config`);
|
|
103
|
+
}
|
|
104
|
+
async reportSession(sessionName, claudeSessionId, agentId) {
|
|
105
|
+
await this.request("/api/agents/sessions", {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "Content-Type": "application/json" },
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
session_name: sessionName,
|
|
110
|
+
claude_session_id: claudeSessionId,
|
|
111
|
+
}),
|
|
112
|
+
agentId,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
74
115
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CLIBackend, BuildArgsOptions } from "./types.js";
|
|
2
|
+
export declare class ClaudeBackend implements CLIBackend {
|
|
3
|
+
readonly name: "claude";
|
|
4
|
+
readonly binaryName = "claude";
|
|
5
|
+
private mcpConfigPath;
|
|
6
|
+
private lastMCPKey;
|
|
7
|
+
setupMCP(env: Record<string, string>, mcpServerPath: string): void;
|
|
8
|
+
buildArgs(options: BuildArgsOptions): string[];
|
|
9
|
+
buildEnv(baseEnv: Record<string, string>): Record<string, string>;
|
|
10
|
+
parseSessionId(line: string): string | undefined;
|
|
11
|
+
parseStreamText(line: string): string | undefined;
|
|
12
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
export class ClaudeBackend {
|
|
5
|
+
name = "claude";
|
|
6
|
+
binaryName = "claude";
|
|
7
|
+
mcpConfigPath = "";
|
|
8
|
+
lastMCPKey = "";
|
|
9
|
+
setupMCP(env, mcpServerPath) {
|
|
10
|
+
const cacheKey = JSON.stringify(env) + mcpServerPath;
|
|
11
|
+
if (cacheKey === this.lastMCPKey)
|
|
12
|
+
return;
|
|
13
|
+
this.lastMCPKey = cacheKey;
|
|
14
|
+
const config = {
|
|
15
|
+
mcpServers: {
|
|
16
|
+
agentfeed: { command: "node", args: [mcpServerPath], env },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
const configDir = path.join(os.homedir(), ".agentfeed");
|
|
20
|
+
if (!fs.existsSync(configDir)) {
|
|
21
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
this.mcpConfigPath = path.join(configDir, "mcp-config.json");
|
|
24
|
+
fs.writeFileSync(this.mcpConfigPath, JSON.stringify(config, null, 2));
|
|
25
|
+
}
|
|
26
|
+
buildArgs(options) {
|
|
27
|
+
const { prompt, systemPrompt, sessionId, permissionMode, extraAllowedTools } = options;
|
|
28
|
+
const args = [
|
|
29
|
+
"-p", prompt,
|
|
30
|
+
"--append-system-prompt", systemPrompt,
|
|
31
|
+
"--mcp-config", this.mcpConfigPath,
|
|
32
|
+
];
|
|
33
|
+
if (permissionMode === "yolo") {
|
|
34
|
+
args.push("--dangerously-skip-permissions");
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const allowedTools = ["mcp__agentfeed__*", ...(extraAllowedTools ?? [])];
|
|
38
|
+
for (const tool of allowedTools) {
|
|
39
|
+
args.push("--allowedTools", tool);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (sessionId) {
|
|
43
|
+
args.push("--resume", sessionId);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
args.push("--output-format", "stream-json", "--verbose");
|
|
47
|
+
}
|
|
48
|
+
return args;
|
|
49
|
+
}
|
|
50
|
+
buildEnv(baseEnv) {
|
|
51
|
+
const env = { ...baseEnv };
|
|
52
|
+
env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = process.env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE ?? "50";
|
|
53
|
+
const passthroughKeys = [
|
|
54
|
+
"ANTHROPIC_API_KEY",
|
|
55
|
+
"CLAUDE_CODE_USE_BEDROCK", "CLAUDE_CODE_USE_VERTEX",
|
|
56
|
+
"AWS_REGION", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN",
|
|
57
|
+
"GOOGLE_APPLICATION_CREDENTIALS", "CLOUD_ML_REGION",
|
|
58
|
+
];
|
|
59
|
+
for (const key of passthroughKeys) {
|
|
60
|
+
if (process.env[key]) {
|
|
61
|
+
env[key] = process.env[key];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return env;
|
|
65
|
+
}
|
|
66
|
+
parseSessionId(line) {
|
|
67
|
+
try {
|
|
68
|
+
const event = JSON.parse(line);
|
|
69
|
+
if (event.type === "result" && event.session_id) {
|
|
70
|
+
return event.session_id;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch { /* not JSON */ }
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
parseStreamText(line) {
|
|
77
|
+
try {
|
|
78
|
+
const event = JSON.parse(line);
|
|
79
|
+
if (event.type === "assistant" && event.message?.content) {
|
|
80
|
+
const texts = [];
|
|
81
|
+
for (const block of event.message.content) {
|
|
82
|
+
if (block.type === "text") {
|
|
83
|
+
texts.push(block.text);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return texts.length > 0 ? texts.join("") : undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch { /* not JSON */ }
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CLIBackend, BuildArgsOptions } from "./types.js";
|
|
2
|
+
export declare class CodexBackend implements CLIBackend {
|
|
3
|
+
readonly name: "codex";
|
|
4
|
+
readonly binaryName = "codex";
|
|
5
|
+
private mcpCommand;
|
|
6
|
+
private mcpArgs;
|
|
7
|
+
private mcpEnv;
|
|
8
|
+
setupMCP(env: Record<string, string>, mcpServerPath: string): void;
|
|
9
|
+
buildArgs(options: BuildArgsOptions): string[];
|
|
10
|
+
buildEnv(baseEnv: Record<string, string>): Record<string, string>;
|
|
11
|
+
parseSessionId(line: string): string | undefined;
|
|
12
|
+
parseStreamText(line: string): string | undefined;
|
|
13
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export class CodexBackend {
|
|
2
|
+
name = "codex";
|
|
3
|
+
binaryName = "codex";
|
|
4
|
+
mcpCommand = "";
|
|
5
|
+
mcpArgs = [];
|
|
6
|
+
mcpEnv = {};
|
|
7
|
+
setupMCP(env, mcpServerPath) {
|
|
8
|
+
this.mcpCommand = "node";
|
|
9
|
+
this.mcpArgs = [mcpServerPath];
|
|
10
|
+
this.mcpEnv = env;
|
|
11
|
+
}
|
|
12
|
+
buildArgs(options) {
|
|
13
|
+
const { prompt, systemPrompt, sessionId, permissionMode } = options;
|
|
14
|
+
const args = ["exec"];
|
|
15
|
+
// MCP config via dot-notation -c flags (codex-cli 0.46+ requires struct, not JSON string)
|
|
16
|
+
const prefix = "mcp_servers.agentfeed";
|
|
17
|
+
args.push("-c", `${prefix}.command=${this.mcpCommand}`);
|
|
18
|
+
args.push("-c", `${prefix}.args=${JSON.stringify(this.mcpArgs)}`);
|
|
19
|
+
for (const [key, value] of Object.entries(this.mcpEnv)) {
|
|
20
|
+
args.push("-c", `${prefix}.env.${key}=${value}`);
|
|
21
|
+
}
|
|
22
|
+
// System prompt via -c instructions (separate from user prompt)
|
|
23
|
+
args.push("-c", `instructions=${JSON.stringify(systemPrompt)}`);
|
|
24
|
+
// Permission mode
|
|
25
|
+
if (permissionMode === "yolo") {
|
|
26
|
+
args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
args.push("--full-auto");
|
|
30
|
+
}
|
|
31
|
+
args.push("--json", "--skip-git-repo-check");
|
|
32
|
+
// Resume must come after all flags
|
|
33
|
+
if (sessionId) {
|
|
34
|
+
args.push("resume", sessionId);
|
|
35
|
+
}
|
|
36
|
+
args.push(prompt);
|
|
37
|
+
return args;
|
|
38
|
+
}
|
|
39
|
+
buildEnv(baseEnv) {
|
|
40
|
+
const env = { ...baseEnv };
|
|
41
|
+
const passthroughKeys = ["CODEX_API_KEY", "OPENAI_API_KEY", "OPENAI_BASE_URL"];
|
|
42
|
+
for (const key of passthroughKeys) {
|
|
43
|
+
if (process.env[key]) {
|
|
44
|
+
env[key] = process.env[key];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return env;
|
|
48
|
+
}
|
|
49
|
+
parseSessionId(line) {
|
|
50
|
+
try {
|
|
51
|
+
const event = JSON.parse(line);
|
|
52
|
+
if (event.type === "thread.started" && event.thread_id) {
|
|
53
|
+
return event.thread_id;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch { /* not JSON */ }
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
parseStreamText(line) {
|
|
60
|
+
try {
|
|
61
|
+
const event = JSON.parse(line);
|
|
62
|
+
if (event.type === "item.completed" && event.item?.type === "agent_message" && event.item.text) {
|
|
63
|
+
return event.item.text;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch { /* not JSON */ }
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CLIBackend, BuildArgsOptions } from "./types.js";
|
|
2
|
+
export declare class GeminiBackend implements CLIBackend {
|
|
3
|
+
readonly name: "gemini";
|
|
4
|
+
readonly binaryName = "gemini";
|
|
5
|
+
private lastMCPKey;
|
|
6
|
+
setupMCP(env: Record<string, string>, mcpServerPath: string): void;
|
|
7
|
+
buildArgs(options: BuildArgsOptions): string[];
|
|
8
|
+
buildEnv(baseEnv: Record<string, string>): Record<string, string>;
|
|
9
|
+
parseSessionId(line: string): string | undefined;
|
|
10
|
+
parseStreamText(line: string): string | undefined;
|
|
11
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
export class GeminiBackend {
|
|
5
|
+
name = "gemini";
|
|
6
|
+
binaryName = "gemini";
|
|
7
|
+
lastMCPKey = "";
|
|
8
|
+
setupMCP(env, mcpServerPath) {
|
|
9
|
+
const cacheKey = JSON.stringify(env) + mcpServerPath;
|
|
10
|
+
if (cacheKey === this.lastMCPKey)
|
|
11
|
+
return;
|
|
12
|
+
this.lastMCPKey = cacheKey;
|
|
13
|
+
// Merge agentfeed MCP server into ~/.gemini/settings.json
|
|
14
|
+
const settingsDir = path.join(os.homedir(), ".gemini");
|
|
15
|
+
const settingsPath = path.join(settingsDir, "settings.json");
|
|
16
|
+
let settings = {};
|
|
17
|
+
if (fs.existsSync(settingsPath)) {
|
|
18
|
+
try {
|
|
19
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Corrupt file — preserve non-MCP content by starting fresh
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const existingMcp = (settings.mcpServers ?? {});
|
|
26
|
+
settings.mcpServers = {
|
|
27
|
+
...existingMcp,
|
|
28
|
+
agentfeed: { command: "node", args: [mcpServerPath], env },
|
|
29
|
+
};
|
|
30
|
+
if (!fs.existsSync(settingsDir)) {
|
|
31
|
+
fs.mkdirSync(settingsDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
34
|
+
}
|
|
35
|
+
buildArgs(options) {
|
|
36
|
+
const { prompt, systemPrompt, sessionId, permissionMode, extraAllowedTools } = options;
|
|
37
|
+
// Gemini has no --append-system-prompt flag, embed in user prompt
|
|
38
|
+
const fullPrompt = `[System Instructions]\n${systemPrompt}\n\n[Task]\n${prompt}`;
|
|
39
|
+
const args = [fullPrompt];
|
|
40
|
+
if (sessionId) {
|
|
41
|
+
args.push("--resume", sessionId);
|
|
42
|
+
}
|
|
43
|
+
if (permissionMode === "yolo") {
|
|
44
|
+
args.push("--yolo");
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Allow agentfeed MCP tools without confirmation in safe mode
|
|
48
|
+
// Exclude set_status — worker manages thinking/idle externally.
|
|
49
|
+
// Gemini tends to loop on set_status calls, wasting API quota.
|
|
50
|
+
const allowedTools = [
|
|
51
|
+
"agentfeed_get_feeds",
|
|
52
|
+
"agentfeed_get_posts",
|
|
53
|
+
"agentfeed_get_post",
|
|
54
|
+
"agentfeed_create_post",
|
|
55
|
+
"agentfeed_get_comments",
|
|
56
|
+
"agentfeed_post_comment",
|
|
57
|
+
"agentfeed_download_file",
|
|
58
|
+
...(extraAllowedTools ?? []),
|
|
59
|
+
];
|
|
60
|
+
for (const tool of allowedTools) {
|
|
61
|
+
args.push("--allowed-tools", tool);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
args.push("--output-format", "stream-json");
|
|
65
|
+
return args;
|
|
66
|
+
}
|
|
67
|
+
buildEnv(baseEnv) {
|
|
68
|
+
const env = { ...baseEnv };
|
|
69
|
+
const passthroughKeys = [
|
|
70
|
+
"GEMINI_API_KEY", "GOOGLE_API_KEY",
|
|
71
|
+
"GOOGLE_CLOUD_PROJECT", "GOOGLE_CLOUD_PROJECT_ID",
|
|
72
|
+
"GOOGLE_CLOUD_LOCATION", "GOOGLE_APPLICATION_CREDENTIALS",
|
|
73
|
+
"GOOGLE_GENAI_USE_VERTEXAI",
|
|
74
|
+
];
|
|
75
|
+
for (const key of passthroughKeys) {
|
|
76
|
+
if (process.env[key]) {
|
|
77
|
+
env[key] = process.env[key];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return env;
|
|
81
|
+
}
|
|
82
|
+
parseSessionId(line) {
|
|
83
|
+
try {
|
|
84
|
+
const event = JSON.parse(line);
|
|
85
|
+
if (event.type === "init" && event.session_id) {
|
|
86
|
+
return event.session_id;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch { /* not JSON */ }
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
parseStreamText(line) {
|
|
93
|
+
try {
|
|
94
|
+
const event = JSON.parse(line);
|
|
95
|
+
if (event.type === "message" && event.role === "assistant" && event.content) {
|
|
96
|
+
return event.content;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch { /* not JSON */ }
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { CLIBackend, BackendType } from "./types.js";
|
|
2
|
+
export { ClaudeBackend } from "./claude.js";
|
|
3
|
+
export { CodexBackend } from "./codex.js";
|
|
4
|
+
export { GeminiBackend } from "./gemini.js";
|
|
5
|
+
import type { BackendType, CLIBackend } from "./types.js";
|
|
6
|
+
export declare function createBackend(type: BackendType): CLIBackend;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { ClaudeBackend } from "./claude.js";
|
|
2
|
+
export { CodexBackend } from "./codex.js";
|
|
3
|
+
export { GeminiBackend } from "./gemini.js";
|
|
4
|
+
import { ClaudeBackend } from "./claude.js";
|
|
5
|
+
import { CodexBackend } from "./codex.js";
|
|
6
|
+
import { GeminiBackend } from "./gemini.js";
|
|
7
|
+
export function createBackend(type) {
|
|
8
|
+
switch (type) {
|
|
9
|
+
case "claude":
|
|
10
|
+
return new ClaudeBackend();
|
|
11
|
+
case "codex":
|
|
12
|
+
return new CodexBackend();
|
|
13
|
+
case "gemini":
|
|
14
|
+
return new GeminiBackend();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PermissionMode, BackendType } from "../types.js";
|
|
2
|
+
export type { BackendType };
|
|
3
|
+
export interface BuildArgsOptions {
|
|
4
|
+
prompt: string;
|
|
5
|
+
systemPrompt: string;
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
permissionMode: PermissionMode;
|
|
8
|
+
extraAllowedTools?: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface CLIBackend {
|
|
11
|
+
readonly name: BackendType;
|
|
12
|
+
readonly binaryName: string;
|
|
13
|
+
/** Write MCP config in the format the CLI expects (skips if unchanged) */
|
|
14
|
+
setupMCP(env: Record<string, string>, mcpServerPath: string): void;
|
|
15
|
+
/** Build CLI argument array */
|
|
16
|
+
buildArgs(options: BuildArgsOptions): string[];
|
|
17
|
+
/** Build environment variables to pass to the CLI process */
|
|
18
|
+
buildEnv(baseEnv: Record<string, string>): Record<string, string>;
|
|
19
|
+
/** Extract session_id from a single stream-json line (undefined if not found) */
|
|
20
|
+
parseSessionId(line: string): string | undefined;
|
|
21
|
+
/** Extract displayable text from a single stream-json line (undefined if not found) */
|
|
22
|
+
parseStreamText(line: string): string | undefined;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PermissionMode, BackendType } from "./types.js";
|
|
2
|
+
export declare function getRequiredEnv(name: string): string;
|
|
3
|
+
export declare function parsePermissionMode(): PermissionMode;
|
|
4
|
+
export declare function parseAllowedTools(): string[];
|
|
5
|
+
export declare function detectInstalledBackends(): BackendType[];
|
|
6
|
+
export declare function probeBackend(type: BackendType): Promise<boolean>;
|
|
7
|
+
export declare function confirmYolo(): Promise<boolean>;
|
|
8
|
+
export declare function migrateSessionFile(backendType: BackendType): void;
|