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 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 Selfie is ready!")}
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": "2.2.0",
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": "https://github.com/SumeLabs/gfclaw"
32
+ "url": ""
33
33
  },
34
- "homepage": "https://github.com/SumeLabs/gfclaw#readme",
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
+ }