stratyfy-cli 0.2.0__tar.gz → 0.2.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 (74) hide show
  1. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/PKG-INFO +8 -5
  2. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/README.md +7 -4
  3. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_cli/main.py +18 -8
  4. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_cli/sandbox.py +3 -2
  5. stratyfy_cli-0.2.1/pre_cli/skill.py +51 -0
  6. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pyproject.toml +1 -1
  7. stratyfy_cli-0.2.0/pre_cli/skill.py +0 -26
  8. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/hatch_build.py +0 -0
  9. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/__init__.py +0 -0
  10. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/__init__.py +0 -0
  11. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/__init__.py +0 -0
  12. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/create_dataset.py +0 -0
  13. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/create_model_version.py +0 -0
  14. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/get_dataset.py +0 -0
  15. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/get_device_token.py +0 -0
  16. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/get_model.py +0 -0
  17. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/get_sample_database.py +0 -0
  18. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/get_workspace.py +0 -0
  19. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/list_datasets.py +0 -0
  20. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/list_models.py +0 -0
  21. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/list_rules.py +0 -0
  22. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/list_samples.py +0 -0
  23. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/predict.py +0 -0
  24. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/revoke_current_key.py +0 -0
  25. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/start_device_login.py +0 -0
  26. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/api/default/train_model.py +0 -0
  27. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/client.py +0 -0
  28. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/errors.py +0 -0
  29. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/__init__.py +0 -0
  30. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/clause.py +0 -0
  31. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/clause_operator.py +0 -0
  32. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/column.py +0 -0
  33. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/column_domain.py +0 -0
  34. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/create_dataset_body.py +0 -0
  35. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/create_model_version_body.py +0 -0
  36. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/dataset.py +0 -0
  37. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/device_code.py +0 -0
  38. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/device_token.py +0 -0
  39. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/device_token_token_type.py +0 -0
  40. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/error.py +0 -0
  41. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/error_code.py +0 -0
  42. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/get_device_token_body.py +0 -0
  43. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/list_datasets_response_200.py +0 -0
  44. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/list_models_response_200.py +0 -0
  45. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/list_rules_response_200.py +0 -0
  46. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/list_samples_response_200.py +0 -0
  47. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/metrics.py +0 -0
  48. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/metrics_calibration_item.py +0 -0
  49. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/metrics_lift_item.py +0 -0
  50. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/model.py +0 -0
  51. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/model_status.py +0 -0
  52. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/predict_body.py +0 -0
  53. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/predict_body_rows_item.py +0 -0
  54. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/predict_response_200.py +0 -0
  55. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/prediction.py +0 -0
  56. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/prediction_fired_rules_item.py +0 -0
  57. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/rule.py +0 -0
  58. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/rule_change.py +0 -0
  59. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/rule_change_action.py +0 -0
  60. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/rule_input.py +0 -0
  61. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/sample.py +0 -0
  62. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/train_request.py +0 -0
  63. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/models/workspace.py +0 -0
  64. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_api_client/types.py +0 -0
  65. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_cli/__init__.py +0 -0
  66. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_cli/config.py +0 -0
  67. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_cli/data.py +0 -0
  68. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_cli/schema.py +0 -0
  69. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_cli/skills/onboard/SKILL.md +0 -0
  70. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_report/__init__.py +0 -0
  71. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_report/render.py +0 -0
  72. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_report/template.html +0 -0
  73. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_report/types.py +0 -0
  74. {stratyfy_cli-0.2.0 → stratyfy_cli-0.2.1}/pre_words/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stratyfy-cli
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: The pre command: describe a database, train a PRE model, read its rules
5
5
  Requires-Python: <4,>=3.11
6
6
  Requires-Dist: attrs>=23
@@ -44,7 +44,7 @@ failure the command exits with 1 and prints `code`, `message` and `next_step`.
44
44
  | `pre guide` | Prints the method a coding agent follows, from set-up to the report. It is the plugin's `onboard` skill, bundled in the CLI. |
45
45
  | `pre init --api-key <key> [--api-url <url>]` | Writes a key you already have to `.pre/config.json`. The key can also come from `PRE_API_KEY`. |
46
46
  | `pre connect <db>` | Describes a SQLite or Postgres database: tables, row counts, columns, types, null share, sample values and foreign keys. It opens the database read-only. |
47
- | `pre setup claude [--yes] [--dry-run] [--local]` | For Claude Code. Run it in your own terminal. It adds the API host and a remote Postgres host to Claude Code's sandbox network allowlist. It shows the change and asks first. It refuses to write when an agent runs it. See [Sandboxes](#sandboxes). |
47
+ | `pre setup claude [--yes] [--dry-run] [--local]` | For Claude Code. Run it in your own terminal. It writes the PRE skill to `.claude/skills/pre-onboard/`, and adds the API host and a remote Postgres host to Claude Code's sandbox network allowlist. It shows the change and asks first. It refuses to write when an agent runs it. See [Sandboxes](#sandboxes). |
48
48
  | `pre status` | Checks that the API answers and accepts the key, and names the workspace. Lists the datasets and models this folder made. |
49
49
  | `pre outcome define --name <slug> --sql <query> [--case account --cases accounts --event churn --event-past churned]` | Runs the outcome query and stores it. The query returns one row per case: the case id, then a 0/1 label. It refuses no rows, a label that is not 0 or 1, a repeated case id and a label that is always the same. The words go into every sentence the CLI prints. It guesses each word you leave out from the column names and says so. |
50
50
  | `pre outcome show` | Lists the stored outcomes and their queries. |
@@ -83,10 +83,13 @@ other column. It maps the hashes back to the case ids in memory.
83
83
 
84
84
  `pre setup` writes the smallest change it can. It merges into the file, so a second run changes nothing.
85
85
 
86
- It writes `.claude/settings.json`, key `sandbox.network.allowedDomains`. For a Postgres on this machine, `--local`
86
+ It writes `.claude/settings.local.json`, key `sandbox.network.allowedDomains`. That is Claude Code's personal
87
+ settings file; the team's `.claude/settings.json` stays as it is. Do not commit it. For a Postgres on this machine, `--local`
87
88
  also sets `sandbox.network.allowLocalBinding`, which opens every local port. It never sets `allowAllUnixSockets`.
88
- Claude Code is the only agent at launch; others are on the roadmap. The onboard skill comes with the plugin
89
- (`tools/agent-plugin`), not with `pre setup`.
89
+ Claude Code is the only agent at launch; others are on the roadmap. It also writes the onboard skill to
90
+ `.claude/skills/pre-onboard/SKILL.md`. It records the copy's SHA-256 in `.pre/config.json`, and later replaces only
91
+ that copy: a skill you edited stays as it is. Commit the skill to share it with the team. Its source is
92
+ `tools/agent-plugin`.
90
93
 
91
94
  These key names come from secondary sources. They live in `pre_cli/sandbox.py`.
92
95
 
@@ -31,7 +31,7 @@ failure the command exits with 1 and prints `code`, `message` and `next_step`.
31
31
  | `pre guide` | Prints the method a coding agent follows, from set-up to the report. It is the plugin's `onboard` skill, bundled in the CLI. |
32
32
  | `pre init --api-key <key> [--api-url <url>]` | Writes a key you already have to `.pre/config.json`. The key can also come from `PRE_API_KEY`. |
33
33
  | `pre connect <db>` | Describes a SQLite or Postgres database: tables, row counts, columns, types, null share, sample values and foreign keys. It opens the database read-only. |
34
- | `pre setup claude [--yes] [--dry-run] [--local]` | For Claude Code. Run it in your own terminal. It adds the API host and a remote Postgres host to Claude Code's sandbox network allowlist. It shows the change and asks first. It refuses to write when an agent runs it. See [Sandboxes](#sandboxes). |
34
+ | `pre setup claude [--yes] [--dry-run] [--local]` | For Claude Code. Run it in your own terminal. It writes the PRE skill to `.claude/skills/pre-onboard/`, and adds the API host and a remote Postgres host to Claude Code's sandbox network allowlist. It shows the change and asks first. It refuses to write when an agent runs it. See [Sandboxes](#sandboxes). |
35
35
  | `pre status` | Checks that the API answers and accepts the key, and names the workspace. Lists the datasets and models this folder made. |
36
36
  | `pre outcome define --name <slug> --sql <query> [--case account --cases accounts --event churn --event-past churned]` | Runs the outcome query and stores it. The query returns one row per case: the case id, then a 0/1 label. It refuses no rows, a label that is not 0 or 1, a repeated case id and a label that is always the same. The words go into every sentence the CLI prints. It guesses each word you leave out from the column names and says so. |
37
37
  | `pre outcome show` | Lists the stored outcomes and their queries. |
@@ -70,10 +70,13 @@ other column. It maps the hashes back to the case ids in memory.
70
70
 
71
71
  `pre setup` writes the smallest change it can. It merges into the file, so a second run changes nothing.
72
72
 
73
- It writes `.claude/settings.json`, key `sandbox.network.allowedDomains`. For a Postgres on this machine, `--local`
73
+ It writes `.claude/settings.local.json`, key `sandbox.network.allowedDomains`. That is Claude Code's personal
74
+ settings file; the team's `.claude/settings.json` stays as it is. Do not commit it. For a Postgres on this machine, `--local`
74
75
  also sets `sandbox.network.allowLocalBinding`, which opens every local port. It never sets `allowAllUnixSockets`.
75
- Claude Code is the only agent at launch; others are on the roadmap. The onboard skill comes with the plugin
76
- (`tools/agent-plugin`), not with `pre setup`.
76
+ Claude Code is the only agent at launch; others are on the roadmap. It also writes the onboard skill to
77
+ `.claude/skills/pre-onboard/SKILL.md`. It records the copy's SHA-256 in `.pre/config.json`, and later replaces only
78
+ that copy: a skill you edited stays as it is. Commit the skill to share it with the team. Its source is
79
+ `tools/agent-plugin`.
77
80
 
78
81
  These key names come from secondary sources. They live in `pre_cli/sandbox.py`.
79
82
 
@@ -250,13 +250,14 @@ def try_sample(
250
250
  with gzip.open(file.payload) as src, part.open("wb") as dst:
251
251
  shutil.copyfileobj(src, dst)
252
252
  part.rename(db)
253
- keep = {k: cfg[k] for k in ("api_url", "api_key", "key_id") if k in cfg}
254
- config.save(config.load(folder / ".pre") | keep, folder / ".pre")
253
+ target = config.load(folder / ".pre") | {k: cfg[k] for k in ("api_url", "api_key", "key_id") if k in cfg}
254
+ if skill.status(folder, target.get("skill_sha256")) in ("missing", "older"):
255
+ target["skill_sha256"] = skill.install(folder)
256
+ config.save(target, folder / ".pre")
255
257
  words = {"name": name, "product": sample.product, "goal": sample.goal, "skill": skill.NAME}
256
258
  for file, template in (("README.md", SAMPLE_README), ("CLAUDE.md", SAMPLE_CLAUDE)):
257
259
  if not (path := folder / file).exists():
258
- path.write_text(template.format(**words))
259
- skill.install(folder)
260
+ path.write_text(template.format(**words), encoding="utf-8")
260
261
  step = f'Run `cd {folder}` and start Claude Code there. Then ask: "{sample.goal}"'
261
262
  result = {"folder": str(folder), "database": str(db), "product": sample.product, "goal": sample.goal}
262
263
  return result | {"next_step": step}, f"Wrote {db}. {sample.product}"
@@ -367,7 +368,7 @@ def setup(
367
368
  local: Annotated[bool, typer.Option("--local", help="Open local ports for a local Postgres.")] = False,
368
369
  json_out: JsonOut = False,
369
370
  ):
370
- """Let a coding agent's sandbox reach the PRE API and the database."""
371
+ """Set up a coding agent in this folder: the PRE skill, and a sandbox that reaches the API and the database."""
371
372
  again = f"pre setup {agent}"
372
373
  if agent not in sandbox.AGENTS:
373
374
  raise CliError("bad_agent", f"`{again}` is not supported.", "Run `pre setup claude`.")
@@ -380,15 +381,24 @@ def setup(
380
381
  ask = not (yes or json_out or dry_run)
381
382
  if ask and plan["offer_local"] and typer.confirm("Also open every local port, so the sandbox reaches Postgres?"):
382
383
  plan = sandbox.plan(*args, True)
383
- result, text = {k: v for k, v in plan.items() if k[0] != "_"} | {"written": False}, sandbox.describe(plan)
384
+ found = skill.status(Path.cwd(), cfg.get("skill_sha256"))
385
+ new_skill = str(skill.path(Path.cwd())) if found in ("missing", "older") else None
386
+ result = {k: v for k, v in plan.items() if k[0] != "_"} | {"skill": new_skill, "written": False}
387
+ text = sandbox.describe(plan) + (f"\nWrite the PRE skill to {new_skill}." if new_skill else "")
388
+ if found == "edited":
389
+ keep = f"{skill.path(Path.cwd())} differs from what pre wrote, so pre left it. Delete it to get the new one."
390
+ result["advice"], text = [*result["advice"], keep], f"{text}\n{keep}"
384
391
  restart = f"Restart {agent} in this folder, then ask it to run `pre status`."
385
- if not plan["add"]:
392
+ if not plan["add"] and not new_skill:
386
393
  return result | {"next_step": restart}, text
387
394
  if dry_run or not (yes or ask):
388
395
  return result | {"next_step": f"Run `{again} --yes` in your own terminal to write it."}, text
389
396
  if ask:
390
397
  typer.confirm(f"{text}\nWrite this?", abort=True)
391
- sandbox.apply(plan)
398
+ if plan["add"]:
399
+ sandbox.apply(plan)
400
+ if new_skill:
401
+ config.save(cfg | {"skill_sha256": skill.install(Path.cwd())})
392
402
  return result | {"written": True, "next_step": restart}, text if yes else "Written."
393
403
 
394
404
 
@@ -1,4 +1,4 @@
1
- """pre setup: let Claude Code's sandbox reach the PRE API and the database. The plugin brings the skill."""
1
+ """pre setup: let Claude Code's sandbox reach the PRE API and the database. main.setup adds the skill."""
2
2
 
3
3
  import json
4
4
  import os
@@ -7,7 +7,8 @@ from urllib.parse import urlsplit
7
7
 
8
8
  # UNCONFIRMED. These names come from secondary sources, not Claude Code's own docs. Correct them here.
9
9
  # Other agents are on the roadmap (PLAN.md); each gets an entry here.
10
- AGENTS = {"claude": {"config": ".claude/settings.json", "env": ["CLAUDECODE"]}}
10
+ # settings.local.json is Claude Code's personal file. A sandbox allowance belongs to one machine, not the team.
11
+ AGENTS = {"claude": {"config": ".claude/settings.local.json", "env": ["CLAUDECODE"]}}
11
12
  CLAUDE_DOMAINS = "sandbox.network.allowedDomains"
12
13
  CLAUDE_LOCAL_BINDING = "sandbox.network.allowLocalBinding"
13
14
  CLAUDE_SOCKETS = "sandbox.network.allowAllUnixSockets"
@@ -0,0 +1,51 @@
1
+ """The onboarding skill: the method a coding agent follows with pre. Its one source is tools/agent-plugin. The
2
+ wheel bundles it as pre_cli/skills/onboard/SKILL.md (hatch_build.py). In the repo, it is read from its source."""
3
+
4
+ import hashlib
5
+ from pathlib import Path
6
+
7
+ HERE = Path(__file__).resolve().parent
8
+ PATHS = (HERE / "skills" / "onboard" / "SKILL.md", HERE.parents[1] / "agent-plugin" / "skills" / "onboard" / "SKILL.md")
9
+ NAME = "pre-onboard" # in a project's .claude/skills, where a bare "onboard" could clash
10
+
11
+
12
+ def text() -> str:
13
+ """SKILL.md with its front matter."""
14
+ return next(found for found in PATHS if found.exists()).read_text(encoding="utf-8")
15
+
16
+
17
+ def body() -> str:
18
+ """SKILL.md without its front matter, to print."""
19
+ return text().split("\n---\n", 1)[1].lstrip()
20
+
21
+
22
+ def path(project: Path) -> Path:
23
+ """Where Claude Code finds the skill in a project."""
24
+ return project / ".claude" / "skills" / NAME / "SKILL.md"
25
+
26
+
27
+ def project_text() -> str:
28
+ return text().replace("\nname: onboard\n", f"\nname: {NAME}\n", 1)
29
+
30
+
31
+ def digest(content: str) -> str:
32
+ return hashlib.sha256(content.encode("utf-8")).hexdigest()
33
+
34
+
35
+ def status(project: Path, written: str | None) -> str:
36
+ """ "current", "missing", "older" (a copy pre wrote, unchanged since), or "edited" (anything else).
37
+
38
+ `written` is the digest of the copy pre last wrote here, from .pre/config.json.
39
+ """
40
+ if not path(project).exists():
41
+ return "missing"
42
+ found = digest(path(project).read_text(encoding="utf-8"))
43
+ return "current" if found == digest(project_text()) else "older" if found == written else "edited"
44
+
45
+
46
+ def install(project: Path) -> str:
47
+ """Write the skill to the project's .claude/skills, where Claude Code finds it. Returns its digest."""
48
+ target = path(project)
49
+ target.parent.mkdir(parents=True, exist_ok=True)
50
+ target.write_text(project_text(), encoding="utf-8")
51
+ return digest(project_text())
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stratyfy-cli"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "The pre command: describe a database, train a PRE model, read its rules"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11,<4"
@@ -1,26 +0,0 @@
1
- """The onboarding skill: the method a coding agent follows with pre. Its one source is tools/agent-plugin. The
2
- wheel bundles it as pre_cli/skills/onboard/SKILL.md (hatch_build.py). In the repo, it is read from its source."""
3
-
4
- from pathlib import Path
5
-
6
- HERE = Path(__file__).resolve().parent
7
- PATHS = (HERE / "skills" / "onboard" / "SKILL.md", HERE.parents[1] / "agent-plugin" / "skills" / "onboard" / "SKILL.md")
8
- NAME = "pre-onboard" # in a project's .claude/skills, where a bare "onboard" could clash
9
-
10
-
11
- def text() -> str:
12
- """SKILL.md with its front matter."""
13
- return next(path for path in PATHS if path.exists()).read_text()
14
-
15
-
16
- def body() -> str:
17
- """SKILL.md without its front matter, to print."""
18
- return text().split("\n---\n", 1)[1].lstrip()
19
-
20
-
21
- def install(project: Path) -> Path:
22
- """Write the skill to the project's .claude/skills, where Claude Code finds it."""
23
- path = project / ".claude" / "skills" / NAME / "SKILL.md"
24
- path.parent.mkdir(parents=True, exist_ok=True)
25
- path.write_text(text().replace("\nname: onboard\n", f"\nname: {NAME}\n", 1))
26
- return path