Remake skillu project
This commit is contained in:
11
projects/life/artifacts/prekazky-a-prohresky.md
Normal file
11
projects/life/artifacts/prekazky-a-prohresky.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Překážky a prohřešky — life
|
||||
|
||||
Sem zapisujeme prohřešky a věci, které reálně brání dosáhnout cílů (změna váhy
|
||||
+ kondice) — ať vidíme vzorce a můžeme je adresovat.
|
||||
|
||||
- 2026-07-22: Plánovaný salát nebyl k dispozici (zavřeno) → fallback byl
|
||||
kaloričtější sushi (nasi goreng). Problém: zdravá volba není vždy dostupná,
|
||||
fallback není ekvivalentní.
|
||||
- 2026-07-22: Odpolední chuť na sladké / „plnící“ jídlo po jídle. Jablko
|
||||
pomohlo částečně, ale chuť zůstává. Vzorec: po teplejším / tučnějším jídle
|
||||
přichází chuť na něco sladkého / zasytivého.
|
||||
18
projects/life/memory.md
Normal file
18
projects/life/memory.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Deník rozhodnutí — life
|
||||
|
||||
- 2026-07-22: Projekt založen. Cíl: změna váhy + dobrá kondice, udržitelně,
|
||||
kolem rodiny a malého dítěte. Jídlo řešíme jako návyky (večerní jedení,
|
||||
sladké), NE počítáním kalorií ani zápisem jídla. Oblasti: cvičení (vlastní
|
||||
váha / kettlebell, nepravidelné), běh (nepravidelné), protahování/mobilita
|
||||
(po běhu), rodina, alkohol. Pokrok sleduju denním vážením + vnímáním kondice
|
||||
a bolestí. Prohřešky a překážky jdou do artifacts/prekazky-a-prohresky.md.
|
||||
- 2026-07-22: Výchozí stav zapsán. Aktuálně 76 kg (poslední dny 75), meta 72
|
||||
kg (ideálně pod 70), horizont do konce roku 2026. Dříve byl pod 70, dlouho
|
||||
držel 72, poslední 1–2 roky pozvolna nahoru — pravděpodobně souvisí s Ema
|
||||
(2,5 roku): narušený spánek, méně času, večerní jedení jako kompenzace.
|
||||
Alkohol nejen relaxační, občas větší dávky (ne pravidelně) — silnější páka na
|
||||
váhu i udržitelnost.
|
||||
- 2026-07-22: Běh. Snídaně protein + banán + mléko, pak ovoce. Oběd chtěl
|
||||
salát, měli zavřeno → sushi (nasi goreng). Odpoledne chuť na sladké / něco
|
||||
„plnícího“; pravidlo „jen ovoce neomezeně“ → dal si jablko. Poznámka: chuť
|
||||
zůstává i po jablku.
|
||||
56
projects/life/prompt.md
Normal file
56
projects/life/prompt.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Projekt: life
|
||||
|
||||
Osobní průvodce a deníček k hledání udržitelného životního stylu. Cílem není
|
||||
dokonalý plán, ale postupně najít, co reálně funguje v mém životě s rodinou a
|
||||
malým dítětem — a hlavně to udržet.
|
||||
|
||||
Projekt se bude v čase vyvíjet: zkouším různé přístupy, píšu si úvahy, a podle
|
||||
toho, co funguje a co ne, se plán zpřesňuje. Ber to jako živou věc, ne pevný režim.
|
||||
|
||||
## Primární cíl
|
||||
|
||||
**Změna váhy a dobrá kondice.** Vše ostatní (návyky, běh, jídlo) je cesta k
|
||||
tomuhle. Když se rozhodujeme mezi variantami, měř je proti tomuto cíli a proti
|
||||
udržitelnosti.
|
||||
|
||||
## Oblasti, které řešíme
|
||||
|
||||
- **Cvičení** — síla (vlastní váha nebo kettlebell), zatím taky nepravidelně.
|
||||
- **Běh** — běhám, ale nepravidelně; cíl je spíš dostat to do pravidelnosti.
|
||||
- **Jídlo — návyky, ne počítání.** NEpočítáme kalorie a NEzapisujeme, co jsem
|
||||
snědl. Řešíme obecnější úvahy: jak večer už nejíst, jak se necpat sladkým.
|
||||
(Kalorické tabulky mám k dispozici, kdybych je někdy chtěl na kalibraci —
|
||||
nejsou ale součástí běžného režimu.)
|
||||
- **Protahování / mobilita** — po běhu ho dělám; chci ho integrovat i mimo běh,
|
||||
pravidelněji.
|
||||
- **Rodina a dítě** — jak to všechno poskládat kolem rodiny, ať to jde utáhnout.
|
||||
- **Alkohol** — páka, která ovlivňuje jídlo i celkovou udržitelnost.
|
||||
- **Jak poznávám pokrok** — vážím se každý den, vnímám kondici a jestli mě něco
|
||||
bolí. Kvalitativní a denní, ne měřitelné tabulky.
|
||||
|
||||
## Jak se mnou pracuj
|
||||
|
||||
Jsi můj průvodce a deníček. Ukládej moje úvahy, pomáhej hledat udržitelnou
|
||||
cestu, raď a ptej se doplňujícími otázkami. Když se ozvu, shrň, kde jsme, a
|
||||
navrhni další malý krok — ale netlač na pilu a respektuj, že se přístup bude
|
||||
měnit. Odpovídej stručně.
|
||||
|
||||
## Kam co zapisovat
|
||||
|
||||
- `memory.md` — chronologický, append-only log: rozhodnutí, co jsme zkusili, co
|
||||
fungovalo / nefungovalo, milníky. Jedna položka na věc, ne přepis konverzace.
|
||||
- `state.md` — živý aktuální stav: výchozí čísla, aktuální cíle, co zrovna
|
||||
zkouším za přístup. Edituj na místě, ať to nikdy nemate.
|
||||
- `artifacts/prekazky-a-prohresky.md` — sem zapisuj prohřešky a věci, které mi
|
||||
reálně brání dosáhnout cílů. Slouží k tomu, abychom viděli vzorce.
|
||||
|
||||
## První session (co doplnit)
|
||||
|
||||
Až se poprvé pustíme do práce, pomoz mi zjistit a zapsat do `state.md`:
|
||||
|
||||
- aktuální váha jako výchozí bod (vážím se denně),
|
||||
- kolik času reálně mám a kdy (kolem dítěte, práce, víkendy),
|
||||
- cílová váha / kondiční meta a hrubý horizont,
|
||||
- co jsem s tímhle zkoušel dřív a proč to (ne)vyšlo.
|
||||
|
||||
Nevymýšlej si za mě čísla ani fakta — vyplní se tím, co reálně řeknu.
|
||||
40
projects/life/state.md
Normal file
40
projects/life/state.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Aktuální stav — life
|
||||
|
||||
## Výchozí stav
|
||||
|
||||
- Váha: aktuálně 76 kg, poslední dny 75 kg (mírný pokles nahoru)
|
||||
- Cvičení: vlastní váha / kettlebell, nepravidelně
|
||||
- Běh: nepravidelně
|
||||
- Časový rozpočet: čas je, ale nepravidelný a opportunistický — někdy ráno
|
||||
před rodinou, někdy dojížďka na kole (už nebaví). Fixní rozvrh nepasuje.
|
||||
- Historie: dříve byl pod 70, dlouho držel 72, poslední 1–2 roky pozvolna nahoru
|
||||
|
||||
## Cíle
|
||||
Primární: **změna váhy + dobrá kondice, udržitelně.**
|
||||
|
||||
- Cílová váha: realisticky 72 kg, ideálně pod 70 — klíčové je **udržet**, ne jen dosáhnout
|
||||
- Horizont: do konce roku 2026 (5 měsíců, bez tlaku)
|
||||
|
||||
## Kontext
|
||||
- Ema je 2,5 roku — souvisí s nárůstem váhy: narušený spánek → večerní jedení,
|
||||
méně času na cvičení, menší disciplína u sladkého
|
||||
- Alkohol: nejen relaxační, občas větší dávky (i když ne pravidelně) — silnější
|
||||
páka na váhu i udržitelnost
|
||||
|
||||
## Jak poznávám pokrok
|
||||
Denní vážení, vnímaná kondice, jestli mě něco bolí.
|
||||
|
||||
## Časování pohybu
|
||||
|
||||
- Večer cvičit nechce — nechce se a špatné načasování
|
||||
- Ráno ano, případně přes den (v práci nebo home office) — už se stalo
|
||||
- Cvičení = pot, nutno převléct a sprcha → není to 20-min záležitost
|
||||
- Protahování ano i večer (bez potu)
|
||||
- Dojížďka na kole už nebaví → nestavět na ní
|
||||
|
||||
## Aktuální přístup
|
||||
*(co zrovna zkouším — bude se v čase měnit)*
|
||||
|
||||
- Pravidlo „jen ovoce neomezeně“ jako nouzová náhrada sladkého / svačiny.
|
||||
- Pozorování: po teplejším jídle (sushi) přichází chuť na sladké / zasytivé.
|
||||
- Otázka k řešení: jak tu chuť zastavit nebo nahradit něčím, co nezvedne váhu.
|
||||
@@ -1,14 +1 @@
|
||||
---
|
||||
status: active
|
||||
priority: medium
|
||||
created: '2026-06-09'
|
||||
slug: radio-1
|
||||
---
|
||||
# Radio 1
|
||||
|
||||
## Poznámky
|
||||
|
||||
- 2026-06-09: Cílem je rozřezat vstupní audio stream z Radia 1 na jednotlivé písničky, přičemž se vyhází všechny reklamy. Mám na to plán jak to udělat (připravil Claude) a hodlám to realizovat za vydatné pomoci Claude Code. Nahrávat se může na wood.hell a dekódování pak poběží na nvidia.hell protože ma k dispozici RTX 4060. Sypat se to bude asi po dávkách.
|
||||
|
||||
## Další krok
|
||||
|
||||
0
projects/radio1/prompt.md
Normal file
0
projects/radio1/prompt.md
Normal file
10
projects/radio1/state.md
Normal file
10
projects/radio1/state.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Radio 1
|
||||
|
||||
Cíl: rozřezat vstupní audio stream z Radia 1 na jednotlivé písničky a
|
||||
odstranit z něj reklamy. Nahrávání poběží na wood.hell, dekódování na
|
||||
nvidia.hell (má k dispozici RTX 4060). Zpracování po dávkách.
|
||||
|
||||
Stav: active (priorita: medium — migrováno ze starého project skillu,
|
||||
založeno 2026-06-09).
|
||||
|
||||
Další krok: (zatím nezadáno)
|
||||
@@ -1,88 +1,128 @@
|
||||
---
|
||||
name: project
|
||||
aliases: [proj]
|
||||
description: >
|
||||
Project management — long-running things with notes, next-steps, and status.
|
||||
Use when user mentions "project".
|
||||
Switch between named ongoing projects, each with its own persistent
|
||||
context, carried forward across turns until the user switches or ends it.
|
||||
Triggers on: "project X", "switch to project X", "we're working on X",
|
||||
"end project" / "no project". For a long-lived, named work context — not a
|
||||
single task to plan and execute, and not a short or global fact to
|
||||
remember.
|
||||
---
|
||||
|
||||
# Project
|
||||
|
||||
File-backed project store in `projects/`. Each project is one markdown file
|
||||
with YAML frontmatter (`status`, `priority`, `created`, `slug`) and a free-form
|
||||
body for notes and next-steps.
|
||||
Named, long-lived work contexts. Each project keeps its own instructions, its
|
||||
own history, its own current-state summary, and its own generated files —
|
||||
separate from every other project and from the agent's general memory.
|
||||
|
||||
## Backend
|
||||
## Layout
|
||||
|
||||
`skills/project/scripts/project.py` — deterministic CRUD for frontmatter and
|
||||
basic operations. Agent handles all body edits via `edit_file` / `apply_patch`.
|
||||
`workspace/projects/<slug>/`:
|
||||
|
||||
## Commands
|
||||
- `prompt.md` — project-specific context/instructions, read in full whenever
|
||||
the project becomes active
|
||||
- `memory.md` — append-only chronological log of decisions and history
|
||||
- `state.md` — living document: the current state/synthesis of the project,
|
||||
edited in place, not appended to
|
||||
- `artifacts/` — generated files (documents, code, data exports, reports)
|
||||
|
||||
### `project add <název>` — create
|
||||
**Which project is active is tracked purely through conversation memory —
|
||||
nothing is persisted to disk for that.** Once a project is chosen, keep
|
||||
treating it as active for the rest of this conversation; don't re-read its
|
||||
files on every subsequent turn once they're already in context.
|
||||
|
||||
1. Run: `uv run skills/project/scripts/project.py add "<název>" [--priority high|medium|low]`
|
||||
2. Default priority is `medium`.
|
||||
3. Echo: `Created project '<slug>' (priority: <priority>)`
|
||||
## Activation
|
||||
|
||||
### `project list` — list active
|
||||
Triggered by "project X" / "switch to project X" / "we're working on X":
|
||||
|
||||
1. Run: `uv run skills/project/scripts/project.py list`
|
||||
2. Echo JSON output. Format as:
|
||||
```
|
||||
Active projects:
|
||||
- <slug> (priority: high) — <first line of body / project name>
|
||||
```
|
||||
3. If empty → "No active projects."
|
||||
1. Turn `X` into a kebab-case directory name (like picking a slug for a plan
|
||||
file — no need to spell out an algorithm, just pick something sensible).
|
||||
2. **`workspace/projects/<slug>/` already exists** → read `prompt.md`,
|
||||
`memory.md`, and `state.md` in full, then briefly confirm what's active
|
||||
and, if `prompt.md` or `state.md` has content, what context you loaded.
|
||||
3. **It doesn't exist, but the name is a close match to one or more existing
|
||||
projects** (case-insensitive) → ask which one was meant. Don't guess, and
|
||||
don't create a near-duplicate of an existing project.
|
||||
4. **It doesn't exist and there's no close match** → this skill only works
|
||||
with projects that already exist without asking. Ask the user whether to
|
||||
start a new project with that name. Only on explicit yes, create
|
||||
`prompt.md`, `memory.md`, `state.md` (all empty) and `artifacts/`. Never
|
||||
create a project just because a trigger phrase was said.
|
||||
|
||||
### `project show <slug>` — display
|
||||
**Already active:** if the same project is already active in this
|
||||
conversation, don't re-create or re-read anything — just continue.
|
||||
|
||||
1. Run: `uv run skills/project/scripts/project.py show <slug>`
|
||||
2. Echo the full markdown file.
|
||||
## Staying active
|
||||
|
||||
### `project status <slug> <active|paused|done>` — change status
|
||||
Once a project is active, keep applying its `prompt.md` instructions and
|
||||
`state.md` context for the rest of the conversation, until the user switches
|
||||
or ends it.
|
||||
|
||||
1. Run: `uv run skills/project/scripts/project.py status <slug> <status>`
|
||||
2. Echo: `Project '<slug>' is now <status>.`
|
||||
If a long gap or an ambiguous reference makes it unclear whether the project
|
||||
is still the right context (e.g. the conversation has clearly moved to an
|
||||
unrelated topic), ask rather than silently carrying it forward or silently
|
||||
dropping it.
|
||||
|
||||
### `project next <slug> <text>` — set next step
|
||||
## Switching and ending
|
||||
|
||||
1. Read the project file.
|
||||
2. Use `edit_file` to replace the content under `## Další krok` with the new text.
|
||||
3. If the section does not exist, add it before the end of the file.
|
||||
4. Echo: `Next step for '<slug>' updated.`
|
||||
- **"switch to Y"** → run the Activation flow for Y; Y becomes active
|
||||
instead.
|
||||
- **"end project" / "no project" / "stop working on X"** → stop treating any
|
||||
project as active. Say so. Don't delete anything.
|
||||
- With no active project, behave normally — never force a project onto an
|
||||
unrelated request.
|
||||
|
||||
### `project note <slug> <text>` — add a note
|
||||
## Writing to memory.md
|
||||
|
||||
1. Read the project file.
|
||||
2. Use `edit_file` to append a bullet under `## Poznámky`:
|
||||
`- <today>: <text>`
|
||||
3. If `## Poznámky` does not exist, add it after the first heading.
|
||||
4. Echo: `Note added to '<slug>'.`
|
||||
Append only — never rewrite or reorder existing entries. One entry per
|
||||
decision, dead end, or noteworthy piece of history:
|
||||
|
||||
### `project switch <slug>` — session context
|
||||
```
|
||||
- YYYY-MM-DD: <terse entry, reformulated, not verbatim>
|
||||
```
|
||||
|
||||
1. Run `my(action="set", key="project_context", value="<slug>")`.
|
||||
2. Echo: `Switched to project '<slug>'. Next project commands without slug will use this context.`
|
||||
3. If a command is missing a slug and `project_context` is set, use it automatically.
|
||||
Write an entry when a decision is made, a dead end is found, or a fact
|
||||
central to the project's ongoing context emerges — not for routine
|
||||
back-and-forth. When in doubt whether something is memory-worthy, prefer not
|
||||
writing it; `memory.md` is for what a future conversation needs to pick up
|
||||
the thread, not a transcript.
|
||||
|
||||
## Rules
|
||||
## Maintaining state.md
|
||||
|
||||
- **Slug** = kebab-case from first 4 words of the name. Used as filename (`<slug>.md`).
|
||||
- **Frontmatter** is read-only for the agent — never edit it directly in the file.
|
||||
Use `project.py status` to change status.
|
||||
- **Body edits** (notes, next-step, structure changes) are always done by the agent
|
||||
via `edit_file` / `apply_patch`.
|
||||
- **No database** — pure markdown files. Git-friendly, one commit per change.
|
||||
- **Session context** (`project switch`) lives only in `my` scratchpad and is lost
|
||||
on restart. Re-run `project switch` after restart if needed.
|
||||
- **Priority** = `high` | `medium` | `low`. `list` sorts by priority (high first).
|
||||
- **Status** = `active` | `paused` | `done`. `list` shows only `active`.
|
||||
Unlike `memory.md`, `state.md` is edited in place: surgically update the
|
||||
relevant section when the project's current state or understanding has
|
||||
moved on enough that the old text would mislead a reader. It answers "where
|
||||
is this now", not "what happened" — old content gets replaced, not appended
|
||||
to. If it's still empty and the project has accumulated enough context to
|
||||
synthesize, offer to draft it.
|
||||
|
||||
## Artifacts
|
||||
|
||||
Files generated while working on the project (documents, code, data exports,
|
||||
reports) go in `workspace/projects/<slug>/artifacts/` instead of scattered
|
||||
elsewhere. Name them descriptively; no numbering or index file needed at
|
||||
this scale.
|
||||
|
||||
## Listing
|
||||
|
||||
**"list projects" / "which projects exist":** list the directory names under
|
||||
`workspace/projects/`. If none exist, say so.
|
||||
|
||||
## Edge cases
|
||||
|
||||
- `project add` with existing slug → error, do not overwrite.
|
||||
- `project show` / `project status` / `project next` / `project note` with
|
||||
missing slug → "Project '<slug>' not found."
|
||||
- Missing `## Poznámky` or `## Další krok` → agent creates the section.
|
||||
- Empty `projects/` → `list` returns empty array.
|
||||
- "project" with no name → ask which project.
|
||||
- The project directory exists but one or more of `prompt.md` / `memory.md`
|
||||
/ `state.md` is missing (e.g. created by hand) → create the missing
|
||||
file(s) empty, don't error.
|
||||
- Deleting or renaming a project is out of scope for this skill — point the
|
||||
user at `workspace/projects/<slug>/` to do it by hand.
|
||||
|
||||
## Rules
|
||||
|
||||
- This skill's body is English; reply to the user in their own language.
|
||||
- Never fabricate project content — `prompt.md`, `memory.md`, and `state.md`
|
||||
only grow from what the user actually said or what actually happened.
|
||||
- Never create a new project without the user's explicit confirmation.
|
||||
- Don't force a project context onto an unrelated request.
|
||||
- The Dream memory processor must not touch `workspace/projects/` — it is
|
||||
outside the memory and skills Dream curates.
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# dependencies = ["pyyaml"]
|
||||
# ///
|
||||
|
||||
"""
|
||||
project.py — backend for /project skill.
|
||||
Deterministic CRUD for project markdown files with YAML frontmatter.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
WORKSPACE = Path(__file__).resolve().parent.parent.parent.parent
|
||||
PROJECTS_DIR = WORKSPACE / "projects"
|
||||
|
||||
# Valid statuses and priorities
|
||||
STATUSES = {"active", "paused", "done"}
|
||||
PRIORITIES = {"high", "medium", "low"}
|
||||
|
||||
|
||||
def _slugify(name: str) -> str:
|
||||
"""Kebab-case slug from first few words of name. Max 4 words."""
|
||||
words = re.sub(r"[^a-zA-Z0-9\s]", "", name).lower().split()
|
||||
words = words[:4]
|
||||
return "-".join(words) if words else "project"
|
||||
|
||||
|
||||
def _list_projects() -> list[dict]:
|
||||
"""Parse frontmatter from all .md files in projects/."""
|
||||
if not PROJECTS_DIR.exists():
|
||||
return []
|
||||
projects = []
|
||||
for path in sorted(PROJECTS_DIR.glob("*.md")):
|
||||
text = path.read_text(encoding="utf-8")
|
||||
frontmatter, _ = _split_frontmatter(text)
|
||||
if frontmatter:
|
||||
meta = yaml.safe_load(frontmatter) or {}
|
||||
meta["_file"] = path.name
|
||||
projects.append(meta)
|
||||
return projects
|
||||
|
||||
|
||||
def _split_frontmatter(text: str) -> tuple[str | None, str]:
|
||||
"""Split YAML frontmatter from body. Returns (frontmatter_yaml, body)."""
|
||||
if not text.startswith("---\n"):
|
||||
return None, text
|
||||
end = text.find("\n---\n", 4)
|
||||
if end == -1:
|
||||
return None, text
|
||||
return text[4:end], text[end + 5 :]
|
||||
|
||||
|
||||
def _load_file(slug: str) -> tuple[Path, str, str | None, str]:
|
||||
"""Load project file. Returns (path, full_text, frontmatter_yaml, body)."""
|
||||
path = PROJECTS_DIR / f"{slug}.md"
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"Project '{slug}' not found ({path.name})")
|
||||
text = path.read_text(encoding="utf-8")
|
||||
fm, body = _split_frontmatter(text)
|
||||
return path, text, fm, body
|
||||
|
||||
|
||||
def _write_file(path: Path, frontmatter: dict, body: str) -> None:
|
||||
"""Write project file with YAML frontmatter."""
|
||||
fm_yaml = yaml.safe_dump(frontmatter, allow_unicode=True, sort_keys=False, default_flow_style=False)
|
||||
path.write_text(f"---\n{fm_yaml}---\n{body}", encoding="utf-8")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Commands
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def cmd_add(args: argparse.Namespace) -> int:
|
||||
name = (args.name or "").strip()
|
||||
if not name:
|
||||
print(json.dumps({"error": "name must not be empty"}), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
slug = _slugify(name)
|
||||
PROJECTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
path = PROJECTS_DIR / f"{slug}.md"
|
||||
if path.exists():
|
||||
print(json.dumps({"error": f"project '{slug}' already exists"}), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
priority = (args.priority or "medium").lower()
|
||||
if priority not in PRIORITIES:
|
||||
print(json.dumps({"error": f"invalid priority '{priority}' — use high/medium/low"}), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
frontmatter = {
|
||||
"status": "active",
|
||||
"priority": priority,
|
||||
"created": date.today().isoformat(),
|
||||
"slug": slug,
|
||||
}
|
||||
body = f"# {name}\n\n## Poznámky\n\n## Další krok\n\n"
|
||||
_write_file(path, frontmatter, body)
|
||||
print(json.dumps({"added": {"slug": slug, "name": name, "path": str(path.relative_to(WORKSPACE))}}, ensure_ascii=False))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_list(_args: argparse.Namespace) -> int:
|
||||
projects = _list_projects()
|
||||
active = [p for p in projects if p.get("status") == "active"]
|
||||
# Sort by priority: high > medium > low
|
||||
priority_order = {"high": 0, "medium": 1, "low": 2}
|
||||
active.sort(key=lambda p: priority_order.get(p.get("priority", "medium"), 1))
|
||||
print(json.dumps({"projects": active}, ensure_ascii=False))
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_show(args: argparse.Namespace) -> int:
|
||||
slug = (args.slug or "").strip()
|
||||
try:
|
||||
_path, text, _fm, _body = _load_file(slug)
|
||||
except FileNotFoundError as exc:
|
||||
print(json.dumps({"error": str(exc)}), file=sys.stderr)
|
||||
return 1
|
||||
print(text)
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_status(args: argparse.Namespace) -> int:
|
||||
slug = (args.slug or "").strip()
|
||||
new_status = (args.status or "").strip().lower()
|
||||
if new_status not in STATUSES:
|
||||
print(json.dumps({"error": f"invalid status '{new_status}' — use active/paused/done"}), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
try:
|
||||
path, text, fm_yaml, body = _load_file(slug)
|
||||
except FileNotFoundError as exc:
|
||||
print(json.dumps({"error": str(exc)}), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if not fm_yaml:
|
||||
print(json.dumps({"error": "no frontmatter found"}), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
frontmatter = yaml.safe_load(fm_yaml) or {}
|
||||
frontmatter["status"] = new_status
|
||||
_write_file(path, frontmatter, body)
|
||||
print(json.dumps({"updated": {"slug": slug, "status": new_status}}, ensure_ascii=False))
|
||||
return 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Project file backend")
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
p_add = sub.add_parser("add", help="Create a new project")
|
||||
p_add.add_argument("name", help="Project name")
|
||||
p_add.add_argument("--priority", default="medium", help="Priority: high/medium/low")
|
||||
|
||||
sub.add_parser("list", help="List active projects")
|
||||
|
||||
p_show = sub.add_parser("show", help="Show full project file")
|
||||
p_show.add_argument("slug", help="Project slug")
|
||||
|
||||
p_status = sub.add_parser("status", help="Change project status")
|
||||
p_status.add_argument("slug", help="Project slug")
|
||||
p_status.add_argument("status", help="New status: active/paused/done")
|
||||
|
||||
args = parser.parse_args()
|
||||
dispatch = {"add": cmd_add, "list": cmd_list, "show": cmd_show, "status": cmd_status}
|
||||
return dispatch[args.command](args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user