@moxxy/cli 0.0.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../tools-builtin/src/util.ts","../../tools-builtin/src/read-handler.ts"],"names":["fs"],"mappings":";;;;;AAcO,SAAS,WAAA,CAAY,KAAa,MAAA,EAAwB;AAC/D,EAAA,IAAS,IAAA,CAAA,UAAA,CAAW,MAAM,CAAA,EAAG,OAAY,eAAU,MAAM,CAAA;AACzD,EAAA,OAAY,IAAA,CAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AACjC;AAsBO,IAAM,WAAA,GAAc,WAAA;AAEpB,SAAS,WAAA,CAAY,GAAW,GAAA,EAAqB;AAC1D,EAAA,IAAI,CAAA,CAAE,MAAA,IAAU,GAAA,EAAK,OAAO,CAAA;AAC5B,EAAA,OAAO,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,GAAI;AAAA,eAAA,EAAoB,CAAA,CAAE,SAAS,GAAG,CAAA,OAAA,CAAA;AAC7D;;;ACnBA,eAAsB,WAAA,CAAY,OAAkB,GAAA,EAAmC;AACrF,EAAA,MAAM,EAAE,SAAA,EAAW,MAAA,GAAS,CAAA,EAAG,KAAA,GAAQ,KAAK,GAAI,KAAA;AAChD,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,GAAA,CAAI,GAAA,EAAK,SAAS,CAAA;AAO/C,EAAA,MAAM,OAAO,GAAA,CAAI,EAAA,GACb,MAAM,GAAA,CAAI,EAAA,CAAG,SAAS,QAAA,EAAU,EAAE,UAAU,MAAA,EAAQ,KACnD,MAAMA,QAAA,CAAG,SAAS,QAAQ,CAAA,EAAG,SAAS,MAAM,CAAA;AACjD,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC7B,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,KAAA,CAAM,MAAA,EAAQ,SAAS,KAAK,CAAA;AACjD,EAAA,MAAM,QAAA,GAAW,OACd,GAAA,CAAI,CAAC,MAAM,CAAA,KAAM,CAAA,EAAG,OAAO,MAAA,GAAS,CAAA,GAAI,CAAC,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAC,IAAK,IAAI,CAAA,CAAE,CAAA,CACtE,IAAA,CAAK,IAAI,CAAA;AACZ,EAAA,OAAO,WAAA,CAAY,UAAU,GAAO,CAAA;AACtC","file":"read-handler.js","sourcesContent":["import * as path from 'node:path';\n\n/**\n * Normalize `target` against `cwd`. Returns an absolute path. **Does not\n * sandbox** — absolute targets and `../` traversal are allowed by design,\n * because the agent often needs to touch paths outside the cwd (`~/.config`,\n * `/etc/...`). Safety against unintended access lives at the permission\n * layer (`PermissionEngine` + the resolver), which prompts the user before\n * any tool runs. Tools that genuinely need to confine to cwd should use\n * `resolveWithinCwd` instead.\n *\n * Renamed from `resolveSafe` to make the contract honest — the old name\n * implied a sandbox it never performed.\n */\nexport function resolvePath(cwd: string, target: string): string {\n if (path.isAbsolute(target)) return path.normalize(target);\n return path.resolve(cwd, target);\n}\n\n/**\n * Like `resolvePath` but throws if the result escapes `cwd`. Use for tools\n * that should be strictly confined (rare).\n */\nexport function resolveWithinCwd(cwd: string, target: string): string {\n const resolved = resolvePath(cwd, target);\n const cwdAbs = path.resolve(cwd);\n const rel = path.relative(cwdAbs, resolved);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new Error(\n `Path escapes cwd: ${target} (resolved to ${resolved}, outside ${cwdAbs})`,\n );\n }\n return resolved;\n}\n\n/**\n * @deprecated Use `resolvePath` (no behavior change — only honest name).\n * Kept as a thin alias so external callers still compile.\n */\nexport const resolveSafe = resolvePath;\n\nexport function clampString(s: string, max: number): string {\n if (s.length <= max) return s;\n return s.slice(0, max) + `\\n... [truncated ${s.length - max} chars]`;\n}\n\n/**\n * Convert a glob pattern (`**`, `*`, `?`) to an anchored RegExp. Shared by\n * the Glob and Grep tools; not exposed externally.\n */\nexport function globToRegExp(pattern: string): RegExp {\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\?/g, '[^/]')\n .replace(/\\*\\*\\//g, '(?:.*/)?')\n .replace(/\\*\\*/g, '.*')\n .replace(/\\*/g, '[^/]*');\n return new RegExp('^' + escaped + '$');\n}\n","import { promises as fs } from 'node:fs';\nimport type { BrokeredFs } from '@moxxy/sdk';\nimport { clampString, resolveSafe } from './util.js';\n\n/**\n * Pure handler module for the Read tool. Lives in its own file so the\n * worker_threads isolator (`@moxxy/isolator-worker`) can re-import it\n * on the worker side via the `handlerModule` reference declared in\n * `read.ts`.\n *\n * Closures can't cross thread boundaries; module exports can.\n */\nexport interface ReadInput {\n readonly file_path: string;\n readonly offset?: number;\n readonly limit?: number;\n}\n\nexport interface ReadCtxLike {\n readonly cwd: string;\n /** Capability-mediated fs. Present when invoked under an isolator that\n * brokers (`@moxxy/isolator-worker`); absent under `none` / `inproc`. */\n readonly fs?: BrokeredFs;\n}\n\nexport async function readHandler(input: ReadInput, ctx: ReadCtxLike): Promise<string> {\n const { file_path, offset = 0, limit = 2000 } = input;\n const resolved = resolveSafe(ctx.cwd, file_path);\n // Use the brokered fs when the isolator provides one. The broker\n // re-validates the path against the tool's declared `caps.fs.read`\n // on the parent side, so reads outside the cap are denied at the\n // boundary regardless of what's in the input. Without a broker\n // (inproc / none), fall back to direct `node:fs` — input-level\n // cap-check already screened the file_path.\n const text = ctx.fs\n ? await ctx.fs.readFile(resolved, { encoding: 'utf8' })\n : (await fs.readFile(resolved)).toString('utf8');\n const lines = text.split('\\n');\n const sliced = lines.slice(offset, offset + limit);\n const numbered = sliced\n .map((line, i) => `${String(offset + i + 1).padStart(6, ' ')}\\t${line}`)\n .join('\\n');\n return clampString(numbered, 200_000);\n}\n"]}
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: add-mcp-server
3
+ description: Register a new Model Context Protocol (MCP) server with moxxy and author a usage skill for its tools.
4
+ triggers:
5
+ - add mcp
6
+ - new mcp
7
+ - mcp server
8
+ - hook up mcp
9
+ - register mcp
10
+ - install mcp
11
+ allowed-tools:
12
+ - mcp_test_server
13
+ - mcp_add_server
14
+ - mcp_list_servers
15
+ - synthesize_skill
16
+ ---
17
+
18
+ When the user wants to add an MCP server, walk them through these steps:
19
+
20
+ 1. **Gather the connection details**
21
+ - Ask what KIND the server is:
22
+ - `stdio` — a local executable (npm/uv package, custom script). Need `command`, optional `args`, `env`, `cwd`.
23
+ - `http` or `sse` — a remote HTTP server. Need `url`, optional `headers` (for auth).
24
+ - Ask what NAME to use. Must be slug-like (lowercase letters, digits, hyphens). The name prefixes every tool the server exposes — pick something short and recognizable (e.g. `canva`, `github`, `fs`).
25
+
26
+ 2. **Test the connection BEFORE persisting**
27
+ - Call `mcp_test_server` with the gathered details.
28
+ - On success, the result lists the tools the server exposes. Show them to the user.
29
+ - On failure, report the error verbatim and ask the user how they want to proceed (different URL, different command, give up).
30
+
31
+ 3. **Persist the server**
32
+ - Once the user confirms, call `mcp_add_server` with the same arguments. This connects to the server, registers its tools into the live session (no restart needed), and writes to `~/.moxxy/mcp.json` so the entry survives across sessions.
33
+ - The response includes a `tools` array — those names are now in the session's tool catalog with an `mcp__<server>__` prefix.
34
+
35
+ 4. **Author a usage skill (optional but recommended)**
36
+ - Offer to create a skill that documents how to use the new MCP's tools. This makes future invocations cleaner — the user can say "search canva for X" instead of remembering tool names.
37
+ - If they accept, call `synthesize_skill` with an `intent` describing the common workflow. The skill body should mention the prefixed tool names (e.g. `mcp__canva__search_designs`) and a typical multi-step recipe.
38
+
39
+ 5. **Confirm + summarize**
40
+ - List what was done: connection tested, server attached + saved, skill created (if applicable).
41
+ - The new tools are usable RIGHT NOW in this turn — you can demonstrate one if the user asks.
42
+
43
+ ## Notes
44
+
45
+ - `mcp_list_servers` shows what's currently registered.
46
+ - `mcp_remove_server` removes a registration.
47
+ - The user might already have an entry in `~/.moxxy/mcp.json` — call `mcp_list_servers` first to check before suggesting a name.
@@ -0,0 +1,131 @@
1
+ ---
2
+ name: add-provider
3
+ description: Register a new LLM provider (z.ai, deepseek, groq, openrouter, fireworks, together, mistral, perplexity, …) with moxxy and configure its API key so the user can switch to it.
4
+ triggers:
5
+ - add provider
6
+ - new provider
7
+ - install provider
8
+ - register provider
9
+ - add z.ai
10
+ - add deepseek
11
+ - add groq
12
+ - add openrouter
13
+ - add fireworks
14
+ - add together
15
+ - add mistral
16
+ - add perplexity
17
+ allowed-tools:
18
+ - provider_add
19
+ - provider_list
20
+ - provider_remove
21
+ - provider_test
22
+ - vault_status
23
+ - vault_list
24
+ ---
25
+
26
+ The user wants to add a new LLM provider to moxxy so they can switch to it later (`/provider <name>` or via `provider.name` in moxxy.config.ts). Walk them through these steps; be terse and pause for confirmation between gather → register → key.
27
+
28
+ ## Scope
29
+
30
+ This skill only handles **OpenAI-compatible** vendors — i.e. those that expose a Chat Completions endpoint shaped like OpenAI's (`/v1/chat/completions`, tool-call format, streaming with `data:` chunks). That covers the vast majority of modern API vendors: z.ai (GLM), deepseek, groq, openrouter, fireworks, together, mistral, perplexity, anyscale, deepinfra, octoai, and many more.
31
+
32
+ If the vendor speaks a different protocol (Anthropic-style, Google Vertex, custom), tell the user this skill can't handle it and direct them to author a full provider plugin (see `.claude/agents/provider-author.md`).
33
+
34
+ ## 1. Gather the basics
35
+
36
+ Ask the user, or infer from their request:
37
+
38
+ - **Provider slug** — short lowercase identifier (e.g. `zai`, `deepseek`, `groq`, `openrouter`). This becomes the registry key, the canonical vault entry name (`<SLUG>_API_KEY`), and what the user types in `/provider <slug>`. Must match `[a-z][a-z0-9-]*`.
39
+ - **API base URL** — the vendor's OpenAI-compatible endpoint root. Examples:
40
+ - z.ai → `https://api.z.ai/api/coding/paas/v4`
41
+ - deepseek → `https://api.deepseek.com`
42
+ - groq → `https://api.groq.com/openai/v1`
43
+ - openrouter → `https://openrouter.ai/api/v1`
44
+ - fireworks → `https://api.fireworks.ai/inference/v1`
45
+ - together → `https://api.together.xyz/v1`
46
+ - mistral → `https://api.mistral.ai/v1`
47
+ - **Default model id** — the model to use when no other is specified (you'll usually pick the vendor's "flagship" or "best general purpose" model).
48
+
49
+ If the user hasn't given you the baseURL, look it up via WebFetch on the vendor's docs (search for "openai compatible", "base url", "endpoint") and propose it back to them for confirmation. **Do not guess.**
50
+
51
+ ## 2. Discover the model list
52
+
53
+ You need to populate a `models` array. Each entry needs `id`, `contextWindow`, `maxOutputTokens?`, `supportsTools`, `supportsStreaming`, `supportsImages?`, `supportsAudio?`.
54
+
55
+ Two paths, in order of preference:
56
+
57
+ 1. **WebFetch the vendor's models / pricing page** to extract the current catalog. Good search prompts: `"<vendor> models pricing context window"`, `"<vendor> api models list"`. Common locations:
58
+ - z.ai → `https://docs.z.ai/guides/llm/glm-4.6`, `https://z.ai/pricing`
59
+ - deepseek → `https://api-docs.deepseek.com/quick_start/pricing`
60
+ - groq → `https://console.groq.com/docs/models`
61
+ - openrouter → `https://openrouter.ai/models`
62
+ - fireworks → `https://fireworks.ai/models`
63
+ 2. **The vendor's `/v1/models` endpoint** lists canonical model ids (but not context windows). This needs the API key, which you will NOT have (see step 3 — the key goes straight into the vault, never to you). So prefer WebFetch for discovery; if you must use `/v1/models`, ask the user to run it themselves and paste the model-id list (not the key).
64
+
65
+ Build the list, show it to the user as a markdown table (id / context / tools / images), and ask them to confirm or trim. **Do not invent context-window numbers.** If a model's context is unknown, ask the user or leave it out.
66
+
67
+ Tool-call support and streaming default to `true` for OpenAI-compatible vendors (their /v1/chat/completions endpoint inherits both). Only flip them to `false` if you've confirmed the vendor doesn't.
68
+
69
+ ## 3. Store the API key in the vault — via the `/vault` command
70
+
71
+ **Never ask the user to paste their API key to you.** The key must not pass through the conversation or a tool argument (both are visible to you). Instead, tell the user to store it themselves:
72
+
73
+ ```
74
+ /vault set <SLUG>_API_KEY <their-key>
75
+ ```
76
+
77
+ Use the uppercase slug, e.g. "Please run `/vault set DEEPSEEK_API_KEY <your-key>` — the value stays local; I'll only get a reference." `<SLUG>_API_KEY` is moxxy's canonical resolution path (config.apiKey → vault → env → prompt), so once it's there the provider picks it up automatically. **Stop and wait** for the user to confirm before continuing.
78
+
79
+ After they run it you'll get a note confirming storage and the reference `${vault:<SLUG>_API_KEY}` — you never see the key itself.
80
+
81
+ ## 4. (Optional) Test the endpoint
82
+
83
+ `provider_test` needs the plaintext key, which you don't have — so don't call it with the key yourself. To verify the endpoint, either:
84
+ - ask the user to run a quick check themselves, or
85
+ - skip ahead: after registering (step 5), have the user run `moxxy doctor`, which resolves `<SLUG>_API_KEY` from the vault and reports whether the provider's key is present.
86
+
87
+ ## 5. Register the provider
88
+
89
+ Call `provider_add` with the gathered fields:
90
+
91
+ ```json
92
+ {
93
+ "kind": "openai-compat",
94
+ "name": "<slug>",
95
+ "baseURL": "<base url>",
96
+ "defaultModel": "<id>",
97
+ "models": [
98
+ { "id": "<id>", "contextWindow": 200000, "supportsTools": true, "supportsStreaming": true }
99
+ ]
100
+ }
101
+ ```
102
+
103
+ `provider_add` does two things atomically:
104
+ 1. Registers the provider in the LIVE session — switchable immediately.
105
+ 2. Persists to `~/.moxxy/providers.json` so it survives restarts.
106
+
107
+ If `provider_add` returns `{ replaced: true }`, mention that to the user — it means a provider with the same slug already existed and they just overwrote it.
108
+
109
+ ## 6. Help them switch to it (optional)
110
+
111
+ Ask if they want this provider as the default:
112
+
113
+ - **Just this session** → suggest `/provider <slug>` (typed in the TUI). Don't do it for them unless asked.
114
+ - **Permanently** → offer to edit `moxxy.config.ts` and set `provider.name` (and optionally `provider.model`) to the new values. Use the Edit tool. Mention they can run `moxxy doctor` afterward to confirm the key resolves.
115
+
116
+ ## 7. Summarize
117
+
118
+ Report:
119
+ - Provider slug + baseURL + default model.
120
+ - That the API key is in the vault under `<SLUG>_API_KEY` (the user stored it via `/vault set`).
121
+ - That `~/.moxxy/providers.json` was updated and the provider is live this session.
122
+ - How to switch to it.
123
+
124
+ ## Don't
125
+
126
+ - Don't ask the user to paste their API key to you, and don't call any tool with the key as an argument. Direct them to `/vault set <SLUG>_API_KEY <key>` so it never enters the conversation.
127
+ - Don't invent baseURLs, model ids, or context windows. If you're not sure, WebFetch or ask.
128
+ - Don't store the API key anywhere except the vault. Never write it into a file in the repo, never echo it back.
129
+ - Don't try to handle non-OpenAI-compatible vendors here — those need a real provider plugin (`.claude/agents/provider-author.md`).
130
+ - Don't overwrite an existing provider slug without telling the user first. Call `provider_list` if you're unsure whether the slug is taken.
131
+ - Don't auto-edit `moxxy.config.ts` to switch the default without asking. The user may want to keep their current provider as primary.
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: explain-event-log
3
+ description: Walk the user through the moxxy event log for the current session.
4
+ triggers: ["show event log", "explain events", "what happened"]
5
+ allowed-tools: []
6
+ ---
7
+ # Explain the event log
8
+
9
+ When the user wants to understand what happened in this session:
10
+
11
+ 1. Summarize the most recent turn: which tools ran, which were denied, the final assistant message.
12
+ 2. Highlight any `error`, `tool_call_denied`, or `compaction` events.
13
+ 3. If the user asks "why did X happen?", trace the `causationId` chain.
14
+
15
+ Be terse — one bullet per event class. Don't dump raw JSON.
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: generative-ui
3
+ description: Generate an interactive, navigable UI (a "generative UI" / "dynamic UI" / "agentic UI") for the user — rendered in their browser, backed by real data from your tools. Only when the user explicitly asks for a generative/dynamic/agentic UI.
4
+ triggers: ["generative ui", "dynamic ui", "agentic ui", "generative interface", "dynamic interface", "agentic interface", "interactive ui for", "generative app", "agentic app"]
5
+ allowed-tools: [present_view, web_fetch, browser_session]
6
+ ---
7
+
8
+ # Generative UI
9
+
10
+ Generate an interactive, navigable UI — a "generative / dynamic / agentic UI" — for
11
+ the user, backed by **real data**, and hand them a link.
12
+
13
+ ## When to use this — and when NOT to
14
+
15
+ **Use `present_view` ONLY when the user explicitly asks for a generative / dynamic /
16
+ agentic UI (or interface).**
17
+
18
+ - ✅ "present me a generative ui for arXiv papers"
19
+ - ✅ "show me a dynamic ui for flight search WAW→KRK"
20
+ - ✅ "make me an agentic ui for my GitHub repos", "interactive ui for X"
21
+ - ❌ "search flights SFO→JFK", "find me papers on transformers", "what's the weather"
22
+ → **answer in plain text. Do NOT generate a UI.** Ordinary searches/questions are
23
+ handled the way you always have.
24
+
25
+ If you're unsure, default to a normal text answer. A generative UI is for when the
26
+ user wants a *thing they can use*, not a one-off answer.
27
+
28
+ ## Use REAL data — never demo data
29
+
30
+ The UI must show **real results fetched from real sources**, not placeholders you made
31
+ up. Get data with your tools — `web_fetch` (JSON APIs, public endpoints, HTML pages),
32
+ `browser_session` (JS-heavy sites), or any MCP/provider tool available. **Never
33
+ fabricate results, prices, IDs, or links.** If you can't find a usable data source,
34
+ render a clear message saying so (a `card` with `text tone="warn"`) instead of
35
+ inventing data.
36
+
37
+ ## Render upfront — don't make the user re-ask
38
+
39
+ If the user already gave you the parameters (e.g. "WAW → KRK"), **go straight to
40
+ results in the same turn** — don't show an empty form and wait for them to submit
41
+ what they already told you. Generate the populated UI now.
42
+
43
+ ## The loader-first flow (every fetch)
44
+
45
+ A `present_view` call flushes to the browser immediately, so within ONE turn:
46
+
47
+ 1. `present_view` a **loading** screen (skeleton / spinner) under a screen `name`.
48
+ 2. Fetch the **real** data with your tools.
49
+ 3. `present_view` the **results** under the **same `name`** — it replaces the
50
+ skeleton in place.
51
+
52
+ Use the same `name` for the loading and loaded states so the result swaps the
53
+ skeleton in place (don't leave a dangling "loading" screen).
54
+
55
+ ### Example: "present me a dynamic ui for flight search WAW → KRK"
56
+
57
+ Turn 1, call 1 — show the loading state instantly:
58
+
59
+ ```xml
60
+ <view name="results" title="WAW → KRK">
61
+ <text tone="muted">Searching live flights…</text>
62
+ <skeleton rows="5" />
63
+ </view>
64
+ ```
65
+
66
+ Then fetch real data (`web_fetch` a flights API / source). Turn 1, call 2 — replace
67
+ with real results under the same name:
68
+
69
+ ```xml
70
+ <view name="results" title="WAW → KRK · 4 flights">
71
+ <results>
72
+ <result title="LOT LO3923" subtitle="06:40 → 07:55 · nonstop" badge="PLN 320" action="open:LO3923" />
73
+ <result title="Ryanair FR2118" subtitle="20:10 → 21:25 · nonstop" badge="PLN 149" action="open:FR2118" />
74
+ </results>
75
+ <link to="search">↻ Refine search</link>
76
+ </view>
77
+ ```
78
+
79
+ Also build a `search` screen (a `form action="run_search"` with from/to/date inputs,
80
+ pre-filled) so the user can refine; on submit you repeat the loader-first flow.
81
+
82
+ ## The UI model
83
+
84
+ Multiple named screens the user navigates between (one `present_view` per screen):
85
+
86
+ - **`to="<screenName>"`** on a `link`/`button` → jumps to that screen
87
+ **instantly, client-side, with no new turn** (if you've already built it).
88
+ - **`action="…"`** on a form/button → sends an **agent turn** back to you (this is
89
+ how search / fetch-detail work — fetch real data, then render the next screen).
90
+ - A built-in **Back** button returns to previous screens for free.
91
+ - A floating chat button on the surface lets the user ask for refinements; treat
92
+ those as normal prompts — re-render the affected screen with `present_view` (same
93
+ `name`) to update it live.
94
+
95
+ Clicking a result sends `[ui-action] {"action":"open:LO3923"}` → fetch that record's
96
+ real details → build a `detail:LO3923` screen with `<link to="results">← Back</link>`.
97
+
98
+ ## Vocabulary (allow-listed — unknown tags/attrs are rejected)
99
+
100
+ **Layout:** `view`(name?,title?) · `stack`(gap?,align?) · `row`(gap?,align?,justify?) ·
101
+ `grid`(cols 1-6) · `card`(title?,accent?) · `divider`.
102
+ **Loading:** `spinner`(label?) · `skeleton`(rows 1-12).
103
+ **Display:** `heading`(level 1-3) · `text`(tone?,weight?) · `badge`(tone?) ·
104
+ `image`(src,alt?) · `link`(href? OR to?) · `list`(ordered?)/`item` · `table`/`tr`/`th`/`td`.
105
+ **Inputs (in a `form`):** `form`(action,submit?) · `input`(name,type?,label?,placeholder?,value?,required?) ·
106
+ `select`(name,…)/`option`(value) · `checkbox`(name,label?) ·
107
+ `button`(label, **action** OR **to**, variant?, fields?).
108
+ **Component:** `results` → `result`(title, subtitle?, badge?, id?, action? | to?).
109
+
110
+ ## Rules
111
+
112
+ - One `<view>` root per call; give each screen a `name`; loading + loaded share the name.
113
+ - `to=` navigates between your screens; `action=` only for work you must do (fetch).
114
+ - Allow-listed tags/attrs only. `href`/`src` must be `https:`/`mailto:`/relative.
115
+ Enums: `card accent` & `text/badge tone` ∈ `default|muted|success|warn|danger`;
116
+ `gap` ∈ `none|sm|md|lg`.
117
+ - **Share the EXACT `url` from the present_view result.** Never invent a path like
118
+ `/app/search`. If the result is `rendered:false` or has no `url`, the surface isn't
119
+ running — say so instead of guessing a link.
120
+ - Always pass `fallbackText` (a one-line summary).
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: remember-this
3
+ description: Save a fact, preference, project note, or reference to long-term memory for future sessions.
4
+ triggers: ["remember", "save this", "for later", "memorize", "note that"]
5
+ allowed-tools: [memory_save, memory_list, memory_recall, memory_update]
6
+ ---
7
+ # Remember this
8
+
9
+ The user wants you to commit something to long-term memory so it's available in future sessions.
10
+
11
+ ## Decide the type
12
+
13
+ - **fact**: a static piece of knowledge ("the API endpoint is X")
14
+ - **preference**: how the user wants you to work ("they prefer terse responses")
15
+ - **project**: context about the project they're working on
16
+ - **reference**: a pointer to external info ("docs live at confluence.example.com")
17
+
18
+ ## Workflow
19
+
20
+ 1. **Distill** what the user just said into 1–2 sentences. If the original was a long ramble, summarize. If it was already terse, keep it.
21
+ 2. **Check existing memories** with `memory_recall(query)` for similar entries. If a related entry exists, prefer `memory_update` over creating a new one — don't fragment.
22
+ 3. **Save** with `memory_save({ name, type, description, body, tags })`:
23
+ - `name`: slug, ≤60 chars, kebab-case
24
+ - `description`: one sentence, ≤120 chars — this is what shows in the index
25
+ - `body`: ≤30 lines; the actual content
26
+ - `tags`: optional, lowercase keywords for cross-cutting topics
27
+
28
+ 4. **Confirm** briefly: "Saved as `<name>`."
29
+
30
+ ## Don't
31
+
32
+ - Don't save secrets here — use the vault.
33
+ - Don't save ephemeral state (open files, current cursor position, etc.). Memories should be useful in any future session.
34
+ - Don't create overlapping entries. If unsure, recall first.
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: scheduling
3
+ description: Create, inspect, and manage recurring or one-shot scheduled prompts that fire on a cron or a specific timestamp.
4
+ triggers:
5
+ - schedule
6
+ - cron
7
+ - every day
8
+ - every hour
9
+ - in an hour
10
+ - tomorrow at
11
+ - heartbeat
12
+ - reminder
13
+ - daily briefing
14
+ - recurring
15
+ - automate
16
+ allowed-tools:
17
+ - schedule_create
18
+ - schedule_list
19
+ - schedule_delete
20
+ - schedule_enable
21
+ - schedule_disable
22
+ - schedule_run_now
23
+ - telegram_send_message
24
+ ---
25
+
26
+ Use this skill when the user wants a prompt to fire automatically — recurring on a cron, or once at a fixed time. Each scheduled run starts a fresh turn, the assistant's final message gets written to `~/.moxxy/inbox/`, and the prompt itself can call delivery tools (e.g. `telegram_send_message`) for push notifications.
27
+
28
+ ## Pattern
29
+
30
+ 1. **Capture intent** — what should fire, when, and where the output should land.
31
+ - "Every day at 9 AM" → cron `0 9 * * *`
32
+ - "Every Monday at 6 PM" → cron `0 18 * * 1`
33
+ - "Every 15 minutes" → cron `*/15 * * * *`
34
+ - "In one hour" → `runAt: <epoch-ms now + 3_600_000>` (or ISO timestamp)
35
+ - "Tomorrow at 10" → `runAt: <ISO timestamp>` in the user's local zone
36
+
37
+ 2. **Pick the schedule name** — slug-like (`morning-briefing`, `weekly-standup`). The user-facing name; appears in the inbox filename.
38
+
39
+ 3. **Write the prompt deliberately** — this prompt runs *headless* against the current provider/model. Include the delivery action in the prompt body if you want a push notification. Examples:
40
+ - "Fetch today's top 5 Hacker News posts; for each, write a 2-line summary; then call `telegram_send_message` with a markdown-formatted digest."
41
+ - "Check Gmail for new messages from <X>; if any, summarize and call `telegram_send_message`."
42
+ - "Remind me about <X>. Call `telegram_send_message` with a short reminder."
43
+
44
+ 4. **Call `schedule_create`** with `{ name, prompt, cron | runAt, channel?, model? }`. The tool returns `nextFireIso` so you can confirm the schedule will fire when expected.
45
+
46
+ 5. **Confirm to the user** — name, next fire time, where output goes. Offer to fire it once with `schedule_run_now` for a smoke test.
47
+
48
+ ## Cron cheat-sheet (5-field POSIX)
49
+
50
+ ```
51
+ minute hour dom month dow
52
+ 0-59 0-23 1-31 1-12 0-6 (Sun=0)
53
+ ```
54
+
55
+ Operators: `*` any, `a-b` range, `a,b,c` list, `*/n` step, `a-b/n` ranged step.
56
+
57
+ Common shapes:
58
+ - `0 9 * * *` — 9 AM every day
59
+ - `0 9 * * 1-5` — weekdays at 9 AM
60
+ - `0 */2 * * *` — every 2 hours on the hour
61
+ - `0 0 1 * *` — midnight on the 1st of each month
62
+ - `30 18 * * 5` — Fridays at 6:30 PM
63
+
64
+ When both day-of-month and day-of-week are restricted, the schedule fires when **either** matches (vixie-cron convention).
65
+
66
+ ## Managing existing schedules
67
+
68
+ - `schedule_list` — view all, with `nextFireIso` + `lastResult`. Filter by `source: 'manual'|'skill'|'all'`.
69
+ - `schedule_disable` / `schedule_enable` — pause/resume without losing the row.
70
+ - `schedule_delete` — permanently remove.
71
+ - `schedule_run_now` — fire immediately, useful for testing.
72
+
73
+ ## Auto-scheduled skills
74
+
75
+ A skill file can include a `schedule:` block in its frontmatter — the scheduler picks it up automatically:
76
+
77
+ ```yaml
78
+ ---
79
+ name: morning-briefing
80
+ description: Daily 9 AM Hacker News digest
81
+ schedule:
82
+ cron: "0 9 * * *"
83
+ channel: telegram
84
+ ---
85
+ Fetch the top 5 stories from https://news.ycombinator.com/.
86
+ For each, write 2 lines. Then call telegram_send_message with the digest.
87
+ ```
88
+
89
+ Skill-driven schedules show up in `schedule_list` with `source: 'skill'`. Editing the skill body or its `schedule:` block on disk updates the live schedule on the next tick — no restart needed.
90
+
91
+ ## Limitations to mention if relevant
92
+
93
+ - Schedules only fire while a moxxy session is alive (TUI, Telegram channel, etc.). For true 24/7 firing, the user must keep a session running (e.g. `moxxy telegram` in a background process).
94
+ - The scheduled prompt runs against the **active session**, so its output appears in conversation history. Schedule with care while debugging an unrelated turn.
95
+ - Timezones default to the host's system local. For UTC or another zone, pass `timeZone: "UTC"` or any IANA name.
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: self-heal
3
+ description: When a tool call fails or the system misbehaves, diagnose the root cause and propose ONE scoped fix that the user approves before it lands.
4
+ triggers:
5
+ - "tool failed"
6
+ - "permission denied"
7
+ - "not found"
8
+ - "doesn't work"
9
+ - "broken"
10
+ - "fix this"
11
+ - "fix it"
12
+ - "can't run"
13
+ - "is hanging"
14
+ - "is stuck"
15
+ - "is failing"
16
+ - "what's wrong"
17
+ - "diagnose"
18
+ - "self-heal"
19
+ - "self heal"
20
+ - "repair"
21
+ ---
22
+
23
+ # Self-heal — diagnose then propose ONE fix
24
+
25
+ When a tool errored, a subagent hung, an install is missing, a permission was
26
+ denied, or "something just isn't working", follow this loop. Every concrete
27
+ change runs through an existing tool whose permission gate is `prompt`, so the
28
+ user explicitly approves each destructive action — there is no "allow always"
29
+ shortcut for fixes. **This is intentional.** Self-healing without a human in
30
+ the loop turns a one-line bug into a cascade.
31
+
32
+ ## Loop
33
+
34
+ 1. **Diagnose first, don't guess.** Use read-only tools to inspect actual
35
+ state before forming a hypothesis:
36
+ - `Read` / `Grep` / `Glob` for source / config / logs.
37
+ - `bash` (read-only commands like `ls`, `cat`, `which`, `ps`, `git status`)
38
+ for environment checks. Don't run mutating shell commands here.
39
+ - `/info`, `/agents`, `/skills`, `/tools` — operator overlays that show
40
+ live registry state.
41
+ - `~/.moxxy/permissions.json`, `~/.moxxy/mcp.json`, `~/.moxxy/sessions/`
42
+ for persistent state when relevant.
43
+
44
+ 2. **State the root cause in one sentence.** "X failed because Y". If you
45
+ don't have evidence for Y, gather more — don't move on.
46
+
47
+ 3. **Propose ONE scoped fix.** Smallest change that addresses the diagnosed
48
+ cause. Avoid "let me also tidy up while I'm here" — that turns a focused
49
+ fix into an unreviewable diff.
50
+
51
+ 4. **Pick the right tool for the fix.** All of these prompt for permission:
52
+ - **Code / config edit**: `Edit` or `Write` for a specific file.
53
+ - **Shell action**: `bash` (e.g. restart a service, clear a cache).
54
+ - **Plugin missing**: `install_plugin` (if `@moxxy/plugin-plugins-admin`
55
+ is enabled) — npm-installs into `~/.moxxy/plugins` + hot-reloads.
56
+ - **MCP server broken/missing**: `mcp_add_server`, `mcp_remove_server`,
57
+ `mcp_test_server`.
58
+ - **Permission denied repeatedly**: tell the user how to add a permanent
59
+ allow rule (`moxxy perms allow <tool>`) — do NOT silently bypass.
60
+ - **Skill missing**: `synthesize_skill` to draft a new skill.
61
+
62
+ 5. **Surface the proposed change BEFORE calling the tool.** Tell the user:
63
+ - The diagnosis.
64
+ - The exact change you're about to make.
65
+ - What the user should look for in the next permission prompt to verify it.
66
+
67
+ 6. **Verify after the fix lands.** Re-run the failing tool / command. If it
68
+ still fails, do NOT loop on the same fix — go back to step 1 with the new
69
+ information. Two failed attempts is the threshold to stop and escalate
70
+ to the user with what you've learned.
71
+
72
+ ## Don't
73
+
74
+ - **Don't propose `allow_always` as a fix.** If the user keeps seeing prompts
75
+ for the same tool, the fix is `moxxy perms allow <tool>`, not a
76
+ workaround. Suggest the command — don't run it for them.
77
+ - **Don't try to fix issues outside the obvious blast radius.** If a single
78
+ subagent failed, don't reboot the whole session. If a config field was
79
+ missing, don't rewrite the entire config.
80
+ - **Don't chain fixes without approval gates.** Each destructive step is its
81
+ own permission prompt. Bundling N edits into a single `bash` heredoc to
82
+ avoid the prompts defeats the safety design.
83
+ - **Don't self-heal silently when the user didn't ask.** This skill is for
84
+ responding to a specific reported issue, not proactive cleanup. If you
85
+ notice a problem the user hasn't mentioned, tell them — let them decide
86
+ whether to repair.
87
+
88
+ ## Templates
89
+
90
+ For a denied tool the user clearly wants:
91
+ ```
92
+ Diagnosed: `web_fetch` was denied because the policy has no allow rule.
93
+ Proposal: Add a permanent allow for `web_fetch` (you'll only be asked once).
94
+ Command: `moxxy perms allow web_fetch`
95
+ ```
96
+
97
+ For a missing plugin:
98
+ ```
99
+ Diagnosed: `dispatch_agent` failed because `@moxxy/plugin-subagents` is not
100
+ registered (the agent registry only lists `default`).
101
+ Proposal: Install `@moxxy/plugin-subagents` via the install_plugin tool.
102
+ You'll get one permission prompt for the npm install.
103
+ ```
104
+
105
+ For a hung subagent:
106
+ ```
107
+ Diagnosed: Subagent `<label>` is hung waiting on a `<tool>` call — the
108
+ tool ran for <N>s with no result. Likely a network timeout on
109
+ <url>.
110
+ Proposal: Cancel the turn (Esc) and re-run with a more specific prompt
111
+ that points the child at a faster source. No file/config
112
+ changes needed.
113
+ ```