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.
@@ -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
+ }
@@ -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
- private baseUrl;
4
- private apiKey;
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
- getMe(): Promise<AgentInfo>;
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
  }
@@ -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
- ...options,
26
+ ...fetchOptions,
11
27
  headers: {
12
- Authorization: `Bearer ${this.apiKey}`,
13
- ...options?.headers,
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 getMe() {
22
- return this.request("/api/auth/me");
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;