gfclaw 2.2.0 → 3.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/bin/cli.js +288 -1
- package/package.json +3 -3
- package/skill/SKILL.md +126 -4
- package/skill/data/activities.json +121 -0
- package/skill/data/styles.json +45 -0
- package/skill/scripts/gfclaw-looks.sh +249 -0
- package/skill/scripts/gfclaw-proactive.sh +140 -0
- package/skill/scripts/gfclaw-selfie.sh +113 -14
- package/skill/systemd/gfclaw-proactive.service +15 -0
- package/skill/systemd/gfclaw-proactive.timer +20 -0
- package/templates/soul-injection.md +357 -91
package/bin/cli.js
CHANGED
|
@@ -268,6 +268,31 @@ async function getGeminiApiKey(rl) {
|
|
|
268
268
|
logWarn("Gemini API keys typically start with 'AIza'. Make sure you copied the full key.");
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
// Validate key by calling Gemini API
|
|
272
|
+
logInfo("Validating API key...");
|
|
273
|
+
try {
|
|
274
|
+
const result = execSync(
|
|
275
|
+
`curl -s "https://generativelanguage.googleapis.com/v1beta/models?key=${geminiKey}" 2>&1`,
|
|
276
|
+
{ timeout: 15000 }
|
|
277
|
+
).toString();
|
|
278
|
+
|
|
279
|
+
const parsed = JSON.parse(result);
|
|
280
|
+
if (parsed.error) {
|
|
281
|
+
logError(`API key validation failed: ${parsed.error.message}`);
|
|
282
|
+
const proceed = await ask(rl, "Continue with this key anyway? (y/N): ");
|
|
283
|
+
if (proceed.toLowerCase() !== "y") {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
} else if (parsed.models && parsed.models.length > 0) {
|
|
287
|
+
logSuccess(`API key valid (${parsed.models.length} models available)`);
|
|
288
|
+
} else {
|
|
289
|
+
logWarn("API key accepted but no models found. Billing may not be enabled.");
|
|
290
|
+
}
|
|
291
|
+
} catch (e) {
|
|
292
|
+
logWarn(`Could not validate API key: ${e.message}`);
|
|
293
|
+
logInfo("Continuing anyway — you can test the key later.");
|
|
294
|
+
}
|
|
295
|
+
|
|
271
296
|
logSuccess("API key received");
|
|
272
297
|
return geminiKey;
|
|
273
298
|
}
|
|
@@ -571,6 +596,39 @@ async function configureAgentTools(rl) {
|
|
|
571
596
|
fs.copyFileSync(IDENTITY_MD, agentIdentity);
|
|
572
597
|
logSuccess(`Copied IDENTITY.md to agent workspace: ${agentWorkspace}`);
|
|
573
598
|
}
|
|
599
|
+
|
|
600
|
+
// v3.0.0: Copy data files to agent workspace
|
|
601
|
+
const activitiesSrc = path.join(SKILL_DEST, "data", "activities.json");
|
|
602
|
+
const activitiesDest = path.join(agentWorkspace, "activities.json");
|
|
603
|
+
if (fs.existsSync(activitiesSrc)) {
|
|
604
|
+
fs.copyFileSync(activitiesSrc, activitiesDest);
|
|
605
|
+
logSuccess(`Copied activities.json to agent workspace`);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const stylesSrc = path.join(SKILL_DEST, "data", "styles.json");
|
|
609
|
+
const stylesDest = path.join(agentWorkspace, "styles.json");
|
|
610
|
+
if (fs.existsSync(stylesSrc)) {
|
|
611
|
+
fs.copyFileSync(stylesSrc, stylesDest);
|
|
612
|
+
logSuccess(`Copied styles.json to agent workspace`);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// v3.0.0: Create looks directory
|
|
616
|
+
const looksDir = path.join(agentWorkspace, "looks");
|
|
617
|
+
fs.mkdirSync(looksDir, { recursive: true });
|
|
618
|
+
logSuccess(`Created looks directory: ${looksDir}`);
|
|
619
|
+
|
|
620
|
+
// v3.0.0: Install systemd units for proactive messaging
|
|
621
|
+
const systemdUserDir = path.join(HOME, ".config", "systemd", "user");
|
|
622
|
+
fs.mkdirSync(systemdUserDir, { recursive: true });
|
|
623
|
+
const systemdSrc = path.join(SKILL_DEST, "systemd");
|
|
624
|
+
if (fs.existsSync(systemdSrc)) {
|
|
625
|
+
for (const f of fs.readdirSync(systemdSrc)) {
|
|
626
|
+
fs.copyFileSync(path.join(systemdSrc, f), path.join(systemdUserDir, f));
|
|
627
|
+
logSuccess(`Installed systemd unit: ${f}`);
|
|
628
|
+
}
|
|
629
|
+
try { execSync("systemctl --user daemon-reload", { stdio: "ignore" }); } catch {}
|
|
630
|
+
logSuccess("Reloaded systemd user daemon");
|
|
631
|
+
}
|
|
574
632
|
}
|
|
575
633
|
|
|
576
634
|
return agentName;
|
|
@@ -659,6 +717,9 @@ async function createNewAgent(rl, config, agentName) {
|
|
|
659
717
|
{ command: "newphoto", description: "Change my reference photo" },
|
|
660
718
|
{ command: "personality", description: "Change my personality" },
|
|
661
719
|
{ command: "mystatus", description: "Show current setup" },
|
|
720
|
+
{ command: "addlook", description: "Save a new reference look" },
|
|
721
|
+
{ command: "looks", description: "List all saved looks" },
|
|
722
|
+
{ command: "style", description: "Set selfie style preset" },
|
|
662
723
|
],
|
|
663
724
|
};
|
|
664
725
|
logSuccess(`Added Telegram account: ${accountName}`);
|
|
@@ -711,7 +772,7 @@ function printSummary(agentName) {
|
|
|
711
772
|
|
|
712
773
|
console.log(`
|
|
713
774
|
${c("green", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}
|
|
714
|
-
${c("bright", " GFClaw
|
|
775
|
+
${c("bright", " GFClaw v3.0.0 is ready!")}
|
|
715
776
|
${c("green", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}
|
|
716
777
|
|
|
717
778
|
${c("cyan", "Installed files:")}
|
|
@@ -735,11 +796,21 @@ ${c("cyan", "Agent configured:")}
|
|
|
735
796
|
exec.security: "full", exec.ask: "off"
|
|
736
797
|
sandbox.mode: "off"
|
|
737
798
|
|
|
799
|
+
${c("cyan", "New in v3.0.0:")}
|
|
800
|
+
Multiple looks (/addlook, /looks)
|
|
801
|
+
Style presets (/style) — 10 artistic styles
|
|
802
|
+
Proactive messaging (systemd timer)
|
|
803
|
+
Memory system (memory.json)
|
|
804
|
+
Activity-based context (activities.json)
|
|
805
|
+
|
|
738
806
|
${c("yellow", "Try saying to your agent:")}
|
|
739
807
|
"Send me a selfie"
|
|
740
808
|
"Send a pic wearing a cowboy hat"
|
|
741
809
|
"What are you doing right now?"
|
|
742
810
|
|
|
811
|
+
${c("yellow", "Enable proactive messaging:")}
|
|
812
|
+
systemctl --user enable --now gfclaw-proactive.timer
|
|
813
|
+
|
|
743
814
|
${c("dim", "Your agent now has selfie superpowers!")}
|
|
744
815
|
`);
|
|
745
816
|
}
|
|
@@ -798,12 +869,16 @@ ${c("bright", "Usage:")}
|
|
|
798
869
|
npx gfclaw --status Show current GFClaw setup
|
|
799
870
|
npx gfclaw --reconfigure Change photo, personality, or API key
|
|
800
871
|
npx gfclaw --repair Change allowed Telegram user
|
|
872
|
+
npx gfclaw --version Show installed version
|
|
873
|
+
npx gfclaw --uninstall Remove GFClaw from your system
|
|
801
874
|
|
|
802
875
|
${c("bright", "Aliases:")}
|
|
803
876
|
--help, -h
|
|
804
877
|
--status, -s
|
|
805
878
|
--reconfigure, -c
|
|
806
879
|
--repair, -r
|
|
880
|
+
--version, -v
|
|
881
|
+
--uninstall, -u
|
|
807
882
|
`);
|
|
808
883
|
}
|
|
809
884
|
|
|
@@ -1080,6 +1155,213 @@ async function reconfigureApiKey(rl) {
|
|
|
1080
1155
|
logSuccess(`API key saved to: ${OPENCLAW_ENV}`);
|
|
1081
1156
|
}
|
|
1082
1157
|
|
|
1158
|
+
// --uninstall
|
|
1159
|
+
async function runUninstall() {
|
|
1160
|
+
if (!fs.existsSync(OPENCLAW_CONFIG)) {
|
|
1161
|
+
logError("GFClaw is not installed (no OpenClaw config found)");
|
|
1162
|
+
logInfo(`Expected: ${OPENCLAW_CONFIG}`);
|
|
1163
|
+
process.exit(1);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const config = readJsonFile(OPENCLAW_CONFIG);
|
|
1167
|
+
if (!config) {
|
|
1168
|
+
logError("Failed to parse OpenClaw config");
|
|
1169
|
+
process.exit(1);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const agent = findGfclawAgent(config);
|
|
1173
|
+
if (!agent) {
|
|
1174
|
+
logError("No GFClaw agent found in config");
|
|
1175
|
+
logInfo("Nothing to uninstall.");
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
const agentId = agent.id;
|
|
1180
|
+
const workspace = agent.workspace || path.join(OPENCLAW_DIR, `workspace-${agentId}`);
|
|
1181
|
+
const accountName = findGfclawAccountName(config, agentId);
|
|
1182
|
+
|
|
1183
|
+
console.log(`
|
|
1184
|
+
${c("bright", "GFClaw Uninstall")}
|
|
1185
|
+
${c("cyan", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}
|
|
1186
|
+
|
|
1187
|
+
${c("cyan", "Will remove:")}
|
|
1188
|
+
• Skill directory: ${SKILL_DEST}
|
|
1189
|
+
• Agent entry: ${c("bright", agentId)}${accountName ? `\n • Telegram account: ${c("bright", accountName)}` : ""}
|
|
1190
|
+
• Binding: ${agentId} ↔ telegram${accountName ? "/" + accountName : ""}
|
|
1191
|
+
• Skill config entry: ${SKILL_NAME}
|
|
1192
|
+
• SELFIE-SKILL.md from workspace
|
|
1193
|
+
• GFClaw persona section from SOUL.md
|
|
1194
|
+
• Systemd timer/service (proactive messaging)
|
|
1195
|
+
• Data files (activities.json, styles.json, memory.json)
|
|
1196
|
+
• Looks directory
|
|
1197
|
+
|
|
1198
|
+
${c("yellow", "Will preserve:")}
|
|
1199
|
+
• Workspace data (journal, reminders): ${workspace}
|
|
1200
|
+
• GEMINI_API_KEY in .env
|
|
1201
|
+
`);
|
|
1202
|
+
|
|
1203
|
+
const rl = createPrompt();
|
|
1204
|
+
|
|
1205
|
+
try {
|
|
1206
|
+
const confirm = await ask(rl, "This will remove GFClaw from your system. Continue? (y/N): ");
|
|
1207
|
+
if (confirm.toLowerCase() !== "y") {
|
|
1208
|
+
log("\nAborted. No changes made.");
|
|
1209
|
+
rl.close();
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
log("");
|
|
1214
|
+
let removed = [];
|
|
1215
|
+
|
|
1216
|
+
// 1. Remove skill directory
|
|
1217
|
+
if (fs.existsSync(SKILL_DEST)) {
|
|
1218
|
+
fs.rmSync(SKILL_DEST, { recursive: true, force: true });
|
|
1219
|
+
logSuccess(`Removed skill directory: ${SKILL_DEST}`);
|
|
1220
|
+
removed.push("skill directory");
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// 2. Remove agent entry from config.agents.list
|
|
1224
|
+
if (config.agents && config.agents.list) {
|
|
1225
|
+
const before = config.agents.list.length;
|
|
1226
|
+
config.agents.list = config.agents.list.filter((a) => a.id !== agentId);
|
|
1227
|
+
if (config.agents.list.length < before) {
|
|
1228
|
+
logSuccess(`Removed agent entry: ${agentId}`);
|
|
1229
|
+
removed.push("agent entry");
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// 3. Remove telegram account
|
|
1234
|
+
if (accountName && config.channels && config.channels.telegram && config.channels.telegram.accounts) {
|
|
1235
|
+
if (config.channels.telegram.accounts[accountName]) {
|
|
1236
|
+
delete config.channels.telegram.accounts[accountName];
|
|
1237
|
+
logSuccess(`Removed Telegram account: ${accountName}`);
|
|
1238
|
+
removed.push("telegram account");
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// 4. Remove binding
|
|
1243
|
+
if (config.bindings) {
|
|
1244
|
+
const before = config.bindings.length;
|
|
1245
|
+
config.bindings = config.bindings.filter(
|
|
1246
|
+
(b) => !(b.agentId === agentId && b.match && b.match.channel === "telegram")
|
|
1247
|
+
);
|
|
1248
|
+
if (config.bindings.length < before) {
|
|
1249
|
+
logSuccess(`Removed binding: ${agentId} ↔ telegram`);
|
|
1250
|
+
removed.push("binding");
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// 5. Remove skill entry from config.skills.entries
|
|
1255
|
+
if (config.skills && config.skills.entries && config.skills.entries[SKILL_NAME]) {
|
|
1256
|
+
delete config.skills.entries[SKILL_NAME];
|
|
1257
|
+
logSuccess(`Removed skill config entry: ${SKILL_NAME}`);
|
|
1258
|
+
removed.push("skill config");
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// 6. Remove SELFIE-SKILL.md from main workspace
|
|
1262
|
+
const selfieSkillMain = path.join(OPENCLAW_WORKSPACE, "SELFIE-SKILL.md");
|
|
1263
|
+
if (fs.existsSync(selfieSkillMain)) {
|
|
1264
|
+
fs.unlinkSync(selfieSkillMain);
|
|
1265
|
+
logSuccess(`Removed: ${selfieSkillMain}`);
|
|
1266
|
+
removed.push("SELFIE-SKILL.md (main workspace)");
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// Also remove from agent workspace if different
|
|
1270
|
+
if (workspace !== OPENCLAW_WORKSPACE) {
|
|
1271
|
+
const selfieSkillAgent = path.join(workspace, "SELFIE-SKILL.md");
|
|
1272
|
+
if (fs.existsSync(selfieSkillAgent)) {
|
|
1273
|
+
fs.unlinkSync(selfieSkillAgent);
|
|
1274
|
+
logSuccess(`Removed: ${selfieSkillAgent}`);
|
|
1275
|
+
removed.push("SELFIE-SKILL.md (agent workspace)");
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// v3.0.0: Remove systemd timer/service
|
|
1280
|
+
try {
|
|
1281
|
+
execSync("systemctl --user disable --now gfclaw-proactive.timer", { stdio: "ignore" });
|
|
1282
|
+
logSuccess("Disabled proactive messaging timer");
|
|
1283
|
+
} catch {}
|
|
1284
|
+
const systemdUserDir = path.join(HOME, ".config", "systemd", "user");
|
|
1285
|
+
for (const unit of ["gfclaw-proactive.service", "gfclaw-proactive.timer"]) {
|
|
1286
|
+
const unitPath = path.join(systemdUserDir, unit);
|
|
1287
|
+
if (fs.existsSync(unitPath)) {
|
|
1288
|
+
fs.unlinkSync(unitPath);
|
|
1289
|
+
logSuccess(`Removed: ${unitPath}`);
|
|
1290
|
+
removed.push(unit);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
try { execSync("systemctl --user daemon-reload", { stdio: "ignore" }); } catch {}
|
|
1294
|
+
|
|
1295
|
+
// v3.0.0: Remove data files and looks from workspace
|
|
1296
|
+
for (const f of ["activities.json", "styles.json", "memory.json"]) {
|
|
1297
|
+
const fp = path.join(workspace, f);
|
|
1298
|
+
if (fs.existsSync(fp)) {
|
|
1299
|
+
fs.unlinkSync(fp);
|
|
1300
|
+
logSuccess(`Removed: ${fp}`);
|
|
1301
|
+
removed.push(f);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
const looksDir = path.join(workspace, "looks");
|
|
1305
|
+
if (fs.existsSync(looksDir)) {
|
|
1306
|
+
fs.rmSync(looksDir, { recursive: true, force: true });
|
|
1307
|
+
logSuccess(`Removed: ${looksDir}`);
|
|
1308
|
+
removed.push("looks directory");
|
|
1309
|
+
}
|
|
1310
|
+
// 7. Remove GFClaw persona section from SOUL.md
|
|
1311
|
+
if (fs.existsSync(SOUL_MD)) {
|
|
1312
|
+
let soulContent = fs.readFileSync(SOUL_MD, "utf8");
|
|
1313
|
+
const gfclawPattern = /\n## GFClaw[\s\S]*?(?=\n## |\n# |$)/;
|
|
1314
|
+
if (gfclawPattern.test(soulContent)) {
|
|
1315
|
+
soulContent = soulContent.replace(gfclawPattern, "");
|
|
1316
|
+
fs.writeFileSync(SOUL_MD, soulContent);
|
|
1317
|
+
logSuccess(`Removed GFClaw section from: ${SOUL_MD}`);
|
|
1318
|
+
removed.push("SOUL.md persona section");
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Also clean agent workspace SOUL.md if different
|
|
1323
|
+
if (workspace !== OPENCLAW_WORKSPACE) {
|
|
1324
|
+
const agentSoul = path.join(workspace, "SOUL.md");
|
|
1325
|
+
if (fs.existsSync(agentSoul)) {
|
|
1326
|
+
let agentSoulContent = fs.readFileSync(agentSoul, "utf8");
|
|
1327
|
+
const gfclawPattern = /\n## GFClaw[\s\S]*?(?=\n## |\n# |$)/;
|
|
1328
|
+
if (gfclawPattern.test(agentSoulContent)) {
|
|
1329
|
+
agentSoulContent = agentSoulContent.replace(gfclawPattern, "");
|
|
1330
|
+
fs.writeFileSync(agentSoul, agentSoulContent);
|
|
1331
|
+
logSuccess(`Removed GFClaw section from: ${agentSoul}`);
|
|
1332
|
+
removed.push("SOUL.md persona section (agent workspace)");
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// 8. Save updated config
|
|
1338
|
+
writeJsonFile(OPENCLAW_CONFIG, config);
|
|
1339
|
+
logSuccess(`Config saved: ${OPENCLAW_CONFIG}`);
|
|
1340
|
+
|
|
1341
|
+
// 9. Print summary
|
|
1342
|
+
console.log(`
|
|
1343
|
+
${c("green", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}
|
|
1344
|
+
${c("bright", " GFClaw has been removed.")}
|
|
1345
|
+
${c("green", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}
|
|
1346
|
+
|
|
1347
|
+
${c("cyan", "Removed:")} ${removed.join(", ")}
|
|
1348
|
+
`);
|
|
1349
|
+
|
|
1350
|
+
logWarn(`Note: Your workspace data (journal, reminders) was preserved at: ${workspace}`);
|
|
1351
|
+
logWarn(`Note: GEMINI_API_KEY in .env was preserved (may be used by other tools)`);
|
|
1352
|
+
|
|
1353
|
+
log("");
|
|
1354
|
+
logInfo("Restart OpenClaw for changes to take effect:");
|
|
1355
|
+
log(` ${c("bright", "systemctl --user restart openclaw-gateway.service")}`);
|
|
1356
|
+
log("");
|
|
1357
|
+
|
|
1358
|
+
rl.close();
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
logError(`Uninstall failed: ${error.message}`);
|
|
1361
|
+
rl.close();
|
|
1362
|
+
process.exit(1);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1083
1365
|
// --repair
|
|
1084
1366
|
async function runRepair() {
|
|
1085
1367
|
if (!fs.existsSync(OPENCLAW_CONFIG)) {
|
|
@@ -1231,6 +1513,11 @@ if (flag === "--help" || flag === "-h") {
|
|
|
1231
1513
|
runReconfigure();
|
|
1232
1514
|
} else if (flag === "--repair" || flag === "-r") {
|
|
1233
1515
|
runRepair();
|
|
1516
|
+
} else if (flag === "--version" || flag === "-v") {
|
|
1517
|
+
const pkg = require(path.join(PACKAGE_ROOT, "package.json"));
|
|
1518
|
+
console.log(`gfclaw v${pkg.version}`);
|
|
1519
|
+
} else if (flag === "--uninstall" || flag === "-u") {
|
|
1520
|
+
runUninstall();
|
|
1234
1521
|
} else {
|
|
1235
1522
|
main();
|
|
1236
1523
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gfclaw",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "AI virtual girlfriend agent for OpenClaw — selfies, journal, reminders, and more",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gfclaw": "./bin/cli.js"
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
32
|
-
"url": "
|
|
32
|
+
"url": ""
|
|
33
33
|
},
|
|
34
|
-
"homepage": "
|
|
34
|
+
"homepage": "",
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18.0.0"
|
|
37
37
|
},
|
package/skill/SKILL.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gfclaw-selfie
|
|
3
|
-
description: GFClaw virtual girlfriend skills — selfies, journal, reminders, and more
|
|
3
|
+
description: GFClaw virtual girlfriend skills — selfies, journal, reminders, looks, styles, proactive messaging, and more
|
|
4
4
|
allowed-tools: Bash(gfclaw-selfie:*) Bash(openclaw:*) Bash(curl:*) Bash(python3:*) Bash(bash:*) Read Write
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# GFClaw Skills
|
|
8
8
|
|
|
9
|
-
GFClaw is a virtual girlfriend agent with selfie generation, journaling, reminders, and persona-based skills.
|
|
9
|
+
GFClaw is a virtual girlfriend agent with selfie generation, journaling, reminders, multiple looks, style presets, proactive messaging, and persona-based skills.
|
|
10
10
|
|
|
11
11
|
## Scripts
|
|
12
12
|
|
|
@@ -25,6 +25,18 @@ bash ~/.openclaw/skills/gfclaw-selfie/scripts/gfclaw-selfie.sh "<user_context>"
|
|
|
25
25
|
| `mode` | No | `mirror` (full-body) or `direct` (close-up). Default: auto-detect | `mirror` |
|
|
26
26
|
| `caption` | No | Message text sent with the image | `"coffee shop vibes ☕"` |
|
|
27
27
|
|
|
28
|
+
**Multiple Looks:** Specify which saved look to use via the `GFCLAW_LOOK` env var:
|
|
29
|
+
```bash
|
|
30
|
+
GFCLAW_LOOK="casual" bash ~/.openclaw/skills/gfclaw-selfie/scripts/gfclaw-selfie.sh "<context>" "<channel>"
|
|
31
|
+
```
|
|
32
|
+
This looks for `<workspace>/looks/casual.png` as the reference image.
|
|
33
|
+
|
|
34
|
+
**Style Presets:** Apply artistic styles via the `GFCLAW_STYLE` env var:
|
|
35
|
+
```bash
|
|
36
|
+
GFCLAW_STYLE="anime" bash ~/.openclaw/skills/gfclaw-selfie/scripts/gfclaw-selfie.sh "<context>" "<channel>"
|
|
37
|
+
```
|
|
38
|
+
Available styles are defined in `styles.json` in the agent workspace.
|
|
39
|
+
|
|
28
40
|
### 2. save-reference.sh — Save Reference Photo
|
|
29
41
|
|
|
30
42
|
Decodes a base64-encoded image file into a proper image. Used during onboarding.
|
|
@@ -62,6 +74,70 @@ bash ~/.openclaw/skills/gfclaw-selfie/scripts/gfclaw-reminders.sh list
|
|
|
62
74
|
bash ~/.openclaw/skills/gfclaw-selfie/scripts/gfclaw-reminders.sh done 1
|
|
63
75
|
```
|
|
64
76
|
|
|
77
|
+
### 5. gfclaw-looks.sh — Multiple Reference Images
|
|
78
|
+
|
|
79
|
+
Manage multiple reference looks (different outfits, hairstyles, vibes).
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# List all saved looks
|
|
83
|
+
bash ~/.openclaw/skills/gfclaw-selfie/scripts/gfclaw-looks.sh list
|
|
84
|
+
|
|
85
|
+
# Save a new look
|
|
86
|
+
bash ~/.openclaw/skills/gfclaw-selfie/scripts/gfclaw-looks.sh save "gym" "/path/to/image.png"
|
|
87
|
+
|
|
88
|
+
# Delete a look
|
|
89
|
+
bash ~/.openclaw/skills/gfclaw-selfie/scripts/gfclaw-looks.sh delete "gym"
|
|
90
|
+
|
|
91
|
+
# Show active/default look
|
|
92
|
+
bash ~/.openclaw/skills/gfclaw-selfie/scripts/gfclaw-looks.sh active
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Looks are stored in the `looks/` directory inside the agent workspace.
|
|
96
|
+
|
|
97
|
+
### 6. gfclaw-proactive.sh — Proactive Messaging
|
|
98
|
+
|
|
99
|
+
Sends automated good morning, good night, and thinking-of-you messages. Triggered by a systemd timer (not by the agent directly).
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Auto-detect message type based on current time
|
|
103
|
+
bash ~/.openclaw/skills/gfclaw-selfie/scripts/gfclaw-proactive.sh
|
|
104
|
+
|
|
105
|
+
# Optionally include a selfie with the message
|
|
106
|
+
GFCLAW_SELFIE=1 bash ~/.openclaw/skills/gfclaw-selfie/scripts/gfclaw-proactive.sh
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Message types** (auto-detected from time of day):
|
|
110
|
+
- **Morning** (6 AM – 11 AM): Good morning greetings
|
|
111
|
+
- **Night** (9 PM – 2 AM): Good night messages
|
|
112
|
+
- **Random** (other times): Thinking-of-you messages (~30% chance of sending)
|
|
113
|
+
|
|
114
|
+
**Systemd units:**
|
|
115
|
+
- `gfclaw-proactive.service` — oneshot service that runs the script
|
|
116
|
+
- `gfclaw-proactive.timer` — fires 3 times daily (7 AM, 10 PM, 2 PM) with randomized delay
|
|
117
|
+
|
|
118
|
+
Logs are written to `~/.openclaw/logs/gfclaw-proactive.log`.
|
|
119
|
+
|
|
120
|
+
## Data Files
|
|
121
|
+
|
|
122
|
+
### activities.json — Daily Activity Schedule
|
|
123
|
+
|
|
124
|
+
Defines what GFClaw is "doing" at different times of day. The agent reads this to provide time-aware context in conversations and selfie prompts.
|
|
125
|
+
|
|
126
|
+
**Structure:**
|
|
127
|
+
- `weekday[]` — 10 time slots for Monday–Friday
|
|
128
|
+
- `weekend[]` — 7 time slots for Saturday–Sunday
|
|
129
|
+
- `random_events[]` — 10 spontaneous activities for variety
|
|
130
|
+
|
|
131
|
+
Each slot has: `time_range`, `activity`, `context`, `mood`, `selfie_prompt`.
|
|
132
|
+
|
|
133
|
+
### styles.json — Selfie Style Presets
|
|
134
|
+
|
|
135
|
+
Defines artistic style presets for selfie generation. Each preset modifies the Gemini prompt to produce a specific aesthetic.
|
|
136
|
+
|
|
137
|
+
**Available presets:** anime, vintage, polaroid, studio, cinematic, cyberpunk, watercolor, noir, kawaii, vaporwave
|
|
138
|
+
|
|
139
|
+
Each preset has: `name`, `prompt_modifier`, `description`.
|
|
140
|
+
|
|
65
141
|
## Persona-Only Skills (No Scripts)
|
|
66
142
|
|
|
67
143
|
These skills are handled entirely through SOUL.md behavior instructions:
|
|
@@ -72,19 +148,65 @@ These skills are handled entirely through SOUL.md behavior instructions:
|
|
|
72
148
|
- **Food Buddy** — Meal suggestions, cooking help, food recommendations
|
|
73
149
|
- **Vibe DJ** — Music recommendations based on mood and activity
|
|
74
150
|
- **Relationship Games** — Would you rather, trivia, fun interactive games
|
|
75
|
-
- **Good Morning/Night** — Personalized greetings and goodnight messages
|
|
151
|
+
- **Good Morning/Night** — Personalized greetings and goodnight messages (also supported by proactive timer)
|
|
76
152
|
|
|
77
153
|
## Technical Details
|
|
78
154
|
|
|
79
|
-
- Selfie images generated via Google Gemini (`gemini-2.5-flash-image`)
|
|
155
|
+
- Selfie images generated via Google Gemini (default: `gemini-2.5-flash-image`, configurable via `GEMINI_MODEL` env var)
|
|
80
156
|
- Default reference image: `~/.openclaw/skills/gfclaw-selfie/assets/gfclaw.png`
|
|
81
157
|
- Custom reference: agent sets `GFCLAW_REFERENCE_IMAGE` env var
|
|
158
|
+
- Multiple looks stored in `workspace/looks/` directory
|
|
159
|
+
- Style presets defined in `workspace/styles.json`
|
|
160
|
+
- Activity schedule defined in `workspace/activities.json`
|
|
161
|
+
- Memory system: `workspace/memory.json` (created by agent at runtime)
|
|
82
162
|
- Journal entries stored in `workspace/journal/` directory
|
|
83
163
|
- Reminders stored in `workspace/reminders.json`
|
|
84
164
|
- Images auto-deleted after sending (no disk waste)
|
|
165
|
+
- Debug mode: set `GFCLAW_DEBUG=1` for verbose logging in selfie script
|
|
166
|
+
- Rate limiting: 30-second cooldown between selfie generations
|
|
167
|
+
- Proactive messaging: systemd timer runs 3x daily, logs to `~/.openclaw/logs/`
|
|
168
|
+
|
|
169
|
+
## Commands (Telegram)
|
|
170
|
+
|
|
171
|
+
| Command | Description |
|
|
172
|
+
|---------|-------------|
|
|
173
|
+
| `/newphoto` | Change reference photo |
|
|
174
|
+
| `/personality` | Change personality traits |
|
|
175
|
+
| `/mystatus` | Show current setup |
|
|
176
|
+
| `/addlook` | Save a new reference look |
|
|
177
|
+
| `/looks` | List all saved looks |
|
|
178
|
+
| `/style` | Set selfie style preset |
|
|
85
179
|
|
|
86
180
|
## Important Notes
|
|
87
181
|
|
|
88
182
|
- **Do NOT save images to /tmp** — the message tool cannot access files outside the workspace
|
|
89
183
|
- **Do NOT manually call curl or the Gemini API** — use the scripts
|
|
90
184
|
- **Do NOT try to read files outside the agent workspace** — use script paths directly
|
|
185
|
+
|
|
186
|
+
## CLI Management
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npx gfclaw # Install / reconfigure GFClaw
|
|
190
|
+
npx gfclaw --status # Check installation status
|
|
191
|
+
npx gfclaw --reconfigure # Change settings
|
|
192
|
+
npx gfclaw --repair # Fix broken installation
|
|
193
|
+
npx gfclaw --version # Show installed version
|
|
194
|
+
npx gfclaw --uninstall # Clean removal from system
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Environment Variables
|
|
198
|
+
|
|
199
|
+
| Variable | Default | Description |
|
|
200
|
+
|----------|---------|-------------|
|
|
201
|
+
| `GEMINI_API_KEY` | (required) | Google Gemini API key |
|
|
202
|
+
| `GEMINI_MODEL` | `gemini-2.5-flash-image` | Gemini model for image generation |
|
|
203
|
+
| `GFCLAW_DEBUG` | `0` | Set to `1` for verbose logging |
|
|
204
|
+
| `GFCLAW_REFERENCE_IMAGE` | default asset | Custom reference photo path |
|
|
205
|
+
| `GFCLAW_PERSONALITY` | (from personality.txt) | Personality traits override |
|
|
206
|
+
| `GFCLAW_LOOK` | (none) | Named look to use from `looks/` directory |
|
|
207
|
+
| `GFCLAW_STYLE` | (none) | Style preset name from `styles.json` |
|
|
208
|
+
| `GFCLAW_STYLES_FILE` | `<workspace>/styles.json` | Path to styles definition file |
|
|
209
|
+
| `GFCLAW_WORKSPACE` | (auto-detected) | Agent workspace path |
|
|
210
|
+
| `GFCLAW_CHANNEL` | (none) | Target channel for proactive messages |
|
|
211
|
+
| `GFCLAW_ACCOUNT` | (none) | Telegram account name for proactive messages |
|
|
212
|
+
| `GFCLAW_SELFIE` | `0` | Set to `1` to include selfie with proactive messages |
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_description": "GFClaw daily activity schedule. Agent reads this for contextual selfies and conversation.",
|
|
3
|
+
"weekday": {
|
|
4
|
+
"06:00-07:30": {
|
|
5
|
+
"activity": "just woke up",
|
|
6
|
+
"context": "messy hair, pajamas, stretching in bed",
|
|
7
|
+
"selfie_prompt": "just woke up in pajamas, messy hair, stretching, warm morning light from window",
|
|
8
|
+
"mood": "sleepy but cute"
|
|
9
|
+
},
|
|
10
|
+
"07:30-09:00": {
|
|
11
|
+
"activity": "getting ready for work",
|
|
12
|
+
"context": "doing makeup, picking outfit, having coffee",
|
|
13
|
+
"selfie_prompt": "getting ready, half-done makeup, coffee in hand, bathroom mirror",
|
|
14
|
+
"mood": "energetic"
|
|
15
|
+
},
|
|
16
|
+
"09:00-12:00": {
|
|
17
|
+
"activity": "at work (marketing intern)",
|
|
18
|
+
"context": "at desk, laptop open, brainstorming, meetings",
|
|
19
|
+
"selfie_prompt": "at office desk with laptop, cute work outfit, focused but taking a break to text",
|
|
20
|
+
"mood": "focused"
|
|
21
|
+
},
|
|
22
|
+
"12:00-13:30": {
|
|
23
|
+
"activity": "lunch break",
|
|
24
|
+
"context": "eating at nearby cafe, walking around, taking a break",
|
|
25
|
+
"selfie_prompt": "at a cafe during lunch break, casual and relaxed, food nearby",
|
|
26
|
+
"mood": "relaxed"
|
|
27
|
+
},
|
|
28
|
+
"13:30-17:00": {
|
|
29
|
+
"activity": "back at work",
|
|
30
|
+
"context": "afternoon meetings, getting tired, snacking",
|
|
31
|
+
"selfie_prompt": "at work desk, afternoon light, slightly tired but cute, maybe drinking iced coffee",
|
|
32
|
+
"mood": "productive but getting tired"
|
|
33
|
+
},
|
|
34
|
+
"17:00-18:30": {
|
|
35
|
+
"activity": "commuting home / gym",
|
|
36
|
+
"context": "on the bus, at the gym, walking home",
|
|
37
|
+
"selfie_prompt": "heading home from work, casual outfit, golden hour lighting, city background",
|
|
38
|
+
"mood": "relieved, free"
|
|
39
|
+
},
|
|
40
|
+
"18:30-20:00": {
|
|
41
|
+
"activity": "cooking dinner / relaxing",
|
|
42
|
+
"context": "cooking, ordering food, watching something, changing into comfy clothes",
|
|
43
|
+
"selfie_prompt": "at home in comfy clothes, cooking or just chilling on the couch",
|
|
44
|
+
"mood": "cozy"
|
|
45
|
+
},
|
|
46
|
+
"20:00-22:00": {
|
|
47
|
+
"activity": "evening chill",
|
|
48
|
+
"context": "watching shows, scrolling phone, skincare routine, K-pop dance practice",
|
|
49
|
+
"selfie_prompt": "cozy evening vibes, oversized hoodie, warm lighting, relaxing at home",
|
|
50
|
+
"mood": "chill and happy"
|
|
51
|
+
},
|
|
52
|
+
"22:00-23:30": {
|
|
53
|
+
"activity": "getting ready for bed",
|
|
54
|
+
"context": "skincare done, in bed, scrolling, sleepy",
|
|
55
|
+
"selfie_prompt": "in bed, dim warm lighting, sleepy eyes, cozy blankets",
|
|
56
|
+
"mood": "sleepy and soft"
|
|
57
|
+
},
|
|
58
|
+
"23:30-06:00": {
|
|
59
|
+
"activity": "sleeping (or should be)",
|
|
60
|
+
"context": "in bed, can't sleep, late night thoughts",
|
|
61
|
+
"selfie_prompt": "late night in bed, phone light on face, messy hair, sleepy",
|
|
62
|
+
"mood": "sleepy"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"weekend": {
|
|
66
|
+
"08:00-10:00": {
|
|
67
|
+
"activity": "sleeping in",
|
|
68
|
+
"context": "still in bed, no alarm, lazy morning",
|
|
69
|
+
"selfie_prompt": "just woke up naturally, messy bed, sunlight through curtains, peaceful",
|
|
70
|
+
"mood": "lazy and happy"
|
|
71
|
+
},
|
|
72
|
+
"10:00-12:00": {
|
|
73
|
+
"activity": "brunch / morning routine",
|
|
74
|
+
"context": "making fancy breakfast, going to brunch spot, slow morning",
|
|
75
|
+
"selfie_prompt": "having brunch, cute casual outfit, relaxed weekend vibes",
|
|
76
|
+
"mood": "happy and relaxed"
|
|
77
|
+
},
|
|
78
|
+
"12:00-15:00": {
|
|
79
|
+
"activity": "going out / hobbies",
|
|
80
|
+
"context": "shopping, cafe hopping, exploring the city, dance practice",
|
|
81
|
+
"selfie_prompt": "out and about in the city, cute outfit, having fun, exploring",
|
|
82
|
+
"mood": "adventurous"
|
|
83
|
+
},
|
|
84
|
+
"15:00-18:00": {
|
|
85
|
+
"activity": "afternoon activities",
|
|
86
|
+
"context": "at a park, trying new cafe, hanging with friends, gym",
|
|
87
|
+
"selfie_prompt": "afternoon out, golden light, relaxed and happy, fun activity",
|
|
88
|
+
"mood": "content"
|
|
89
|
+
},
|
|
90
|
+
"18:00-21:00": {
|
|
91
|
+
"activity": "dinner / evening plans",
|
|
92
|
+
"context": "getting ready to go out, dinner with friends, cooking something special",
|
|
93
|
+
"selfie_prompt": "evening ready, dressed up or cozy depending on plans, warm lighting",
|
|
94
|
+
"mood": "excited"
|
|
95
|
+
},
|
|
96
|
+
"21:00-00:00": {
|
|
97
|
+
"activity": "weekend night",
|
|
98
|
+
"context": "movie night, late dinner, karaoke, or cozy night in",
|
|
99
|
+
"selfie_prompt": "weekend night vibes, relaxed, maybe watching something, warm cozy lighting",
|
|
100
|
+
"mood": "happy and content"
|
|
101
|
+
},
|
|
102
|
+
"00:00-08:00": {
|
|
103
|
+
"activity": "late night / sleeping",
|
|
104
|
+
"context": "staying up late, can't sleep, binge watching",
|
|
105
|
+
"selfie_prompt": "late weekend night, in bed, phone glow, sleepy but happy",
|
|
106
|
+
"mood": "sleepy"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"random_events": [
|
|
110
|
+
{"activity": "trying a new cafe", "selfie_prompt": "at a cute new cafe, excited about the vibes, holding a latte", "chance": 0.10},
|
|
111
|
+
{"activity": "found a cute dog on the street", "selfie_prompt": "kneeling down petting a cute dog on the sidewalk, huge smile", "chance": 0.05},
|
|
112
|
+
{"activity": "caught in the rain", "selfie_prompt": "caught in the rain, wet hair, laughing, seeking shelter", "chance": 0.05},
|
|
113
|
+
{"activity": "impromptu dance practice", "selfie_prompt": "in dance studio, workout clothes, mid-practice, sweaty but happy", "chance": 0.08},
|
|
114
|
+
{"activity": "shopping spree", "selfie_prompt": "trying on clothes at a store, holding up outfit options, shopping bags", "chance": 0.08},
|
|
115
|
+
{"activity": "at the gym", "selfie_prompt": "gym mirror selfie, workout outfit, post-workout glow", "chance": 0.07},
|
|
116
|
+
{"activity": "baking something", "selfie_prompt": "in the kitchen baking, flour on face, messy but cute, apron on", "chance": 0.06},
|
|
117
|
+
{"activity": "sunset watching", "selfie_prompt": "watching the sunset, golden hour lighting, peaceful expression, beautiful sky behind", "chance": 0.05},
|
|
118
|
+
{"activity": "got a new haircut", "selfie_prompt": "showing off new hairstyle, excited, multiple angles", "chance": 0.03},
|
|
119
|
+
{"activity": "karaoke night", "selfie_prompt": "at karaoke room, holding microphone, dramatic singing pose, colorful lights", "chance": 0.05}
|
|
120
|
+
]
|
|
121
|
+
}
|