musonda 1.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/IMPLEMENTATION.md +432 -0
- package/README.md +119 -0
- package/dist/agent/harness.d.ts +25 -0
- package/dist/agent/harness.js +215 -0
- package/dist/agent/harness.js.map +1 -0
- package/dist/agent/openaiResponsesClient.d.ts +35 -0
- package/dist/agent/openaiResponsesClient.js +250 -0
- package/dist/agent/openaiResponsesClient.js.map +1 -0
- package/dist/agent/systemPrompt.d.ts +9 -0
- package/dist/agent/systemPrompt.js +100 -0
- package/dist/agent/systemPrompt.js.map +1 -0
- package/dist/agent/types.d.ts +53 -0
- package/dist/agent/types.js +2 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli/commands/chat.d.ts +6 -0
- package/dist/cli/commands/chat.js +8 -0
- package/dist/cli/commands/chat.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +4 -0
- package/dist/cli/commands/doctor.js +336 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/gateway.d.ts +20 -0
- package/dist/cli/commands/gateway.js +135 -0
- package/dist/cli/commands/gateway.js.map +1 -0
- package/dist/cli/commands/help.d.ts +4 -0
- package/dist/cli/commands/help.js +159 -0
- package/dist/cli/commands/help.js.map +1 -0
- package/dist/cli/commands/plugins.d.ts +18 -0
- package/dist/cli/commands/plugins.js +250 -0
- package/dist/cli/commands/plugins.js.map +1 -0
- package/dist/cli/commands/service.d.ts +16 -0
- package/dist/cli/commands/service.js +136 -0
- package/dist/cli/commands/service.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +4 -0
- package/dist/cli/commands/setup.js +8 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/status.d.ts +4 -0
- package/dist/cli/commands/status.js +73 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/update.d.ts +22 -0
- package/dist/cli/commands/update.js +226 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/daemon.d.ts +33 -0
- package/dist/cli/daemon.js +208 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/interactiveMenu.d.ts +5 -0
- package/dist/cli/interactiveMenu.js +225 -0
- package/dist/cli/interactiveMenu.js.map +1 -0
- package/dist/cli/musondaCli.d.ts +30 -0
- package/dist/cli/musondaCli.js +269 -0
- package/dist/cli/musondaCli.js.map +1 -0
- package/dist/cli/ui.d.ts +49 -0
- package/dist/cli/ui.js +90 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +315 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/loader.d.ts +12 -0
- package/dist/config/loader.js +217 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/paths.d.ts +11 -0
- package/dist/config/paths.js +18 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/types.d.ts +246 -0
- package/dist/config/types.js +155 -0
- package/dist/config/types.js.map +1 -0
- package/dist/gateway/channels/cli.d.ts +37 -0
- package/dist/gateway/channels/cli.js +298 -0
- package/dist/gateway/channels/cli.js.map +1 -0
- package/dist/gateway/channels/discord.d.ts +18 -0
- package/dist/gateway/channels/discord.js +419 -0
- package/dist/gateway/channels/discord.js.map +1 -0
- package/dist/gateway/channels/slack.d.ts +26 -0
- package/dist/gateway/channels/slack.js +470 -0
- package/dist/gateway/channels/slack.js.map +1 -0
- package/dist/gateway/channels/webhook.d.ts +18 -0
- package/dist/gateway/channels/webhook.js +49 -0
- package/dist/gateway/channels/webhook.js.map +1 -0
- package/dist/gateway/http.d.ts +33 -0
- package/dist/gateway/http.js +605 -0
- package/dist/gateway/http.js.map +1 -0
- package/dist/gateway/server.d.ts +38 -0
- package/dist/gateway/server.js +233 -0
- package/dist/gateway/server.js.map +1 -0
- package/dist/gateway/types.d.ts +28 -0
- package/dist/gateway/types.js +2 -0
- package/dist/gateway/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/memoryManager.d.ts +22 -0
- package/dist/memory/memoryManager.js +200 -0
- package/dist/memory/memoryManager.js.map +1 -0
- package/dist/memory/storage.d.ts +24 -0
- package/dist/memory/storage.js +187 -0
- package/dist/memory/storage.js.map +1 -0
- package/dist/memory/types.d.ts +43 -0
- package/dist/memory/types.js +2 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/plugins/manifest.d.ts +23 -0
- package/dist/plugins/manifest.js +185 -0
- package/dist/plugins/manifest.js.map +1 -0
- package/dist/plugins/mcp/mcpBridge.d.ts +31 -0
- package/dist/plugins/mcp/mcpBridge.js +115 -0
- package/dist/plugins/mcp/mcpBridge.js.map +1 -0
- package/dist/plugins/mcp/mcpClient.d.ts +43 -0
- package/dist/plugins/mcp/mcpClient.js +309 -0
- package/dist/plugins/mcp/mcpClient.js.map +1 -0
- package/dist/plugins/mcp/mcpConfig.d.ts +34 -0
- package/dist/plugins/mcp/mcpConfig.js +326 -0
- package/dist/plugins/mcp/mcpConfig.js.map +1 -0
- package/dist/plugins/mcp/types.d.ts +48 -0
- package/dist/plugins/mcp/types.js +2 -0
- package/dist/plugins/mcp/types.js.map +1 -0
- package/dist/plugins/pluginManager.d.ts +164 -0
- package/dist/plugins/pluginManager.js +1016 -0
- package/dist/plugins/pluginManager.js.map +1 -0
- package/dist/plugins/skills/frontmatterParser.d.ts +28 -0
- package/dist/plugins/skills/frontmatterParser.js +189 -0
- package/dist/plugins/skills/frontmatterParser.js.map +1 -0
- package/dist/plugins/skills/skillDiscovery.d.ts +15 -0
- package/dist/plugins/skills/skillDiscovery.js +148 -0
- package/dist/plugins/skills/skillDiscovery.js.map +1 -0
- package/dist/plugins/skills/skillTools.d.ts +8 -0
- package/dist/plugins/skills/skillTools.js +195 -0
- package/dist/plugins/skills/skillTools.js.map +1 -0
- package/dist/plugins/skills/types.d.ts +25 -0
- package/dist/plugins/skills/types.js +2 -0
- package/dist/plugins/skills/types.js.map +1 -0
- package/dist/plugins/types.d.ts +30 -0
- package/dist/plugins/types.js +2 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +25 -0
- package/dist/scheduler/scheduler.js +239 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/types.d.ts +39 -0
- package/dist/scheduler/types.js +2 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/security/childProcessEnv.d.ts +5 -0
- package/dist/security/childProcessEnv.js +42 -0
- package/dist/security/childProcessEnv.js.map +1 -0
- package/dist/security/fileSandbox.d.ts +153 -0
- package/dist/security/fileSandbox.js +779 -0
- package/dist/security/fileSandbox.js.map +1 -0
- package/dist/service/index.d.ts +30 -0
- package/dist/service/index.js +41 -0
- package/dist/service/index.js.map +1 -0
- package/dist/service/systemd.d.ts +36 -0
- package/dist/service/systemd.js +409 -0
- package/dist/service/systemd.js.map +1 -0
- package/dist/service/types.d.ts +118 -0
- package/dist/service/types.js +6 -0
- package/dist/service/types.js.map +1 -0
- package/dist/tools/builtin/calcTools.d.ts +2 -0
- package/dist/tools/builtin/calcTools.js +51 -0
- package/dist/tools/builtin/calcTools.js.map +1 -0
- package/dist/tools/builtin/fileTools.d.ts +11 -0
- package/dist/tools/builtin/fileTools.js +352 -0
- package/dist/tools/builtin/fileTools.js.map +1 -0
- package/dist/tools/builtin/memoryTools.d.ts +4 -0
- package/dist/tools/builtin/memoryTools.js +123 -0
- package/dist/tools/builtin/memoryTools.js.map +1 -0
- package/dist/tools/builtin/noteTools.d.ts +4 -0
- package/dist/tools/builtin/noteTools.js +91 -0
- package/dist/tools/builtin/noteTools.js.map +1 -0
- package/dist/tools/builtin/pluginTools.d.ts +29 -0
- package/dist/tools/builtin/pluginTools.js +406 -0
- package/dist/tools/builtin/pluginTools.js.map +1 -0
- package/dist/tools/builtin/scheduleTools.d.ts +4 -0
- package/dist/tools/builtin/scheduleTools.js +156 -0
- package/dist/tools/builtin/scheduleTools.js.map +1 -0
- package/dist/tools/builtin/timeTools.d.ts +2 -0
- package/dist/tools/builtin/timeTools.js +41 -0
- package/dist/tools/builtin/timeTools.js.map +1 -0
- package/dist/tools/builtin/webTools.d.ts +12 -0
- package/dist/tools/builtin/webTools.js +325 -0
- package/dist/tools/builtin/webTools.js.map +1 -0
- package/dist/tools/registry.d.ts +15 -0
- package/dist/tools/registry.js +111 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +41 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/utils/logger.d.ts +29 -0
- package/dist/utils/logger.js +124 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/wizard/onboarding.d.ts +3 -0
- package/dist/wizard/onboarding.js +408 -0
- package/dist/wizard/onboarding.js.map +1 -0
- package/package.json +69 -0
- package/plugins/example-calc/mcp.json +11 -0
- package/plugins/example-calc/plugin.json +12 -0
- package/plugins/example-calc/server.mjs +92 -0
- package/plugins/example-calc/skills/math-assistant/SKILL.md +15 -0
- package/plugins/plugin-builder/plugin.json +12 -0
- package/plugins/plugin-builder/skills/create-plugin/SKILL.md +149 -0
- package/plugins/plugin-builder/skills/manage-plugins/SKILL.md +157 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# Musonda Implementation Guide 🛠️
|
|
2
|
+
|
|
3
|
+
This is the technical reference for Musonda contributors, integrators, and
|
|
4
|
+
operators. For a friendly introduction and quick start, see the
|
|
5
|
+
[README](README.md).
|
|
6
|
+
|
|
7
|
+
Musonda is a personal AI assistant and autonomous agent harness built with
|
|
8
|
+
TypeScript. It runs continuously inside an extensible gateway service connecting
|
|
9
|
+
the terminal, Discord, Slack, and HTTP clients.
|
|
10
|
+
|
|
11
|
+
Musonda uses the OpenAI Responses API and includes persistent memory, dynamic
|
|
12
|
+
persona synthesis, autonomous scheduling, and an extensible tool suite.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🌟 Key Features
|
|
17
|
+
|
|
18
|
+
- **Autonomous Gateway Architecture**:
|
|
19
|
+
- Central gateway daemon managing connections to communication platforms.
|
|
20
|
+
- **CLI Channel**: Interactive terminal REPL session (`npm run cli` or `npm start -- --cli`) for real-time conversational agent interaction directly in your terminal, with live thinking indicators, built-in slash commands (`/help`, `/status`, `/profile`, `/clear`, `/exit`), and proactive scheduled reminder delivery.
|
|
21
|
+
- **Discord Channel**: Full `discord.js` adapter with mention stripping, typing indicators, auto-chunking (2000-character Discord limit), and security whitelists.
|
|
22
|
+
- **Slack Channel**: Native `@slack/bolt` adapter with **Socket Mode** (no public IP/tunnel needed!), app mentions (`@Musonda`), DMs, thread replies, and visual reaction feedback.
|
|
23
|
+
- **HTTP REST Gateway** (port 4141): Exposes `/health`, `/status`, `/api/profile`, `/api/memories`, `/api/schedules`, and `/api/chat`.
|
|
24
|
+
- Per-session message concurrency queues preventing race conditions.
|
|
25
|
+
|
|
26
|
+
- **⏰ Autonomous Schedules & Cron Jobs Engine**:
|
|
27
|
+
- Built-in persistent scheduler using `croner` running inside the Gateway daemon.
|
|
28
|
+
- **Recurring Cron Tasks**: Standard 5-part cron syntax (e.g. `0 8 * * 1-5` for weekdays at 8:00 AM) evaluated in your configured timezone.
|
|
29
|
+
- **Relative Timers**: Convenient relative delays like `+30m`, `+2h`, `+1d` for one-time reminders.
|
|
30
|
+
- **Dynamic Autonomous Execution**: Scheduled jobs can run autonomous instruction prompts through Musonda's full reasoning harness (with memory & web search) or send direct reminder notes to your Discord/Slack channel.
|
|
31
|
+
- **Conversational Management**: Say *"Remind me in 45 minutes to check tests"* or *"Every weekday at 9am send a standup summary"* directly in Discord or Slack.
|
|
32
|
+
|
|
33
|
+
- **Initial Onboarding Wizard**:
|
|
34
|
+
- Interactive CLI setup wizard (`npm run onboard`) that runs before first boot.
|
|
35
|
+
- Allows you to give a **custom name** to your assistant (or keep default *Musonda*).
|
|
36
|
+
- Collects your name, background/bio, timezone, and communication style.
|
|
37
|
+
- Configures OpenAI credentials (`gpt-5.6-luna`), Discord bot token, and Slack tokens.
|
|
38
|
+
- Automatically seeds your assistant's persistent memory with your preferences.
|
|
39
|
+
|
|
40
|
+
- **OpenAI Responses API (`gpt-5.6-luna`)**:
|
|
41
|
+
- Built specifically for the OpenAI `/v1/responses` API.
|
|
42
|
+
- Native support for system `instructions`, stateful conversation items, function tool calling (`type: 'function'`), and `function_call_output` returns.
|
|
43
|
+
- Optimized compatibility with `gpt-5.6-luna` parameter expectations.
|
|
44
|
+
|
|
45
|
+
- **Multi-Tiered Memory System**:
|
|
46
|
+
- **User Profile**: Structured identity, bio, timezone, and tone settings stored in `data/memory/profile.json`.
|
|
47
|
+
- **Long-Term Memory**: Persistent store for facts, preferences, project details, and goals with keyword and semantic relevance scoring.
|
|
48
|
+
- **Short-Term Session History**: Sliding conversation context buffer per channel and user saved in `data/sessions/`.
|
|
49
|
+
- **Automatic Recall**: Musonda automatically retrieves relevant past memories matching your query and injects them into the reasoning prompt.
|
|
50
|
+
|
|
51
|
+
- **Extensible Tool Registry (Hermes / OpenClaw Pattern)**:
|
|
52
|
+
- `schedule_create`: Schedule a one-off timer or recurring cron job.
|
|
53
|
+
- `schedule_list`: List active cron jobs and timers.
|
|
54
|
+
- `schedule_cancel`: Cancel an active schedule by ID or title.
|
|
55
|
+
- `memory_store`: Store facts, user preferences, or project updates directly into long-term memory.
|
|
56
|
+
- `memory_recall`: Search and retrieve past memories matching keywords or queries.
|
|
57
|
+
- `update_user_profile`: Dynamically update your profile (bio, name, tone, notes).
|
|
58
|
+
- `current_time`: Timezone-aware date, time, and day information.
|
|
59
|
+
- `calculate`: Safe mathematical expression evaluation.
|
|
60
|
+
- `web_fetch`: Fetch webpage content and extract clean text.
|
|
61
|
+
- `web_search`: Query real-time search information.
|
|
62
|
+
- `note_save`, `note_read`, `note_list`: Manage personal markdown notes and documents.
|
|
63
|
+
- `plugin_install`: Install and activate portable Agent Plugins from Git repositories or local folders.
|
|
64
|
+
- `plugin_remove`: Safely uninstall and unload plugins from runtime and disk.
|
|
65
|
+
- `plugin_find`: Search and discover plugins locally and across ecosystem catalogs.
|
|
66
|
+
- `plugin_info`: Deeply inspect plugin manifests, skills, and MCP configuration.
|
|
67
|
+
- `plugin_create`: Scaffold and dynamically author new Agent Plugins on the fly.
|
|
68
|
+
- `plugin_reload`: Refresh plugins from disk without restarting the Gateway daemon.
|
|
69
|
+
- `plugin_list`: Inspect all loaded plugins, active MCP tools, and skills in runtime.
|
|
70
|
+
- `skill_list`, `skill_read`, `skill_run_script`: Progressive disclosure and execution of Agent Skills.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 📁 Project Structure
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
musonda/
|
|
79
|
+
├── package.json # Scripts, ESM configuration, dependencies
|
|
80
|
+
├── tsconfig.json # TypeScript configuration
|
|
81
|
+
├── .env.example # Example environment variables
|
|
82
|
+
├── README.md # Documentation
|
|
83
|
+
├── src/
|
|
84
|
+
│ ├── index.ts # Gateway daemon entry point (checks onboarding)
|
|
85
|
+
│ ├── config/
|
|
86
|
+
│ │ ├── types.ts # Zod schemas & TypeScript config types
|
|
87
|
+
│ │ └── loader.ts # Reads/writes musonda.config.json & .env
|
|
88
|
+
│ ├── wizard/
|
|
89
|
+
│ │ └── onboarding.ts # Interactive initial onboarding wizard (Discord & Slack)
|
|
90
|
+
│ ├── scheduler/
|
|
91
|
+
│ │ ├── types.ts # ScheduleJob, ScheduleTarget, and action interfaces
|
|
92
|
+
│ │ └── scheduler.ts # MusondaScheduler (Croner engine, persistence, triggers)
|
|
93
|
+
│ ├── memory/
|
|
94
|
+
│ │ ├── types.ts # Memory & session data models
|
|
95
|
+
│ │ ├── storage.ts # Atomic JSON file storage engine
|
|
96
|
+
│ │ └── memoryManager.ts # Profile, long-term memory, auto-recall & sessions
|
|
97
|
+
│ ├── tools/
|
|
98
|
+
│ │ ├── types.ts # Tool interfaces & OpenAI function schemas
|
|
99
|
+
│ │ ├── registry.ts # Tool registry and execution dispatcher
|
|
100
|
+
│ │ └── builtin/
|
|
101
|
+
│ │ ├── scheduleTools.ts # schedule_create, schedule_list, schedule_cancel
|
|
102
|
+
│ │ ├── memoryTools.ts # memory_store, memory_recall, update_user_profile
|
|
103
|
+
│ │ ├── timeTools.ts # current_time
|
|
104
|
+
│ │ ├── calcTools.ts # calculate
|
|
105
|
+
│ │ ├── webTools.ts # web_fetch, web_search
|
|
106
|
+
│ │ └── noteTools.ts # note_save, note_read, note_list
|
|
107
|
+
│ ├── agent/
|
|
108
|
+
│ │ ├── types.ts # Agent message and run types
|
|
109
|
+
│ │ ├── systemPrompt.ts # Dynamic system prompt & persona builder
|
|
110
|
+
│ │ ├── openaiResponsesClient.ts # OpenAI Responses API client (gpt-5.6-luna)
|
|
111
|
+
│ │ └── harness.ts # Multi-turn reasoning & tool calling loop
|
|
112
|
+
│ ├── gateway/
|
|
113
|
+
│ │ ├── types.ts # Inbound/outbound message & adapter interfaces
|
|
114
|
+
│ │ ├── server.ts # MusondaGateway central daemon
|
|
115
|
+
│ │ ├── http.ts # HTTP REST server (health, chat, schedules API)
|
|
116
|
+
│ │ └── channels/
|
|
117
|
+
│ │ ├── cli.ts # Interactive CLI terminal channel adapter
|
|
118
|
+
│ │ ├── discord.ts # Discord.js bot channel adapter
|
|
119
|
+
│ │ ├── slack.ts # Slack Bolt channel adapter (Socket Mode)
|
|
120
|
+
│ │ └── webhook.ts # Webhook / API direct channel adapter
|
|
121
|
+
│ └── utils/
|
|
122
|
+
│ └── logger.ts # Structured, colored logging
|
|
123
|
+
└── test/
|
|
124
|
+
├── harness.test.ts # Unit tests for config, memory, tools & prompt
|
|
125
|
+
├── gateway.test.ts # Integration test for Gateway HTTP & lifecycle
|
|
126
|
+
└── scheduler.test.ts # Unit tests for Scheduler, Cron jobs & timers
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## ⏰ Schedules & Cron Jobs
|
|
132
|
+
|
|
133
|
+
Musonda runs scheduled tasks autonomously in the background.
|
|
134
|
+
|
|
135
|
+
### Natural Language Examples (In Slack or Discord):
|
|
136
|
+
- **Relative Timers**:
|
|
137
|
+
> *"Remind me in 30 minutes to drink water"*
|
|
138
|
+
> *"Remind me in 2 hours to check our deployment logs"*
|
|
139
|
+
- **Recurring Cron Jobs**:
|
|
140
|
+
> *"Every weekday at 8:30 AM, send me a brief news summary on Slack"*
|
|
141
|
+
> *"Every Sunday at 6 PM, check my notes and send me a weekly review"*
|
|
142
|
+
- **Inspecting & Cancelling**:
|
|
143
|
+
> *"What schedules do I have active?"*
|
|
144
|
+
> *"Cancel my water reminder schedule"*
|
|
145
|
+
|
|
146
|
+
### Cron Pattern Reference:
|
|
147
|
+
- `0 9 * * 1-5`: Monday through Friday at 9:00 AM (in your configured timezone)
|
|
148
|
+
- `0 8 * * *`: Every day at 8:00 AM
|
|
149
|
+
- `*/30 * * * *`: Every 30 minutes
|
|
150
|
+
- `+45m`: 45 minutes from now (one-time)
|
|
151
|
+
- `+2h`: 2 hours from now (one-time)
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 💻 Musonda Interactive CLI (`musonda`)
|
|
156
|
+
|
|
157
|
+
Musonda comes with an all-in-one interactive Command Line Interface. Users install `musonda` (globally via `npm install -g musonda` or clone & `npm link`), and `musonda` manages the entire agent lifecycle—including launching the gateway, live terminal chat, plugin management, system diagnostics, and autonomous updates.
|
|
158
|
+
|
|
159
|
+
### Quick Command Reference:
|
|
160
|
+
|
|
161
|
+
| Command | Description |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `musonda` | Open the **Interactive Terminal Dashboard & Menu** |
|
|
164
|
+
| `musonda start gateway` | Start the Musonda Gateway server (foreground mode) |
|
|
165
|
+
| `musonda start gateway --daemon` | Start the Gateway in the background as a daemon process |
|
|
166
|
+
| `musonda stop gateway` | Gracefully stop the running Gateway daemon |
|
|
167
|
+
| `musonda restart gateway` | Restart the Gateway daemon |
|
|
168
|
+
| `musonda cli` | Open interactive AI chat with real-time streaming tokens |
|
|
169
|
+
| `musonda help` | View comprehensive CLI help & documentation |
|
|
170
|
+
| `musonda add plugin <name\|repo\|path>` | Install and activate an Agent Plugin (local, git, or curated) |
|
|
171
|
+
| `musonda remove plugin <name>` | Uninstall and remove an installed Agent Plugin |
|
|
172
|
+
| `musonda list plugins` | List all installed plugins, skills, and MCP tools |
|
|
173
|
+
| `musonda find plugins [query]` | Search plugins across local catalog and remote ecosystem |
|
|
174
|
+
| `musonda update` | Check and apply updates to both the core agent and installed plugins |
|
|
175
|
+
| `musonda update agent` | Update core Musonda assistant package (npm or git) |
|
|
176
|
+
| `musonda update plugins` | Update all installed Agent Plugins from git remotes |
|
|
177
|
+
| `musonda status` | Display live gateway health, AI model, persona, channels, and uptime |
|
|
178
|
+
| `musonda doctor` | Run system diagnostics (Node.js, configs, keys, storage, ports, plugins) |
|
|
179
|
+
| `musonda setup` | Run interactive onboarding wizard to configure persona & keys |
|
|
180
|
+
| `musonda version` | Display Musonda version and environment info |
|
|
181
|
+
|
|
182
|
+
### Interactive Dashboard:
|
|
183
|
+
Running `musonda` without arguments in an interactive terminal opens the interactive terminal dashboard:
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
███╗ ███╗██╗ ██╗███████╗ ██████╗ ███╗ ██╗██████╗ █████╗
|
|
187
|
+
████╗ ████║██║ ██║██╔════╝██╔═══██╗████╗ ██║██╔══██╗██╔══██╗
|
|
188
|
+
██╔████╔██║██║ ██║███████╗██║ ██║██╔██╗ ██║██║ ██║███████║
|
|
189
|
+
██║╚██╔╝██║██║ ██║╚════██║██║ ██║██║╚██╗██║██║ ██║██╔══██║
|
|
190
|
+
██║ ╚═╝ ██║╚██████╔╝███████║╚██████╔╝██║ ╚████║██████╔╝██║ ██║
|
|
191
|
+
╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚═╝ ╚═╝
|
|
192
|
+
=== Musonda Personal AI Assistant & Autonomous Agent Harness ===
|
|
193
|
+
|
|
194
|
+
Assistant: Musonda | Model: gpt-5.6-luna | Gateway: ● Online (http://127.0.0.1:4141)
|
|
195
|
+
|
|
196
|
+
[1] 💬 Start Chat CLI (Talk directly to Musonda)
|
|
197
|
+
[2] 🚀 Start Gateway Daemon (Run server in background)
|
|
198
|
+
[3] ✖ Stop Gateway Daemon
|
|
199
|
+
[4] 🧩 Manage Plugins (Add, list, search, remove Agent Plugins)
|
|
200
|
+
[5] 🔄 Update Musonda & Plugins (Check & apply updates)
|
|
201
|
+
[6] 📊 System Status & Health
|
|
202
|
+
[7] 🩺 Run Musonda Doctor (Environment diagnostics)
|
|
203
|
+
[8] ⚙️ Setup & Onboarding Wizard
|
|
204
|
+
[9] 💻 Interactive Command Shell (musonda>)
|
|
205
|
+
[10] 📖 Help & Command Reference
|
|
206
|
+
[0] 🚪 Exit
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Adding & Managing Plugins:
|
|
210
|
+
Musonda supports the universal [Agent Plugins 1.0.0 Specification](https://agent-plugins.org/):
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Add a curated ecosystem plugin
|
|
214
|
+
musonda add plugin example-calc
|
|
215
|
+
|
|
216
|
+
# Add a plugin from GitHub
|
|
217
|
+
musonda add plugin agentplugins/agent-plugins-example
|
|
218
|
+
|
|
219
|
+
# Add a plugin from a local directory
|
|
220
|
+
musonda add plugin ./my-custom-plugin
|
|
221
|
+
|
|
222
|
+
# List installed plugins
|
|
223
|
+
musonda list plugins
|
|
224
|
+
|
|
225
|
+
# Search ecosystem for plugins
|
|
226
|
+
musonda find plugins math
|
|
227
|
+
|
|
228
|
+
# Remove an installed plugin
|
|
229
|
+
musonda remove plugin example-calc
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Managing Agent Updates:
|
|
233
|
+
```bash
|
|
234
|
+
# Check and apply updates to both agent and plugins
|
|
235
|
+
musonda update
|
|
236
|
+
|
|
237
|
+
# Dry-run check without applying
|
|
238
|
+
musonda update --check-only
|
|
239
|
+
|
|
240
|
+
# Update only the core Musonda agent
|
|
241
|
+
musonda update agent
|
|
242
|
+
|
|
243
|
+
# Update installed plugins from git
|
|
244
|
+
musonda update plugins
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 🚀 Getting Started
|
|
250
|
+
|
|
251
|
+
### 1. Installation
|
|
252
|
+
|
|
253
|
+
Install globally to use the `musonda` CLI anywhere:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
npm install -g musonda
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Or clone and link locally:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
git clone https://github.com/musonda/musonda.git
|
|
263
|
+
cd musonda
|
|
264
|
+
npm install
|
|
265
|
+
npm run build
|
|
266
|
+
npm link
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 2. Run Diagnostics & Setup
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
# Check environment and prerequisites
|
|
273
|
+
musonda doctor
|
|
274
|
+
|
|
275
|
+
# Run interactive setup wizard
|
|
276
|
+
musonda setup
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### 3. Start the Gateway & Chat
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# Start Gateway daemon in background
|
|
283
|
+
musonda start gateway --daemon
|
|
284
|
+
|
|
285
|
+
# Open conversational chat session with real-time streaming
|
|
286
|
+
musonda cli
|
|
287
|
+
|
|
288
|
+
# Check live status
|
|
289
|
+
musonda status
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## 🌐 Gateway HTTP Endpoints
|
|
295
|
+
|
|
296
|
+
The Gateway exposes an HTTP server (default: `http://127.0.0.1:4141` or `0.0.0.0:4141` in container mode):
|
|
297
|
+
|
|
298
|
+
| Method | Endpoint | Description |
|
|
299
|
+
|---|---|---|
|
|
300
|
+
| `GET` | `/health` or `/status` | Liveness and, when authenticated, gateway diagnostics |
|
|
301
|
+
| `GET` | `/ready` or `/readyz` | Readiness probe (in production, verifies gateway auth, storage, AI credentials, and model access) |
|
|
302
|
+
| `GET` | `/api/profile` | Inspect active user profile and assistant persona (Auth required) |
|
|
303
|
+
| `GET` | `/api/memories` | Inspect all persistent memories (Auth required) |
|
|
304
|
+
| `GET` | `/api/plugins` | Inspect all loaded Agent Plugins, active MCP tools, and skills (Auth required) |
|
|
305
|
+
| `POST` | `/api/plugins/install` | Dynamically install and activate a plugin in live runtime (Auth required, restricted to plugin dir) |
|
|
306
|
+
| `POST` | `/api/plugins/remove` | Dynamically remove and unload a plugin from runtime and disk (Auth required) |
|
|
307
|
+
| `GET` | `/api/plugins/find` | Discover plugins across local directories and remote ecosystem (Auth required) |
|
|
308
|
+
| `GET` | `/api/plugins/info` | Inspect detailed manifest, skills, and MCP tools of a plugin (Auth required) |
|
|
309
|
+
| `GET` | `/api/schedules` | Inspect all scheduled cron jobs and timers (Auth required) |
|
|
310
|
+
| `POST` | `/api/schedules` | Create a schedule via JSON API (Auth required) |
|
|
311
|
+
| `POST` | `/api/schedules/cancel` | Cancel an active schedule by `{ "id": "..." }` (Auth required) |
|
|
312
|
+
| `POST` | `/api/chat` | Send a JSON payload `{"message": "Hello!"}` (supports SSE streaming, Auth required) |
|
|
313
|
+
| `POST` | `/api/gateway/stop` | Gracefully shut down the Gateway daemon (Auth required) |
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## 🐳 Production & Container Deployment
|
|
318
|
+
|
|
319
|
+
### 1. Docker Compose Deployment (Recommended)
|
|
320
|
+
Musonda includes a production-ready `Dockerfile` and `docker-compose.yml` with:
|
|
321
|
+
- **Non-root execution**: Runs under unprivileged `node` user (UID 1000) with permissions configured on `/app/data`.
|
|
322
|
+
- **Zero-host-lock networking**: Sets `HOST=0.0.0.0` and `GATEWAY_HOST=0.0.0.0` inside container.
|
|
323
|
+
- **Mandatory Production Authentication**: Protected `/api/*` endpoints fail closed when `NODE_ENV=production` unless a valid `GATEWAY_API_KEY` is provided.
|
|
324
|
+
- **Health & Readiness Probes**: `/health` is a lightweight liveness endpoint; the container `HEALTHCHECK` uses `/ready` and verifies storage, authentication configuration, and live AI model access.
|
|
325
|
+
- **Persistent Plugins**: Runtime plugin installation targets `/app/data/plugins`, which is covered by the Compose volume. Bundled plugins remain available from `/app/plugins`.
|
|
326
|
+
- **Private Host Binding**: Compose publishes the gateway on `127.0.0.1:4141`; use a TLS-terminating reverse proxy for public access.
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
# 1. Copy and configure production secrets
|
|
330
|
+
cp .env.example .env
|
|
331
|
+
# Set GATEWAY_API_KEY, OPENAI_API_KEY, etc. in .env
|
|
332
|
+
|
|
333
|
+
# 2. Start container service with persistent volume
|
|
334
|
+
docker compose up -d
|
|
335
|
+
|
|
336
|
+
# 3. View live logs
|
|
337
|
+
docker compose logs -f
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### 2. Security Hardening & Guardrails
|
|
341
|
+
- **Authentication**: All `/api/*` endpoints require `Authorization: Bearer <key>` or `X-API-Key: <key>`. Comparisons use timing-safe equality to prevent side-channel timing attacks.
|
|
342
|
+
- **Proxy Trust**: `X-Forwarded-For` is ignored by default. Set `GATEWAY_TRUST_PROXY=true` only when direct gateway access is blocked and your reverse proxy overwrites that header.
|
|
343
|
+
- **Plugin Sandbox Lockdown**: Plugin installation only permits installing into managed plugin directories; path traversal and arbitrary directory deletions are strictly blocked. Dependency installations run with `--ignore-scripts` to block untrusted postinstall scripts.
|
|
344
|
+
- **Child Process Secret Isolation**: MCP servers and skill scripts inherit only safe system environment variables; critical secrets (`OPENAI_API_KEY`, channel tokens, gateway key) are filtered out.
|
|
345
|
+
- **SSRF Hardening**: Outbound web requests validate hostnames and DNS records against loopback, link-local, carrier-grade NAT, and RFC 1918 private subnets, then pin the connection to the validated address to prevent DNS rebinding. Redirects are manually revalidated and response bodies have a strict 2 MB limit.
|
|
346
|
+
- **Persistence & Snapshots**: `JsonStorage` implements file-based concurrency locking, automatic pre-write `.bak` snapshots, and timestamped backup rotation.
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
## 🧩 Agent Plugins Subsystem (v1.0.0 Specification - https://agent-plugins.org/)
|
|
352
|
+
|
|
353
|
+
Musonda natively implements the vendor-neutral **Agent Plugins Specification 1.0.0** ([https://agent-plugins.org/](https://agent-plugins.org/)). This allows you and Musonda to discover, install, author, use, and remove portable agent packages on the fly.
|
|
354
|
+
|
|
355
|
+
### Package Architecture:
|
|
356
|
+
```text
|
|
357
|
+
plugins/<plugin-name>/
|
|
358
|
+
├── plugin.json # Required manifest ($schema: https://agent-plugins.org/schemas/1.0.0/plugin.schema.json)
|
|
359
|
+
├── mcp.json # Optional MCP server definitions ($schema: https://agent-plugins.org/schemas/1.0.0/mcp.schema.json)
|
|
360
|
+
└── skills/ # Optional Agent Skills directory
|
|
361
|
+
└── <skill-name>/
|
|
362
|
+
├── SKILL.md # Skill instructions with YAML frontmatter
|
|
363
|
+
├── scripts/ # Bundled executable automation scripts
|
|
364
|
+
└── references/ # Knowledge base & reference files
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Conversational Plugin Management:
|
|
368
|
+
You can manage plugins naturally via Discord, Slack, or terminal CLI:
|
|
369
|
+
- **Find Plugins**:
|
|
370
|
+
> *"Musonda, find plugins for PostgreSQL database management"*
|
|
371
|
+
> *"What plugins are available for GitHub integration?"*
|
|
372
|
+
- **Install Plugins**:
|
|
373
|
+
> *"Install the plugin from https://github.com/agentplugins/agent-plugins-example.git"*
|
|
374
|
+
> *"Add plugin from ./plugins/my-custom-tools"*
|
|
375
|
+
- **Use Skills & Tools**:
|
|
376
|
+
> *"What skills do you have installed?"* (`skill_list`)
|
|
377
|
+
> *"Read instructions for the code-review skill"* (`skill_read`)
|
|
378
|
+
> Directly invoke any connected MCP tools (`mcp_<plugin>_<server>_<tool>`)
|
|
379
|
+
- **Remove Plugins**:
|
|
380
|
+
> *"Remove the weather plugin"* (`plugin_remove`)
|
|
381
|
+
- **Inspect Status**:
|
|
382
|
+
> *"List loaded plugins and their MCP servers"* (`plugin_list` or `plugin_info`)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## 🧪 Testing
|
|
388
|
+
|
|
389
|
+
Run the full automated test suite:
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
npm test
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Runs:
|
|
396
|
+
1. `harness.test.ts`: Config, Memory, Built-in Tools, Dynamic Prompt Builder, Webhook.
|
|
397
|
+
2. `gateway.test.ts`: Gateway lifecycle, Channel statuses, REST endpoints.
|
|
398
|
+
3. `scheduler.test.ts`: Cron jobs, Relative timers, Auto-triggers, Persistence, Schedule tools.
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## 📦 Releasing to npm
|
|
403
|
+
|
|
404
|
+
Publish the version currently declared in `package.json`:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
npm run release
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
For later releases, bump the version and publish in one command:
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
npm run release:patch
|
|
414
|
+
npm run release:minor
|
|
415
|
+
npm run release:major
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Every publish runs the build, full test suite, and package dry run first. npm will
|
|
419
|
+
prompt for two-factor authentication when required. If a bumped release fails,
|
|
420
|
+
the original `package.json` and `package-lock.json` versions are restored.
|
|
421
|
+
|
|
422
|
+
Run only the release checks with:
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
npm run release:check
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## 📄 License
|
|
431
|
+
|
|
432
|
+
MIT License.
|
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Musonda
|
|
2
|
+
|
|
3
|
+
**A personal AI companion that remembers, helps, and stays available.**
|
|
4
|
+
|
|
5
|
+
Musonda is an AI assistant you can run and make your own. It gets to know how
|
|
6
|
+
you like to work, remembers the things that matter to you, and helps you carry
|
|
7
|
+
ideas and tasks from one conversation to the next.
|
|
8
|
+
|
|
9
|
+
Talk to Musonda from your terminal, Discord, or Slack. Ask a question, save a
|
|
10
|
+
note, set a reminder, look something up, or give it new abilities with plugins.
|
|
11
|
+
|
|
12
|
+
## Meet Musonda
|
|
13
|
+
|
|
14
|
+
Most AI conversations disappear when the chat ends. Musonda is designed to be
|
|
15
|
+
part of your day: available when you need it and able to remember useful context
|
|
16
|
+
over time.
|
|
17
|
+
|
|
18
|
+
You decide its name, personality, communication style, and where you want to
|
|
19
|
+
talk to it. Musonda can be a quiet helper in your terminal, a teammate in Slack,
|
|
20
|
+
or a companion you message on Discord.
|
|
21
|
+
|
|
22
|
+
## What can Musonda do?
|
|
23
|
+
|
|
24
|
+
- **Remember what matters** — preferences, goals, project details, and useful
|
|
25
|
+
facts can carry into future conversations.
|
|
26
|
+
- **Keep you on track** — create one-time reminders or recurring routines using
|
|
27
|
+
everyday language.
|
|
28
|
+
- **Take and find notes** — save ideas, lists, and personal notes, then ask for
|
|
29
|
+
them later.
|
|
30
|
+
- **Help with everyday questions** — search the web, read pages, calculate, and
|
|
31
|
+
work through a problem with you.
|
|
32
|
+
- **Meet you where you work** — chat in the terminal, Discord, Slack, or through
|
|
33
|
+
another app connected to Musonda.
|
|
34
|
+
- **Learn new abilities** — install plugins and skills when you want Musonda to
|
|
35
|
+
do more.
|
|
36
|
+
|
|
37
|
+
## Things you can say
|
|
38
|
+
|
|
39
|
+
> “Remember that I prefer short progress updates.”
|
|
40
|
+
|
|
41
|
+
> “Remind me in 45 minutes to check the deployment.”
|
|
42
|
+
|
|
43
|
+
> “Every weekday morning, help me prepare for stand-up.”
|
|
44
|
+
|
|
45
|
+
> “Save this idea in my notes.”
|
|
46
|
+
|
|
47
|
+
> “What do you remember about my current project?”
|
|
48
|
+
|
|
49
|
+
> “Find a plugin that can help me work with GitHub.”
|
|
50
|
+
|
|
51
|
+
## Getting started
|
|
52
|
+
|
|
53
|
+
You need [Node.js](https://nodejs.org/) 20 or newer and an OpenAI API key.
|
|
54
|
+
Discord and Slack are optional—you can start entirely in your terminal.
|
|
55
|
+
|
|
56
|
+
Install Musonda:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install -g musonda
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Run the guided setup:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
musonda setup
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then open Musonda:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
musonda
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The setup guide helps you choose how Musonda should address you, how it should
|
|
75
|
+
communicate, and which channels you want to connect.
|
|
76
|
+
|
|
77
|
+
To go straight to a terminal conversation:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
musonda start gateway --daemon
|
|
81
|
+
musonda cli
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Make it yours
|
|
85
|
+
|
|
86
|
+
Musonda starts with useful everyday abilities, but it does not have to stay the
|
|
87
|
+
same. You can shape its personality, connect the channels you enjoy using, and
|
|
88
|
+
add plugins for the kind of work you do.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Discover available plugins
|
|
92
|
+
musonda find plugins
|
|
93
|
+
|
|
94
|
+
# Add a plugin
|
|
95
|
+
musonda add plugin example-calc
|
|
96
|
+
|
|
97
|
+
# See what is installed
|
|
98
|
+
musonda list plugins
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
You can also ask Musonda to find, install, explain, or remove plugins during a
|
|
102
|
+
conversation.
|
|
103
|
+
|
|
104
|
+
## Everyday commands
|
|
105
|
+
|
|
106
|
+
| Command | What it does |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `musonda` | Opens the main menu |
|
|
109
|
+
| `musonda setup` | Personalizes and connects Musonda |
|
|
110
|
+
| `musonda cli` | Starts a terminal conversation |
|
|
111
|
+
| `musonda start gateway --daemon` | Keeps Musonda running in the background |
|
|
112
|
+
| `musonda stop gateway` | Stops the background assistant |
|
|
113
|
+
| `musonda status` | Shows whether Musonda is running |
|
|
114
|
+
| `musonda doctor` | Checks your setup and suggests fixes |
|
|
115
|
+
| `musonda help` | Shows all available commands |
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT License.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { MusondaConfig } from '../config/types.js';
|
|
2
|
+
import { MemoryManager } from '../memory/memoryManager.js';
|
|
3
|
+
import { ToolRegistry } from '../tools/registry.js';
|
|
4
|
+
import { OpenAIResponsesClient } from './openaiResponsesClient.js';
|
|
5
|
+
import { AgentRunInput, AgentRunResult } from './types.js';
|
|
6
|
+
export declare class AgentHarness {
|
|
7
|
+
private config;
|
|
8
|
+
private memory;
|
|
9
|
+
private tools;
|
|
10
|
+
private client;
|
|
11
|
+
private maxToolIterations;
|
|
12
|
+
private scheduler?;
|
|
13
|
+
private pluginManager?;
|
|
14
|
+
constructor(config: MusondaConfig, memory: MemoryManager, tools: ToolRegistry, client: OpenAIResponsesClient, scheduler?: any, pluginManager?: any);
|
|
15
|
+
setScheduler(scheduler: any): void;
|
|
16
|
+
getScheduler(): any;
|
|
17
|
+
setPluginManager(pluginManager: any): void;
|
|
18
|
+
getPluginManager(): any;
|
|
19
|
+
getMemoryManager(): MemoryManager;
|
|
20
|
+
getToolRegistry(): ToolRegistry;
|
|
21
|
+
getClient(): OpenAIResponsesClient;
|
|
22
|
+
private formatToolStatus;
|
|
23
|
+
private formatToolReviewStatus;
|
|
24
|
+
processMessage(input: AgentRunInput): Promise<AgentRunResult>;
|
|
25
|
+
}
|