kotools 2026.7.14__tar.gz
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.
- kotools-2026.7.14/.github/workflows/publish.yml +17 -0
- kotools-2026.7.14/.gitignore +21 -0
- kotools-2026.7.14/.python-version +1 -0
- kotools-2026.7.14/AGENTS.md +61 -0
- kotools-2026.7.14/CHANGELOG.md +106 -0
- kotools-2026.7.14/CLAUDE.md +108 -0
- kotools-2026.7.14/LICENSE +674 -0
- kotools-2026.7.14/PKG-INFO +252 -0
- kotools-2026.7.14/README.md +211 -0
- kotools-2026.7.14/WORKLOG.md +253 -0
- kotools-2026.7.14/docs/fleet.md +137 -0
- kotools-2026.7.14/docs/gdocs-markdown.md +96 -0
- kotools-2026.7.14/docs/ideas.md +340 -0
- kotools-2026.7.14/docs/memory.md +206 -0
- kotools-2026.7.14/docs/notes.md +47 -0
- kotools-2026.7.14/docs/papers-cli-design.md +83 -0
- kotools-2026.7.14/docs/publish-ai.md +79 -0
- kotools-2026.7.14/docs/publish.md +70 -0
- kotools-2026.7.14/docs/pydantic-ai.md +96 -0
- kotools-2026.7.14/docs/remote.md +84 -0
- kotools-2026.7.14/pyproject.toml +68 -0
- kotools-2026.7.14/src/ko/__init__.py +1 -0
- kotools-2026.7.14/src/ko/_cli_shared.py +81 -0
- kotools-2026.7.14/src/ko/agents/__init__.py +27 -0
- kotools-2026.7.14/src/ko/agents/_files.py +200 -0
- kotools-2026.7.14/src/ko/agents/_memory.py +170 -0
- kotools-2026.7.14/src/ko/agents/_shared.py +116 -0
- kotools-2026.7.14/src/ko/agents/_toolsets.py +307 -0
- kotools-2026.7.14/src/ko/agents/ai.py +58 -0
- kotools-2026.7.14/src/ko/agents/repo.py +65 -0
- kotools-2026.7.14/src/ko/agents/research.py +52 -0
- kotools-2026.7.14/src/ko/agents/tv.py +48 -0
- kotools-2026.7.14/src/ko/arxiv.py +121 -0
- kotools-2026.7.14/src/ko/billing.py +56 -0
- kotools-2026.7.14/src/ko/brief.py +131 -0
- kotools-2026.7.14/src/ko/cli.py +557 -0
- kotools-2026.7.14/src/ko/cli_ai.py +895 -0
- kotools-2026.7.14/src/ko/cli_google.py +1001 -0
- kotools-2026.7.14/src/ko/cli_web.py +941 -0
- kotools-2026.7.14/src/ko/config.py +88 -0
- kotools-2026.7.14/src/ko/dirs.py +42 -0
- kotools-2026.7.14/src/ko/doc.py +30 -0
- kotools-2026.7.14/src/ko/exa.py +103 -0
- kotools-2026.7.14/src/ko/fetch.py +242 -0
- kotools-2026.7.14/src/ko/gcal.py +261 -0
- kotools-2026.7.14/src/ko/gdocs.py +238 -0
- kotools-2026.7.14/src/ko/gdrive.py +247 -0
- kotools-2026.7.14/src/ko/gmail.py +166 -0
- kotools-2026.7.14/src/ko/google_auth.py +267 -0
- kotools-2026.7.14/src/ko/gsheets.py +704 -0
- kotools-2026.7.14/src/ko/hf.py +129 -0
- kotools-2026.7.14/src/ko/hn.py +184 -0
- kotools-2026.7.14/src/ko/llm.py +202 -0
- kotools-2026.7.14/src/ko/logs.py +136 -0
- kotools-2026.7.14/src/ko/mcp_client.py +437 -0
- kotools-2026.7.14/src/ko/mcp_server.py +118 -0
- kotools-2026.7.14/src/ko/papers.py +291 -0
- kotools-2026.7.14/src/ko/prompt.py +91 -0
- kotools-2026.7.14/src/ko/prompts/ko-publish-site.md +47 -0
- kotools-2026.7.14/src/ko/prompts/local-first-python-app.md +153 -0
- kotools-2026.7.14/src/ko/prompts/opencode.md +61 -0
- kotools-2026.7.14/src/ko/prompts/pydantic-ai-agent.md +190 -0
- kotools-2026.7.14/src/ko/prompts/python-cli.md +140 -0
- kotools-2026.7.14/src/ko/prompts/research-papers.md +82 -0
- kotools-2026.7.14/src/ko/prompts/sveltekit-app.md +56 -0
- kotools-2026.7.14/src/ko/prompts/sveltekit-embedded-ai.md +135 -0
- kotools-2026.7.14/src/ko/publish.py +1223 -0
- kotools-2026.7.14/src/ko/refs.py +259 -0
- kotools-2026.7.14/src/ko/sessions.py +347 -0
- kotools-2026.7.14/src/ko/telemetry.py +80 -0
- kotools-2026.7.14/src/ko/ticktick.py +109 -0
- kotools-2026.7.14/src/ko/tmdb.py +121 -0
- kotools-2026.7.14/src/ko/x.py +300 -0
- kotools-2026.7.14/src/ko/yt.py +151 -0
- kotools-2026.7.14/tests/test_arxiv.py +83 -0
- kotools-2026.7.14/tests/test_billing.py +17 -0
- kotools-2026.7.14/tests/test_brief.py +102 -0
- kotools-2026.7.14/tests/test_cli.py +169 -0
- kotools-2026.7.14/tests/test_config.py +187 -0
- kotools-2026.7.14/tests/test_doc.py +37 -0
- kotools-2026.7.14/tests/test_exa.py +26 -0
- kotools-2026.7.14/tests/test_fetch.py +56 -0
- kotools-2026.7.14/tests/test_files_tools.py +182 -0
- kotools-2026.7.14/tests/test_gcal.py +203 -0
- kotools-2026.7.14/tests/test_gdocs.py +62 -0
- kotools-2026.7.14/tests/test_gdrive.py +64 -0
- kotools-2026.7.14/tests/test_gmail.py +104 -0
- kotools-2026.7.14/tests/test_google_auth.py +78 -0
- kotools-2026.7.14/tests/test_gsheets.py +161 -0
- kotools-2026.7.14/tests/test_hf.py +59 -0
- kotools-2026.7.14/tests/test_hn.py +126 -0
- kotools-2026.7.14/tests/test_llm.py +72 -0
- kotools-2026.7.14/tests/test_logs.py +53 -0
- kotools-2026.7.14/tests/test_mcp_client.py +122 -0
- kotools-2026.7.14/tests/test_memory_tools.py +180 -0
- kotools-2026.7.14/tests/test_papers.py +197 -0
- kotools-2026.7.14/tests/test_prompt.py +44 -0
- kotools-2026.7.14/tests/test_publish.py +206 -0
- kotools-2026.7.14/tests/test_refs.py +139 -0
- kotools-2026.7.14/tests/test_sessions.py +479 -0
- kotools-2026.7.14/tests/test_ticktick.py +20 -0
- kotools-2026.7.14/tests/test_tmdb.py +58 -0
- kotools-2026.7.14/tests/test_x.py +219 -0
- kotools-2026.7.14/tests/test_yt.py +171 -0
- kotools-2026.7.14/uv.lock +1988 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
pypi:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # OIDC — PyPI trusted publishing, no tokens
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v5
|
|
15
|
+
- uses: astral-sh/setup-uv@v7
|
|
16
|
+
- run: uv build --no-sources
|
|
17
|
+
- run: uv publish
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.venv/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
.DS_Store
|
|
10
|
+
.claude/settings.local.json
|
|
11
|
+
.arxiv2md_cache/
|
|
12
|
+
|
|
13
|
+
# node tooling for `ko publish` (wrangler) — package.json + lock are committed
|
|
14
|
+
node_modules/
|
|
15
|
+
.wrangler/
|
|
16
|
+
|
|
17
|
+
# never commit real credentials or tokens
|
|
18
|
+
google_client.json
|
|
19
|
+
google_token.json
|
|
20
|
+
*.secret.*
|
|
21
|
+
.env
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# AGENTS.md — driving `ko` from a shell
|
|
2
|
+
|
|
3
|
+
`ko` is an opinionated CLI of thin SDK wrappers (web search, papers, HN, Google
|
|
4
|
+
Sheets/Docs/Calendar/Gmail, X, publishing, LLM/agents). It's built to be driven by
|
|
5
|
+
agents over bash as much as by a human. This file is the contract; `ko <cmd> --help`
|
|
6
|
+
is the detailed contract for any one command.
|
|
7
|
+
|
|
8
|
+
## First move
|
|
9
|
+
|
|
10
|
+
Run `ko doctor` — it reports, per tool, what env/keys/binaries it needs and whether
|
|
11
|
+
they're present. If a command fails with an auth/key error, `ko doctor` tells you why.
|
|
12
|
+
|
|
13
|
+
## Output contract
|
|
14
|
+
|
|
15
|
+
- **stdout is data; stderr is everything else.** Results go to stdout; errors, notes,
|
|
16
|
+
and "no results" messages go to stderr. You can pipe stdout into `jq`/`cut`/`awk`
|
|
17
|
+
without it being polluted by status text.
|
|
18
|
+
- **Default format is plain text / TSV** — `cut -f N` safe. `ko gsheets get` and other
|
|
19
|
+
tabular commands emit TSV.
|
|
20
|
+
- **`--json` for structured output.** Most read commands take `--json`. The shape is a
|
|
21
|
+
**JSON array of objects** unless the command's `--help` says otherwise (a few return a
|
|
22
|
+
single object — e.g. `ko tv --json` is `{top, matches}`, `ko hn item --json` is
|
|
23
|
+
`{story, comments}`, `ko papers get --json` is a single work object, `ko hf info --json`
|
|
24
|
+
is a single paper object — and `ko gsheets get --json` is a 2D array).
|
|
25
|
+
- Under `--json`, **errors are a JSON object on stderr**: `{"error": "...", "code": "..."}`.
|
|
26
|
+
- Under `--json`, **empty results are `[]` on stdout** (plus a note on stderr), so a
|
|
27
|
+
downstream `| jq` never chokes.
|
|
28
|
+
|
|
29
|
+
## Exit codes
|
|
30
|
+
|
|
31
|
+
- `0` — success, **including empty results** (empty is not an error).
|
|
32
|
+
- `1` — runtime error (network, auth, bad upstream). One clean line on stderr; no traceback.
|
|
33
|
+
- `2` — usage error (bad flags/args; Typer prints usage to stderr).
|
|
34
|
+
|
|
35
|
+
## Limits / pagination
|
|
36
|
+
|
|
37
|
+
- List commands take `--n` (default ~10) to bound output. Note: `ko gmail` / `ko cal`
|
|
38
|
+
currently use `-n` as the short alias; most other commands use `--n` with no short form.
|
|
39
|
+
When in doubt, check `ko <cmd> --help`.
|
|
40
|
+
|
|
41
|
+
## Bare-argument shortcuts (not visible in `--help`)
|
|
42
|
+
|
|
43
|
+
`ko` routes a leading bare argument deterministically (explicit command names always win):
|
|
44
|
+
|
|
45
|
+
- `ko <url>` → `ko fetch <url>` (URL → markdown; YouTube URLs return the transcript, or fail cleanly when captions are off)
|
|
46
|
+
- `ko <file>` → `ko doc <file>` (PDF/Office/image → text), when the arg is an existing file
|
|
47
|
+
- `ko x <name>` → `ko x list <name>` (anything after `x` that isn't an x subcommand is a list name)
|
|
48
|
+
- `ko <anything> help` → `ko <anything> --help` (trailing `help` prints help at any level: `ko help`, `ko exa help`, `ko exa search help`)
|
|
49
|
+
|
|
50
|
+
## Auth
|
|
51
|
+
|
|
52
|
+
Google commands (`gsheets`/`gdocs`/`cal`/`gmail`) share one OAuth token per account.
|
|
53
|
+
`ko gsheets auth` grants it (covers Sheets+Docs+Calendar read+write, Gmail read-only).
|
|
54
|
+
No browser? The only interactive prompt in the whole CLI is that one-time OAuth consent.
|
|
55
|
+
|
|
56
|
+
## Working on the code
|
|
57
|
+
|
|
58
|
+
- Python 3.14, `uv`, `ruff`, `pytest`, `typer`.
|
|
59
|
+
- Install/run: `uv run ko ...`. Test: `uv run pytest -q`. Lint: `uv run ruff check`.
|
|
60
|
+
- One module per domain under `src/ko/`; `cli.py` registers the Typer subapps. See
|
|
61
|
+
`CLAUDE.md` for the full design principles and how to add a subcommand.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `ko` (kotools) are documented in this file. It is the
|
|
4
|
+
canonical release record — GitHub release notes are drawn from it. WORKLOG.md is
|
|
5
|
+
the dev journal (why/decisions); this is what changed, release-grained.
|
|
6
|
+
|
|
7
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/2.0.0/).
|
|
8
|
+
Versioning is [CalVer](https://calver.org/), PEP 440-normalized: `YYYY.M.D`
|
|
9
|
+
(tag `vYYYY.M.D`; a second release the same day appends `.1`). The version covers
|
|
10
|
+
the **CLI contract** (commands, flags, output shapes, exit codes — see AGENTS.md);
|
|
11
|
+
breaking changes to it carry a `**Breaking:**` marker inline under their type.
|
|
12
|
+
Cut a release with the repo's `/release` skill.
|
|
13
|
+
|
|
14
|
+
## [Unreleased]
|
|
15
|
+
|
|
16
|
+
## [2026.7.14] - 2026-07-14
|
|
17
|
+
|
|
18
|
+
The money-and-agents release: model tiers with per-call cost notes, agents that
|
|
19
|
+
remember between runs, and releases that publish themselves to PyPI.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **PyPI publishing**: a tag push now builds and publishes `kotools` via GitHub
|
|
24
|
+
OIDC trusted publishing (`.github/workflows/publish.yml`, no tokens) — this
|
|
25
|
+
release is the first through the pipeline; `uv tool install kotools` from
|
|
26
|
+
here on.
|
|
27
|
+
- `ko ai "<prompt>"` — the default agent: every toolset (web, papers, HN,
|
|
28
|
+
movies, read-only ~/code files) plus its own memory; medium-tier model,
|
|
29
|
+
hard-capped at 30 model requests per run. One-shot or REPL.
|
|
30
|
+
- `ko agent repo` — repo-explorer agent: "how does repo X do Y" over `~/code`
|
|
31
|
+
(read-only by construction; ripgrep search; knows the refs/CLAUDE.md map;
|
|
32
|
+
cites file:line; basic tier — under a cent a run).
|
|
33
|
+
- Agent memory: research + repo agents keep a per-agent markdown workspace
|
|
34
|
+
(`memory.md` anchor injected each run + free-form notes; append/edit tools
|
|
35
|
+
with a uniqueness guard), plus a shared hand-edited `~/.config/ko/memory.md`
|
|
36
|
+
injected into every memory-carrying agent (research, repo, ai). A fresh run
|
|
37
|
+
recalls what a prior run saved. Hardened after a trace-eval + review pass:
|
|
38
|
+
huge/minified files can't blow the context, broken symlinks and rg timeouts
|
|
39
|
+
degrade cleanly, and `grep` grew the `limit` param the model reached for.
|
|
40
|
+
- Model tiers: `[llm] basic/medium/smart/ultra` in config.toml (`llm.model_for`) —
|
|
41
|
+
basic (deepseek-v4-flash) drives llm/tv/brief/summarize defaults, smart
|
|
42
|
+
(`~x-ai/grok-latest`) the research agent, ultra (gpt-5.6-sol) is reserved for
|
|
43
|
+
high-stakes calls. **Changed**: the everyday default moves from gemini-flash
|
|
44
|
+
direct to DeepSeek via OpenRouter — one prepaid pool, visible in `ko billing`.
|
|
45
|
+
- LLM calls report their cost on stderr — `[model · 66→21 tok · $0.0012]` —
|
|
46
|
+
using OpenRouter's *actual* billed cost when available (genai-prices estimate,
|
|
47
|
+
marked `~`, otherwise). Covers `ko llm`, agents, `ko brief`, `yt -s`; `sessions
|
|
48
|
+
summarize` totals its spend.
|
|
49
|
+
- `ko refs` — manage the `~/code/refs` reference-repo folder: bare = parallel
|
|
50
|
+
`--ff-only` pull of all clones (only moved HEADs printed), `setup` bootstraps
|
|
51
|
+
the baked list + `~/.config/ko/refs.txt` on any machine, `add <url>` clones and
|
|
52
|
+
remembers, `list` = TSV. `ko doctor` shows the folder's disk footprint and
|
|
53
|
+
flags clones over 500MB.
|
|
54
|
+
- Opt-in LLM telemetry: `[telemetry] enabled = true` + `POSTHOG_API_KEY` sends
|
|
55
|
+
provider-agnostic traces (model/tokens/cost) to PostHog via pydantic-ai OTel
|
|
56
|
+
instrumentation; off by default, metadata-only unless `include_content = true`.
|
|
57
|
+
- `[llm] model` and `[agents] model` in config.toml — the default model now has
|
|
58
|
+
a config home (env var still wins); `ko doctor` gains an effective-settings
|
|
59
|
+
footer (each value's source: env/config/default, plus the three dirs) and
|
|
60
|
+
live-checks the OpenRouter key via `/credits` — proves it works and shows
|
|
61
|
+
credits left, without spending tokens.
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- A malformed `config.toml` now warns loudly (every command + doctor) instead of
|
|
66
|
+
silently ignoring all keys and settings; README documents the three dirs.
|
|
67
|
+
|
|
68
|
+
## [2026.7.9] - 2026-07-09
|
|
69
|
+
|
|
70
|
+
First tagged release — everything since the repo started (2026-04). `ko` is an
|
|
71
|
+
opinionated personal CLI of thin SDK wrappers, built to be driven by humans and
|
|
72
|
+
AI agents over bash: `--help` is the contract, stdout is data, exit codes mean
|
|
73
|
+
things, `--json` where structure matters.
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- **Read anything**: `ko fetch <url>` — articles, PDFs, arxiv, YouTube, DOIs →
|
|
78
|
+
markdown, with Wayback/archive.today fallbacks (bare `ko <url>` routes here);
|
|
79
|
+
`ko doc <file>` — PDF/Office/image → text, local; `ko yt <url>` — transcripts,
|
|
80
|
+
`-s` summarizes.
|
|
81
|
+
- **Papers**: `ko papers` — cross-publisher search + citation graph via OpenAlex
|
|
82
|
+
(`search|get|cites|refs|similar`, no key); `ko arxiv` — relevance-ranked search
|
|
83
|
+
+ paper → markdown via arxiv2md; `ko hf` — HF Daily Papers.
|
|
84
|
+
- **News & social**: `ko hn` — `top` (incl. `--now` live front page), `search`,
|
|
85
|
+
`item` comment trees; `ko x` — search (incl. full-archive), lists by
|
|
86
|
+
name/id/URL, user timelines; `ko tt` — TickTick lists, read-only.
|
|
87
|
+
- **Google** (one OAuth token, multi-account): `ko gsheets` read & write with a
|
|
88
|
+
clobber guard; `ko gdocs` Markdown ↔ Docs + comments; `ko cal` agenda/find/add;
|
|
89
|
+
`ko gmail` read-only search/view/thread.
|
|
90
|
+
- **AI**: `ko llm` — one-shot, stdin-aware, `-m` any model whose provider key is
|
|
91
|
+
set; `ko agent research|tv` — pydantic-ai agents with saved, resumable
|
|
92
|
+
sessions; `ko agent sessions` — listing with `--tag`/`--search` filters over an
|
|
93
|
+
LLM-built SQLite index (`sessions summarize`); `ko brief` — morning brief;
|
|
94
|
+
`ko prompt` — kickoff briefs; `ko mcp` — inspect/call/overview/auth-info for
|
|
95
|
+
MCP servers.
|
|
96
|
+
- **Publish**: `ko publish` — folder → Cloudflare static site (scaffolds,
|
|
97
|
+
optional PIN gate, custom domains).
|
|
98
|
+
- **Meta**: `ko doctor` setup status; `ko billing` OpenRouter credits; `ko logs`
|
|
99
|
+
local command log (no args/secrets); `ko tv` ratings + where to stream.
|
|
100
|
+
- **Agent contract** (AGENTS.md): stdout=data / stderr=notes, exit 0/1/2,
|
|
101
|
+
`--json` errors as `{error, code}`, bare-arg shortcuts.
|
|
102
|
+
|
|
103
|
+
[Unreleased]: https://github.com/khalido/kotools/compare/v2026.7.14...HEAD
|
|
104
|
+
[2026.7.14]: https://github.com/khalido/kotools/compare/v2026.7.9...v2026.7.14
|
|
105
|
+
[2026.7.9]: https://github.com/khalido/kotools/releases/tag/v2026.7.9
|
|
106
|
+
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# ko
|
|
2
|
+
|
|
3
|
+
Ko's personal opinionated CLI. Thin wrappers around SDKs I use often, built so both I and AI agents (Claude Code via bash) can lean on it.
|
|
4
|
+
|
|
5
|
+
## Subcommands
|
|
6
|
+
- `ko exa search|get` — Exa semantic web search + URL → markdown
|
|
7
|
+
- `ko arxiv search|fetch` — arxiv search (**relevance-ranked**; `--recent` = newest-first for browsing a `cat:cs.LG`) + paper-to-markdown. For cross-publisher relevance, prefer `ko papers search`.
|
|
8
|
+
- `ko gsheets info|tabs|get|find | set|put|header|add-tab|new|clear|auth` — read **& write** Google Sheets via OAuth. Accepts a URL or bare ID. Reads use the readonly scope; writes need read+write (`ko gsheets auth` grants it by default). Shape-aware writers (`set`/`put`, library `write_values`/`write_ranges`/`write_df`) refuse to clobber non-empty cells unless `--overwrite` — the error lists them. `write_df` duck-types polars **or** pandas (neither is a dep). Generic port from the NibbleEdge `ne` tool; no project-specific shortcuts.
|
|
9
|
+
- `ko doc <file>` — PDF/Office/image → plain text via liteparse (local, no models). Bare shortcut: `ko <file>` routes here when the arg is an existing file.
|
|
10
|
+
- `ko fetch <url>` — URL → markdown, deterministic routing: arxiv→arxiv2md, YouTube→transcript (via `ko yt`; fails loud when captions are off — no silent fall-through to the useless description page), PDF→~/Downloads+liteparse (`--no-save`), doi.org→OA copy, else trafilatura, dead/empty→Wayback (`--archive` forces). Bare shortcut: `ko <url>`.
|
|
11
|
+
- `ko yt <url|id>` — YouTube → transcript via **youtube-transcript-api** (free, keyless, no video download; prefers manual subs over auto-captions). Plain text with paragraph breaks (>2s gaps); `--json` = `[{start, text, duration}]` snippets; `-s/--summarize` = one-shot LLM summary (`-m` overrides model). Accepts watch/youtu.be/shorts/live/embed URLs or a bare 11-char id. Stderr notes the ~token count. Future fallback for caption-less videos: Gemini native video understanding (URL as `file_data`, fractions of a cent) — package-first because it's free.
|
|
12
|
+
- `ko llm "<prompt>"` — one-shot LLM, stdin-aware, never has tools. Default = the **basic tier** (deepseek-v4-flash; `KO_DEFAULT_MODEL` → `[llm] model` → `[llm] basic` → baked; `-m` per-run, tab-completes models whose env key is set). Prints a cost note to stderr after each call. OpenRouter's catalog is fetched from its public `/models` and cached at `~/.cache/ko/openrouter_models.json` (24h TTL) so `-m openrouter:<slug>` completes.
|
|
13
|
+
- `ko models [--refresh]` — list model strings usable with `-m`, one per line. `--refresh` force-refetches the OpenRouter catalog past the 24h cache (for grabbing a just-released model).
|
|
14
|
+
- `ko brief [--raw] [-m model]` — morning brief: deterministic gather (today's calendar + unread gmail + `hn top` + `hf top`, each best-effort — an unconfigured source becomes a note, never a crash) → **one** cheap-model synthesis (scannable markdown: what's on, mail that matters, the 2-3 items worth a click). `--raw` prints the gathered sections and skips the LLM entirely. A scripted pipeline, NOT an agent loop. ⚠️ deliberate: sends calendar/email content to the configured LLM (noted in help).
|
|
15
|
+
- `ko logs [--n] [--errors] [--json]` — recent local command logs. ko writes **one structured JSON event per command** via **loguru** (`~/.local/state/ko/logs/ko.jsonl`, serialized JSONL, weekly rotation: cmd label, duration, exit code, error type — **no args/secrets**, following PostHog's "one wide event per request"). loguru's default stderr sink is removed (ko's own output stays clean), and its sink model makes adding a sync trivial — a PostHog sync is just another `logger.add(sink)`. On by default; `[logs] enabled = false` (or `[logs] level = "debug"`) in config.toml. Best-effort — never breaks a command.
|
|
16
|
+
- `ko billing` — read-only balance + recent usage across paid services. v1: OpenRouter credits ($ remaining of total purchased; today/week/month usage). `--json`. One function per provider in `billing.py` (degrades gracefully like `ko doctor` — a missing key is a note, not a crash). pydantic-ai has no account-billing API, so we hit each provider's REST API directly.
|
|
17
|
+
- `ko hn top|search|item` — Hacker News via Algolia (no auth). `top` = hckrnews-style top 10/20 by points (`--now` = the live front page via Algolia's `front_page` tag — complementary: the tag only marks the *current* page, `top` does past windows); `search` defaults to last 12 months; `item` = story + comment tree as text.
|
|
18
|
+
- `ko hf top|search|info|get` — Hugging Face paper pages (no auth). `top` = Daily Papers in HF's *trending* order (upvote-driven with recency decay — not strictly upvote-sorted); `search` = semantic; `info` = metadata incl. github/models/datasets; `get` = paper as markdown. Same ids as arxiv — composes with `ko arxiv fetch`.
|
|
19
|
+
- `ko papers search|get|cites|refs|similar` — cross-publisher literature scouting via **OpenAlex** (no key; `mailto` polite pool; its `oa_url` IS Unpaywall's data). Takes DOIs, arxiv ids, or OpenAlex W-ids interchangeably. `search` = all publishers (TSV: year, cites, oa_status, title, doi, journal); `get` = full text via the OA copy + fetch pipeline, else a metadata card (authors, journal, S2 tldr, abstract reconstructed from `abstract_inverted_index`) — paywalled ≠ error; `cites`/`refs` = citation graph, most-cited first; `similar` = S2 recommendations (needs free `S2_API_KEY` — keyless S2 429s immediately, so tldr/similar degrade gracefully without it). Plus DOI routing in `ko fetch`: doi.org URLs resolve the OA copy first instead of 403ing on publisher landing pages. Design: `docs/papers-cli-design.md`. Known wart: a few OpenAlex DOI records are bad merges (wrong title, right abstract) — `search` for the title and use the W-id when a record looks off.
|
|
20
|
+
- `ko tv <title>` — movie/TV rating + overview + regional watch providers (`TMDB_READ_ACCESS_TOKEN`, free; AU default, `--country`).
|
|
21
|
+
- `ko x search|list|user|lists` — X via the official XDK (`X_BEARER_TOKEN`). `list <name|id|url>` = recent posts from a list (name = one of mine; a bare list id or `x.com/i/lists/<id>` URL reads **any public list**); bare shortcut `ko x ai` ≡ `ko x list ai`. `user <@handle|url>` = one user's timeline (further back than search's ~7-day window). `lists` = my lists, biggest first, with descriptions + member counts (owned lists; *followed* lists need a higher tier). `search` ≤7 days uses the recent index; `--days >7` switches to **full-archive** (years back); `--list <name|id|url>` scopes a search to a list (`ko x search "claude code" --list ai --days 90`). **Likes/bookmarks/home-timeline aren't available** — they need OAuth user-context, not the app-only bearer (which also can't self-identify, so set your handle via `[x] handle` in config.toml or `KO_X_HANDLE`; default `ko`). **Cost**: X is pay-per-use since 2026-02 (prepaid credits) — ~$0.005/post read (owned reads like `lists` $0.001), 2M reads/mo cap; so `ko x list --n 20` ≈ $0.10, `ko x lists` ≈ a cent. Full-archive/big `--n` cost more — mind them. [pricing](https://docs.x.com/x-api/getting-started/pricing)
|
|
22
|
+
- `ko tt lists|items <list>` — TickTick (read-only) via its hosted MCP (`TICKTICK_API_KEY`, env or config.toml). `lists` = your lists; `items <list>` = open tasks (name substring or id). Bare shortcut `ko tt <list>` ≡ `ko tt items <list>`. Read-only by design — manage tasks in TickTick itself. We're an MCP *client* here (`mcp` SDK over Streamable HTTP).
|
|
23
|
+
- `ko gdocs get|push|replace|append|comments|reply|shade-table` (alias `ko gd`) — Markdown ↔ Google Docs via OAuth (shared token; Docs + Drive `drive.file` scopes). `get` = Doc→Markdown (Drive export, tables included, pipe it); `push <file> [doc]` = Markdown→new Doc, or update an existing one in place (shows a diff + confirm, `--force` skips); `replace`/`append` = in-place edits; `comments`/`reply` = Drive comments; `shade-table` = header/totals cell shading (the bit Markdown can't do). What converts: see `docs/gdocs-markdown.md`.
|
|
24
|
+
- `ko cal [agenda] | day | find | add | cals` — Google Calendar agenda + quick-add (shared token; `calendar.readonly` + `calendar.events`). Bare `ko cal` = next 7 days across calendars (`--days`/`--today`); `find <text>` = events matching a title substring, forward or `--past` ('when was my last X'; borrowed from chota-bot); `add "title" <when>` accepts ISO / today / tomorrow (no date lib). Times in `[cal] timezone` (default Australia/Sydney). Minimal — no edit/delete/RSVP.
|
|
25
|
+
- `ko gmail [recent] | search | from | view | thread` — read Gmail (**read-only**; shared token, `gmail.readonly`). Bare `ko gmail` = recent inbox, one line/msg (id, date, from, subject + snippet; `-n`, `--unread`); `search "<query>"` passes Gmail's own syntax verbatim (`from:`/`newer_than:7d`/`is:unread`); `from <who>` shortcut; `view <id> [--full]` = one message; `thread <id> [--full]` = the whole conversation, oldest first. `--json` everywhere. Concise on purpose (vs the verbose Gmail MCP). No send/labels/drafts.
|
|
26
|
+
- `ko publish new <dir> [--md|--bare|--hono]` / `ko publish preview [dir]` / `ko publish [dir]` / `ko publish list` — publish a folder to Cloudflare. `preview` runs `wrangler dev` locally (real http; `file://` breaks ES modules + fetch). `new` scaffolds: default static (Tailwind+Alpine), `--md` (markdown doc site — generic `?page=` shell, README hub, sidebar TOC), `--bare` (just a CLAUDE.md of hints). Bare `ko publish [dir]` deploys; the folder's own `wrangler.jsonc` makes the name sticky → re-publish overwrites the same URL. Custom domain defaults to `<name>.khalido.dev` (`DEFAULT_DOMAIN`; override via `[publish] domain`). Creds: `KO_CLOUDFLARE_API_TOKEN`/`KO_CLOUDFLARE_ACCOUNT_ID` → config.toml → standard `CLOUDFLARE_*`. **Won't silently take over** an existing Worker name (checks the account; `--force` to override). Needs **wrangler** (pinned repo-local via `npm install`; `KO_WRANGLER` → `node_modules/.bin` → PATH → `npx wrangler@4`). `--hono` adds a Hono worker (API routes, D1/R2, optional `--pin` gate, cached `/api/data`); `ko publish --pin new` rotates the gate PIN. Cloudflare Access private pages + AI on sites deferred — see `docs/publish.md` / `docs/publish-ai.md`.
|
|
27
|
+
- `ko ai "<prompt>"` — **the default agent**: all Layer-1 toolsets (web, papers, HN, movies, read-only ~/code files) + its own memory (`memory/ai/`), medium tier, hard cap 30 model requests/run. One-shot or REPL, `-m`/`-r` as usual. Explicit command on purpose — intelligence is opt-in; a typo'd subcommand must never silently spend tokens. Specialists stay under `ko agent`.
|
|
28
|
+
- `ko agent research|repo|tv|sessions` — pydantic-ai agents. `research` (web+papers+HN, smart tier), `repo` (repo-explorer: "how does repo X do Y" over ~/code read-only — own repos + `refs/`; knows the refs/CLAUDE.md map, injected fresh per run; cites file:line; basic tier — a full exploration costs ~$0.0005), `tv` (movies/TV in AU, cheap model), no prompt = interactive REPL, `-m` overrides model, `-r <id>` resumes a saved session. `sessions` lists saved sessions (title/summary/tags once summarized; `--tag`/`--search` filter); `sessions summarize [--n] [-m]` = cheap-model title + one-line takeaway + tags per session into the SQLite index. **`ko a …` is a hidden shorthand for `ko agent …`** (e.g. `ko a research "…"`). See Agents below.
|
|
29
|
+
- `ko mcp inspect <name|url>` / `ko mcp call <name|url> <tool>` / `ko mcp overview <name|url>` / `ko mcp servers` — investigate/use MCP servers (ko is itself an MCP client). `overview` runs a pydantic-ai agent that connects (via `load_mcp_toolsets` — same mcp.json shape) and summarizes what the server is for + what's useful (reads only, won't call mutating tools; cheap model, `-m` to override) — handy for sizing up your own dev server. Target a **server name** from `~/.config/ko/mcp.json` (the standard `{"mcpServers": {...}}` shape — copy-paste-compatible with Claude Code/opencode; supports both `url`+`headers` and stdio `command`/`args`; secrets use `${ENV_VAR}` placeholders expanded from env/config at load, so the file is safe to check in) **or a raw URL**. `inspect` lists tools + resources + prompts (+ capabilities + the server's instructions); `--tool <name>` dumps that tool's full JSON input schema. `call` invokes a tool (`--arg k=v`). `servers` lists the registry. **`auth-info <url>`** is spec-aware (RFC 9728 + 8414): it walks a remote server's OAuth discovery surface *before* connecting — protected-resource + auth-server metadata, scopes, endpoints, DCR — and flags anything malformed (e.g. HTML where JSON should be); exit 1 on problems. `-H` overrides/adds headers, `--json` everywhere. **On failure** it prints the server's real HTTP status + body, the `WWW-Authenticate` header (the server saying *why*), and — if a Bearer token was passed — the token's decoded `aud/iss/scope/exp` claims (a wrong audience is otherwise invisible). (A 503 "not configured" = server-side, not your client.) A verb takes a name/url = client; a future `ko mcp serve` (no target) = ko as server. Listings to stdout, banner to stderr.
|
|
30
|
+
- `ko refs [pull] | setup | add <url> | list` — manage `~/code/refs` (read-only reference clones for ideas/patterns; `[refs] dir`/`KO_REFS_DIR` overrides). Bare `ko refs` = parallel `git pull --ff-only --prune` (worktree groups serial; prints only repos whose HEAD moved as `repo: old -> new`; confirm on TTY only — pipes/agents never prompt). `setup` = idempotent bootstrap: dir + the folder CLAUDE.md (written only if missing — it accumulates takeaways) + clones the **baked list** (`refs.BAKED_REPOS`, the opinionated default) plus `~/.config/ko/refs.txt` (machine-local, one URL/line, dotfile-synced). `add <url> [--note]` clones + stubs a CLAUDE.md entry + appends to refs.txt so setup restores it anywhere. The dir itself is the truth for pull/list; the lists exist only for bootstrap. Pull logic ported from dotfiles/gitupdateall.py. my opinionated "how I build X" notes, pulled by name to load into an agent ("get `ko prompt sveltekit-app`") or copy-paste (`--copy`). Bare `ko prompt` lists (TSV: name, description); `ko prompt <name>` prints the markdown. Briefs ship in `src/ko/prompts/*.md`; add or override per machine in `~/.config/ko/prompts/*.md` (a user file wins by name). A meta-layer on top of generic llms.txt — my picks + the gotchas, with links out to the canonical docs. The tool is just a thin loader; the content is hand-written.
|
|
31
|
+
|
|
32
|
+
## Principles
|
|
33
|
+
- **Opinionated, not generic.** Do what I actually do, the way I do it. Defaults match my habits.
|
|
34
|
+
- **Thin wrappers.** Each module (`exa.py`, `arxiv.py`, `gsheets.py`) is a self-contained domain module over one upstream SDK. Keep parameter names matching the SDK so agent-written code composes with upstream docs.
|
|
35
|
+
- **Help lines carry their weight.** Every subcommand and flag has a concise, useful `help=`. Assume humans and agents skim `--help` and nothing else.
|
|
36
|
+
- **Human-readable and pipeable by default.** Plain text / TSV. Add `--json` when structure matters. Errors to stderr. `typer.Exit(0)` for "no results" (not an error).
|
|
37
|
+
- **One file per domain.** `cli.py` registers subapps; each module owns its logic. No shared `utils/` until three things need the same thing.
|
|
38
|
+
- **`--help` is the contract.** Machine-friendly output is a design constraint, not an afterthought.
|
|
39
|
+
|
|
40
|
+
## Stack
|
|
41
|
+
- Python 3.14, `uv`, `ruff`, `pytest`
|
|
42
|
+
- `typer` for the CLI
|
|
43
|
+
- Module-local `@dataclass` for structured returns
|
|
44
|
+
- `@lru_cache` for credential/service singletons
|
|
45
|
+
|
|
46
|
+
## Releasing
|
|
47
|
+
`/release` (repo skill, `.claude/skills/release/`) — CalVer (`YYYY.M.D`, PEP 440), rolls commits
|
|
48
|
+
into `CHANGELOG.md` (canonical) + a GitHub release (derived), fact-checked by a fresh subagent,
|
|
49
|
+
human approval before tag/publish. WORKLOG = dev journal; CHANGELOG = release-grained facts.
|
|
50
|
+
|
|
51
|
+
## Adding a subcommand
|
|
52
|
+
1. `src/ko/<thing>.py` — thin SDK wrapper + dataclass result types
|
|
53
|
+
2. `src/ko/cli.py` — Typer subapp + commands, every flag with `help=`
|
|
54
|
+
3. `tests/test_<thing>.py` — smoke test; skip live calls if env var / token missing
|
|
55
|
+
4. Deps in `pyproject.toml` under `[project].dependencies`
|
|
56
|
+
|
|
57
|
+
## Agents (`src/ko/agents/`)
|
|
58
|
+
- **Tools declared once** as `FunctionToolset`s in `_toolsets.py` (`web` = exa+fetch, `papers` = arxiv+hf+OpenAlex citation-graph [search/get/cites/refs], `news` = hn [top/front_page/search/discussion — search passes through since_months/by_date/min_comments], `tmdb`, `files` = read-only list_dir/read_file/grep/glob over `~/code` for the repo agent (names/shapes match Claude Code's Read/Grep/Glob priors; listings show ≈token sizes so the model judges read-whole vs grep-first) — `agents/_files.py`: containment resolves symlinks BEFORE `is_relative_to` (thinker's `resolve_in` + harness's TOCTTOU ordering), read-only by construction (no write tools exist), capped output, ripgrep search, ModelRetry-with-listing on misses); agents compose the subset they need. **Docstring-as-contract**: a tool docstring must state any silent filtering default of the underlying function (date windows, result caps, sort orders, index coverage) — the agent-side twin of "--help is the contract". The research agent's instructions encode a snowball workflow (seed → cites/refs → saturation); see `ko prompt research-papers` for the human-facing version. A toolset is stateless and shared by reference; each carries its own `instructions=`.
|
|
59
|
+
- **One file per agent** (`ai.py` = the default, `research.py`, `repo.py`, `tv.py`): ~15-50 lines — `Agent(model, instructions=..., toolsets=[...])`. Per-agent default model (research = capable, tv = cheap); `KO_AGENT_MODEL` → `[agents] model` in config.toml overrides the default at import, `-m` overrides per run (passed to the run call, not baked into the Agent).
|
|
60
|
+
- **`_shared.py`** — generic run/stream/repl over any Agent; streams pretty markdown to a TTY, plain text when piped.
|
|
61
|
+
- **Resilient tools** — each wraps its call in `_try`, returning an error note instead of raising, so one flaky source (rate limit, timeout) doesn't abort a run; the model routes to another source.
|
|
62
|
+
- **Sessions** (`src/ko/sessions.py`) — every turn dumps `all_messages()` (full tool trace) to `~/.local/state/ko/sessions/<id>.json` via pydantic-ai's `ModelMessagesTypeAdapter`. Resume with `-r <id>`; list with `ko agent sessions`. Flat files in state (not config — generated, not hand-edited).
|
|
63
|
+
- **Memory** (`agents/_memory.py`, per docs/memory.md "v1 first") — research + repo agents keep a writable workspace at `~/.local/state/ko/memory/<agent>/`: `memory.md` is the anchor (head-injected every run, pin marker `<!-- ko:pin-end -->` protects foundations from truncation, newest tail survives), plus any other .md notes the agent creates (listed in the injection, read on demand). Tools: `read_note`/`append_memory`/`edit_note` (uniqueness-guarded)/`write_note` (anchor protected from overwrite) — scoped to the agent's own dir, .md-only; the code `files` toolset stays read-only. Shared `~/.config/ko/memory.md` (dotfile-synced user context) injects into all memory agents; hand-edited in v1. Curation rules live in the toolset instructions (thinker's discipline).
|
|
64
|
+
- **Session index** (`~/.local/state/ko/ko.db`, SQLite) — `ko agent sessions summarize` writes one row per session (title ≤8 words, one-sentence takeaway, 3-5 tags) via a cheap-model structured-output call; `sessions --tag/--search` filter on it. The JSON files stay the source of truth — the DB is a rebuildable projection ("the log is the agent"). The model sees a **deterministic digest**, not the raw log: Q&A verbatim, each tool call compressed to one `[tool: name(args…) → return…]` line; trimming drops tool lines first, middle turns next, never the first prompt or final answer.
|
|
65
|
+
- **No YAML agent-spec**: pydantic-ai specs can't reference custom toolsets, so code factories win. Capabilities (lifecycle hooks) are deferred until we need request/tool interception.
|
|
66
|
+
|
|
67
|
+
## Adding an agent
|
|
68
|
+
1. Add/extend a `FunctionToolset` in `agents/_toolsets.py` if new tools are needed.
|
|
69
|
+
2. `agents/<name>.py` — `Agent(model, instructions=..., toolsets=[...])` + thin `run`/`repl` binding `_shared`.
|
|
70
|
+
3. Export in `agents/__init__.py`; add a `@agent_app.command` in `cli.py`.
|
|
71
|
+
4. **Close the loop — the toolset eval method** (house method for any toolset work, proven
|
|
72
|
+
2026-07-10 on the files tools): after building or changing tools, (a) run the agent on 1-2
|
|
73
|
+
real questions (cheap — cents), (b) have a subagent READ THE SESSION TRACES
|
|
74
|
+
(`~/.local/state/ko/sessions/` keeps full tool calls/returns): flailing? repeated calls?
|
|
75
|
+
params the model *invented*? (that's a tool gap — the files `grep` gained `limit` exactly
|
|
76
|
+
this way), (c) a separate fresh-context adversarial review of the tool code. Each pass
|
|
77
|
+
catches what the others can't: traces find ergonomics, review finds crashes, and
|
|
78
|
+
prior-alignment (do names/shapes match what models are trained on — Claude Code's
|
|
79
|
+
Read/Grep/Glob, rg semantics?) finds friction. The tools' heaviest user is the model —
|
|
80
|
+
let it review them.
|
|
81
|
+
|
|
82
|
+
## Google auth (gsheets)
|
|
83
|
+
- OAuth user flow (desktop app). First command triggers browser consent, token cached at `~/.local/state/ko/google_token.json`.
|
|
84
|
+
- `ko gsheets auth` (or `gdocs`/`cal`/`gmail auth` — same flow) grants **one read+write token per account** covering Sheets + Docs + Calendar (write) and Gmail (**read-only**). **No Drive scope** — can't browse Drive, only docs/sheets by ID. Reads use the narrower readonly scopes; the one token serves all. `--readonly` narrows; upgrade an existing token with `auth --logout` then `auth`. Adding another API later = one re-consent.
|
|
85
|
+
- Personal Gmail: an **External** app left in *Testing* expires the refresh token after **7 days** — set the consent screen to **Internal** (Workspace) or **Publish** the app. The token file is portable across machines (copy it, or share via `KO_STATE_DIR`).
|
|
86
|
+
- **Multi-account.** Active account = `-a/--account` (any `ko gsheets` cmd, sets `KO_GOOGLE_ACCOUNT`) → `[google] account` → `"default"`. Per-account token `google_token_<account>.json` (legacy `google_token.json` for `default`); per-account client `google_client_<account>.json` falls back to the shared `google_client.json` (only needed when one OAuth client can't authorize an account, e.g. a Workspace-Internal consent screen + a personal Gmail). `ko gsheets accounts` lists authed accounts. All paths via `google_auth.token_file()/client_file()/active_account()`.
|
|
87
|
+
- **Scope is per-API, not per-folder.** OAuth grants the tool *your* access within the scope (now Sheets only). It can't be limited to specific folders/files; true per-file scoping needs a service account (share files with its email) — a separate model not used here.
|
|
88
|
+
- Prereq: OAuth 2.0 Desktop client JSON at `~/.config/ko/google_client.json` (or set `KO_GOOGLE_CLIENT_FILE`). Full setup in README.
|
|
89
|
+
|
|
90
|
+
## Directories (`src/ko/dirs.py`)
|
|
91
|
+
- `~/.config/ko/` — user-editable config, dotfile-sync-safe (`KO_CONFIG_DIR`). `config.toml` `[keys]` table supplies API keys/tokens as a fallback to env vars (env always wins); loaded into the environment at startup so SDKs that read `os.environ` directly pick them up. Non-secret settings follow the same chain via `config.setting()` (env → config.toml → baked default): `[llm] model`, `[agents] model`/`tv_tastes`, `[x] handle`, `[google] account`, `[cal] timezone`, `[publish] domain`, `[logs]`, `[telemetry]`. **Model tiers** (`llm.model_for(tier)`): `[llm] basic/medium/smart/ultra`, override per-tier in config.toml. Money-first picks (prices $/M in+out, 2026-07-10): basic = deepseek-v4-pro $0.43/$0.87 (llm default, tv, summarize, brief; deepseek-v4-flash is 5× cheaper if basic feels rich), medium = glm-5.2 $0.53/$1.67, smart = `~x-ai/grok-latest` $2/$6 (alias, tracks newest grok; research agent), ultra = gpt-5.6-sol $5/$30 (reserved — nothing uses it by default). Cheap Chinese models carry basic/medium; ultra is the only expensive one. All via OpenRouter, so `ko billing`/doctor's credits line watches the whole spend. **Per-run cost notes**: every LLM call prints `[model · in→out tok · $x]` to stderr (`llm.run_cost`/`last_cost`) — OpenRouter's actual billed cost from `provider_details['cost']` when present, else a genai-prices estimate marked `~` (which misses brand-new models → tokens only). `ko doctor` shows each key's source (env / config / missing), an effective-settings + tiers + dirs footer, warns on malformed config.toml, and live-checks the OpenRouter key via `/credits` (proves the key + shows $ left, no tokens spent).
|
|
92
|
+
- **`[telemetry]` — opt-in LLM telemetry** (`telemetry.py`, off by default): `enabled = true` + `POSTHOG_API_KEY` instruments all pydantic-ai agents (`Agent.instrument_all` → OTel `gen_ai.*` spans → PostHog's AI OTLP endpoint) — provider-agnostic, covers llm/agent/brief/summarize. `include_content = false` by default (metadata only — brief pipes gmail/cal through the LLM; don't mirror that to PostHog). Disabled path is one dict lookup, no OTel imports. GH issue #2.
|
|
93
|
+
- `~/.local/state/ko/` — tokens + id caches, never synced (`KO_STATE_DIR`); old config-dir state files auto-migrate
|
|
94
|
+
- `~/.cache/ko/` — disposable (`KO_CACHE_DIR`)
|
|
95
|
+
- `ko doctor` shows per-tool setup status (keys set, binaries found, auth done)
|
|
96
|
+
|
|
97
|
+
## Agent notes
|
|
98
|
+
- **`AGENTS.md`** at the repo root is the agent-facing contract (output/exit-code/`--json` conventions, bare-arg shortcuts). Keep it in sync with the bullets below.
|
|
99
|
+
- `ko --help` / `ko <cmd> --help` are the contract
|
|
100
|
+
- `ko gsheets get ... ` defaults to TSV — `cut -f N` safe
|
|
101
|
+
- `--json` on most read commands → a JSON array of objects (a few return a single object; `gsheets get` is a 2D array). Under `--json`: errors are `{error, code}` on stderr, empty results are `[]` on stdout. stdout = data, stderr = notes/errors.
|
|
102
|
+
- Exit codes: `0` success (incl. empty results), `1` runtime error, `2` usage error
|
|
103
|
+
- No interactive prompts inside commands (except the one-time OAuth browser popup)
|
|
104
|
+
|
|
105
|
+
## Not yet
|
|
106
|
+
- **MCP server (`ko mcp`)** — `mcp_server.py` is scaffolded but not wired; expose the same module functions over MCP.
|
|
107
|
+
- **tmdb lists by id** in config; `ko ai` skills/capabilities + mcp.json toolsets (registry ready, wire when needed). Full candidate list + priorities in `docs/ideas.md`.
|
|
108
|
+
- **Gemini video fallback for `ko yt`** — caption-less videos via native video understanding (URL as `file_data`); build when the no-captions case actually bites.
|