craftkit 0.3.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {craftkit-0.3.0/craftkit.egg-info → craftkit-0.3.1}/PKG-INFO +20 -6
  2. {craftkit-0.3.0 → craftkit-0.3.1}/README.md +19 -5
  3. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/__init__.py +1 -1
  4. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/agent.py +73 -13
  5. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/cli.py +30 -3
  6. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/home.py +14 -3
  7. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/tables.py +20 -2
  8. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/ui.py +12 -0
  9. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/workspace.py +3 -2
  10. {craftkit-0.3.0 → craftkit-0.3.1/craftkit.egg-info}/PKG-INFO +20 -6
  11. {craftkit-0.3.0 → craftkit-0.3.1}/pyproject.toml +1 -1
  12. {craftkit-0.3.0 → craftkit-0.3.1}/LICENSE-APACHE +0 -0
  13. {craftkit-0.3.0 → craftkit-0.3.1}/LICENSE-MIT +0 -0
  14. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/SKILL.md +0 -0
  15. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/__main__.py +0 -0
  16. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/banner.py +0 -0
  17. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/brief.py +0 -0
  18. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/browser.py +0 -0
  19. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/checks.py +0 -0
  20. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/png.py +0 -0
  21. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/proc.py +0 -0
  22. {craftkit-0.3.0 → craftkit-0.3.1}/craft_kit/study.py +0 -0
  23. {craftkit-0.3.0 → craftkit-0.3.1}/craftkit.egg-info/SOURCES.txt +0 -0
  24. {craftkit-0.3.0 → craftkit-0.3.1}/craftkit.egg-info/dependency_links.txt +0 -0
  25. {craftkit-0.3.0 → craftkit-0.3.1}/craftkit.egg-info/entry_points.txt +0 -0
  26. {craftkit-0.3.0 → craftkit-0.3.1}/craftkit.egg-info/requires.txt +0 -0
  27. {craftkit-0.3.0 → craftkit-0.3.1}/craftkit.egg-info/top_level.txt +0 -0
  28. {craftkit-0.3.0 → craftkit-0.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: craftkit
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Study → build → check → refine any software, from the terminal, with your coding agent.
5
5
  License-Expression: MIT OR Apache-2.0
6
6
  Keywords: cli,coding-agent,claude-code,codex,gemini,rebuild,skill
@@ -23,7 +23,7 @@ Dynamic: license-file
23
23
  > **In English:** `craft` rebuilds any software (website, CLI, app, game, library) with the coding
24
24
  > agent you already use (Claude Code, Codex, Gemini CLI): it studies the original, briefs the agent,
25
25
  > checks the result against the original and sends it back until it holds. Install with
26
- > `npm install -g craftkit` or `pipx install craftkit`, then type `craft`.
26
+ > `npm install -g craftkit-cli` or `pipx install craftkit`, then type `craft`.
27
27
 
28
28
  La méthode de Craft Anything, réduite à l'essentiel : **étudier → construire → vérifier → affiner**,
29
29
  pour recréer ou construire n'importe quel logiciel (site web, outil en ligne de commande, application,
@@ -66,16 +66,16 @@ construction bleu, affinage violet, vérification ambre, prêt vert.
66
66
  Gratuit, au choix :
67
67
 
68
68
  ```sh
69
- npm install -g craftkit # avec Node ; il faut aussi Python 3.9+ sur la machine
70
- pipx install craftkit # ou : uv tool install craftkit
69
+ npm install -g craftkit-cli # avec Node ; il faut aussi Python 3.9+ sur la machine
70
+ pipx install craftkit # ou : uv tool install craftkit
71
71
  ```
72
72
 
73
- Pour l'essayer sans rien installer : `npx craftkit` ou `uvx craftkit`.
73
+ Pour l'essayer sans rien installer : `npx craftkit-cli` ou `uvx craftkit`.
74
74
  Ensuite, le skill pour Claude Code (tous les projets) : `craft skill --install ~/.claude/skills`.
75
75
 
76
76
  Les deux paquets contiennent exactement le même programme Python (aucune dépendance) ; celui de npm
77
77
  le lance avec le Python de la machine (`CRAFT_PYTHON=/chemin/vers/python3` pour en choisir un).
78
- Mise à jour : `npm update -g craftkit`, `pipx upgrade craftkit` ou `uv tool upgrade craftkit`.
78
+ Mise à jour : `npm update -g craftkit-cli`, `pipx upgrade craftkit` ou `uv tool upgrade craftkit`.
79
79
 
80
80
  Depuis les sources : `pipx install .` dans ce dossier, ou `python3 -m craft_kit …` sans installer.
81
81
 
@@ -122,6 +122,7 @@ craft open # ouvrir le résultat
122
122
  | `craft status` | où en est le projet |
123
123
  | `craft answer` | répondre aux questions de l'agent |
124
124
  | `craft brief` | afficher la tâche, pour la coller dans n'importe quel agent |
125
+ | `craft agents` | les agents de code installés, et comment en installer un |
125
126
  | `craft skill [--install DOSSIER]` | afficher ou installer le SKILL.md |
126
127
 
127
128
  Options de `new` : `--kind website|cli|app|game|tool`, `--stack rust|python|react|html|…`,
@@ -149,6 +150,19 @@ propres outils.
149
150
 
150
151
  ## Agents
151
152
 
153
+ `craft` n'écrit pas le code lui-même : il faut un agent de code sur la machine. Sans agent, `craft`
154
+ le dit dès l'écran d'accueil, à `craft new` et au début de `craft run` (avant l'étude), avec les
155
+ commandes exactes ; `craft agents` montre ce qui est trouvé :
156
+
157
+ | Agent | Installer | Puis, une fois |
158
+ |-------|-----------|----------------|
159
+ | Claude Code | `npm install -g @anthropic-ai/claude-code` (ou `curl -fsSL https://claude.ai/install.sh \| bash`) | lancer `claude` et se connecter |
160
+ | Codex | `npm install -g @openai/codex` | lancer `codex` et se connecter |
161
+ | Gemini CLI | `npm install -g @google/gemini-cli` (ou `brew install gemini-cli`) | lancer `gemini` et se connecter (offre gratuite avec un compte Google) |
162
+
163
+ Sans agent du tout : `craft brief` affiche la tâche, à coller dans n'importe quel assistant de code
164
+ (Cursor, Copilot…) ouvert dans le dossier du projet, puis `craft check`.
165
+
152
166
  Ordre par défaut : `claude`, `codex`, `gemini` (le premier installé). Choisir : `--agent codex` ou
153
167
  `CRAFT_AGENT=codex`. La tâche est envoyée sur l'entrée standard (jamais en argument : elle peut être
154
168
  longue). Ajouter ou modifier un agent dans `~/.config/craft/agents.json` :
@@ -3,7 +3,7 @@
3
3
  > **In English:** `craft` rebuilds any software (website, CLI, app, game, library) with the coding
4
4
  > agent you already use (Claude Code, Codex, Gemini CLI): it studies the original, briefs the agent,
5
5
  > checks the result against the original and sends it back until it holds. Install with
6
- > `npm install -g craftkit` or `pipx install craftkit`, then type `craft`.
6
+ > `npm install -g craftkit-cli` or `pipx install craftkit`, then type `craft`.
7
7
 
8
8
  La méthode de Craft Anything, réduite à l'essentiel : **étudier → construire → vérifier → affiner**,
9
9
  pour recréer ou construire n'importe quel logiciel (site web, outil en ligne de commande, application,
@@ -46,16 +46,16 @@ construction bleu, affinage violet, vérification ambre, prêt vert.
46
46
  Gratuit, au choix :
47
47
 
48
48
  ```sh
49
- npm install -g craftkit # avec Node ; il faut aussi Python 3.9+ sur la machine
50
- pipx install craftkit # ou : uv tool install craftkit
49
+ npm install -g craftkit-cli # avec Node ; il faut aussi Python 3.9+ sur la machine
50
+ pipx install craftkit # ou : uv tool install craftkit
51
51
  ```
52
52
 
53
- Pour l'essayer sans rien installer : `npx craftkit` ou `uvx craftkit`.
53
+ Pour l'essayer sans rien installer : `npx craftkit-cli` ou `uvx craftkit`.
54
54
  Ensuite, le skill pour Claude Code (tous les projets) : `craft skill --install ~/.claude/skills`.
55
55
 
56
56
  Les deux paquets contiennent exactement le même programme Python (aucune dépendance) ; celui de npm
57
57
  le lance avec le Python de la machine (`CRAFT_PYTHON=/chemin/vers/python3` pour en choisir un).
58
- Mise à jour : `npm update -g craftkit`, `pipx upgrade craftkit` ou `uv tool upgrade craftkit`.
58
+ Mise à jour : `npm update -g craftkit-cli`, `pipx upgrade craftkit` ou `uv tool upgrade craftkit`.
59
59
 
60
60
  Depuis les sources : `pipx install .` dans ce dossier, ou `python3 -m craft_kit …` sans installer.
61
61
 
@@ -102,6 +102,7 @@ craft open # ouvrir le résultat
102
102
  | `craft status` | où en est le projet |
103
103
  | `craft answer` | répondre aux questions de l'agent |
104
104
  | `craft brief` | afficher la tâche, pour la coller dans n'importe quel agent |
105
+ | `craft agents` | les agents de code installés, et comment en installer un |
105
106
  | `craft skill [--install DOSSIER]` | afficher ou installer le SKILL.md |
106
107
 
107
108
  Options de `new` : `--kind website|cli|app|game|tool`, `--stack rust|python|react|html|…`,
@@ -129,6 +130,19 @@ propres outils.
129
130
 
130
131
  ## Agents
131
132
 
133
+ `craft` n'écrit pas le code lui-même : il faut un agent de code sur la machine. Sans agent, `craft`
134
+ le dit dès l'écran d'accueil, à `craft new` et au début de `craft run` (avant l'étude), avec les
135
+ commandes exactes ; `craft agents` montre ce qui est trouvé :
136
+
137
+ | Agent | Installer | Puis, une fois |
138
+ |-------|-----------|----------------|
139
+ | Claude Code | `npm install -g @anthropic-ai/claude-code` (ou `curl -fsSL https://claude.ai/install.sh \| bash`) | lancer `claude` et se connecter |
140
+ | Codex | `npm install -g @openai/codex` | lancer `codex` et se connecter |
141
+ | Gemini CLI | `npm install -g @google/gemini-cli` (ou `brew install gemini-cli`) | lancer `gemini` et se connecter (offre gratuite avec un compte Google) |
142
+
143
+ Sans agent du tout : `craft brief` affiche la tâche, à coller dans n'importe quel assistant de code
144
+ (Cursor, Copilot…) ouvert dans le dossier du projet, puis `craft check`.
145
+
132
146
  Ordre par défaut : `claude`, `codex`, `gemini` (le premier installé). Choisir : `--agent codex` ou
133
147
  `CRAFT_AGENT=codex`. La tâche est envoyée sur l'entrée standard (jamais en argument : elle peut être
134
148
  longue). Ajouter ou modifier un agent dans `~/.config/craft/agents.json` :
@@ -1,3 +1,3 @@
1
1
  """craft-kit: study → build → check → refine any software, from the terminal."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.3.1"
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
 
6
6
  import json
7
7
  import os
8
+ import sys
8
9
  from typing import Optional
9
10
 
10
11
  from . import proc, tables
@@ -15,19 +16,74 @@ TURN_TIMEOUT = float(os.environ.get("CRAFT_TURN_TIMEOUT", 3 * 3600))
15
16
  IDLE_TIMEOUT = float(os.environ.get("CRAFT_IDLE_TIMEOUT", 20 * 60))
16
17
 
17
18
 
18
- def pick(name: Optional[str]) -> tuple[str, dict]:
19
+ def installed(table: Optional[dict] = None) -> list[str]:
20
+ """The agents whose program is on this computer, in order of preference."""
21
+ table = table or tables.agents()
22
+ return [n for n in tables.AGENT_ORDER if n in table and proc.which(table[n]["argv"][0])]
23
+
24
+
25
+ def guide(ui: UI, names: Optional[list] = None) -> None:
26
+ """How to install and sign in to a coding agent, with the exact commands."""
27
+ table = tables.agents()
28
+ names = [n for n in (names or tables.AGENT_ORDER) if n in table]
29
+ platform = sys.platform if sys.platform in ("darwin", "win32") else "linux"
30
+ ui.say("")
31
+ ui.say(f" craft doesn't write code itself: a coding agent does. {'Install it' if len(names) == 1 else 'Install one of these'}, then sign in once:")
32
+ ui.say("")
33
+ width = max([len(table[n].get("title", n)) for n in names] + [8]) + 3
34
+ for n in names:
35
+ spec = table[n]
36
+ title = spec.get("title", n)
37
+ install = spec.get("install") or f"install `{spec['argv'][0]}` and put it on your PATH"
38
+ ui.say(f" {ui.bold(f'{title:<{width}}')}{install}")
39
+ alt = (spec.get("alt") or {}).get(platform)
40
+ if alt:
41
+ ui.say(f" {' ' * width}{ui.dim('or:')} {alt}")
42
+ if spec.get("login"):
43
+ ui.say(f" {' ' * width}{ui.dim('then ' + spec['login'])}")
44
+ if not proc.which("npm"):
45
+ ui.say("")
46
+ ui.say(f" {ui.yellow('npm')} isn't on this computer: it comes with Node.js ({tables.NODE_URL}).")
47
+ ui.say("")
48
+ ui.say(f" {ui.dim('Then:')} craft run {ui.dim('· see what is found:')} craft agents")
49
+ ui.say(f" {ui.dim('No agent here? craft brief prints the task: paste it into any coding assistant')}")
50
+ ui.say(f" {ui.dim('(Cursor, Copilot, …) opened in the project folder, then run')} craft check{ui.dim('.')}")
51
+
52
+
53
+ def no_agent() -> CraftError:
54
+ return CraftError("No coding agent found on this computer (Claude Code, Codex or Gemini CLI).", guide=guide)
55
+
56
+
57
+ def resolve(remembered: Optional[str], asked: Optional[str]) -> tuple[str, dict, Optional[str]]:
58
+ """The agent for a project: (name, spec, the remembered agent that's gone, if any). Raises a
59
+ CraftError with install steps when none can run."""
60
+ if not asked and not os.environ.get("CRAFT_AGENT") and remembered and remembered not in installed():
61
+ name, spec = pick(None)
62
+ return name, spec, remembered
63
+ name, spec = pick(asked or remembered)
64
+ return name, spec, None
65
+
66
+
67
+ def pick(name: Optional[str], strict: bool = True) -> tuple[str, dict]:
68
+ """The agent to use. An agent asked for by name must be installed; one only remembered from an
69
+ earlier run gives way to another installed agent."""
19
70
  table = tables.agents()
20
- name = name or os.environ.get("CRAFT_AGENT")
21
- if name:
22
- if name == "none":
71
+ explicit = name or os.environ.get("CRAFT_AGENT")
72
+ if explicit:
73
+ if explicit == "none":
23
74
  raise CraftError("No agent: the brief is in BRIEF.md.", "Paste BRIEF.md into any coding agent opened in this folder, then run `craft check`.")
24
- if name not in table:
25
- raise CraftError(f"Unknown agent “{name}”.", f"Known: {', '.join(table)}. Add one in {tables.config_dir() / 'agents.json'}.")
26
- return name, table[name]
27
- for n in tables.AGENT_ORDER:
28
- if n in table and proc.which(table[n]["argv"][0]):
29
- return n, table[n]
30
- raise CraftError("No coding agent found (claude, codex or gemini).", "Install one, or use `craft brief` and paste it into the agent you use, then `craft check`.")
75
+ if explicit not in table:
76
+ raise CraftError(f"Unknown agent “{explicit}”.", f"Known: {', '.join(table)}. Add one in {tables.config_dir() / 'agents.json'}.")
77
+ if proc.which(table[explicit]["argv"][0]) or not strict:
78
+ return explicit, table[explicit]
79
+ title = table[explicit].get("title", explicit)
80
+ others = [n for n in installed(table) if n != explicit]
81
+ hint = f" ({', '.join(others)} is installed: craft run --agent {others[0]})" if others else ""
82
+ raise CraftError(f"{title} isn't installed, or isn't on your PATH{hint}.", guide=lambda ui: guide(ui, [explicit]))
83
+ found = installed(table)
84
+ if found:
85
+ return found[0], table[found[0]]
86
+ raise no_agent()
31
87
 
32
88
 
33
89
  TOOL_KINDS = {
@@ -122,7 +178,10 @@ def _describe_tool(name: str, args: dict, root: str = "") -> str:
122
178
 
123
179
 
124
180
  def turn(p: Project, message: str, ui: UI, agent: Optional[str], label: str) -> str:
125
- name, spec = pick(agent or p.state.get("agent"))
181
+ name, spec, gone = resolve(p.state.get("agent"), agent)
182
+ if gone:
183
+ ui.note(f"{gone} isn't on this computer any more; using {name}.")
184
+ p.state["session"] = None
126
185
  p.state["agent"] = name
127
186
  p.state["turns"] = p.state.get("turns", 0) + 1
128
187
  rd = p.root / "checks" / f"turn-{p.state['turns']}"
@@ -159,7 +218,8 @@ def turn(p: Project, message: str, ui: UI, agent: Optional[str], label: str) ->
159
218
  p.save()
160
219
  if not r.ok or (reader is not None and reader.is_error):
161
220
  why = r.error or (f"exit {r.code}" if r.code else "it reported an error")
162
- raise CraftError(f"{name} stopped: {why}.\n{r.tail(12)}", f"Full output: {rd / 'agent.log'}. Fix the cause, then `craft run` carries on.")
221
+ login = f"If it needs signing in: {spec['login']}.\n" if spec.get("login") else ""
222
+ raise CraftError(f"{name} stopped: {why}.\n{r.tail(12)}", f"{login}Full output: {rd / 'agent.log'}. Fix the cause, then `craft run` carries on.")
163
223
  reply = reader.reply if reader is not None else r.tail(40)
164
224
  write_atomic(rd / "reply.md", reply)
165
225
  return reply
@@ -8,6 +8,7 @@
8
8
  craft answer answer the builder's questions
9
9
  craft brief print the builder's brief (to paste into any agent)
10
10
  craft open open the result
11
+ craft agents which coding agents are installed, and how to get one
11
12
  craft skill [--install DIR] print or install the SKILL.md this tool follows
12
13
  """
13
14
 
@@ -114,6 +115,7 @@ def finish(p: Project, ui: UI, res: Optional[dict], reply: str) -> None:
114
115
 
115
116
  def loop(p: Project, ui: UI, message: Optional[str], agent_name: Optional[str]) -> int:
116
117
  s = p.state
118
+ agent.resolve(s.get("agent"), agent_name) # no agent: say how to get one now, not after the study
117
119
  if message: # a new request resets the refinement budget; retries never do
118
120
  s["rounds_used"] = 0
119
121
  s["unchanged"] = 0
@@ -194,6 +196,32 @@ def cmd_new(a, ui: UI) -> int:
194
196
  ui.header(p.state["name"], f"{kind} · {stack} · {p.mode()}")
195
197
  ui.step("done", "new", str(folder))
196
198
  ui.say(f" {ui.dim('add pictures to')} {folder / 'reference'}{ui.dim(', then:')} cd {folder.name} && craft run")
199
+ if not agent.installed():
200
+ ui.say("")
201
+ ui.warn("No coding agent found yet: `craft run` needs one.")
202
+ agent.guide(ui)
203
+ return 0
204
+
205
+
206
+ def cmd_agents(a, ui: UI) -> int:
207
+ table = tables.agents()
208
+ found = agent.installed(table)
209
+ for n in tables.AGENT_ORDER:
210
+ if n not in table:
211
+ continue
212
+ spec = table[n]
213
+ title = spec.get("title", n)
214
+ where = agent.proc.which(spec["argv"][0])
215
+ if where:
216
+ ui.step("done", n, f"{title} · {where}" + (" ← used by default" if found and n == found[0] else ""), stage="ready")
217
+ else:
218
+ ui.step("todo", n, f"{title} · not installed" + (f": {spec.get('install', 'install ' + spec['argv'][0])}" if found else ""))
219
+ if not found:
220
+ ui.say("")
221
+ ui.warn("No coding agent found.")
222
+ agent.guide(ui)
223
+ else:
224
+ ui.say(f" {ui.dim('pick another with')} craft run --agent NAME {ui.dim('or')} CRAFT_AGENT=NAME")
197
225
  return 0
198
226
 
199
227
 
@@ -336,6 +364,7 @@ def parser() -> argparse.ArgumentParser:
336
364
  ("open", cmd_open, "open the result")):
337
365
  sub.add_parser(name, help=hlp).set_defaults(fn=fn)
338
366
  sub.add_parser("home", help="the home screen: past creations and a new project").set_defaults(fn=cmd_home)
367
+ sub.add_parser("agents", help="which coding agents are installed, and how to get one").set_defaults(fn=cmd_agents)
339
368
  s = sub.add_parser("skill", help="print or install SKILL.md")
340
369
  s.add_argument("--install", metavar="DIR", help="skills folder, e.g. ~/.claude/skills")
341
370
  s.set_defaults(fn=cmd_skill)
@@ -370,9 +399,7 @@ def main(argv: Optional[list] = None) -> int:
370
399
  try:
371
400
  return a.fn(a, ui)
372
401
  except CraftError as e:
373
- ui.error(e.message)
374
- if e.fix:
375
- ui.say(f" {ui.dim(e.fix)}")
402
+ ui.report(e)
376
403
  return 2
377
404
  except KeyboardInterrupt:
378
405
  ui.say("")
@@ -145,6 +145,12 @@ class Home:
145
145
  self.line()
146
146
  if describe:
147
147
  self.describe()
148
+ from . import agent
149
+
150
+ if not agent.installed(): # say it before anyone starts a project
151
+ ui = self.ui
152
+ self.line(f" {ui.yellow('!')} No coding agent found: install Claude Code, Codex or Gemini CLI {ui.dim('(details: craft agents)')}")
153
+ self.line()
148
154
 
149
155
  def describe(self) -> None:
150
156
  """What the tool does, in a few lines, on the home screen."""
@@ -203,9 +209,7 @@ class Home:
203
209
  try:
204
210
  pause = action() is not False
205
211
  except CraftError as e:
206
- self.ui.error(e.message)
207
- if e.fix:
208
- self.ui.say(f" {self.ui.dim(e.fix)}")
212
+ self.ui.report(e)
209
213
  except KeyboardInterrupt:
210
214
  self.ui.say(f"\n {self.ui.dim('stopped')}")
211
215
  if pause:
@@ -357,6 +361,13 @@ class Home:
357
361
  p = Project.create(folder, target, request, kind, stack, deep, allow_run)
358
362
  ui.step("done", "new", f"{folder} · {kind} · {stack} · {p.mode()}")
359
363
  result["path"] = str(folder)
364
+ from . import agent
365
+
366
+ if not agent.installed():
367
+ ui.say("")
368
+ ui.warn("No coding agent found yet: building needs one.")
369
+ agent.guide(ui)
370
+ return True
360
371
  if ui.choose("Start building now?", ["Yes, start", "Later"]).startswith("Yes"):
361
372
  ui.say("")
362
373
  from . import cli
@@ -93,10 +93,28 @@ AGENTS: dict[str, dict] = {
93
93
  "stream": "claude-json",
94
94
  "resume": ["--resume", "{session}"],
95
95
  "lost": "No conversation found",
96
+ "title": "Claude Code",
97
+ "install": "npm install -g @anthropic-ai/claude-code",
98
+ "alt": {"darwin": "curl -fsSL https://claude.ai/install.sh | bash", "linux": "curl -fsSL https://claude.ai/install.sh | bash", "win32": "irm https://claude.ai/install.ps1 | iex (PowerShell)"},
99
+ "login": "run `claude` once and sign in (Claude account or API key)",
100
+ },
101
+ "codex": {
102
+ "argv": ["codex", "exec", "--full-auto", "-"],
103
+ "stream": "text",
104
+ "title": "Codex",
105
+ "install": "npm install -g @openai/codex",
106
+ "login": "run `codex` once and sign in (ChatGPT account or API key)",
107
+ },
108
+ "gemini": {
109
+ "argv": ["gemini", "--yolo"],
110
+ "stream": "text",
111
+ "title": "Gemini CLI",
112
+ "install": "npm install -g @google/gemini-cli",
113
+ "alt": {"darwin": "brew install gemini-cli"},
114
+ "login": "run `gemini` once and sign in (a Google account has a free tier)",
96
115
  },
97
- "codex": {"argv": ["codex", "exec", "--full-auto", "-"], "stream": "text"},
98
- "gemini": {"argv": ["gemini", "--yolo"], "stream": "text"},
99
116
  }
117
+ NODE_URL = "https://nodejs.org" # npm, used by the install commands above, comes with Node.js
100
118
  AGENT_ORDER = ["claude", "codex", "gemini"]
101
119
 
102
120
 
@@ -152,6 +152,18 @@ class UI:
152
152
  def error(self, text: str) -> None:
153
153
  self.say(f" {self.red('✗')} {text}")
154
154
 
155
+ def warn(self, text: str) -> None:
156
+ self.say(f" {self.yellow('!')} {text}")
157
+
158
+ def report(self, e) -> None:
159
+ """A CraftError: what went wrong, then what to do (its guide, or its one-line fix)."""
160
+ self.error(e.message)
161
+ if getattr(e, "guide", None):
162
+ e.guide(self)
163
+ elif getattr(e, "fix", ""):
164
+ for line in e.fix.splitlines():
165
+ self.say(f" {self.dim(line)}")
166
+
155
167
  def live(self, label: str, stage: Optional[str] = None) -> "Live":
156
168
  return Live(self, label, stage)
157
169
 
@@ -35,10 +35,11 @@ IGNORED = {"node_modules", "target", "dist", "out", "build", ".git", "__pycache_
35
35
  class CraftError(Exception):
36
36
  """A failure with a plain-English remediation."""
37
37
 
38
- def __init__(self, message: str, fix: str = ""):
38
+ def __init__(self, message: str, fix: str = "", guide=None):
39
39
  super().__init__(message)
40
40
  self.message = message
41
41
  self.fix = fix
42
+ self.guide = guide # optional callable(ui): a longer, formatted remediation
42
43
 
43
44
 
44
45
  def write_atomic(path: Path, data: str) -> None:
@@ -203,7 +204,7 @@ class Project:
203
204
  raise CraftError(f"{root / STATE} can't be read.", "Fix or delete the file; it is JSON.")
204
205
  v = state.get("version")
205
206
  if not isinstance(v, int) or v > VERSION:
206
- raise CraftError(f"{STATE} says version {v}; this craft reads version {VERSION}.", "Update craft (`pipx upgrade craftkit`, `uv tool upgrade craftkit` or `npm update -g craftkit`).")
207
+ raise CraftError(f"{STATE} says version {v}; this craft reads version {VERSION}.", "Update craft (`pipx upgrade craftkit`, `uv tool upgrade craftkit` or `npm update -g craftkit-cli`).")
207
208
  # migrations go here as versions are added
208
209
  state.setdefault("steps", {s: "todo" for s in STEPS})
209
210
  remember(root)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: craftkit
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Study → build → check → refine any software, from the terminal, with your coding agent.
5
5
  License-Expression: MIT OR Apache-2.0
6
6
  Keywords: cli,coding-agent,claude-code,codex,gemini,rebuild,skill
@@ -23,7 +23,7 @@ Dynamic: license-file
23
23
  > **In English:** `craft` rebuilds any software (website, CLI, app, game, library) with the coding
24
24
  > agent you already use (Claude Code, Codex, Gemini CLI): it studies the original, briefs the agent,
25
25
  > checks the result against the original and sends it back until it holds. Install with
26
- > `npm install -g craftkit` or `pipx install craftkit`, then type `craft`.
26
+ > `npm install -g craftkit-cli` or `pipx install craftkit`, then type `craft`.
27
27
 
28
28
  La méthode de Craft Anything, réduite à l'essentiel : **étudier → construire → vérifier → affiner**,
29
29
  pour recréer ou construire n'importe quel logiciel (site web, outil en ligne de commande, application,
@@ -66,16 +66,16 @@ construction bleu, affinage violet, vérification ambre, prêt vert.
66
66
  Gratuit, au choix :
67
67
 
68
68
  ```sh
69
- npm install -g craftkit # avec Node ; il faut aussi Python 3.9+ sur la machine
70
- pipx install craftkit # ou : uv tool install craftkit
69
+ npm install -g craftkit-cli # avec Node ; il faut aussi Python 3.9+ sur la machine
70
+ pipx install craftkit # ou : uv tool install craftkit
71
71
  ```
72
72
 
73
- Pour l'essayer sans rien installer : `npx craftkit` ou `uvx craftkit`.
73
+ Pour l'essayer sans rien installer : `npx craftkit-cli` ou `uvx craftkit`.
74
74
  Ensuite, le skill pour Claude Code (tous les projets) : `craft skill --install ~/.claude/skills`.
75
75
 
76
76
  Les deux paquets contiennent exactement le même programme Python (aucune dépendance) ; celui de npm
77
77
  le lance avec le Python de la machine (`CRAFT_PYTHON=/chemin/vers/python3` pour en choisir un).
78
- Mise à jour : `npm update -g craftkit`, `pipx upgrade craftkit` ou `uv tool upgrade craftkit`.
78
+ Mise à jour : `npm update -g craftkit-cli`, `pipx upgrade craftkit` ou `uv tool upgrade craftkit`.
79
79
 
80
80
  Depuis les sources : `pipx install .` dans ce dossier, ou `python3 -m craft_kit …` sans installer.
81
81
 
@@ -122,6 +122,7 @@ craft open # ouvrir le résultat
122
122
  | `craft status` | où en est le projet |
123
123
  | `craft answer` | répondre aux questions de l'agent |
124
124
  | `craft brief` | afficher la tâche, pour la coller dans n'importe quel agent |
125
+ | `craft agents` | les agents de code installés, et comment en installer un |
125
126
  | `craft skill [--install DOSSIER]` | afficher ou installer le SKILL.md |
126
127
 
127
128
  Options de `new` : `--kind website|cli|app|game|tool`, `--stack rust|python|react|html|…`,
@@ -149,6 +150,19 @@ propres outils.
149
150
 
150
151
  ## Agents
151
152
 
153
+ `craft` n'écrit pas le code lui-même : il faut un agent de code sur la machine. Sans agent, `craft`
154
+ le dit dès l'écran d'accueil, à `craft new` et au début de `craft run` (avant l'étude), avec les
155
+ commandes exactes ; `craft agents` montre ce qui est trouvé :
156
+
157
+ | Agent | Installer | Puis, une fois |
158
+ |-------|-----------|----------------|
159
+ | Claude Code | `npm install -g @anthropic-ai/claude-code` (ou `curl -fsSL https://claude.ai/install.sh \| bash`) | lancer `claude` et se connecter |
160
+ | Codex | `npm install -g @openai/codex` | lancer `codex` et se connecter |
161
+ | Gemini CLI | `npm install -g @google/gemini-cli` (ou `brew install gemini-cli`) | lancer `gemini` et se connecter (offre gratuite avec un compte Google) |
162
+
163
+ Sans agent du tout : `craft brief` affiche la tâche, à coller dans n'importe quel assistant de code
164
+ (Cursor, Copilot…) ouvert dans le dossier du projet, puis `craft check`.
165
+
152
166
  Ordre par défaut : `claude`, `codex`, `gemini` (le premier installé). Choisir : `--agent codex` ou
153
167
  `CRAFT_AGENT=codex`. La tâche est envoyée sur l'entrée standard (jamais en argument : elle peut être
154
168
  longue). Ajouter ou modifier un agent dans `~/.config/craft/agents.json` :
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "craftkit"
7
- version = "0.3.0"
7
+ version = "0.3.1"
8
8
  description = "Study → build → check → refine any software, from the terminal, with your coding agent."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes