#!/bin/bash # desktop-assistant tool: start a new project. # Copyright (C) 2026 Danilo M. # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License version 2 as published # by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # project.sh [claude|opencode]: a folder in # ~/Programming/GIT with AGENTS.md, CLAUDE.md, README.md and LICENSE, a # signed first commit, then a kitty tab running the agent there. Check: # test-project.sh. set -uo pipefail name=${1:-} license=${2:-} desc=${3:-} agent=${4:-claude} root=${PROJECTS_DIR:-$HOME/Programming/GIT} kitty=${KITTY:-kitty} here=$(dirname "$(readlink -f "$0")") year=$(date +%Y) author="Danilo M. " [[ $name =~ ^[a-z0-9][a-z0-9._-]*$ ]] || { echo "name must be lowercase letters, digits, - _ . (not first), not: '$name'" >&2; exit 1; } case "${license:-gpl2}" in gpl2) lic="GPLv2 only"; text=gpl-2.0.txt ;; gpl2+) lic="GPLv2 or later"; text=gpl-2.0.txt ;; gpl3) lic="GPLv3 only"; text=gpl-3.0.txt ;; mit) lic="MIT"; text=mit.txt ;; *) echo "license must be gpl2, gpl2+, gpl3 or mit, not: $license" >&2; exit 1 ;; esac [ -n "$desc" ] || { echo "description is empty" >&2; exit 1; } case "$agent" in claude|opencode) ;; *) echo "agent must be claude or opencode, not: $agent" >&2; exit 1 ;; esac dir=$root/$name [ -e "$dir" ] && { echo "$dir already exists" >&2; exit 1; } mkdir -p "$dir" && cd "$dir" || exit 1 # Only MIT has its holder filled in: the GPL texts carry in their # how-to-apply appendix, which must stay as published. if [ $text = mit.txt ]; then sed "s/ /$year $author/" "$here/licenses/$text" > LICENSE else cp "$here/licenses/$text" LICENSE fi cat > CLAUDE.md <<'EOF' # CLAUDE.md This file is intentionally thin. AGENTS.md is the single source of truth for project guidance, shared across every agent tool. Edit AGENTS.md instead. @AGENTS.md EOF cat > AGENTS.md < README.md <&1); then c="committed $(git rev-parse --short HEAD)" else c="not committed: $(grep -m1 -E 'error|fatal' <<< "$err" || tail -1 <<< "$err")" fi # A tab in the running kitty if one listens (kitty-conf: listen_on # kitty-{kitty_pid}), else a new window. # ponytail: focuses some kitty window, not necessarily the one with the new # tab when several are open; match by kitty's window id if that bites. prompt="New project: ${desc%.}. Fill in AGENTS.md and README.md from this, then wait." # opencode starts in its plan agent (default_agent in its config), which # cannot edit: it proposes, the user switches to build to write. [ "$agent" = claude ] && run=(claude "$prompt") || run=(opencode --prompt "New project: ${desc%.}. Read AGENTS.md and README.md and propose what they should say, then wait.") how=window for s in $(ls -t "${XDG_RUNTIME_DIR:-/tmp}"/kitty-* 2>/dev/null); do if "$kitty" @ --to "unix:$s" launch --type=tab --cwd="$dir" "${run[@]}" >/dev/null 2>&1; then how=tab hyprctl dispatch focuswindow class:kitty >/dev/null 2>&1 break fi done [ $how = tab ] || setsid -f "$kitty" -d "$dir" "${run[@]}" >/dev/null 2>&1 echo "created $dir ($lic), $c, opened $agent in a kitty $how"