opencode-froggy 0.11.0 → 1.0.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 (46) hide show
  1. package/README.md +29 -9
  2. package/command/diff-summary.md +27 -8
  3. package/command/doc-changes.md +1 -0
  4. package/command/linear-stale-check.md +199 -0
  5. package/command/review-changes.md +1 -0
  6. package/command/review-pr.md +1 -0
  7. package/command/simplify-changes.md +1 -0
  8. package/dist/command-installer.d.ts +6 -0
  9. package/dist/command-installer.js +49 -0
  10. package/dist/command-installer.test.d.ts +1 -0
  11. package/dist/command-installer.test.js +58 -0
  12. package/dist/config-paths.d.ts +2 -0
  13. package/dist/config-paths.js +6 -0
  14. package/dist/config-paths.test.js +16 -0
  15. package/dist/index.d.ts +3 -3
  16. package/dist/index.js +326 -259
  17. package/dist/index.test.js +3 -3
  18. package/dist/loaders.d.ts +2 -0
  19. package/dist/loaders.js +1 -0
  20. package/dist/session-children.d.ts +14 -0
  21. package/dist/session-children.js +27 -0
  22. package/dist/session-children.test.d.ts +1 -0
  23. package/dist/session-children.test.js +25 -0
  24. package/dist/skill-activation.js +1 -1
  25. package/dist/tools/agent-promote.d.ts +38 -11
  26. package/dist/tools/agent-promote.js +35 -33
  27. package/dist/tools/blockchain/eth-address-balance.d.ts +18 -8
  28. package/dist/tools/blockchain/eth-address-balance.js +15 -14
  29. package/dist/tools/blockchain/eth-address-txs.d.ts +22 -10
  30. package/dist/tools/blockchain/eth-address-txs.js +19 -18
  31. package/dist/tools/blockchain/eth-token-transfers.d.ts +22 -10
  32. package/dist/tools/blockchain/eth-token-transfers.js +19 -18
  33. package/dist/tools/blockchain/eth-transaction.d.ts +30 -14
  34. package/dist/tools/blockchain/eth-transaction.js +24 -21
  35. package/dist/tools/gitingest.d.ts +27 -15
  36. package/dist/tools/gitingest.js +28 -21
  37. package/dist/tools/index.d.ts +1 -1
  38. package/dist/tools/index.js +1 -1
  39. package/dist/tools/list-child-sessions.d.ts +11 -7
  40. package/dist/tools/list-child-sessions.js +22 -17
  41. package/dist/tools/pdf-to-markdown.d.ts +18 -8
  42. package/dist/tools/pdf-to-markdown.js +20 -13
  43. package/dist/tools/prompt-session.d.ts +26 -11
  44. package/dist/tools/prompt-session.js +23 -28
  45. package/package.json +13 -5
  46. package/tui.ts +24 -0
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Tool to get Ethereum transaction details by hash
3
3
  */
4
- import { tool } from "@opencode-ai/plugin";
5
4
  import { EtherscanClient, EtherscanClientError, validateTxHash, weiToEth } from "./etherscan-client";
6
5
  import { getTransactionReceipt, getBlock, getTokenMetadata } from "./viem-client";
7
6
  import { CHAIN_ID_DESCRIPTION, DEFAULT_CHAIN_ID, } from "./types";
@@ -164,40 +163,44 @@ export async function getTransactionDetails(hash, chainId, options = {}) {
164
163
  }
165
164
  return result;
166
165
  }
167
- export const ethTransactionTool = tool({
166
+ export const ethTransactionTool = {
167
+ name: "eth-transaction",
168
168
  description: "Get Ethereum transaction details by transaction hash. " +
169
169
  "Returns status, block, addresses, gas costs in JSON format. " +
170
170
  "Use optional parameters to include internal transactions, token transfers, and decoded event logs.",
171
- args: {
172
- hash: tool.schema.string().describe("Transaction hash (0x...)"),
173
- chainId: tool.schema.string().optional().describe(CHAIN_ID_DESCRIPTION),
174
- includeInternalTxs: tool.schema
175
- .boolean()
176
- .optional()
177
- .describe("Include internal transactions (ETH transfers between contracts)"),
178
- includeTokenTransfers: tool.schema
179
- .boolean()
180
- .optional()
181
- .describe("Include ERC-20 token transfers"),
182
- decodeLogs: tool.schema
183
- .boolean()
184
- .optional()
185
- .describe("Decode event logs (Transfer, Approval, Deposit, Withdrawal)"),
171
+ input: {
172
+ type: "object",
173
+ properties: {
174
+ hash: { type: "string", description: "Transaction hash (0x...)" },
175
+ chainId: { type: "string", description: CHAIN_ID_DESCRIPTION },
176
+ includeInternalTxs: {
177
+ type: "boolean",
178
+ description: "Include internal transactions (ETH transfers between contracts)",
179
+ },
180
+ includeTokenTransfers: { type: "boolean", description: "Include ERC-20 token transfers" },
181
+ decodeLogs: {
182
+ type: "boolean",
183
+ description: "Decode event logs (Transfer, Approval, Deposit, Withdrawal)",
184
+ },
185
+ },
186
+ required: ["hash"],
187
+ additionalProperties: false,
186
188
  },
187
- async execute(args, _context) {
189
+ async execute(input) {
190
+ const args = input;
188
191
  try {
189
192
  const result = await getTransactionDetails(args.hash, args.chainId, {
190
193
  includeInternalTxs: args.includeInternalTxs,
191
194
  includeTokenTransfers: args.includeTokenTransfers,
192
195
  decodeLogs: args.decodeLogs,
193
196
  });
194
- return JSON.stringify(result, null, 2);
197
+ return { content: JSON.stringify(result, null, 2) };
195
198
  }
196
199
  catch (error) {
197
200
  if (error instanceof EtherscanClientError) {
198
- return JSON.stringify({ error: error.message });
201
+ return { content: JSON.stringify({ error: error.message }) };
199
202
  }
200
203
  throw error;
201
204
  }
202
205
  },
203
- });
206
+ };
@@ -1,4 +1,3 @@
1
- import { type ToolContext } from "@opencode-ai/plugin";
2
1
  export interface GitingestArgs {
3
2
  url: string;
4
3
  maxFileSize?: number;
@@ -7,20 +6,33 @@ export interface GitingestArgs {
7
6
  }
8
7
  export declare function fetchGitingest(args: GitingestArgs): Promise<string>;
9
8
  export declare const gitingestTool: {
9
+ name: string;
10
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
- }>>;
11
+ input: {
12
+ type: string;
13
+ properties: {
14
+ url: {
15
+ type: string;
16
+ description: string;
17
+ };
18
+ maxFileSize: {
19
+ type: string;
20
+ description: string;
21
+ };
22
+ pattern: {
23
+ type: string;
24
+ description: string;
25
+ };
26
+ patternType: {
27
+ type: string;
28
+ enum: string[];
29
+ description: string;
30
+ };
31
+ };
32
+ required: string[];
33
+ additionalProperties: boolean;
19
34
  };
20
- execute(args: {
21
- url: string;
22
- maxFileSize?: number | undefined;
23
- pattern?: string | undefined;
24
- patternType?: "include" | "exclude" | undefined;
25
- }, context: ToolContext): Promise<string>;
35
+ execute(input: unknown): Promise<{
36
+ content: string;
37
+ }>;
26
38
  };
@@ -1,4 +1,3 @@
1
- import { tool } from "@opencode-ai/plugin";
2
1
  export async function fetchGitingest(args) {
3
2
  const response = await fetch("https://gitingest.com/api/ingest", {
4
3
  method: "POST",
@@ -16,26 +15,34 @@ export async function fetchGitingest(args) {
16
15
  const data = (await response.json());
17
16
  return `${data.summary}\n\n${data.tree}\n\n${data.content}`;
18
17
  }
19
- export const gitingestTool = tool({
18
+ export const gitingestTool = {
19
+ name: "gitingest",
20
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)"),
21
+ input: {
22
+ type: "object",
23
+ properties: {
24
+ url: {
25
+ type: "string",
26
+ description: "GitHub repository URL (e.g., https://github.com/owner/repo)",
27
+ },
28
+ maxFileSize: {
29
+ type: "number",
30
+ description: "Maximum file size in bytes to include (default: 50000)",
31
+ },
32
+ pattern: {
33
+ type: "string",
34
+ description: "Glob pattern to filter files (e.g., '*.py' or 'src/*')",
35
+ },
36
+ patternType: {
37
+ type: "string",
38
+ enum: ["include", "exclude"],
39
+ description: "Whether pattern includes or excludes matching files (default: exclude)",
40
+ },
41
+ },
42
+ required: ["url"],
43
+ additionalProperties: false,
37
44
  },
38
- async execute(args, _context) {
39
- return fetchGitingest(args);
45
+ async execute(input) {
46
+ return { content: await fetchGitingest(input) };
40
47
  },
41
- });
48
+ };
@@ -3,5 +3,5 @@ export { convertPdfToMarkdown, type PdfToMarkdownArgs } from "./pdf-to-markdown-
3
3
  export { pdfToMarkdownTool } from "./pdf-to-markdown";
4
4
  export { createPromptSessionTool, type PromptSessionArgs } from "./prompt-session";
5
5
  export { createListChildSessionsTool } from "./list-child-sessions";
6
- export { createAgentPromoteTool, getPromotedAgents, type AgentPromoteArgs } from "./agent-promote";
6
+ export { createAgentPromoteTool, getPromotedAgents, setPromotedAgent, validateGrade, validateAgentName, VALID_GRADES, AGENT_PROMOTE_STORAGE_KEY, type AgentMode, type AgentPromoteArgs, } from "./agent-promote";
7
7
  export { ethTransactionTool, ethAddressTxsTool, ethAddressBalanceTool, ethTokenTransfersTool, EtherscanClient, EtherscanClientError, weiToEth, formatTimestamp, shortenAddress, type EthTransactionArgs, type EthAddressTxsArgs, type EthAddressBalanceArgs, type EthTokenTransfersArgs, } from "./blockchain";
@@ -3,5 +3,5 @@ export { convertPdfToMarkdown } from "./pdf-to-markdown-core";
3
3
  export { pdfToMarkdownTool } from "./pdf-to-markdown";
4
4
  export { createPromptSessionTool } from "./prompt-session";
5
5
  export { createListChildSessionsTool } from "./list-child-sessions";
6
- export { createAgentPromoteTool, getPromotedAgents } from "./agent-promote";
6
+ export { createAgentPromoteTool, getPromotedAgents, setPromotedAgent, validateGrade, validateAgentName, VALID_GRADES, AGENT_PROMOTE_STORAGE_KEY, } from "./agent-promote";
7
7
  export { ethTransactionTool, ethAddressTxsTool, ethAddressBalanceTool, ethTokenTransfersTool, EtherscanClient, EtherscanClientError, weiToEth, formatTimestamp, shortenAddress, } from "./blockchain";
@@ -1,9 +1,13 @@
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): {
1
+ import type { ChildSessionTracker } from "../session-children";
2
+ export declare function createListChildSessionsTool(tracker: ChildSessionTracker): {
3
+ name: string;
5
4
  description: string;
6
- args: {};
7
- execute(args: Record<string, never>, context: ToolContext): Promise<string>;
5
+ input: {
6
+ type: string;
7
+ properties: {};
8
+ additionalProperties: boolean;
9
+ };
10
+ execute(_input: unknown, context: unknown): Promise<{
11
+ content: string;
12
+ }>;
8
13
  };
9
- export {};
@@ -1,24 +1,29 @@
1
- import { tool } from "@opencode-ai/plugin";
2
1
  import { log } from "../logger";
3
- export function createListChildSessionsTool(client) {
4
- return tool({
2
+ export function createListChildSessionsTool(tracker) {
3
+ return {
4
+ name: "list-child-sessions",
5
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 ?? [];
6
+ input: {
7
+ type: "object",
8
+ properties: {},
9
+ additionalProperties: false,
10
+ },
11
+ async execute(_input, context) {
12
+ const ctx = context;
13
+ const childList = tracker.listChildren(ctx.sessionID);
12
14
  if (childList.length === 0) {
13
- return "No child sessions found";
15
+ return { content: "No child sessions found" };
14
16
  }
15
17
  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}`;
18
+ const formatted = childList
19
+ .map((child, index) => {
20
+ const created = new Date(child.created).toISOString();
21
+ const updated = new Date(child.updated).toISOString();
22
+ const title = child.title ? ` ${child.title}` : "";
23
+ return `${index + 1}. [${child.id}]${title}\n Created: ${created} | Updated: ${updated}`;
24
+ })
25
+ .join("\n\n");
26
+ return { content: `Child sessions (${childList.length}):\n\n${formatted}` };
22
27
  },
23
- });
28
+ };
24
29
  }
@@ -1,12 +1,22 @@
1
- import { type ToolContext } from "@opencode-ai/plugin";
2
1
  export declare const pdfToMarkdownTool: {
2
+ name: string;
3
3
  description: string;
4
- args: {
5
- filePath: import("zod").ZodString;
6
- maxPages: import("zod").ZodOptional<import("zod").ZodNumber>;
4
+ input: {
5
+ type: string;
6
+ properties: {
7
+ filePath: {
8
+ type: string;
9
+ description: string;
10
+ };
11
+ maxPages: {
12
+ type: string;
13
+ description: string;
14
+ };
15
+ };
16
+ required: string[];
17
+ additionalProperties: boolean;
7
18
  };
8
- execute(args: {
9
- filePath: string;
10
- maxPages?: number | undefined;
11
- }, context: ToolContext): Promise<string>;
19
+ execute(input: unknown): Promise<{
20
+ content: string;
21
+ }>;
12
22
  };
@@ -1,17 +1,24 @@
1
- import { tool } from "@opencode-ai/plugin";
2
1
  import { convertPdfToMarkdown } from "./pdf-to-markdown-core";
3
- export const pdfToMarkdownTool = tool({
2
+ export const pdfToMarkdownTool = {
3
+ name: "pdf-to-markdown",
4
4
  description: "Convert a text-based PDF into enriched Markdown (headings, paragraphs, lists). Returns Markdown as plain text.",
5
- args: {
6
- filePath: tool.schema.string().describe("Absolute path to the PDF file to convert"),
7
- maxPages: tool.schema
8
- .number()
9
- .int()
10
- .positive()
11
- .optional()
12
- .describe("Limit the number of pages to convert"),
5
+ input: {
6
+ type: "object",
7
+ properties: {
8
+ filePath: {
9
+ type: "string",
10
+ description: "Absolute path to the PDF file to convert",
11
+ },
12
+ maxPages: {
13
+ type: "number",
14
+ description: "Limit the number of pages to convert",
15
+ },
16
+ },
17
+ required: ["filePath"],
18
+ additionalProperties: false,
13
19
  },
14
- async execute(args, _context) {
15
- return convertPdfToMarkdown(args.filePath, { maxPages: args.maxPages });
20
+ async execute(input) {
21
+ const args = input;
22
+ return { content: await convertPdfToMarkdown(args.filePath, { maxPages: args.maxPages }) };
16
23
  },
17
- });
24
+ };
@@ -1,19 +1,34 @@
1
- import { type ToolContext } from "@opencode-ai/plugin";
2
- import type { createOpencodeClient } from "@opencode-ai/sdk";
3
- type Client = ReturnType<typeof createOpencodeClient>;
1
+ import type { ChildSessionTracker } from "../session-children";
4
2
  export interface PromptSessionArgs {
5
3
  message: string;
6
4
  sessionId?: string;
7
5
  }
8
- export declare function createPromptSessionTool(client: Client): {
6
+ interface SessionPrompter {
7
+ prompt(input: {
8
+ sessionID: string;
9
+ text: string;
10
+ }): Promise<unknown>;
11
+ }
12
+ export declare function createPromptSessionTool(session: SessionPrompter, tracker: ChildSessionTracker): {
13
+ name: string;
9
14
  description: string;
10
- args: {
11
- message: import("zod").ZodString;
12
- sessionId: import("zod").ZodOptional<import("zod").ZodString>;
15
+ input: {
16
+ type: string;
17
+ properties: {
18
+ message: {
19
+ type: string;
20
+ description: string;
21
+ };
22
+ sessionId: {
23
+ type: string;
24
+ description: string;
25
+ };
26
+ };
27
+ required: string[];
28
+ additionalProperties: boolean;
13
29
  };
14
- execute(args: {
15
- message: string;
16
- sessionId?: string | undefined;
17
- }, context: ToolContext): Promise<string>;
30
+ execute(input: unknown, context: unknown): Promise<{
31
+ content: string;
32
+ }>;
18
33
  };
19
34
  export {};
@@ -1,39 +1,34 @@
1
- import { tool } from "@opencode-ai/plugin";
2
1
  import { log } from "../logger";
3
- export function createPromptSessionTool(client) {
4
- return tool({
2
+ export function createPromptSessionTool(session, tracker) {
3
+ return {
4
+ name: "prompt-session",
5
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)"),
6
+ input: {
7
+ type: "object",
8
+ properties: {
9
+ message: { type: "string", description: "The message to send to the child session" },
10
+ sessionId: {
11
+ type: "string",
12
+ description: "The child session ID to target (optional - uses last child if not provided)",
13
+ },
14
+ },
15
+ required: ["message"],
16
+ additionalProperties: false,
9
17
  },
10
- async execute(args, context) {
11
- let targetSessionId = args.sessionId;
18
+ async execute(input, context) {
19
+ const args = input;
20
+ const ctx = context;
21
+ const targetSessionId = args.sessionId ?? tracker.lastChild(ctx.sessionID)?.id;
12
22
  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;
23
+ return { content: "Error: No child session found for current session" };
21
24
  }
22
25
  log("[prompt-session] Sending message to child session", {
23
- parentSessionID: context.sessionID,
26
+ parentSessionID: ctx.sessionID,
24
27
  childSessionID: targetSessionId,
25
28
  messagePreview: args.message.slice(0, 100),
26
29
  });
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";
30
+ await session.prompt({ sessionID: targetSessionId, text: args.message });
31
+ return { content: `Message sent to child session ${targetSessionId}` };
37
32
  },
38
- });
33
+ };
39
34
  }
package/package.json CHANGED
@@ -1,15 +1,26 @@
1
1
  {
2
2
  "name": "opencode-froggy",
3
- "version": "0.11.0",
3
+ "version": "1.0.0",
4
4
  "description": "OpenCode plugin with a hook layer (tool.before.*, session.idle...), agents (code-reviewer, doc-writer), and commands (/review-pr, /commit)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ },
12
+ "./tui": {
13
+ "import": "./tui.ts",
14
+ "types": "./tui.ts"
15
+ }
16
+ },
7
17
  "type": "module",
8
18
  "files": [
9
19
  "dist",
10
20
  "agent",
11
21
  "command",
12
22
  "skill",
23
+ "tui.ts",
13
24
  "images",
14
25
  "README.md"
15
26
  ],
@@ -38,19 +49,16 @@
38
49
  "url": "https://github.com/smartfrog/opencode-froggy"
39
50
  },
40
51
  "dependencies": {
52
+ "@opencode/plugin": "^2.0.2",
41
53
  "js-yaml": "^4.1.0",
42
54
  "pdfjs-dist": "^4.8.69",
43
55
  "viem": "^2.44.1"
44
56
  },
45
57
  "devDependencies": {
46
- "@opencode-ai/plugin": "^1.4.6",
47
58
  "@types/bun": "latest",
48
59
  "@types/js-yaml": "^4.0.9",
49
60
  "@vitest/coverage-v8": "^4.0.17",
50
61
  "typescript": "^5.7.0",
51
62
  "vitest": "^4.0.16"
52
- },
53
- "peerDependencies": {
54
- "@opencode-ai/plugin": "*"
55
63
  }
56
64
  }
package/tui.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { Plugin } from "@opencode/plugin/tui"
2
+
3
+ export default Plugin.define({
4
+ id: "opencode-froggy.cli",
5
+ setup(context) {
6
+ const location = context.location ?? context.data.location.default()
7
+
8
+ const refreshAgents = () => {
9
+ context.data.location.agent.invalidate(location)
10
+ void context.data.location.agent.sync(location).catch(() => {})
11
+ }
12
+
13
+ const stopAgents = context.data.on("agent.updated", refreshAgents)
14
+ const stopCommands = context.data.on("command.updated", () => {
15
+ context.data.location.command.invalidate(location)
16
+ void context.data.location.command.sync(location).catch(() => {})
17
+ })
18
+
19
+ return () => {
20
+ stopAgents()
21
+ stopCommands()
22
+ }
23
+ },
24
+ })