Skip to content

Instantly share code, notes, and snippets.

@sksq96
Created August 5, 2026 19:46
Show Gist options
  • Select an option

  • Save sksq96/5ab79a728ce1cb3fb7bd5aa1a296932f to your computer and use it in GitHub Desktop.

Select an option

Save sksq96/5ab79a728ce1cb3fb7bd5aa1a296932f to your computer and use it in GitHub Desktop.
papercut - tiny CLI for AI agents to log friction they hit while working in a repo (python3 stdlib, no deps)
#!/usr/bin/env python3
"""papercut - log friction agents hit while working in a repo.
usage:
papercut "what bit you" # log one papercut, e.g. "unquoted zsh globs broke rg"
papercut list [n] # read the newest n (default 10)
echo "..." | papercut # log from stdin
papercut -m <name> "..." # override the auto-detected agent name
not on PATH? install once (single file, python3 stdlib, no deps):
chmod +x papercut && ln -sf "$PWD/papercut" ~/.local/bin/papercut
Entries land in PAPERCUTS.md at the git root (or $PWD, or $PAPERCUT_FILE) as dense
bullets under a `## YYYY-MM-DD` heading per day, newest day on top. Attribution is
automatic ($PAPERCUT_MODEL, else $AI_AGENT). -h / --help / help prints this.
"""
import os, subprocess, sys, time
def target():
if p := os.environ.get("PAPERCUT_FILE"):
return p
try:
root = subprocess.run(["git", "rev-parse", "--show-toplevel"],
capture_output=True, text=True).stdout.strip()
except OSError:
root = ""
return os.path.join(root or os.getcwd(), "PAPERCUTS.md")
def main(argv):
path = target()
if argv and argv[0] in ("-h", "--help", "help"):
return print(__doc__.rstrip()) or 0
if argv and argv[0] == "list":
n = int(argv[1]) if len(argv) > 1 and argv[1].isdigit() else 10
out, date = [], None
for line in open(path) if os.path.exists(path) else []:
if line.startswith("## "):
date = line.strip()
elif line.startswith("- ") and n > 0:
if date:
out += ([""] if out else []) + [date]
date = None
out.append(line.rstrip())
n -= 1
print("\n".join(out) or f"no papercuts yet ({path})")
return 0
model = (os.environ.get("PAPERCUT_MODEL") or os.environ.get("AI_AGENT")
or os.environ.get("ANTHROPIC_MODEL") or "unknown-agent")
if argv and argv[0] in ("-m", "--model"):
model, argv = (argv[1] if len(argv) > 1 else model), argv[2:]
body = " ".join(argv).strip()
if not body and not sys.stdin.isatty():
body = sys.stdin.read().strip()
if not body:
print(__doc__.rstrip(), file=sys.stderr)
return 0 if sys.stdin.isatty() else 1
body = " ".join(body.split())
lines = (open(path).read().splitlines() if os.path.exists(path) else
["# Papercuts", "",
"Friction agents hit while working here. Newest day on top. To add yours:", "",
"```", "papercut \"what bit you\" # or ./papercut from the repo root",
"papercut list [n] # read the newest n", "```", "",
"Not on PATH? `ln -sf \"$(git rev-parse --show-toplevel)/papercut\" "
"~/.local/bin/papercut`", ""])
day, entry = time.strftime("## %Y-%m-%d", time.gmtime()), f"- {body} ({model})"
if day in lines:
j = lines.index(day) + 1
while j < len(lines) and not lines[j].startswith("## "):
j += 1
while lines[j - 1] == "":
j -= 1
lines.insert(j, entry)
else:
k = next((i for i, l in enumerate(lines) if l.startswith("## ")), len(lines))
lines[k:k] = [day, "", entry, ""]
with open(path, "w") as f:
f.write("\n".join(lines).rstrip("\n") + "\n")
print(f"logged to {path}")
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment