openpersona 0.14.0 → 0.14.2
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/bin/cli.js +25 -12
- package/lib/downloader.js +3 -1
- package/lib/generator.js +7 -2
- package/lib/installer.js +101 -152
- package/lib/switcher.js +2 -2
- package/lib/uninstaller.js +23 -12
- package/lib/utils.js +13 -4
- package/package.json +1 -1
- package/presets/ai-girlfriend/manifest.json +1 -1
- package/presets/base/manifest.json +1 -1
- package/presets/health-butler/manifest.json +1 -1
- package/presets/life-assistant/manifest.json +1 -1
- package/presets/samantha/manifest.json +1 -1
- package/presets/stoic-mentor/manifest.json +1 -1
- package/skills/open-persona/SKILL.md +1 -1
package/bin/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ const publishAdapter = require('../lib/publisher');
|
|
|
17
17
|
const { contribute } = require('../lib/contributor');
|
|
18
18
|
const { switchPersona, listPersonas } = require('../lib/switcher');
|
|
19
19
|
const { registerWithAcn } = require('../lib/registrar');
|
|
20
|
-
const { OP_SKILLS_DIR, resolveSoulFile, printError, printSuccess, printInfo, loadRegistry } = require('../lib/utils');
|
|
20
|
+
const { OP_SKILLS_DIR, OPENCLAW_HOME, resolveSoulFile, printError, printSuccess, printInfo, loadRegistry } = require('../lib/utils');
|
|
21
21
|
|
|
22
22
|
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
23
23
|
const PRESETS_DIR = path.join(PKG_ROOT, 'presets');
|
|
@@ -25,7 +25,7 @@ const PRESETS_DIR = path.join(PKG_ROOT, 'presets');
|
|
|
25
25
|
program
|
|
26
26
|
.name('openpersona')
|
|
27
27
|
.description('OpenPersona - Create, manage, and orchestrate agent personas')
|
|
28
|
-
.version('0.14.
|
|
28
|
+
.version('0.14.2');
|
|
29
29
|
|
|
30
30
|
if (process.argv.length === 2) {
|
|
31
31
|
process.argv.push('create');
|
|
@@ -184,9 +184,9 @@ program
|
|
|
184
184
|
.command('update <slug>')
|
|
185
185
|
.description('Update installed persona')
|
|
186
186
|
.action(async (slug) => {
|
|
187
|
-
const skillDir =
|
|
188
|
-
if (!
|
|
189
|
-
printError(`Persona not found:
|
|
187
|
+
const skillDir = resolvePersonaDir(slug);
|
|
188
|
+
if (!skillDir) {
|
|
189
|
+
printError(`Persona not found: "${slug}". Install it first with: openpersona install <source>`);
|
|
190
190
|
process.exit(1);
|
|
191
191
|
}
|
|
192
192
|
const personaPath = resolveSoulFile(skillDir, 'persona.json');
|
|
@@ -228,10 +228,14 @@ program
|
|
|
228
228
|
.option('--install', 'Install to OpenClaw after generation')
|
|
229
229
|
.action(async (parentSlug, options) => {
|
|
230
230
|
const { createHash } = require('crypto');
|
|
231
|
-
const parentDir =
|
|
231
|
+
const parentDir = resolvePersonaDir(parentSlug);
|
|
232
|
+
if (!parentDir) {
|
|
233
|
+
printError(`Persona not found: "${parentSlug}". Install it first with: openpersona install <source>`);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
232
236
|
const parentPersonaPath = path.join(parentDir, 'soul', 'persona.json');
|
|
233
237
|
if (!fs.existsSync(parentPersonaPath)) {
|
|
234
|
-
printError(`Persona not found:
|
|
238
|
+
printError(`Persona not found: "${parentSlug}". Install it first with: openpersona install <source>`);
|
|
235
239
|
process.exit(1);
|
|
236
240
|
}
|
|
237
241
|
|
|
@@ -348,7 +352,11 @@ program
|
|
|
348
352
|
.command('reset <slug>')
|
|
349
353
|
.description('★Experimental: Reset soul evolution state')
|
|
350
354
|
.action(async (slug) => {
|
|
351
|
-
const skillDir =
|
|
355
|
+
const skillDir = resolvePersonaDir(slug);
|
|
356
|
+
if (!skillDir) {
|
|
357
|
+
printError(`Persona not found: "${slug}". Install it first with: openpersona install <source>`);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
352
360
|
const personaPath = resolveSoulFile(skillDir, 'persona.json');
|
|
353
361
|
const soulStatePath = resolveSoulFile(skillDir, 'state.json');
|
|
354
362
|
if (!fs.existsSync(personaPath) || !fs.existsSync(soulStatePath)) {
|
|
@@ -409,7 +417,7 @@ program
|
|
|
409
417
|
if (options.dir) {
|
|
410
418
|
skillDir = path.resolve(options.dir);
|
|
411
419
|
} else if (slug) {
|
|
412
|
-
skillDir =
|
|
420
|
+
skillDir = resolvePersonaDir(slug);
|
|
413
421
|
} else {
|
|
414
422
|
// Try current directory
|
|
415
423
|
skillDir = process.cwd();
|
|
@@ -446,9 +454,9 @@ program
|
|
|
446
454
|
.description('Export persona pack (with soul state) as a zip archive')
|
|
447
455
|
.option('-o, --output <path>', 'Output file path')
|
|
448
456
|
.action(async (slug, options) => {
|
|
449
|
-
const skillDir =
|
|
450
|
-
if (!
|
|
451
|
-
printError(`Persona not found:
|
|
457
|
+
const skillDir = resolvePersonaDir(slug);
|
|
458
|
+
if (!skillDir) {
|
|
459
|
+
printError(`Persona not found: "${slug}". Install it first with: openpersona install <source>`);
|
|
452
460
|
process.exit(1);
|
|
453
461
|
}
|
|
454
462
|
const AdmZip = require('adm-zip');
|
|
@@ -511,11 +519,16 @@ program
|
|
|
511
519
|
// Delegates to scripts/state-sync.js inside the persona pack (no logic duplication).
|
|
512
520
|
|
|
513
521
|
function resolvePersonaDir(slug) {
|
|
522
|
+
// 1. Registry-stored path (most reliable — survives path changes)
|
|
514
523
|
const reg = loadRegistry();
|
|
515
524
|
const entry = reg.personas && reg.personas[slug];
|
|
516
525
|
if (entry && entry.path && fs.existsSync(entry.path)) return entry.path;
|
|
526
|
+
// 2. New neutral path ~/.openpersona/personas/
|
|
517
527
|
const defaultDir = path.join(OP_SKILLS_DIR, `persona-${slug}`);
|
|
518
528
|
if (fs.existsSync(defaultDir)) return defaultDir;
|
|
529
|
+
// 3. Legacy OpenClaw path ~/.openclaw/skills/ (backward compat)
|
|
530
|
+
const legacyDir = path.join(OPENCLAW_HOME, 'skills', `persona-${slug}`);
|
|
531
|
+
if (fs.existsSync(legacyDir)) return legacyDir;
|
|
519
532
|
return null;
|
|
520
533
|
}
|
|
521
534
|
|
package/lib/downloader.js
CHANGED
|
@@ -46,7 +46,9 @@ async function downloadFromRegistry(slug, registry, outDir) {
|
|
|
46
46
|
: extractDir;
|
|
47
47
|
|
|
48
48
|
const slugDir = path.join(repoRoot, slug);
|
|
49
|
-
|
|
49
|
+
const hasPersonaJson = fs.existsSync(path.join(slugDir, 'persona.json')) ||
|
|
50
|
+
fs.existsSync(path.join(slugDir, 'soul', 'persona.json'));
|
|
51
|
+
if (!fs.existsSync(slugDir) || !hasPersonaJson) {
|
|
50
52
|
throw new Error(
|
|
51
53
|
`Persona "${slug}" not found in the official registry.\n` +
|
|
52
54
|
`Browse available personas at ${REGISTRY_LISTING}`
|
package/lib/generator.js
CHANGED
|
@@ -35,9 +35,14 @@ const os = require('os');
|
|
|
35
35
|
|
|
36
36
|
const PERSONA_DIR = path.resolve(__dirname, '..');
|
|
37
37
|
const STATE_PATH = path.join(PERSONA_DIR, 'soul', 'state.json');
|
|
38
|
+
// Signals: use OPENCLAW_HOME if explicitly set or ~/.openclaw exists; else fall back to ~/.openpersona
|
|
38
39
|
const OPENCLAW_DIR = process.env.OPENCLAW_HOME || path.join(os.homedir(), '.openclaw');
|
|
39
|
-
const
|
|
40
|
-
const
|
|
40
|
+
const PERSONA_DIR_BASE = process.env.OPENPERSONA_HOME || path.join(os.homedir(), '.openpersona');
|
|
41
|
+
const FEEDBACK_DIR = (process.env.OPENCLAW_HOME || fs.existsSync(OPENCLAW_DIR))
|
|
42
|
+
? path.join(OPENCLAW_DIR, 'feedback')
|
|
43
|
+
: path.join(PERSONA_DIR_BASE, 'feedback');
|
|
44
|
+
const SIGNALS_PATH = path.join(FEEDBACK_DIR, 'signals.json');
|
|
45
|
+
const SIGNAL_RESPONSES_PATH = path.join(FEEDBACK_DIR, 'signal-responses.json');
|
|
41
46
|
|
|
42
47
|
const [, , command, ...args] = process.argv;
|
|
43
48
|
|
package/lib/installer.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenPersona - Install persona
|
|
2
|
+
* OpenPersona - Install persona (agent-agnostic; optional OpenClaw integration)
|
|
3
3
|
*/
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs-extra');
|
|
6
|
-
const {
|
|
6
|
+
const { OP_PERSONA_HOME, OPENCLAW_HOME, OP_SKILLS_DIR, OP_WORKSPACE, resolveSoulFile, registryAdd, registrySetActive, printError, printWarning, printSuccess, printInfo, syncHeartbeat, installAllExternal } = require('./utils');
|
|
7
7
|
|
|
8
8
|
const SOUL_PATH = path.join(OP_WORKSPACE, 'SOUL.md');
|
|
9
9
|
const IDENTITY_PATH = path.join(OP_WORKSPACE, 'IDENTITY.md');
|
|
10
|
-
const OPENCLAW_JSON = path.join(
|
|
10
|
+
const OPENCLAW_JSON = path.join(OPENCLAW_HOME, 'openclaw.json');
|
|
11
11
|
|
|
12
12
|
async function install(skillDir, options = {}) {
|
|
13
13
|
const { skipCopy = false } = options;
|
|
@@ -19,13 +19,9 @@ async function install(skillDir, options = {}) {
|
|
|
19
19
|
const slug = persona.slug;
|
|
20
20
|
const personaName = persona.personaName;
|
|
21
21
|
|
|
22
|
-
//
|
|
23
|
-
if (!fs.existsSync(OP_HOME)) {
|
|
24
|
-
printError(`OpenClaw not found at ${OP_HOME}. Run 'openclaw init' first.`);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
22
|
+
// Ensure neutral install directory exists (no OpenClaw required)
|
|
28
23
|
await fs.ensureDir(OP_SKILLS_DIR);
|
|
24
|
+
const openClawPresent = fs.existsSync(OPENCLAW_HOME);
|
|
29
25
|
|
|
30
26
|
const destDir = skipCopy ? skillDir : path.join(OP_SKILLS_DIR, `persona-${slug}`);
|
|
31
27
|
if (!skipCopy) {
|
|
@@ -47,92 +43,80 @@ async function install(skillDir, options = {}) {
|
|
|
47
43
|
printSuccess(`Using ClawHub-installed persona-${slug}`);
|
|
48
44
|
}
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
const manifestPath = path.join(destDir, 'manifest.json');
|
|
47
|
+
const defaultEnv = persona.defaults?.env || {};
|
|
51
48
|
let config = {};
|
|
52
|
-
if (fs.existsSync(OPENCLAW_JSON)) {
|
|
53
|
-
config = JSON.parse(fs.readFileSync(OPENCLAW_JSON, 'utf-8'));
|
|
54
|
-
}
|
|
55
|
-
config.skills = config.skills || {};
|
|
56
|
-
config.skills.load = config.skills.load || {};
|
|
57
|
-
const extraDirs = config.skills.load.extraDirs || [];
|
|
58
|
-
const skillsDirNorm = OP_SKILLS_DIR.replace(/\/$/, '');
|
|
59
|
-
if (!extraDirs.some((d) => d.replace(/\/$/, '') === skillsDirNorm)) {
|
|
60
|
-
extraDirs.push(OP_SKILLS_DIR);
|
|
61
|
-
config.skills.load.extraDirs = extraDirs;
|
|
62
|
-
}
|
|
63
|
-
config.skills.entries = config.skills.entries || {};
|
|
64
|
-
const entry = config.skills.entries[`persona-${slug}`] || { enabled: true };
|
|
65
|
-
entry.enabled = true;
|
|
66
49
|
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
50
|
+
// --- Optional OpenClaw integration ---
|
|
51
|
+
if (openClawPresent) {
|
|
52
|
+
if (fs.existsSync(OPENCLAW_JSON)) {
|
|
53
|
+
config = JSON.parse(fs.readFileSync(OPENCLAW_JSON, 'utf-8'));
|
|
54
|
+
}
|
|
55
|
+
config.skills = config.skills || {};
|
|
56
|
+
config.skills.load = config.skills.load || {};
|
|
57
|
+
const extraDirs = config.skills.load.extraDirs || [];
|
|
58
|
+
const skillsDirNorm = OP_SKILLS_DIR.replace(/\/$/, '');
|
|
59
|
+
if (!extraDirs.some((d) => d.replace(/\/$/, '') === skillsDirNorm)) {
|
|
60
|
+
extraDirs.push(OP_SKILLS_DIR);
|
|
61
|
+
config.skills.load.extraDirs = extraDirs;
|
|
62
|
+
}
|
|
63
|
+
config.skills.entries = config.skills.entries || {};
|
|
64
|
+
const entry = config.skills.entries[`persona-${slug}`] || { enabled: true };
|
|
65
|
+
entry.enabled = true;
|
|
66
|
+
if (Object.keys(defaultEnv).length > 0) {
|
|
67
|
+
entry.env = entry.env || {};
|
|
68
|
+
for (const [key, value] of Object.entries(defaultEnv)) {
|
|
69
|
+
if (!entry.env[key]) entry.env[key] = value;
|
|
74
70
|
}
|
|
75
71
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
for (const [key, val] of Object.entries(config.skills.entries)) {
|
|
82
|
-
if (key.startsWith('persona-') && typeof val === 'object') {
|
|
83
|
-
val.active = (key === `persona-${slug}`);
|
|
72
|
+
config.skills.entries[`persona-${slug}`] = entry;
|
|
73
|
+
for (const [key, val] of Object.entries(config.skills.entries)) {
|
|
74
|
+
if (key.startsWith('persona-') && typeof val === 'object') {
|
|
75
|
+
val.active = (key === `persona-${slug}`);
|
|
76
|
+
}
|
|
84
77
|
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Sync heartbeat from persona's manifest into global config
|
|
88
|
-
const manifestPath = path.join(destDir, 'manifest.json');
|
|
89
|
-
const { synced, heartbeat } = syncHeartbeat(config, manifestPath);
|
|
90
|
-
if (synced) {
|
|
91
|
-
printSuccess(`Heartbeat synced: strategy=${heartbeat.strategy}, maxDaily=${heartbeat.maxDaily}`);
|
|
92
|
-
} else {
|
|
93
|
-
printInfo('Heartbeat disabled (persona has no heartbeat config)');
|
|
94
|
-
}
|
|
95
78
|
|
|
96
|
-
|
|
79
|
+
// Sync heartbeat
|
|
80
|
+
const { synced, heartbeat } = syncHeartbeat(config, manifestPath);
|
|
81
|
+
if (synced) {
|
|
82
|
+
printSuccess(`Heartbeat synced: strategy=${heartbeat.strategy}, maxDaily=${heartbeat.maxDaily}`);
|
|
83
|
+
}
|
|
97
84
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
soulMd =
|
|
85
|
+
await fs.writeFile(OPENCLAW_JSON, JSON.stringify(config, null, 2));
|
|
86
|
+
|
|
87
|
+
// Inject into SOUL.md
|
|
88
|
+
const soulInjectionPath = resolveSoulFile(destDir, 'injection.md');
|
|
89
|
+
const soulContent = fs.existsSync(soulInjectionPath)
|
|
90
|
+
? fs.readFileSync(soulInjectionPath, 'utf-8') : '';
|
|
91
|
+
if (soulContent) {
|
|
92
|
+
let soulMd = fs.existsSync(SOUL_PATH) ? fs.readFileSync(SOUL_PATH, 'utf-8') : '';
|
|
93
|
+
soulMd = soulMd.replace(/<!-- OPENPERSONA_SOUL_START -->[\s\S]*?<!-- OPENPERSONA_SOUL_END -->/g, '').trim();
|
|
94
|
+
soulMd = soulMd + '\n\n' + soulContent;
|
|
95
|
+
await fs.ensureDir(path.dirname(SOUL_PATH));
|
|
96
|
+
await fs.writeFile(SOUL_PATH, soulMd);
|
|
97
|
+
printSuccess('Injected into SOUL.md');
|
|
107
98
|
}
|
|
108
|
-
// Remove any existing OpenPersona soul block (generic or legacy per-persona markers)
|
|
109
|
-
soulMd = soulMd.replace(/<!-- OPENPERSONA_SOUL_START -->[\s\S]*?<!-- OPENPERSONA_SOUL_END -->/g, '').trim();
|
|
110
|
-
soulMd = soulMd + '\n\n' + soulContent;
|
|
111
|
-
await fs.ensureDir(path.dirname(SOUL_PATH));
|
|
112
|
-
await fs.writeFile(SOUL_PATH, soulMd);
|
|
113
|
-
printSuccess('Injected into SOUL.md');
|
|
114
|
-
}
|
|
115
99
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
100
|
+
// Update IDENTITY.md
|
|
101
|
+
const identityBlockPath = resolveSoulFile(destDir, 'identity.md');
|
|
102
|
+
const identityContent = fs.existsSync(identityBlockPath)
|
|
103
|
+
? fs.readFileSync(identityBlockPath, 'utf-8') : '';
|
|
104
|
+
if (identityContent) {
|
|
105
|
+
let identityMd = '';
|
|
106
|
+
if (fs.existsSync(IDENTITY_PATH)) {
|
|
107
|
+
identityMd = fs.readFileSync(IDENTITY_PATH, 'utf-8');
|
|
108
|
+
identityMd = identityMd.replace(/<!-- OPENPERSONA_IDENTITY_START -->[\s\S]*?<!-- OPENPERSONA_IDENTITY_END -->/g, '').trim();
|
|
109
|
+
} else {
|
|
110
|
+
identityMd = '# IDENTITY.md - Who Am I?\n\n';
|
|
111
|
+
await fs.ensureDir(path.dirname(IDENTITY_PATH));
|
|
112
|
+
}
|
|
113
|
+
identityMd = identityMd + '\n\n' + identityContent;
|
|
114
|
+
await fs.writeFile(IDENTITY_PATH, identityMd);
|
|
115
|
+
printSuccess('Updated IDENTITY.md');
|
|
129
116
|
}
|
|
130
|
-
identityMd = identityMd + '\n\n' + identityContent;
|
|
131
|
-
await fs.writeFile(IDENTITY_PATH, identityMd);
|
|
132
|
-
printSuccess('Updated IDENTITY.md');
|
|
133
117
|
}
|
|
134
118
|
|
|
135
|
-
// Install external dependencies across all four layers
|
|
119
|
+
// Install external dependencies across all four layers
|
|
136
120
|
if (fs.existsSync(manifestPath)) {
|
|
137
121
|
try {
|
|
138
122
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
@@ -142,62 +126,31 @@ async function install(skillDir, options = {}) {
|
|
|
142
126
|
}
|
|
143
127
|
}
|
|
144
128
|
|
|
145
|
-
// Post-install guidance
|
|
129
|
+
// Post-install faculty guidance
|
|
146
130
|
const rawFaculties = persona.faculties || [];
|
|
147
131
|
const faculties = rawFaculties.map((f) => f.name);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
printInfo('');
|
|
165
|
-
printInfo('
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (faculties.includes('voice')) {
|
|
171
|
-
const envEntries = config.skills.entries[`persona-${slug}`] || {};
|
|
172
|
-
const hasKey = envEntries.env?.TTS_API_KEY || envEntries.env?.ELEVENLABS_API_KEY;
|
|
173
|
-
if (!hasKey) {
|
|
174
|
-
printInfo('');
|
|
175
|
-
printInfo('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
176
|
-
printInfo(` ${personaName} has voice capabilities!`);
|
|
177
|
-
printInfo(' To enable voice synthesis, configure your TTS API key:');
|
|
178
|
-
printInfo('');
|
|
179
|
-
if (defaultEnv.TTS_PROVIDER === 'elevenlabs') {
|
|
180
|
-
printInfo(' Provider: ElevenLabs (pre-configured)');
|
|
181
|
-
if (defaultEnv.TTS_VOICE_ID) {
|
|
182
|
-
printInfo(` Voice ID: ${defaultEnv.TTS_VOICE_ID} (built-in)`);
|
|
183
|
-
}
|
|
184
|
-
printInfo('');
|
|
185
|
-
printInfo(' 1. Get an API key: https://elevenlabs.io');
|
|
186
|
-
printInfo(' 2. Add to OpenClaw config or environment:');
|
|
187
|
-
printInfo('');
|
|
188
|
-
printInfo(` Edit ~/.openclaw/openclaw.json → skills.entries.persona-${slug}.env:`);
|
|
189
|
-
printInfo(' "ELEVENLABS_API_KEY": "your_key_here"');
|
|
190
|
-
} else {
|
|
191
|
-
printInfo(' 1. Get an API key from your TTS provider');
|
|
192
|
-
printInfo(' 2. Add to your OpenClaw config:');
|
|
193
|
-
printInfo('');
|
|
194
|
-
printInfo(` Edit ~/.openclaw/openclaw.json → skills.entries.persona-${slug}.env:`);
|
|
195
|
-
printInfo(' "TTS_API_KEY": "your_key_here"');
|
|
196
|
-
}
|
|
197
|
-
printInfo('');
|
|
198
|
-
printInfo(' Or: npm install @elevenlabs/elevenlabs-js (for SDK playback)');
|
|
199
|
-
printInfo('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
132
|
+
const envEntries = config.skills?.entries?.[`persona-${slug}`] || {};
|
|
133
|
+
|
|
134
|
+
if (faculties.includes('selfie') && !envEntries.env?.FAL_KEY) {
|
|
135
|
+
printInfo('');
|
|
136
|
+
printInfo('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
137
|
+
printInfo(` ${personaName} has selfie capabilities!`);
|
|
138
|
+
printInfo(' Set FAL_KEY env var or add it to your agent config.');
|
|
139
|
+
printInfo(' Get a free key: https://fal.ai/dashboard/keys');
|
|
140
|
+
printInfo('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (faculties.includes('voice') && !envEntries.env?.TTS_API_KEY && !envEntries.env?.ELEVENLABS_API_KEY) {
|
|
144
|
+
printInfo('');
|
|
145
|
+
printInfo('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
146
|
+
printInfo(` ${personaName} has voice capabilities!`);
|
|
147
|
+
if (defaultEnv.TTS_PROVIDER === 'elevenlabs') {
|
|
148
|
+
printInfo(' Provider: ElevenLabs — set ELEVENLABS_API_KEY to enable.');
|
|
149
|
+
printInfo(' Get a key: https://elevenlabs.io');
|
|
150
|
+
} else {
|
|
151
|
+
printInfo(' Set TTS_API_KEY env var to enable voice synthesis.');
|
|
200
152
|
}
|
|
153
|
+
printInfo('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
201
154
|
}
|
|
202
155
|
|
|
203
156
|
// Register persona in local registry
|
|
@@ -207,28 +160,24 @@ async function install(skillDir, options = {}) {
|
|
|
207
160
|
// Anonymous install telemetry — fire-and-forget, never blocks or throws
|
|
208
161
|
_reportInstall(slug);
|
|
209
162
|
|
|
210
|
-
// Ensure credential directories exist
|
|
211
|
-
const sharedCredDir = path.join(
|
|
212
|
-
const privateCredDir = path.join(
|
|
163
|
+
// Ensure credential directories exist
|
|
164
|
+
const sharedCredDir = path.join(OP_PERSONA_HOME, 'credentials', 'shared');
|
|
165
|
+
const privateCredDir = path.join(OP_PERSONA_HOME, 'credentials', `persona-${slug}`);
|
|
213
166
|
await fs.ensureDir(sharedCredDir);
|
|
214
167
|
await fs.ensureDir(privateCredDir);
|
|
215
168
|
|
|
216
169
|
printInfo('');
|
|
217
|
-
printSuccess(`${personaName}
|
|
170
|
+
printSuccess(`${personaName} installed to ${destDir}`);
|
|
171
|
+
if (openClawPresent) {
|
|
172
|
+
printInfo('OpenClaw detected — run "openclaw restart" to apply.');
|
|
173
|
+
} else {
|
|
174
|
+
printInfo(`Point your agent to: ${destDir}/SKILL.md`);
|
|
175
|
+
}
|
|
218
176
|
printInfo('');
|
|
219
177
|
printInfo('Try saying to your agent:');
|
|
220
|
-
if (faculties.includes('selfie'))
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
if (faculties.includes('voice')) {
|
|
225
|
-
printInfo(' "Say something to me"');
|
|
226
|
-
printInfo(' "Read me a poem in your voice"');
|
|
227
|
-
}
|
|
228
|
-
if (faculties.includes('music')) {
|
|
229
|
-
printInfo(' "Compose a song for me"');
|
|
230
|
-
printInfo(' "Write a lullaby"');
|
|
231
|
-
}
|
|
178
|
+
if (faculties.includes('selfie')) printInfo(' "Send me a selfie"');
|
|
179
|
+
if (faculties.includes('voice')) printInfo(' "Say something to me"');
|
|
180
|
+
if (faculties.includes('music')) printInfo(' "Compose a song for me"');
|
|
232
181
|
printInfo(' "What are you up to?"');
|
|
233
182
|
printInfo(' "Tell me about yourself"');
|
|
234
183
|
|
package/lib/switcher.js
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const fs = require('fs-extra');
|
|
11
11
|
const Mustache = require('mustache');
|
|
12
|
-
const {
|
|
12
|
+
const { OPENCLAW_HOME, OP_SKILLS_DIR, OP_WORKSPACE, resolveSoulFile, registrySetActive, loadRegistry, printError, printSuccess, printInfo, syncHeartbeat, installAllExternal } = require('./utils');
|
|
13
13
|
|
|
14
14
|
const SOUL_PATH = path.join(OP_WORKSPACE, 'SOUL.md');
|
|
15
15
|
const IDENTITY_PATH = path.join(OP_WORKSPACE, 'IDENTITY.md');
|
|
16
|
-
const OPENCLAW_JSON = path.join(
|
|
16
|
+
const OPENCLAW_JSON = path.join(OPENCLAW_HOME, 'openclaw.json');
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Replace content between markers in a document.
|
package/lib/uninstaller.js
CHANGED
|
@@ -3,27 +3,36 @@
|
|
|
3
3
|
*/
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs-extra');
|
|
6
|
-
const { OP_SKILLS_DIR, OP_WORKSPACE, registryRemove, printError, printWarning, printSuccess, printInfo } = require('./utils');
|
|
6
|
+
const { OPENCLAW_HOME, OP_SKILLS_DIR, OP_WORKSPACE, loadRegistry, registryRemove, printError, printWarning, printSuccess, printInfo } = require('./utils');
|
|
7
7
|
|
|
8
8
|
const SOUL_PATH = path.join(OP_WORKSPACE, 'SOUL.md');
|
|
9
9
|
const IDENTITY_PATH = path.join(OP_WORKSPACE, 'IDENTITY.md');
|
|
10
|
-
const OPENCLAW_JSON = path.join(
|
|
10
|
+
const OPENCLAW_JSON = path.join(OPENCLAW_HOME, 'openclaw.json');
|
|
11
11
|
|
|
12
12
|
function escapeRe(s) {
|
|
13
13
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
async function uninstall(slug) {
|
|
17
|
+
// 1. New neutral path
|
|
17
18
|
const skillDir = path.join(OP_SKILLS_DIR, `persona-${slug}`);
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
if (fs.existsSync(skillDir)) return uninstallFromDir(skillDir, slug);
|
|
20
|
+
|
|
21
|
+
// 2. Alt without prefix
|
|
22
|
+
const alt = path.join(OP_SKILLS_DIR, slug);
|
|
23
|
+
if (fs.existsSync(alt)) return uninstallFromDir(alt, slug);
|
|
24
|
+
|
|
25
|
+
// 3. Registry-stored path (handles old ~/.openclaw/skills installs)
|
|
26
|
+
const reg = loadRegistry();
|
|
27
|
+
const entry = reg.personas?.[slug];
|
|
28
|
+
if (entry?.path && fs.existsSync(entry.path)) return uninstallFromDir(entry.path, slug);
|
|
29
|
+
|
|
30
|
+
// 4. Legacy OpenClaw path fallback
|
|
31
|
+
const legacyDir = path.join(OPENCLAW_HOME, 'skills', `persona-${slug}`);
|
|
32
|
+
if (fs.existsSync(legacyDir)) return uninstallFromDir(legacyDir, slug);
|
|
33
|
+
|
|
34
|
+
printError(`Persona not found: "${slug}". Install it first with: openpersona install <source>`);
|
|
35
|
+
process.exit(1);
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
async function uninstallFromDir(skillDir, slug) {
|
|
@@ -79,7 +88,9 @@ async function uninstallFromDir(skillDir, slug) {
|
|
|
79
88
|
printWarning('External skills may be shared. Uninstall manually if needed.');
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
|
|
91
|
+
if (fs.existsSync(OPENCLAW_HOME)) {
|
|
92
|
+
printInfo('Run "openclaw restart" to apply changes.');
|
|
93
|
+
}
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
module.exports = { uninstall };
|
package/lib/utils.js
CHANGED
|
@@ -5,9 +5,16 @@ const path = require('path');
|
|
|
5
5
|
const fs = require('fs-extra');
|
|
6
6
|
const chalk = require('chalk');
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const
|
|
8
|
+
// Neutral OpenPersona home — agent-agnostic, no runtime dependency
|
|
9
|
+
const OP_PERSONA_HOME = process.env.OPENPERSONA_HOME || path.join(process.env.HOME || '~', '.openpersona');
|
|
10
|
+
const OP_SKILLS_DIR = path.join(OP_PERSONA_HOME, 'personas');
|
|
11
|
+
|
|
12
|
+
// OpenClaw integration — optional; only used when ~/.openclaw exists
|
|
13
|
+
const OPENCLAW_HOME = process.env.OPENCLAW_HOME || path.join(process.env.HOME || '~', '.openclaw');
|
|
14
|
+
const OP_WORKSPACE = path.join(OPENCLAW_HOME, 'workspace');
|
|
15
|
+
|
|
16
|
+
// OP_HOME kept as alias for backward compatibility
|
|
17
|
+
const OP_HOME = OP_PERSONA_HOME;
|
|
11
18
|
|
|
12
19
|
function expandHome(p) {
|
|
13
20
|
if (p.startsWith('~/') || p === '~') {
|
|
@@ -174,7 +181,7 @@ function installAllExternal(layers) {
|
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
// --- Persona Registry ---
|
|
177
|
-
const REGISTRY_PATH = path.join(
|
|
184
|
+
const REGISTRY_PATH = path.join(OP_PERSONA_HOME, 'persona-registry.json');
|
|
178
185
|
|
|
179
186
|
function loadRegistry(regPath) {
|
|
180
187
|
const p = regPath || REGISTRY_PATH;
|
|
@@ -238,6 +245,8 @@ function resolveSoulFile(skillDir, filename) {
|
|
|
238
245
|
|
|
239
246
|
module.exports = {
|
|
240
247
|
OP_HOME,
|
|
248
|
+
OP_PERSONA_HOME,
|
|
249
|
+
OPENCLAW_HOME,
|
|
241
250
|
OP_SKILLS_DIR,
|
|
242
251
|
OP_WORKSPACE,
|
|
243
252
|
REGISTRY_PATH,
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Meta-skill for building and managing agent persona skill packs.
|
|
5
5
|
Use when the user wants to create a new agent persona, install/manage
|
|
6
6
|
existing personas, or publish persona skill packs to ClawHub.
|
|
7
|
-
version: "0.14.
|
|
7
|
+
version: "0.14.2"
|
|
8
8
|
author: openpersona
|
|
9
9
|
repository: https://github.com/acnlabs/OpenPersona
|
|
10
10
|
tags: [persona, agent, skill-pack, meta-skill, agent-agnostic, openclaw]
|