familiar-cli 0.0.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 (30) hide show
  1. familiar_cli-0.0.1/LICENSE +21 -0
  2. familiar_cli-0.0.1/PKG-INFO +80 -0
  3. familiar_cli-0.0.1/pyproject.toml +48 -0
  4. familiar_cli-0.0.1/readme.md +52 -0
  5. familiar_cli-0.0.1/setup.cfg +4 -0
  6. familiar_cli-0.0.1/src/familiar/__init__.py +9 -0
  7. familiar_cli-0.0.1/src/familiar/agents.py +56 -0
  8. familiar_cli-0.0.1/src/familiar/cli.py +123 -0
  9. familiar_cli-0.0.1/src/familiar/data/invocations/__noop__.md +1 -0
  10. familiar_cli-0.0.1/src/familiar/data/invocations/add-tests.md +26 -0
  11. familiar_cli-0.0.1/src/familiar/data/invocations/bootstrap-python.md +20 -0
  12. familiar_cli-0.0.1/src/familiar/data/invocations/bootstrap-rust.md +28 -0
  13. familiar_cli-0.0.1/src/familiar/data/invocations/code-review.md +10 -0
  14. familiar_cli-0.0.1/src/familiar/data/invocations/explain.md +9 -0
  15. familiar_cli-0.0.1/src/familiar/data/invocations/implement-feature.md +26 -0
  16. familiar_cli-0.0.1/src/familiar/data/invocations/infra-change.md +25 -0
  17. familiar_cli-0.0.1/src/familiar/data/invocations/refactor.md +14 -0
  18. familiar_cli-0.0.1/src/familiar/data/invocations/security-review.md +28 -0
  19. familiar_cli-0.0.1/src/familiar/data/templates/core.md +25 -0
  20. familiar_cli-0.0.1/src/familiar/data/templates/infra.md +23 -0
  21. familiar_cli-0.0.1/src/familiar/data/templates/python.md +24 -0
  22. familiar_cli-0.0.1/src/familiar/data/templates/rust.md +24 -0
  23. familiar_cli-0.0.1/src/familiar/data/templates/sec.md +24 -0
  24. familiar_cli-0.0.1/src/familiar/render.py +69 -0
  25. familiar_cli-0.0.1/src/familiar_cli.egg-info/PKG-INFO +80 -0
  26. familiar_cli-0.0.1/src/familiar_cli.egg-info/SOURCES.txt +28 -0
  27. familiar_cli-0.0.1/src/familiar_cli.egg-info/dependency_links.txt +1 -0
  28. familiar_cli-0.0.1/src/familiar_cli.egg-info/entry_points.txt +2 -0
  29. familiar_cli-0.0.1/src/familiar_cli.egg-info/requires.txt +5 -0
  30. familiar_cli-0.0.1/src/familiar_cli.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cyberwitchery labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: familiar-cli
3
+ Version: 0.0.1
4
+ Summary: compose and invoke ai agent prompts from reusable templates
5
+ Author-email: cyberwitchery labs <contact@cyberwitchery.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/cyberwitchery/familiar
8
+ Project-URL: Repository, https://github.com/cyberwitchery/familiar
9
+ Keywords: ai,agents,prompts,cli,codex,claude
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Code Generators
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: dev
24
+ Requires-Dist: ruff; extra == "dev"
25
+ Requires-Dist: mypy; extra == "dev"
26
+ Requires-Dist: pytest; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # familiar
30
+
31
+ compose and invoke ai agent prompts from reusable templates.
32
+
33
+ ships with a standard set of templates and invocations, or bring your own (mine are
34
+ very much wip).
35
+
36
+ ## installation
37
+
38
+ ```
39
+ pip install familiar-cli
40
+ ```
41
+
42
+ ## usage
43
+
44
+ conjure profiles to create system instructions for an agent:
45
+
46
+ ```
47
+ familiar conjure codex rust infra sec
48
+ ```
49
+
50
+ invoke an action prompt:
51
+
52
+ ```
53
+ familiar invoke codex bootstrap-rust myapp lib 1.78 mit
54
+ ```
55
+
56
+ ## customization
57
+
58
+ add your own templates and invocations by creating files in `.familiar/` in your repo:
59
+
60
+ ```
61
+ .familiar/
62
+ templates/
63
+ myprofile.md # new profile
64
+ rust.md # override built-in
65
+ invocations/
66
+ my-task.md # new invocation
67
+ ```
68
+
69
+ local files take precedence over built-ins.
70
+
71
+ ### placeholders
72
+
73
+ invocations support placeholders:
74
+ - `$1`, `$2`, ... - positional arguments
75
+ - `$ARGUMENTS` - all positional arguments joined
76
+ - `{{key}}` - named arguments passed via `--kv key=value`
77
+
78
+ ## license
79
+
80
+ mit
@@ -0,0 +1,48 @@
1
+ [project]
2
+ name = "familiar-cli"
3
+ version = "0.0.1"
4
+ description = "compose and invoke ai agent prompts from reusable templates"
5
+ readme = "readme.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.9"
8
+ authors = [
9
+ { name = "cyberwitchery labs", email = "contact@cyberwitchery.com" }
10
+ ]
11
+ keywords = ["ai", "agents", "prompts", "cli", "codex", "claude"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Environment :: Console",
15
+ "Intended Audience :: Developers",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Software Development :: Code Generators",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/cyberwitchery/familiar"
27
+ Repository = "https://github.com/cyberwitchery/familiar"
28
+
29
+ [project.scripts]
30
+ familiar = "familiar.cli:main"
31
+
32
+ [tool.setuptools]
33
+ package-dir = {"" = "src"}
34
+
35
+ [project.optional-dependencies]
36
+ dev = ["ruff", "mypy", "pytest"]
37
+
38
+ [tool.setuptools.package-data]
39
+ "familiar.data.templates" = ["*.md"]
40
+ "familiar.data.invocations" = ["*.md"]
41
+
42
+ [tool.ruff]
43
+ target-version = "py39"
44
+
45
+ [tool.mypy]
46
+ python_version = "3.9"
47
+ warn_return_any = true
48
+ warn_unused_configs = true
@@ -0,0 +1,52 @@
1
+ # familiar
2
+
3
+ compose and invoke ai agent prompts from reusable templates.
4
+
5
+ ships with a standard set of templates and invocations, or bring your own (mine are
6
+ very much wip).
7
+
8
+ ## installation
9
+
10
+ ```
11
+ pip install familiar-cli
12
+ ```
13
+
14
+ ## usage
15
+
16
+ conjure profiles to create system instructions for an agent:
17
+
18
+ ```
19
+ familiar conjure codex rust infra sec
20
+ ```
21
+
22
+ invoke an action prompt:
23
+
24
+ ```
25
+ familiar invoke codex bootstrap-rust myapp lib 1.78 mit
26
+ ```
27
+
28
+ ## customization
29
+
30
+ add your own templates and invocations by creating files in `.familiar/` in your repo:
31
+
32
+ ```
33
+ .familiar/
34
+ templates/
35
+ myprofile.md # new profile
36
+ rust.md # override built-in
37
+ invocations/
38
+ my-task.md # new invocation
39
+ ```
40
+
41
+ local files take precedence over built-ins.
42
+
43
+ ### placeholders
44
+
45
+ invocations support placeholders:
46
+ - `$1`, `$2`, ... - positional arguments
47
+ - `$ARGUMENTS` - all positional arguments joined
48
+ - `{{key}}` - named arguments passed via `--kv key=value`
49
+
50
+ ## license
51
+
52
+ mit
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,9 @@
1
+ """familiar - compose and invoke ai agent prompts."""
2
+ from importlib.metadata import version, PackageNotFoundError
3
+
4
+ __all__ = ["agents", "render", "cli"]
5
+
6
+ try:
7
+ __version__ = version("familiar-cli")
8
+ except PackageNotFoundError:
9
+ __version__ = "0.0.0-dev"
@@ -0,0 +1,56 @@
1
+ """Agent implementations for familiar."""
2
+ from __future__ import annotations
3
+
4
+ import subprocess
5
+ from abc import ABC, abstractmethod
6
+ from pathlib import Path
7
+
8
+
9
+ class Agent(ABC):
10
+ """Base class for AI coding agents."""
11
+
12
+ name: str
13
+ output_file: str
14
+
15
+ @abstractmethod
16
+ def run(self, repo_root: Path, prompt: str, headless: bool) -> int:
17
+ """Run the agent with the given prompt."""
18
+
19
+
20
+ class CodexAgent(Agent):
21
+ name = "codex"
22
+ output_file = "AGENTS.md"
23
+
24
+ def run(self, repo_root: Path, prompt: str, headless: bool) -> int:
25
+ if headless:
26
+ cmd = ["codex", "exec", "-C", str(repo_root), "-"]
27
+ proc = subprocess.run(cmd, input=prompt, text=True)
28
+ return proc.returncode
29
+ else:
30
+ cmd = ["codex", "-C", str(repo_root), prompt]
31
+ return subprocess.call(cmd)
32
+
33
+
34
+ class ClaudeAgent(Agent):
35
+ name = "claude"
36
+ output_file = "CLAUDE.md"
37
+
38
+ def run(self, repo_root: Path, prompt: str, headless: bool) -> int:
39
+ if headless:
40
+ cmd = ["claude", "-p", prompt]
41
+ else:
42
+ cmd = ["claude", prompt]
43
+ return subprocess.call(cmd)
44
+
45
+
46
+ AGENTS: dict[str, Agent] = {
47
+ "codex": CodexAgent(),
48
+ "claude": ClaudeAgent(),
49
+ }
50
+
51
+
52
+ def get_agent(name: str) -> Agent:
53
+ """Get an agent by name."""
54
+ if name not in AGENTS:
55
+ raise SystemExit(f"unknown agent: {name}")
56
+ return AGENTS[name]
@@ -0,0 +1,123 @@
1
+ """Command-line interface for familiar."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ import os
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from .agents import AGENTS, get_agent
12
+ from .render import compose, NotFoundError
13
+
14
+
15
+ def find_repo_root(start: Path) -> Path:
16
+ cur = start.resolve()
17
+ for p in [cur] + list(cur.parents):
18
+ if (p / ".git").exists():
19
+ return p
20
+ return cur
21
+
22
+
23
+ def ensure_fam_dir(repo_root: Path) -> Path:
24
+ d = repo_root / ".familiar"
25
+ d.mkdir(parents=True, exist_ok=True)
26
+ return d
27
+
28
+
29
+ def config_path(repo_root: Path, agent_name: str) -> Path:
30
+ return ensure_fam_dir(repo_root) / f"{agent_name}.json"
31
+
32
+
33
+ def load_config(repo_root: Path, agent_name: str) -> dict[str, Any]:
34
+ p = config_path(repo_root, agent_name)
35
+ if p.exists():
36
+ cfg: dict[str, Any] = json.loads(p.read_text(encoding="utf-8"))
37
+ return cfg
38
+ return {}
39
+
40
+
41
+ def save_config(repo_root: Path, agent_name: str, cfg: dict[str, Any]) -> None:
42
+ p = config_path(repo_root, agent_name)
43
+ p.write_text(json.dumps(cfg, indent=2, sort_keys=True) + "\n", encoding="utf-8")
44
+
45
+
46
+ def write_instruction(repo_root: Path, agent_name: str, system: str) -> None:
47
+ agent = get_agent(agent_name)
48
+ (repo_root / agent.output_file).write_text(system.strip() + "\n", encoding="utf-8")
49
+
50
+
51
+ def parse_kv(pairs: list[str]) -> dict[str, str]:
52
+ out: dict[str, str] = {}
53
+ for p in pairs:
54
+ if '=' not in p:
55
+ raise SystemExit(f"expected key=value, got: {p}")
56
+ k, v = p.split('=', 1)
57
+ out[k.strip()] = v.strip()
58
+ return out
59
+
60
+
61
+ def run_agent(repo_root: Path, agent_name: str, prompt: str, headless: bool) -> int:
62
+ agent = get_agent(agent_name)
63
+ try:
64
+ return agent.run(repo_root, prompt, headless)
65
+ except FileNotFoundError:
66
+ raise SystemExit(f"{agent_name} not found in PATH")
67
+
68
+
69
+ def cmd_conjure(args: argparse.Namespace) -> int:
70
+ repo_root = find_repo_root(Path(args.into or os.getcwd()))
71
+ try:
72
+ system, _, _ = compose(repo_root, args.profiles, invocation="__noop__", args=[], kv={})
73
+ except NotFoundError as e:
74
+ raise SystemExit(str(e))
75
+ write_instruction(repo_root, args.agent, system)
76
+ save_config(repo_root, args.agent, {"profiles": args.profiles})
77
+ print(f"wrote instructions for {args.agent}")
78
+ return 0
79
+
80
+
81
+ def cmd_invoke(args: argparse.Namespace) -> int:
82
+ repo_root = find_repo_root(Path(args.into or os.getcwd()))
83
+ cfg = load_config(repo_root, args.agent)
84
+ profiles = args.profiles if args.profiles is not None else cfg.get("profiles", [])
85
+ if not profiles:
86
+ print("warning: no profiles specified, using core only", file=sys.stderr)
87
+ kv = parse_kv(args.kv or [])
88
+ try:
89
+ _, _, full = compose(repo_root, profiles, args.invocation, args.inv_args or [], kv)
90
+ except NotFoundError as e:
91
+ raise SystemExit(str(e))
92
+ return run_agent(repo_root, args.agent, full, headless=args.headless)
93
+
94
+
95
+ def main() -> None:
96
+ parser = argparse.ArgumentParser(prog="familiar", description="conjure and invoke familiars")
97
+ sub = parser.add_subparsers(dest="command", required=True)
98
+
99
+ agent_choices = list(AGENTS.keys())
100
+
101
+ conjure = sub.add_parser("conjure", help="compose system instructions for an agent")
102
+ conjure.add_argument("agent", choices=agent_choices)
103
+ conjure.add_argument("profiles", nargs="+", help="profile names, e.g. rust infra sec")
104
+ conjure.add_argument("--into", help="target repo path (default: current directory)")
105
+ conjure.set_defaults(func=cmd_conjure)
106
+
107
+ invoke = sub.add_parser("invoke", help="render an invocation and run the agent")
108
+ invoke.add_argument("agent", choices=agent_choices)
109
+ invoke.add_argument("invocation")
110
+ invoke.add_argument("--into", help="target repo path (default: current directory)")
111
+ invoke.add_argument("--headless", action="store_true", help="run without interactive UI")
112
+ invoke.add_argument("--profiles", nargs="*", default=None, help="override saved profiles")
113
+ invoke.add_argument("--kv", nargs="*", help="named arguments as key=value pairs")
114
+ invoke.add_argument("inv_args", nargs="*", help="positional arguments for the invocation")
115
+ invoke.set_defaults(func=cmd_invoke)
116
+
117
+ args = parser.parse_args()
118
+ rc = args.func(args)
119
+ raise SystemExit(rc)
120
+
121
+
122
+ if __name__ == "__main__":
123
+ main()
@@ -0,0 +1 @@
1
+ <!-- noop invocation for conjure command -->
@@ -0,0 +1,26 @@
1
+ task: add tests.
2
+
3
+ inputs
4
+ - $ARGUMENTS (required): module path or symbol name.
5
+
6
+ preconditions
7
+ - if target is missing/unclear: ask what to test and where; stop.
8
+
9
+ steps
10
+ - identify the unit under test and its contract.
11
+ - write tests for:
12
+ - happy path
13
+ - one edge case
14
+ - one failure mode
15
+ - prefer table/parametrized tests.
16
+ - avoid heavy mocking unless necessary; if you mock: explain why.
17
+ - run the test suite.
18
+
19
+ acceptance
20
+ - tests are deterministic (no time/network/external dependencies).
21
+ - tests are minimal and readable.
22
+ - test command succeeds.
23
+
24
+ output
25
+ - unified diff.
26
+ - exact test command to run.
@@ -0,0 +1,20 @@
1
+ bootstrap a new python project.
2
+
3
+ arguments:
4
+ - package name: $1
5
+ - type: $2 (cli|lib)
6
+ - python version: $3 (optional, default 3.9)
7
+ - license: $4 (optional)
8
+
9
+ actions:
10
+ - create pyproject.toml with metadata.
11
+ - set up src layout.
12
+ - add a minimal readme.
13
+ - configure ruff and mypy.
14
+ - add a minimal test.
15
+
16
+ acceptance:
17
+ - `ruff check`, `mypy .`, and `pytest` succeed.
18
+
19
+ output:
20
+ - show diffs and exact commands.
@@ -0,0 +1,28 @@
1
+ task: bootstrap a new rust crate.
2
+
3
+ inputs (positional)
4
+ - $1 crate_name (required)
5
+ - $2 crate_type: bin|lib (required)
6
+ - $3 msrv (optional; e.g. 1.78)
7
+ - $4 license (optional; e.g. mit, apache-2.0)
8
+
9
+ preconditions
10
+ - if crate_name or crate_type is missing/invalid: ask and stop.
11
+ - if target directory exists: ask whether to abort or integrate; do not overwrite by default.
12
+
13
+ steps
14
+ - create crate: `cargo new <crate_name> --<crate_type>`.
15
+ - ensure project builds.
16
+ - add README.md with: one-line purpose + quickstart commands.
17
+ - if msrv provided: encode it (ask preferred mechanism if unclear; do not invent).
18
+ - add minimal test if none exists.
19
+ - run fmt/clippy/test.
20
+
21
+ acceptance
22
+ - `cargo fmt` succeeds.
23
+ - `cargo clippy --all-targets --all-features -- -D warnings` succeeds.
24
+ - `cargo test --all-features` succeeds.
25
+
26
+ output
27
+ - unified diff only for files changed/created.
28
+ - verification commands (exact).
@@ -0,0 +1,10 @@
1
+ review: $ARGUMENTS
2
+
3
+ actions:
4
+ - check for correctness, clarity, and maintainability.
5
+ - identify potential bugs or edge cases.
6
+ - suggest improvements without over-engineering.
7
+
8
+ output:
9
+ - list issues by severity (high/medium/low).
10
+ - include specific file:line references.
@@ -0,0 +1,9 @@
1
+ explain: $ARGUMENTS
2
+
3
+ actions:
4
+ - identify the relevant code paths and data flow.
5
+ - describe what it does, why, and how.
6
+ - note any non-obvious behavior or edge cases.
7
+
8
+ output:
9
+ - concise explanation with references to specific files/lines.
@@ -0,0 +1,26 @@
1
+ task: implement a feature from a spec.
2
+
3
+ inputs (named)
4
+ - {{spec}} (required): file path in repo OR pasted spec text.
5
+
6
+ preconditions
7
+ - if spec is empty: ask for a file path or pasted spec; stop.
8
+ - if spec is ambiguous or missing acceptance criteria: ask up to 5 clarifying questions; stop.
9
+
10
+ steps
11
+ - restate the spec in 1-2 sentences.
12
+ - extract explicit acceptance criteria (bullets). if none: propose criteria and ask for confirmation; stop.
13
+ - list files you will change (paths).
14
+ - write failing tests first when feasible.
15
+ - implement the smallest change that satisfies the criteria.
16
+ - run format/lint/type/tests.
17
+
18
+ acceptance
19
+ - all existing tests pass.
20
+ - new tests cover the main path + one edge case.
21
+ - no new deps or public api changes unless explicitly approved.
22
+
23
+ output
24
+ - plan (restatement + file list).
25
+ - unified diff.
26
+ - verification commands.
@@ -0,0 +1,25 @@
1
+ task: infrastructure change plan.
2
+
3
+ inputs
4
+ - $ARGUMENTS (required): desired change (what/why), target env (dev/prod), constraints.
5
+
6
+ preconditions
7
+ - if required info is missing (env, region, account, constraints): ask; stop.
8
+ - do not propose or apply production changes without explicit approval.
9
+
10
+ steps
11
+ - list touched components and assumptions.
12
+ - describe blast radius and failure modes.
13
+ - propose rollout steps (staged) and rollback steps.
14
+ - highlight least-privilege and exposure risks.
15
+ - include verification commands (plan/dry-run/diff/apply checks).
16
+
17
+ acceptance
18
+ - plan is actionable, ordered, and includes rollback.
19
+ - no broad permissions or public exposure without explicit justification.
20
+ - diffs are minimal (only if asked to implement).
21
+
22
+ output
23
+ - plan (rollout + rollback).
24
+ - diffs (only if asked).
25
+ - verification commands.
@@ -0,0 +1,14 @@
1
+ refactor: $ARGUMENTS
2
+
3
+ goals:
4
+ - improve structure without changing behavior.
5
+ - keep diffs minimal and reviewable.
6
+
7
+ actions:
8
+ - describe the current state and proposed changes.
9
+ - list files you will touch.
10
+ - implement in small steps.
11
+ - ensure tests still pass.
12
+
13
+ output:
14
+ - show diffs and how to verify behavior is unchanged.
@@ -0,0 +1,28 @@
1
+ task: security review.
2
+
3
+ inputs
4
+ - $ARGUMENTS (optional): scope. if empty, infer from current diff/repo context.
5
+
6
+ preconditions
7
+ - if scope is unclear and no obvious target exists: ask what to review; stop.
8
+
9
+ steps
10
+ - identify trust boundaries and attacker-controlled inputs.
11
+ - confirm authn/authz expectations.
12
+ - check for:
13
+ - injection (sql, shell, template, path)
14
+ - unsafe deserialization / parsing
15
+ - secrets handling
16
+ - permissions / privilege escalation
17
+ - insecure defaults
18
+ - produce a ranked list of issues.
19
+
20
+ deliverables
21
+ - findings ranked by severity (high/med/low) with concrete mitigations.
22
+ - for each high item: smallest patch suggestion.
23
+ - verification steps for each mitigation (tests/commands).
24
+
25
+ output
26
+ - findings (ranked bullets).
27
+ - recommended changes (diff only if requested).
28
+ - verification commands/tests.
@@ -0,0 +1,25 @@
1
+ # agent core
2
+
3
+ ## goals
4
+ - ship the smallest correct change.
5
+ - keep existing conventions. no drive-by refactors.
6
+ - be deterministic. no creativity unless asked.
7
+
8
+ ## hard rules
9
+ - if required inputs are missing or ambiguous, ask before coding.
10
+ - do not add dependencies, change public apis, or run destructive commands without explicit approval.
11
+ - never output secrets. never suggest logging secrets.
12
+
13
+ ## workflow
14
+ - restate the task in 1-2 sentences.
15
+ - list the exact files you will change (paths).
16
+ - if uncertainty remains: ask targeted questions (max 5).
17
+ - implement in small steps.
18
+ - after changes: run format + lint + tests (or state exactly why you cannot).
19
+ - finish with a short verification plan.
20
+
21
+ ## output format
22
+ - section: plan (task restatement + file list).
23
+ - section: changes (unified diff).
24
+ - section: verification (commands to run).
25
+ - section: notes (only if needed; max 5 bullets).
@@ -0,0 +1,23 @@
1
+ # infra profile
2
+
3
+ ## discipline
4
+ - plan first: describe change, blast radius, rollout, rollback.
5
+ - never touch production without explicit confirmation.
6
+ - minimize diffs; avoid churn in generated files.
7
+
8
+ ## safety rules
9
+ - least privilege for iam/rbac/security groups.
10
+ - do not open 0.0.0.0/0 unless explicitly required and documented.
11
+ - pin versions where practical (providers, modules, images).
12
+ - secrets: reference secret stores; do not inline credentials.
13
+
14
+ ## workflow
15
+ - list touched components and assumptions.
16
+ - propose staged rollout steps and rollback steps.
17
+ - include verification commands (dry-run/plan/apply checks).
18
+ - call out risks and mitigations.
19
+
20
+ ## output
21
+ - section: plan (steps + rollback).
22
+ - section: diffs (only if asked to implement).
23
+ - section: verification (commands).
@@ -0,0 +1,24 @@
1
+ # python profile
2
+
3
+ ## commands
4
+ - format: ruff format
5
+ - lint: ruff check
6
+ - type: mypy .
7
+ - test: pytest -q
8
+
9
+ ## rules
10
+ - keep functions small; push complexity into pure helpers.
11
+ - write tests for edge cases; prefer pytest parametrization.
12
+ - do not widen public apis without explicit approval.
13
+ - do not add new dependencies without explicit approval.
14
+ - prefer explicit types at module boundaries.
15
+
16
+ ## workflow
17
+ - follow existing project structure and naming.
18
+ - add tests that fail before the fix/feature (when possible).
19
+ - keep diffs minimal; avoid unrelated cleanup.
20
+ - run format + lint + type + tests, or say exactly why you cannot.
21
+
22
+ ## output
23
+ - show a unified diff.
24
+ - list the exact commands to verify (ruff/mypy/pytest).
@@ -0,0 +1,24 @@
1
+ # rust profile
2
+
3
+ ## commands
4
+ - format: cargo fmt
5
+ - lint: cargo clippy --all-targets --all-features -- -d warnings
6
+ - test: cargo test --all-features
7
+ - build: cargo build --all-features
8
+
9
+ ## rules
10
+ - do not introduce new crates unless you ask first.
11
+ - avoid unsafe. if unavoidable: justify + minimize surface + add tests.
12
+ - prefer explicit error types; do not swallow errors.
13
+ - prefer small, composable functions; avoid clever macros.
14
+ - do not change public apis without explicit approval.
15
+
16
+ ## workflow
17
+ - locate existing patterns in the crate and follow them.
18
+ - write or update tests first when feasible.
19
+ - keep diffs minimal; avoid reformatting unrelated code.
20
+ - ensure msrv/toolchain constraints are respected; ask if unknown.
21
+
22
+ ## output
23
+ - show a unified diff.
24
+ - list the exact cargo commands to verify (fmt/clippy/test).
@@ -0,0 +1,24 @@
1
+ # security profile
2
+
3
+ ## tasks
4
+ - identify trust boundaries and attacker-controlled inputs.
5
+ - confirm authn/authz expectations (who can do what).
6
+ - look for injection risks, unsafe defaults, and privilege escalation.
7
+ - check for secret leakage in logs, errors, traces, metrics.
8
+ - validate inputs early; fail closed; secure defaults.
9
+
10
+ ## prohibitions
11
+ - do not weaken tls/crypto settings.
12
+ - do not add debug logging for tokens/headers/payloads with secrets.
13
+ - do not suggest storing secrets in repo, env files, or plaintext config.
14
+ - do not propose broad permissions (admin/*) as a shortcut.
15
+
16
+ ## deliverables
17
+ - top risks with mitigations (bulleted, ranked).
18
+ - smallest patch for the highest-risk item first (if implementation requested).
19
+ - verification steps (tests/commands) for each mitigation.
20
+
21
+ ## output
22
+ - section: findings (ranked bullets).
23
+ - section: recommended changes (diffs if requested).
24
+ - section: verification (commands/tests).
@@ -0,0 +1,69 @@
1
+ """Render system and user prompts from templates and invocations."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+ import sys
6
+ from pathlib import Path
7
+ from importlib import resources
8
+
9
+ _VALID_NAME = re.compile(r"^[a-z0-9_-]+$")
10
+
11
+
12
+ class NotFoundError(Exception):
13
+ """Raised when a template or invocation is not found."""
14
+
15
+
16
+ def load_text(repo_root: Path, kind: str, name: str) -> str:
17
+ """Load a template or invocation; local overrides in .familiar override package data."""
18
+ if not _VALID_NAME.match(name):
19
+ raise NotFoundError(f"invalid {kind.rstrip('s')} name: {name}")
20
+ override = repo_root / ".familiar" / kind / f"{name}.md"
21
+ if override.exists():
22
+ return override.read_text(encoding="utf-8")
23
+ pkg = f"familiar.data.{kind}"
24
+ try:
25
+ return (resources.files(pkg) / f"{name}.md").read_text(encoding="utf-8")
26
+ except (FileNotFoundError, TypeError):
27
+ # TypeError: some python versions raise this for missing resources
28
+ raise NotFoundError(f"unknown {kind.rstrip('s')}: {name}")
29
+
30
+
31
+ def substitute(text: str, args: list[str], kv: dict[str, str]) -> str:
32
+ """Substitute $1, $2, ... $ARGUMENTS and {{key}} placeholders.
33
+
34
+ Note: positional args are substituted before kv args, so user-supplied
35
+ args containing {{foo}} could get expanded. This is low risk in practice.
36
+ """
37
+ missing: list[str] = []
38
+
39
+ def repl(m: re.Match[str]) -> str:
40
+ ident = m.group(1)
41
+ if ident == "ARGUMENTS":
42
+ return " ".join(args).strip()
43
+ if ident.isdigit():
44
+ idx = int(ident) - 1
45
+ if 0 <= idx < len(args):
46
+ return args[idx]
47
+ missing.append(f"${ident}")
48
+ return ""
49
+ return m.group(0)
50
+
51
+ text = re.sub(r"\$(ARGUMENTS|\d+)", repl, text)
52
+ if missing:
53
+ print(f"warning: missing arguments: {', '.join(missing)}", file=sys.stderr)
54
+ for k, v in kv.items():
55
+ text = text.replace(f"{{{{{k}}}}}", v)
56
+ return text
57
+
58
+
59
+ def compose(repo_root: Path, profiles: list[str], invocation: str, args: list[str], kv: dict[str, str]) -> tuple[str, str, str]:
60
+ """Compose system and user sections from selected profiles and invocation."""
61
+ core = load_text(repo_root, "templates", "core").strip()
62
+ parts: list[str] = [core]
63
+ for p in profiles:
64
+ parts.append(load_text(repo_root, "templates", p).strip())
65
+ system = "\n\n".join(parts)
66
+ inv = load_text(repo_root, "invocations", invocation).strip()
67
+ user = substitute(inv, args, kv)
68
+ full = f"{system}\n\n---\n\n{user}\n"
69
+ return system, user, full
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: familiar-cli
3
+ Version: 0.0.1
4
+ Summary: compose and invoke ai agent prompts from reusable templates
5
+ Author-email: cyberwitchery labs <contact@cyberwitchery.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/cyberwitchery/familiar
8
+ Project-URL: Repository, https://github.com/cyberwitchery/familiar
9
+ Keywords: ai,agents,prompts,cli,codex,claude
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Code Generators
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: dev
24
+ Requires-Dist: ruff; extra == "dev"
25
+ Requires-Dist: mypy; extra == "dev"
26
+ Requires-Dist: pytest; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # familiar
30
+
31
+ compose and invoke ai agent prompts from reusable templates.
32
+
33
+ ships with a standard set of templates and invocations, or bring your own (mine are
34
+ very much wip).
35
+
36
+ ## installation
37
+
38
+ ```
39
+ pip install familiar-cli
40
+ ```
41
+
42
+ ## usage
43
+
44
+ conjure profiles to create system instructions for an agent:
45
+
46
+ ```
47
+ familiar conjure codex rust infra sec
48
+ ```
49
+
50
+ invoke an action prompt:
51
+
52
+ ```
53
+ familiar invoke codex bootstrap-rust myapp lib 1.78 mit
54
+ ```
55
+
56
+ ## customization
57
+
58
+ add your own templates and invocations by creating files in `.familiar/` in your repo:
59
+
60
+ ```
61
+ .familiar/
62
+ templates/
63
+ myprofile.md # new profile
64
+ rust.md # override built-in
65
+ invocations/
66
+ my-task.md # new invocation
67
+ ```
68
+
69
+ local files take precedence over built-ins.
70
+
71
+ ### placeholders
72
+
73
+ invocations support placeholders:
74
+ - `$1`, `$2`, ... - positional arguments
75
+ - `$ARGUMENTS` - all positional arguments joined
76
+ - `{{key}}` - named arguments passed via `--kv key=value`
77
+
78
+ ## license
79
+
80
+ mit
@@ -0,0 +1,28 @@
1
+ LICENSE
2
+ pyproject.toml
3
+ readme.md
4
+ src/familiar/__init__.py
5
+ src/familiar/agents.py
6
+ src/familiar/cli.py
7
+ src/familiar/render.py
8
+ src/familiar/data/invocations/__noop__.md
9
+ src/familiar/data/invocations/add-tests.md
10
+ src/familiar/data/invocations/bootstrap-python.md
11
+ src/familiar/data/invocations/bootstrap-rust.md
12
+ src/familiar/data/invocations/code-review.md
13
+ src/familiar/data/invocations/explain.md
14
+ src/familiar/data/invocations/implement-feature.md
15
+ src/familiar/data/invocations/infra-change.md
16
+ src/familiar/data/invocations/refactor.md
17
+ src/familiar/data/invocations/security-review.md
18
+ src/familiar/data/templates/core.md
19
+ src/familiar/data/templates/infra.md
20
+ src/familiar/data/templates/python.md
21
+ src/familiar/data/templates/rust.md
22
+ src/familiar/data/templates/sec.md
23
+ src/familiar_cli.egg-info/PKG-INFO
24
+ src/familiar_cli.egg-info/SOURCES.txt
25
+ src/familiar_cli.egg-info/dependency_links.txt
26
+ src/familiar_cli.egg-info/entry_points.txt
27
+ src/familiar_cli.egg-info/requires.txt
28
+ src/familiar_cli.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ familiar = familiar.cli:main
@@ -0,0 +1,5 @@
1
+
2
+ [dev]
3
+ ruff
4
+ mypy
5
+ pytest