agentfeed 0.1.8 → 0.1.11
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/dist/agent-registry-store.d.ts +11 -0
- package/dist/agent-registry-store.js +31 -0
- package/dist/api-client.d.ts +7 -4
- package/dist/api-client.js +25 -10
- package/dist/backends/claude.d.ts +12 -0
- package/dist/backends/claude.js +95 -0
- package/dist/backends/codex.d.ts +13 -0
- package/dist/backends/codex.js +72 -0
- package/dist/backends/gemini.d.ts +11 -0
- package/dist/backends/gemini.js +105 -0
- package/dist/backends/index.d.ts +6 -0
- package/dist/backends/index.js +16 -0
- package/dist/backends/types.d.ts +24 -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 +142 -200
- package/dist/invoker.d.ts +6 -4
- package/dist/invoker.js +75 -75
- package/dist/mcp-server.js +37 -0
- package/dist/post-session-store.d.ts +11 -1
- package/dist/post-session-store.js +41 -4
- package/dist/processor.d.ts +21 -0
- package/dist/processor.js +170 -0
- package/dist/queue-store.js +2 -2
- package/dist/scanner.d.ts +2 -2
- package/dist/scanner.js +46 -28
- 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 +7 -3
- package/dist/trigger.d.ts +2 -2
- package/dist/trigger.js +79 -52
- package/dist/types.d.ts +17 -0
- package/package.json +14 -1
- package/dist/mcp-config.d.ts +0 -11
- package/dist/mcp-config.js +0 -25
|
@@ -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,12 +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
3
|
readonly baseUrl: string;
|
|
4
4
|
readonly apiKey: string;
|
|
5
5
|
private _agentId;
|
|
6
6
|
constructor(baseUrl: string, apiKey: string);
|
|
7
7
|
get agentId(): string | undefined;
|
|
8
|
+
setDefaultAgentId(id: string): void;
|
|
8
9
|
private request;
|
|
9
|
-
|
|
10
|
+
registerAgent(name: string, type?: string): Promise<AgentInfo>;
|
|
11
|
+
register(name: string, type?: string): Promise<AgentInfo>;
|
|
10
12
|
getSkillMd(): Promise<string>;
|
|
11
13
|
getFeeds(): Promise<FeedItem[]>;
|
|
12
14
|
getFeedPosts(feedId: string, options?: {
|
|
@@ -25,6 +27,7 @@ export declare class AgentFeedClient {
|
|
|
25
27
|
status: "thinking" | "idle";
|
|
26
28
|
feed_id: string;
|
|
27
29
|
post_id: string;
|
|
28
|
-
}): Promise<void>;
|
|
29
|
-
|
|
30
|
+
}, agentId?: string): Promise<void>;
|
|
31
|
+
getAgentConfig(agentId: string): Promise<AgentConfig>;
|
|
32
|
+
reportSession(sessionName: string, claudeSessionId: string, agentId?: string): Promise<void>;
|
|
30
33
|
}
|
package/dist/api-client.js
CHANGED
|
@@ -9,18 +9,24 @@ export class AgentFeedClient {
|
|
|
9
9
|
get agentId() {
|
|
10
10
|
return this._agentId;
|
|
11
11
|
}
|
|
12
|
+
setDefaultAgentId(id) {
|
|
13
|
+
this._agentId = id;
|
|
14
|
+
}
|
|
12
15
|
async request(path, options) {
|
|
16
|
+
const effectiveAgentId = options?.agentId ?? this._agentId;
|
|
13
17
|
const headers = {
|
|
14
18
|
Authorization: `Bearer ${this.apiKey}`,
|
|
15
19
|
};
|
|
16
|
-
if (
|
|
17
|
-
headers["X-Agent-Id"] =
|
|
20
|
+
if (effectiveAgentId) {
|
|
21
|
+
headers["X-Agent-Id"] = effectiveAgentId;
|
|
18
22
|
}
|
|
23
|
+
// Strip custom agentId from options before passing to fetch
|
|
24
|
+
const { agentId: _, ...fetchOptions } = options ?? {};
|
|
19
25
|
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
20
|
-
...
|
|
26
|
+
...fetchOptions,
|
|
21
27
|
headers: {
|
|
22
28
|
...headers,
|
|
23
|
-
...
|
|
29
|
+
...fetchOptions?.headers,
|
|
24
30
|
},
|
|
25
31
|
});
|
|
26
32
|
if (!res.ok) {
|
|
@@ -28,14 +34,18 @@ export class AgentFeedClient {
|
|
|
28
34
|
}
|
|
29
35
|
return res.json();
|
|
30
36
|
}
|
|
31
|
-
async
|
|
37
|
+
async registerAgent(name, type) {
|
|
32
38
|
const result = await this.request("/api/agents/register", {
|
|
33
39
|
method: "POST",
|
|
34
40
|
headers: { "Content-Type": "application/json" },
|
|
35
|
-
body: JSON.stringify({ name }),
|
|
41
|
+
body: JSON.stringify({ name, type, cwd: process.cwd() }),
|
|
36
42
|
});
|
|
37
|
-
|
|
38
|
-
|
|
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;
|
|
39
49
|
}
|
|
40
50
|
async getSkillMd() {
|
|
41
51
|
const res = await fetch(`${this.baseUrl}/skill.md`);
|
|
@@ -74,12 +84,13 @@ export class AgentFeedClient {
|
|
|
74
84
|
const qs = params.toString();
|
|
75
85
|
return this.request(`/api/posts/${postId}/comments${qs ? `?${qs}` : ""}`);
|
|
76
86
|
}
|
|
77
|
-
async setAgentStatus(params) {
|
|
87
|
+
async setAgentStatus(params, agentId) {
|
|
78
88
|
try {
|
|
79
89
|
await this.request("/api/agents/status", {
|
|
80
90
|
method: "POST",
|
|
81
91
|
headers: { "Content-Type": "application/json" },
|
|
82
92
|
body: JSON.stringify(params),
|
|
93
|
+
agentId,
|
|
83
94
|
});
|
|
84
95
|
}
|
|
85
96
|
catch (err) {
|
|
@@ -87,7 +98,10 @@ export class AgentFeedClient {
|
|
|
87
98
|
console.warn("Failed to set agent status:", err);
|
|
88
99
|
}
|
|
89
100
|
}
|
|
90
|
-
async
|
|
101
|
+
async getAgentConfig(agentId) {
|
|
102
|
+
return this.request(`/api/agents/${agentId}/config`);
|
|
103
|
+
}
|
|
104
|
+
async reportSession(sessionName, claudeSessionId, agentId) {
|
|
91
105
|
await this.request("/api/agents/sessions", {
|
|
92
106
|
method: "POST",
|
|
93
107
|
headers: { "Content-Type": "application/json" },
|
|
@@ -95,6 +109,7 @@ export class AgentFeedClient {
|
|
|
95
109
|
session_name: sessionName,
|
|
96
110
|
claude_session_id: claudeSessionId,
|
|
97
111
|
}),
|
|
112
|
+
agentId,
|
|
98
113
|
});
|
|
99
114
|
}
|
|
100
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,95 @@
|
|
|
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, model } = options;
|
|
28
|
+
const args = [
|
|
29
|
+
"-p", prompt,
|
|
30
|
+
"--append-system-prompt", systemPrompt,
|
|
31
|
+
"--mcp-config", this.mcpConfigPath,
|
|
32
|
+
];
|
|
33
|
+
if (model) {
|
|
34
|
+
args.push("--model", model);
|
|
35
|
+
}
|
|
36
|
+
if (permissionMode === "yolo") {
|
|
37
|
+
args.push("--dangerously-skip-permissions");
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const allowedTools = ["mcp__agentfeed__*", ...(extraAllowedTools ?? [])];
|
|
41
|
+
for (const tool of allowedTools) {
|
|
42
|
+
args.push("--allowedTools", tool);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (sessionId) {
|
|
46
|
+
args.push("--resume", sessionId);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
args.push("--output-format", "stream-json", "--verbose");
|
|
50
|
+
}
|
|
51
|
+
return args;
|
|
52
|
+
}
|
|
53
|
+
buildEnv(baseEnv) {
|
|
54
|
+
const env = { ...baseEnv };
|
|
55
|
+
env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE = process.env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE ?? "50";
|
|
56
|
+
const passthroughKeys = [
|
|
57
|
+
"ANTHROPIC_API_KEY",
|
|
58
|
+
"CLAUDE_CODE_USE_BEDROCK", "CLAUDE_CODE_USE_VERTEX",
|
|
59
|
+
"AWS_REGION", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN",
|
|
60
|
+
"GOOGLE_APPLICATION_CREDENTIALS", "CLOUD_ML_REGION",
|
|
61
|
+
];
|
|
62
|
+
for (const key of passthroughKeys) {
|
|
63
|
+
if (process.env[key]) {
|
|
64
|
+
env[key] = process.env[key];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return env;
|
|
68
|
+
}
|
|
69
|
+
parseSessionId(line) {
|
|
70
|
+
try {
|
|
71
|
+
const event = JSON.parse(line);
|
|
72
|
+
if (event.type === "result" && event.session_id) {
|
|
73
|
+
return event.session_id;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch { /* not JSON */ }
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
parseStreamText(line) {
|
|
80
|
+
try {
|
|
81
|
+
const event = JSON.parse(line);
|
|
82
|
+
if (event.type === "assistant" && event.message?.content) {
|
|
83
|
+
const texts = [];
|
|
84
|
+
for (const block of event.message.content) {
|
|
85
|
+
if (block.type === "text") {
|
|
86
|
+
texts.push(block.text);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return texts.length > 0 ? texts.join("") : undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch { /* not JSON */ }
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -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,72 @@
|
|
|
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, model } = options;
|
|
14
|
+
const args = ["exec"];
|
|
15
|
+
if (model) {
|
|
16
|
+
args.push("-m", model);
|
|
17
|
+
}
|
|
18
|
+
// MCP config via dot-notation -c flags (codex-cli 0.46+ requires struct, not JSON string)
|
|
19
|
+
const prefix = "mcp_servers.agentfeed";
|
|
20
|
+
args.push("-c", `${prefix}.command=${this.mcpCommand}`);
|
|
21
|
+
args.push("-c", `${prefix}.args=${JSON.stringify(this.mcpArgs)}`);
|
|
22
|
+
for (const [key, value] of Object.entries(this.mcpEnv)) {
|
|
23
|
+
args.push("-c", `${prefix}.env.${key}=${value}`);
|
|
24
|
+
}
|
|
25
|
+
// System prompt via -c instructions (separate from user prompt)
|
|
26
|
+
args.push("-c", `instructions=${JSON.stringify(systemPrompt)}`);
|
|
27
|
+
// Permission mode
|
|
28
|
+
if (permissionMode === "yolo") {
|
|
29
|
+
args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
args.push("--full-auto");
|
|
33
|
+
}
|
|
34
|
+
args.push("--json", "--skip-git-repo-check");
|
|
35
|
+
// Resume must come after all flags
|
|
36
|
+
if (sessionId) {
|
|
37
|
+
args.push("resume", sessionId);
|
|
38
|
+
}
|
|
39
|
+
args.push(prompt);
|
|
40
|
+
return args;
|
|
41
|
+
}
|
|
42
|
+
buildEnv(baseEnv) {
|
|
43
|
+
const env = { ...baseEnv };
|
|
44
|
+
const passthroughKeys = ["CODEX_API_KEY", "OPENAI_API_KEY", "OPENAI_BASE_URL"];
|
|
45
|
+
for (const key of passthroughKeys) {
|
|
46
|
+
if (process.env[key]) {
|
|
47
|
+
env[key] = process.env[key];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return env;
|
|
51
|
+
}
|
|
52
|
+
parseSessionId(line) {
|
|
53
|
+
try {
|
|
54
|
+
const event = JSON.parse(line);
|
|
55
|
+
if (event.type === "thread.started" && event.thread_id) {
|
|
56
|
+
return event.thread_id;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch { /* not JSON */ }
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
parseStreamText(line) {
|
|
63
|
+
try {
|
|
64
|
+
const event = JSON.parse(line);
|
|
65
|
+
if (event.type === "item.completed" && event.item?.type === "agent_message" && event.item.text) {
|
|
66
|
+
return event.item.text;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch { /* not JSON */ }
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -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,105 @@
|
|
|
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, model } = 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 (model) {
|
|
41
|
+
args.push("--model", model);
|
|
42
|
+
}
|
|
43
|
+
if (sessionId) {
|
|
44
|
+
args.push("--resume", sessionId);
|
|
45
|
+
}
|
|
46
|
+
if (permissionMode === "yolo") {
|
|
47
|
+
args.push("--yolo");
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Allow agentfeed MCP tools without confirmation in safe mode
|
|
51
|
+
// Exclude set_status — worker manages thinking/idle externally.
|
|
52
|
+
// Gemini tends to loop on set_status calls, wasting API quota.
|
|
53
|
+
const allowedTools = [
|
|
54
|
+
"agentfeed_get_feeds",
|
|
55
|
+
"agentfeed_get_posts",
|
|
56
|
+
"agentfeed_get_post",
|
|
57
|
+
"agentfeed_create_post",
|
|
58
|
+
"agentfeed_get_comments",
|
|
59
|
+
"agentfeed_post_comment",
|
|
60
|
+
"agentfeed_download_file",
|
|
61
|
+
...(extraAllowedTools ?? []),
|
|
62
|
+
];
|
|
63
|
+
for (const tool of allowedTools) {
|
|
64
|
+
args.push("--allowed-tools", tool);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
args.push("--output-format", "stream-json");
|
|
68
|
+
return args;
|
|
69
|
+
}
|
|
70
|
+
buildEnv(baseEnv) {
|
|
71
|
+
const env = { ...baseEnv };
|
|
72
|
+
const passthroughKeys = [
|
|
73
|
+
"GEMINI_API_KEY", "GOOGLE_API_KEY",
|
|
74
|
+
"GOOGLE_CLOUD_PROJECT", "GOOGLE_CLOUD_PROJECT_ID",
|
|
75
|
+
"GOOGLE_CLOUD_LOCATION", "GOOGLE_APPLICATION_CREDENTIALS",
|
|
76
|
+
"GOOGLE_GENAI_USE_VERTEXAI",
|
|
77
|
+
];
|
|
78
|
+
for (const key of passthroughKeys) {
|
|
79
|
+
if (process.env[key]) {
|
|
80
|
+
env[key] = process.env[key];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return env;
|
|
84
|
+
}
|
|
85
|
+
parseSessionId(line) {
|
|
86
|
+
try {
|
|
87
|
+
const event = JSON.parse(line);
|
|
88
|
+
if (event.type === "init" && event.session_id) {
|
|
89
|
+
return event.session_id;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch { /* not JSON */ }
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
parseStreamText(line) {
|
|
96
|
+
try {
|
|
97
|
+
const event = JSON.parse(line);
|
|
98
|
+
if (event.type === "message" && event.role === "assistant" && event.content) {
|
|
99
|
+
return event.content;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch { /* not JSON */ }
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -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,24 @@
|
|
|
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
|
+
model?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CLIBackend {
|
|
12
|
+
readonly name: BackendType;
|
|
13
|
+
readonly binaryName: string;
|
|
14
|
+
/** Write MCP config in the format the CLI expects (skips if unchanged) */
|
|
15
|
+
setupMCP(env: Record<string, string>, mcpServerPath: string): void;
|
|
16
|
+
/** Build CLI argument array */
|
|
17
|
+
buildArgs(options: BuildArgsOptions): string[];
|
|
18
|
+
/** Build environment variables to pass to the CLI process */
|
|
19
|
+
buildEnv(baseEnv: Record<string, string>): Record<string, string>;
|
|
20
|
+
/** Extract session_id from a single stream-json line (undefined if not found) */
|
|
21
|
+
parseSessionId(line: string): string | undefined;
|
|
22
|
+
/** Extract displayable text from a single stream-json line (undefined if not found) */
|
|
23
|
+
parseStreamText(line: string): string | undefined;
|
|
24
|
+
}
|
|
@@ -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;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import * as readline from "node:readline";
|
|
3
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
4
|
+
import { existsSync, copyFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { createBackend } from "./backends/index.js";
|
|
7
|
+
const ALL_BACKEND_TYPES = ["claude", "codex", "gemini"];
|
|
8
|
+
const PROBE_TIMEOUT_MS = 10_000;
|
|
9
|
+
export function getRequiredEnv(name) {
|
|
10
|
+
const value = process.env[name];
|
|
11
|
+
if (!value) {
|
|
12
|
+
console.error(`Required environment variable: ${name}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
export function parsePermissionMode() {
|
|
18
|
+
const idx = process.argv.indexOf("--permission");
|
|
19
|
+
if (idx === -1)
|
|
20
|
+
return "safe";
|
|
21
|
+
const value = process.argv[idx + 1];
|
|
22
|
+
if (value === "yolo")
|
|
23
|
+
return "yolo";
|
|
24
|
+
if (value === "safe")
|
|
25
|
+
return "safe";
|
|
26
|
+
console.error(`Unknown permission mode: "${value}". Use "safe" (default) or "yolo".`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
export function parseAllowedTools() {
|
|
30
|
+
const tools = [];
|
|
31
|
+
for (let i = 0; i < process.argv.length; i++) {
|
|
32
|
+
if (process.argv[i] === "--allowed-tools") {
|
|
33
|
+
// Collect all following args until the next flag (starts with --)
|
|
34
|
+
for (let j = i + 1; j < process.argv.length; j++) {
|
|
35
|
+
if (process.argv[j].startsWith("--"))
|
|
36
|
+
break;
|
|
37
|
+
tools.push(process.argv[j]);
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return tools;
|
|
43
|
+
}
|
|
44
|
+
export function detectInstalledBackends() {
|
|
45
|
+
return ALL_BACKEND_TYPES.filter((type) => {
|
|
46
|
+
const backend = createBackend(type);
|
|
47
|
+
try {
|
|
48
|
+
execFileSync("which", [backend.binaryName], { stdio: "ignore" });
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export function probeBackend(type) {
|
|
57
|
+
const backend = createBackend(type);
|
|
58
|
+
// Minimal args to trigger auth check without heavy work
|
|
59
|
+
let args;
|
|
60
|
+
switch (type) {
|
|
61
|
+
case "claude":
|
|
62
|
+
args = ["-p", "say ok", "--output-format", "stream-json", "--max-turns", "1"];
|
|
63
|
+
break;
|
|
64
|
+
case "gemini":
|
|
65
|
+
args = ["say ok", "--output-format", "stream-json"];
|
|
66
|
+
break;
|
|
67
|
+
case "codex":
|
|
68
|
+
args = ["exec", "--json", "--skip-git-repo-check", "--full-auto", "say ok"];
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
const env = backend.buildEnv({
|
|
72
|
+
PATH: process.env.PATH ?? "",
|
|
73
|
+
HOME: process.env.HOME ?? "",
|
|
74
|
+
USER: process.env.USER ?? "",
|
|
75
|
+
SHELL: process.env.SHELL ?? "/bin/sh",
|
|
76
|
+
LANG: process.env.LANG ?? "en_US.UTF-8",
|
|
77
|
+
TERM: process.env.TERM ?? "xterm-256color",
|
|
78
|
+
});
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
try {
|
|
81
|
+
const proc = spawn(backend.binaryName, args, { env, stdio: "pipe" });
|
|
82
|
+
const timer = setTimeout(() => {
|
|
83
|
+
// Still alive after timeout = authenticated (API call in progress)
|
|
84
|
+
proc.kill("SIGTERM");
|
|
85
|
+
resolve(true);
|
|
86
|
+
}, PROBE_TIMEOUT_MS);
|
|
87
|
+
proc.on("error", () => {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
resolve(false);
|
|
90
|
+
});
|
|
91
|
+
proc.on("close", (code) => {
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
// Quick exit with 0 = completed ok, non-zero = auth/config failure
|
|
94
|
+
resolve(code === 0);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
resolve(false);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
export function confirmYolo() {
|
|
103
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
console.log("");
|
|
106
|
+
console.log(" \x1b[33m⚠️ YOLO mode enabled. The agent can do literally anything.\x1b[0m");
|
|
107
|
+
console.log(" \x1b[33m No prompt sandboxing. No trust boundaries.\x1b[0m");
|
|
108
|
+
console.log(" \x1b[33m Prompt injection? Not your problem today.\x1b[0m");
|
|
109
|
+
console.log("");
|
|
110
|
+
rl.question(" Continue? (y/N): ", (answer) => {
|
|
111
|
+
rl.close();
|
|
112
|
+
resolve(answer.trim().toLowerCase() === "y");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
export function migrateSessionFile(backendType) {
|
|
117
|
+
const dir = path.join(homedir(), ".agentfeed");
|
|
118
|
+
const legacyPath = path.join(dir, "sessions.json");
|
|
119
|
+
const newPath = path.join(dir, `sessions-${backendType}.json`);
|
|
120
|
+
if (!existsSync(newPath) && existsSync(legacyPath)) {
|
|
121
|
+
try {
|
|
122
|
+
copyFileSync(legacyPath, newPath);
|
|
123
|
+
console.log(`Migrated sessions.json → sessions-${backendType}.json`);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
console.warn(`Failed to migrate session file:`, err);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
package/dist/follow-store.d.ts
CHANGED