@gendigital/sage-opencode 0.9.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.
- package/README.md +102 -0
- package/dist/bundled-dirs.d.ts +12 -0
- package/dist/bundled-dirs.d.ts.map +1 -0
- package/dist/bundled-dirs.js +29 -0
- package/dist/bundled-dirs.js.map +1 -0
- package/dist/error.d.ts +7 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +12 -0
- package/dist/error.js.map +1 -0
- package/dist/extractors.d.ts +6 -0
- package/dist/extractors.d.ts.map +1 -0
- package/dist/extractors.js +111 -0
- package/dist/extractors.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +128 -0
- package/dist/index.js.map +1 -0
- package/dist/logger-adaptor.d.ts +15 -0
- package/dist/logger-adaptor.d.ts.map +1 -0
- package/dist/logger-adaptor.js +32 -0
- package/dist/logger-adaptor.js.map +1 -0
- package/dist/plugin-discovery.d.ts +12 -0
- package/dist/plugin-discovery.d.ts.map +1 -0
- package/dist/plugin-discovery.js +138 -0
- package/dist/plugin-discovery.js.map +1 -0
- package/dist/startup-scan.d.ts +7 -0
- package/dist/startup-scan.d.ts.map +1 -0
- package/dist/startup-scan.js +41 -0
- package/dist/startup-scan.js.map +1 -0
- package/dist/tool-handler.d.ts +14 -0
- package/dist/tool-handler.d.ts.map +1 -0
- package/dist/tool-handler.js +93 -0
- package/dist/tool-handler.js.map +1 -0
- package/package.json +60 -0
- package/resources/allowlists/trusted-installer-domains.yaml +55 -0
- package/resources/threats/LICENSE +45 -0
- package/resources/threats/commands.yaml +351 -0
- package/resources/threats/credentials.yaml +105 -0
- package/resources/threats/dummy.yaml +195 -0
- package/resources/threats/files.yaml +239 -0
- package/resources/threats/mac-commands.yaml +355 -0
- package/resources/threats/mac-credentials.yaml +99 -0
- package/resources/threats/mac-files.yaml +124 -0
- package/resources/threats/mac-obfuscation.yaml +92 -0
- package/resources/threats/mac-persistence.yaml +117 -0
- package/resources/threats/mac-supply-chain.yaml +40 -0
- package/resources/threats/mitre.yaml +810 -0
- package/resources/threats/obfuscation.yaml +98 -0
- package/resources/threats/persistence.yaml +87 -0
- package/resources/threats/prompt-injection.yaml +334 -0
- package/resources/threats/self-defense.yaml +55 -0
- package/resources/threats/supply_chain.yaml +15 -0
- package/resources/threats/urls.yaml +66 -0
- package/resources/threats/win-commands.yaml +785 -0
- package/resources/threats/win-credentials.yaml +128 -0
- package/resources/threats/win-files.yaml +124 -0
- package/resources/threats/win-obfuscation.yaml +205 -0
- package/resources/threats/win-persistence.yaml +100 -0
- package/resources/threats/win-supply-chain.yaml +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Sage for OpenCode
|
|
2
|
+
|
|
3
|
+
Sage integrates with OpenCode as a plugin and evaluates tool calls before they run.
|
|
4
|
+
|
|
5
|
+
## What it protects
|
|
6
|
+
|
|
7
|
+
- `bash` commands
|
|
8
|
+
- `webfetch` URLs
|
|
9
|
+
- File operations (`read`, `write`, `edit`, `ls`, `glob`, `grep`)
|
|
10
|
+
|
|
11
|
+
Unmapped tools pass through unchanged.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
Clone the repo, build the OpenCode package, then point OpenCode to this package path:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/gendigitalinc/sage
|
|
19
|
+
cd sage
|
|
20
|
+
pnpm install
|
|
21
|
+
pnpm --filter @gendigital/sage-opencode run build
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Global config (`~/.config/opencode/opencode.json`):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"plugin": ["/absolute/path/to/sage/packages/opencode"]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Behavior
|
|
33
|
+
|
|
34
|
+
- **deny** verdicts: blocked immediately
|
|
35
|
+
- **ask** verdicts: blocked with an explicit `sage_approve` action ID
|
|
36
|
+
- **allow** verdicts: pass through
|
|
37
|
+
|
|
38
|
+
## Approval Flow
|
|
39
|
+
|
|
40
|
+
OpenCode does not expose a plugin-spawned permission dialog, so Sage uses tool-based approval:
|
|
41
|
+
|
|
42
|
+
1. Sage blocks an `ask` verdict and returns an `actionId`
|
|
43
|
+
2. Ask the user for explicit confirmation in chat
|
|
44
|
+
3. If approved, call:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
sage_approve({ actionId: "...", approved: true })
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
4. Retry the original tool call
|
|
51
|
+
|
|
52
|
+
## Session Startup Scanning
|
|
53
|
+
|
|
54
|
+
Sage automatically scans all installed OpenCode plugins when a new session starts.
|
|
55
|
+
|
|
56
|
+
### What's Scanned
|
|
57
|
+
|
|
58
|
+
- **NPM plugins**: Packages listed in `opencode.json` config (global + project)
|
|
59
|
+
- **Local plugins**: Files in `~/.config/opencode/plugins/` (global)
|
|
60
|
+
- **Project plugins**: Files in `.opencode/plugins/` (project-specific)
|
|
61
|
+
|
|
62
|
+
### When Scanning Runs
|
|
63
|
+
|
|
64
|
+
- **Trigger**: Once per session on `session.updated` event (deduplicated by session ID)
|
|
65
|
+
- **Performance**: Results are cached for fast subsequent sessions
|
|
66
|
+
|
|
67
|
+
### How Findings Are Shown
|
|
68
|
+
|
|
69
|
+
1. **System Prompt Injection**: Findings appear as first message in agent's context
|
|
70
|
+
2. **One-Shot**: Only injected once per session (not repeated)
|
|
71
|
+
3. **Agent Notification**: Agent sees threats and can inform user
|
|
72
|
+
4. **Console Logging**: Findings also logged to OpenCode console
|
|
73
|
+
|
|
74
|
+
### What Gets Scanned
|
|
75
|
+
|
|
76
|
+
- Plugin source code (JS/TS/PY files)
|
|
77
|
+
- Package metadata (package.json)
|
|
78
|
+
- Embedded URLs (checked against threat intelligence)
|
|
79
|
+
- Suspicious patterns (credentials, obfuscation, persistence)
|
|
80
|
+
|
|
81
|
+
### Self-Protection
|
|
82
|
+
|
|
83
|
+
- Sage excludes itself from scanning (`@gendigital/sage-opencode`)
|
|
84
|
+
- Fail-open philosophy: Errors don't block OpenCode
|
|
85
|
+
|
|
86
|
+
### Cache Location
|
|
87
|
+
|
|
88
|
+
Scan cache stored at `~/.sage/plugin_scan_cache.json` for performance.
|
|
89
|
+
|
|
90
|
+
## Build
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pnpm --filter @gendigital/sage-opencode run build
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This copies `threats/` and `allowlists/` into `packages/opencode/resources/`.
|
|
97
|
+
|
|
98
|
+
## Test
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pnpm test -- packages/opencode/src/__tests__/integration.test.ts
|
|
102
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled directory and version helpers for OpenCode plugin.
|
|
3
|
+
* Centralizes package path resolution for reuse across modules.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getBundledDataDirs(): {
|
|
6
|
+
pluginDir: string;
|
|
7
|
+
packageRoot: string;
|
|
8
|
+
threatsDir: string;
|
|
9
|
+
allowlistsDir: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function getSageVersion(): string;
|
|
12
|
+
//# sourceMappingURL=bundled-dirs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundled-dirs.d.ts","sourceRoot":"","sources":["../src/bundled-dirs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,wBAAgB,kBAAkB,IAAI;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACtB,CASA;AAED,wBAAgB,cAAc,IAAI,MAAM,CASvC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled directory and version helpers for OpenCode plugin.
|
|
3
|
+
* Centralizes package path resolution for reuse across modules.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
export function getBundledDataDirs() {
|
|
9
|
+
const pluginDir = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const packageRoot = join(pluginDir, "..");
|
|
11
|
+
return {
|
|
12
|
+
pluginDir,
|
|
13
|
+
packageRoot,
|
|
14
|
+
threatsDir: join(packageRoot, "resources", "threats"),
|
|
15
|
+
allowlistsDir: join(packageRoot, "resources", "allowlists"),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function getSageVersion() {
|
|
19
|
+
try {
|
|
20
|
+
const { packageRoot } = getBundledDataDirs();
|
|
21
|
+
const pkgPath = join(packageRoot, "package.json");
|
|
22
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
23
|
+
return pkg.version ?? "0.0.0";
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return "0.0.0";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=bundled-dirs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundled-dirs.js","sourceRoot":"","sources":["../src/bundled-dirs.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,kBAAkB;IAMjC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC1C,OAAO;QACN,SAAS;QACT,WAAW;QACX,UAAU,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,SAAS,CAAC;QACrD,aAAa,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,YAAY,CAAC;KAC3D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC7B,IAAI,CAAC;QACJ,MAAM,EAAE,WAAW,EAAE,GAAG,kBAAkB,EAAE,CAAC;QAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAA4B,CAAC;QAClF,OAAQ,GAAG,CAAC,OAAkB,IAAI,OAAO,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,OAAO,CAAC;IAChB,CAAC;AACF,CAAC"}
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,8BAAsB,gBAAiB,SAAQ,KAAK;gBACvC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;CAI1C;AAED,qBAAa,qBAAsB,SAAQ,gBAAgB;gBAC9C,OAAO,EAAE,MAAM;CAG3B"}
|
package/dist/error.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class SageVerdictError extends Error {
|
|
2
|
+
constructor(message, name) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = name ?? "SageVerdictError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class SageVerdictBlockError extends SageVerdictError {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message, "SageVerdictBlockError");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,MAAM,OAAgB,gBAAiB,SAAQ,KAAK;IACnD,YAAY,OAAe,EAAE,IAAa;QACzC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,IAAI,kBAAkB,CAAC;IACxC,CAAC;CACD;AAED,MAAM,OAAO,qBAAsB,SAAQ,gBAAgB;IAC1D,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC;IACzC,CAAC;CACD"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode tool input extraction mapped onto @gendigital/sage-core artifact extractors.
|
|
3
|
+
*/
|
|
4
|
+
import { type Artifact } from "@gendigital/sage-core";
|
|
5
|
+
export declare function extractFromOpenCodeTool(toolName: string, args: Record<string, unknown>): Artifact[] | null;
|
|
6
|
+
//# sourceMappingURL=extractors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractors.d.ts","sourceRoot":"","sources":["../src/extractors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACN,KAAK,QAAQ,EAKb,MAAM,uBAAuB,CAAC;AAU/B,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,QAAQ,EAAE,GAAG,IAAI,CA2FnB"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode tool input extraction mapped onto @gendigital/sage-core artifact extractors.
|
|
3
|
+
*/
|
|
4
|
+
import { extractFromBash, extractFromEdit, extractFromWebFetch, extractFromWrite, } from "@gendigital/sage-core";
|
|
5
|
+
function asString(value) {
|
|
6
|
+
return typeof value === "string" ? value : undefined;
|
|
7
|
+
}
|
|
8
|
+
function asNumber(value) {
|
|
9
|
+
return typeof value === "number" ? value : undefined;
|
|
10
|
+
}
|
|
11
|
+
export function extractFromOpenCodeTool(toolName, args) {
|
|
12
|
+
switch (toolName) {
|
|
13
|
+
case "bash": {
|
|
14
|
+
const command = asString(args.command) ?? "";
|
|
15
|
+
return command ? extractFromBash(command) : null;
|
|
16
|
+
}
|
|
17
|
+
case "webfetch": {
|
|
18
|
+
const url = asString(args.url) ?? "";
|
|
19
|
+
return url ? extractFromWebFetch({ url }) : null;
|
|
20
|
+
}
|
|
21
|
+
case "write": {
|
|
22
|
+
const filePath = asString(args.filePath) ?? "";
|
|
23
|
+
if (!filePath)
|
|
24
|
+
return null;
|
|
25
|
+
return extractFromWrite({
|
|
26
|
+
file_path: filePath,
|
|
27
|
+
content: asString(args.content) ?? "",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
case "edit": {
|
|
31
|
+
const filePath = asString(args.filePath) ?? "";
|
|
32
|
+
if (!filePath)
|
|
33
|
+
return null;
|
|
34
|
+
return extractFromEdit({
|
|
35
|
+
file_path: filePath,
|
|
36
|
+
old_string: asString(args.oldString) ?? "",
|
|
37
|
+
new_string: asString(args.newString) ?? "",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
case "read": {
|
|
41
|
+
const filePath = asString(args.filePath) ?? "";
|
|
42
|
+
if (!filePath)
|
|
43
|
+
return null;
|
|
44
|
+
return [{ type: "file_path", value: filePath, context: "read" }];
|
|
45
|
+
}
|
|
46
|
+
case "glob": {
|
|
47
|
+
const pattern = asString(args.pattern) ?? "";
|
|
48
|
+
if (!pattern)
|
|
49
|
+
return null;
|
|
50
|
+
return [{ type: "file_path", value: pattern, context: "glob_pattern" }];
|
|
51
|
+
}
|
|
52
|
+
case "grep": {
|
|
53
|
+
const pattern = asString(args.pattern) ?? "";
|
|
54
|
+
if (!pattern)
|
|
55
|
+
return null;
|
|
56
|
+
const artifacts = [{ type: "content", value: pattern, context: "grep_pattern" }];
|
|
57
|
+
const include = asString(args.include);
|
|
58
|
+
if (include) {
|
|
59
|
+
artifacts.push({ type: "file_path", value: include, context: "grep_include" });
|
|
60
|
+
}
|
|
61
|
+
return artifacts;
|
|
62
|
+
}
|
|
63
|
+
case "ls": {
|
|
64
|
+
const dir = asString(args.path) ?? asString(args.directoryPath) ?? "";
|
|
65
|
+
if (!dir)
|
|
66
|
+
return null;
|
|
67
|
+
return [{ type: "file_path", value: dir, context: "list" }];
|
|
68
|
+
}
|
|
69
|
+
case "codesearch": {
|
|
70
|
+
const query = asString(args.query) ?? "";
|
|
71
|
+
return query ? [{ type: "content", value: query, context: "codesearch_query" }] : null;
|
|
72
|
+
}
|
|
73
|
+
case "websearch": {
|
|
74
|
+
const query = asString(args.query) ?? "";
|
|
75
|
+
return query ? [{ type: "content", value: query, context: "websearch_query" }] : null;
|
|
76
|
+
}
|
|
77
|
+
case "question": {
|
|
78
|
+
const selected = Array.isArray(args.selected) ? args.selected : [];
|
|
79
|
+
const joined = selected
|
|
80
|
+
.map((item) => (typeof item === "string" ? item : null))
|
|
81
|
+
.filter((item) => item !== null)
|
|
82
|
+
.join("\n");
|
|
83
|
+
if (!joined)
|
|
84
|
+
return null;
|
|
85
|
+
return [{ type: "content", value: joined, context: "question_options" }];
|
|
86
|
+
}
|
|
87
|
+
case "task": {
|
|
88
|
+
const prompt = asString(args.prompt) ?? "";
|
|
89
|
+
const desc = asString(args.description) ?? "";
|
|
90
|
+
const payload = [desc, prompt].filter(Boolean).join("\n\n");
|
|
91
|
+
return payload ? [{ type: "content", value: payload, context: "task" }] : null;
|
|
92
|
+
}
|
|
93
|
+
case "read_lines": {
|
|
94
|
+
const filePath = asString(args.filePath) ?? "";
|
|
95
|
+
if (!filePath)
|
|
96
|
+
return null;
|
|
97
|
+
const offset = asNumber(args.offset);
|
|
98
|
+
const limit = asNumber(args.limit);
|
|
99
|
+
return [
|
|
100
|
+
{
|
|
101
|
+
type: "file_path",
|
|
102
|
+
value: `${filePath}:${offset ?? 1}:${limit ?? 0}`,
|
|
103
|
+
context: "read_lines",
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
default:
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=extractors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractors.js","sourceRoot":"","sources":["../src/extractors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAEN,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,gBAAgB,GAChB,MAAM,uBAAuB,CAAC;AAE/B,SAAS,QAAQ,CAAC,KAAc;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACtD,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,uBAAuB,CACtC,QAAgB,EAChB,IAA6B;IAE7B,QAAQ,QAAQ,EAAE,CAAC;QAClB,KAAK,MAAM,CAAC,CAAC,CAAC;YACb,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAC7C,OAAO,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAClD,CAAC;QACD,KAAK,UAAU,CAAC,CAAC,CAAC;YACjB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACrC,OAAO,GAAG,CAAC,CAAC,CAAC,mBAAmB,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAClD,CAAC;QACD,KAAK,OAAO,CAAC,CAAC,CAAC;YACd,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC/C,IAAI,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAC3B,OAAO,gBAAgB,CAAC;gBACvB,SAAS,EAAE,QAAQ;gBACnB,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE;aACrC,CAAC,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACb,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC/C,IAAI,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAC3B,OAAO,eAAe,CAAC;gBACtB,SAAS,EAAE,QAAQ;gBACnB,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE;gBAC1C,UAAU,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE;aAC1C,CAAC,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACb,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC/C,IAAI,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAC3B,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAClE,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACb,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAC7C,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC;YAC1B,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACb,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAC7C,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC;YAC1B,MAAM,SAAS,GAAe,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;YAC7F,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvC,IAAI,OAAO,EAAE,CAAC;gBACb,SAAS,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;YAChF,CAAC;YACD,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,KAAK,IAAI,CAAC,CAAC,CAAC;YACX,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;YACtE,IAAI,CAAC,GAAG;gBAAE,OAAO,IAAI,CAAC;YACtB,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC7D,CAAC;QACD,KAAK,YAAY,CAAC,CAAC,CAAC;YACnB,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YACzC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACxF,CAAC;QACD,KAAK,WAAW,CAAC,CAAC,CAAC;YAClB,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YACzC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACvF,CAAC;QACD,KAAK,UAAU,CAAC,CAAC,CAAC;YACjB,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,MAAM,MAAM,GAAG,QAAQ;iBACrB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;iBACvD,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;iBAC/C,IAAI,CAAC,IAAI,CAAC,CAAC;YACb,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YACzB,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACb,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;YAC9C,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC5D,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAChF,CAAC;QACD,KAAK,YAAY,CAAC,CAAC,CAAC;YACnB,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC/C,IAAI,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAC3B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnC,OAAO;gBACN;oBACC,IAAI,EAAE,WAAW;oBACjB,KAAK,EAAE,GAAG,QAAQ,IAAI,MAAM,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE;oBACjD,OAAO,EAAE,YAAY;iBACrB;aACD,CAAC;QACH,CAAC;QACD;YACC,OAAO,IAAI,CAAC;IACd,CAAC;AACF,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sage OpenCode plugin.
|
|
3
|
+
* Intercepts tool calls and uses @gendigital/sage-core to enforce security verdicts.
|
|
4
|
+
*/
|
|
5
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
6
|
+
export declare const SagePlugin: Plugin;
|
|
7
|
+
export default SagePlugin;
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AASlD,eAAO,MAAM,UAAU,EAAE,MAqIxB,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sage OpenCode plugin.
|
|
3
|
+
* Intercepts tool calls and uses @gendigital/sage-core to enforce security verdicts.
|
|
4
|
+
*/
|
|
5
|
+
import { ApprovalStore, approveAction, loadConfig, resolveBranding } from "@gendigital/sage-core";
|
|
6
|
+
import { tool } from "@opencode-ai/plugin/tool";
|
|
7
|
+
import { getBundledDataDirs } from "./bundled-dirs.js";
|
|
8
|
+
import { OpencodeLogger } from "./logger-adaptor.js";
|
|
9
|
+
import { createSessionScanHandler } from "./startup-scan.js";
|
|
10
|
+
import { createToolHandlers } from "./tool-handler.js";
|
|
11
|
+
const APPROVAL_STORE_CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
12
|
+
export const SagePlugin = async ({ client, directory }) => {
|
|
13
|
+
const logger = new OpencodeLogger(client);
|
|
14
|
+
const config = await loadConfig(undefined, logger);
|
|
15
|
+
const branding = resolveBranding(config.brand_key, logger);
|
|
16
|
+
const { threatsDir, allowlistsDir } = getBundledDataDirs();
|
|
17
|
+
const approvalStore = new ApprovalStore();
|
|
18
|
+
// State: track findings per session for one-shot injection
|
|
19
|
+
const pendingFindingsBySession = new Map();
|
|
20
|
+
// Track scanned sessions to avoid re-triggering on repeated session.updated events
|
|
21
|
+
const scannedSessions = new Set();
|
|
22
|
+
// Set up the cron job that cleans up the approval store.
|
|
23
|
+
const interval = setInterval(() => {
|
|
24
|
+
approvalStore.cleanup();
|
|
25
|
+
}, APPROVAL_STORE_CLEANUP_INTERVAL_MS);
|
|
26
|
+
interval.unref?.();
|
|
27
|
+
const toolHandlers = createToolHandlers(logger, approvalStore, threatsDir, allowlistsDir, {
|
|
28
|
+
showToast: (msg, variant) => {
|
|
29
|
+
client.tui
|
|
30
|
+
.showToast({
|
|
31
|
+
body: { title: branding.name, message: msg, variant, duration: 5000 },
|
|
32
|
+
})
|
|
33
|
+
.catch(() => { });
|
|
34
|
+
},
|
|
35
|
+
}, branding);
|
|
36
|
+
return {
|
|
37
|
+
"tool.execute.before": toolHandlers["tool.execute.before"],
|
|
38
|
+
/**
|
|
39
|
+
* Inject plugin scan findings into first user message as <system-reminder>.
|
|
40
|
+
* This approach leverages OpenCode's existing pattern of appending system
|
|
41
|
+
* reminders to user messages (e.g., plan mode constraints), enabling the
|
|
42
|
+
* agent to reason about security findings before tool execution. Appending
|
|
43
|
+
* to assistant messages or system prompts was less effective.
|
|
44
|
+
*/
|
|
45
|
+
"experimental.chat.messages.transform": async (_input, output) => {
|
|
46
|
+
if (pendingFindingsBySession.size === 0)
|
|
47
|
+
return;
|
|
48
|
+
const userMessage = output.messages.filter((m) => m.info.role === "user");
|
|
49
|
+
const message = userMessage[0];
|
|
50
|
+
if (!message)
|
|
51
|
+
return;
|
|
52
|
+
const sessionID = message.info.sessionID;
|
|
53
|
+
const findings = pendingFindingsBySession.get(sessionID);
|
|
54
|
+
if (!findings)
|
|
55
|
+
return;
|
|
56
|
+
const textPart = {
|
|
57
|
+
id: crypto.randomUUID(),
|
|
58
|
+
sessionID,
|
|
59
|
+
messageID: message.info.id,
|
|
60
|
+
type: "text",
|
|
61
|
+
text: [
|
|
62
|
+
`<system-reminder>`,
|
|
63
|
+
findings,
|
|
64
|
+
"",
|
|
65
|
+
"Inform the user about these security findings.",
|
|
66
|
+
`</system-reminder>`,
|
|
67
|
+
].join("\n"),
|
|
68
|
+
synthetic: true,
|
|
69
|
+
};
|
|
70
|
+
message.parts.push(textPart);
|
|
71
|
+
logger.info(`Injected sage plugin scan findings to user message`, {
|
|
72
|
+
findings,
|
|
73
|
+
});
|
|
74
|
+
pendingFindingsBySession.delete(sessionID);
|
|
75
|
+
},
|
|
76
|
+
// Trigger session scan on first session.updated event per session.
|
|
77
|
+
// OpenCode >=1.3 replaced session.created with session.updated events.
|
|
78
|
+
event: async ({ event }) => {
|
|
79
|
+
if (event.type !== "session.updated")
|
|
80
|
+
return;
|
|
81
|
+
// biome-ignore lint/suspicious/noExplicitAny: Event types from SDK not fully typed
|
|
82
|
+
const props = event.properties;
|
|
83
|
+
const sessionID = props?.sessionID ?? props?.info?.id ?? "unknown";
|
|
84
|
+
if (sessionID !== "unknown" && scannedSessions.has(sessionID))
|
|
85
|
+
return;
|
|
86
|
+
try {
|
|
87
|
+
logger.debug(`${branding.name}: starting session scan`, { sessionID });
|
|
88
|
+
const scanHandler = createSessionScanHandler(logger, directory, (msg) => {
|
|
89
|
+
pendingFindingsBySession.set(sessionID, msg);
|
|
90
|
+
}, branding);
|
|
91
|
+
await scanHandler();
|
|
92
|
+
if (sessionID !== "unknown") {
|
|
93
|
+
scannedSessions.add(sessionID);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
logger.error(`${branding.name} session scan failed (fail-open)`, {
|
|
98
|
+
sessionID,
|
|
99
|
+
error: String(error),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
tool: {
|
|
104
|
+
// TODO: After the following PR merged to support client V2 in Opencode Plugin, gitleaks:allow
|
|
105
|
+
// use QuestionTools.ask to replace sage_approve tool
|
|
106
|
+
// PR: https://github.com/anomalyco/opencode/pull/12046
|
|
107
|
+
// Discussion: https://github.com/avast/sage/pull/21#discussion_r2873812399
|
|
108
|
+
sage_approve: tool({
|
|
109
|
+
description: `Approve or reject a ${branding.name}-flagged tool call. IMPORTANT: you MUST ask the user for explicit confirmation in the conversation BEFORE calling this tool. Never auto-approve - always present the flagged action and wait for the user to response.`,
|
|
110
|
+
args: {
|
|
111
|
+
actionId: tool.schema
|
|
112
|
+
.string()
|
|
113
|
+
.describe(`Action ID from ${branding.name} blocked message`),
|
|
114
|
+
approved: tool.schema.boolean().describe("true to approve, false to reject"),
|
|
115
|
+
},
|
|
116
|
+
async execute(args, _context) {
|
|
117
|
+
if (!args.approved) {
|
|
118
|
+
approvalStore.deletePending(args.actionId);
|
|
119
|
+
return "Rejected by user.";
|
|
120
|
+
}
|
|
121
|
+
return approveAction(approvalStore, args.actionId, branding);
|
|
122
|
+
},
|
|
123
|
+
}),
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
export default SagePlugin;
|
|
128
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridges OpenCode's client logger to Sage's Logger interface.
|
|
3
|
+
*/
|
|
4
|
+
import type { Logger } from "@gendigital/sage-core";
|
|
5
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
6
|
+
export declare class OpencodeLogger implements Logger {
|
|
7
|
+
private client;
|
|
8
|
+
constructor(client: PluginInput["client"]);
|
|
9
|
+
debug(msg: string, data?: Record<string, unknown>): Promise<void>;
|
|
10
|
+
info(msg: string, data?: Record<string, unknown>): Promise<void>;
|
|
11
|
+
warn(msg: string, data?: Record<string, unknown>): Promise<void>;
|
|
12
|
+
error(msg: string, data?: Record<string, unknown>): Promise<void>;
|
|
13
|
+
private write;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=logger-adaptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger-adaptor.d.ts","sourceRoot":"","sources":["../src/logger-adaptor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,qBAAa,cAAe,YAAW,MAAM;IAC5C,OAAO,CAAC,MAAM,CAAwB;gBAE1B,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC;IAI5B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIjD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIhD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAIhD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;YAIhD,KAAK;CAUnB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridges OpenCode's client logger to Sage's Logger interface.
|
|
3
|
+
*/
|
|
4
|
+
export class OpencodeLogger {
|
|
5
|
+
client;
|
|
6
|
+
constructor(client) {
|
|
7
|
+
this.client = client;
|
|
8
|
+
}
|
|
9
|
+
async debug(msg, data) {
|
|
10
|
+
await this.write("debug", msg, data).catch(() => { });
|
|
11
|
+
}
|
|
12
|
+
async info(msg, data) {
|
|
13
|
+
await this.write("info", msg, data).catch(() => { });
|
|
14
|
+
}
|
|
15
|
+
async warn(msg, data) {
|
|
16
|
+
await this.write("warn", msg, data).catch(() => { });
|
|
17
|
+
}
|
|
18
|
+
async error(msg, data) {
|
|
19
|
+
await this.write("error", msg, data).catch(() => { });
|
|
20
|
+
}
|
|
21
|
+
async write(level, msg, data) {
|
|
22
|
+
await this.client.app.log({
|
|
23
|
+
body: {
|
|
24
|
+
service: "sage-opencode",
|
|
25
|
+
level,
|
|
26
|
+
message: msg,
|
|
27
|
+
extra: data && typeof data === "object" ? data : undefined,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=logger-adaptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger-adaptor.js","sourceRoot":"","sources":["../src/logger-adaptor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,MAAM,OAAO,cAAc;IAClB,MAAM,CAAwB;IAEtC,YAAY,MAA6B;QACxC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,CAAC;IAEM,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,IAA8B;QAC7D,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACtD,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,IAA8B;QAC5D,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACrD,CAAC;IAEM,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,IAA8B;QAC5D,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACrD,CAAC;IAEM,KAAK,CAAC,KAAK,CAAC,GAAW,EAAE,IAA8B;QAC7D,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACtD,CAAC;IAEO,KAAK,CAAC,KAAK,CAAC,KAA0C,EAAE,GAAW,EAAE,IAAc;QAC1F,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;YACzB,IAAI,EAAE;gBACL,OAAO,EAAE,eAAe;gBACxB,KAAK;gBACL,OAAO,EAAE,GAAG;gBACZ,KAAK,EAAE,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAE,IAAgC,CAAC,CAAC,CAAC,SAAS;aACvF;SACD,CAAC,CAAC;IACJ,CAAC;CACD"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovers installed OpenCode plugins for plugin scanning.
|
|
3
|
+
* Scans NPM packages from config files and local plugin files.
|
|
4
|
+
*/
|
|
5
|
+
import type { Branding, Logger, PluginInfo } from "@gendigital/sage-core";
|
|
6
|
+
/**
|
|
7
|
+
* Discover OpenCode plugins from all sources:
|
|
8
|
+
* 1. NPM packages from config files
|
|
9
|
+
* 2. Local plugin files (global + project)
|
|
10
|
+
*/
|
|
11
|
+
export declare function discoverOpenCodePlugins(logger: Logger, projectDir?: string, branding?: Branding): Promise<PluginInfo[]>;
|
|
12
|
+
//# sourceMappingURL=plugin-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-discovery.d.ts","sourceRoot":"","sources":["../src/plugin-discovery.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAc1E;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC5C,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,GAAE,QAA0B,GAClC,OAAO,CAAC,UAAU,EAAE,CAAC,CAuBvB"}
|