Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b683961
feat(facade): add ToolResult/ToolDescriptor/Subscription/CommandCateg…
Shackless Jun 8, 2026
b67609c
feat(facade): ctx.ai gains converse + summarize + generate_image
Shackless Jun 8, 2026
9abfd76
feat(facade): SkillLocalAiView (support->generate) + SkillMemory
Shackless Jun 8, 2026
6cdcade
feat(facade): ctx.tts gains voice/voices()/speak() (interrupt-positive)
Shackless Jun 8, 2026
fc47072
feat(facade): ctx.audio param rename (volume/fade_out) + Subscription…
Shackless Jun 8, 2026
36f269f
feat(facade): SkillTools (was SkillRegistryView) + source/describe/al…
Shackless Jun 8, 2026
5ceca9d
feat(facade): ctx.commands editable + categories + register_function …
Shackless Jun 8, 2026
e7bfb40
feat(facade): add SkillConversation/SkillSecrets/SkillSkills/SkillSet…
Shackless Jun 8, 2026
6e9910b
refactor(facade): remove SkillRegistryView aliases (clean break, no s…
Shackless Jun 8, 2026
feef609
feat(facade): rewrite WingmanContext as a closed feature-driven surfa…
Shackless Jun 8, 2026
adb4271
refactor(skills): seal skill_base (remove local_ai/retrieve_secret/th…
Shackless Jun 8, 2026
66099cb
docs(skills): add v2->v3 migration guide (users + coding agents)
Shackless Jun 8, 2026
190e560
refactor(hud): tool source via ctx.tools.source (drop registry privates)
Shackless Jun 8, 2026
605b0cf
refactor(timer): migrate to v3 facade (tools/conversation/tts namespa…
Shackless Jun 8, 2026
b3cb3c9
refactor(radio_chatter): migrate to v3 facade (threaded tts helper, c…
Shackless Jun 8, 2026
4583327
feat(facade): ctx.ai.generate accepts a prebuilt messages= list (capped)
Shackless Jun 9, 2026
446db8e
docs(skills): refine v3 migration guide from dogfooding (messages, po…
Shackless Jun 9, 2026
d1c29fc
fix(uexcorp): capped ctx.ai.generate, rename helper threaded_executio…
Shackless Jun 9, 2026
0258cf3
refactor(skills): migrate ats_telemetry/msfs2020/quick_commands/voice…
Shackless Jun 9, 2026
b4b619b
fix(audio_device_changer): unsubscribe via Subscription handle (off_p…
Shackless Jun 9, 2026
fed4e9b
docs(skills): cover off_playback removal, printr/log boundary, unused…
Shackless Jun 9, 2026
a1ca924
test(facade): grep gate for forbidden skill side-doors
Shackless Jun 9, 2026
e1470b2
refactor(skills): drop orphaned SkillLocalAI type import from skill_base
Shackless Jun 9, 2026
50f26b7
docs(skills): document the v3 facade + require api_version in templates
Shackless Jun 9, 2026
b1ebc3d
fix(facade): ToolResult.skill returns name not object; helpful Facade…
Shackless Jun 9, 2026
3c1d635
feat(facade): add ctx.tools.icon() + set_output_device(None) reset; r…
Shackless Jun 9, 2026
b86e08a
refactor(audio_device_changer): switch devices in-process via ctx.aud…
Shackless Jun 9, 2026
0ebfe12
chore(audio_device_changer): drop now-unused aiohttp requirement + ve…
Shackless Jun 9, 2026
f56d9af
docs(skills): complete commands/audio reference rows; tidy stale secr…
Shackless Jun 16, 2026
4265ec0
chore: gitignore tests/ (local-only dev scripts, not shipped or CI'd)
Shackless Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ frontmatter.json

# Wingman AI:
skills/**/dependencies
# Standalone dev test scripts (house convention: run locally with PYTHONPATH=. — not shipped/CI'd)
/tests/
whispercpp/
whispercpp-cuda/
whispercpp-models/
Expand Down
72 changes: 57 additions & 15 deletions skills/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Before creating or modifying a skill, **read [README.md](README.md)** in this directory for full documentation including custom property types, discovery metadata guidelines, dependency bundling, and example skills.

**Migrating an existing skill?** See [MIGRATING-TO-V3.md](MIGRATING-TO-V3.md).

## STOP — Before You Start Implementing

**You MUST ask the user these questions before writing any code:**
Expand Down Expand Up @@ -187,6 +189,7 @@ class YourSkill(Skill):

```yaml
module: skills.your_skill_name.main
api_version: 3 # REQUIRED for v3 — without it the skill is treated as legacy and won't load
name: YourSkillName # Must match class name exactly
display_name: Your Skill Name
author: Your Name
Expand Down Expand Up @@ -223,9 +226,9 @@ async def unload(self) -> None

```python
self.retrieve_custom_property_value(property_id, errors) # Config value (just-in-time!)
await self.retrieve_secret(secret_name, errors, hint) # Secrets via SecretKeeper
await self.wingman.secrets.retrieve(secret_name, errors) # stored secret (prompts user if missing)
self.wingman.config # READ-ONLY view of the config
self.printr.print() / await self.printr.print_async() # Logging
self.log.info(msg) / self.log.warning(msg) / self.log.error(msg) # Logging (server_only=True skips the toast)
self.get_generated_files_dir() # Persistent storage directory
```

Expand All @@ -241,28 +244,67 @@ but you may only **change** things through sanctioned capabilities. Writing to c

# CHANGE (sanctioned capabilities only):
await self.wingman.tts.set_voice(voice) # voice on the CURRENT provider (no switching)
await self.wingman.tts.speak(text, interrupt=True) # say text; interrupt=False waits for current playback
self.wingman.audio.is_playing # read playback state
await self.wingman.audio.play(cfg) / .stop(cfg) # play/stop your own audio
self.wingman.audio.on_playback_started(cb) / .on_playback_finished(cb) # + off_* to unsubscribe
sub = self.wingman.audio.on_playback_started(cb) # returns a Subscription; sub.unsubscribe() in unload()
await self.wingman.audio.set_output_device(device_id) # switch output device in-process
self.wingman.commands.get(name) / .all() / await .save() # read/edit/persist commands
self.wingman.registry.has_tool(name) / await .invoke(name, args) # discover + invoke tools/commands

# MAIN AI — two clearly-different calls (both replace the removed self.llm_call):
text = await self.wingman.ai.generate(prompt, system=..., data=..., image=..., auto_shorten=False)
# single-turn side-call, NOT added to the conversation. Input is CAPPED when conversation
# condensation is on (Wingman Pro hardcoded; own providers config.features.skill_max_input_tokens).
self.wingman.tools.has(name) / await .invoke(name, args) # discover + invoke tools/commands -> ToolResult
self.wingman.tools.source(name) / .all() / .servers() # tool origin + enumerate callable functions / MCP servers

# CONVERSATION:
self.wingman.conversation.history() / .summary # read the live conversation
await self.wingman.conversation.add_user(c) / .add_assistant(c) / .reset()
await self.wingman.conversation.summarize() # summarize the live convo (free, local)

# SECRETS / MEMORY:
await self.wingman.secrets.retrieve(name, errors) # stored secret (prompts user if missing)
self.wingman.memory.available # persistent memory ready?
await self.wingman.memory.remember(c) / .recall(q) / .context(q) / .update(id, c) / .forget(q) / .forget_by_id(id)

# MAIN AI — two clearly-different calls (both replace the removed raw LLM call):
text = await self.wingman.ai.generate(prompt, system=..., data=..., image=..., messages=..., auto_shorten=False)
# single-turn side-call, NOT added to the conversation; returns a str (""). Input is CAPPED when
# conversation condensation is on (Wingman Pro hardcoded; own providers config.features.skill_max_input_tokens).
# Over the cap -> FacadeError (or truncates if auto_shorten=True). Images are charged a flat
# estimate, never the base64 length.
summary = await self.local_ai.summarize(...) # bulk reduction on the FREE local model
# estimate, never the base64 length. Pass messages= to send a prebuilt message list directly.
summary = await self.wingman.local_ai.summarize(...) # bulk reduction on the FREE local model
text2 = await self.wingman.local_ai.generate(t, system=...) # free local single-turn -> str
```

**Removed (do NOT use):** the raw LLM call (`self.llm_call(...)` / `actual_llm_call` — use `self.wingman.ai.generate`),
`self.wingman.switch_tts_provider(...)` (runtime provider switching is not allowed; use `tts.set_voice`),
the raw registries (`self.wingman.registry.*` — use `self.wingman.tools.*`), and writing to
`self.wingman.config` / `self.settings` (read-only; use the capabilities above).

### Calling other skills & MCP servers

```python
# Discover everything callable right now (with origin + params)
for tool in self.wingman.tools.all():
self.log.info(f"{tool.name} (from {tool.source})", server_only=True)

# Call another ACTIVE skill's tool by name
if self.wingman.tools.has("take_screenshot"):
result = await self.wingman.tools.invoke("take_screenshot", {})
self.log.info(f"{result.response} (from {result.skill})")

# Call your own MCP server's tool (many skills ship an MCP for their datasource)
servers = {s["display_name"] for s in self.wingman.tools.servers()}
if "My Data MCP" in servers and self.wingman.tools.has("mydata_query"):
res = await self.wingman.tools.invoke("mydata_query", {"q": "ships"})
data = res.response
else:
self.log.warning("My Data MCP not active; skipping enriched lookup")
```

**Removed (do NOT use):** `self.llm_call(...)` (use `ctx.ai.generate`), `self.wingman.switch_tts_provider(...)`
(runtime provider switching is not allowed), and writing to `self.wingman.config` (use the capabilities above).
MCP tool names are prefixed by the registry — use the name exactly as it appears in
`self.wingman.tools.names()` / `.all()`.

## Local Support Model — Sampling Parameters
## Local Model — Sampling Parameters

Global defaults are tuned for summarization (low temperature). **Override for creative tasks.** Use `SamplingPreset` from `services/skill_local_ai.py` or pass `temperature` / `top_p` directly to `support()`, `support_sync()`, and `summarize()`. Manual values override presets. See `SamplingPreset` docstring for available presets and values.
Global defaults are tuned for summarization (low temperature). **Override for creative tasks.** Use `SamplingPreset` from `services/skill_local_ai.py` or pass `temperature` / `top_p` directly to `self.wingman.local_ai.generate()`, `.generate_sync()`, and `.summarize()`. Manual values override presets. See `SamplingPreset` docstring for available presets and values.

## Example Skills

Expand Down
Loading
Loading