1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
# 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/<name>` 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) <year> Danilo M. <danix@danix.xyz>` and the license
name, then the "Development Approach" section verbatim from the
global preferences.
- `LICENSE`: copied from `examples/tools/licenses/<file>`, 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 <prompt>` or `opencode --prompt
<prompt>`; both read AGENTS.md (CLAUDE.md points Claude Code at it).
Prompt: `New project: <description>. Fill in
AGENTS.md and README.md from this, then wait.`
- Sockets `$XDG_RUNTIME_DIR/kitty-*`, newest first: the first where
`kitty @ --to unix:<sock> launch --type=tab --cwd=<dir> <agent
command>` succeeds wins (a stale socket just fails), then
`hyprctl dispatch focuswindow class:kitty`.
- No socket answers: `setsid -f kitty -d <dir> <agent command>`, a new
window, detached.
- `KITTY` env var overrides the `kitty` binary, for the check.
5. **Output**, one line: `created <path> (<license>), committed <short
sha> | not committed: <reason>, opened <agent> 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.
|