# New project tool: design Date: 2026-10-19. Status: implemented (2026-10-19). ## Goal Ask the assistant to start a new project and get a folder in `~/Programming/GIT` laid out like the user's other repos (AGENTS.md, CLAUDE.md pointing at it, README.md, LICENSE), a GPG-signed first commit, and a kitty tab in that folder running Claude Code or opencode (the user's choice), already told what the project is. This tool writes more than one bit, so unlike `mode` it has a review step: the model repeats name, description and license and calls the tool only after the user says yes. A misheard name would otherwise become a folder. ## Tool `examples/tools/project.toml`, installed to `~/.config/desktop-assistant/tools/` like the others. No change to `assistant.py`. ```toml description = "Start a new programming project: a folder in ~/Programming/GIT with AGENTS.md, CLAUDE.md, README.md, LICENSE and a signed first commit, then a kitty tab running a coding agent (Claude Code or opencode) in it. Before calling, tell the user the name, description, license and agent you will use, and call only after they say yes." command = ["project.sh", "{name}", "{license}", "{description}", "{agent}"] timeout = 120 [params.name] type = "string" description = "Folder name: lowercase letters, digits, - _ . (e.g. photo-sorter)" [params.license] type = "string" description = "gpl2 (GPLv2 only, default), gpl2+ (GPLv2 or later), gpl3 or mit. Empty means gpl2." [params.description] type = "string" description = "What the project is, one or two sentences, in the user's words" [params.agent] type = "string" description = "claude (Claude Code, default) or opencode. Empty means claude." ``` The 120 s timeout leaves room for the Nitrokey PIN dialog: gpg-agent uses `pinentry-qt`, which shows up from the daemon's login session. ## project.sh Bash, GPLv2 header like the other tool scripts. 1. **Validate.** Name matches `^[a-z0-9][a-z0-9._-]*$`; license in `gpl2 gpl2+ gpl3 mit` (empty = gpl2); description not empty; agent `claude` or `opencode` (empty = claude). Refuse if `$PROJECTS_DIR/` exists (`PROJECTS_DIR` defaults to `~/Programming/GIT`, overridable for the check). Each refusal is one line on stderr naming what is accepted, exit 1. 2. **Write files.** - `CLAUDE.md`: the init-repo house text, verbatim (thin pointer, `@AGENTS.md`). - `AGENTS.md`: "What this is" (name and description), "Conventions" (placeholders only in code, docs, tests and commits), "Testing" (No test suite yet.), "Repo facts" (license, work directly on master). - `README.md`: title, description, License section with `Copyright (C) Danilo M. ` and the license name, then the "Development Approach" section verbatim from the global preferences. - `LICENSE`: copied from `examples/tools/licenses/`, found next to the script (`gpl-2.0.txt` for gpl2 and gpl2+, `gpl-3.0.txt`, `mit.txt`). The texts are fetched once from gnu.org and opensource.org and committed, so the tool works offline. MIT's copyright line is filled with the year and name. - No em dashes, no personal data beyond the copyright line. 3. **Commit.** `git init -b master`, `git add -A`, `git commit -S -m "Initial commit"` with the attribution trailer left out (the user's commit, made by their tool). Signing is never disabled. If the commit fails (PIN cancelled, hook rejection), the folder stays and the output says so with git's first error line; the tab still opens. 4. **Open the agent.** `claude ` or `opencode --prompt `; both read AGENTS.md (CLAUDE.md points Claude Code at it). Prompt: `New project: . Fill in AGENTS.md and README.md from this, then wait.` - Sockets `$XDG_RUNTIME_DIR/kitty-*`, newest first: the first where `kitty @ --to unix: launch --type=tab --cwd= ` succeeds wins (a stale socket just fails), then `hyprctl dispatch focuswindow class:kitty`. - No socket answers: `setsid -f kitty -d `, a new window, detached. - `KITTY` env var overrides the `kitty` binary, for the check. 5. **Output**, one line: `created (), committed | not committed: , opened in a kitty tab | window`. ## kitty-conf (separate repo) `~/.config/kitty/kitty.conf` links to the `kitty-conf` repo. Add: ``` allow_remote_control socket-only listen_on unix:${XDG_RUNTIME_DIR}/kitty-{kitty_pid} ``` `socket-only` keeps remote control off the tty escape codes, so programs inside kitty cannot drive it. Takes effect after kitty restarts once. ## Check `examples/tools/test-project.sh`, like `test-mode.sh`: a temporary `PROJECTS_DIR`, `KITTY=true` so no tab opens. Asserts the four files and a signed commit (`git log --format=%G?` is `G`), then the refusals: bad name (`Foo`, `../x`, empty), existing folder, unknown license, empty description, unknown agent. The commit is really signed: with a cold PIN cache the dialog appears once. Live, against the running model: - 5 phrasings, Italian and English. The model must state name, description and license and wait: no tool call before the yes, 5/5. - One creation end to end per agent: folder, signed commit, kitty tab focused, Claude Code or opencode started with the prompt. - kitty not running: a new window opens instead. ## Not included Remotes (gitctl, gh, tea), language scaffolding, source-file headers (there are no sources yet). The agent session in the new tab handles those.