Skip to content

Commit c3c0e8c

Browse files
dbieberclaude
andcommitted
Add profile support to Go Note Go
Implements multiple profile support allowing quick switching between different GNG configurations. Features: - Save/load named profiles with all settings - Quick shortcuts (:1, :2, :3, :4) for default profiles (roam, work, assistant, guest) - Automatic backup before profile switching - List, view current, and delete profiles - Profile init command to set up defaults Use cases: - Switch between personal/work note-taking systems - Different assistant configurations - Guest mode with safe settings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3d6fc0d commit c3c0e8c

File tree

4 files changed

+277
-0
lines changed

4 files changed

+277
-0
lines changed

PROFILES.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Go Note Go Profiles
2+
3+
Profiles allow you to quickly switch between different Go Note Go configurations. Each profile stores a complete snapshot of all settings.
4+
5+
## Quick Start
6+
7+
### Initialize Default Profiles
8+
9+
First, set up your settings the way you want them, then run:
10+
```
11+
:profile init
12+
```
13+
14+
This creates four default profiles:
15+
- `roam` (shortcut: `:1`)
16+
- `work` (shortcut: `:2`)
17+
- `assistant` (shortcut: `:3`)
18+
- `guest` (shortcut: `:4`)
19+
20+
### Switch Between Profiles
21+
22+
Use the numeric shortcuts:
23+
```
24+
:1 # Switch to roam profile
25+
:2 # Switch to work profile
26+
:3 # Switch to assistant profile
27+
:4 # Switch to guest profile
28+
```
29+
30+
Or use the full command:
31+
```
32+
:profile load roam
33+
```
34+
35+
## Commands
36+
37+
### Save a Profile
38+
```
39+
:profile save <name>
40+
```
41+
Saves your current settings as a named profile.
42+
43+
### Load a Profile
44+
```
45+
:profile load <name>
46+
```
47+
Loads all settings from the specified profile. Your current settings are automatically backed up to the `backup` profile before loading.
48+
49+
### List Profiles
50+
```
51+
:profile list
52+
```
53+
Shows all saved profiles.
54+
55+
### Current Profile
56+
```
57+
:profile current
58+
```
59+
Shows which profile is currently active.
60+
61+
### Delete a Profile
62+
```
63+
:profile delete <name>
64+
```
65+
Deletes a saved profile.
66+
67+
## Example Use Cases
68+
69+
### Personal vs Work
70+
- Profile 1 (roam): Personal Roam graph with your personal account
71+
- Profile 2 (work): Work Roam graph or different note-taking system
72+
73+
### Different Assistants
74+
- Profile 3 (assistant): Connected to your personal assistant
75+
- Profile 4 (guest): Safe settings for letting others try your GNG
76+
77+
### Different Upload Destinations
78+
Switch between different note-taking systems (Roam, RemNote, Notion, etc.) with a single command.
79+
80+
## How It Works
81+
82+
- Each profile is stored as a JSON blob in Redis containing all setting values
83+
- When you load a profile, your current settings are automatically backed up
84+
- The `backup` profile always contains your most recent settings before the last profile switch
85+
- Settings include: uploader type, credentials, custom command paths, API keys, etc.
86+
87+
## Safety
88+
89+
- Your current settings are always backed up to `backup` before loading a new profile
90+
- The secure_settings.py file is never modified - profiles only affect Redis settings
91+
- You can always revert to your previous settings by running `:profile load backup`

gonotego/command_center/commands.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from gonotego.command_center import assistant_commands # noqa: F401
99
from gonotego.command_center import custom_commands # noqa: F401
1010
from gonotego.command_center import note_commands # noqa: F401
11+
from gonotego.command_center import profile_commands # noqa: F401
1112
from gonotego.command_center import settings_commands # noqa: F401
1213
from gonotego.command_center import system_commands # noqa: F401
1314
from gonotego.command_center import twitter_commands # noqa: F401
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Profile commands for switching between Go Note Go configurations.
2+
3+
from gonotego.command_center import registry
4+
from gonotego.command_center import system_commands
5+
from gonotego.settings import profiles
6+
7+
register_command = registry.register_command
8+
say = system_commands.say
9+
10+
11+
# Predefined profile shortcuts
12+
PROFILE_SHORTCUTS = {
13+
'1': 'roam',
14+
'2': 'work',
15+
'3': 'assistant',
16+
'4': 'guest',
17+
}
18+
19+
20+
@register_command('{}')
21+
def load_profile_shortcut(shortcut):
22+
"""Load a profile using numeric shortcut (e.g., :1 for roam)."""
23+
if shortcut not in PROFILE_SHORTCUTS:
24+
return # Not a profile shortcut, let other commands handle it
25+
26+
profile_name = PROFILE_SHORTCUTS[shortcut]
27+
result = profiles.load_profile(profile_name)
28+
29+
if result is None:
30+
say(f'Profile {profile_name} not found. Creating from current settings.')
31+
profiles.save_profile(profile_name)
32+
say(f'Saved current settings as {profile_name}.')
33+
else:
34+
say(f'Loaded profile: {profile_name}')
35+
36+
37+
@register_command('profile save {}')
38+
def save_profile(profile_name):
39+
"""Save current settings as a named profile."""
40+
profiles.save_profile(profile_name)
41+
say(f'Saved profile: {profile_name}')
42+
43+
44+
@register_command('profile load {}')
45+
def load_profile(profile_name):
46+
"""Load a named profile."""
47+
result = profiles.load_profile(profile_name)
48+
if result is None:
49+
say(f'Profile not found: {profile_name}')
50+
else:
51+
say(f'Loaded profile: {profile_name}')
52+
53+
54+
@register_command('profile list')
55+
def list_profiles():
56+
"""List all saved profiles."""
57+
profile_names = profiles.list_profiles()
58+
if not profile_names:
59+
say('No profiles saved.')
60+
else:
61+
say(f'Profiles: {", ".join(profile_names)}')
62+
63+
64+
@register_command('profile current')
65+
def current_profile():
66+
"""Show the currently active profile."""
67+
current = profiles.get_current_profile()
68+
if current is None:
69+
say('No profile currently active.')
70+
else:
71+
say(f'Current profile: {current}')
72+
73+
74+
@register_command('profile delete {}')
75+
def delete_profile(profile_name):
76+
"""Delete a saved profile."""
77+
profiles.delete_profile(profile_name)
78+
say(f'Deleted profile: {profile_name}')
79+
80+
81+
@register_command('profile init')
82+
def init_default_profiles():
83+
"""Initialize default profiles (roam, work, assistant, guest) from current settings."""
84+
current_settings = profiles.get_all_settings()
85+
86+
for shortcut, profile_name in PROFILE_SHORTCUTS.items():
87+
profiles.save_profile(profile_name)
88+
89+
say('Initialized default profiles: roam, work, assistant, guest')

gonotego/settings/profiles.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"""Profile management for Go Note Go settings.
2+
3+
Profiles allow switching between different configurations (roam, work, assistant, guest).
4+
Each profile stores a complete snapshot of all settings.
5+
"""
6+
import json
7+
from gonotego.settings import settings
8+
from gonotego.settings import secure_settings
9+
from gonotego.common import interprocess
10+
11+
PROFILES_KEY = 'GoNoteGo:profiles'
12+
CURRENT_PROFILE_KEY = 'GoNoteGo:current_profile'
13+
BACKUP_PROFILE_KEY = 'GoNoteGo:backup_profile'
14+
15+
16+
def get_redis_key(profile_name):
17+
"""Get Redis key for a specific profile."""
18+
return f'{PROFILES_KEY}:{profile_name}'
19+
20+
21+
def get_all_settings():
22+
"""Get all current settings as a dict."""
23+
settings_dict = {}
24+
# Get all setting names from secure_settings
25+
setting_names = [s for s in dir(secure_settings) if not s.startswith('_')]
26+
for key in setting_names:
27+
settings_dict[key] = settings.get(key)
28+
return settings_dict
29+
30+
31+
def save_profile(profile_name):
32+
"""Save current settings to a named profile."""
33+
r = interprocess.get_redis_client()
34+
current_settings = get_all_settings()
35+
settings_json = json.dumps(current_settings)
36+
r.set(get_redis_key(profile_name), settings_json)
37+
return current_settings
38+
39+
40+
def load_profile(profile_name):
41+
"""Load settings from a named profile.
42+
43+
1. Backs up current settings to 'backup' profile
44+
2. Loads all settings from the specified profile
45+
3. Sets current profile marker
46+
"""
47+
r = interprocess.get_redis_client()
48+
49+
# First, backup current settings
50+
save_profile('backup')
51+
52+
# Load the requested profile
53+
profile_json = r.get(get_redis_key(profile_name))
54+
if profile_json is None:
55+
return None
56+
57+
profile_settings = json.loads(profile_json)
58+
59+
# Clear all current settings and load profile settings
60+
settings.clear_all()
61+
for key, value in profile_settings.items():
62+
settings.set(key, value)
63+
64+
# Mark this as the current profile
65+
r.set(CURRENT_PROFILE_KEY, profile_name)
66+
67+
return profile_settings
68+
69+
70+
def get_current_profile():
71+
"""Get the name of the currently active profile."""
72+
r = interprocess.get_redis_client()
73+
profile_bytes = r.get(CURRENT_PROFILE_KEY)
74+
if profile_bytes is None:
75+
return None
76+
return profile_bytes.decode('utf-8')
77+
78+
79+
def list_profiles():
80+
"""List all saved profile names."""
81+
r = interprocess.get_redis_client()
82+
profile_keys = r.keys(get_redis_key('*'))
83+
profile_names = []
84+
for key in profile_keys:
85+
key_str = key.decode('utf-8') if isinstance(key, bytes) else key
86+
# Extract profile name from key
87+
if key_str.startswith(f'{PROFILES_KEY}:'):
88+
profile_name = key_str[len(f'{PROFILES_KEY}:'):]
89+
profile_names.append(profile_name)
90+
return sorted(profile_names)
91+
92+
93+
def delete_profile(profile_name):
94+
"""Delete a saved profile."""
95+
r = interprocess.get_redis_client()
96+
r.delete(get_redis_key(profile_name))

0 commit comments

Comments
 (0)