Initial commit
This commit is contained in:
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
.opencode/node_modules/
|
||||||
|
|
||||||
|
# secrets / local env
|
||||||
|
.env*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
4
.opencode/plugin/envsitter-guard.ts
Normal file
4
.opencode/plugin/envsitter-guard.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import EnvSitterGuard from "../../index";
|
||||||
|
|
||||||
|
export default EnvSitterGuard;
|
||||||
|
export { EnvSitterGuard } from "../../index";
|
||||||
152
AGENTS.md
Normal file
152
AGENTS.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# AGENTS.md — envsitter-guard
|
||||||
|
|
||||||
|
This repository is a minimal TypeScript plugin meant to run under OpenCode (`@opencode-ai/plugin`).
|
||||||
|
|
||||||
|
## Repo layout
|
||||||
|
|
||||||
|
- `index.ts`: main plugin implementation (`EnvSitterGuard`).
|
||||||
|
- `tsconfig.json`: TypeScript config (strict, `noEmit`).
|
||||||
|
- `.opencode/`: OpenCode packaging/runtime wrapper.
|
||||||
|
- `.opencode/plugin/envsitter-guard.ts` re-exports the plugin from `index.ts`.
|
||||||
|
- `.opencode/bun.lock` indicates Bun-managed deps inside `.opencode/`.
|
||||||
|
- `node_modules/`: vendored deps (present locally; do not edit).
|
||||||
|
|
||||||
|
## Commands (build / lint / test)
|
||||||
|
|
||||||
|
### Install
|
||||||
|
|
||||||
|
- Root install (lockfile: `package-lock.json`):
|
||||||
|
- `npm ci`
|
||||||
|
- `npm install` (ok for local dev; `npm ci` preferred for reproducibility)
|
||||||
|
|
||||||
|
- `.opencode/` install (Bun lockfile present):
|
||||||
|
- If you need to refresh `.opencode/node_modules`, use Bun from `.opencode/`:
|
||||||
|
- `cd .opencode && bun install`
|
||||||
|
- If Bun is unavailable, do not guess; ask before changing `.opencode/` dependency management.
|
||||||
|
|
||||||
|
### Build / typecheck
|
||||||
|
|
||||||
|
This repo does not emit JS as part of its normal workflow.
|
||||||
|
|
||||||
|
- Typecheck (canonical):
|
||||||
|
- `npm run typecheck`
|
||||||
|
- Equivalent: `npx tsc -p tsconfig.json`
|
||||||
|
|
||||||
|
### Lint / format
|
||||||
|
|
||||||
|
- No linting or formatting commands are configured in the root project.
|
||||||
|
- No `.eslintrc*`, `eslint.config.*`, `biome.json`, or Prettier config exists at repo root.
|
||||||
|
- Do not introduce new linters/formatters unless explicitly requested.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- No test runner/config is configured in the root project (`package.json` has no `test` script).
|
||||||
|
|
||||||
|
If/when tests are added, prefer Node’s built-in test runner (matches existing ecosystem usage):
|
||||||
|
- Run all tests:
|
||||||
|
- `node --test`
|
||||||
|
- Run a single test file:
|
||||||
|
- `node --test path/to/some.test.js`
|
||||||
|
- Run a single test by name/pattern:
|
||||||
|
- `node --test --test-name-pattern "pattern" path/to/some.test.js`
|
||||||
|
|
||||||
|
Note: This repo is TypeScript-only and `tsconfig.json` uses `noEmit: true`. If you add tests, ensure the test runner can execute them (either compile first, or use a TS-capable runner) and document the chosen approach.
|
||||||
|
|
||||||
|
## TypeScript / module conventions
|
||||||
|
|
||||||
|
- ESM project: `package.json` has `"type": "module"`.
|
||||||
|
- TS config: `module: "NodeNext"`, `moduleResolution: "NodeNext"`, `target: "ES2022"`, `strict: true`, `noEmit: true`.
|
||||||
|
- Prefer `import type { ... }` for type-only imports (see `index.ts`).
|
||||||
|
- Prefer `node:` specifier for Node built-ins (e.g. `import path from "node:path"`).
|
||||||
|
|
||||||
|
## Formatting conventions (match existing code)
|
||||||
|
|
||||||
|
- Indentation: 4 spaces.
|
||||||
|
- Quotes: double quotes.
|
||||||
|
- Semicolons: required.
|
||||||
|
- Trailing commas: used in multiline objects/args.
|
||||||
|
- Keep functions small and single-purpose (see path validation helpers in `index.ts`).
|
||||||
|
|
||||||
|
## Naming conventions
|
||||||
|
|
||||||
|
- `camelCase`: functions, locals, parameters.
|
||||||
|
- `PascalCase`: exported plugin symbol (`EnvSitterGuard`).
|
||||||
|
- Booleans use `is*` / `has*` prefixes (e.g. `isSensitiveDotEnvPath`).
|
||||||
|
|
||||||
|
## Error handling & validation
|
||||||
|
|
||||||
|
This plugin is security-sensitive. Follow these patterns:
|
||||||
|
|
||||||
|
- Prefer early validation + `throw new Error("...")` with a clear, user-facing message.
|
||||||
|
- Avoid swallowing errors (no empty `catch` blocks).
|
||||||
|
- Avoid `any` and type suppression (`as any`, `@ts-ignore`, `@ts-expect-error`).
|
||||||
|
- When parsing unknown inputs, narrow types explicitly (e.g. `typeof args === "object"`, then access via `Record<string, unknown>`).
|
||||||
|
|
||||||
|
## Security & secrets
|
||||||
|
|
||||||
|
- Do not read or print `.env` values.
|
||||||
|
- Treat `.env.secure` as sensitive; never commit secrets.
|
||||||
|
- This repo’s plugin is designed to block sensitive `.env*` and `.envsitter/pepper` access via tooling and to guide users toward safe inspection:
|
||||||
|
- `envsitter_keys` (lists keys only)
|
||||||
|
- `envsitter_fingerprint` (hash/fingerprint only)
|
||||||
|
|
||||||
|
## Working with `.opencode/`
|
||||||
|
|
||||||
|
- `.opencode/` is a packaging/runtime layer for OpenCode plugins; treat it as part of the deployment surface.
|
||||||
|
- Keep `.opencode/plugin/envsitter-guard.ts` as a thin re-export (do not add logic there).
|
||||||
|
- Avoid editing `.opencode/node_modules` directly.
|
||||||
|
|
||||||
|
## Change discipline
|
||||||
|
|
||||||
|
- Prefer minimal diffs; avoid refactors during bugfixes.
|
||||||
|
- Keep behavior consistent with `index.ts` patterns:
|
||||||
|
- normalize paths before regex matching
|
||||||
|
- ensure file paths stay within `worktree`
|
||||||
|
- throttle UI toasts (`lastToastAt` pattern)
|
||||||
|
|
||||||
|
## Plugin behavior notes
|
||||||
|
|
||||||
|
- OpenCode entrypoint is `EnvSitterGuard` exported from `index.ts`.
|
||||||
|
- Plugin registration file `.opencode/plugin/envsitter-guard.ts` must remain a thin re-export.
|
||||||
|
- Tool surface area intentionally small:
|
||||||
|
- `envsitter_keys`: lists keys only (no values)
|
||||||
|
- `envsitter_fingerprint`: deterministic fingerprint of a single key (no value)
|
||||||
|
- Blocking behavior is enforced in `"tool.execute.before"`:
|
||||||
|
- Blocks reads of `.env*` (except `.env.example`).
|
||||||
|
- Blocks edits/writes/patching of `.env*` and `.envsitter/pepper`.
|
||||||
|
- Throttles UI toasts via `lastToastAt`.
|
||||||
|
|
||||||
|
## Path handling rules
|
||||||
|
|
||||||
|
- Treat all incoming tool args as untrusted.
|
||||||
|
- Normalize path separators before matching (Windows `\\` → `/`).
|
||||||
|
- Validate allowed paths before resolving to absolute paths.
|
||||||
|
- Ensure resolved paths stay within `worktree` using `path.relative()` checks.
|
||||||
|
|
||||||
|
## Output conventions
|
||||||
|
|
||||||
|
- Prefer returning structured JSON via `JSON.stringify(obj, null, 2)`.
|
||||||
|
- Never include secret values in output strings, prompts, or toast messages.
|
||||||
|
- Errors should be user-facing and actionable (what was blocked + safe alternative).
|
||||||
|
|
||||||
|
## Dependency & change policy
|
||||||
|
|
||||||
|
- Keep this repo minimalist: prefer existing dependencies over adding new ones.
|
||||||
|
- Do not change `.opencode/` dependency management unless you know the OpenCode packaging constraints.
|
||||||
|
- Never edit vendored `node_modules/` or `.opencode/node_modules/`.
|
||||||
|
|
||||||
|
## Repo hygiene
|
||||||
|
|
||||||
|
- When searching the codebase, exclude `node_modules/` and `.opencode/node_modules/` unless you are explicitly auditing dependency behavior.
|
||||||
|
- Do not add new config files (ESLint/Prettier/Biome/etc.) unless explicitly requested.
|
||||||
|
|
||||||
|
## Verification checklist
|
||||||
|
|
||||||
|
- `npm run typecheck` passes.
|
||||||
|
- No tool path allows reading raw `.env*` values.
|
||||||
|
- `.opencode/plugin/envsitter-guard.ts` remains a re-export only.
|
||||||
|
- Error messages remain clear and do not leak file contents.
|
||||||
|
|
||||||
|
## Cursor / Copilot rules
|
||||||
|
|
||||||
|
- No `.cursor/rules/`, `.cursorrules`, or `.github/copilot-instructions.md` were found in this repository.
|
||||||
156
index.ts
Normal file
156
index.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import type { Plugin } from "@opencode-ai/plugin";
|
||||||
|
import { tool } from "@opencode-ai/plugin/tool";
|
||||||
|
import { EnvSitter } from "envsitter";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
function normalizePath(input: string): string {
|
||||||
|
return input.replace(/\\/g, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDotEnvExamplePath(input: string): boolean {
|
||||||
|
return /(^|\/)\.env\.example$/.test(normalizePath(input));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSensitiveDotEnvPath(input: string): boolean {
|
||||||
|
const normalized = normalizePath(input);
|
||||||
|
if (isDotEnvExamplePath(normalized)) return false;
|
||||||
|
return /(^|\/)\.env($|\.)/.test(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDotEnvishPath(input: string): boolean {
|
||||||
|
const normalized = normalizePath(input);
|
||||||
|
return isDotEnvExamplePath(normalized) || isSensitiveDotEnvPath(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEnvSitterPepperPath(input: string): boolean {
|
||||||
|
const normalized = normalizePath(input);
|
||||||
|
return /(^|\/)\.envsitter\/pepper$/.test(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripAtPrefix(input: string): string {
|
||||||
|
return input.trim().replace(/^@+/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFilePathFromArgs(args: unknown): string | undefined {
|
||||||
|
if (!args || typeof args !== "object") return;
|
||||||
|
const record = args as Record<string, unknown>;
|
||||||
|
|
||||||
|
const candidates: Array<unknown> = [record.filePath, record.path, record.file_path];
|
||||||
|
|
||||||
|
const found = candidates.find((value) => typeof value === "string") as string | undefined;
|
||||||
|
return found ? stripAtPrefix(found) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDotEnvPath(params: {
|
||||||
|
worktree: string;
|
||||||
|
directory: string;
|
||||||
|
filePath: string;
|
||||||
|
}): { absolutePath: string; displayPath: string } {
|
||||||
|
const normalized = normalizePath(params.filePath);
|
||||||
|
|
||||||
|
if (isEnvSitterPepperPath(normalized)) {
|
||||||
|
throw new Error("Access to `.envsitter/pepper` is blocked.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDotEnvishPath(normalized)) {
|
||||||
|
throw new Error("Only `.env`-style paths are allowed (e.g. `.env`, `.env.local`, `.env.example`).");
|
||||||
|
}
|
||||||
|
|
||||||
|
const absolutePath = path.resolve(params.directory, normalized);
|
||||||
|
const relativeToWorktree = path.relative(params.worktree, absolutePath);
|
||||||
|
if (relativeToWorktree.startsWith("..") || path.isAbsolute(relativeToWorktree)) {
|
||||||
|
throw new Error("EnvSitter tools only operate on files inside the current project.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { absolutePath, displayPath: relativeToWorktree };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EnvSitterGuard: Plugin = async ({ client, directory, worktree }) => {
|
||||||
|
let lastToastAt = 0;
|
||||||
|
|
||||||
|
async function notifyBlocked(action: string): Promise<void> {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastToastAt < 5000) return;
|
||||||
|
lastToastAt = now;
|
||||||
|
|
||||||
|
await client.tui.showToast({
|
||||||
|
body: {
|
||||||
|
title: "Blocked sensitive file access",
|
||||||
|
variant: "warning",
|
||||||
|
message:
|
||||||
|
`${action} of sensitive env files is blocked. Use EnvSitter instead (never prints values):\n` +
|
||||||
|
"- envsitter_keys { filePath: '.env' }\n" +
|
||||||
|
"- envsitter_fingerprint { filePath: '.env', key: 'SOME_KEY' }\n" +
|
||||||
|
"(CLI: `npx envsitter keys --file .env`) ",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.tui.appendPrompt({
|
||||||
|
body: {
|
||||||
|
text: "\nTip: use EnvSitter for `.env*` inspection (keys/fingerprints) instead of reading the file.\n",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tool: {
|
||||||
|
envsitter_keys: tool({
|
||||||
|
description: "List keys in a .env file (never returns values).",
|
||||||
|
args: {
|
||||||
|
filePath: tool.schema.string().optional(),
|
||||||
|
},
|
||||||
|
async execute(args) {
|
||||||
|
const resolved = resolveDotEnvPath({
|
||||||
|
worktree,
|
||||||
|
directory,
|
||||||
|
filePath: args.filePath ?? ".env",
|
||||||
|
});
|
||||||
|
|
||||||
|
const es = EnvSitter.fromDotenvFile(resolved.absolutePath);
|
||||||
|
const keys = await es.listKeys();
|
||||||
|
|
||||||
|
return JSON.stringify({ file: resolved.displayPath, keys }, null, 2);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
envsitter_fingerprint: tool({
|
||||||
|
description: "Compute a deterministic fingerprint for a single key (never returns the value).",
|
||||||
|
args: {
|
||||||
|
filePath: tool.schema.string().optional(),
|
||||||
|
key: tool.schema.string(),
|
||||||
|
},
|
||||||
|
async execute(args) {
|
||||||
|
const resolved = resolveDotEnvPath({
|
||||||
|
worktree,
|
||||||
|
directory,
|
||||||
|
filePath: args.filePath ?? ".env",
|
||||||
|
});
|
||||||
|
|
||||||
|
const es = EnvSitter.fromDotenvFile(resolved.absolutePath);
|
||||||
|
const result = await es.fingerprintKey(args.key);
|
||||||
|
|
||||||
|
return JSON.stringify({ file: resolved.displayPath, key: args.key, result }, null, 2);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"tool.execute.before": async (input, output) => {
|
||||||
|
const filePath = getFilePathFromArgs(output.args);
|
||||||
|
if (!filePath) return;
|
||||||
|
|
||||||
|
if (!isSensitiveDotEnvPath(filePath) && !isEnvSitterPepperPath(filePath)) return;
|
||||||
|
|
||||||
|
if (input.tool === "read") {
|
||||||
|
await notifyBlocked("Reading");
|
||||||
|
throw new Error(
|
||||||
|
"Reading `.env*` is blocked. Use EnvSitter tools instead: envsitter_keys / envsitter_fingerprint (never prints values)."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.tool === "edit" || input.tool === "write" || input.tool === "patch" || input.tool === "multiedit") {
|
||||||
|
await notifyBlocked("Editing");
|
||||||
|
throw new Error("Editing `.env*` and `.envsitter/pepper` via tools is blocked.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnvSitterGuard;
|
||||||
86
package-lock.json
generated
Normal file
86
package-lock.json
generated
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"name": "envsitter-guard",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "envsitter-guard",
|
||||||
|
"dependencies": {
|
||||||
|
"envsitter": "*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@opencode-ai/plugin": "*",
|
||||||
|
"@types/node": "*",
|
||||||
|
"typescript": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@opencode-ai/plugin": {
|
||||||
|
"version": "1.1.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.1.14.tgz",
|
||||||
|
"integrity": "sha512-tfF4bEjeF7Gm0W0ViQUhzy77AaZfRxQ/kcPa7/Bc/YM9HddzjEqz0wOJ6ePG8UdUYc0dkKSJOJVhapUbAn/tOw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@opencode-ai/sdk": "1.1.14",
|
||||||
|
"zod": "4.1.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@opencode-ai/sdk": {
|
||||||
|
"version": "1.1.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.1.14.tgz",
|
||||||
|
"integrity": "sha512-PJFu2QPxnOk0VZzlPm+IxhD1wSA41PJyCG6gkxAMI767gfAO96A0ukJJN7VK/gO6MbxLF5oTFaxBX5rAGcBRVw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "25.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.6.tgz",
|
||||||
|
"integrity": "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~7.16.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/envsitter": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/envsitter/-/envsitter-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-gDJ/ZMjD0z31MSpj88IiaHNdJVfzWpXANm4uLwUD1ScKtt3LaKZYir2nY+peJOBjZM9579d5y+rTIQD+kU4lIA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"envsitter": "dist/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||||
|
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "4.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz",
|
||||||
|
"integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
package.json
Normal file
16
package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "envsitter-guard",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@opencode-ai/plugin": "*",
|
||||||
|
"@types/node": "*",
|
||||||
|
"typescript": "*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"envsitter": "*"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "tsc -p tsconfig.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["index.ts", ".opencode/plugin/**/*.ts"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user