Restructure as a self-contained Agent Skills package (fix Codex install)
Move the skill into skills/watch/ so SKILL.md and its scripts/ runtime are
siblings inside one folder. `npx skills add` (Codex/Cursor/Copilot/agents) now
copies a working skill as a unit; previously it grabbed the root SKILL.md alone
and left scripts/ behind, so the skill was dead on arrival on every non-Claude
host. Mirrors the layout last30days-skill adopted for the same reason.
- skills/watch/{SKILL.md,scripts/}: self-contained skill folder
- SKILL.md: resolve a harness-agnostic $SKILL_DIR (the dir it was Read from)
instead of the Claude-Code-only ${CLAUDE_SKILL_DIR}; guard + 19 call sites
- drop commands/watch.md: /watch derives from frontmatter (name + user-invocable)
- .codex-plugin/plugin.json: full manifest with "skills": "./skills/" + interface
- add .agents/plugins/marketplace.json, AGENTS.md, CLAUDE.md, .skillignore
- build-skill.sh: archive the skills/watch subtree (one SKILL.md, no zip -d)
- fix paths in tests, hooks hint, .gitattributes, release.yml
- relocate dev-sync.sh to repo root and fix REPO_ROOT
- README: content-ideas structure, npx skills install, star history
Verified: 37/37 tests pass; npx skills add bundles the full scripts/ runtime;
manifests valid; versions synced at 0.1.3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Shared /watch configuration helpers."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CONFIG_DIR = Path.home() / ".config" / "watch"
|
||||
CONFIG_FILE = CONFIG_DIR / ".env"
|
||||
|
||||
DEFAULT_DETAIL = "balanced"
|
||||
|
||||
DETAILS = {"transcript", "efficient", "balanced", "token-burner"}
|
||||
|
||||
|
||||
def read_env_file(path: Path | None = None) -> dict[str, str]:
|
||||
if path is None:
|
||||
path = CONFIG_FILE
|
||||
values: dict[str, str] = {}
|
||||
if not path.exists():
|
||||
return values
|
||||
try:
|
||||
lines = path.read_text(encoding="utf-8").splitlines()
|
||||
except OSError:
|
||||
return values
|
||||
for line in lines:
|
||||
raw = line.strip()
|
||||
if not raw or raw.startswith("#") or "=" not in raw:
|
||||
continue
|
||||
key, _, value = raw.partition("=")
|
||||
value = value.strip()
|
||||
if len(value) >= 2 and value[0] in ('"', "'") and value[-1] == value[0]:
|
||||
value = value[1:-1]
|
||||
values[key.strip()] = value
|
||||
return values
|
||||
|
||||
|
||||
def get_config() -> dict[str, object]:
|
||||
file_values = read_env_file()
|
||||
|
||||
detail = (
|
||||
os.environ.get("WATCH_DETAIL")
|
||||
or file_values.get("WATCH_DETAIL")
|
||||
or DEFAULT_DETAIL
|
||||
)
|
||||
if detail not in DETAILS:
|
||||
detail = DEFAULT_DETAIL
|
||||
|
||||
return {
|
||||
"detail": detail,
|
||||
"config_file": str(CONFIG_FILE),
|
||||
}
|
||||
|
||||
|
||||
def frame_cap(detail: str) -> int | None:
|
||||
if detail == "efficient":
|
||||
return 50
|
||||
if detail == "balanced":
|
||||
return 100
|
||||
if detail == "token-burner":
|
||||
return None
|
||||
if detail == "transcript":
|
||||
return None
|
||||
return 100
|
||||
Reference in New Issue
Block a user