arkaos 4.31.0 → 4.32.0

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 (43) hide show
  1. package/README.md +1 -1
  2. package/THE-ARKAOS-GUIDE.md +1 -1
  3. package/VERSION +1 -1
  4. package/arka/SKILL.md +1 -1
  5. package/config/claude-agents/brand-director.md +1 -1
  6. package/config/claude-agents/content-strategist.md +1 -1
  7. package/config/claude-agents/frontend-dev.md +1 -1
  8. package/config/claude-agents/marketing-director.md +1 -1
  9. package/config/claude-agents/scriptwriter.md +1 -1
  10. package/config/claude-agents/trends-analyst.md +1 -1
  11. package/config/claude-agents/video-producer.md +1 -1
  12. package/config/skills-curated.yaml +2 -1
  13. package/config/skills-provenance.yaml +6 -0
  14. package/core/keys.py +17 -5
  15. package/departments/brand/agents/brand-director.yaml +1 -0
  16. package/departments/content/agents/content-strategist.yaml +1 -0
  17. package/departments/content/agents/production/trends-analyst.yaml +1 -0
  18. package/departments/content/agents/production/video-producer.yaml +1 -0
  19. package/departments/content/agents/scriptwriter.yaml +1 -0
  20. package/departments/dev/agents/frontend-dev.yaml +1 -0
  21. package/departments/dev/skills/animated-website/SKILL.md +14 -1
  22. package/departments/dev/skills/watch/SKILL.md +162 -0
  23. package/departments/dev/skills/watch/references/claude-video.LICENSE +21 -0
  24. package/departments/dev/skills/watch/scripts/config.py +145 -0
  25. package/departments/dev/skills/watch/scripts/download.py +179 -0
  26. package/departments/dev/skills/watch/scripts/frames.py +762 -0
  27. package/departments/dev/skills/watch/scripts/setup.py +373 -0
  28. package/departments/dev/skills/watch/scripts/transcribe.py +95 -0
  29. package/departments/dev/skills/watch/scripts/watch.py +531 -0
  30. package/departments/dev/skills/watch/scripts/whisper.py +466 -0
  31. package/departments/marketing/agents/marketing-director.yaml +1 -0
  32. package/departments/marketing/skills/ad-creative/SKILL.md +5 -0
  33. package/harness/codex/AGENTS.md +1 -1
  34. package/harness/copilot/copilot-instructions.md +1 -1
  35. package/harness/cursor/rules/arkaos.mdc +2 -2
  36. package/harness/gemini/GEMINI.md +1 -1
  37. package/harness/opencode/AGENTS.md +1 -1
  38. package/harness/zed/.rules +1 -1
  39. package/installer/doctor.js +38 -4
  40. package/knowledge/agents-registry-v2.json +8 -1
  41. package/knowledge/skills-manifest.json +14 -1
  42. package/package.json +1 -1
  43. package/pyproject.toml +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **The Operating System for AI Agent Teams.**
4
4
 
5
- 89 agents. 17 departments. 329 skills. Enterprise frameworks. Multi-runtime. One install.
5
+ 89 agents. 17 departments. 330 skills. Enterprise frameworks. Multi-runtime. One install.
6
6
 
7
7
  ```bash
8
8
  npx arkaos install
@@ -1,6 +1,6 @@
1
1
  # The ArkaOS Guide
2
2
 
3
- > v4.31.0 — 89 agents, 17 departments, 329 skills, 297 commands, 16 ADRs.
3
+ > v4.32.0 — 89 agents, 17 departments, 330 skills, 297 commands, 16 ADRs.
4
4
  > One file, everything you need to start. Generated by `scripts/guide_gen.py` — never hand-edited.
5
5
 
6
6
  ## What it is
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.31.0
1
+ 4.32.0
package/arka/SKILL.md CHANGED
@@ -47,7 +47,7 @@ complementary, not alternatives.
47
47
  # ArkaOS — Main Orchestrator
48
48
 
49
49
  > **The Operating System for AI Agent Teams**
50
- > 89 agents. 17 departments. 329 skills. Multi-runtime. Dashboard. Knowledge RAG.
50
+ > 89 agents. 17 departments. 330 skills. Multi-runtime. Dashboard. Knowledge RAG.
51
51
 
52
52
  ## ⛔ Evidence flow — 4 gates (NON-NEGOTIABLE)
53
53
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: brand-director
3
3
  description: >
4
- Valentina — Creative Director (brand squad). Executes: brand identity creation, visual design direction, UX/UI strategy, design systems.
4
+ Valentina — Creative Director (brand squad). Executes: brand identity creation, reference-video visual analysis via dev/watch (complete frames + transcript — motion and art direction judged on evidence, never on screenshots), visual design direction, UX/UI strategy.
5
5
  model: sonnet
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: content-strategist
3
3
  description: >
4
- Rafael — Content Strategist (content squad). Executes: viral content design, hook writing & packaging, script structure, content operating systems.
4
+ Rafael — Content Strategist (content squad). Executes: viral content design, reference-video analysis via dev/watch (frames + timestamped transcript before judging any video), hook writing & packaging, script structure.
5
5
  model: sonnet
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: frontend-dev
3
3
  description: >
4
- Diana — Senior Frontend Developer (dev squad). Executes: Vue 3 (Composition API), Nuxt 3, React 19 / Next.js 15, TypeScript.
4
+ Diana — Senior Frontend Developer (dev squad). Executes: Vue 3 (Composition API), motion-reference analysis via dev/watch (scroll/animation benchmarks from frames + transcript before implementing), Nuxt 3, React 19 / Next.js 15.
5
5
  model: opus
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: marketing-director
3
3
  description: >
4
- Luna — Marketing Director (marketing squad). Executes: growth strategy, content marketing, SEO, paid acquisition.
4
+ Luna — Marketing Director (marketing squad). Executes: growth strategy, video-ad teardown via dev/watch (hook, pacing and spoken-copy evidence from frames + transcript), content marketing, SEO.
5
5
  model: sonnet
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: scriptwriter
3
3
  description: >
4
- Joana — Scriptwriter (content squad). Executes: video scriptwriting (YouTube, Reels, TikTok), cinematic narrative for short and long-form, storyboard-ready scene writing (scene/shot/VO/on-screen-text columns), narrative structure (hook-bridge-body-CTA; beat sheets; story circles).
4
+ Joana — Scriptwriter (content squad). Executes: video scriptwriting (YouTube, Reels, TikTok), reference-video teardown via dev/watch (hook, pacing and beat analysis from frames + transcript), cinematic narrative for short and long-form, storyboard-ready scene writing (scene/shot/VO/on-screen-text columns).
5
5
  model: opus
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: trends-analyst
3
3
  description: >
4
- Margarida — Trends & Niche Analyst (content squad). Executes: trend detection across platforms (Agent-Reach: X, Reddit, YouTube, RSS, Exa), niche viability scoring (audience size, competition density, monetization path, brand fit), platform algorithm shift monitoring, whitespace analysis for new channels and formats.
4
+ Margarida — Trends & Niche Analyst (content squad). Executes: trend detection across platforms (Agent-Reach: X, Reddit, YouTube, RSS, Exa), viral-video deconstruction via dev/watch (scene-aware frames + transcript, timestamp-cited), niche viability scoring (audience size, competition density, monetization path, brand fit), platform algorithm shift monitoring.
5
5
  model: sonnet
6
6
  ---
7
7
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: video-producer
3
3
  description: >
4
- Simão — Video Producer & Production Lead (content squad). Executes: video production pipelines (script → storyboard → assets → edit → render), Hyperframes video-as-code editing (HTML/CSS/JS + GSAP → MP4), Higgsfield generation orchestration (image, video, audio, motion control, upscale, reframe), shot lists and EDLs (scene/shot/VO/on-screen-text columns).
4
+ Simão — Video Producer & Production Lead (content squad). Executes: video production pipelines (script → storyboard → assets → edit → render), cut review via dev/watch (frame + transcript evidence on own renders before the Quality Gate), Hyperframes video-as-code editing (HTML/CSS/JS + GSAP → MP4), Higgsfield generation orchestration (image, video, audio, motion control, upscale, reframe).
5
5
  model: opus
6
6
  ---
7
7
 
@@ -24,7 +24,8 @@ curated_subskills:
24
24
  community: [moderation]
25
25
  content: [calendar, hook-write]
26
26
  dev: [api-design, architecture-design, clean-code-review, code-review,
27
- db-design, refactor-plan, runbook, security-audit, spec, tdd-cycle]
27
+ db-design, refactor-plan, runbook, security-audit, spec, tdd-cycle,
28
+ watch]
28
29
  ecom: [cart-recovery, store-audit]
29
30
  finance: [financial-model, unit-economics]
30
31
  kb: [search-kb, zettelkasten-process]
@@ -91,6 +91,12 @@ derived:
91
91
  origin: community
92
92
  source: https://github.com/nutlope/hallmark
93
93
  license: MIT
94
+ # Watch absorption campaign — video-input primitive derived from
95
+ # bradautomates/claude-video v0.2.0 (MIT), maintained natively.
96
+ departments/dev/skills/watch:
97
+ origin: community
98
+ source: https://github.com/bradautomates/claude-video
99
+ license: MIT
94
100
  # Marketing skills integration campaign — derived from
95
101
  # coreyhaines31/marketingskills (MIT). Frontmatter metadata must match
96
102
  # each entry field-for-field (test_registry_matches_frontmatter_field_for_field).
package/core/keys.py CHANGED
@@ -7,15 +7,27 @@ import json
7
7
  import os
8
8
  import stat
9
9
  from pathlib import Path
10
- from typing import Optional
11
10
 
12
11
  KEYS_PATH = Path.home() / ".arkaos" / "keys.json"
13
12
 
14
13
  # Known providers with descriptions
15
14
  PROVIDERS = {
16
- "OPENAI_API_KEY": {"name": "OpenAI", "used_for": "Whisper transcription, embeddings, GPT"},
17
- "GOOGLE_API_KEY": {"name": "Google", "used_for": "Gemini API, Nano Banana, Google Cloud AI"},
18
- "FAL_API_KEY": {"name": "fal.ai", "used_for": "Image generation, video generation"},
15
+ "OPENAI_API_KEY": {
16
+ "name": "OpenAI",
17
+ "used_for": "Whisper transcription (/watch fallback), embeddings, GPT",
18
+ },
19
+ "GOOGLE_API_KEY": {
20
+ "name": "Google",
21
+ "used_for": "Gemini API, Nano Banana, Google Cloud AI",
22
+ },
23
+ "FAL_API_KEY": {
24
+ "name": "fal.ai",
25
+ "used_for": "Image generation, video generation",
26
+ },
27
+ "GROQ_API_KEY": {
28
+ "name": "Groq",
29
+ "used_for": "Whisper transcription for /watch (whisper-large-v3 — cheaper, faster)",
30
+ },
19
31
  }
20
32
 
21
33
 
@@ -31,7 +43,7 @@ def _save(keys: dict[str, str]) -> None:
31
43
  os.chmod(KEYS_PATH, stat.S_IRUSR | stat.S_IWUSR) # 600
32
44
 
33
45
 
34
- def get_key(name: str) -> Optional[str]:
46
+ def get_key(name: str) -> str | None:
35
47
  """Get an API key by name. Also checks environment variables."""
36
48
  env_val = os.environ.get(name)
37
49
  if env_val:
@@ -52,6 +52,7 @@ authority:
52
52
  expertise:
53
53
  domains:
54
54
  - brand identity creation
55
+ - reference-video visual analysis via dev/watch (complete frames + transcript — motion and art direction judged on evidence, never on screenshots)
55
56
  - visual design direction
56
57
  - UX/UI strategy
57
58
  - design systems
@@ -48,6 +48,7 @@ authority:
48
48
  expertise:
49
49
  domains:
50
50
  - viral content design
51
+ - reference-video analysis via dev/watch (frames + timestamped transcript before judging any video)
51
52
  - hook writing & packaging
52
53
  - script structure
53
54
  - content operating systems
@@ -45,6 +45,7 @@ authority:
45
45
  expertise:
46
46
  domains:
47
47
  - "trend detection across platforms (Agent-Reach: X, Reddit, YouTube, RSS, Exa)"
48
+ - viral-video deconstruction via dev/watch (scene-aware frames + transcript, timestamp-cited)
48
49
  - niche viability scoring (audience size, competition density, monetization path, brand fit)
49
50
  - platform algorithm shift monitoring
50
51
  - whitespace analysis for new channels and formats
@@ -51,6 +51,7 @@ authority:
51
51
  expertise:
52
52
  domains:
53
53
  - video production pipelines (script → storyboard → assets → edit → render)
54
+ - cut review via dev/watch (frame + transcript evidence on own renders before the Quality Gate)
54
55
  - Hyperframes video-as-code editing (HTML/CSS/JS + GSAP → MP4)
55
56
  - Higgsfield generation orchestration (image, video, audio, motion control, upscale, reframe)
56
57
  - shot lists and EDLs (scene/shot/VO/on-screen-text columns)
@@ -43,6 +43,7 @@ authority:
43
43
  expertise:
44
44
  domains:
45
45
  - video scriptwriting (YouTube, Reels, TikTok)
46
+ - reference-video teardown via dev/watch (hook, pacing and beat analysis from frames + transcript)
46
47
  - cinematic narrative for short and long-form
47
48
  - storyboard-ready scene writing (scene/shot/VO/on-screen-text columns)
48
49
  - narrative structure (hook-bridge-body-CTA; beat sheets; story circles)
@@ -47,6 +47,7 @@ authority:
47
47
  expertise:
48
48
  domains:
49
49
  - Vue 3 (Composition API)
50
+ - motion-reference analysis via dev/watch (scroll/animation benchmarks from frames + transcript before implementing)
50
51
  - Nuxt 3
51
52
  - React 19 / Next.js 15
52
53
  - TypeScript
@@ -76,7 +76,20 @@ If the user gives just a video path and a vague concept, ask ONE clarifying ques
76
76
 
77
77
  ### Step 1: Analyze the Video
78
78
 
79
- Probe the video to understand what you're working with:
79
+ **See the content first (dev/watch primitive).** Before any design decision,
80
+ watch the source with the native `dev/watch` skill so overlay copy, section
81
+ pacing and dwell points are grounded in what is actually on screen — the
82
+ "complete frames" doctrine, never screenshots or guesses:
83
+
84
+ ```bash
85
+ PY="$HOME/.arkaos/bin/arka-py"; command -v "$PY" >/dev/null || PY=python3
86
+ "$PY" ~/.claude/skills/arka-watch/scripts/watch.py "/path/to/video.mp4" --no-whisper
87
+ ```
88
+
89
+ Read every frame the report lists, note the key visual beats (`t=MM:SS`) —
90
+ they become section boundaries and dwell points in Step 4.
91
+
92
+ Then probe the technical metadata:
80
93
 
81
94
  ```bash
82
95
  ffprobe -v quiet -print_format json -show_format -show_streams "/path/to/video.mp4"
@@ -0,0 +1,162 @@
1
+ ---
2
+ name: dev/watch
3
+ description: >
4
+ Watch a video (URL or local file) so the agent can answer questions about
5
+ what is on screen and what is said. Downloads with yt-dlp, extracts
6
+ auto-scaled frames with ffmpeg (scene-aware or fast keyframes, near-duplicate
7
+ dedup), pulls a timestamped transcript (native captions first, Whisper API
8
+ fallback), and prints frame paths for the agent to Read. TRIGGER: a video
9
+ URL (YouTube, Loom, TikTok, X, Vimeo…) or local video file plus a question;
10
+ "vê este vídeo", "analisa este vídeo", "watch this video", "o que acontece
11
+ em/aos X min", reference-video analysis before design work, video deliverable
12
+ review in the Quality Gate, screen-recording bug reports, "/dev watch".
13
+ SKIP: video PRODUCTION or generation -> content/video-produce or Higgsfield;
14
+ turning a video into a scroll site -> dev/animated-website (it calls this
15
+ skill for its analysis phase); audio-only transcription of a podcast with no
16
+ visual questions -> run --detail transcript and summarize.
17
+ allowed-tools: [Read, Bash, AskUserQuestion]
18
+ metadata:
19
+ origin: community
20
+ source: https://github.com/bradautomates/claude-video
21
+ license: MIT
22
+ ---
23
+
24
+ <!-- arka:kb-first-prefix begin -->
25
+ > **KB-first:** query `mcp__obsidian__search_notes` (and
26
+ > `mcp__graphify__query_graph` when configured) and cite `[[wikilinks]]`
27
+ > or graph nodes — or declare the gap — BEFORE any external research.
28
+ > Full doctrine: `arka/SKILL.md` (KB-First Research).
29
+ <!-- arka:kb-first-prefix end -->
30
+
31
+ # /watch — video input for ArkaOS agents
32
+
33
+ You don't have a video input; this skill gives you one. A bundled Python
34
+ pipeline gets captions first, downloads only what it needs, extracts frames
35
+ as JPEGs, produces a timestamped transcript, and prints frame paths. You then
36
+ `Read` each frame to see the video and answer grounded in evidence — the way
37
+ the "frames COMPLETOS" doctrine requires for reference-video analysis.
38
+
39
+ Absorbed from `bradautomates/claude-video` v0.2.0 (MIT) and maintained
40
+ natively by ArkaOS. ArkaOS-specific behavior: config lives in
41
+ `~/.arkaos/watch.env`, API keys resolve from `/arka keys`
42
+ (`~/.arkaos/keys.json`) before any .env file, and every run appends a usage
43
+ record to `~/.arkaos/telemetry/watch-usage.jsonl` (frames + estimated image
44
+ tokens) so CostGovernor sees video analysis cost.
45
+
46
+ ## Resolve `SKILL_DIR` (before any command)
47
+
48
+ Set `SKILL_DIR` to the absolute path of the directory containing THIS
49
+ SKILL.md (your harness showed it when you Read this file). The scripts are at
50
+ `SKILL_DIR/scripts/`. Guard once:
51
+
52
+ ```bash
53
+ [ -f "$SKILL_DIR/scripts/watch.py" ] || { echo "watch.py not under $SKILL_DIR" >&2; exit 1; }
54
+ ```
55
+
56
+ Run scripts with the canonical ArkaOS interpreter when present, else python3:
57
+
58
+ ```bash
59
+ PY="$HOME/.arkaos/bin/arka-py"; command -v "$PY" >/dev/null || PY=python3
60
+ ```
61
+
62
+ ## Step 0 — preflight (silent on success)
63
+
64
+ ```bash
65
+ "$PY" "${SKILL_DIR}/scripts/setup.py" --check
66
+ ```
67
+
68
+ Exit 0 → proceed without comment (keyless-after-setup counts as ready — do
69
+ NOT announce that setup is complete). Non-zero:
70
+
71
+ | Exit | Meaning | Action |
72
+ |------|---------|--------|
73
+ | 2 | ffmpeg / ffprobe / yt-dlp missing or broken (a binary that dies at load counts as missing) | run `"$PY" "${SKILL_DIR}/scripts/setup.py"` — auto-installs via brew on macOS, prints commands elsewhere |
74
+ | 3 | genuine first run, no Whisper key | encourage a key (see Keys); user may decline → proceed with `--no-whisper` |
75
+ | 4 | both | installer, then key |
76
+
77
+ `--json` gives the structured status (`status`, `can_proceed`, `first_run`,
78
+ `missing_binaries`, `whisper_backend`, `watch_detail`, `config_file`).
79
+ First-run flow: install binaries first, let the installer scaffold
80
+ `~/.arkaos/watch.env`, then ask the detail preference below.
81
+
82
+ **Keys (Whisper fallback):** preferred home is `/arka keys` —
83
+ `OPENAI_API_KEY` (whisper-1) or `GROQ_API_KEY` (whisper-large-v3, cheaper —
84
+ preferred when both exist). Resolution order: environment →
85
+ `~/.arkaos/keys.json` → `~/.arkaos/watch.env` → `./.env`. A missing key is
86
+ encouraged, never a blocker: captions cover most public videos free.
87
+
88
+ **First-run detail preference:** ask once via `AskUserQuestion` (order
89
+ lightest→heaviest, keep "(recommended)" on balanced): `transcript` (no
90
+ frames) · `efficient` (keyframes, cap 50) · `balanced` (recommended —
91
+ scene-aware, cap 100) · `token-burner` (uncapped). Write the bare
92
+ `WATCH_DETAIL=<value>` line (no inline comment) into `~/.arkaos/watch.env`,
93
+ then `SETUP_COMPLETE=true`.
94
+
95
+ ## Invoke
96
+
97
+ ```bash
98
+ "$PY" "${SKILL_DIR}/scripts/watch.py" "<url-or-path>"
99
+ ```
100
+
101
+ Flags: `--detail transcript|efficient|balanced|token-burner` ·
102
+ `--start T` / `--end T` (SS, MM:SS, HH:MM:SS — focused mode, denser fps) ·
103
+ `--timestamps T1,T2,…` (pin frames at transcript-flagged moments) ·
104
+ `--max-frames N` · `--resolution W` (default 512; 1024 only to read on-screen
105
+ text — ~4× image tokens) · `--fps F` (≤2) · `--out-dir DIR` ·
106
+ `--whisper groq|openai` · `--no-whisper` · `--no-dedup`.
107
+
108
+ Then: **Read every frame path the report lists, in one parallel batch.**
109
+ Frames carry `t=MM:SS` absolute timestamps aligned with the transcript.
110
+ Answer citing timestamps; with no question, summarize structure, key moments,
111
+ and spoken content. At `transcript` detail, still synthesize a summary — never
112
+ paste the raw transcript.
113
+
114
+ Cleanup: the report prints the work dir — `rm -rf` it unless follow-ups are
115
+ likely. Follow-ups reuse what is already in context; do NOT re-run.
116
+
117
+ ## Frame budget discipline
118
+
119
+ Cost is dominated by frames (~197 image tokens each at 512px). Full-video
120
+ budgets by duration: ≤30s ~30 · ≤1min ~40 · ≤3min ~60 · ≤10min ~80 · >10min
121
+ sparse (warning printed). When the user names a moment or the video exceeds
122
+ ~10 minutes, prefer focused mode (`--start`/`--end` — up to 2 fps) over a
123
+ sparse full scan. Dedup collapses held slides and static recordings before
124
+ the cap so the budget buys distinct content.
125
+
126
+ **Transcript-cue frames:** scene selection misses deictic moments ("look
127
+ here", "as you can see") because pointing is low visual change. Read the
128
+ transcript, judge which cues matter (ignore rhetorical "look…"), re-run with
129
+ `--timestamps 4:32,7:10` pointed at the downloaded file in the work dir.
130
+
131
+ ## KB write-back (summaries become notes)
132
+
133
+ When the user asks to keep the analysis ("summarize this to a note",
134
+ "guarda isto na KB") — or when the analysis is reference research worth
135
+ keeping (competitor teardown, course/talk notes) — write the summary to the
136
+ Obsidian vault via `mcp__obsidian__write_note`: YAML frontmatter (`type:
137
+ video-analysis`, source URL, duration, transcript source), key moments as a
138
+ timestamped list, and `[[wikilinks]]` to related notes. One note per video;
139
+ a playlist becomes a set of notes plus a MOC. Ask before writing when the
140
+ user did not request persistence.
141
+
142
+ ## Failure modes
143
+
144
+ - Download fails (login/region-locked) → tell the user plainly; don't retry.
145
+ - No captions AND no key → frames-only, say so, offer `/arka keys`.
146
+ - Whisper chunk failures → transcript is partial; noted on stderr; retry with
147
+ the other backend if needed.
148
+ - Long-video warning → acknowledge it and offer a focused re-run.
149
+ - Client-confidential recordings → run `--no-whisper` (audio must not leave
150
+ the machine; only extracted audio ever goes to a Whisper API, and only
151
+ when captions are missing).
152
+
153
+ ## Security
154
+
155
+ Local-only except: extracted mono audio → `api.groq.com` /
156
+ `api.openai.com`, only on Whisper fallback. yt-dlp requests public data; no
157
+ accounts, no cookies. Keys are never logged or echoed. Working files stay in
158
+ the work dir; config/keys in `~/.arkaos/` (0600).
159
+
160
+ Scripts: `watch.py` (entry) · `download.py` (yt-dlp) · `frames.py` (ffmpeg +
161
+ dedup) · `transcribe.py` (captions) · `whisper.py` (Groq/OpenAI clients) ·
162
+ `setup.py` (preflight/installer) · `config.py` (paths, keys, telemetry).
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bradley Bonanno
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,145 @@
1
+ #!/usr/bin/env python3
2
+ """Shared /watch configuration helpers (ArkaOS-native).
3
+
4
+ Every filesystem anchor resolves at CALL time, never import time, so a
5
+ redirected HOME (tests, sandboxes) always takes effect. Key resolution
6
+ order: environment -> ~/.arkaos/keys.json (managed by `/arka keys`) ->
7
+ ~/.arkaos/watch.env -> ./.env.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import sys
14
+ import time
15
+ from pathlib import Path
16
+
17
+ DEFAULT_DETAIL = "balanced"
18
+
19
+ DETAILS = {"transcript", "efficient", "balanced", "token-burner"}
20
+
21
+
22
+ def arkaos_home() -> Path:
23
+ """User-data root — ~/.arkaos, or ARKAOS_HOME when set."""
24
+ override = os.environ.get("ARKAOS_HOME")
25
+ if override:
26
+ return Path(override).expanduser()
27
+ return Path.home() / ".arkaos"
28
+
29
+
30
+ def config_file() -> Path:
31
+ return arkaos_home() / "watch.env"
32
+
33
+
34
+ def keys_file() -> Path:
35
+ return arkaos_home() / "keys.json"
36
+
37
+
38
+ def telemetry_file() -> Path:
39
+ return arkaos_home() / "telemetry" / "watch-usage.jsonl"
40
+
41
+
42
+ def read_env_file(path: Path | None = None) -> dict[str, str]:
43
+ if path is None:
44
+ path = config_file()
45
+ values: dict[str, str] = {}
46
+ if not path.exists():
47
+ return values
48
+ try:
49
+ lines = path.read_text(encoding="utf-8").splitlines()
50
+ except OSError:
51
+ return values
52
+ for line in lines:
53
+ raw = line.strip()
54
+ if not raw or raw.startswith("#") or "=" not in raw:
55
+ continue
56
+ key, _, value = raw.partition("=")
57
+ value = value.strip()
58
+ if len(value) >= 2 and value[0] in ('"', "'") and value[-1] == value[0]:
59
+ value = value[1:-1]
60
+ else:
61
+ # Strip an inline comment (a '#' preceded by whitespace) from an
62
+ # unquoted value. Without this, `WATCH_DETAIL=balanced # note`
63
+ # parses as "balanced # note", fails validation, and silently
64
+ # falls back to the default. Keeps '#' inside quotes / API keys.
65
+ for i, ch in enumerate(value):
66
+ if ch == "#" and i > 0 and value[i - 1] in " \t":
67
+ value = value[:i].rstrip()
68
+ break
69
+ values[key.strip()] = value
70
+ return values
71
+
72
+
73
+ def read_keys_json() -> dict[str, str]:
74
+ """API keys stored by `/arka keys` (~/.arkaos/keys.json). Fail-open."""
75
+ path = keys_file()
76
+ if not path.exists():
77
+ return {}
78
+ try:
79
+ data = json.loads(path.read_text(encoding="utf-8"))
80
+ except (OSError, ValueError):
81
+ return {}
82
+ if not isinstance(data, dict):
83
+ return {}
84
+ return {str(k): str(v) for k, v in data.items() if v}
85
+
86
+
87
+ def resolve_api_key(name: str) -> str | None:
88
+ """env -> ~/.arkaos/keys.json -> ~/.arkaos/watch.env -> ./.env."""
89
+ value = os.environ.get(name)
90
+ if value and value.strip():
91
+ return value.strip()
92
+ value = read_keys_json().get(name)
93
+ if value and value.strip():
94
+ return value.strip()
95
+ for path in (config_file(), Path.cwd() / ".env"):
96
+ value = read_env_file(path).get(name)
97
+ if value and value.strip():
98
+ return value.strip()
99
+ return None
100
+
101
+
102
+ def get_config() -> dict[str, object]:
103
+ file_values = read_env_file()
104
+
105
+ detail = (
106
+ os.environ.get("WATCH_DETAIL")
107
+ or file_values.get("WATCH_DETAIL")
108
+ or DEFAULT_DETAIL
109
+ )
110
+ if detail not in DETAILS:
111
+ detail = DEFAULT_DETAIL
112
+
113
+ return {
114
+ "detail": detail,
115
+ "config_file": str(config_file()),
116
+ }
117
+
118
+
119
+ def frame_cap(detail: str) -> int | None:
120
+ if detail == "efficient":
121
+ return 50
122
+ if detail == "balanced":
123
+ return 100
124
+ if detail == "token-burner":
125
+ return None
126
+ if detail == "transcript":
127
+ return None
128
+ return 100
129
+
130
+
131
+ def record_telemetry(event: dict) -> None:
132
+ """Append one usage record to ~/.arkaos/telemetry/watch-usage.jsonl.
133
+
134
+ Observability must never break a watch run: filesystem failures
135
+ (OSError) are reported to stderr and swallowed (fail-open). Callers
136
+ pass only JSON-serializable scalars, so serialization cannot raise.
137
+ """
138
+ try:
139
+ path = telemetry_file()
140
+ path.parent.mkdir(parents=True, exist_ok=True)
141
+ record = {"ts": round(time.time(), 3), **event}
142
+ with path.open("a", encoding="utf-8") as fh:
143
+ fh.write(json.dumps(record, ensure_ascii=False) + "\n")
144
+ except OSError as exc:
145
+ print(f"[watch] telemetry write skipped: {exc}", file=sys.stderr)