I Built a 25MB Terminal Database Studio to Replace 1.5GB DBeaver Bloat
Every time I needed to run a quick SELECT, inspect an index, or check foreign key constraints, I found myself waiting 15+ seconds for heavy GUI clients like DBeaver or DataGrip to launch.
By the time the splash screen disappeared, my laptop fans were spinning and my RAM had swallowed 1.5 GB+ of memory just to show a 4-row SQLite table.
Even worse? The constant dread of accidentally executing a destructive UPDATE or DELETE statement without a WHERE clause on a production database.
I wanted something different:
- Instant cold boot (under 120 ms).
- Lightweight footprint (~25 MB of RAM).
- Keyboard-driven workflow (no mouse hunting across menus).
- Built-in safety guard against accidental data loss.
So I built GhostBase 👻.
---
⚡ What is GhostBase?
GhostBase is an open-source, keyboard-driven terminal database studio built with Python and Textual. It connects natively to SQLite, PostgreSQL, and MySQL.
No Java runtimes. No Electron wrapper. Zero telemetry.
---
🛡️ Key Features
1. Production Safety Guard & Safe Mode (Ctrl + S)
GhostBase inspects queries before execution. If it detects a dangerous statement (DELETE or UPDATE without a WHERE clause, DROP TABLE, TRUNCATE), it intercepts it immediately and demands double confirmation. In Safe Mode, all destructive operations are strictly blocked.
2. 1-Key SQL Beautifier (Ctrl + B)
Messy, one-line queries? Hit Ctrl + B to auto-capitalize keywords (SELECT, FROM, WHERE, JOIN, ORDER BY) and indent sub-clauses instantly.
3. 1-Key Query Plan Inspector (Ctrl + P)
Press Ctrl + P to execute an instant EXPLAIN QUERY PLAN on any query to detect missing indexes and table scan bottlenecks.
4. Zero-Config .env Auto-Connect
Run ghostbase in any project folder; it automatically discovers your DATABASE_URL from your local .env file without typing credentials.
5. Multi-Format Exports
Export any query results directly from the terminal:
- SQL INSERTs (Ctrl + I): Generates executable INSERT INTO statements.
- Markdown Tables (Ctrl + M): Ready to paste directly into GitHub Issues, PRs, or Notion.
- CSV (Ctrl + E) & JSON (Ctrl + J)
---
🚀 Quick Start
Install directly from PyPI:
pip install ghostbase-cli
Launch it with your database:
# Auto-detect local .env
ghostbase
# Or connect directly
ghostbase path/to/database.db
ghostbase postgresql://user:password@localhost:5432/my_db
ghostbase mysql://user:password@localhost:3306/my_db
---
🤝 Open Source & Feedback
GhostBase is completely open-source under the MIT License.
⭐ GitHub Repository: 4yk5/GhostBase
If you find it useful, feel free to drop a star on GitHub! I'd love to hear your feedback, feature ideas, and suggestions in the comments below.
Ghost BASIC: the dead language still runs, and Gemma has to learn its dialect
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
What I Built
Ghost BASIC is a small BASIC environment you run in a terminal. The ghost is the language.
Numbered BASIC is the dialect people met in school computer labs: line numbers, FOR/NEXT, PRINT, a program that fit on one screen. Those machines are gone. The programs are not. Ghost BASIC loads a .bas file, including old DOS text, and runs it. Press F5 and it prints. Press F1 and it opens an offline reference for the dialect it actually implements.
Week 4 asked for something spooky, or something that keeps a memory alive. I took the second reading. The first one is painted on the splash screen: an ASCII ghost crosses the terminal, then gets out of the way. What remains is an editor.
Code Magic, on F8, is the optional companion. It can debug, explain, write, or suggest. The default model is Gemma 4 31B (gemma4:31b) through Ollama Cloud. It does not get to invent a richer BASIC than the one on the left. A proposal is compiled locally, and applied only after you review the diff.
Many entries this week put a spirit in the shell. An offline séance with ALGOL 60 keeps a dead language honest by refusing to let the model speak past a curated record. Ghost BASIC is the other half of that idea: the language is executable again, and the model has to write something the runtime will accept.
This is a numbered-BASIC subset, not a finished GW-BASIC or QBasic. QBasic procedures can be opened and read. They cannot run yet.
Demo
Ghost BASIC is a terminal program, so the live site is a tour with real screenshots, not a hosted interpreter:
**tusharv.github.io
!Startup splash
!Editor running a pyramid example
!Code Magic beside the program
Python 3.12 or newer. No account for the editor, the examples, F1 help, or Run. Code Magic asks for your own Ollama Cloud key the first time you open it.
git clone https://github.com/tusharv/GBASIC.git
cd GBASIC
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[dev]'
GBASIC
Then F3, choose fibonacci.bas, F5. Answers to INPUT go in the bottom field. F6 stops a run. GBASIC --no-splash skips the ghost.
The syntax check around Magic is tested with an in-process stand-in for the HTTP call. I did not have an Ollama key in the build environment, so a live model reply is something you will see with your own key. I am not pasting one here as a recorded run.
Code
MIT licensed. The interpreter does not phone home. Magic contacts the network only when you press Send.
{% embed tusharv/GBASIC %}
examples/fibonacci.bas:
10 A=0: B=1
20 FOR I=1 TO 10
30 PRINT A;
40 T=A+B: A=B: B=T
50 NEXT I
60 PRINT
The editor, the runtime, and the 175-topic help stay on the machine. Send is the only arrow that leaves: one fenced program comes back, the local compiler checks it, and you apply the diff yourself.
How I Built It
Ghost BASIC is Python 3.12. The terminal UI is Textual. Execution, file loading, and help need no model and no network. Help covers 175 topics, and anything not implemented is marked that way.
Old files are part of the point. Open accepts UTF-8 and falls back to DOS CP437, including box-drawing characters and a trailing Ctrl+Z. Save writes UTF-8 and does not rewrite the original just because you opened it.
Code Magic calls Ollama's chat endpoint. The default model name is gemma4:31b. You can type another Cloud model name. The key is stored in the OS credential vault (macOS Keychain, Windows Credential Locker, or a Linux secret service), not in a project file.
The model has no shell, no filesystem, and no permission to run the program. Each Send can include the open filename (not the folder), the source, and the last 6,000 characters of that run's output. Turn Context off and those stay behind. The prompt lists the implemented statements and tells the model not to invent RND, procedures, or graphics.
If the reply contains exactly one basic fence, Ghost BASIC compiles that source and does not execute it. A syntax error triggers one correction request. A second failure is shown to you. Apply appears only after you open the diff. Ctrl+Z undoes it. F5 is how you learn whether the program does what you asked. A clean compile is not proof of behavior.
That constraint is the project. A general model will happily write QBasic. Ghost BASIC cannot run QBasic procedures. The interesting failure is a fluent program the interpreter rejects.
Why Does Open Innovation Matter?
A closed chat API could explain a FOR loop. It could not be the language.
The interpreter, the help, and the examples stay on the machine. A classroom or a folder of old .bas files does not need an account. If the model host disappears, PRINT still prints. That is the part a closed API cannot offer: the artifact outlives the service.
Magic does use a service, and that limit should be plain. It needs a network and an Ollama Cloud account. It does not run with the cable pulled. What the open-weight model changes is the contract around that call:
- The default brain is Gemma, named in settings as gemma4:31b. Swapping the model name does not mean rewriting the interpreter.
- The dialect rules live next to the compiler that enforces them. A vendor cannot silently widen BASIC with features this runtime does not have. The local check sends those answers back.
- You can read the prompt, the size limits, and the one-correction rule. Apply is a diff against the editor, not a hidden write.
- Only the open buffer and a slice of output go out, and only when you press Send.
A closed assistant would still draw a triangle of asterisks. It would also be free to describe a language I did not build. Here the language is the repository. The model is a guest that has to pass the compiler.
The Spirit of It
The splash ghost is a joke that lasts a few seconds. The part that stayed is quieter.
Open a file saved on a DOS machine. The line endings are wrong for a modern editor, the box-drawing characters are in CP437, and the file may end with the Ctrl+Z those programs used as an end mark. Ghost BASIC reads it, leaves the original bytes on disk, and shows numbered lines you can run.
That is the offering. Not a story about the person who typed 10 PRINT. The program itself, still willing to print.
Prize Categories
- Best Use of Gemma. Code Magic defaults to gemma4:31b on Ollama Cloud. The prompt, the one-shot syntax repair, and the apply path are built around that open-weight model, and the name can be changed. Inference is hosted. This is not a local Gemma runtime.
まとめサイトをターミナルで読む TUI「matome」を作った
まとめサイトをターミナルの中で読むための TUI「matome」を作って、公開しました。
synchrium/matome
デモのサイトと記事は架空のものです。画像は NASA のパブリックドメインの写真を使っています
ターミナルから離れずにまとめサイトを読みたかったのが、作ったきっかけです。
設計の相談からコードを書くところまで、Claude Code と一緒に作りました(最後の「Claude Code と作った」の節も見てください)。
できること
vim 風のキー操作で、サイト一覧 → 記事一覧 → 記事と進んで読む(j/k で移動、l で開く、q で戻る)
画像をターミナルの中にそのまま表示(sixel / Kitty グラフィックス対応の端末。WezTerm・iTerm2・Windows Terminal・Kitty・Ghostty など)
本文とコメントを分けて表示。c でコメントへ飛べる
画像一覧(I):記事をまたいで画像をタイル状に並べて眺められる。拡大・コピー・保存もできる
動画:記事の中の動画は外部のプレイヤーで再生(YouTube はブラウザで開く)。動画一覧(V)もある
本文の中のリンクは w3m で開き、閉じると同じ記事の同じ位置に戻る
先読み、ブックマーク、既読を隠す、グループ分け、OPML の書き出し・読み込み
Linux / macOS / Windows(WSL も)で動く。SSH ごしでも使える
インストール
Python 3.10 以上が必要です。
git clone synchrium/matome
cd matome
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
./matome
Windows(PowerShell)での入れ方は README にあります。
最初はサイトが 1 つも登録されていないので、サイト一覧で a を押して、RSS の URL かサイトのトップページの URL を入れます。
livedoor ブログ系のまとめサイトは、本文とコメントの場所を自動で見分けるので、ほぼそのまま読めます。作りが独特ないくつかのサイトには専用の設定も入れてあります。
作りの話
Textual + textual-image
画面は Textual で作っています。画像の表示には textual-image を使い、端末に合わせて sixel・Kitty グラフィックス・文字(ハーフブロック)を切り替えています。
sixel を SSH ごしでも重くしない工夫
sixel は送るデータが大きく(画像一覧のタイル 1 枚で 60KB ほど)、そのままだと SSH ごしでは動きが重くなります。そこで、次のようにしています。
画面の端で一部しか見えていない画像は描かず、全部見えてから描く
画像一覧のタイルは軽さを優先して小さめに描く(z で枠いっぱいに切り替え)
タイルは画面 3 枚ぶんだけ並べ、残りは画像の取得だけ先に進めておく
拡大表示は、見えている部分だけを切り出して描く
sixel は 256 色までなので、減色のときにディザをかけて、グラデーションに色のムラが出にくいようにもしています。
先読み
記事一覧でカーソルを止めると、その記事と下の 2 件を裏で取っておくので、開いたときにすぐ読めます。
サイトへの配慮
matome はブラウザや RSS リーダーと同じく、使う人の PC が各サイトから直接記事を読んで表示します。記事を集めて配り直すサーバーはありません。
広告は消しません(消すのは、端末に出せないスクリプトや埋め込みだけ)
サイトに負担をかけないよう、先読みは同時に 2 件まで、画像一覧も記事を 4 件ずつ読みます
User-Agent では matome とリポジトリの URL を名乗ります
元のページは o でブラウザで開けます。気に入ったサイトは、ぜひブラウザでも見に行ってください
サイトの運営者の方で、matome からのアクセスについて要望があれば、リポジトリの Issue で知らせてください。対応します。
Claude Code と作った
matome は、Claude Code と相談しながら作りました。作り始めてから公開まで、約 3 週間でした。
「こういう画面にしたい」「このサイトの本文が取れない」と伝えて、コードを書いてもらう
実際に画面で動かして、気になったところを伝えて直してもらう、のくり返し
公開の前には、ライセンスや個人の情報が入っていないか、サイトに迷惑をかけないかのチェックも一緒にやった
アイデアを出して相談すると、期待以上の答えと実装が返ってくるので、作るのがすごく楽しかったです。
おわりに
まだまだアイデアがあるので、これからも形にしていきたいと思います。
感想や不具合の報告は、リポジトリの Issue にもらえるとうれしいです。
キー操作や設定の詳しいことは README にまとめてあります。
synchrium/matome
Your Function Signature Is Already a CLI — Meet ArgSense
!ArgSense: write a function, get a CLI
ArgSense is a small command-line toolkit for Python. Point it at a function, and it turns the function signature plus its docstring into a complete CLI: argument parsing, type conversion, and a styled help panel — with no parser object to build and no decorator to remember. And when you would rather fill in a command than type it, the very same function is also a form.
If you have ever written this:
import argparse
def hello(name: str = 'world'):
print(f'hello {name}')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('name', nargs='?', default='world', help='Who to greet.')
args = parser.parse_args()
hello(name=args.name)
...you know the pattern: the parser, the type=, the help= strings and the real function all describe the same thing — and they slowly drift apart.
ArgSense removes that duplication.
Install
pip install argsense
Requires Python 3.9 and above. The optional form (the TUI) pulls in Textual as well:
pip install argsense[tui]
From a function to a CLI in 30 seconds
# main.py
from argsense import cli
def hello(name: str = 'world'):
print(f'hello {name}')
if __name__ == '__main__':
cli.run(hello)
python main.py # hello world
python main.py Alice # hello Alice
python main.py -h # a styled help panel
!the help panel for hello -h
No parser object, no add_argument, no help=. The function is the interface.
The same thing, in four libraries
!the same command in argparse, Click, Typer and ArgSense
- argparse builds a parser imperatively and then unpacks args.
- Click adds @click.command() and @click.option(...) decorators.
- Typer infers from type hints, but help text still lives in Annotated[...].
- ArgSense infers from type hints — and the docstring is the help text.
What makes it click
The docstring is the help text
Write each parameter's description once, in the docstring:
def install(appid: str, version: str = '', use_exist: bool = True):
"""
install an app from the cloud.
params:
appid: the app id (a normalized app name).
version: the app version; the last installed one is used if omitted.
use_exist: reuse the installed version when possible.
"""
!the help panel generated from the docstring
With Click or Typer you would pass those same strings into help= / Annotated. Here there is one source of truth, so the code and the help can never disagree.
Types — including the values you actually want
Annotations drive the conversion, so count: int and rate: float just work. On top of that, ArgSense understands a few sentinels that argparse and Typer don't have:
| Input | Becomes |
| --- | --- |
| :true / :false | True / False |
| :none | None |
| :empty | '' |
That matters more than it sounds: --tag :none gives you a real None, instead of the string ":none" that you would have to post-process yourself.
Several commands in one file
Define more functions and call cli.run() with no argument — every public function in the module becomes a command:
def foo(a: str):
"""download a file by name."""
def bar(b: int):
"""update the local cache."""
if __name__ == '__main__':
cli.run()
!the command list
Click needs a @click.group() and Typer needs a Typer() app. Here every public function is simply a command.
Group them when they multiply
Past a handful of commands a flat list gets hard to scan. cli.run_group(...) puts them in named groups — the same functions, the same docstrings, just organized:
if __name__ == '__main__':
cli.run_group({
'app': (install, upgrade, run),
'debug': (doctor, echo),
})
!the command groups
The group name is a label in the help interface, not a word you have to type: it stays python main.py install my-app. (With Click or Typer, nested groups are real sub-applications, so the invocation becomes python main.py app install my-app.)
A form, not just a command line
Sometimes you do not want to remember the flags — you want to see them. Pass mode='tui', or type :tui on the command line, and the same commands are rendered as a form: a sidebar of groups, a tab per command, a widget per parameter, and a log panel for whatever the command prints.
if __name__ == '__main__':
cli.run_group({'app': (install, upgrade, run)}, mode='tui')
!the same commands as a form
Nothing extra is declared for it: the form reads the same signature and docstring, so the parameters, the types, the help text and the defaults are the ones you already wrote. A text parameter becomes an input, a number gets a narrow box of its own, and a flag becomes a switch; consecutive small widgets are packed into a single row so the form stays compact. It does need the optional dependency:
pip install argsense[tui]
Document a shared parameter once
When a few commands share a parameter, describe it once and reference it elsewhere:
def foo(name: str):
"""
params:
name: the leader's name.
"""
def bar(name: str):
"""
params:
name: see `@foo:name`
"""
The reference is resolved lazily, so registration order doesn't matter (and references can even chain). The libraries compared here have no docstring-level equivalent — the usual workaround is extracting a shared decorator by hand.
!the reused parameter description
Help that stays readable
Long descriptions are the classic failure mode of CLI help: the description column gets squeezed into a narrow strip. ArgSense notices when a description doesn't get enough room and reflows it onto a full-width line.
!before and after
Colors that belong on a terminal
The styling adapts to what the terminal can actually show. On a truecolor terminal it draws the Catppuccin mocha palette with a subtle gradient; on a 256-color terminal the gradient drops but the palette stays; and on an old 16-color terminal it falls back to plain ANSI without ever looking broken.
!the catppuccin palette, and the legacy fallback
You pick nothing and configure nothing — the palette is chosen from the terminal's own color system.
How it compares
| Library | Interface declared with | Help text from | Type conversion | Multiple commands | Help UI | Form UI |
| --- | --- | --- | --- | --- | --- | --- |
| argparse | add_argument calls | help= | manual | subparsers | plain | no |
| Click | decorators | help= + docstring | manual | @click.group | pretty | no |
| Typer | type hints | Annotated + docstring | yes | Typer app | rich | no |
| Python Fire | function signature | docstring | yes | dict / class | plain | no |
| ArgSense | type hints + docstring | docstring (params:) | yes | cli.run() / cli.run_group() | rich | TUI |
Built on Rich, so the help panel is styled out of the box, and on Textual the very same commands can be filled in as a form — with a deliberately small surface area: annotate a function and you're done.
Try it
pip install argsense # the CLI
pip install argsense[tui] # the CLI, plus the optional TUI form
- Repo: likianta/argsense-cli
- PyPI: pypi.org
- In-depth comparison (argparse, Click, Typer, Python Fire, docopt, clize, cyclopts, defopt): see the "Comparison with Other Libraries" section of the README.
!install and try it
If you build command-line tools in Python, give it a look. And if it saves you some boilerplate, a star on GitHub goes a long way.
Mentions
I Built a 25MB Terminal Database Studio to Replace 1.5GB DBeaver Bloat Every time I needed to run a quick SELECT, inspect an index, or check foreign key constraints, I found myself waiting 15+ seconds for heavy GUI clients like DBeaver or DataGrip to launch. By the time the splash screen disappeared, my laptop fans were spinning and my RAM had swallowed 1.5 GB+ of memory just to show a 4-row SQLite table. Even worse? The constant dread of accidentally executing a destructive UPDATE or DELETE statement without a WHERE clause on a production database. I wanted something different: - Instant cold boot (under 120 ms). - Lightweight footprint (~25 MB of RAM). - Keyboard-driven workflow (no mouse hunting across menus). - Built-in safety guard against accidental data loss. So I built GhostBase 👻. --- ⚡ What is GhostBase? GhostBase is an open-source, keyboard-driven terminal database studio built with Python and Textual. It connects natively to SQLite, PostgreSQL, and MySQL. No Java runtimes. No Electron wrapper. Zero telemetry. --- 🛡️ Key Features 1. Production Safety Guard & Safe Mode (Ctrl + S) GhostBase inspects queries before execution. If it detects a dangerous statement (DELETE or UPDATE without a WHERE clause, DROP TABLE, TRUNCATE), it intercepts it immediately and demands double confirmation. In Safe Mode, all destructive operations are strictly blocked. 2. 1-Key SQL Beautifier (Ctrl + B) Messy, one-line queries? Hit Ctrl + B to auto-capitalize keywords (SELECT, FROM, WHERE, JOIN, ORDER BY) and indent sub-clauses instantly. 3. 1-Key Query Plan Inspector (Ctrl + P) Press Ctrl + P to execute an instant EXPLAIN QUERY PLAN on any query to detect missing indexes and table scan bottlenecks. 4. Zero-Config .env Auto-Connect Run ghostbase in any project folder; it automatically discovers your DATABASE_URL from your local .env file without typing credentials. 5. Multi-Format Exports Export any query results directly from the terminal: - SQL INSERTs (Ctrl + I): Generates executable INSERT INTO statements. - Markdown Tables (Ctrl + M): Ready to paste directly into GitHub Issues, PRs, or Notion. - CSV (Ctrl + E) & JSON (Ctrl + J) --- 🚀 Quick Start Install directly from PyPI: pip install ghostbase-cli Launch it with your database: # Auto-detect local .env ghostbase # Or connect directly ghostbase path/to/database.db ghostbase postgresql://user:password@localhost:5432/my_db ghostbase mysql://user:password@localhost:3306/my_db --- 🤝 Open Source & Feedback GhostBase is completely open-source under the MIT License. ⭐ GitHub Repository: 4yk5/GhostBase If you find it useful, feel free to drop a star on GitHub! I'd love to hear your feedback, feature ideas, and suggestions in the comments below.
Ghost BASIC: the dead language still runs, and Gemma has to learn its dialect This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine What I Built Ghost BASIC is a small BASIC environment you run in a terminal. The ghost is the language. Numbered BASIC is the dialect people met in school computer labs: line numbers, FOR/NEXT, PRINT, a program that fit on one screen. Those machines are gone. The programs are not. Ghost BASIC loads a .bas file, including old DOS text, and runs it. Press F5 and it prints. Press F1 and it opens an offline reference for the dialect it actually implements. Week 4 asked for something spooky, or something that keeps a memory alive. I took the second reading. The first one is painted on the splash screen: an ASCII ghost crosses the terminal, then gets out of the way. What remains is an editor. Code Magic, on F8, is the optional companion. It can debug, explain, write, or suggest. The default model is Gemma 4 31B (gemma4:31b) through Ollama Cloud. It does not get to invent a richer BASIC than the one on the left. A proposal is compiled locally, and applied only after you review the diff. Many entries this week put a spirit in the shell. An offline séance with ALGOL 60 keeps a dead language honest by refusing to let the model speak past a curated record. Ghost BASIC is the other half of that idea: the language is executable again, and the model has to write something the runtime will accept. This is a numbered-BASIC subset, not a finished GW-BASIC or QBasic. QBasic procedures can be opened and read. They cannot run yet. Demo Ghost BASIC is a terminal program, so the live site is a tour with real screenshots, not a hosted interpreter: **tusharv.github.io !Startup splash !Editor running a pyramid example !Code Magic beside the program Python 3.12 or newer. No account for the editor, the examples, F1 help, or Run. Code Magic asks for your own Ollama Cloud key the first time you open it. git clone https://github.com/tusharv/GBASIC.git cd GBASIC python3 -m venv .venv source .venv/bin/activate python -m pip install -e '.[dev]' GBASIC Then F3, choose fibonacci.bas, F5. Answers to INPUT go in the bottom field. F6 stops a run. GBASIC --no-splash skips the ghost. The syntax check around Magic is tested with an in-process stand-in for the HTTP call. I did not have an Ollama key in the build environment, so a live model reply is something you will see with your own key. I am not pasting one here as a recorded run. Code MIT licensed. The interpreter does not phone home. Magic contacts the network only when you press Send. {% embed tusharv/GBASIC %} examples/fibonacci.bas: 10 A=0: B=1 20 FOR I=1 TO 10 30 PRINT A; 40 T=A+B: A=B: B=T 50 NEXT I 60 PRINT The editor, the runtime, and the 175-topic help stay on the machine. Send is the only arrow that leaves: one fenced program comes back, the local compiler checks it, and you apply the diff yourself. How I Built It Ghost BASIC is Python 3.12. The terminal UI is Textual. Execution, file loading, and help need no model and no network. Help covers 175 topics, and anything not implemented is marked that way. Old files are part of the point. Open accepts UTF-8 and falls back to DOS CP437, including box-drawing characters and a trailing Ctrl+Z. Save writes UTF-8 and does not rewrite the original just because you opened it. Code Magic calls Ollama's chat endpoint. The default model name is gemma4:31b. You can type another Cloud model name. The key is stored in the OS credential vault (macOS Keychain, Windows Credential Locker, or a Linux secret service), not in a project file. The model has no shell, no filesystem, and no permission to run the program. Each Send can include the open filename (not the folder), the source, and the last 6,000 characters of that run's output. Turn Context off and those stay behind. The prompt lists the implemented statements and tells the model not to invent RND, procedures, or graphics. If the reply contains exactly one basic fence, Ghost BASIC compiles that source and does not execute it. A syntax error triggers one correction request. A second failure is shown to you. Apply appears only after you open the diff. Ctrl+Z undoes it. F5 is how you learn whether the program does what you asked. A clean compile is not proof of behavior. That constraint is the project. A general model will happily write QBasic. Ghost BASIC cannot run QBasic procedures. The interesting failure is a fluent program the interpreter rejects. Why Does Open Innovation Matter? A closed chat API could explain a FOR loop. It could not be the language. The interpreter, the help, and the examples stay on the machine. A classroom or a folder of old .bas files does not need an account. If the model host disappears, PRINT still prints. That is the part a closed API cannot offer: the artifact outlives the service. Magic does use a service, and that limit should be plain. It needs a network and an Ollama Cloud account. It does not run with the cable pulled. What the open-weight model changes is the contract around that call: - The default brain is Gemma, named in settings as gemma4:31b. Swapping the model name does not mean rewriting the interpreter. - The dialect rules live next to the compiler that enforces them. A vendor cannot silently widen BASIC with features this runtime does not have. The local check sends those answers back. - You can read the prompt, the size limits, and the one-correction rule. Apply is a diff against the editor, not a hidden write. - Only the open buffer and a slice of output go out, and only when you press Send. A closed assistant would still draw a triangle of asterisks. It would also be free to describe a language I did not build. Here the language is the repository. The model is a guest that has to pass the compiler. The Spirit of It The splash ghost is a joke that lasts a few seconds. The part that stayed is quieter. Open a file saved on a DOS machine. The line endings are wrong for a modern editor, the box-drawing characters are in CP437, and the file may end with the Ctrl+Z those programs used as an end mark. Ghost BASIC reads it, leaves the original bytes on disk, and shows numbered lines you can run. That is the offering. Not a story about the person who typed 10 PRINT. The program itself, still willing to print. Prize Categories - Best Use of Gemma. Code Magic defaults to gemma4:31b on Ollama Cloud. The prompt, the one-shot syntax repair, and the apply path are built around that open-weight model, and the name can be changed. Inference is hosted. This is not a local Gemma runtime.
まとめサイトをターミナルで読む TUI「matome」を作った まとめサイトをターミナルの中で読むための TUI「matome」を作って、公開しました。 synchrium/matome デモのサイトと記事は架空のものです。画像は NASA のパブリックドメインの写真を使っています ターミナルから離れずにまとめサイトを読みたかったのが、作ったきっかけです。 設計の相談からコードを書くところまで、Claude Code と一緒に作りました(最後の「Claude Code と作った」の節も見てください)。 できること vim 風のキー操作で、サイト一覧 → 記事一覧 → 記事と進んで読む(j/k で移動、l で開く、q で戻る) 画像をターミナルの中にそのまま表示(sixel / Kitty グラフィックス対応の端末。WezTerm・iTerm2・Windows Terminal・Kitty・Ghostty など) 本文とコメントを分けて表示。c でコメントへ飛べる 画像一覧(I):記事をまたいで画像をタイル状に並べて眺められる。拡大・コピー・保存もできる 動画:記事の中の動画は外部のプレイヤーで再生(YouTube はブラウザで開く)。動画一覧(V)もある 本文の中のリンクは w3m で開き、閉じると同じ記事の同じ位置に戻る 先読み、ブックマーク、既読を隠す、グループ分け、OPML の書き出し・読み込み Linux / macOS / Windows(WSL も)で動く。SSH ごしでも使える インストール Python 3.10 以上が必要です。 git clone synchrium/matome cd matome python3 -m venv .venv .venv/bin/pip install -r requirements.txt ./matome Windows(PowerShell)での入れ方は README にあります。 最初はサイトが 1 つも登録されていないので、サイト一覧で a を押して、RSS の URL かサイトのトップページの URL を入れます。 livedoor ブログ系のまとめサイトは、本文とコメントの場所を自動で見分けるので、ほぼそのまま読めます。作りが独特ないくつかのサイトには専用の設定も入れてあります。 作りの話 Textual + textual-image 画面は Textual で作っています。画像の表示には textual-image を使い、端末に合わせて sixel・Kitty グラフィックス・文字(ハーフブロック)を切り替えています。 sixel を SSH ごしでも重くしない工夫 sixel は送るデータが大きく(画像一覧のタイル 1 枚で 60KB ほど)、そのままだと SSH ごしでは動きが重くなります。そこで、次のようにしています。 画面の端で一部しか見えていない画像は描かず、全部見えてから描く 画像一覧のタイルは軽さを優先して小さめに描く(z で枠いっぱいに切り替え) タイルは画面 3 枚ぶんだけ並べ、残りは画像の取得だけ先に進めておく 拡大表示は、見えている部分だけを切り出して描く sixel は 256 色までなので、減色のときにディザをかけて、グラデーションに色のムラが出にくいようにもしています。 先読み 記事一覧でカーソルを止めると、その記事と下の 2 件を裏で取っておくので、開いたときにすぐ読めます。 サイトへの配慮 matome はブラウザや RSS リーダーと同じく、使う人の PC が各サイトから直接記事を読んで表示します。記事を集めて配り直すサーバーはありません。 広告は消しません(消すのは、端末に出せないスクリプトや埋め込みだけ) サイトに負担をかけないよう、先読みは同時に 2 件まで、画像一覧も記事を 4 件ずつ読みます User-Agent では matome とリポジトリの URL を名乗ります 元のページは o でブラウザで開けます。気に入ったサイトは、ぜひブラウザでも見に行ってください サイトの運営者の方で、matome からのアクセスについて要望があれば、リポジトリの Issue で知らせてください。対応します。 Claude Code と作った matome は、Claude Code と相談しながら作りました。作り始めてから公開まで、約 3 週間でした。 「こういう画面にしたい」「このサイトの本文が取れない」と伝えて、コードを書いてもらう 実際に画面で動かして、気になったところを伝えて直してもらう、のくり返し 公開の前には、ライセンスや個人の情報が入っていないか、サイトに迷惑をかけないかのチェックも一緒にやった アイデアを出して相談すると、期待以上の答えと実装が返ってくるので、作るのがすごく楽しかったです。 おわりに まだまだアイデアがあるので、これからも形にしていきたいと思います。 感想や不具合の報告は、リポジトリの Issue にもらえるとうれしいです。 キー操作や設定の詳しいことは README にまとめてあります。 synchrium/matome
Your Function Signature Is Already a CLI — Meet ArgSense !ArgSense: write a function, get a CLI ArgSense is a small command-line toolkit for Python. Point it at a function, and it turns the function signature plus its docstring into a complete CLI: argument parsing, type conversion, and a styled help panel — with no parser object to build and no decorator to remember. And when you would rather fill in a command than type it, the very same function is also a form. If you have ever written this: import argparse def hello(name: str = 'world'): print(f'hello {name}') if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('name', nargs='?', default='world', help='Who to greet.') args = parser.parse_args() hello(name=args.name) ...you know the pattern: the parser, the type=, the help= strings and the real function all describe the same thing — and they slowly drift apart. ArgSense removes that duplication. Install pip install argsense Requires Python 3.9 and above. The optional form (the TUI) pulls in Textual as well: pip install argsense[tui] From a function to a CLI in 30 seconds # main.py from argsense import cli def hello(name: str = 'world'): print(f'hello {name}') if __name__ == '__main__': cli.run(hello) python main.py # hello world python main.py Alice # hello Alice python main.py -h # a styled help panel !the help panel for hello -h No parser object, no add_argument, no help=. The function is the interface. The same thing, in four libraries !the same command in argparse, Click, Typer and ArgSense - argparse builds a parser imperatively and then unpacks args. - Click adds @click.command() and @click.option(...) decorators. - Typer infers from type hints, but help text still lives in Annotated[...]. - ArgSense infers from type hints — and the docstring is the help text. What makes it click The docstring is the help text Write each parameter's description once, in the docstring: def install(appid: str, version: str = '', use_exist: bool = True): """ install an app from the cloud. params: appid: the app id (a normalized app name). version: the app version; the last installed one is used if omitted. use_exist: reuse the installed version when possible. """ !the help panel generated from the docstring With Click or Typer you would pass those same strings into help= / Annotated. Here there is one source of truth, so the code and the help can never disagree. Types — including the values you actually want Annotations drive the conversion, so count: int and rate: float just work. On top of that, ArgSense understands a few sentinels that argparse and Typer don't have: | Input | Becomes | | --- | --- | | :true / :false | True / False | | :none | None | | :empty | '' | That matters more than it sounds: --tag :none gives you a real None, instead of the string ":none" that you would have to post-process yourself. Several commands in one file Define more functions and call cli.run() with no argument — every public function in the module becomes a command: def foo(a: str): """download a file by name.""" def bar(b: int): """update the local cache.""" if __name__ == '__main__': cli.run() !the command list Click needs a @click.group() and Typer needs a Typer() app. Here every public function is simply a command. Group them when they multiply Past a handful of commands a flat list gets hard to scan. cli.run_group(...) puts them in named groups — the same functions, the same docstrings, just organized: if __name__ == '__main__': cli.run_group({ 'app': (install, upgrade, run), 'debug': (doctor, echo), }) !the command groups The group name is a label in the help interface, not a word you have to type: it stays python main.py install my-app. (With Click or Typer, nested groups are real sub-applications, so the invocation becomes python main.py app install my-app.) A form, not just a command line Sometimes you do not want to remember the flags — you want to see them. Pass mode='tui', or type :tui on the command line, and the same commands are rendered as a form: a sidebar of groups, a tab per command, a widget per parameter, and a log panel for whatever the command prints. if __name__ == '__main__': cli.run_group({'app': (install, upgrade, run)}, mode='tui') !the same commands as a form Nothing extra is declared for it: the form reads the same signature and docstring, so the parameters, the types, the help text and the defaults are the ones you already wrote. A text parameter becomes an input, a number gets a narrow box of its own, and a flag becomes a switch; consecutive small widgets are packed into a single row so the form stays compact. It does need the optional dependency: pip install argsense[tui] Document a shared parameter once When a few commands share a parameter, describe it once and reference it elsewhere: def foo(name: str): """ params: name: the leader's name. """ def bar(name: str): """ params: name: see `@foo:name` """ The reference is resolved lazily, so registration order doesn't matter (and references can even chain). The libraries compared here have no docstring-level equivalent — the usual workaround is extracting a shared decorator by hand. !the reused parameter description Help that stays readable Long descriptions are the classic failure mode of CLI help: the description column gets squeezed into a narrow strip. ArgSense notices when a description doesn't get enough room and reflows it onto a full-width line. !before and after Colors that belong on a terminal The styling adapts to what the terminal can actually show. On a truecolor terminal it draws the Catppuccin mocha palette with a subtle gradient; on a 256-color terminal the gradient drops but the palette stays; and on an old 16-color terminal it falls back to plain ANSI without ever looking broken. !the catppuccin palette, and the legacy fallback You pick nothing and configure nothing — the palette is chosen from the terminal's own color system. How it compares | Library | Interface declared with | Help text from | Type conversion | Multiple commands | Help UI | Form UI | | --- | --- | --- | --- | --- | --- | --- | | argparse | add_argument calls | help= | manual | subparsers | plain | no | | Click | decorators | help= + docstring | manual | @click.group | pretty | no | | Typer | type hints | Annotated + docstring | yes | Typer app | rich | no | | Python Fire | function signature | docstring | yes | dict / class | plain | no | | ArgSense | type hints + docstring | docstring (params:) | yes | cli.run() / cli.run_group() | rich | TUI | Built on Rich, so the help panel is styled out of the box, and on Textual the very same commands can be filled in as a form — with a deliberately small surface area: annotate a function and you're done. Try it pip install argsense # the CLI pip install argsense[tui] # the CLI, plus the optional TUI form - Repo: likianta/argsense-cli - PyPI: pypi.org - In-depth comparison (argparse, Click, Typer, Python Fire, docopt, clize, cyclopts, defopt): see the "Comparison with Other Libraries" section of the README. !install and try it If you build command-line tools in Python, give it a look. And if it saves you some boilerplate, a star on GitHub goes a long way.