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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +309 -0
  3. package/bin/cli.js +253 -0
  4. package/layers/embodiments/README.md +30 -0
  5. package/layers/faculties/music/SKILL.md +157 -0
  6. package/layers/faculties/music/faculty.json +9 -0
  7. package/layers/faculties/music/scripts/compose.sh +169 -0
  8. package/layers/faculties/reminder/SKILL.md +17 -0
  9. package/layers/faculties/reminder/faculty.json +9 -0
  10. package/layers/faculties/selfie/SKILL.md +151 -0
  11. package/layers/faculties/selfie/faculty.json +9 -0
  12. package/layers/faculties/selfie/scripts/generate-image.sh +202 -0
  13. package/layers/faculties/soul-evolution/SKILL.md +41 -0
  14. package/layers/faculties/soul-evolution/faculty.json +9 -0
  15. package/layers/faculties/soul-evolution/soul-state.template.json +1 -0
  16. package/layers/faculties/voice/SKILL.md +189 -0
  17. package/layers/faculties/voice/faculty.json +9 -0
  18. package/layers/faculties/voice/scripts/speak.js +169 -0
  19. package/layers/faculties/voice/scripts/speak.sh +171 -0
  20. package/layers/skills/README.md +33 -0
  21. package/layers/soul/README.md +18 -0
  22. package/lib/contributor.js +392 -0
  23. package/lib/downloader.js +106 -0
  24. package/lib/generator.js +301 -0
  25. package/lib/installer.js +227 -0
  26. package/lib/publisher/clawhub.js +42 -0
  27. package/lib/publisher/index.js +14 -0
  28. package/lib/searcher.js +24 -0
  29. package/lib/uninstaller.js +82 -0
  30. package/lib/utils.js +56 -0
  31. package/package.json +51 -0
  32. package/presets/ai-girlfriend/manifest.json +24 -0
  33. package/presets/ai-girlfriend/persona.json +25 -0
  34. package/presets/health-butler/manifest.json +21 -0
  35. package/presets/health-butler/persona.json +20 -0
  36. package/presets/life-assistant/manifest.json +21 -0
  37. package/presets/life-assistant/persona.json +20 -0
  38. package/presets/samantha/manifest.json +29 -0
  39. package/presets/samantha/persona.json +25 -0
  40. package/schemas/body/embodiment.schema.json +27 -0
  41. package/schemas/faculty/faculty.schema.json +25 -0
  42. package/schemas/manifest.schema.json +52 -0
  43. package/schemas/skill/skill-declaration.spec.md +15 -0
  44. package/schemas/soul/persona.schema.json +37 -0
  45. package/schemas/soul/soul-state.schema.json +30 -0
  46. package/skill/SKILL.md +170 -0
  47. package/templates/identity.template.md +9 -0
  48. package/templates/readme.template.md +23 -0
  49. package/templates/skill.template.md +20 -0
  50. 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.