opencode-froggy 0.12.0 → 1.0.1
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/README.md +10 -9
- package/command/diff-summary.md +27 -8
- package/command/doc-changes.md +1 -0
- package/command/review-changes.md +1 -0
- package/command/review-pr.md +1 -0
- package/command/simplify-changes.md +1 -0
- package/dist/command-installer.d.ts +6 -0
- package/dist/command-installer.js +49 -0
- package/dist/command-installer.test.d.ts +1 -0
- package/dist/command-installer.test.js +58 -0
- package/dist/config-paths.d.ts +2 -0
- package/dist/config-paths.js +6 -0
- package/dist/config-paths.test.js +16 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +326 -259
- package/dist/index.test.js +3 -3
- package/dist/loaders.d.ts +2 -0
- package/dist/loaders.js +1 -0
- package/dist/session-children.d.ts +14 -0
- package/dist/session-children.js +27 -0
- package/dist/session-children.test.d.ts +1 -0
- package/dist/session-children.test.js +25 -0
- package/dist/skill-activation.js +1 -1
- package/dist/tools/agent-promote.d.ts +38 -11
- package/dist/tools/agent-promote.js +35 -33
- package/dist/tools/blockchain/eth-address-balance.d.ts +18 -8
- package/dist/tools/blockchain/eth-address-balance.js +15 -14
- package/dist/tools/blockchain/eth-address-txs.d.ts +22 -10
- package/dist/tools/blockchain/eth-address-txs.js +19 -18
- package/dist/tools/blockchain/eth-token-transfers.d.ts +22 -10
- package/dist/tools/blockchain/eth-token-transfers.js +19 -18
- package/dist/tools/blockchain/eth-transaction.d.ts +30 -14
- package/dist/tools/blockchain/eth-transaction.js +24 -21
- package/dist/tools/gitingest.d.ts +27 -15
- package/dist/tools/gitingest.js +28 -21
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/list-child-sessions.d.ts +11 -7
- package/dist/tools/list-child-sessions.js +22 -17
- package/dist/tools/pdf-to-markdown.d.ts +18 -8
- package/dist/tools/pdf-to-markdown.js +20 -13
- package/dist/tools/prompt-session.d.ts +26 -11
- package/dist/tools/prompt-session.js +23 -28
- package/package.json +13 -5
- package/tui.ts +24 -0
package/README.md
CHANGED
|
@@ -56,16 +56,18 @@ Add the plugin to your OpenCode configuration file (`opencode.json`):
|
|
|
56
56
|
```json
|
|
57
57
|
{
|
|
58
58
|
"$schema": "https://opencode.ai/config.json",
|
|
59
|
-
"
|
|
59
|
+
"plugins": ["opencode-froggy"]
|
|
60
60
|
}
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
Requires OpenCode V2.
|
|
64
|
+
|
|
63
65
|
### From local files
|
|
64
66
|
|
|
65
67
|
Alternatively, clone or copy the plugin files to one of these directories:
|
|
66
68
|
|
|
67
|
-
- **Project-local**: `.opencode/
|
|
68
|
-
- **Global**: `~/.config/opencode/
|
|
69
|
+
- **Project-local**: `.opencode/plugins/opencode-froggy/`
|
|
70
|
+
- **Global**: `~/.config/opencode/plugins/opencode-froggy/`
|
|
69
71
|
|
|
70
72
|
---
|
|
71
73
|
|
|
@@ -140,7 +142,7 @@ It does not modify Linear issues, add comments, or update project files.
|
|
|
140
142
|
|
|
141
143
|
## Skills
|
|
142
144
|
|
|
143
|
-
Skills are contextual instructions loaded on demand via the `skill` tool. The agent invokes `skill({
|
|
145
|
+
Skills are contextual instructions loaded on demand via the `skill` tool. The agent invokes `skill({ id: "skill-name" })` to load the instructions when needed.
|
|
144
146
|
|
|
145
147
|
### Overview
|
|
146
148
|
|
|
@@ -270,7 +272,7 @@ A skill without `name` or `description` will be ignored.
|
|
|
270
272
|
If a skill defines `use_when`, a directive is injected into the system prompt:
|
|
271
273
|
|
|
272
274
|
```
|
|
273
|
-
MANDATORY: Call skill({
|
|
275
|
+
MANDATORY: Call skill({ id: "my-skill" }) <use_when content>
|
|
274
276
|
```
|
|
275
277
|
|
|
276
278
|
This instructs the agent to load the skill when the specified condition is met.
|
|
@@ -361,8 +363,8 @@ promptSession({
|
|
|
361
363
|
|
|
362
364
|
#### Behavior
|
|
363
365
|
|
|
364
|
-
- If `sessionId` is not provided, the tool
|
|
365
|
-
- Returns the
|
|
366
|
+
- If `sessionId` is not provided, the tool targets the most recently created child session tracked since the plugin loaded (pass `sessionId` explicitly for older sessions)
|
|
367
|
+
- Returns a confirmation once the message is admitted to the child session
|
|
366
368
|
- Returns an error message if no child session exists for the current session
|
|
367
369
|
|
|
368
370
|
---
|
|
@@ -468,8 +470,7 @@ Promote an agent to primary (default) or specify a grade.
|
|
|
468
470
|
#### Notes
|
|
469
471
|
|
|
470
472
|
- Only agents from this plugin can be promoted (see [Agents](#agents) table)
|
|
471
|
-
- Changes persist
|
|
472
|
-
- After promotion, use `Tab` or `<leader>a` to select the agent
|
|
473
|
+
- Changes apply immediately and persist across restarts via plugin storage
|
|
473
474
|
|
|
474
475
|
---
|
|
475
476
|
|
package/command/diff-summary.md
CHANGED
|
@@ -4,30 +4,49 @@ description: Show working tree changes or diff between branches ($1=source, $2=t
|
|
|
4
4
|
|
|
5
5
|
# Diff Summary
|
|
6
6
|
|
|
7
|
+
Branch arguments (if provided): source=$1 target=$2
|
|
8
|
+
|
|
7
9
|
!`bash -c '
|
|
8
10
|
SOURCE="$1"
|
|
9
11
|
TARGET="$2"
|
|
10
12
|
TARGET="${TARGET:-HEAD}"
|
|
11
13
|
|
|
14
|
+
valid_ref() {
|
|
15
|
+
case "$REF" in
|
|
16
|
+
""|-*) return 1 ;;
|
|
17
|
+
esac
|
|
18
|
+
git rev-parse --verify --quiet "$REF^{commit}" >/dev/null
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
if [ -n "$SOURCE" ]; then
|
|
22
|
+
REF="$SOURCE"
|
|
23
|
+
if ! valid_ref; then
|
|
24
|
+
echo "Invalid source ref: $SOURCE"
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
REF="$TARGET"
|
|
28
|
+
if ! valid_ref; then
|
|
29
|
+
echo "Invalid target ref: $TARGET"
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
13
32
|
echo "## Branch Comparison: $SOURCE → $TARGET"
|
|
14
33
|
echo ""
|
|
15
34
|
git fetch --all --prune 2>/dev/null || true
|
|
16
|
-
|
|
35
|
+
|
|
17
36
|
echo "### Stats Overview"
|
|
18
|
-
git diff --stat "$TARGET"..."$SOURCE"
|
|
19
|
-
|
|
37
|
+
git diff --stat "$TARGET"..."$SOURCE" --
|
|
38
|
+
|
|
20
39
|
echo ""
|
|
21
40
|
echo "### Commits"
|
|
22
|
-
git log --oneline --no-merges "$TARGET".."$SOURCE"
|
|
23
|
-
|
|
41
|
+
git log --oneline --no-merges "$TARGET".."$SOURCE" --
|
|
42
|
+
|
|
24
43
|
echo ""
|
|
25
44
|
echo "### Files Changed"
|
|
26
|
-
git diff --name-only "$TARGET"..."$SOURCE"
|
|
27
|
-
|
|
45
|
+
git diff --name-only "$TARGET"..."$SOURCE" --
|
|
46
|
+
|
|
28
47
|
echo ""
|
|
29
48
|
echo "### Full Diff"
|
|
30
|
-
git diff "$TARGET"..."$SOURCE"
|
|
49
|
+
git diff "$TARGET"..."$SOURCE" --
|
|
31
50
|
else
|
|
32
51
|
echo "## Status"
|
|
33
52
|
git status --porcelain
|
package/command/doc-changes.md
CHANGED
package/command/review-pr.md
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { constants, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { log } from "./logger";
|
|
4
|
+
export function installBundledFiles(sourceDir, targetDir) {
|
|
5
|
+
const installed = [];
|
|
6
|
+
const skipped = [];
|
|
7
|
+
const updated = [];
|
|
8
|
+
if (!existsSync(sourceDir))
|
|
9
|
+
return { installed, skipped, updated };
|
|
10
|
+
try {
|
|
11
|
+
mkdirSync(targetDir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
log("[commands] failed to prepare target dir", { targetDir, error: String(error) });
|
|
15
|
+
return { installed, skipped, updated };
|
|
16
|
+
}
|
|
17
|
+
let files;
|
|
18
|
+
try {
|
|
19
|
+
files = readdirSync(sourceDir);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
log("[commands] failed to read bundled files", { sourceDir, error: String(error) });
|
|
23
|
+
return { installed, skipped, updated };
|
|
24
|
+
}
|
|
25
|
+
for (const file of files) {
|
|
26
|
+
if (!file.endsWith(".md"))
|
|
27
|
+
continue;
|
|
28
|
+
const source = join(sourceDir, file);
|
|
29
|
+
const target = join(targetDir, file);
|
|
30
|
+
try {
|
|
31
|
+
const content = readFileSync(source, "utf-8");
|
|
32
|
+
if (!existsSync(target)) {
|
|
33
|
+
copyFileSync(source, target, constants.COPYFILE_EXCL);
|
|
34
|
+
installed.push(file);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (readFileSync(target, "utf-8") !== content) {
|
|
38
|
+
copyFileSync(source, target);
|
|
39
|
+
updated.push(file);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
skipped.push(file);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
log("[commands] failed to install bundled file", { file, error: String(error) });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { installed, skipped, updated };
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { installBundledFiles } from "./command-installer";
|
|
6
|
+
describe("installBundledFiles", () => {
|
|
7
|
+
let sourceDir;
|
|
8
|
+
let targetDir;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
const base = join(tmpdir(), `froggy-files-${Date.now()}-${Math.random().toString(16).slice(2)}`);
|
|
11
|
+
sourceDir = join(base, "source");
|
|
12
|
+
targetDir = join(base, "target");
|
|
13
|
+
mkdirSync(sourceDir, { recursive: true });
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
rmSync(join(sourceDir, ".."), { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
it("installs missing files", () => {
|
|
19
|
+
writeFileSync(join(sourceDir, "review-pr.md"), "# review");
|
|
20
|
+
const result = installBundledFiles(sourceDir, targetDir);
|
|
21
|
+
expect(result.installed).toEqual(["review-pr.md"]);
|
|
22
|
+
expect(readFileSync(join(targetDir, "review-pr.md"), "utf-8")).toBe("# review");
|
|
23
|
+
});
|
|
24
|
+
it("skips existing files with identical content", () => {
|
|
25
|
+
writeFileSync(join(sourceDir, "review-pr.md"), "# bundled");
|
|
26
|
+
mkdirSync(targetDir, { recursive: true });
|
|
27
|
+
writeFileSync(join(targetDir, "review-pr.md"), "# bundled");
|
|
28
|
+
const result = installBundledFiles(sourceDir, targetDir);
|
|
29
|
+
expect(result.skipped).toEqual(["review-pr.md"]);
|
|
30
|
+
expect(readFileSync(join(targetDir, "review-pr.md"), "utf-8")).toBe("# bundled");
|
|
31
|
+
});
|
|
32
|
+
it("updates an existing file when the bundled content differs", () => {
|
|
33
|
+
writeFileSync(join(sourceDir, "review-pr.md"), "# v2");
|
|
34
|
+
mkdirSync(targetDir, { recursive: true });
|
|
35
|
+
writeFileSync(join(targetDir, "review-pr.md"), "# v1");
|
|
36
|
+
const result = installBundledFiles(sourceDir, targetDir);
|
|
37
|
+
expect(result.updated).toEqual(["review-pr.md"]);
|
|
38
|
+
expect(readFileSync(join(targetDir, "review-pr.md"), "utf-8")).toBe("# v2");
|
|
39
|
+
});
|
|
40
|
+
it("never deletes a user-customized file", () => {
|
|
41
|
+
writeFileSync(join(sourceDir, "review-pr.md"), "# bundled");
|
|
42
|
+
mkdirSync(targetDir, { recursive: true });
|
|
43
|
+
writeFileSync(join(targetDir, "review-pr.md"), "# user customization");
|
|
44
|
+
const result = installBundledFiles(sourceDir, targetDir);
|
|
45
|
+
expect(result.updated).toEqual(["review-pr.md"]);
|
|
46
|
+
expect(existsSync(join(targetDir, "review-pr.md"))).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
it("ignores non-markdown files", () => {
|
|
49
|
+
writeFileSync(join(sourceDir, "notes.txt"), "notes");
|
|
50
|
+
const result = installBundledFiles(sourceDir, targetDir);
|
|
51
|
+
expect(result.installed).toEqual([]);
|
|
52
|
+
expect(existsSync(join(targetDir, "notes.txt"))).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
it("returns empty result for missing source dir", () => {
|
|
55
|
+
const result = installBundledFiles(join(sourceDir, "missing"), targetDir);
|
|
56
|
+
expect(result).toEqual({ installed: [], skipped: [], updated: [] });
|
|
57
|
+
});
|
|
58
|
+
});
|
package/dist/config-paths.d.ts
CHANGED
|
@@ -9,3 +9,5 @@
|
|
|
9
9
|
export declare function getUserConfigDir(): string;
|
|
10
10
|
export declare function getGlobalHookDir(): string;
|
|
11
11
|
export declare function getProjectHookDir(directory: string): string;
|
|
12
|
+
export declare function getGlobalCommandDir(): string;
|
|
13
|
+
export declare function getGlobalAgentDir(): string;
|
package/dist/config-paths.js
CHANGED
|
@@ -30,3 +30,9 @@ export function getGlobalHookDir() {
|
|
|
30
30
|
export function getProjectHookDir(directory) {
|
|
31
31
|
return join(directory, ".opencode", "hook");
|
|
32
32
|
}
|
|
33
|
+
export function getGlobalCommandDir() {
|
|
34
|
+
return join(getUserConfigDir(), "opencode", "commands");
|
|
35
|
+
}
|
|
36
|
+
export function getGlobalAgentDir() {
|
|
37
|
+
return join(getUserConfigDir(), "opencode", "agents");
|
|
38
|
+
}
|
|
@@ -86,6 +86,22 @@ describe("config-paths", () => {
|
|
|
86
86
|
expect(getGlobalHookDir()).toBe("/custom/config/opencode/hook");
|
|
87
87
|
});
|
|
88
88
|
});
|
|
89
|
+
describe("getGlobalCommandDir", () => {
|
|
90
|
+
it("should return correct path on Linux", async () => {
|
|
91
|
+
Object.defineProperty(process, "platform", { value: "linux" });
|
|
92
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
93
|
+
const { getGlobalCommandDir } = await import("./config-paths");
|
|
94
|
+
expect(getGlobalCommandDir()).toBe(join(homedir(), ".config", "opencode", "commands"));
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe("getGlobalAgentDir", () => {
|
|
98
|
+
it("should return correct path on Linux", async () => {
|
|
99
|
+
Object.defineProperty(process, "platform", { value: "linux" });
|
|
100
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
101
|
+
const { getGlobalAgentDir } = await import("./config-paths");
|
|
102
|
+
expect(getGlobalAgentDir()).toBe(join(homedir(), ".config", "opencode", "agents"));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
89
105
|
describe("getProjectHookDir", () => {
|
|
90
106
|
it("should return correct path for project directory", async () => {
|
|
91
107
|
const { getProjectHookDir } = await import("./config-paths");
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Plugin } from "@opencode/plugin";
|
|
2
2
|
export { parseFrontmatter, loadAgents, loadCommands, type LoadedSkill } from "./loaders";
|
|
3
3
|
export { buildSkillActivationBlock } from "./skill-activation";
|
|
4
|
-
declare const
|
|
5
|
-
export default
|
|
4
|
+
declare const _default: Plugin.Plugin;
|
|
5
|
+
export default _default;
|