style(design): apply Minimal Dark Mode design system\n\n- Switched to complete Dark Mode (OLED-friendly)\n- Established standard color palette matching modern technical aesthetics\n- Changed font to Plus Jakarta Sans for increased legibility\n- Enforced dark mode as default in App.tsx\n- Fixed several React cascading render (setState in useEffect) warnings
This commit is contained in:
1 parent
b80531f8a8
commit
bac7b5591e
738 files changed
+138513
-138123
No files matched your search
+197
-197
@@ -1,197 +1,197 @@
|
||||
# Gemini Nexus CLI
|
||||
|
||||
A powerful command-line interface for managing multiple Google Cloud AI accounts in Gemini Nexus IDE. Supports account switching, quota monitoring, token validation, and more.
|
||||
|
||||
## Features
|
||||
|
||||
- 📊 **Quota Management**: Real-time quota tracking for Gemini and Claude models
|
||||
- 🔄 **Account Switching**: Seamless switching between multiple Google accounts
|
||||
- ✅ **Token Validation**: Automatic token refresh and expiry detection
|
||||
- 🎯 **Smart Auto-Switch**: Automatically select the best account based on quotas
|
||||
- 🏷️ **Aliases**: Create shortcuts for your accounts (e.g., `work`, `personal`)
|
||||
- 💾 **Backup/Restore**: Export and import account configurations
|
||||
- 🔍 **Database Sync**: View sync status between CLI and IDE
|
||||
- 🩺 **Diagnostics**: System health checks and troubleshooting
|
||||
- 🎮 **Interactive Mode**: Beautiful keyboard-navigated menus
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Install Python dependencies:**
|
||||
```powershell
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **(Optional) Add to PATH:**
|
||||
```powershell
|
||||
.\agm.bat setup-path
|
||||
```
|
||||
Restart your terminal after this.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Interactive Mode (Recommended for Beginners)
|
||||
Simply run without arguments to get a menu:
|
||||
```powershell
|
||||
.\agm.bat
|
||||
```
|
||||
|
||||
### Command Line (For Power Users)
|
||||
|
||||
**List all accounts:**
|
||||
```powershell
|
||||
agm list
|
||||
```
|
||||
|
||||
**Switch accounts:**
|
||||
```powershell
|
||||
agm switch [email protected]
|
||||
# or use partial match
|
||||
agm switch aleks
|
||||
```
|
||||
|
||||
**Refresh quotas:**
|
||||
```powershell
|
||||
agm refresh-all
|
||||
# or single account
|
||||
agm refresh [email protected]
|
||||
```
|
||||
|
||||
**Validate tokens:**
|
||||
```powershell
|
||||
agm validate
|
||||
```
|
||||
|
||||
**Quick status:**
|
||||
```powershell
|
||||
agm status
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Aliases
|
||||
Create shortcuts for frequently used accounts:
|
||||
```powershell
|
||||
# Set alias
|
||||
agm alias work [email protected]
|
||||
|
||||
# Use alias
|
||||
agm switch work
|
||||
agm refresh work
|
||||
|
||||
# View all aliases
|
||||
agm alias
|
||||
|
||||
# Remove alias
|
||||
agm unalias work
|
||||
```
|
||||
|
||||
### Auto-Switch
|
||||
Automatically switch to the account with the best quota:
|
||||
```powershell
|
||||
# Any model with at least 50% quota
|
||||
agm auto-switch
|
||||
|
||||
# Prefer Claude models
|
||||
agm auto-switch --model claude --min-quota 30
|
||||
|
||||
# Prefer Gemini models
|
||||
agm auto-switch --model gemini --min-quota 60
|
||||
```
|
||||
|
||||
### Backup & Restore
|
||||
```powershell
|
||||
# Export accounts
|
||||
agm export my_accounts.json
|
||||
|
||||
# Import accounts
|
||||
agm import-backup my_accounts.json
|
||||
```
|
||||
|
||||
### Live Monitoring
|
||||
Watch quota changes in real-time:
|
||||
```powershell
|
||||
# Refresh every 10 seconds (default)
|
||||
agm watch
|
||||
|
||||
# Custom interval
|
||||
agm watch --interval 30
|
||||
```
|
||||
|
||||
### Diagnostics
|
||||
Run system health checks:
|
||||
```powershell
|
||||
agm doctor
|
||||
```
|
||||
|
||||
## All Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `agm list` | List all accounts with quota summary |
|
||||
| `agm info <email>` | Show detailed quota for specific account |
|
||||
| `agm switch <email>` | Switch to an account |
|
||||
| `agm status` | Show active account and quotas |
|
||||
| `agm refresh <email>` | Refresh quotas for one account |
|
||||
| `agm refresh-all` | Refresh quotas for all accounts |
|
||||
| `agm validate` | Check and refresh expired tokens |
|
||||
| `agm sync` | Show CLI ↔ IDE sync status |
|
||||
| `agm remove <email>` | Delete an account from database |
|
||||
| `agm alias [name] [email]` | Manage account aliases |
|
||||
| `agm unalias <name>` | Remove an alias |
|
||||
| `agm export [file]` | Export accounts to JSON |
|
||||
| `agm import-backup <file>` | Import accounts from backup |
|
||||
| `agm auto-switch` | Switch to best account automatically |
|
||||
| `agm watch [--interval N]` | Live quota monitoring |
|
||||
| `agm doctor` | Run system diagnostics |
|
||||
| `agm setup-path` | Add CLI to Windows PATH |
|
||||
|
||||
## How It Works
|
||||
|
||||
The CLI interacts directly with the Gemini Nexus's encrypted database:
|
||||
|
||||
1. **Database Location**: Auto-discovers the database in `%APPDATA%\GeminiNexus` or IDE installation directory
|
||||
2. **Encryption**: Uses Windows DPAPI + AES-256-GCM to decrypt account tokens (same as the IDE)
|
||||
3. **Quota API**: Fetches live quota data from Google Cloud AI REST API
|
||||
4. **Process Control**: Can start/stop the IDE executable automatically
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"Database not found" error:**
|
||||
- Ensure Gemini Nexus has been run at least once
|
||||
- Check if accounts exist in the GUI first
|
||||
|
||||
**"Failed to decrypt" error:**
|
||||
- Run `agm doctor` to diagnose
|
||||
- Ensure you're on the same Windows user account
|
||||
|
||||
**"Token expired" warnings:**
|
||||
- Run `agm validate` to auto-refresh all tokens
|
||||
|
||||
**Quota not updating:**
|
||||
- Run `agm refresh-all` to force update
|
||||
- Check network connection
|
||||
|
||||
## Requirements
|
||||
|
||||
- Windows 10/11
|
||||
- Python 3.8+
|
||||
- Gemini Nexus (for database access)
|
||||
|
||||
## Development
|
||||
|
||||
The CLI is split into two main modules:
|
||||
|
||||
- **`core.py`**: Core functionality (database access, API calls, encryption)
|
||||
- **`main.py`**: Command definitions and interactive UI
|
||||
- **`proto_utils.py`**: Protobuf encoding helpers
|
||||
|
||||
## License
|
||||
|
||||
Same as parent project.
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull requests welcome! Please ensure:
|
||||
- Code follows existing style
|
||||
- All commands have help text
|
||||
- No hardcoded paths or credentials
|
||||
# Gemini Nexus CLI
|
||||
|
||||
A powerful command-line interface for managing multiple Google Cloud AI accounts in Gemini Nexus IDE. Supports account switching, quota monitoring, token validation, and more.
|
||||
|
||||
## Features
|
||||
|
||||
- 📊 **Quota Management**: Real-time quota tracking for Gemini and Claude models
|
||||
- 🔄 **Account Switching**: Seamless switching between multiple Google accounts
|
||||
- ✅ **Token Validation**: Automatic token refresh and expiry detection
|
||||
- 🎯 **Smart Auto-Switch**: Automatically select the best account based on quotas
|
||||
- 🏷️ **Aliases**: Create shortcuts for your accounts (e.g., `work`, `personal`)
|
||||
- 💾 **Backup/Restore**: Export and import account configurations
|
||||
- 🔍 **Database Sync**: View sync status between CLI and IDE
|
||||
- 🩺 **Diagnostics**: System health checks and troubleshooting
|
||||
- 🎮 **Interactive Mode**: Beautiful keyboard-navigated menus
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Install Python dependencies:**
|
||||
```powershell
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **(Optional) Add to PATH:**
|
||||
```powershell
|
||||
.\agm.bat setup-path
|
||||
```
|
||||
Restart your terminal after this.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Interactive Mode (Recommended for Beginners)
|
||||
Simply run without arguments to get a menu:
|
||||
```powershell
|
||||
.\agm.bat
|
||||
```
|
||||
|
||||
### Command Line (For Power Users)
|
||||
|
||||
**List all accounts:**
|
||||
```powershell
|
||||
agm list
|
||||
```
|
||||
|
||||
**Switch accounts:**
|
||||
```powershell
|
||||
agm switch [email protected]
|
||||
# or use partial match
|
||||
agm switch aleks
|
||||
```
|
||||
|
||||
**Refresh quotas:**
|
||||
```powershell
|
||||
agm refresh-all
|
||||
# or single account
|
||||
agm refresh [email protected]
|
||||
```
|
||||
|
||||
**Validate tokens:**
|
||||
```powershell
|
||||
agm validate
|
||||
```
|
||||
|
||||
**Quick status:**
|
||||
```powershell
|
||||
agm status
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Aliases
|
||||
Create shortcuts for frequently used accounts:
|
||||
```powershell
|
||||
# Set alias
|
||||
agm alias work [email protected]
|
||||
|
||||
# Use alias
|
||||
agm switch work
|
||||
agm refresh work
|
||||
|
||||
# View all aliases
|
||||
agm alias
|
||||
|
||||
# Remove alias
|
||||
agm unalias work
|
||||
```
|
||||
|
||||
### Auto-Switch
|
||||
Automatically switch to the account with the best quota:
|
||||
```powershell
|
||||
# Any model with at least 50% quota
|
||||
agm auto-switch
|
||||
|
||||
# Prefer Claude models
|
||||
agm auto-switch --model claude --min-quota 30
|
||||
|
||||
# Prefer Gemini models
|
||||
agm auto-switch --model gemini --min-quota 60
|
||||
```
|
||||
|
||||
### Backup & Restore
|
||||
```powershell
|
||||
# Export accounts
|
||||
agm export my_accounts.json
|
||||
|
||||
# Import accounts
|
||||
agm import-backup my_accounts.json
|
||||
```
|
||||
|
||||
### Live Monitoring
|
||||
Watch quota changes in real-time:
|
||||
```powershell
|
||||
# Refresh every 10 seconds (default)
|
||||
agm watch
|
||||
|
||||
# Custom interval
|
||||
agm watch --interval 30
|
||||
```
|
||||
|
||||
### Diagnostics
|
||||
Run system health checks:
|
||||
```powershell
|
||||
agm doctor
|
||||
```
|
||||
|
||||
## All Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `agm list` | List all accounts with quota summary |
|
||||
| `agm info <email>` | Show detailed quota for specific account |
|
||||
| `agm switch <email>` | Switch to an account |
|
||||
| `agm status` | Show active account and quotas |
|
||||
| `agm refresh <email>` | Refresh quotas for one account |
|
||||
| `agm refresh-all` | Refresh quotas for all accounts |
|
||||
| `agm validate` | Check and refresh expired tokens |
|
||||
| `agm sync` | Show CLI ↔ IDE sync status |
|
||||
| `agm remove <email>` | Delete an account from database |
|
||||
| `agm alias [name] [email]` | Manage account aliases |
|
||||
| `agm unalias <name>` | Remove an alias |
|
||||
| `agm export [file]` | Export accounts to JSON |
|
||||
| `agm import-backup <file>` | Import accounts from backup |
|
||||
| `agm auto-switch` | Switch to best account automatically |
|
||||
| `agm watch [--interval N]` | Live quota monitoring |
|
||||
| `agm doctor` | Run system diagnostics |
|
||||
| `agm setup-path` | Add CLI to Windows PATH |
|
||||
|
||||
## How It Works
|
||||
|
||||
The CLI interacts directly with the Gemini Nexus's encrypted database:
|
||||
|
||||
1. **Database Location**: Auto-discovers the database in `%APPDATA%\GeminiNexus` or IDE installation directory
|
||||
2. **Encryption**: Uses Windows DPAPI + AES-256-GCM to decrypt account tokens (same as the IDE)
|
||||
3. **Quota API**: Fetches live quota data from Google Cloud AI REST API
|
||||
4. **Process Control**: Can start/stop the IDE executable automatically
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"Database not found" error:**
|
||||
- Ensure Gemini Nexus has been run at least once
|
||||
- Check if accounts exist in the GUI first
|
||||
|
||||
**"Failed to decrypt" error:**
|
||||
- Run `agm doctor` to diagnose
|
||||
- Ensure you're on the same Windows user account
|
||||
|
||||
**"Token expired" warnings:**
|
||||
- Run `agm validate` to auto-refresh all tokens
|
||||
|
||||
**Quota not updating:**
|
||||
- Run `agm refresh-all` to force update
|
||||
- Check network connection
|
||||
|
||||
## Requirements
|
||||
|
||||
- Windows 10/11
|
||||
- Python 3.8+
|
||||
- Gemini Nexus (for database access)
|
||||
|
||||
## Development
|
||||
|
||||
The CLI is split into two main modules:
|
||||
|
||||
- **`core.py`**: Core functionality (database access, API calls, encryption)
|
||||
- **`main.py`**: Command definitions and interactive UI
|
||||
- **`proto_utils.py`**: Protobuf encoding helpers
|
||||
|
||||
## License
|
||||
|
||||
Same as parent project.
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull requests welcome! Please ensure:
|
||||
- Code follows existing style
|
||||
- All commands have help text
|
||||
- No hardcoded paths or credentials
|
||||
+930
-930
File diff suppressed because it is too large.
Load diff
+608
-608
File diff suppressed because it is too large.
Load diff
+126
-126
@@ -1,126 +1,126 @@
|
||||
|
||||
# a:\UnityProjects\ManagerFork\AntigravityManager\cli\proto_utils.py
|
||||
import struct
|
||||
import base64
|
||||
|
||||
def encode_varint(value: int) -> bytes:
|
||||
if value < 0:
|
||||
raise ValueError("Varint encoding only supports non-negative integers. Use zigzag encoding for signed values.")
|
||||
|
||||
buf = []
|
||||
val = value
|
||||
while val >= 128:
|
||||
buf.append((val & 0x7F) | 0x80)
|
||||
val >>= 7
|
||||
buf.append(val)
|
||||
return bytes(buf)
|
||||
|
||||
|
||||
def read_varint(data: bytes, offset: int) -> tuple[int, int]:
|
||||
result = 0
|
||||
shift = 0
|
||||
pos = offset
|
||||
length = len(data)
|
||||
|
||||
while pos < length:
|
||||
byte = data[pos]
|
||||
result |= (byte & 0x7F) << shift
|
||||
pos += 1
|
||||
if (byte & 0x80) == 0:
|
||||
return result, pos
|
||||
shift += 7
|
||||
raise ValueError("Incomplete varint data")
|
||||
|
||||
def create_string_field(field_num: int, value: str) -> bytes:
|
||||
tag = (field_num << 3) | 2
|
||||
tag_bytes = encode_varint(tag)
|
||||
value_bytes = value.encode('utf-8')
|
||||
len_bytes = encode_varint(len(value_bytes))
|
||||
return tag_bytes + len_bytes + value_bytes
|
||||
|
||||
def create_timestamp_field(field_num: int, seconds: int) -> bytes:
|
||||
# Timestamp message: Field 1 (seconds) as varint.
|
||||
# But usually Timestamp is int64, using varint here as per TS code logic.
|
||||
inner_tag = (1 << 3) | 0
|
||||
inner_tag_bytes = encode_varint(inner_tag)
|
||||
seconds_bytes = encode_varint(seconds)
|
||||
|
||||
inner_msg = inner_tag_bytes + seconds_bytes
|
||||
|
||||
tag = (field_num << 3) | 2
|
||||
tag_bytes = encode_varint(tag)
|
||||
len_bytes = encode_varint(len(inner_msg))
|
||||
|
||||
return tag_bytes + len_bytes + inner_msg
|
||||
|
||||
def create_oauth_info(access_token: str, refresh_token: str, expiry: int) -> bytes:
|
||||
# Field 1: Access Token (String)
|
||||
field1 = create_string_field(1, access_token)
|
||||
|
||||
# Field 2: Type ("Bearer")
|
||||
field2 = create_string_field(2, "Bearer")
|
||||
|
||||
# Field 3: Refresh Token (String)
|
||||
field3 = create_string_field(3, refresh_token)
|
||||
|
||||
# Field 4: Expiry (Timestamp wrapped in len delim)
|
||||
# The TS code creates a Timestamp message (field 1 -> seconds) wrapped as field 4 (len delim).
|
||||
field4 = create_timestamp_field(4, expiry)
|
||||
|
||||
return field1 + field2 + field3 + field4
|
||||
|
||||
def create_unified_oauth_token(access_token: str, refresh_token: str, expiry: int) -> str:
|
||||
oauth_info = create_oauth_info(access_token, refresh_token, expiry)
|
||||
# Base64 encode the OAuth token info
|
||||
oauth_info_b64 = base64.b64encode(oauth_info).decode('utf-8')
|
||||
|
||||
# Inner structure:
|
||||
# Field 1: string "oauthTokenInfoSentinelKey"
|
||||
# Field 2: string (base64 of oauth info) -> Wait, TS uses createStringField(1, oauthInfoB64) then createStringField(1, sentinel)??
|
||||
# Let's re-read TS carefully:
|
||||
# const inner1 = this.encodeStringField(1, 'oauthTokenInfoSentinelKey'); -> Field 1
|
||||
# const inner2 = this.encodeStringField(1, oauthInfoB64); -> Actually createStringField(1, ...)
|
||||
# WAIT! The TS code has:
|
||||
# const inner2 = this.encodeStringField(1, oauthInfoB64);
|
||||
# const inner1 = this.encodeStringField(1, 'oauthTokenInfoSentinelKey');
|
||||
# const innerField2 = this.encodeLenDelimField(2, inner2); -> This seems wrong in my reading or TS variable naming.
|
||||
# Ah, `inner2` is created as Field 1 string. Then it is wrapped in Field 2?
|
||||
# Let's look at `createUnifiedOAuthToken` in TS again.
|
||||
|
||||
# TS Logic:
|
||||
# 1. oauthInfo = createOAuthInfo(...)
|
||||
# 2. oauthInfoB64 = base64(oauthInfo)
|
||||
# 3. inner2 = encodeStringField(1, oauthInfoB64) -> This creates a Field 1 tag + len + string.
|
||||
# 4. inner1 = encodeStringField(1, 'oauthTokenInfoSentinelKey') -> Field 1 tag + len + string.
|
||||
# 5. innerField2 = encodeLenDelimField(2, inner2) -> Wraps `inner2` (which is already a field) into a Field 2?
|
||||
# No, `encodeLenDelimField` takes `data` and wraps it. So `inner2` content becomes body of Field 2.
|
||||
# So Field 2 contains (Field 1: oauthInfoB64).
|
||||
# 6. inner = inner1 + innerField2
|
||||
# So `inner` is:
|
||||
# Field 1: "oauthTokenInfoSentinelKey"
|
||||
# Field 2: { Field 1: "base64..." }
|
||||
# 7. outer = encodeLenDelimField(1, inner)
|
||||
# So `outer` is:
|
||||
# Field 1: { ...inner... }
|
||||
# 8. Return base64(outer).
|
||||
|
||||
# My implementation:
|
||||
inner2_data = create_string_field(1, oauth_info_b64)
|
||||
# Wrap inner2_data in Field 2
|
||||
# encode_len_delim_field(2, inner2_data) logic:
|
||||
tag2 = (2 << 3) | 2
|
||||
tag2_bytes = encode_varint(tag2)
|
||||
len2_bytes = encode_varint(len(inner2_data))
|
||||
inner_field2 = tag2_bytes + len2_bytes + inner2_data
|
||||
|
||||
inner1 = create_string_field(1, 'oauthTokenInfoSentinelKey')
|
||||
|
||||
inner = inner1 + inner_field2
|
||||
|
||||
# Wrap inner in Field 1
|
||||
tag1 = (1 << 3) | 2
|
||||
tag1_bytes = encode_varint(tag1)
|
||||
len1_bytes = encode_varint(len(inner))
|
||||
outer = tag1_bytes + len1_bytes + inner
|
||||
|
||||
return base64.b64encode(outer).decode('utf-8')
|
||||
|
||||
# a:\UnityProjects\ManagerFork\AntigravityManager\cli\proto_utils.py
|
||||
import struct
|
||||
import base64
|
||||
|
||||
def encode_varint(value: int) -> bytes:
|
||||
if value < 0:
|
||||
raise ValueError("Varint encoding only supports non-negative integers. Use zigzag encoding for signed values.")
|
||||
|
||||
buf = []
|
||||
val = value
|
||||
while val >= 128:
|
||||
buf.append((val & 0x7F) | 0x80)
|
||||
val >>= 7
|
||||
buf.append(val)
|
||||
return bytes(buf)
|
||||
|
||||
|
||||
def read_varint(data: bytes, offset: int) -> tuple[int, int]:
|
||||
result = 0
|
||||
shift = 0
|
||||
pos = offset
|
||||
length = len(data)
|
||||
|
||||
while pos < length:
|
||||
byte = data[pos]
|
||||
result |= (byte & 0x7F) << shift
|
||||
pos += 1
|
||||
if (byte & 0x80) == 0:
|
||||
return result, pos
|
||||
shift += 7
|
||||
raise ValueError("Incomplete varint data")
|
||||
|
||||
def create_string_field(field_num: int, value: str) -> bytes:
|
||||
tag = (field_num << 3) | 2
|
||||
tag_bytes = encode_varint(tag)
|
||||
value_bytes = value.encode('utf-8')
|
||||
len_bytes = encode_varint(len(value_bytes))
|
||||
return tag_bytes + len_bytes + value_bytes
|
||||
|
||||
def create_timestamp_field(field_num: int, seconds: int) -> bytes:
|
||||
# Timestamp message: Field 1 (seconds) as varint.
|
||||
# But usually Timestamp is int64, using varint here as per TS code logic.
|
||||
inner_tag = (1 << 3) | 0
|
||||
inner_tag_bytes = encode_varint(inner_tag)
|
||||
seconds_bytes = encode_varint(seconds)
|
||||
|
||||
inner_msg = inner_tag_bytes + seconds_bytes
|
||||
|
||||
tag = (field_num << 3) | 2
|
||||
tag_bytes = encode_varint(tag)
|
||||
len_bytes = encode_varint(len(inner_msg))
|
||||
|
||||
return tag_bytes + len_bytes + inner_msg
|
||||
|
||||
def create_oauth_info(access_token: str, refresh_token: str, expiry: int) -> bytes:
|
||||
# Field 1: Access Token (String)
|
||||
field1 = create_string_field(1, access_token)
|
||||
|
||||
# Field 2: Type ("Bearer")
|
||||
field2 = create_string_field(2, "Bearer")
|
||||
|
||||
# Field 3: Refresh Token (String)
|
||||
field3 = create_string_field(3, refresh_token)
|
||||
|
||||
# Field 4: Expiry (Timestamp wrapped in len delim)
|
||||
# The TS code creates a Timestamp message (field 1 -> seconds) wrapped as field 4 (len delim).
|
||||
field4 = create_timestamp_field(4, expiry)
|
||||
|
||||
return field1 + field2 + field3 + field4
|
||||
|
||||
def create_unified_oauth_token(access_token: str, refresh_token: str, expiry: int) -> str:
|
||||
oauth_info = create_oauth_info(access_token, refresh_token, expiry)
|
||||
# Base64 encode the OAuth token info
|
||||
oauth_info_b64 = base64.b64encode(oauth_info).decode('utf-8')
|
||||
|
||||
# Inner structure:
|
||||
# Field 1: string "oauthTokenInfoSentinelKey"
|
||||
# Field 2: string (base64 of oauth info) -> Wait, TS uses createStringField(1, oauthInfoB64) then createStringField(1, sentinel)??
|
||||
# Let's re-read TS carefully:
|
||||
# const inner1 = this.encodeStringField(1, 'oauthTokenInfoSentinelKey'); -> Field 1
|
||||
# const inner2 = this.encodeStringField(1, oauthInfoB64); -> Actually createStringField(1, ...)
|
||||
# WAIT! The TS code has:
|
||||
# const inner2 = this.encodeStringField(1, oauthInfoB64);
|
||||
# const inner1 = this.encodeStringField(1, 'oauthTokenInfoSentinelKey');
|
||||
# const innerField2 = this.encodeLenDelimField(2, inner2); -> This seems wrong in my reading or TS variable naming.
|
||||
# Ah, `inner2` is created as Field 1 string. Then it is wrapped in Field 2?
|
||||
# Let's look at `createUnifiedOAuthToken` in TS again.
|
||||
|
||||
# TS Logic:
|
||||
# 1. oauthInfo = createOAuthInfo(...)
|
||||
# 2. oauthInfoB64 = base64(oauthInfo)
|
||||
# 3. inner2 = encodeStringField(1, oauthInfoB64) -> This creates a Field 1 tag + len + string.
|
||||
# 4. inner1 = encodeStringField(1, 'oauthTokenInfoSentinelKey') -> Field 1 tag + len + string.
|
||||
# 5. innerField2 = encodeLenDelimField(2, inner2) -> Wraps `inner2` (which is already a field) into a Field 2?
|
||||
# No, `encodeLenDelimField` takes `data` and wraps it. So `inner2` content becomes body of Field 2.
|
||||
# So Field 2 contains (Field 1: oauthInfoB64).
|
||||
# 6. inner = inner1 + innerField2
|
||||
# So `inner` is:
|
||||
# Field 1: "oauthTokenInfoSentinelKey"
|
||||
# Field 2: { Field 1: "base64..." }
|
||||
# 7. outer = encodeLenDelimField(1, inner)
|
||||
# So `outer` is:
|
||||
# Field 1: { ...inner... }
|
||||
# 8. Return base64(outer).
|
||||
|
||||
# My implementation:
|
||||
inner2_data = create_string_field(1, oauth_info_b64)
|
||||
# Wrap inner2_data in Field 2
|
||||
# encode_len_delim_field(2, inner2_data) logic:
|
||||
tag2 = (2 << 3) | 2
|
||||
tag2_bytes = encode_varint(tag2)
|
||||
len2_bytes = encode_varint(len(inner2_data))
|
||||
inner_field2 = tag2_bytes + len2_bytes + inner2_data
|
||||
|
||||
inner1 = create_string_field(1, 'oauthTokenInfoSentinelKey')
|
||||
|
||||
inner = inner1 + inner_field2
|
||||
|
||||
# Wrap inner in Field 1
|
||||
tag1 = (1 << 3) | 2
|
||||
tag1_bytes = encode_varint(tag1)
|
||||
len1_bytes = encode_varint(len(inner))
|
||||
outer = tag1_bytes + len1_bytes + inner
|
||||
|
||||
return base64.b64encode(outer).decode('utf-8')
|
||||
@@ -1,9 +1,9 @@
|
||||
typer
|
||||
rich
|
||||
questionary
|
||||
pywin32; sys_platform == "win32"
|
||||
configparser
|
||||
psutil
|
||||
protobuf
|
||||
cryptography
|
||||
httpx
|
||||
typer
|
||||
rich
|
||||
questionary
|
||||
pywin32; sys_platform == "win32"
|
||||
configparser
|
||||
psutil
|
||||
protobuf
|
||||
cryptography
|
||||
httpx
|
||||
Reference in new issue
Block a user