@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.
- package/dist/bin.js +119004 -0
- package/dist/bin.js.map +1 -0
- package/dist/public/app.js +54 -0
- package/dist/public/index.html +134 -0
- package/dist/read-handler.js +30 -0
- package/dist/read-handler.js.map +1 -0
- package/dist/skills/add-mcp-server.md +47 -0
- package/dist/skills/add-provider.md +131 -0
- package/dist/skills/explain-event-log.md +15 -0
- package/dist/skills/generative-ui.md +120 -0
- package/dist/skills/remember-this.md +34 -0
- package/dist/skills/scheduling.md +95 -0
- package/dist/skills/self-heal.md +113 -0
- package/dist/skills/self-update.md +137 -0
- package/dist/skills/synthesize-skill.md +20 -0
- package/dist/skills/telegram-setup.md +40 -0
- package/dist/skills/vault-setup.md +48 -0
- package/dist/skills/web-research.md +53 -0
- package/package.json +46 -0
|
@@ -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
|
+
```
|