Compile a new skill from this KB's wiki. NAME is a kebab-case slug used for the output directory and skill name. INTENT is a natural-language description of what this skill should do. Example: openkb skill new karpathy-thinking "Reason about transformers like Karpathy"
(ctx, name, intent, yes_flag)
| 2309 | ) |
| 2310 | @click.pass_context |
| 2311 | def skill_new(ctx, name, intent, yes_flag): |
| 2312 | """Compile a new skill from this KB's wiki. |
| 2313 | |
| 2314 | NAME is a kebab-case slug used for the output directory and skill name. |
| 2315 | INTENT is a natural-language description of what this skill should do. |
| 2316 | |
| 2317 | Example: |
| 2318 | |
| 2319 | openkb skill new karpathy-thinking "Reason about transformers like Karpathy" |
| 2320 | """ |
| 2321 | kb_dir = _find_kb_dir(ctx.obj.get("kb_dir_override")) |
| 2322 | if kb_dir is None: |
| 2323 | click.echo("No knowledge base found. Run `openkb init` first.", err=True) |
| 2324 | ctx.exit(1) |
| 2325 | |
| 2326 | err = _preflight_skill_new(kb_dir, name) |
| 2327 | if err: |
| 2328 | click.echo(f"[ERROR] {err}", err=True) |
| 2329 | ctx.exit(1) |
| 2330 | |
| 2331 | # Verify LLM key + load config BEFORE touching existing output. Any |
| 2332 | # failure here (missing API key, malformed config) must leave the old |
| 2333 | # skill directory intact — we can't replace it if we can't proceed. |
| 2334 | try: |
| 2335 | _setup_llm_key(kb_dir) |
| 2336 | except RuntimeError as exc: |
| 2337 | click.echo(f"[ERROR] {exc}", err=True) |
| 2338 | ctx.exit(1) |
| 2339 | config = load_config(kb_dir / ".openkb" / "config.yaml") |
| 2340 | model = config.get("model", DEFAULT_CONFIG["model"]) |
| 2341 | |
| 2342 | # Overwrite handling (CLI-specific). Done AFTER key/config so a |
| 2343 | # missing key doesn't wipe the user's existing skill output. |
| 2344 | # |
| 2345 | # When overwriting, we don't destroy the old skill — we copy it |
| 2346 | # into <kb>/output/skills/<name>-workspace/iteration-N/ first, so |
| 2347 | # the user can roll back via `openkb skill rollback`. See |
| 2348 | # ``openkb/skill/workspace.py``. |
| 2349 | from openkb.skill import skill_dir |
| 2350 | from openkb.skill.workspace import save_iteration, write_diff |
| 2351 | |
| 2352 | target = skill_dir(kb_dir, name) |
| 2353 | saved_iteration: Path | None = None |
| 2354 | if target.exists(): |
| 2355 | if yes_flag: |
| 2356 | saved_iteration = save_iteration(kb_dir, name) |
| 2357 | _clear_existing_skill_dir(kb_dir, name) |
| 2358 | elif sys.stdin.isatty(): |
| 2359 | if not click.confirm( |
| 2360 | f"output/skills/{name}/ already exists. Overwrite?", |
| 2361 | default=False, |
| 2362 | ): |
| 2363 | click.echo("Aborted.") |
| 2364 | ctx.exit(1) |
| 2365 | saved_iteration = save_iteration(kb_dir, name) |
| 2366 | _clear_existing_skill_dir(kb_dir, name) |
| 2367 | else: |
| 2368 | click.echo( |
nothing calls this directly
no test coverage detected