Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,9 @@ The allowlist is generated from snip's own filters, pinned to the v0.25.2 releas
files, 173 command/subcommand rules), plus anything you drop in `~/.config/snip/filters/`.
`exclude_flags` and `require_flags` are matched the way snip matches them — against every
argument, flag or not — so `git log --format=...`, `git diff -p` and `gh pr diff` all stay
raw, and `npm install` wraps while `npm view` does not. `#nosnip` anywhere in a command
skips the whole thing.
raw, and `npm install` wraps while `npm view` does not. Output another program parses also
stays raw: `--json`, `--jq`, `--template`, `--format json`, `-o json`, and `git show REV:path`
file contents. `#nosnip` anywhere in a command skips the whole thing.

Filters are read once at startup. A new file in `~/.config/snip/filters/` needs an
opencode restart before smartsnip routes to it. `smartsnip doctor` reports your installed
Expand Down
3 changes: 2 additions & 1 deletion llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
- Append `#nosnip` to any bash command when you need its full raw output. That is the
only way to be certain nothing was dropped.
- `git log --format=...`, `git status --porcelain` etc. are never filtered — explicit
format flags are respected.
format flags are respected. So are `--json`, `--jq`, `--template`, `--format json`,
`-o json` and `git show REV:path`.

## Files

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "opencode-smartsnip",
"version": "0.2.0",
"version": "0.2.1",
"description": "Cuts shell-output tokens in opencode. Wraps only what snip can filter — everything else runs byte-identical.",
"type": "module",
"main": "dist/index.js",
Expand Down
13 changes: 13 additions & 0 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ function isDenied(info: SegmentInfo, config: SmartSnipConfig): boolean {
)
}

const DATA_FLAGS = ["--json", "--jq", "--template"]
const FORMAT_FLAGS = ["--format", "--output", "-o"]

/** Output another program will parse, or file contents (`git show REV:path`), must stay verbatim. */
function isDataOutput(info: SegmentInfo): boolean {
const args = info.tokens.slice(1).map((a) => a.replace(/^(['"])(.*)\1$/, "$2"))
if (args.some((a) => DATA_FLAGS.some((flag) => a === flag || a.startsWith(`${flag}=`)))) return true
if (args.some((a, i) => FORMAT_FLAGS.some((flag) => a === `${flag}=json` || (a === flag && args[i + 1] === "json"))))
return true
return info.head === "git" && args[0] === "show" && args.some((a) => !a.startsWith("-") && a.includes(":"))
}

/** Decide whether a single analyzed segment should be wrapped with snip. */
export function shouldWrap(
segment: string,
Expand All @@ -76,6 +88,7 @@ export function shouldWrap(
if (BUILTINS.has(info.head)) return null
if (info.body.startsWith("(") || info.body.startsWith("{")) return null // subshell/group

if (isDataOutput(info)) return null
const entry = table.get(info.head)
if (!entry) return null
if (!matchesEntry(entry, info)) return null
Expand Down
11 changes: 11 additions & 0 deletions test/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ describe("basic wrapping", () => {
expect(rw("bun run format")).toBe("bun run format")
})

test("machine-readable output passes through verbatim", () => {
expect(rw("gh pr list --json number,title")).toBe("gh pr list --json number,title")
expect(rw("gh pr view 12 --jq .title")).toBe("gh pr view 12 --jq .title")
expect(rw("gh run list --template '{{.name}}'")).toBe("gh run list --template '{{.name}}'")
expect(rw(`gh pr list "--json" number,title`)).toBe(`gh pr list "--json" number,title`)
expect(rw(`gh pr list '--json=number,title'`)).toBe(`gh pr list '--json=number,title'`)
expect(rw("docker ps --format json")).toBe("docker ps --format json")
expect(rw("git show origin/main:src/app.ts")).toBe("git show origin/main:src/app.ts")
expect(rw("git show HEAD")).toBe("snip git show HEAD")
})

test("builtins never wrapped (issue #6)", () => {
expect(rw("cd /tmp")).toBe("cd /tmp")
expect(rw("source .venv/bin/activate")).toBe("source .venv/bin/activate")
Expand Down
Loading