opencode-froggy 0.1.0 → 0.3.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +508 -246
  3. package/agent/architect.md +91 -0
  4. package/agent/partner.md +143 -0
  5. package/agent/rubber-duck.md +129 -0
  6. package/command/commit-push.md +21 -0
  7. package/command/doc-changes.md +45 -0
  8. package/command/review-changes.md +1 -21
  9. package/command/review-pr.md +1 -22
  10. package/command/send-to.md +21 -0
  11. package/command/simplify-changes.md +2 -20
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.js +27 -52
  14. package/dist/index.test.js +29 -8
  15. package/dist/loaders.d.ts +9 -5
  16. package/dist/loaders.js +5 -1
  17. package/dist/tools/blockchain/eth-address-balance.d.ts +20 -0
  18. package/dist/tools/blockchain/eth-address-balance.js +37 -0
  19. package/dist/tools/blockchain/eth-address-txs.d.ts +23 -0
  20. package/dist/tools/blockchain/eth-address-txs.js +41 -0
  21. package/dist/tools/blockchain/eth-token-transfers.d.ts +23 -0
  22. package/dist/tools/blockchain/eth-token-transfers.js +41 -0
  23. package/dist/tools/blockchain/eth-transaction.d.ts +20 -0
  24. package/dist/tools/blockchain/eth-transaction.js +40 -0
  25. package/dist/tools/blockchain/etherscan-client.d.ts +25 -0
  26. package/dist/tools/blockchain/etherscan-client.js +156 -0
  27. package/dist/tools/blockchain/etherscan-client.test.d.ts +1 -0
  28. package/dist/tools/blockchain/etherscan-client.test.js +211 -0
  29. package/dist/tools/blockchain/formatters.d.ts +10 -0
  30. package/dist/tools/blockchain/formatters.js +147 -0
  31. package/dist/tools/blockchain/index.d.ts +10 -0
  32. package/dist/tools/blockchain/index.js +10 -0
  33. package/dist/tools/blockchain/tools.test.d.ts +1 -0
  34. package/dist/tools/blockchain/tools.test.js +208 -0
  35. package/dist/tools/blockchain/types.d.ts +90 -0
  36. package/dist/tools/blockchain/types.js +8 -0
  37. package/dist/tools/diff-summary.d.ts +20 -0
  38. package/dist/tools/diff-summary.js +111 -0
  39. package/dist/tools/gitingest.d.ts +26 -0
  40. package/dist/tools/gitingest.js +41 -0
  41. package/dist/tools/index.d.ts +5 -0
  42. package/dist/tools/index.js +5 -0
  43. package/dist/tools/list-child-sessions.d.ts +9 -0
  44. package/dist/tools/list-child-sessions.js +24 -0
  45. package/dist/tools/prompt-session.d.ts +19 -0
  46. package/dist/tools/prompt-session.js +39 -0
  47. package/dist/tools/reply-child.d.ts +19 -0
  48. package/dist/tools/reply-child.js +42 -0
  49. package/images/logo.png +0 -0
  50. package/package.json +4 -2
  51. package/command/commit.md +0 -18
@@ -0,0 +1,208 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { formatTransactionReceipt, formatTransactionList, formatBalance, formatTokenTransferList, } from "./formatters";
3
+ describe("Blockchain Formatters", () => {
4
+ describe("formatTransactionReceipt", () => {
5
+ it("should format successful transaction receipt", () => {
6
+ const receipt = {
7
+ status: "0x1",
8
+ blockNumber: "0xf4240",
9
+ from: "0x1111111111111111111111111111111111111111",
10
+ to: "0x2222222222222222222222222222222222222222",
11
+ gasUsed: "0x5208",
12
+ effectiveGasPrice: "0x3b9aca00",
13
+ logs: [],
14
+ };
15
+ const result = formatTransactionReceipt("0xabc123", receipt);
16
+ expect(result).toContain("## Transaction Details");
17
+ expect(result).toContain("**Hash:** 0xabc123");
18
+ expect(result).toContain("**Status:** Success");
19
+ expect(result).toContain("**Block:** 1000000");
20
+ expect(result).toContain("**From:** 0x1111111111111111111111111111111111111111");
21
+ expect(result).toContain("**To:** 0x2222222222222222222222222222222222222222");
22
+ expect(result).toContain("**Gas Used:** 21000");
23
+ expect(result).toContain("**Log Count:** 0");
24
+ });
25
+ it("should format failed transaction receipt", () => {
26
+ const receipt = {
27
+ status: "0x0",
28
+ blockNumber: "0xf4240",
29
+ from: "0x1111111111111111111111111111111111111111",
30
+ to: "0x2222222222222222222222222222222222222222",
31
+ gasUsed: "0x5208",
32
+ effectiveGasPrice: "0x3b9aca00",
33
+ logs: [],
34
+ };
35
+ const result = formatTransactionReceipt("0xabc123", receipt);
36
+ expect(result).toContain("**Status:** Failed");
37
+ });
38
+ it("should handle contract creation", () => {
39
+ const receipt = {
40
+ status: "0x1",
41
+ blockNumber: "0xf4240",
42
+ from: "0x1111111111111111111111111111111111111111",
43
+ to: null,
44
+ contractAddress: "0x3333333333333333333333333333333333333333",
45
+ gasUsed: "0x5208",
46
+ effectiveGasPrice: "0x3b9aca00",
47
+ logs: [],
48
+ };
49
+ const result = formatTransactionReceipt("0xabc123", receipt);
50
+ expect(result).toContain("**To:** Contract Creation");
51
+ expect(result).toContain("**Contract Created:** 0x3333333333333333333333333333333333333333");
52
+ });
53
+ });
54
+ describe("formatTransactionList", () => {
55
+ it("should format transaction list with in and out", () => {
56
+ const address = "0x1234567890123456789012345678901234567890";
57
+ const transactions = [
58
+ {
59
+ hash: "0xabc123",
60
+ blockNumber: "1000000",
61
+ blockHash: "0x...",
62
+ timeStamp: "1640000000",
63
+ from: address,
64
+ to: "0x2222222222222222222222222222222222222222",
65
+ value: "1000000000000000000",
66
+ gas: "21000",
67
+ gasPrice: "1000000000",
68
+ gasUsed: "21000",
69
+ nonce: "1",
70
+ transactionIndex: "0",
71
+ input: "0x",
72
+ isError: "0",
73
+ txreceipt_status: "1",
74
+ contractAddress: "",
75
+ cumulativeGasUsed: "21000",
76
+ confirmations: "100",
77
+ methodId: "0x",
78
+ functionName: "transfer(address,uint256)",
79
+ },
80
+ {
81
+ hash: "0xdef456",
82
+ blockNumber: "999999",
83
+ blockHash: "0x...",
84
+ timeStamp: "1639999000",
85
+ from: "0x3333333333333333333333333333333333333333",
86
+ to: address,
87
+ value: "500000000000000000",
88
+ gas: "21000",
89
+ gasPrice: "1000000000",
90
+ gasUsed: "21000",
91
+ nonce: "1",
92
+ transactionIndex: "0",
93
+ input: "0x",
94
+ isError: "0",
95
+ txreceipt_status: "1",
96
+ contractAddress: "",
97
+ cumulativeGasUsed: "21000",
98
+ confirmations: "100",
99
+ methodId: "0x",
100
+ functionName: "",
101
+ },
102
+ ];
103
+ const result = formatTransactionList(address, transactions);
104
+ expect(result).toContain("## Transactions for 0x1234...7890");
105
+ expect(result).toContain("**Total:** 2 (1 incoming, 1 outgoing)");
106
+ expect(result).toContain("[OUT]");
107
+ expect(result).toContain("[IN]");
108
+ expect(result).toContain("Value: 1 ETH");
109
+ expect(result).toContain("Value: 0.5 ETH");
110
+ expect(result).toContain("Function: transfer(address,uint256)");
111
+ });
112
+ it("should return no transactions message for empty list", () => {
113
+ const result = formatTransactionList("0x1234567890123456789012345678901234567890", []);
114
+ expect(result).toContain("No transactions found");
115
+ });
116
+ });
117
+ describe("formatBalance", () => {
118
+ it("should format balance in ETH and Wei", () => {
119
+ const result = formatBalance("0x1234567890123456789012345678901234567890", "1500000000000000000");
120
+ expect(result).toContain("## Balance for 0x1234567890123456789012345678901234567890");
121
+ expect(result).toContain("**ETH:** 1.5");
122
+ expect(result).toContain("**Wei:** 1500000000000000000");
123
+ });
124
+ it("should handle zero balance", () => {
125
+ const result = formatBalance("0x1234567890123456789012345678901234567890", "0");
126
+ expect(result).toContain("**ETH:** 0");
127
+ expect(result).toContain("**Wei:** 0");
128
+ });
129
+ });
130
+ describe("formatTokenTransferList", () => {
131
+ it("should format token transfer list", () => {
132
+ const address = "0x1234567890123456789012345678901234567890";
133
+ const transfers = [
134
+ {
135
+ hash: "0xtoken123",
136
+ blockNumber: "1000000",
137
+ timeStamp: "1640000000",
138
+ from: address,
139
+ to: "0x2222222222222222222222222222222222222222",
140
+ value: "1000000000000000000",
141
+ contractAddress: "0x4444444444444444444444444444444444444444",
142
+ tokenName: "Test Token",
143
+ tokenSymbol: "TST",
144
+ tokenDecimal: "18",
145
+ gas: "60000",
146
+ gasPrice: "1000000000",
147
+ gasUsed: "55000",
148
+ nonce: "1",
149
+ transactionIndex: "0",
150
+ },
151
+ ];
152
+ const result = formatTokenTransferList(address, transfers);
153
+ expect(result).toContain("## ERC-20 Token Transfers for 0x1234...7890");
154
+ expect(result).toContain("**Total:** 1 transfers");
155
+ expect(result).toContain("**Unique Tokens:** 1");
156
+ expect(result).toContain("Token: Test Token (TST)");
157
+ expect(result).toContain("[OUT]");
158
+ expect(result).toContain("Value: 1 TST");
159
+ });
160
+ it("should return no transfers message for empty list", () => {
161
+ const result = formatTokenTransferList("0x1234567890123456789012345678901234567890", []);
162
+ expect(result).toContain("No ERC-20 token transfers found");
163
+ });
164
+ it("should show token summary with multiple tokens", () => {
165
+ const address = "0x1234567890123456789012345678901234567890";
166
+ const transfers = [
167
+ {
168
+ hash: "0xtoken1",
169
+ blockNumber: "1000000",
170
+ timeStamp: "1640000000",
171
+ from: address,
172
+ to: "0x2222222222222222222222222222222222222222",
173
+ value: "1000000000000000000",
174
+ contractAddress: "0x4444444444444444444444444444444444444444",
175
+ tokenName: "Token A",
176
+ tokenSymbol: "TKA",
177
+ tokenDecimal: "18",
178
+ gas: "60000",
179
+ gasPrice: "1000000000",
180
+ gasUsed: "55000",
181
+ nonce: "1",
182
+ transactionIndex: "0",
183
+ },
184
+ {
185
+ hash: "0xtoken2",
186
+ blockNumber: "1000001",
187
+ timeStamp: "1640001000",
188
+ from: "0x3333333333333333333333333333333333333333",
189
+ to: address,
190
+ value: "2000000000000000000",
191
+ contractAddress: "0x5555555555555555555555555555555555555555",
192
+ tokenName: "Token B",
193
+ tokenSymbol: "TKB",
194
+ tokenDecimal: "18",
195
+ gas: "60000",
196
+ gasPrice: "1000000000",
197
+ gasUsed: "55000",
198
+ nonce: "1",
199
+ transactionIndex: "0",
200
+ },
201
+ ];
202
+ const result = formatTokenTransferList(address, transfers);
203
+ expect(result).toContain("**Unique Tokens:** 2");
204
+ expect(result).toContain("TKA: 0 in, 1 out");
205
+ expect(result).toContain("TKB: 1 in, 0 out");
206
+ });
207
+ });
208
+ });
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Etherscan API types for Ethereum blockchain tools
3
+ */
4
+ export interface EtherscanResponse<T> {
5
+ status: "0" | "1";
6
+ message: string;
7
+ result: T;
8
+ }
9
+ export interface EthTransaction {
10
+ hash: string;
11
+ blockNumber: string;
12
+ blockHash: string;
13
+ timeStamp: string;
14
+ from: string;
15
+ to: string;
16
+ value: string;
17
+ gas: string;
18
+ gasPrice: string;
19
+ gasUsed: string;
20
+ nonce: string;
21
+ transactionIndex: string;
22
+ input: string;
23
+ isError: string;
24
+ txreceipt_status: string;
25
+ contractAddress: string;
26
+ cumulativeGasUsed: string;
27
+ confirmations: string;
28
+ methodId: string;
29
+ functionName: string;
30
+ }
31
+ export interface EthTokenTransfer {
32
+ hash: string;
33
+ blockNumber: string;
34
+ timeStamp: string;
35
+ from: string;
36
+ to: string;
37
+ value: string;
38
+ contractAddress: string;
39
+ tokenName: string;
40
+ tokenSymbol: string;
41
+ tokenDecimal: string;
42
+ gas: string;
43
+ gasPrice: string;
44
+ gasUsed: string;
45
+ nonce: string;
46
+ transactionIndex: string;
47
+ }
48
+ export interface EthInternalTransaction {
49
+ hash: string;
50
+ blockNumber: string;
51
+ timeStamp: string;
52
+ from: string;
53
+ to: string;
54
+ value: string;
55
+ contractAddress: string;
56
+ input: string;
57
+ type: string;
58
+ gas: string;
59
+ gasUsed: string;
60
+ isError: string;
61
+ errCode: string;
62
+ }
63
+ export interface TransactionReceipt {
64
+ status: string;
65
+ blockHash: string;
66
+ blockNumber: string;
67
+ transactionHash: string;
68
+ transactionIndex: string;
69
+ from: string;
70
+ to: string;
71
+ contractAddress: string | null;
72
+ cumulativeGasUsed: string;
73
+ gasUsed: string;
74
+ effectiveGasPrice: string;
75
+ logs: TransactionLog[];
76
+ }
77
+ export interface TransactionLog {
78
+ address: string;
79
+ topics: string[];
80
+ data: string;
81
+ blockNumber: string;
82
+ transactionHash: string;
83
+ transactionIndex: string;
84
+ blockHash: string;
85
+ logIndex: string;
86
+ removed: boolean;
87
+ }
88
+ export declare const DEFAULT_TRANSACTION_LIMIT = 20;
89
+ export declare const DEFAULT_CHAIN_ID = "1";
90
+ export declare const CHAIN_ID_DESCRIPTION: string;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Etherscan API types for Ethereum blockchain tools
3
+ */
4
+ export const DEFAULT_TRANSACTION_LIMIT = 20;
5
+ export const DEFAULT_CHAIN_ID = "1";
6
+ export const CHAIN_ID_DESCRIPTION = "Chain ID (default: 1 for Ethereum). " +
7
+ "Examples: 1=Ethereum, 137=Polygon, 56=BSC, 42161=Arbitrum, " +
8
+ "10=Optimism, 8453=Base, 43114=Avalanche, 250=Fantom, 324=zkSync";
@@ -0,0 +1,20 @@
1
+ import { type ToolContext } from "@opencode-ai/plugin";
2
+ export interface DiffSummaryArgs {
3
+ source?: string;
4
+ target?: string;
5
+ remote?: string;
6
+ }
7
+ export declare function diffSummary(args: DiffSummaryArgs, cwd: string): Promise<string>;
8
+ export declare function createDiffSummaryTool(directory: string): {
9
+ description: string;
10
+ args: {
11
+ source: import("zod").ZodOptional<import("zod").ZodString>;
12
+ target: import("zod").ZodOptional<import("zod").ZodString>;
13
+ remote: import("zod").ZodOptional<import("zod").ZodString>;
14
+ };
15
+ execute(args: {
16
+ source?: string | undefined;
17
+ target?: string | undefined;
18
+ remote?: string | undefined;
19
+ }, context: ToolContext): Promise<string>;
20
+ };
@@ -0,0 +1,111 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { tool } from "@opencode-ai/plugin";
4
+ import { log } from "../logger";
5
+ const execFileAsync = promisify(execFile);
6
+ const DIFF_CONTEXT_LINES = 5;
7
+ async function git(args, cwd) {
8
+ try {
9
+ const result = await execFileAsync("git", args, { cwd, maxBuffer: 10 * 1024 * 1024 });
10
+ return result.stdout;
11
+ }
12
+ catch (error) {
13
+ const execError = error;
14
+ if (execError.stdout)
15
+ return execError.stdout;
16
+ throw error;
17
+ }
18
+ }
19
+ async function getDiffSet(cwd, extraArgs = []) {
20
+ const [stats, files, diff] = await Promise.all([
21
+ git(["diff", ...extraArgs, "--stat"], cwd),
22
+ git(["diff", ...extraArgs, "--name-status"], cwd),
23
+ git(["diff", ...extraArgs, `-U${DIFF_CONTEXT_LINES}`, "--function-context"], cwd),
24
+ ]);
25
+ return { stats, files, diff };
26
+ }
27
+ async function getBranchesDiff(source, target, remote, cwd) {
28
+ const refSource = remote ? `${remote}/${source}` : source;
29
+ const refTarget = remote ? `${remote}/${target}` : target;
30
+ const range = `${refTarget}...${refSource}`;
31
+ const rangeLog = `${refTarget}..${refSource}`;
32
+ if (remote) {
33
+ await git(["fetch", remote, source, target, "--prune"], cwd);
34
+ }
35
+ const [stats, commits, files, diff] = await Promise.all([
36
+ git(["diff", "--stat", range], cwd),
37
+ git(["log", "--oneline", "--no-merges", rangeLog], cwd),
38
+ git(["diff", "--name-only", range], cwd),
39
+ git(["diff", `-U${DIFF_CONTEXT_LINES}`, "--function-context", range], cwd),
40
+ ]);
41
+ return [
42
+ "## Stats Overview", "```", stats.trim(), "```",
43
+ "",
44
+ "## Commits to Review", "```", commits.trim(), "```",
45
+ "",
46
+ "## Files Changed", "```", files.trim(), "```",
47
+ "",
48
+ "## Full Diff", "```diff", diff.trim(), "```",
49
+ ].join("\n\n");
50
+ }
51
+ async function getWorkingTreeDiff(cwd) {
52
+ const sections = [];
53
+ const [status, staged, unstaged, untrackedList] = await Promise.all([
54
+ git(["status", "--porcelain=v1", "-uall"], cwd),
55
+ getDiffSet(cwd, ["--cached"]),
56
+ getDiffSet(cwd),
57
+ git(["ls-files", "--others", "--exclude-standard"], cwd),
58
+ ]);
59
+ sections.push("## Status Overview", "```", status.trim() || "(no changes)", "```");
60
+ if (staged.stats.trim() || staged.files.trim()) {
61
+ sections.push("## Staged Changes", "### Stats", "```", staged.stats.trim() || "(none)", "```", "### Files", "```", staged.files.trim() || "(none)", "```", "### Diff", "```diff", staged.diff.trim() || "(none)", "```");
62
+ }
63
+ if (unstaged.stats.trim() || unstaged.files.trim()) {
64
+ sections.push("## Unstaged Changes", "### Stats", "```", unstaged.stats.trim() || "(none)", "```", "### Files", "```", unstaged.files.trim() || "(none)", "```", "### Diff", "```diff", unstaged.diff.trim() || "(none)", "```");
65
+ }
66
+ const untrackedFiles = untrackedList.trim().split("\n").filter(Boolean);
67
+ if (untrackedFiles.length > 0) {
68
+ const untrackedDiffs = [];
69
+ for (const file of untrackedFiles) {
70
+ try {
71
+ const fileDiff = await git(["diff", "--no-index", `-U${DIFF_CONTEXT_LINES}`, "--function-context", "--", "/dev/null", file], cwd);
72
+ untrackedDiffs.push(`=== NEW: ${file} ===\n${fileDiff.trim()}`);
73
+ }
74
+ catch (error) {
75
+ log("[diff-summary] failed to diff untracked file", { file, error: String(error) });
76
+ untrackedDiffs.push(`=== NEW: ${file} === (could not diff)`);
77
+ }
78
+ }
79
+ sections.push("## Untracked Files", "```", untrackedFiles.join("\n"), "```", "### Diffs", "```diff", untrackedDiffs.join("\n\n"), "```");
80
+ }
81
+ return sections.join("\n\n");
82
+ }
83
+ export async function diffSummary(args, cwd) {
84
+ const { source, target = "main", remote = "origin" } = args;
85
+ if (source) {
86
+ return getBranchesDiff(source, target, remote, cwd);
87
+ }
88
+ return getWorkingTreeDiff(cwd);
89
+ }
90
+ export function createDiffSummaryTool(directory) {
91
+ return tool({
92
+ description: "Generate a structured summary of git diffs. Use for reviewing branches comparison or working tree changes. Returns stats, commits, files changed, and full diff.",
93
+ args: {
94
+ source: tool.schema
95
+ .string()
96
+ .optional()
97
+ .describe("Source branch to compare (e.g., 'feature-branch'). If omitted, analyzes working tree changes."),
98
+ target: tool.schema
99
+ .string()
100
+ .optional()
101
+ .describe("Target branch to compare against (default: 'main')"),
102
+ remote: tool.schema
103
+ .string()
104
+ .optional()
105
+ .describe("Git remote name (default: 'origin')"),
106
+ },
107
+ async execute(args, _context) {
108
+ return diffSummary(args, directory);
109
+ },
110
+ });
111
+ }
@@ -0,0 +1,26 @@
1
+ import { type ToolContext } from "@opencode-ai/plugin";
2
+ export interface GitingestArgs {
3
+ url: string;
4
+ maxFileSize?: number;
5
+ pattern?: string;
6
+ patternType?: "include" | "exclude";
7
+ }
8
+ export declare function fetchGitingest(args: GitingestArgs): Promise<string>;
9
+ export declare const gitingestTool: {
10
+ description: string;
11
+ args: {
12
+ url: import("zod").ZodString;
13
+ maxFileSize: import("zod").ZodOptional<import("zod").ZodNumber>;
14
+ pattern: import("zod").ZodOptional<import("zod").ZodString>;
15
+ patternType: import("zod").ZodOptional<import("zod").ZodEnum<{
16
+ include: "include";
17
+ exclude: "exclude";
18
+ }>>;
19
+ };
20
+ execute(args: {
21
+ url: string;
22
+ maxFileSize?: number | undefined;
23
+ pattern?: string | undefined;
24
+ patternType?: "include" | "exclude" | undefined;
25
+ }, context: ToolContext): Promise<string>;
26
+ };
@@ -0,0 +1,41 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ export async function fetchGitingest(args) {
3
+ const response = await fetch("https://gitingest.com/api/ingest", {
4
+ method: "POST",
5
+ headers: { "Content-Type": "application/json" },
6
+ body: JSON.stringify({
7
+ input_text: args.url,
8
+ max_file_size: args.maxFileSize ?? 50000,
9
+ pattern: args.pattern ?? "",
10
+ pattern_type: args.patternType ?? "exclude",
11
+ }),
12
+ });
13
+ if (!response.ok) {
14
+ throw new Error(`gitingest API error: ${response.status} ${response.statusText}`);
15
+ }
16
+ const data = (await response.json());
17
+ return `${data.summary}\n\n${data.tree}\n\n${data.content}`;
18
+ }
19
+ export const gitingestTool = tool({
20
+ description: "Fetch a GitHub repository's full content via gitingest.com. Returns summary, directory tree, and file contents optimized for LLM analysis. Use when you need to understand an external repository's structure or code.",
21
+ args: {
22
+ url: tool.schema
23
+ .string()
24
+ .describe("GitHub repository URL (e.g., https://github.com/owner/repo)"),
25
+ maxFileSize: tool.schema
26
+ .number()
27
+ .optional()
28
+ .describe("Maximum file size in bytes to include (default: 50000)"),
29
+ pattern: tool.schema
30
+ .string()
31
+ .optional()
32
+ .describe("Glob pattern to filter files (e.g., '*.py' or 'src/*')"),
33
+ patternType: tool.schema
34
+ .enum(["include", "exclude"])
35
+ .optional()
36
+ .describe("Whether pattern includes or excludes matching files (default: exclude)"),
37
+ },
38
+ async execute(args, _context) {
39
+ return fetchGitingest(args);
40
+ },
41
+ });
@@ -0,0 +1,5 @@
1
+ export { gitingestTool, fetchGitingest, type GitingestArgs } from "./gitingest";
2
+ export { createDiffSummaryTool, diffSummary, type DiffSummaryArgs } from "./diff-summary";
3
+ export { createPromptSessionTool, type PromptSessionArgs } from "./prompt-session";
4
+ export { createListChildSessionsTool } from "./list-child-sessions";
5
+ export { ethTransactionTool, ethAddressTxsTool, ethAddressBalanceTool, ethTokenTransfersTool, EtherscanClient, EtherscanClientError, weiToEth, formatTimestamp, shortenAddress, type EthTransactionArgs, type EthAddressTxsArgs, type EthAddressBalanceArgs, type EthTokenTransfersArgs, } from "./blockchain";
@@ -0,0 +1,5 @@
1
+ export { gitingestTool, fetchGitingest } from "./gitingest";
2
+ export { createDiffSummaryTool, diffSummary } from "./diff-summary";
3
+ export { createPromptSessionTool } from "./prompt-session";
4
+ export { createListChildSessionsTool } from "./list-child-sessions";
5
+ export { ethTransactionTool, ethAddressTxsTool, ethAddressBalanceTool, ethTokenTransfersTool, EtherscanClient, EtherscanClientError, weiToEth, formatTimestamp, shortenAddress, } from "./blockchain";
@@ -0,0 +1,9 @@
1
+ import { type ToolContext } from "@opencode-ai/plugin";
2
+ import type { createOpencodeClient } from "@opencode-ai/sdk";
3
+ type Client = ReturnType<typeof createOpencodeClient>;
4
+ export declare function createListChildSessionsTool(client: Client): {
5
+ description: string;
6
+ args: {};
7
+ execute(args: Record<string, never>, context: ToolContext): Promise<string>;
8
+ };
9
+ export {};
@@ -0,0 +1,24 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import { log } from "../logger";
3
+ export function createListChildSessionsTool(client) {
4
+ return tool({
5
+ description: "List all child sessions (subagents) of the current session",
6
+ args: {},
7
+ async execute(_args, context) {
8
+ const children = await client.session.children({
9
+ path: { id: context.sessionID },
10
+ });
11
+ const childList = children.data ?? [];
12
+ if (childList.length === 0) {
13
+ return "No child sessions found";
14
+ }
15
+ log("[list-child-sessions] Found child sessions", { count: childList.length });
16
+ const formatted = childList.map((child, index) => {
17
+ const created = new Date(child.time.created).toISOString();
18
+ const updated = new Date(child.time.updated).toISOString();
19
+ return `${index + 1}. [${child.id}] ${child.title}\n Created: ${created} | Updated: ${updated}`;
20
+ }).join("\n\n");
21
+ return `Child sessions (${childList.length}):\n\n${formatted}`;
22
+ },
23
+ });
24
+ }
@@ -0,0 +1,19 @@
1
+ import { type ToolContext } from "@opencode-ai/plugin";
2
+ import type { createOpencodeClient } from "@opencode-ai/sdk";
3
+ type Client = ReturnType<typeof createOpencodeClient>;
4
+ export interface PromptSessionArgs {
5
+ message: string;
6
+ sessionId?: string;
7
+ }
8
+ export declare function createPromptSessionTool(client: Client): {
9
+ description: string;
10
+ args: {
11
+ message: import("zod").ZodString;
12
+ sessionId: import("zod").ZodOptional<import("zod").ZodString>;
13
+ };
14
+ execute(args: {
15
+ message: string;
16
+ sessionId?: string | undefined;
17
+ }, context: ToolContext): Promise<string>;
18
+ };
19
+ export {};
@@ -0,0 +1,39 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import { log } from "../logger";
3
+ export function createPromptSessionTool(client) {
4
+ return tool({
5
+ description: "Send a message to a child session (subagent) to continue the conversation",
6
+ args: {
7
+ message: tool.schema.string().describe("The message to send to the child session"),
8
+ sessionId: tool.schema.string().optional().describe("The child session ID to target (optional - uses last child if not provided)"),
9
+ },
10
+ async execute(args, context) {
11
+ let targetSessionId = args.sessionId;
12
+ if (!targetSessionId) {
13
+ const children = await client.session.children({
14
+ path: { id: context.sessionID },
15
+ });
16
+ const lastChild = (children.data ?? []).at(-1);
17
+ if (!lastChild) {
18
+ return "Error: No child session found for current session";
19
+ }
20
+ targetSessionId = lastChild.id;
21
+ }
22
+ log("[prompt-session] Sending message to child session", {
23
+ parentSessionID: context.sessionID,
24
+ childSessionID: targetSessionId,
25
+ messagePreview: args.message.slice(0, 100),
26
+ });
27
+ const response = await client.session.prompt({
28
+ path: { id: targetSessionId },
29
+ body: { parts: [{ type: "text", text: args.message }] },
30
+ });
31
+ const parts = response.data?.parts ?? [];
32
+ const textContent = parts
33
+ .filter((p) => p.type === "text" && p.text)
34
+ .map((p) => p.text)
35
+ .join("\n");
36
+ return textContent || "Message sent to child session";
37
+ },
38
+ });
39
+ }
@@ -0,0 +1,19 @@
1
+ import { type ToolContext } from "@opencode-ai/plugin";
2
+ import type { createOpencodeClient } from "@opencode-ai/sdk";
3
+ type Client = ReturnType<typeof createOpencodeClient>;
4
+ export interface ReplyChildArgs {
5
+ message: string;
6
+ sessionId?: string;
7
+ }
8
+ export declare function createReplyChildTool(client: Client): {
9
+ description: string;
10
+ args: {
11
+ message: import("zod").ZodString;
12
+ sessionId: import("zod").ZodOptional<import("zod").ZodString>;
13
+ };
14
+ execute(args: {
15
+ message: string;
16
+ sessionId?: string | undefined;
17
+ }, context: ToolContext): Promise<string>;
18
+ };
19
+ export {};