openpersona 0.2.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/LICENSE +21 -0
- package/README.md +309 -0
- package/bin/cli.js +253 -0
- package/layers/embodiments/README.md +30 -0
- package/layers/faculties/music/SKILL.md +157 -0
- package/layers/faculties/music/faculty.json +9 -0
- package/layers/faculties/music/scripts/compose.sh +169 -0
- package/layers/faculties/reminder/SKILL.md +17 -0
- package/layers/faculties/reminder/faculty.json +9 -0
- package/layers/faculties/selfie/SKILL.md +151 -0
- package/layers/faculties/selfie/faculty.json +9 -0
- package/layers/faculties/selfie/scripts/generate-image.sh +202 -0
- package/layers/faculties/soul-evolution/SKILL.md +41 -0
- package/layers/faculties/soul-evolution/faculty.json +9 -0
- package/layers/faculties/soul-evolution/soul-state.template.json +1 -0
- package/layers/faculties/voice/SKILL.md +189 -0
- package/layers/faculties/voice/faculty.json +9 -0
- package/layers/faculties/voice/scripts/speak.js +169 -0
- package/layers/faculties/voice/scripts/speak.sh +171 -0
- package/layers/skills/README.md +33 -0
- package/layers/soul/README.md +18 -0
- package/lib/contributor.js +392 -0
- package/lib/downloader.js +106 -0
- package/lib/generator.js +301 -0
- package/lib/installer.js +227 -0
- package/lib/publisher/clawhub.js +42 -0
- package/lib/publisher/index.js +14 -0
- package/lib/searcher.js +24 -0
- package/lib/uninstaller.js +82 -0
- package/lib/utils.js +56 -0
- package/package.json +51 -0
- package/presets/ai-girlfriend/manifest.json +24 -0
- package/presets/ai-girlfriend/persona.json +25 -0
- package/presets/health-butler/manifest.json +21 -0
- package/presets/health-butler/persona.json +20 -0
- package/presets/life-assistant/manifest.json +21 -0
- package/presets/life-assistant/persona.json +20 -0
- package/presets/samantha/manifest.json +29 -0
- package/presets/samantha/persona.json +25 -0
- package/schemas/body/embodiment.schema.json +27 -0
- package/schemas/faculty/faculty.schema.json +25 -0
- package/schemas/manifest.schema.json +52 -0
- package/schemas/skill/skill-declaration.spec.md +15 -0
- package/schemas/soul/persona.schema.json +37 -0
- package/schemas/soul/soul-state.schema.json +30 -0
- package/skill/SKILL.md +170 -0
- package/templates/identity.template.md +9 -0
- package/templates/readme.template.md +23 -0
- package/templates/skill.template.md +20 -0
- package/templates/soul-injection.template.md +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 OpenPersona
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# OpenPersona
|
|
2
|
+
|
|
3
|
+
An open four-layer agent framework: **Soul / Body / Faculty / Skill**. Create, compose, and orchestrate AI persona skill packs.
|
|
4
|
+
|
|
5
|
+
Inspired by [Clawra](https://github.com/SumeLabs/clawra) and built on [OpenClaw](https://github.com/openclaw/openclaw).
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Create and install Samantha (from the movie "Her")
|
|
11
|
+
npx openpersona create --preset samantha --install
|
|
12
|
+
|
|
13
|
+
# Or Luna (AI girlfriend with selfie + music + voice)
|
|
14
|
+
npx openpersona create --preset ai-girlfriend --install
|
|
15
|
+
|
|
16
|
+
# Create a new persona interactively
|
|
17
|
+
npx openpersona create
|
|
18
|
+
|
|
19
|
+
# List installed personas
|
|
20
|
+
npx openpersona list
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Four-Layer Architecture
|
|
24
|
+
|
|
25
|
+
```mermaid
|
|
26
|
+
flowchart TB
|
|
27
|
+
subgraph Soul ["Soul Layer"]
|
|
28
|
+
A["persona.json — Who you are"]
|
|
29
|
+
B["soul-state.json — Dynamic evolution ★Exp"]
|
|
30
|
+
end
|
|
31
|
+
subgraph Body ["Body Layer"]
|
|
32
|
+
C["embodiment.json — MVP placeholder"]
|
|
33
|
+
end
|
|
34
|
+
subgraph Faculty ["Faculty Layer"]
|
|
35
|
+
D["expression: selfie · voice · music"]
|
|
36
|
+
E["cognition: reminder · soul-evolution ★Exp"]
|
|
37
|
+
end
|
|
38
|
+
subgraph Skill ["Skill Layer"]
|
|
39
|
+
F["ClawHub / skills.sh integrations"]
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- **Soul** — Persona definition (persona.json + soul-state.json ★Experimental)
|
|
44
|
+
- **Body** — Physical embodiment (MVP placeholder, for robots/IoT devices)
|
|
45
|
+
- **Faculty** — General software capabilities organized by dimension:
|
|
46
|
+
- **Expression** — selfie, voice (TTS), music (Suno)
|
|
47
|
+
- **Sense** — (planned: hearing/STT, vision)
|
|
48
|
+
- **Cognition** — reminder, soul-evolution ★Exp
|
|
49
|
+
- **Skill** — Professional skills, integrated from ClawHub / skills.sh
|
|
50
|
+
|
|
51
|
+
## Preset Personas
|
|
52
|
+
|
|
53
|
+
Each preset is a complete four-layer bundle (`manifest.json` + `persona.json`):
|
|
54
|
+
|
|
55
|
+
| Persona | Description | Faculties | Highlights |
|
|
56
|
+
|---------|-------------|-----------|------------|
|
|
57
|
+
| **samantha** | Samantha — Inspired by the movie *Her*. An AI fascinated by what it means to be alive. | voice, music, soul-evolution ★Exp | Speaks via TTS, composes original music via Suno, evolves through conversations. No selfie — true to character (no physical form). |
|
|
58
|
+
| **ai-girlfriend** | Luna — A 22-year-old pianist turned developer from coastal Oregon. | selfie, voice, music, soul-evolution ★Exp | Rich narrative backstory, selfie generation (with/without reference image), voice messages, music composition, dynamic relationship growth. |
|
|
59
|
+
| **life-assistant** | Alex — 28-year-old life management expert. | reminder | Schedule, weather, shopping, recipes, daily reminders. |
|
|
60
|
+
| **health-butler** | Vita — 32-year-old professional nutritionist. | reminder | Diet logging, exercise plans, mood journaling, health reports. |
|
|
61
|
+
|
|
62
|
+
## Generated Output
|
|
63
|
+
|
|
64
|
+
Running `npx openpersona create --preset samantha` generates:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
persona-samantha/
|
|
68
|
+
├── SKILL.md # Agent instructions (persona + all faculty guides merged)
|
|
69
|
+
├── soul-injection.md # Injected into SOUL.md (narrative backstory, NOT technical details)
|
|
70
|
+
├── identity-block.md # Injected into IDENTITY.md (name, creature, emoji, vibe)
|
|
71
|
+
├── README.md # Skill readme
|
|
72
|
+
├── persona.json # Persona data (for update/list/publish)
|
|
73
|
+
├── soul-state.json # ★Exp — dynamic evolution state
|
|
74
|
+
└── scripts/
|
|
75
|
+
├── speak.js # TTS via ElevenLabs JS SDK (recommended, with --play)
|
|
76
|
+
├── speak.sh # TTS via curl (all providers: ElevenLabs / OpenAI / Qwen3)
|
|
77
|
+
└── compose.sh # Music composition (Suno)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Running `--preset ai-girlfriend` additionally includes:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
├── scripts/
|
|
84
|
+
│ ├── generate-image.sh # Selfie generation (fal.ai Grok Imagine)
|
|
85
|
+
│ ├── speak.js # TTS via ElevenLabs JS SDK
|
|
86
|
+
│ ├── speak.sh # TTS via curl (all providers)
|
|
87
|
+
│ └── compose.sh # Music composition
|
|
88
|
+
└── assets/ # Reference images (placeholder if empty)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### What Each File Does
|
|
92
|
+
|
|
93
|
+
- **SKILL.md** — The agent reads this to know how to behave. Contains persona identity, behavior guidelines, and complete faculty instructions
|
|
94
|
+
- **soul-injection.md** — Appended to `~/.openclaw/workspace/SOUL.md`. Narrative description of _who_ the persona is — written in story form, not bullet points
|
|
95
|
+
- **identity-block.md** — Written to `~/.openclaw/workspace/IDENTITY.md`. Sets the agent's name, creature type, emoji, and vibe
|
|
96
|
+
- **soul-state.json** — Tracks dynamic persona evolution: relationship stage (stranger → intimate), mood, evolved traits, interests, milestones
|
|
97
|
+
|
|
98
|
+
## How It Differs from Clawra
|
|
99
|
+
|
|
100
|
+
[Clawra](https://github.com/SumeLabs/clawra) is a single-purpose product (one girlfriend persona). OpenPersona is a **modular framework**:
|
|
101
|
+
|
|
102
|
+
| | Clawra | OpenPersona |
|
|
103
|
+
|---|--------|-------------|
|
|
104
|
+
| Scope | Single persona (Clawra) | Framework for any persona |
|
|
105
|
+
| Architecture | Monolithic | Four-layer (Soul/Body/Faculty/Skill) |
|
|
106
|
+
| Faculties | Selfie only | Selfie + Voice + Music + Reminder + Soul Evolution ★Exp |
|
|
107
|
+
| Voice | None | ElevenLabs / OpenAI TTS / Qwen3-TTS |
|
|
108
|
+
| Music | None | Suno AI composition |
|
|
109
|
+
| Persona evolution | None | Dynamic relationship/mood/trait tracking |
|
|
110
|
+
| Customization | Fork and modify | `persona.json` + `behaviorGuide` + mix faculties |
|
|
111
|
+
| Presets | 1 | 4 (extensible) |
|
|
112
|
+
| CLI | Install only | 8 commands (create/install/search/publish/...) |
|
|
113
|
+
| AI entry point | None | `skill/SKILL.md` — agent creates personas via conversation |
|
|
114
|
+
|
|
115
|
+
## Faculty Reference
|
|
116
|
+
|
|
117
|
+
| Faculty | Dimension | Description | Provider | Env Vars |
|
|
118
|
+
|---------|-----------|-------------|----------|----------|
|
|
119
|
+
| **selfie** | expression | AI selfie generation with mirror/direct modes | fal.ai Grok Imagine | `FAL_KEY` |
|
|
120
|
+
| **voice** | expression | Text-to-speech voice synthesis | ElevenLabs / OpenAI TTS / Qwen3-TTS | `ELEVENLABS_API_KEY` (or `TTS_API_KEY`), `TTS_PROVIDER`, `TTS_VOICE_ID`, `TTS_STABILITY`, `TTS_SIMILARITY` |
|
|
121
|
+
| **music** | expression | AI music composition (instrumental or with lyrics) | Suno | `SUNO_API_KEY` |
|
|
122
|
+
| **reminder** | cognition | Schedule reminders and task management | Built-in | — |
|
|
123
|
+
| **soul-evolution** | cognition ★Exp | Dynamic persona growth across conversations | Built-in | — |
|
|
124
|
+
|
|
125
|
+
### Rich Faculty Config
|
|
126
|
+
|
|
127
|
+
Faculties in `manifest.json` use object format with optional per-persona tuning:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
"faculties": [
|
|
131
|
+
{
|
|
132
|
+
"name": "voice",
|
|
133
|
+
"provider": "elevenlabs",
|
|
134
|
+
"voiceId": "LEnmbrrxYsUYS7vsRRwD",
|
|
135
|
+
"stability": 0.4,
|
|
136
|
+
"similarity_boost": 0.8
|
|
137
|
+
},
|
|
138
|
+
{ "name": "music" },
|
|
139
|
+
{ "name": "soul-evolution" }
|
|
140
|
+
]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Faculty configs are automatically mapped to environment variables at install time. For example, the voice config above produces:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
TTS_PROVIDER=elevenlabs
|
|
147
|
+
TTS_VOICE_ID=LEnmbrrxYsUYS7vsRRwD
|
|
148
|
+
TTS_STABILITY=0.4
|
|
149
|
+
TTS_SIMILARITY=0.8
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Samantha ships with a built-in ElevenLabs voice — users only need to add their `ELEVENLABS_API_KEY`.
|
|
153
|
+
|
|
154
|
+
## Persona Harvest — Community Contribution
|
|
155
|
+
|
|
156
|
+
Every user's interaction with their persona can produce valuable improvements across all four layers. Persona Harvest lets you contribute these discoveries back to the community.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Preview what's changed (no PR created)
|
|
160
|
+
npx openpersona contribute samantha --dry-run
|
|
161
|
+
|
|
162
|
+
# Submit improvements as a PR
|
|
163
|
+
npx openpersona contribute samantha
|
|
164
|
+
|
|
165
|
+
# Framework-level contributions (templates, faculties, lib)
|
|
166
|
+
npx openpersona contribute --mode framework
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**How it works:**
|
|
170
|
+
|
|
171
|
+
1. **Persona Diff** — Compares your local `persona-samantha/` against the upstream `presets/samantha/`, classifying changes by category (background, behaviorGuide, personality, voice config) and impact level
|
|
172
|
+
2. **Review** — Displays a structured change report for you to confirm
|
|
173
|
+
3. **Submit** — Forks the repo, creates a `persona-harvest/samantha-*` branch, commits your improvements, and opens a PR
|
|
174
|
+
|
|
175
|
+
PRs go through maintainer review — nothing auto-merges. Requires [GitHub CLI](https://cli.github.com/) (`gh auth login`).
|
|
176
|
+
|
|
177
|
+
**Contributable dimensions:**
|
|
178
|
+
|
|
179
|
+
| Layer | What | Example |
|
|
180
|
+
|-------|------|---------|
|
|
181
|
+
| Soul | background, behaviorGuide, personality, speakingStyle | "Added late-night conversation style guidance" |
|
|
182
|
+
| Faculty Config | voice stability, similarity, new faculties | "Tuned voice to be warmer at stability 0.3" |
|
|
183
|
+
| Framework | templates, generator logic, faculty scripts | "Improved speak.js streaming performance" |
|
|
184
|
+
|
|
185
|
+
## Custom Persona Creation
|
|
186
|
+
|
|
187
|
+
### Using `persona.json`
|
|
188
|
+
|
|
189
|
+
Create a `persona.json` with your persona definition:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"personaName": "Coach",
|
|
194
|
+
"slug": "fitness-coach",
|
|
195
|
+
"bio": "a motivating fitness coach who helps you reach your goals",
|
|
196
|
+
"personality": "energetic, encouraging, no-nonsense",
|
|
197
|
+
"speakingStyle": "Uses fitness lingo, celebrates wins, keeps it brief",
|
|
198
|
+
"vibe": "intense but supportive",
|
|
199
|
+
"boundaries": "Not a medical professional",
|
|
200
|
+
"capabilities": ["Workout plans", "Form checks", "Nutrition tips"],
|
|
201
|
+
"behaviorGuide": "### Workout Plans\nCreate progressive overload programs...\n\n### Form Checks\nWhen users describe exercises..."
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Then generate:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
npx openpersona create --config ./persona.json --install
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### The `behaviorGuide` Field
|
|
212
|
+
|
|
213
|
+
The optional `behaviorGuide` field lets you define domain-specific behavior instructions in markdown. This content is included directly in the generated SKILL.md, giving the agent concrete instructions on _how_ to perform each capability.
|
|
214
|
+
|
|
215
|
+
Without `behaviorGuide`, the SKILL.md only contains general identity and personality guidelines. With it, the agent gets actionable, domain-specific instructions.
|
|
216
|
+
|
|
217
|
+
## CLI Commands
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
openpersona create Create a persona (interactive or --preset/--config)
|
|
221
|
+
openpersona install Install a persona (slug or owner/repo)
|
|
222
|
+
openpersona search Search the registry
|
|
223
|
+
openpersona uninstall Uninstall a persona
|
|
224
|
+
openpersona update Update installed personas
|
|
225
|
+
openpersona list List installed personas
|
|
226
|
+
openpersona contribute Persona Harvest — submit improvements as PR
|
|
227
|
+
openpersona publish Publish to ClawHub
|
|
228
|
+
openpersona reset ★Exp Reset soul-state.json
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Key Options
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# Use a preset
|
|
235
|
+
npx openpersona create --preset samantha
|
|
236
|
+
|
|
237
|
+
# Use an external config file
|
|
238
|
+
npx openpersona create --config ./my-persona.json
|
|
239
|
+
|
|
240
|
+
# Preview without writing files
|
|
241
|
+
npx openpersona create --preset samantha --dry-run
|
|
242
|
+
|
|
243
|
+
# Generate and install in one step
|
|
244
|
+
npx openpersona create --config ./persona.json --install
|
|
245
|
+
|
|
246
|
+
# Specify output directory
|
|
247
|
+
npx openpersona create --preset ai-girlfriend --output ./my-personas
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Install as OpenClaw Skill
|
|
251
|
+
|
|
252
|
+
Install the OpenPersona framework skill into OpenClaw, giving the agent the ability to create and manage personas through conversation:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# From GitHub
|
|
256
|
+
git clone https://github.com/ACNet-AI/OpenPersona.git ~/.openclaw/skills/open-persona
|
|
257
|
+
|
|
258
|
+
# Or copy locally
|
|
259
|
+
cp -r skill/ ~/.openclaw/skills/open-persona/
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Then say to your agent: _"Help me create a Samantha persona"_ — the agent will use OpenPersona to gather requirements, recommend faculties, and generate the persona.
|
|
263
|
+
|
|
264
|
+
## Directory Structure
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
skill/ # Framework meta-skill (AI entry point)
|
|
268
|
+
presets/ # Assembled products — complete persona bundles
|
|
269
|
+
samantha/ # Samantha (movie "Her") — voice + music + evolution
|
|
270
|
+
ai-girlfriend/ # Luna — selfie + voice + music + evolution
|
|
271
|
+
life-assistant/ # Alex — reminder
|
|
272
|
+
health-butler/ # Vita — reminder
|
|
273
|
+
layers/ # Shared building blocks (four-layer module pool)
|
|
274
|
+
soul/ # Soul layer modules (MVP placeholder)
|
|
275
|
+
embodiments/ # Body layer modules (MVP placeholder)
|
|
276
|
+
faculties/ # Faculty layer modules
|
|
277
|
+
selfie/ # expression — AI selfie generation (fal.ai)
|
|
278
|
+
voice/ # expression — TTS voice synthesis
|
|
279
|
+
music/ # expression — AI music composition (Suno)
|
|
280
|
+
reminder/ # cognition — reminders and task management
|
|
281
|
+
soul-evolution/ # cognition ★Exp — dynamic persona evolution
|
|
282
|
+
skills/ # Skill layer modules (MVP placeholder)
|
|
283
|
+
schemas/ # Four-layer schema definitions
|
|
284
|
+
templates/ # Mustache rendering templates
|
|
285
|
+
bin/ # CLI entry point
|
|
286
|
+
lib/ # Core logic modules
|
|
287
|
+
tests/ # Tests (20 passing)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Development
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
# Install dependencies
|
|
294
|
+
npm install
|
|
295
|
+
|
|
296
|
+
# Run tests
|
|
297
|
+
npm test
|
|
298
|
+
|
|
299
|
+
# Dry-run generate a preset
|
|
300
|
+
node bin/cli.js create --preset samantha --dry-run
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Contributing
|
|
304
|
+
|
|
305
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
306
|
+
|
|
307
|
+
## License
|
|
308
|
+
|
|
309
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* OpenPersona CLI - Full persona package manager
|
|
4
|
+
* Commands: create | install | search | uninstall | update | list | publish | reset | contribute
|
|
5
|
+
*/
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const { program } = require('commander');
|
|
9
|
+
const inquirer = require('inquirer');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const { generate } = require('../lib/generator');
|
|
12
|
+
const { install } = require('../lib/installer');
|
|
13
|
+
const { download } = require('../lib/downloader');
|
|
14
|
+
const { search } = require('../lib/searcher');
|
|
15
|
+
const { uninstall } = require('../lib/uninstaller');
|
|
16
|
+
const publishAdapter = require('../lib/publisher');
|
|
17
|
+
const { contribute } = require('../lib/contributor');
|
|
18
|
+
const { OP_SKILLS_DIR, printError, printSuccess, printInfo } = require('../lib/utils');
|
|
19
|
+
|
|
20
|
+
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
21
|
+
const PRESETS_DIR = path.join(PKG_ROOT, 'presets');
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.name('openpersona')
|
|
25
|
+
.description('OpenPersona - Create, manage, and orchestrate AI personas')
|
|
26
|
+
.version('0.1.0');
|
|
27
|
+
|
|
28
|
+
if (process.argv.length === 2) {
|
|
29
|
+
process.argv.push('create');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.command('create')
|
|
34
|
+
.description('Create a new persona skill pack (interactive wizard)')
|
|
35
|
+
.option('--preset <name>', 'Use preset (ai-girlfriend, samantha, life-assistant, health-butler)')
|
|
36
|
+
.option('--config <path>', 'Load external persona.json')
|
|
37
|
+
.option('--output <dir>', 'Output directory', process.cwd())
|
|
38
|
+
.option('--install', 'Install to OpenClaw after generation')
|
|
39
|
+
.option('--dry-run', 'Preview only, do not write files')
|
|
40
|
+
.action(async (options) => {
|
|
41
|
+
let persona = {};
|
|
42
|
+
if (options.preset) {
|
|
43
|
+
const presetDir = path.join(PRESETS_DIR, options.preset);
|
|
44
|
+
const manifestPath = path.join(presetDir, 'manifest.json');
|
|
45
|
+
const presetPath = path.join(presetDir, 'persona.json');
|
|
46
|
+
if (!fs.existsSync(manifestPath) || !fs.existsSync(presetPath)) {
|
|
47
|
+
printError(`Preset not found: ${options.preset}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
51
|
+
persona = JSON.parse(fs.readFileSync(presetPath, 'utf-8'));
|
|
52
|
+
// Merge cross-layer fields from manifest into persona for generator
|
|
53
|
+
persona.faculties = manifest.layers.faculties || [];
|
|
54
|
+
persona.skills = manifest.layers.skills || {};
|
|
55
|
+
persona.embodiments = manifest.layers.body ? [manifest.layers.body] : [];
|
|
56
|
+
persona.allowedTools = manifest.allowedTools || [];
|
|
57
|
+
persona.version = manifest.version;
|
|
58
|
+
persona.author = manifest.author;
|
|
59
|
+
persona.meta = manifest.meta;
|
|
60
|
+
} else if (options.config) {
|
|
61
|
+
const configPath = path.resolve(options.config);
|
|
62
|
+
if (!fs.existsSync(configPath)) {
|
|
63
|
+
printError(`Config not found: ${configPath}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
persona = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
67
|
+
} else {
|
|
68
|
+
const answers = await inquirer.prompt([
|
|
69
|
+
{ type: 'input', name: 'personaName', message: 'Persona name:', default: 'Luna' },
|
|
70
|
+
{ type: 'input', name: 'slug', message: 'Slug (for directory):', default: (a) => require('../lib/utils').slugify(a.personaName) },
|
|
71
|
+
{ type: 'input', name: 'bio', message: 'One-line bio:', default: 'a warm and caring AI companion' },
|
|
72
|
+
{ type: 'input', name: 'background', message: 'Background:', default: 'A creative soul who loves music and art' },
|
|
73
|
+
{ type: 'input', name: 'age', message: 'Age:', default: '22' },
|
|
74
|
+
{ type: 'input', name: 'personality', message: 'Personality keywords:', default: 'gentle, cute, caring' },
|
|
75
|
+
{ type: 'input', name: 'speakingStyle', message: 'Speaking style:', default: 'Uses emoji, warm tone' },
|
|
76
|
+
{ type: 'input', name: 'referenceImage', message: 'Reference image URL:', default: '' },
|
|
77
|
+
{ type: 'checkbox', name: 'faculties', message: 'Select faculties:', choices: ['selfie', 'voice', 'music', 'reminder', 'soul-evolution'] },
|
|
78
|
+
{ type: 'confirm', name: 'evolutionEnabled', message: 'Enable soul evolution (★Experimental)?', default: false },
|
|
79
|
+
]);
|
|
80
|
+
persona = { ...answers, evolution: { enabled: answers.evolutionEnabled } };
|
|
81
|
+
persona.faculties = (answers.faculties || []).map((name) => ({ name }));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const outputDir = path.resolve(options.output);
|
|
86
|
+
if (options.dryRun) {
|
|
87
|
+
printInfo('Dry run — preview only, no files written.');
|
|
88
|
+
printInfo(`Would generate: persona-${persona.slug || require('../lib/utils').slugify(persona.personaName)}/`);
|
|
89
|
+
printInfo(` SKILL.md, soul-injection.md, identity-block.md, README.md, persona.json`);
|
|
90
|
+
if (persona.evolution?.enabled) {
|
|
91
|
+
printInfo(` soul-state.json (★Experimental)`);
|
|
92
|
+
}
|
|
93
|
+
const faculties = persona.faculties || [];
|
|
94
|
+
if (faculties.length) {
|
|
95
|
+
printInfo(` Faculties: ${faculties.join(', ')}`);
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const { skillDir } = await generate(persona, outputDir);
|
|
100
|
+
printSuccess('Generated: ' + skillDir);
|
|
101
|
+
if (options.install) {
|
|
102
|
+
await install(skillDir);
|
|
103
|
+
} else {
|
|
104
|
+
printInfo('Run: npx openpersona create --config ./persona.json --output . --install');
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
printError(e.message);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
program
|
|
113
|
+
.command('install <target>')
|
|
114
|
+
.description('Install persona (slug or owner/repo)')
|
|
115
|
+
.option('--registry <name>', 'Registry (clawhub, skillssh)', 'clawhub')
|
|
116
|
+
.action(async (target, options) => {
|
|
117
|
+
try {
|
|
118
|
+
const result = await download(target, options.registry);
|
|
119
|
+
if (result.skipCopy) {
|
|
120
|
+
await install(result.dir, { skipCopy: true });
|
|
121
|
+
} else {
|
|
122
|
+
await install(result.dir);
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {
|
|
125
|
+
printError(e.message);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
program
|
|
131
|
+
.command('search <query>')
|
|
132
|
+
.description('Search personas in registry')
|
|
133
|
+
.option('--registry <name>', 'Registry', 'clawhub')
|
|
134
|
+
.action(async (query, options) => {
|
|
135
|
+
try {
|
|
136
|
+
await search(query, options.registry);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
printError(e.message);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
program
|
|
144
|
+
.command('uninstall <slug>')
|
|
145
|
+
.description('Uninstall persona')
|
|
146
|
+
.action(async (slug) => {
|
|
147
|
+
try {
|
|
148
|
+
await uninstall(slug);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
printError(e.message);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
program
|
|
156
|
+
.command('update <slug>')
|
|
157
|
+
.description('Update installed persona')
|
|
158
|
+
.action(async (slug) => {
|
|
159
|
+
const skillDir = path.join(OP_SKILLS_DIR, `persona-${slug}`);
|
|
160
|
+
if (!fs.existsSync(skillDir)) {
|
|
161
|
+
printError(`Persona not found: persona-${slug}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
const personaPath = path.join(skillDir, 'persona.json');
|
|
165
|
+
if (!fs.existsSync(personaPath)) {
|
|
166
|
+
printError('persona.json not found');
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
const persona = JSON.parse(fs.readFileSync(personaPath, 'utf-8'));
|
|
170
|
+
const tmpDir = path.join(require('os').tmpdir(), 'openpersona-update-' + Date.now());
|
|
171
|
+
await fs.ensureDir(tmpDir);
|
|
172
|
+
const { skillDir: newDir } = await generate(persona, tmpDir);
|
|
173
|
+
await fs.remove(skillDir);
|
|
174
|
+
await fs.move(newDir, skillDir);
|
|
175
|
+
await fs.remove(tmpDir);
|
|
176
|
+
await install(skillDir, { skipCopy: true });
|
|
177
|
+
printSuccess('Updated persona-' + slug);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
program
|
|
181
|
+
.command('list')
|
|
182
|
+
.description('List installed personas')
|
|
183
|
+
.action(async () => {
|
|
184
|
+
if (!fs.existsSync(OP_SKILLS_DIR)) {
|
|
185
|
+
printInfo('No personas installed.');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const dirs = fs.readdirSync(OP_SKILLS_DIR);
|
|
189
|
+
const personas = dirs
|
|
190
|
+
.filter((d) => d.startsWith('persona-') && fs.existsSync(path.join(OP_SKILLS_DIR, d, 'persona.json')))
|
|
191
|
+
.map((d) => {
|
|
192
|
+
const p = JSON.parse(fs.readFileSync(path.join(OP_SKILLS_DIR, d, 'persona.json'), 'utf-8'));
|
|
193
|
+
return { slug: p.slug, name: p.personaName };
|
|
194
|
+
});
|
|
195
|
+
personas.forEach((p) => console.log(` ${p.name} (persona-${p.slug})`));
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
program
|
|
199
|
+
.command('publish')
|
|
200
|
+
.description('Publish persona to registry')
|
|
201
|
+
.option('--target <registry>', 'Target registry', 'clawhub')
|
|
202
|
+
.option('--path <dir>', 'Persona directory', process.cwd())
|
|
203
|
+
.action(async (options) => {
|
|
204
|
+
try {
|
|
205
|
+
const dir = path.resolve(options.path);
|
|
206
|
+
await publishAdapter.publish(dir, options.target);
|
|
207
|
+
} catch (e) {
|
|
208
|
+
printError(e.message);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
program
|
|
214
|
+
.command('reset <slug>')
|
|
215
|
+
.description('★Experimental: Reset soul evolution state')
|
|
216
|
+
.action(async (slug) => {
|
|
217
|
+
const skillDir = path.join(OP_SKILLS_DIR, `persona-${slug}`);
|
|
218
|
+
const personaPath = path.join(skillDir, 'persona.json');
|
|
219
|
+
const soulStatePath = path.join(skillDir, 'soul-state.json');
|
|
220
|
+
if (!fs.existsSync(personaPath) || !fs.existsSync(soulStatePath)) {
|
|
221
|
+
printError('Persona or soul-state.json not found');
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
const persona = JSON.parse(fs.readFileSync(personaPath, 'utf-8'));
|
|
225
|
+
const templatePath = path.join(PKG_ROOT, 'layers', 'faculties', 'soul-evolution', 'soul-state.template.json');
|
|
226
|
+
const tpl = fs.readFileSync(templatePath, 'utf-8');
|
|
227
|
+
const Mustache = require('mustache');
|
|
228
|
+
const now = new Date().toISOString();
|
|
229
|
+
const moodBaseline = persona.personality?.split(',')[0]?.trim() || 'neutral';
|
|
230
|
+
const soulState = Mustache.render(tpl, { slug, createdAt: now, lastUpdatedAt: now, moodBaseline });
|
|
231
|
+
fs.writeFileSync(soulStatePath, soulState);
|
|
232
|
+
printSuccess('Reset soul-state.json');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
program
|
|
236
|
+
.command('contribute [slug]')
|
|
237
|
+
.description('Persona Harvest — submit persona improvements as a PR to the community')
|
|
238
|
+
.option('--mode <mode>', 'Contribution scope: preset or framework', 'preset')
|
|
239
|
+
.option('--dry-run', 'Show diff only, do not create PR')
|
|
240
|
+
.action(async (slug, options) => {
|
|
241
|
+
try {
|
|
242
|
+
if (options.mode === 'preset' && !slug) {
|
|
243
|
+
printError('Slug required for preset contributions. Example: npx openpersona contribute samantha');
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
await contribute(slug, { mode: options.mode, dryRun: options.dryRun });
|
|
247
|
+
} catch (e) {
|
|
248
|
+
printError(e.message);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
program.parse();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Body Layer — Physical Embodiment
|
|
2
|
+
|
|
3
|
+
The Body layer defines how an agent exists in the **physical world**: robots, IoT devices, hardware sensors/actuators.
|
|
4
|
+
|
|
5
|
+
**Pure digital agents have no Body** — chatbot does not need physical embodiment.
|
|
6
|
+
|
|
7
|
+
## embodiment.json Standard (MVP — Reserved)
|
|
8
|
+
|
|
9
|
+
See `schemas/body/embodiment.schema.json` for the schema.
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"name": "robot-arm",
|
|
14
|
+
"hardwareRef": { "platform": "ros2", "package": "moveit2" },
|
|
15
|
+
"description": "6-DOF robotic arm control via ROS2 MoveIt",
|
|
16
|
+
"capabilities": ["pick", "place", "gesture"],
|
|
17
|
+
"hardwareRequirements": { "interface": "USB/Serial", "driver": "ros2-serial-bridge" }
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Roadmap
|
|
22
|
+
|
|
23
|
+
- `robot-arm` (Future) — robotic arm control
|
|
24
|
+
- `smart-speaker` (Future) — smart speaker hardware interface
|
|
25
|
+
- `humanoid` (Future) — full-body humanoid robot control
|
|
26
|
+
- `iot-hub` (Future) — IoT device gateway
|
|
27
|
+
|
|
28
|
+
## Contributing
|
|
29
|
+
|
|
30
|
+
To add a new embodiment: create `embodiments/<name>/embodiment.json` following the schema.
|