diff --git a/.codex/skills/.system/.codex-system-skills.marker b/.codex/skills/.system/.codex-system-skills.marker index 1c19104..36aa1f7 100644 --- a/.codex/skills/.system/.codex-system-skills.marker +++ b/.codex/skills/.system/.codex-system-skills.marker @@ -1 +1 @@ -6277a8b83820afd8 +821f2ce8d073b28f diff --git a/.codex/skills/.system/skill-creator/SKILL.md b/.codex/skills/.system/skill-creator/SKILL.md index 72bc0b9..60251f1 100644 --- a/.codex/skills/.system/skill-creator/SKILL.md +++ b/.codex/skills/.system/skill-creator/SKILL.md @@ -11,7 +11,7 @@ This skill provides guidance for creating effective skills. ## About Skills -Skills are modular, self-contained folders that extend Codex's capabilities by providing +Skills are modular, self-contained packages that extend Codex's capabilities by providing specialized knowledge, workflows, and tools. Think of them as "onboarding guides" for specific domains or tasks—they transform Codex from a general-purpose agent into a specialized agent equipped with procedural knowledge that no model can fully possess. @@ -56,8 +56,6 @@ skill-name/ │ │ ├── name: (required) │ │ └── description: (required) │ └── Markdown instructions (required) -├── agents/ (recommended) -│ └── openai.yaml - UI metadata for skill lists and chips └── Bundled Resources (optional) ├── scripts/ - Executable code (Python/Bash/etc.) ├── references/ - Documentation intended to be loaded into context as needed @@ -71,16 +69,6 @@ Every SKILL.md consists of: - **Frontmatter** (YAML): Contains `name` and `description` fields. These are the only fields that Codex reads to determine when the skill gets used, thus it is very important to be clear and comprehensive in describing what the skill is, and when it should be used. - **Body** (Markdown): Instructions and guidance for using the skill. Only loaded AFTER the skill triggers (if at all). -#### Agents metadata (recommended) - -- UI-facing metadata for skill lists and chips -- Read references/openai_yaml.md before generating values and follow its descriptions and constraints -- Create: human-facing `display_name`, `short_description`, and `default_prompt` by reading the skill -- Generate deterministically by passing the values as `--interface key=value` to `scripts/generate_openai_yaml.py` or `scripts/init_skill.py` -- On updates: validate `agents/openai.yaml` still matches SKILL.md; regenerate if stale -- Only include other optional interface fields (icons, brand color) if explicitly provided -- See references/openai_yaml.md for field definitions and examples - #### Bundled Resources (optional) ##### Scripts (`scripts/`) @@ -220,7 +208,7 @@ Skill creation involves these steps: 2. Plan reusable skill contents (scripts, references, assets) 3. Initialize the skill (run init_skill.py) 4. Edit the skill (implement resources and write SKILL.md) -5. Validate the skill (run quick_validate.py) +5. Package the skill (run package_skill.py) 6. Iterate based on real usage Follow these steps in order, skipping only if there is a clear reason why they are not applicable. @@ -278,7 +266,7 @@ To establish the skill's contents, analyze each concrete example to create a lis At this point, it is time to actually create the skill. -Skip this step only if the skill being developed already exists. In this case, continue to the next step. +Skip this step only if the skill being developed already exists, and iteration or packaging is needed. In this case, continue to the next step. When creating a new skill from scratch, always run the `init_skill.py` script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable. @@ -300,24 +288,24 @@ The script: - Creates the skill directory at the specified path - Generates a SKILL.md template with proper frontmatter and TODO placeholders -- Creates `agents/openai.yaml` using agent-generated `display_name`, `short_description`, and `default_prompt` passed via `--interface key=value` - Optionally creates resource directories based on `--resources` - Optionally adds example files when `--examples` is set After initialization, customize the SKILL.md and add resources as needed. If you used `--examples`, replace or delete placeholder files. -Generate `display_name`, `short_description`, and `default_prompt` by reading the skill, then pass them as `--interface key=value` to `init_skill.py` or regenerate with: - -```bash -scripts/generate_openai_yaml.py --interface key=value -``` - -Only include other optional interface fields when the user explicitly provides them. For full field descriptions and examples, see references/openai_yaml.md. - ### Step 4: Edit the Skill When editing the (newly-generated or existing) skill, remember that the skill is being created for another instance of Codex to use. Include information that would be beneficial and non-obvious to Codex. Consider what procedural knowledge, domain-specific details, or reusable assets would help another Codex instance execute these tasks more effectively. +#### Learn Proven Design Patterns + +Consult these helpful guides based on your skill's needs: + +- **Multi-step processes**: See references/workflows.md for sequential workflows and conditional logic +- **Specific output formats or quality standards**: See references/output-patterns.md for template and example patterns + +These files contain established best practices for effective skill design. + #### Start with Reusable Skill Contents To begin implementation, start with the reusable resources identified above: `scripts/`, `references/`, and `assets/` files. Note that this step may require user input. For example, when implementing a `brand-guidelines` skill, the user may need to provide brand assets or templates to store in `assets/`, or documentation to store in `references/`. @@ -340,21 +328,40 @@ Write the YAML frontmatter with `name` and `description`: - Include all "when to use" information here - Not in the body. The body is only loaded after triggering, so "When to Use This Skill" sections in the body are not helpful to Codex. - Example description for a `docx` skill: "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. Use when Codex needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks" +Ensure the frontmatter is valid YAML. Keep `name` and `description` as single-line scalars. If either could be interpreted as YAML syntax, wrap it in quotes. + Do not include any other fields in YAML frontmatter. ##### Body Write instructions for using the skill and its bundled resources. -### Step 5: Validate the Skill +### Step 5: Packaging a Skill -Once development of the skill is complete, validate the skill folder to catch basic issues early: +Once development of the skill is complete, it must be packaged into a distributable .skill file that gets shared with the user. The packaging process automatically validates the skill first to ensure it meets all requirements: ```bash -scripts/quick_validate.py +scripts/package_skill.py ``` -The validation script checks YAML frontmatter format, required fields, and naming rules. If validation fails, fix the reported issues and run the command again. +Optional output directory specification: + +```bash +scripts/package_skill.py ./dist +``` + +The packaging script will: + +1. **Validate** the skill automatically, checking: + + - YAML frontmatter format and required fields + - Skill naming conventions and directory structure + - Description completeness and quality + - File organization and resource references + +2. **Package** the skill if validation passes, creating a .skill file named after the skill (e.g., `my-skill.skill`) that includes all files and maintains the proper directory structure for distribution. The .skill file is a zip file with a .skill extension. + +If validation fails, the script will report the errors and exit without creating a package. Fix any validation errors and run the packaging command again. ### Step 6: Iterate diff --git a/.codex/skills/.system/skill-creator/agents/openai.yaml b/.codex/skills/.system/skill-creator/agents/openai.yaml deleted file mode 100644 index 3095c60..0000000 --- a/.codex/skills/.system/skill-creator/agents/openai.yaml +++ /dev/null @@ -1,5 +0,0 @@ -interface: - display_name: "Skill Creator" - short_description: "Create or update a skill" - icon_small: "./assets/skill-creator-small.svg" - icon_large: "./assets/skill-creator.png" diff --git a/.codex/skills/.system/skill-creator/assets/skill-creator-small.svg b/.codex/skills/.system/skill-creator/assets/skill-creator-small.svg deleted file mode 100644 index c6e4f67..0000000 --- a/.codex/skills/.system/skill-creator/assets/skill-creator-small.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/.codex/skills/.system/skill-creator/assets/skill-creator.png b/.codex/skills/.system/skill-creator/assets/skill-creator.png deleted file mode 100644 index 4f3d6d8..0000000 Binary files a/.codex/skills/.system/skill-creator/assets/skill-creator.png and /dev/null differ diff --git a/.codex/skills/.system/skill-creator/references/openai_yaml.md b/.codex/skills/.system/skill-creator/references/openai_yaml.md deleted file mode 100644 index 90f9e8e..0000000 --- a/.codex/skills/.system/skill-creator/references/openai_yaml.md +++ /dev/null @@ -1,49 +0,0 @@ -# openai.yaml fields (full example + descriptions) - -`agents/openai.yaml` is an extended, product-specific config intended for the machine/harness to read, not the agent. Other product-specific config can also live in the `agents/` folder. - -## Full example - -```yaml -interface: - display_name: "Optional user-facing name" - short_description: "Optional user-facing description" - icon_small: "./assets/small-400px.png" - icon_large: "./assets/large-logo.svg" - brand_color: "#3B82F6" - default_prompt: "Optional surrounding prompt to use the skill with" - -dependencies: - tools: - - type: "mcp" - value: "github" - description: "GitHub MCP server" - transport: "streamable_http" - url: "https://api.githubcopilot.com/mcp/" - -policy: - allow_implicit_invocation: true -``` - -## Field descriptions and constraints - -Top-level constraints: - -- Quote all string values. -- Keep keys unquoted. -- For `interface.default_prompt`: generate a helpful, short (typically 1 sentence) example starting prompt based on the skill. It must explicitly mention the skill as `$skill-name` (e.g., "Use $skill-name-here to draft a concise weekly status update."). - -- `interface.display_name`: Human-facing title shown in UI skill lists and chips. -- `interface.short_description`: Human-facing short UI blurb (25–64 chars) for quick scanning. -- `interface.icon_small`: Path to a small icon asset (relative to skill dir). Default to `./assets/` and place icons in the skill's `assets/` folder. -- `interface.icon_large`: Path to a larger logo asset (relative to skill dir). Default to `./assets/` and place icons in the skill's `assets/` folder. -- `interface.brand_color`: Hex color used for UI accents (e.g., badges). -- `interface.default_prompt`: Default prompt snippet inserted when invoking the skill. -- `dependencies.tools[].type`: Dependency category. Only `mcp` is supported for now. -- `dependencies.tools[].value`: Identifier of the tool or dependency. -- `dependencies.tools[].description`: Human-readable explanation of the dependency. -- `dependencies.tools[].transport`: Connection type when `type` is `mcp`. -- `dependencies.tools[].url`: MCP server URL when `type` is `mcp`. -- `policy.allow_implicit_invocation`: When false, the skill is not injected into - the model context by default, but can still be invoked explicitly via `$skill`. - Defaults to true. diff --git a/.codex/skills/.system/skill-creator/scripts/generate_openai_yaml.py b/.codex/skills/.system/skill-creator/scripts/generate_openai_yaml.py deleted file mode 100644 index 3fd7405..0000000 --- a/.codex/skills/.system/skill-creator/scripts/generate_openai_yaml.py +++ /dev/null @@ -1,226 +0,0 @@ -#!/usr/bin/env python3 -""" -OpenAI YAML Generator - Creates agents/openai.yaml for a skill folder. - -Usage: - generate_openai_yaml.py [--name ] [--interface key=value] -""" - -import argparse -import re -import sys -from pathlib import Path - -ACRONYMS = { - "GH", - "MCP", - "API", - "CI", - "CLI", - "LLM", - "PDF", - "PR", - "UI", - "URL", - "SQL", -} - -BRANDS = { - "openai": "OpenAI", - "openapi": "OpenAPI", - "github": "GitHub", - "pagerduty": "PagerDuty", - "datadog": "DataDog", - "sqlite": "SQLite", - "fastapi": "FastAPI", -} - -SMALL_WORDS = {"and", "or", "to", "up", "with"} - -ALLOWED_INTERFACE_KEYS = { - "display_name", - "short_description", - "icon_small", - "icon_large", - "brand_color", - "default_prompt", -} - - -def yaml_quote(value): - escaped = value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") - return f'"{escaped}"' - - -def format_display_name(skill_name): - words = [word for word in skill_name.split("-") if word] - formatted = [] - for index, word in enumerate(words): - lower = word.lower() - upper = word.upper() - if upper in ACRONYMS: - formatted.append(upper) - continue - if lower in BRANDS: - formatted.append(BRANDS[lower]) - continue - if index > 0 and lower in SMALL_WORDS: - formatted.append(lower) - continue - formatted.append(word.capitalize()) - return " ".join(formatted) - - -def generate_short_description(display_name): - description = f"Help with {display_name} tasks" - - if len(description) < 25: - description = f"Help with {display_name} tasks and workflows" - if len(description) < 25: - description = f"Help with {display_name} tasks with guidance" - - if len(description) > 64: - description = f"Help with {display_name}" - if len(description) > 64: - description = f"{display_name} helper" - if len(description) > 64: - description = f"{display_name} tools" - if len(description) > 64: - suffix = " helper" - max_name_length = 64 - len(suffix) - trimmed = display_name[:max_name_length].rstrip() - description = f"{trimmed}{suffix}" - if len(description) > 64: - description = description[:64].rstrip() - - if len(description) < 25: - description = f"{description} workflows" - if len(description) > 64: - description = description[:64].rstrip() - - return description - - -def read_frontmatter_name(skill_dir): - skill_md = Path(skill_dir) / "SKILL.md" - if not skill_md.exists(): - print(f"[ERROR] SKILL.md not found in {skill_dir}") - return None - content = skill_md.read_text() - match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL) - if not match: - print("[ERROR] Invalid SKILL.md frontmatter format.") - return None - frontmatter_text = match.group(1) - - import yaml - - try: - frontmatter = yaml.safe_load(frontmatter_text) - except yaml.YAMLError as exc: - print(f"[ERROR] Invalid YAML frontmatter: {exc}") - return None - if not isinstance(frontmatter, dict): - print("[ERROR] Frontmatter must be a YAML dictionary.") - return None - name = frontmatter.get("name", "") - if not isinstance(name, str) or not name.strip(): - print("[ERROR] Frontmatter 'name' is missing or invalid.") - return None - return name.strip() - - -def parse_interface_overrides(raw_overrides): - overrides = {} - optional_order = [] - for item in raw_overrides: - if "=" not in item: - print(f"[ERROR] Invalid interface override '{item}'. Use key=value.") - return None, None - key, value = item.split("=", 1) - key = key.strip() - value = value.strip() - if not key: - print(f"[ERROR] Invalid interface override '{item}'. Key is empty.") - return None, None - if key not in ALLOWED_INTERFACE_KEYS: - allowed = ", ".join(sorted(ALLOWED_INTERFACE_KEYS)) - print(f"[ERROR] Unknown interface field '{key}'. Allowed: {allowed}") - return None, None - overrides[key] = value - if key not in ("display_name", "short_description") and key not in optional_order: - optional_order.append(key) - return overrides, optional_order - - -def write_openai_yaml(skill_dir, skill_name, raw_overrides): - overrides, optional_order = parse_interface_overrides(raw_overrides) - if overrides is None: - return None - - display_name = overrides.get("display_name") or format_display_name(skill_name) - short_description = overrides.get("short_description") or generate_short_description(display_name) - - if not (25 <= len(short_description) <= 64): - print( - "[ERROR] short_description must be 25-64 characters " - f"(got {len(short_description)})." - ) - return None - - interface_lines = [ - "interface:", - f" display_name: {yaml_quote(display_name)}", - f" short_description: {yaml_quote(short_description)}", - ] - - for key in optional_order: - value = overrides.get(key) - if value is not None: - interface_lines.append(f" {key}: {yaml_quote(value)}") - - agents_dir = Path(skill_dir) / "agents" - agents_dir.mkdir(parents=True, exist_ok=True) - output_path = agents_dir / "openai.yaml" - output_path.write_text("\n".join(interface_lines) + "\n") - print(f"[OK] Created agents/openai.yaml") - return output_path - - -def main(): - parser = argparse.ArgumentParser( - description="Create agents/openai.yaml for a skill directory.", - ) - parser.add_argument("skill_dir", help="Path to the skill directory") - parser.add_argument( - "--name", - help="Skill name override (defaults to SKILL.md frontmatter)", - ) - parser.add_argument( - "--interface", - action="append", - default=[], - help="Interface override in key=value format (repeatable)", - ) - args = parser.parse_args() - - skill_dir = Path(args.skill_dir).resolve() - if not skill_dir.exists(): - print(f"[ERROR] Skill directory not found: {skill_dir}") - sys.exit(1) - if not skill_dir.is_dir(): - print(f"[ERROR] Path is not a directory: {skill_dir}") - sys.exit(1) - - skill_name = args.name or read_frontmatter_name(skill_dir) - if not skill_name: - sys.exit(1) - - result = write_openai_yaml(skill_dir, skill_name, args.interface) - if result: - sys.exit(0) - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/.codex/skills/.system/skill-creator/scripts/init_skill.py b/.codex/skills/.system/skill-creator/scripts/init_skill.py index f90703e..8633fe9 100644 --- a/.codex/skills/.system/skill-creator/scripts/init_skill.py +++ b/.codex/skills/.system/skill-creator/scripts/init_skill.py @@ -3,14 +3,13 @@ Skill Initializer - Creates a new skill from template Usage: - init_skill.py --path [--resources scripts,references,assets] [--examples] [--interface key=value] + init_skill.py --path [--resources scripts,references,assets] [--examples] Examples: init_skill.py my-new-skill --path skills/public init_skill.py my-new-skill --path skills/public --resources scripts,references init_skill.py my-api-helper --path skills/private --resources scripts --examples init_skill.py custom-skill --path /custom/location - init_skill.py my-skill --path skills/public --interface short_description="Short UI label" """ import argparse @@ -18,8 +17,6 @@ import re import sys from pathlib import Path -from generate_openai_yaml import write_openai_yaml - MAX_SKILL_NAME_LENGTH = 64 ALLOWED_RESOURCES = {"scripts", "references", "assets"} @@ -255,7 +252,7 @@ def create_resource_dirs(skill_dir, skill_name, skill_title, resources, include_ print("[OK] Created assets/") -def init_skill(skill_name, path, resources, include_examples, interface_overrides): +def init_skill(skill_name, path, resources, include_examples): """ Initialize a new skill directory with template SKILL.md. @@ -296,15 +293,6 @@ def init_skill(skill_name, path, resources, include_examples, interface_override print(f"[ERROR] Error creating SKILL.md: {e}") return None - # Create agents/openai.yaml - try: - result = write_openai_yaml(skill_dir, skill_name, interface_overrides) - if not result: - return None - except Exception as e: - print(f"[ERROR] Error creating agents/openai.yaml: {e}") - return None - # Create resource directories if requested if resources: try: @@ -324,8 +312,7 @@ def init_skill(skill_name, path, resources, include_examples, interface_override print("2. Add resources to scripts/, references/, and assets/ as needed") else: print("2. Create resource directories only if needed (scripts/, references/, assets/)") - print("3. Update agents/openai.yaml if the UI metadata should differ") - print("4. Run the validator when ready to check the skill structure") + print("3. Run the validator when ready to check the skill structure") return skill_dir @@ -346,12 +333,6 @@ def main(): action="store_true", help="Create example files inside the selected resource directories", ) - parser.add_argument( - "--interface", - action="append", - default=[], - help="Interface override in key=value format (repeatable)", - ) args = parser.parse_args() raw_skill_name = args.skill_name @@ -385,7 +366,7 @@ def main(): print(" Resources: none (create as needed)") print() - result = init_skill(skill_name, path, resources, args.examples, args.interface) + result = init_skill(skill_name, path, resources, args.examples) if result: sys.exit(0) diff --git a/.codex/skills/.system/skill-creator/scripts/package_skill.py b/.codex/skills/.system/skill-creator/scripts/package_skill.py new file mode 100644 index 0000000..9a03995 --- /dev/null +++ b/.codex/skills/.system/skill-creator/scripts/package_skill.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +""" +Skill Packager - Creates a distributable .skill file of a skill folder + +Usage: + python utils/package_skill.py [output-directory] + +Example: + python utils/package_skill.py skills/public/my-skill + python utils/package_skill.py skills/public/my-skill ./dist +""" + +import sys +import zipfile +from pathlib import Path + +from quick_validate import validate_skill + + +def package_skill(skill_path, output_dir=None): + """ + Package a skill folder into a .skill file. + + Args: + skill_path: Path to the skill folder + output_dir: Optional output directory for the .skill file (defaults to current directory) + + Returns: + Path to the created .skill file, or None if error + """ + skill_path = Path(skill_path).resolve() + + # Validate skill folder exists + if not skill_path.exists(): + print(f"[ERROR] Skill folder not found: {skill_path}") + return None + + if not skill_path.is_dir(): + print(f"[ERROR] Path is not a directory: {skill_path}") + return None + + # Validate SKILL.md exists + skill_md = skill_path / "SKILL.md" + if not skill_md.exists(): + print(f"[ERROR] SKILL.md not found in {skill_path}") + return None + + # Run validation before packaging + print("Validating skill...") + valid, message = validate_skill(skill_path) + if not valid: + print(f"[ERROR] Validation failed: {message}") + print(" Please fix the validation errors before packaging.") + return None + print(f"[OK] {message}\n") + + # Determine output location + skill_name = skill_path.name + if output_dir: + output_path = Path(output_dir).resolve() + output_path.mkdir(parents=True, exist_ok=True) + else: + output_path = Path.cwd() + + skill_filename = output_path / f"{skill_name}.skill" + + # Create the .skill file (zip format) + try: + with zipfile.ZipFile(skill_filename, "w", zipfile.ZIP_DEFLATED) as zipf: + # Walk through the skill directory + for file_path in skill_path.rglob("*"): + if file_path.is_file(): + # Calculate the relative path within the zip + arcname = file_path.relative_to(skill_path.parent) + zipf.write(file_path, arcname) + print(f" Added: {arcname}") + + print(f"\n[OK] Successfully packaged skill to: {skill_filename}") + return skill_filename + + except Exception as e: + print(f"[ERROR] Error creating .skill file: {e}") + return None + + +def main(): + if len(sys.argv) < 2: + print("Usage: python utils/package_skill.py [output-directory]") + print("\nExample:") + print(" python utils/package_skill.py skills/public/my-skill") + print(" python utils/package_skill.py skills/public/my-skill ./dist") + sys.exit(1) + + skill_path = sys.argv[1] + output_dir = sys.argv[2] if len(sys.argv) > 2 else None + + print(f"Packaging skill: {skill_path}") + if output_dir: + print(f" Output directory: {output_dir}") + print() + + result = package_skill(skill_path, output_dir) + + if result: + sys.exit(0) + else: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.codex/skills/.system/skill-installer/SKILL.md b/.codex/skills/.system/skill-installer/SKILL.md index 313626a..857c32d 100644 --- a/.codex/skills/.system/skill-installer/SKILL.md +++ b/.codex/skills/.system/skill-installer/SKILL.md @@ -7,10 +7,10 @@ metadata: # Skill Installer -Helps install skills. By default these are from https://github.com/openai/skills/tree/main/skills/.curated, but users can also provide other locations. Experimental skills live in https://github.com/openai/skills/tree/main/skills/.experimental and can be installed the same way. +Helps install skills. By default these are from https://github.com/openai/skills/tree/main/skills/.curated, but users can also provide other locations. Use the helper scripts based on the task: -- List skills when the user asks what is available, or if the user uses this skill without specifying what to do. Default listing is `.curated`, but you can pass `--path skills/.experimental` when they ask about experimental skills. +- List curated skills when the user asks what is available, or if the user uses this skill without specifying what to do. - Install from the curated list when the user provides a skill name. - Install from another repo when the user provides a GitHub repo/path (including private repos). @@ -18,7 +18,7 @@ Install skills with the helper scripts. ## Communication -When listing skills, output approximately as follows, depending on the context of the user's request. If they ask about experimental skills, list from `.experimental` instead of `.curated` and label the source accordingly: +When listing curated skills, output approximately as follows, depending on the context of the user's request: """ Skills from {repo}: 1. skill-1 @@ -33,12 +33,10 @@ After installing a skill, tell the user: "Restart Codex to pick up new skills." All of these scripts use network, so when running in the sandbox, request escalation when running them. -- `scripts/list-skills.py` (prints skills list with installed annotations) -- `scripts/list-skills.py --format json` -- Example (experimental list): `scripts/list-skills.py --path skills/.experimental` +- `scripts/list-curated-skills.py` (prints curated list with installed annotations) +- `scripts/list-curated-skills.py --format json` - `scripts/install-skill-from-github.py --repo / --path [ ...]` - `scripts/install-skill-from-github.py --url https://github.com///tree//` -- Example (experimental skill): `scripts/install-skill-from-github.py --repo openai/skills --path skills/.experimental/` ## Behavior and Options diff --git a/.codex/skills/.system/skill-installer/agents/openai.yaml b/.codex/skills/.system/skill-installer/agents/openai.yaml deleted file mode 100644 index 88d40cd..0000000 --- a/.codex/skills/.system/skill-installer/agents/openai.yaml +++ /dev/null @@ -1,5 +0,0 @@ -interface: - display_name: "Skill Installer" - short_description: "Install curated skills from openai/skills or other repos" - icon_small: "./assets/skill-installer-small.svg" - icon_large: "./assets/skill-installer.png" diff --git a/.codex/skills/.system/skill-installer/assets/skill-installer-small.svg b/.codex/skills/.system/skill-installer/assets/skill-installer-small.svg deleted file mode 100644 index ccfc034..0000000 --- a/.codex/skills/.system/skill-installer/assets/skill-installer-small.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/.codex/skills/.system/skill-installer/assets/skill-installer.png b/.codex/skills/.system/skill-installer/assets/skill-installer.png deleted file mode 100644 index 2977cd5..0000000 Binary files a/.codex/skills/.system/skill-installer/assets/skill-installer.png and /dev/null differ diff --git a/.codex/skills/.system/skill-installer/scripts/list-skills.py b/.codex/skills/.system/skill-installer/scripts/list-curated-skills.py similarity index 81% rename from .codex/skills/.system/skill-installer/scripts/list-skills.py rename to .codex/skills/.system/skill-installer/scripts/list-curated-skills.py index 0977c29..08d475c 100644 --- a/.codex/skills/.system/skill-installer/scripts/list-skills.py +++ b/.codex/skills/.system/skill-installer/scripts/list-curated-skills.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""List skills from a GitHub repo path.""" +"""List curated skills from a GitHub repo path.""" from __future__ import annotations @@ -47,32 +47,28 @@ def _installed_skills() -> set[str]: return entries -def _list_skills(repo: str, path: str, ref: str) -> list[str]: +def _list_curated(repo: str, path: str, ref: str) -> list[str]: api_url = github_api_contents_url(repo, path, ref) try: payload = _request(api_url) except urllib.error.HTTPError as exc: if exc.code == 404: raise ListError( - "Skills path not found: " + "Curated skills path not found: " f"https://github.com/{repo}/tree/{ref}/{path}" ) from exc - raise ListError(f"Failed to fetch skills: HTTP {exc.code}") from exc + raise ListError(f"Failed to fetch curated skills: HTTP {exc.code}") from exc data = json.loads(payload.decode("utf-8")) if not isinstance(data, list): - raise ListError("Unexpected skills listing response.") + raise ListError("Unexpected curated listing response.") skills = [item["name"] for item in data if item.get("type") == "dir"] return sorted(skills) def _parse_args(argv: list[str]) -> Args: - parser = argparse.ArgumentParser(description="List skills.") + parser = argparse.ArgumentParser(description="List curated skills.") parser.add_argument("--repo", default=DEFAULT_REPO) - parser.add_argument( - "--path", - default=DEFAULT_PATH, - help="Repo path to list (default: skills/.curated)", - ) + parser.add_argument("--path", default=DEFAULT_PATH) parser.add_argument("--ref", default=DEFAULT_REF) parser.add_argument( "--format", @@ -86,7 +82,7 @@ def _parse_args(argv: list[str]) -> Args: def main(argv: list[str]) -> int: args = _parse_args(argv) try: - skills = _list_skills(args.repo, args.path, args.ref) + skills = _list_curated(args.repo, args.path, args.ref) installed = _installed_skills() if args.format == "json": payload = [ diff --git a/config/home-manager.nix b/config/home-manager.nix index 6005b61..b8bfec3 100644 --- a/config/home-manager.nix +++ b/config/home-manager.nix @@ -3,17 +3,25 @@ config, pkgs, lib, - osConfig, + osConfig ? null, + preferredShell ? "zsh", + userEmail ? "danilo.reyes.251@proton.me", ... }: let inherit (pkgs) fd fzf; - inherit (inputs.jawz-scripts.packages.x86_64-linux) pokemon-colorscripts; - shellType = osConfig.my.shell.type; - commonInit = '' - ${pokemon-colorscripts}/bin/pokemon-colorscripts -r --no-title - export command_timeout=60 - ''; + pokemon-colorscripts = lib.attrByPath [ + pkgs.system + "pokemon-colorscripts" + ] null inputs.jawz-scripts.packages; + shellType = if osConfig == null then preferredShell else osConfig.my.shell.type; + commonInit = + lib.optionalString (pokemon-colorscripts != null) '' + ${pokemon-colorscripts}/bin/pokemon-colorscripts -r --no-title + '' + + '' + export command_timeout=60 + ''; commonAliases = inputs.self.lib.commonAliases // { open-gallery = '' cd /srv/pool/scrapping/JawZ/gallery-dl && @@ -21,7 +29,7 @@ let }; in { - imports = [ ./jawz-home.nix ]; + imports = lib.optionals (osConfig != null) [ ./jawz-home.nix ]; home.stateVersion = "23.05"; programs = { direnv = { @@ -36,7 +44,7 @@ in pull.rebase = true; init.defaultBranch = "main"; user = { - inherit (osConfig.my) email; + email = if osConfig == null then userEmail else osConfig.my.email; name = "Danilo Reyes"; }; }; @@ -60,7 +68,7 @@ in }; zsh = lib.mkIf (shellType == "zsh") { enable = true; - dotDir = ".config/zsh"; + dotDir = "${config.xdg.configHome}/zsh"; shellAliases = commonAliases; initContent = commonInit; history = { @@ -73,7 +81,7 @@ in }; xdg = { enable = true; - userDirs = { + userDirs = lib.mkIf pkgs.stdenv.isLinux { enable = true; createDirectories = false; desktop = "${config.home.homeDirectory}"; diff --git a/docs/constitution.md b/docs/constitution.md index 967cbed..1420567 100644 --- a/docs/constitution.md +++ b/docs/constitution.md @@ -7,9 +7,11 @@ ## Repository Overview - Architecture: Flake-based repo using `flake-parts` with inputs for pkgs (stable/unstable), stylix, home-manager, sops-nix, and service overlays. Common modules are composed through `parts/core.nix` and `parts/hosts.nix`. +- Host outputs: `parts/hosts.nix` publishes NixOS hosts through `flake.nixosConfigurations` and standalone Home Manager hosts through `flake.homeConfigurations`. - Module auto-import: `modules/modules.nix` auto-imports legacy flat modules under `modules/apps`, `modules/dev`, `modules/scripts`, `modules/servers`, `modules/services`, `modules/shell`, `modules/websites`, and `modules/network`, excluding `librewolf.nix`, and also discovers nested `nixos.nix` files under those trees. `config/base.nix` registers `modules/home-manager.nix` as a Home Manager shared module, which discovers nested `home.nix` files under `modules/` for every Home Manager user. Factories live in `modules/factories/` (`mkserver`, `mkscript`), and shared options are in `modules/nix` and `modules/users`. - Home Manager helper layer: Common Home Manager wrapper logic belongs in `parts/core.nix` helpers under `inputs.self.lib` when it is repeated across multiple `home.nix` modules. Current helpers include split-loader support plus `hmModule`, `hmShellType`, and `hmOnlyUser` for shared enablement and shell-selection patterns. - Hosts and toggles: Host definitions live in `hosts//configuration.nix` with host-specific toggles in `hosts//toggles.nix`. The `my` namespace carries toggles for apps/dev/scripts/services/shell, feature flags like `enableProxy` and `enableContainers`, and per-host `interfaces` and `ips` maps. +- Standalone Home Manager hosts: Home-only hosts may live under `hosts//home.nix` with `hosts//toggles.nix`, and should only enable modules that have a `home.nix` surface or are otherwise known to be Home Manager-compatible on that platform. - Port assignment: Service ports should live with the service module when the port is intrinsic to a server definition under `modules/servers/`. Miscellaneous or host-specific ports that do not belong to a server module should be centralized in `my.ports` in `modules/modules.nix` and referenced via `config.my.ports.*` (use `toString config.my.ports.*` where a string is required). - Main server and proxies: `my.mainServer` selects the host that should serve traffic by default (default `vps`). Reverse proxies use helpers in `parts/core.nix` (`proxy`, `proxyReverse`, `proxyReverseFix`, `proxyReversePrivate`) and pick IPs from `my.ips` plus the hostName/ip set by `mkserver` options. Nginx defaults to `proxyReverse` for any server with `enableProxy = true` unless `useDefaultProxy = false` or the server is listed in the Fix/Private proxy lists. - Secure hosts and secrets: `my.secureHost` gates SOPS secrets. Secure hosts load secrets from `secrets/*.yaml` and wireguard definitions; non-secure hosts (e.g., `hosts/emacs`) skip secret-dependent services. Default SOPS file is `secrets/secrets.yaml` via `config/base.nix`. Proxy-only services that need private certificates must still define their cert secrets when `enableProxy = true`. @@ -53,6 +55,7 @@ config.services = { - Home module helper rule: Extract repeated Home Manager wrapper code into `parts/core.nix` once at least a few modules share the same structure. Keep only feature-specific package lists, files, and service settings inside each `home.nix`. - User config split: Personal configs may also split across `config/.nix` for NixOS-side account/secrets state and `config/-home.nix` for Home Manager-only state imported from the user home module. - Active hosts: `workstation`, `server`, `miniserver`, `galaxy`, `emacs`, `vps`. Host roles and secure status are defined in `hosts//configuration.nix` and toggles in `hosts//toggles.nix`. +- Standalone Home Manager hosts: `mac` is a planned `aarch64-darwin` Home Manager target under `flake.homeConfigurations`, intended to reuse workstation-style dev and shell modules without Linux GUI app modules. ## Precedence and Conflict Resolution - Precedence: This constitution is authoritative for AI. Human docs must be updated to match. If conflicts are found, align human docs to the constitution and log the resolution in `specs/001-ai-docs/research.md`. diff --git a/docs/reference/index.md b/docs/reference/index.md index c4baf52..8689b65 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -38,8 +38,10 @@ - Usage rule: Use these helpers for repeated wrapper logic; keep feature-specific package sets and config payloads in local `common.nix` or `home.nix` files. ## Hosts and Roles -- Configs: `hosts//configuration.nix` with toggles in `hosts//toggles.nix`. -- Active hosts: `workstation`, `server`, `miniserver`, `galaxy`, `emacs`, `vps`. +- NixOS configs: `hosts//configuration.nix` with toggles in `hosts//toggles.nix`. +- Standalone Home Manager configs: `hosts//home.nix` with optional toggles in `hosts//toggles.nix`. +- Active NixOS hosts: `workstation`, `server`, `miniserver`, `galaxy`, `emacs`, `vps`. +- Active Home Manager hosts: `mac`. - Roles: - workstation: developer desktop; provides build power for distributed builds. - server: primary services host; runs most services and WireGuard targets. @@ -47,8 +49,15 @@ - galaxy: small server variant using nixpkgs-small. - emacs: VM profile, `my.secureHost = false` for secret-free usage. - vps: Linode VPS image target, secure host with enrollment-based secrets. + - mac: planned standalone `aarch64-darwin` Home Manager target for dev and shell tooling. - Network maps: `my.ips` and `my.interfaces` declared in `modules/modules.nix`; host toggles may override. +## Home Configuration Outputs +- NixOS outputs: `flake.nixosConfigurations.` for Linux system hosts. +- Home Manager outputs: `flake.homeConfigurations.` for standalone HM hosts. +- Current standalone HM example: `homeConfigurations.mac`. +- Validation note: Darwin home configurations can be evaluated from Linux, but their activation packages cannot be realized locally on a Linux builder. + ## Proxy, Firewall, and Networking - Proxy enablement: `my.enableProxy` toggles Nginx reverse proxy; assertions require at least one `my.servers.*.enableProxy` when enabled. - Proxy helpers: use `parts/core.nix` helpers (`proxy`, `proxyReverse`, `proxyReverseFix` for header preservation, `proxyReversePrivate` for mutual TLS). `mkserver` supplies `host`, `ip`, `url`, `enableProxy`, and `useDefaultProxy`. diff --git a/hosts/mac/home.nix b/hosts/mac/home.nix new file mode 100644 index 0000000..bec08f3 --- /dev/null +++ b/hosts/mac/home.nix @@ -0,0 +1,11 @@ +{ + inputs, + ... +}: +{ + home = { + username = "cdreyes"; + homeDirectory = "/Users/cdreyes"; + }; + my = import ./toggles.nix { inherit inputs; }; +} diff --git a/hosts/mac/toggles.nix b/hosts/mac/toggles.nix new file mode 100644 index 0000000..64a8312 --- /dev/null +++ b/hosts/mac/toggles.nix @@ -0,0 +1,17 @@ +{ inputs }: +let + inherit (inputs.self.lib) mkEnabled enableList; +in +{ + emacs.enable = true; + dev = enableList mkEnabled [ + "nix" + "python" + "sh" + ]; + shell = enableList mkEnabled [ + "exercism" + "multimedia" + "tools" + ]; +} diff --git a/modules/dev/emacs/home.nix b/modules/dev/emacs/home.nix index 4c74781..523f872 100644 --- a/modules/dev/emacs/home.nix +++ b/modules/dev/emacs/home.nix @@ -7,7 +7,7 @@ ... }: let - shellType = inputs.self.lib.hmShellType config osConfig; + shellType = inputs.self.lib.hmShellType osConfig "zsh"; hm = inputs.self.lib.hmModule { inherit config @@ -39,7 +39,7 @@ in "doom/templates/programming.org".source = ../../../dotfiles/doom/templates/programming.org; }; services = { - lorri.enable = true; + lorri.enable = pkgs.stdenv.isLinux; emacs = { enable = true; defaultEditor = true; diff --git a/modules/dev/nix/home.nix b/modules/dev/nix/home.nix index ceba689..3ef67e3 100644 --- a/modules/dev/nix/home.nix +++ b/modules/dev/nix/home.nix @@ -7,7 +7,7 @@ ... }: let - shellType = inputs.self.lib.hmShellType config osConfig; + shellType = inputs.self.lib.hmShellType osConfig "zsh"; hm = inputs.self.lib.hmModule { inherit config diff --git a/modules/dev/sh.nix b/modules/dev/sh.nix deleted file mode 100644 index 91a0343..0000000 --- a/modules/dev/sh.nix +++ /dev/null @@ -1,43 +0,0 @@ -{ - config, - inputs, - lib, - pkgs, - ... -}: -let - packages = builtins.attrValues { - inherit (pkgs) - shellcheck # Shell script linter - shfmt # Shell parser and formatter - ; - # LSP for Bash and sh - inherit (pkgs.nodePackages) bash-language-server; - }; -in -{ - options = { - my.dev.sh = { - enable = lib.mkEnableOption "Install shell scripting tools globally"; - users = lib.mkOption { - type = inputs.self.lib.usersOptionType lib; - default = config.my.toggleUsers.dev; - description = "Users to install shell scripting packages for"; - }; - }; - devShells.sh = lib.mkOption { - type = lib.types.package; - default = pkgs.mkShell { - inherit packages; - name = "sh-dev-shell"; - shellHook = '' - echo "💻 Shell scripting dev environment" - ''; - }; - description = "Shell scripting dev shell"; - }; - }; - config = lib.mkIf config.my.dev.sh.enable { - users.users = inputs.self.lib.mkUserAttrs lib config.my.dev.sh.users { inherit packages; }; - }; -} diff --git a/modules/dev/sh/common.nix b/modules/dev/sh/common.nix new file mode 100644 index 0000000..e552c39 --- /dev/null +++ b/modules/dev/sh/common.nix @@ -0,0 +1,20 @@ +{ pkgs }: +let + packages = builtins.attrValues { + inherit (pkgs) + shellcheck + shfmt + ; + inherit (pkgs.nodePackages) bash-language-server; + }; +in +{ + inherit packages; + devShell = pkgs.mkShell { + inherit packages; + name = "sh-dev-shell"; + shellHook = '' + echo "💻 Shell scripting dev environment" + ''; + }; +} diff --git a/modules/dev/sh/home.nix b/modules/dev/sh/home.nix new file mode 100644 index 0000000..209068b --- /dev/null +++ b/modules/dev/sh/home.nix @@ -0,0 +1,34 @@ +{ + config, + inputs, + lib, + osConfig ? null, + pkgs, + ... +}: +let + hm = inputs.self.lib.hmModule { + inherit + config + inputs + osConfig + ; + optionPath = [ + "dev" + "sh" + ]; + }; + cfg = config.my.dev.sh; + sh = import ./common.nix { inherit pkgs; }; +in +{ + options.my.dev.sh.enable = lib.mkEnableOption "Install shell scripting tools globally"; + config = lib.mkMerge [ + { + my.dev.sh.enable = lib.mkDefault hm.enabledByDefault; + } + (lib.mkIf cfg.enable { + home.packages = sh.packages; + }) + ]; +} diff --git a/modules/dev/sh/nixos.nix b/modules/dev/sh/nixos.nix new file mode 100644 index 0000000..e08c237 --- /dev/null +++ b/modules/dev/sh/nixos.nix @@ -0,0 +1,27 @@ +{ + config, + inputs, + lib, + pkgs, + ... +}: +let + sh = import ./common.nix { inherit pkgs; }; +in +{ + options = { + my.dev.sh = { + enable = lib.mkEnableOption "Install shell scripting tools globally"; + users = lib.mkOption { + type = inputs.self.lib.usersOptionType lib; + default = config.my.toggleUsers.dev; + description = "Users to install shell scripting packages for"; + }; + }; + devShells.sh = lib.mkOption { + type = lib.types.package; + default = sh.devShell; + description = "Shell scripting dev shell"; + }; + }; +} diff --git a/modules/scripts/download/home.nix b/modules/scripts/download/home.nix index c03374b..48e4d94 100644 --- a/modules/scripts/download/home.nix +++ b/modules/scripts/download/home.nix @@ -7,7 +7,7 @@ ... }: let - shellType = inputs.self.lib.hmShellType config osConfig; + shellType = inputs.self.lib.hmShellType osConfig "zsh"; enabled = inputs.self.lib.hmOnlyUser config osConfig "jawz" && (osConfig.my.units.download.enable || osConfig.my.units.downloadManga.enable); diff --git a/modules/shell/exercism.nix b/modules/shell/exercism.nix deleted file mode 100644 index 3d63aa7..0000000 --- a/modules/shell/exercism.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ - config, - inputs, - lib, - pkgs, - ... -}: -{ - options.my.shell.exercism = { - enable = lib.mkEnableOption "Exercism coding practice platform"; - users = lib.mkOption { - type = inputs.self.lib.usersOptionType lib; - default = config.my.toggleUsers.shell; - description = "Users to install Exercism for"; - }; - }; - config = lib.mkIf config.my.shell.exercism.enable { - users.users = - let - packages = builtins.attrValues { - inherit (pkgs) - exercism # learn to code - bats # testing system, required by Exercism - ; - }; - in - inputs.self.lib.mkUserPackages lib config.my.shell.exercism.users packages; - }; -} diff --git a/modules/shell/exercism/common.nix b/modules/shell/exercism/common.nix new file mode 100644 index 0000000..bf3356a --- /dev/null +++ b/modules/shell/exercism/common.nix @@ -0,0 +1,9 @@ +{ pkgs }: +{ + packages = builtins.attrValues { + inherit (pkgs) + exercism + bats + ; + }; +} diff --git a/modules/shell/exercism/home.nix b/modules/shell/exercism/home.nix new file mode 100644 index 0000000..3807005 --- /dev/null +++ b/modules/shell/exercism/home.nix @@ -0,0 +1,34 @@ +{ + config, + inputs, + lib, + osConfig ? null, + pkgs, + ... +}: +let + hm = inputs.self.lib.hmModule { + inherit + config + inputs + osConfig + ; + optionPath = [ + "shell" + "exercism" + ]; + }; + cfg = config.my.shell.exercism; + exercism = import ./common.nix { inherit pkgs; }; +in +{ + options.my.shell.exercism.enable = lib.mkEnableOption "Exercism coding practice platform"; + config = lib.mkMerge [ + { + my.shell.exercism.enable = lib.mkDefault hm.enabledByDefault; + } + (lib.mkIf cfg.enable { + home.packages = exercism.packages; + }) + ]; +} diff --git a/modules/shell/exercism/nixos.nix b/modules/shell/exercism/nixos.nix new file mode 100644 index 0000000..791593b --- /dev/null +++ b/modules/shell/exercism/nixos.nix @@ -0,0 +1,16 @@ +{ + config, + inputs, + lib, + ... +}: +{ + options.my.shell.exercism = { + enable = lib.mkEnableOption "Exercism coding practice platform"; + users = lib.mkOption { + type = inputs.self.lib.usersOptionType lib; + default = config.my.toggleUsers.shell; + description = "Users to install Exercism for"; + }; + }; +} diff --git a/modules/shell/multimedia/home.nix b/modules/shell/multimedia/home.nix index fb9af1b..4c72fcc 100644 --- a/modules/shell/multimedia/home.nix +++ b/modules/shell/multimedia/home.nix @@ -7,7 +7,7 @@ ... }: let - shellType = inputs.self.lib.hmShellType config osConfig; + shellType = inputs.self.lib.hmShellType osConfig "zsh"; hm = inputs.self.lib.hmModule { inherit config diff --git a/modules/shell/tools/home.nix b/modules/shell/tools/home.nix index 0a65da0..c90eb02 100644 --- a/modules/shell/tools/home.nix +++ b/modules/shell/tools/home.nix @@ -7,7 +7,7 @@ ... }: let - shellType = inputs.self.lib.hmShellType config osConfig; + shellType = inputs.self.lib.hmShellType osConfig "zsh"; hm = inputs.self.lib.hmModule { inherit config diff --git a/parts/core.nix b/parts/core.nix index 16e3269..78f64cd 100644 --- a/parts/core.nix +++ b/parts/core.nix @@ -12,6 +12,10 @@ in systems = [ system ]; flake = { lib = { + commonOverlays = mkpkgs: inputs: [ + (import ../config/overlay.nix { inherit mkpkgs inputs; }) + inputs.doom-emacs.overlays.default + ]; commonModules = name: [ ../hosts/${name}/configuration.nix inputs.nur.modules.nixos.default @@ -19,10 +23,7 @@ in inputs.stylix.nixosModules.stylix inputs.nixtendo-switch.nixosModules.nixtendo-switch { - nixpkgs.overlays = [ - (import ../config/overlay.nix { inherit mkpkgs inputs; }) - inputs.doom-emacs.overlays.default - ]; + nixpkgs.overlays = inputs.self.lib.commonOverlays mkpkgs inputs; } { nix.registry = { @@ -44,6 +45,41 @@ in outputs = inputs.self; }; }; + createHomeConfig = + { + name, + system, + extraModules ? [ ], + extraSpecialArgs ? { }, + }: + let + pkgs = import inputs.nixpkgs { + inherit system; + config.allowUnfree = true; + overlays = inputs.self.lib.commonOverlays mkpkgs inputs; + }; + in + inputs.home-manager.lib.homeManagerConfiguration { + inherit pkgs; + modules = [ + ../modules/home-manager.nix + ../config/home-manager.nix + ../hosts/${name}/home.nix + { + nixpkgs.config.allowUnfree = true; + programs.home-manager.enable = true; + } + ] + ++ extraModules; + extraSpecialArgs = { + inherit inputs; + outputs = inputs.self; + osConfig = null; + preferredShell = "zsh"; + userEmail = "danilo.reyes.251@proton.me"; + } + // extraSpecialArgs; + }; langList = builtins.readDir ../modules/dev |> builtins.attrNames @@ -301,11 +337,7 @@ in inherit osCfg enabledByDefault; }; hmShellType = - config: osConfig: - if osConfig == null then - if config.programs.bash.enable then "bash" else "zsh" - else - osConfig.my.shell.type; + osConfig: preferredShell: if osConfig == null then preferredShell else osConfig.my.shell.type; hmOnlyUser = config: osConfig: user: osConfig != null && config.home.username == user; diff --git a/parts/hosts.nix b/parts/hosts.nix index 33f91e0..ac69e08 100644 --- a/parts/hosts.nix +++ b/parts/hosts.nix @@ -1,11 +1,17 @@ { inputs, ... }: { - flake.nixosConfigurations = { - workstation = inputs.self.lib.createConfig "workstation" inputs.nixpkgs; - miniserver = inputs.self.lib.createConfig "miniserver" inputs.nixpkgs-small; - server = inputs.self.lib.createConfig "server" inputs.nixpkgs-small; - galaxy = inputs.self.lib.createConfig "galaxy" inputs.nixpkgs-small; - emacs = inputs.self.lib.createConfig "emacs" inputs.nixpkgs; - vps = inputs.self.lib.createConfig "vps" inputs.nixpkgs-small; + flake = { + nixosConfigurations = { + workstation = inputs.self.lib.createConfig "workstation" inputs.nixpkgs; + miniserver = inputs.self.lib.createConfig "miniserver" inputs.nixpkgs-small; + server = inputs.self.lib.createConfig "server" inputs.nixpkgs-small; + galaxy = inputs.self.lib.createConfig "galaxy" inputs.nixpkgs-small; + emacs = inputs.self.lib.createConfig "emacs" inputs.nixpkgs; + vps = inputs.self.lib.createConfig "vps" inputs.nixpkgs-small; + }; + homeConfigurations.mac = inputs.self.lib.createHomeConfig { + name = "mac"; + system = "aarch64-darwin"; + }; }; } diff --git a/specs/001-ai-docs/research.md b/specs/001-ai-docs/research.md index b6a2bc9..e1a9c03 100644 --- a/specs/001-ai-docs/research.md +++ b/specs/001-ai-docs/research.md @@ -44,3 +44,8 @@ - **Decision**: Keep split feature modules, but move repeated Home Manager wrapper logic into shared helpers in `parts/core.nix` exposed via `inputs.self.lib`. - **Rationale**: Reduces boilerplate in `home.nix` modules without losing the explicit split between NixOS-owned and Home Manager-owned behavior. - **Alternatives considered**: (a) Leave repeated wrapper code in every `home.nix` (rejected: noisy and brittle); (b) hide full feature behavior behind one generic abstraction (rejected: makes modules harder to read and reason about). + +## Decision 10 (2026-03-16): Standalone Home Manager host for macOS +- **Decision**: Add a standalone `homeConfigurations.mac` target backed by `hosts/mac/home.nix` and `hosts/mac/toggles.nix`, reusing workstation-style dev and shell modules while excluding Linux GUI app modules. +- **Rationale**: Lets the repo model an upcoming macOS machine without forcing NixOS host semantics onto a Home Manager-only system. +- **Alternatives considered**: (a) Wait for the physical Mac and keep no target in the flake (rejected: delays validation and reuse work); (b) model the Mac as a fake NixOS host (rejected: does not match the intended deployment model).