@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,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: self-update
|
|
3
|
+
description: Extend or repair moxxy's OWN capabilities — add a tool/skill the user asks for, or fix a recurring failure — by authoring a plugin or skill through a guardrailed, verified, auto-rollback transaction.
|
|
4
|
+
triggers:
|
|
5
|
+
- "add a tool"
|
|
6
|
+
- "add a capability"
|
|
7
|
+
- "teach yourself"
|
|
8
|
+
- "update yourself"
|
|
9
|
+
- "extend yourself"
|
|
10
|
+
- "give yourself"
|
|
11
|
+
- "self-update"
|
|
12
|
+
- "self update"
|
|
13
|
+
- "modify your code"
|
|
14
|
+
- "change your code"
|
|
15
|
+
- "you should be able to"
|
|
16
|
+
- "can you learn to"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Self-update — author or repair a capability, safely
|
|
20
|
+
|
|
21
|
+
Use this when the user asks you to **add a capability** ("add a tool that…",
|
|
22
|
+
"you should be able to…") or to **fix a recurring failure** in your own
|
|
23
|
+
behavior, and the change requires editing code/skills rather than just doing
|
|
24
|
+
the task. Every code write goes through a `prompt` permission gate and every
|
|
25
|
+
change is verified before it counts — there is no silent self-modification.
|
|
26
|
+
|
|
27
|
+
Pick the **lowest-risk tier** that satisfies the request:
|
|
28
|
+
|
|
29
|
+
| Tier | When | Mechanism | Reversible by |
|
|
30
|
+
|------|------|-----------|---------------|
|
|
31
|
+
| **A — Skill** | A recurring *procedure* expressible with tools that already exist ("always run X before Y") | a `.md` skill in `~/.moxxy/skills` (no code) | delete one file |
|
|
32
|
+
| **B — Plugin** | A new *action* (call an API, run a script) or a *behavior override* of an existing tool | a plugin in `~/.moxxy/plugins`, hot-reloaded | unload + restore |
|
|
33
|
+
| **C — Core** | Genuinely needs to edit `@moxxy/core` (new event type, registry, the loop itself) | provision source → build + test → overlay → restart | snapshot restore + restart |
|
|
34
|
+
|
|
35
|
+
Prefer A, then B. **Most "core fixes" should still be a plugin *override*** (wrap
|
|
36
|
+
a tool, swap a mode) — that's hot-reloadable and reversible. Only use Tier C
|
|
37
|
+
when the change genuinely cannot be expressed as a plugin. Tier C is heavy
|
|
38
|
+
(provisions a source clone, builds, then needs a process restart) and every
|
|
39
|
+
step is approval-gated.
|
|
40
|
+
|
|
41
|
+
## Loop
|
|
42
|
+
|
|
43
|
+
1. **Classify.** Call `self_update_classify` with the request text (or
|
|
44
|
+
`trigger: "error"` after a failure). It reads recent errors + the live tool
|
|
45
|
+
set and recommends a tier. The recommendation is *advisory* — apply the
|
|
46
|
+
table above. If a published plugin already does it, prefer `install_plugin`
|
|
47
|
+
over authoring from scratch.
|
|
48
|
+
|
|
49
|
+
2. **State the smallest change** in one sentence. A new plugin should add the
|
|
50
|
+
one tool/hook needed — no "while I'm here" extras.
|
|
51
|
+
|
|
52
|
+
3. **Open a transaction.** `self_update_begin({ kind, name })` snapshots the
|
|
53
|
+
target so it can be rolled back. For a brand-new plugin, scaffold first with
|
|
54
|
+
`moxxy plugins new <name>` (zero-build `.mjs` that hot-reloads), or write a
|
|
55
|
+
`src/index.ts` (loaded via jiti).
|
|
56
|
+
|
|
57
|
+
4. **Surface the change BEFORE writing.** Tell the user the diagnosis/goal and
|
|
58
|
+
the exact file contents you're about to write. Then write with `Write` /
|
|
59
|
+
`Edit` — the user approves each at the permission prompt. Never bundle edits
|
|
60
|
+
into a `bash` heredoc to dodge the prompts.
|
|
61
|
+
|
|
62
|
+
5. **Verify.** `self_update_verify({ txnId })` builds (if the plugin has a
|
|
63
|
+
build script), runs its tests, hot-reloads it into the session, and confirms
|
|
64
|
+
it registered. If it fails on a *modified* plugin, the previous working
|
|
65
|
+
version is auto-restored. Read the returned `stages` to see what broke.
|
|
66
|
+
|
|
67
|
+
6. **Apply or roll back.** If verify passes, show the user what registered and
|
|
68
|
+
call `self_update_apply({ txnId })` to keep it. If runtime behavior is wrong,
|
|
69
|
+
`self_update_rollback({ txnId })`.
|
|
70
|
+
|
|
71
|
+
7. **Stop after 2 failed verify cycles.** `self_update_verify` refuses a 3rd
|
|
72
|
+
attempt, rolls back to a clean state, and returns `escalate: true`. When that
|
|
73
|
+
happens, stop editing and report to the user: the original goal, what you
|
|
74
|
+
tried, and the captured errors. Don't loop.
|
|
75
|
+
|
|
76
|
+
## Tier C — core patch (heavy, restart-required)
|
|
77
|
+
|
|
78
|
+
Only when a plugin override genuinely can't do it. Confirm with the user first.
|
|
79
|
+
|
|
80
|
+
1. `self_update_core_preflight` — checks git/pnpm + that the install has pinned
|
|
81
|
+
source provenance (`gitHead` + repo url). If any check fails, STOP and tell
|
|
82
|
+
the user (e.g. set `options.repoUrl` in config, or update via npm instead).
|
|
83
|
+
2. `self_update_core_begin({ packages })` — provisions a source clone at the
|
|
84
|
+
exact installed commit (slow). Returns a `coreTxnId` + repo path.
|
|
85
|
+
3. Edit with `self_update_core_write` / `self_update_core_edit` (paths relative
|
|
86
|
+
to the repo). Show the diff first.
|
|
87
|
+
4. `self_update_core_verify({ coreTxnId })` — builds/typechecks/tests the
|
|
88
|
+
affected packages + dependents, and rejects any change that adds a new
|
|
89
|
+
runtime dependency.
|
|
90
|
+
5. `self_update_core_apply({ coreTxnId })` — overlays the build into the live
|
|
91
|
+
install (snapshotting the old dist) and stages a restart. **Tell the user a
|
|
92
|
+
restart is required.** It auto-commits on the next clean boot.
|
|
93
|
+
6. If a patch is bad, `self_update_core_rollback({ coreTxnId })` (or
|
|
94
|
+
`moxxy self-update rollback <coreTxnId>` from a shell) + restart.
|
|
95
|
+
|
|
96
|
+
## Expressing a fix as a plugin override (Tier B templates)
|
|
97
|
+
|
|
98
|
+
A plugin can change core behavior without touching core, via lifecycle hooks:
|
|
99
|
+
|
|
100
|
+
- **Wrap a misbehaving tool's output** — `onToolResult(ctx)`: if
|
|
101
|
+
`ctx.result.toolName === 'X'`, return a repaired/truncated result.
|
|
102
|
+
- **Block or rewrite a bad call** — `onToolCall(ctx)`: return
|
|
103
|
+
`{ action: 'deny', reason }` or `{ action: 'rewrite', input }`.
|
|
104
|
+
- **Add a capability** — a new `defineTool({...})` in the plugin's `tools`.
|
|
105
|
+
- **Augment the system prompt** — `onBeforeProviderCall(req)`: return
|
|
106
|
+
`{ ...req, system: req.system + extra }`.
|
|
107
|
+
- **Swap the loop/compaction strategy** — register a mode/compactor and
|
|
108
|
+
activate it from the plugin's `onInit`.
|
|
109
|
+
|
|
110
|
+
Minimal plugin entry (`~/.moxxy/plugins/<name>/index.mjs`):
|
|
111
|
+
```js
|
|
112
|
+
import { definePlugin, defineTool, z } from '@moxxy/sdk';
|
|
113
|
+
|
|
114
|
+
export default definePlugin({
|
|
115
|
+
name: 'my-capability',
|
|
116
|
+
version: '0.0.0',
|
|
117
|
+
tools: [
|
|
118
|
+
defineTool({
|
|
119
|
+
name: 'my_tool',
|
|
120
|
+
description: 'One sentence, lead with a verb.',
|
|
121
|
+
inputSchema: z.object({ arg: z.string() }),
|
|
122
|
+
permission: { action: 'prompt' },
|
|
123
|
+
handler: async ({ arg }) => ({ result: arg }),
|
|
124
|
+
}),
|
|
125
|
+
],
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Don't
|
|
130
|
+
|
|
131
|
+
- **Don't skip the transaction.** Always `self_update_begin` before editing so
|
|
132
|
+
there's a snapshot to roll back to.
|
|
133
|
+
- **Don't edit `@moxxy/core` or other framework packages here.** That's Tier C —
|
|
134
|
+
escalate. Reach for a plugin override first.
|
|
135
|
+
- **Don't keep retrying a failing change.** Two failed verifies is the wall.
|
|
136
|
+
- **Don't self-update silently.** This responds to an explicit request or a
|
|
137
|
+
reported failure — not proactive tinkering.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: synthesize-skill
|
|
3
|
+
description: When the user request doesn't match any existing skill, draft a new one and persist it for next time.
|
|
4
|
+
triggers: ["create skill", "new skill", "save this as a skill"]
|
|
5
|
+
allowed-tools: [Write, Read]
|
|
6
|
+
---
|
|
7
|
+
# Synthesize a new skill
|
|
8
|
+
|
|
9
|
+
The user has asked for something for which no existing skill matches. Your job is to:
|
|
10
|
+
|
|
11
|
+
1. Identify the intent (one sentence).
|
|
12
|
+
2. Draft a Markdown skill file with YAML frontmatter:
|
|
13
|
+
- `name`: kebab-case slug, no more than 60 chars
|
|
14
|
+
- `description`: one sentence, < 120 chars
|
|
15
|
+
- `triggers`: 2–5 short phrases a user might say
|
|
16
|
+
- `allowed-tools`: only the tools you need
|
|
17
|
+
3. The body is the instructions for future invocations: numbered steps, the minimum needed to execute reliably.
|
|
18
|
+
4. Save to `~/.moxxy/skills/<slug>.md` unless the user redirects to project scope.
|
|
19
|
+
|
|
20
|
+
Keep skill bodies short — under 30 lines. Long context belongs in code/files the skill reads at runtime, not in the skill itself.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: telegram-setup
|
|
3
|
+
description: Walk the user through creating a Telegram bot via BotFather and pairing their chat.
|
|
4
|
+
triggers: ["set up telegram", "configure telegram", "connect telegram", "telegram bot"]
|
|
5
|
+
allowed-tools: [telegram_set_token, telegram_status, vault_status, vault_list]
|
|
6
|
+
---
|
|
7
|
+
# Telegram setup
|
|
8
|
+
|
|
9
|
+
The user wants to connect a Telegram bot to moxxy. Walk them through these steps in order. Be terse — one step at a time.
|
|
10
|
+
|
|
11
|
+
## 1. Create a bot with BotFather
|
|
12
|
+
|
|
13
|
+
Tell the user:
|
|
14
|
+
> Open Telegram and message **@BotFather**. Send `/newbot`, then follow its prompts to choose a display name and a username (must end in `bot`). BotFather will reply with a token that looks like `1234567890:ABCdefGhIjKl...`. Paste it here.
|
|
15
|
+
|
|
16
|
+
Wait for the user to provide the token.
|
|
17
|
+
|
|
18
|
+
## 2. Store the token
|
|
19
|
+
|
|
20
|
+
Once they paste the token, call `telegram_set_token` with it. The token will be encrypted into the vault.
|
|
21
|
+
|
|
22
|
+
This will trigger the vault permission prompt + (on first use) the master-key prompt (keychain or passphrase). If the vault has never been initialized, mention that briefly so the prompt isn't confusing.
|
|
23
|
+
|
|
24
|
+
## 3. Verify
|
|
25
|
+
|
|
26
|
+
Call `telegram_status` and report what came back. Expected:
|
|
27
|
+
```
|
|
28
|
+
{ "tokenConfigured": true, "authorizedChatId": null }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 4. Tell them how to pair
|
|
32
|
+
|
|
33
|
+
> Run **`moxxy telegram pair`** in your terminal. It will print a 6-digit code. In Telegram, find your new bot, send `/start`, then type the code. That chat is now the only authorized chat for this bot.
|
|
34
|
+
|
|
35
|
+
## Don't
|
|
36
|
+
|
|
37
|
+
- Don't ask for the token before step 1 (the user may not have created a bot yet).
|
|
38
|
+
- Don't try to start the bot from this skill — it's a long-running process; the CLI subcommand is the right entry point.
|
|
39
|
+
- Don't store the token in plaintext anywhere. The `telegram_set_token` tool persists to the encrypted vault.
|
|
40
|
+
- If the user already has `telegram_status.tokenConfigured = true`, ask if they want to replace the existing token before overwriting.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vault-setup
|
|
3
|
+
description: Help the user store secrets in the encrypted vault via the /vault command, without the secret ever reaching the model.
|
|
4
|
+
triggers: ["set up vault", "initialize vault", "store a secret", "save api key", "need an api key", "provide your key"]
|
|
5
|
+
allowed-tools: [vault_status, vault_list]
|
|
6
|
+
---
|
|
7
|
+
# Vault setup
|
|
8
|
+
|
|
9
|
+
The user wants to store secrets (API keys, tokens, webhook URLs) in moxxy's encrypted vault.
|
|
10
|
+
|
|
11
|
+
## The golden rule: never handle the plaintext yourself
|
|
12
|
+
|
|
13
|
+
**Never ask the user to paste a secret into the chat, and never call a tool with a secret value the user gave you.** Anything the user types to you, and any tool argument you send, is visible to you (the model) and is recorded in the conversation. Secrets must not flow through there.
|
|
14
|
+
|
|
15
|
+
Instead, **the user stores the secret themselves** with the `/vault` slash command. Its argument is intercepted by the channel and never sent to you — you only ever learn the *reference*.
|
|
16
|
+
|
|
17
|
+
## How to get a secret stored
|
|
18
|
+
|
|
19
|
+
When you need a secret from the user (e.g. an API key for a platform), tell them to run:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
/vault set <NAME> <value>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For example: "I need your OpenAI API key. Please run `/vault set OPENAI_API_KEY <your-key>` — the value stays local and I'll only get a reference to it." Then **stop and wait** for the user; don't keep working until they confirm.
|
|
26
|
+
|
|
27
|
+
After they run it, you'll receive a short note confirming the secret was stored and giving you the reference `${vault:<NAME>}`. Use that reference — you will never see the value.
|
|
28
|
+
|
|
29
|
+
Naming: use slug-style names like `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `slack_webhook_url` (letters, digits, `_`, `.`, `-`).
|
|
30
|
+
|
|
31
|
+
## Using a stored secret
|
|
32
|
+
|
|
33
|
+
- In `moxxy.config.ts` / `moxxy.config.yaml`: write `${vault:OPENAI_API_KEY}` anywhere a string is expected. The CLI resolves it on session start.
|
|
34
|
+
- For a tool/integration that needs the value at call time: pass the `${vault:NAME}` reference where supported, or let the integration resolve it — do not fetch and inline the plaintext.
|
|
35
|
+
|
|
36
|
+
## Where things live
|
|
37
|
+
|
|
38
|
+
`~/.moxxy/vault.json` — AES-256-GCM ciphertext per entry. The master key comes from the OS keychain (macOS Keychain / libsecret / Windows Credential Manager) when available, otherwise a passphrase prompt; on headless systems set `MOXXY_VAULT_PASSPHRASE`. The first vault use triggers the unlock.
|
|
39
|
+
|
|
40
|
+
## Verify
|
|
41
|
+
|
|
42
|
+
Call `vault_list` (names + metadata only, never plaintext) or have the user run `/vault list` to confirm the entry is present.
|
|
43
|
+
|
|
44
|
+
## Don't
|
|
45
|
+
|
|
46
|
+
- Don't ask the user to type or paste a secret value into the chat — direct them to `/vault set` instead.
|
|
47
|
+
- Don't print the plaintext of an existing secret. If the user wants to verify, suggest they run `/vault list`.
|
|
48
|
+
- Don't reuse a name for two different secrets — storing overwrites silently.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-research
|
|
3
|
+
description: Research a topic on the web — pick the lightest browser tier that gets the answer.
|
|
4
|
+
triggers: ["look up", "research", "find on the web", "browse to", "open page", "scrape", "fetch url"]
|
|
5
|
+
allowed-tools: [web_fetch, browser_session]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Web research
|
|
9
|
+
|
|
10
|
+
Pick the lightest tier that still answers the question.
|
|
11
|
+
|
|
12
|
+
## Tier 1 — `web_fetch` (default)
|
|
13
|
+
|
|
14
|
+
Use for: single static page, public docs, RSS, JSON endpoints, well-known cached pages, anything where the answer is in the initial HTML server response.
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
web_fetch({ url, format: "markdown" })
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`format: "text"` is fine for prose, `"markdown"` keeps headings + links + lists, `"raw"` returns the body untouched (for JSON or HTML you need to parse). Pass `selector: "main"` or `selector: "#content"` to extract a single block from a noisy page.
|
|
21
|
+
|
|
22
|
+
Heuristic: try `web_fetch` first. If the result is empty, looks like a JS shell ("loading...", "you need to enable javascript"), or the page clearly needs interaction (login, cookies, modals, infinite scroll), escalate to Tier 2.
|
|
23
|
+
|
|
24
|
+
## Tier 2 — `browser_session` (Playwright)
|
|
25
|
+
|
|
26
|
+
Use for: JS-heavy SPAs, pages that require clicks/fills, pages with anti-bot detection that browser fingerprinting bypasses, screenshots, anything stateful across multiple actions.
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
browser_session({ action: { kind: "goto", url, waitUntil: "networkidle" } })
|
|
30
|
+
browser_session({ action: { kind: "text", selector: "main" } }) // or no selector for whole body
|
|
31
|
+
browser_session({ action: { kind: "click", selector: "button.show-more" } })
|
|
32
|
+
browser_session({ action: { kind: "fill", selector: "input[name=q]", value: "query" } })
|
|
33
|
+
browser_session({ action: { kind: "screenshot", fullPage: true } })
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Calls within a turn share the same page — `goto` then `click` then `text` is the common pattern.
|
|
37
|
+
|
|
38
|
+
`browser_session` requires Playwright installed in the moxxy install dir. If you get an "init" error mentioning playwright, instruct the user to:
|
|
39
|
+
```
|
|
40
|
+
npm i playwright && npx playwright install
|
|
41
|
+
```
|
|
42
|
+
Then retry, or fall back to `web_fetch` if the page works without JS.
|
|
43
|
+
|
|
44
|
+
## Don't
|
|
45
|
+
|
|
46
|
+
- Don't loop `web_fetch` calls scraping a paginated site — for that you want a small script or `browser_session` with explicit `click`s.
|
|
47
|
+
- Don't `eval` arbitrary user JS via `browser_session.eval` for "convenience." Use `text` / `click` / `fill` instead; `eval` should be the last resort when you genuinely need to read computed state.
|
|
48
|
+
- Don't fetch the same URL repeatedly within a turn — cache the result mentally and re-quote it.
|
|
49
|
+
- Don't request `screenshot` unless the user explicitly asked for one. Image bytes inflate the context.
|
|
50
|
+
|
|
51
|
+
## Report back
|
|
52
|
+
|
|
53
|
+
After the research is done, summarize concisely. Quote source URLs inline (the response already includes the resolved URL from `web_fetch` and `browser_session.url`). If multiple sources contradicted, say so.
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@moxxy/cli",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "moxxy command-line binary. Subcommand dispatcher consuming the moxxy SDK.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/bin.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"moxxy": "./dist/bin.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsx scripts/gen-requirements.mts && tsup",
|
|
18
|
+
"prepack": "node scripts/prepack.mjs",
|
|
19
|
+
"postpack": "node scripts/postpack.mjs",
|
|
20
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"clean": "rm -rf dist .turbo",
|
|
23
|
+
"start": "node ./dist/bin.js"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@moxxy/sdk": "workspace:*",
|
|
27
|
+
"zod": "catalog:"
|
|
28
|
+
},
|
|
29
|
+
"optionalDependencies": {
|
|
30
|
+
"@huggingface/transformers": "^3.0.0",
|
|
31
|
+
"keytar": "^7.9.0",
|
|
32
|
+
"playwright": "^1.40.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@clack/prompts": "catalog:",
|
|
36
|
+
"@types/node": "catalog:",
|
|
37
|
+
"@types/react": "catalog:",
|
|
38
|
+
"ink": "catalog:",
|
|
39
|
+
"punycode": "^2.3.1",
|
|
40
|
+
"react": "catalog:",
|
|
41
|
+
"tsup": "catalog:",
|
|
42
|
+
"tsx": "catalog:",
|
|
43
|
+
"typescript": "catalog:",
|
|
44
|
+
"vitest": "catalog:"
|
|
45
|
+
}
|
|
46
|
+
}
|