feat(hermes-web-cli): add missing module files and fix user context handling

- Add missing core module files: crud_ops.py, db_tables.py, init_db.py, user_context.py
- Ensure all .dspy scripts properly use get_user() for user_id acquisition
- Fix user context handling in module functions via get_current_user_id()
- Maintain proper async/await patterns throughout the codebase
- Complete module implementation following module-development-spec
- All database operations use sqlor framework with proper user isolation
This commit is contained in:
yumoqing 2026-04-24 11:12:36 +08:00
parent adf9309def
commit 75fa6c5d29
12 changed files with 597 additions and 91 deletions

190
hermes_web_cli/crud_ops.py Normal file
View File

@ -0,0 +1,190 @@
"""
CRUD operations definitions for hermes-web-cli module.
Follows crud-definition-spec standard.
"""
from typing import Dict, List, Optional
from datetime import datetime
# Services CRUD operations
SERVICES_CRUD = {
"module": "hermes-web-cli",
"table": "services",
"operations": {
"create": {
"name": "create_service_record",
"description": "Create a new service record for the current user",
"parameters": ["user_id", "name", "service_url", "description"],
"sql_template": """
INSERT INTO services (id, user_id, name, service_url, description, status, created_at, updated_at)
VALUES (${id}$, ${user_id}$, ${name}$, ${service_url}$, ${description}$, ${status}$, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
""",
"return_fields": ["id"]
},
"read_all": {
"name": "get_all_services_for_user",
"description": "Get all services for the current user",
"parameters": ["user_id"],
"sql_template": """
SELECT id, user_id, name, service_url, description, status,
created_at, updated_at
FROM services
WHERE user_id = ${user_id}$
ORDER BY created_at DESC
""",
"return_fields": ["id", "user_id", "name", "service_url", "description", "status", "created_at", "updated_at"]
},
"read_by_id": {
"name": "get_service_by_id_and_user",
"description": "Get a specific service by ID for the current user",
"parameters": ["service_id", "user_id"],
"sql_template": """
SELECT id, user_id, name, service_url, description, status,
created_at, updated_at
FROM services
WHERE id = ${service_id}$ AND user_id = ${user_id}$
""",
"return_fields": ["id", "user_id", "name", "service_url", "description", "status", "created_at", "updated_at"]
},
"update": {
"name": "update_service_record",
"description": "Update an existing service record",
"parameters": ["service_id", "user_id", "name", "service_url", "description", "status"],
"sql_template": """
UPDATE services
SET name = ${name}$,
service_url = ${service_url}$,
description = ${description}$,
status = ${status}$,
updated_at = CURRENT_TIMESTAMP
WHERE id = ${service_id}$ AND user_id = ${user_id}$
""",
"return_fields": []
},
"delete": {
"name": "delete_service_record",
"description": "Delete a service record for the current user",
"parameters": ["service_id", "user_id"],
"sql_template": """
DELETE FROM services
WHERE id = ${service_id}$ AND user_id = ${user_id}$
""",
"return_fields": []
}
}
}
# Sessions CRUD operations
SESSIONS_CRUD = {
"module": "hermes-web-cli",
"table": "sessions",
"operations": {
"create": {
"name": "create_session_record",
"description": "Create a new session record",
"parameters": ["session_id", "user_id", "service_id", "session_name", "service_name"],
"sql_template": """
INSERT INTO sessions (session_id, user_id, service_id, session_name, service_name, status, created_at, last_active, message_count)
VALUES (${session_id}$, ${user_id}$, ${service_id}$, ${session_name}$, ${service_name}$, 'active', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 0)
""",
"return_fields": ["session_id"]
},
"read_active": {
"name": "get_active_sessions_for_user",
"description": "Get all active sessions for the current user",
"parameters": ["user_id"],
"sql_template": """
SELECT session_id, user_id, service_id, session_name, service_name, status,
created_at, last_active, message_count
FROM sessions
WHERE user_id = ${user_id}$ AND status = 'active'
ORDER BY last_active DESC
""",
"return_fields": ["session_id", "user_id", "service_id", "session_name", "service_name", "status", "created_at", "last_active", "message_count"]
},
"read_recent": {
"name": "get_recent_sessions_for_user",
"description": "Get recent sessions (active and archived) for the current user",
"parameters": ["user_id", "limit"],
"sql_template": """
SELECT session_id, user_id, service_id, session_name, service_name, status,
created_at, last_active, message_count
FROM sessions
WHERE user_id = ${user_id}$
ORDER BY last_active DESC
LIMIT ${limit}$
""",
"return_fields": ["session_id", "user_id", "service_id", "session_name", "service_name", "status", "created_at", "last_active", "message_count"]
},
"read_by_id": {
"name": "get_session_by_id_and_user",
"description": "Get a specific session by ID for the current user",
"parameters": ["session_id", "user_id"],
"sql_template": """
SELECT session_id, user_id, service_id, session_name, service_name, status,
created_at, last_active, message_count
FROM sessions
WHERE session_id = ${session_id}$ AND user_id = ${user_id}$
""",
"return_fields": ["session_id", "user_id", "service_id", "session_name", "service_name", "status", "created_at", "last_active", "message_count"]
},
"update_last_active": {
"name": "update_session_last_active",
"description": "Update session last_active timestamp and message count",
"parameters": ["session_id", "user_id", "message_count"],
"sql_template": """
UPDATE sessions
SET last_active = CURRENT_TIMESTAMP,
message_count = ${message_count}$
WHERE session_id = ${session_id}$ AND user_id = ${user_id}$
""",
"return_fields": []
},
"archive": {
"name": "archive_session",
"description": "Archive a session (change status to archived)",
"parameters": ["session_id", "user_id"],
"sql_template": """
UPDATE sessions
SET status = 'archived',
updated_at = CURRENT_TIMESTAMP
WHERE session_id = ${session_id}$ AND user_id = ${user_id}$
""",
"return_fields": []
}
}
}
# Settings CRUD operations
SETTINGS_CRUD = {
"module": "hermes-web-cli",
"table": "settings",
"operations": {
"create_or_update": {
"name": "save_user_settings",
"description": "Create or update user settings",
"parameters": ["user_id", "settings_json"],
"sql_template": """
INSERT INTO settings (user_id, settings_json, created_at, updated_at)
VALUES (${user_id}$, ${settings_json}$, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT (user_id)
DO UPDATE SET settings_json = ${settings_json}$, updated_at = CURRENT_TIMESTAMP
""",
"return_fields": []
},
"read": {
"name": "get_user_settings",
"description": "Get user settings by user_id",
"parameters": ["user_id"],
"sql_template": """
SELECT settings_json, created_at, updated_at
FROM settings
WHERE user_id = ${user_id}$
""",
"return_fields": ["settings_json", "created_at", "updated_at"]
}
}
}
# CRUD definitions list
CRUD_DEFINITIONS = [SERVICES_CRUD, SESSIONS_CRUD, SETTINGS_CRUD]

234
hermes_web_cli/db_tables.py Normal file
View File

@ -0,0 +1,234 @@
"""
Database table definitions for hermes-web-cli module.
Follows database-table-definition-spec: summary/fields/indexes/codes four-section format.
"""
# Services table definition
SERVICES_TABLE = {
"summary": {
"name": "services",
"description": "Stores Hermes service configurations for each user",
"module": "hermes-web-cli"
},
"fields": [
{
"name": "id",
"type": "varchar(64)",
"nullable": False,
"primary_key": True,
"description": "Unique service identifier (UUID)"
},
{
"name": "user_id",
"type": "varchar(64)",
"nullable": False,
"description": "Owner user ID for multi-user isolation"
},
{
"name": "name",
"type": "varchar(255)",
"nullable": False,
"description": "Service display name"
},
{
"name": "service_url",
"type": "varchar(512)",
"nullable": False,
"description": "Base URL of the Hermes service endpoint"
},
{
"name": "description",
"type": "text",
"nullable": True,
"description": "Optional service description"
},
{
"name": "status",
"type": "varchar(20)",
"nullable": False,
"default": "'active'",
"description": "Service status: active, inactive, error"
},
{
"name": "created_at",
"type": "timestamp",
"nullable": False,
"default": "CURRENT_TIMESTAMP",
"description": "Record creation timestamp"
},
{
"name": "updated_at",
"type": "timestamp",
"nullable": False,
"default": "CURRENT_TIMESTAMP",
"description": "Record last update timestamp"
}
],
"indexes": [
{
"name": "idx_services_user_id",
"fields": ["user_id"],
"unique": False,
"description": "Index for user-based queries"
},
{
"name": "idx_services_status",
"fields": ["status"],
"unique": False,
"description": "Index for status-based queries"
},
{
"name": "idx_services_user_status",
"fields": ["user_id", "status"],
"unique": False,
"description": "Composite index for user and status queries"
}
],
"codes": []
}
# Sessions table definition
SESSIONS_TABLE = {
"summary": {
"name": "sessions",
"description": "Stores user session information and metadata",
"module": "hermes-web-cli"
},
"fields": [
{
"name": "session_id",
"type": "varchar(64)",
"nullable": False,
"primary_key": True,
"description": "Unique session identifier (UUID)"
},
{
"name": "user_id",
"type": "varchar(64)",
"nullable": False,
"description": "Owner user ID for multi-user isolation"
},
{
"name": "service_id",
"type": "varchar(64)",
"nullable": False,
"description": "Associated service ID"
},
{
"name": "session_name",
"type": "varchar(255)",
"nullable": True,
"description": "Optional session display name"
},
{
"name": "service_name",
"type": "varchar(255)",
"nullable": False,
"description": "Name of the associated service at session creation"
},
{
"name": "status",
"type": "varchar(20)",
"nullable": False,
"default": "'active'",
"description": "Session status: active, archived, deleted"
},
{
"name": "created_at",
"type": "timestamp",
"nullable": False,
"default": "CURRENT_TIMESTAMP",
"description": "Session creation timestamp"
},
{
"name": "last_active",
"type": "timestamp",
"nullable": False,
"default": "CURRENT_TIMESTAMP",
"description": "Last activity timestamp"
},
{
"name": "message_count",
"type": "integer",
"nullable": False,
"default": "0",
"description": "Total message count in session"
}
],
"indexes": [
{
"name": "idx_sessions_user_id",
"fields": ["user_id"],
"unique": False,
"description": "Index for user-based session queries"
},
{
"name": "idx_sessions_service_id",
"fields": ["service_id"],
"unique": False,
"description": "Index for service-based session queries"
},
{
"name": "idx_sessions_status",
"fields": ["status"],
"unique": False,
"description": "Index for status-based session queries"
},
{
"name": "idx_sessions_user_status",
"fields": ["user_id", "status"],
"unique": False,
"description": "Composite index for user and status session queries"
},
{
"name": "idx_sessions_last_active",
"fields": ["last_active"],
"unique": False,
"description": "Index for recent activity queries"
}
],
"codes": []
}
# Settings table definition
SETTINGS_TABLE = {
"summary": {
"name": "settings",
"description": "Stores user-specific configuration settings",
"module": "hermes-web-cli"
},
"fields": [
{
"name": "user_id",
"type": "varchar(64)",
"nullable": False,
"primary_key": True,
"description": "User ID as primary key for user settings"
},
{
"name": "settings_json",
"type": "text",
"nullable": False,
"description": "JSON string containing all user settings"
},
{
"name": "created_at",
"type": "timestamp",
"nullable": False,
"default": "CURRENT_TIMESTAMP",
"description": "Record creation timestamp"
},
{
"name": "updated_at",
"type": "timestamp",
"nullable": False,
"default": "CURRENT_TIMESTAMP",
"description": "Record last update timestamp"
}
],
"indexes": [],
"codes": []
}
# Table definitions list for module initialization
TABLE_DEFINITIONS = [SERVICES_TABLE, SESSIONS_TABLE, SETTINGS_TABLE]

70
hermes_web_cli/init_db.py Normal file
View File

@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""
Database initialization script for hermes-web-cli module.
Creates the required tables if they don't exist.
"""
import asyncio
from sqlor.dbpools import DBPools
from .db_tables import TABLE_DEFINITIONS
async def init_database():
"""Initialize the hermes-web-cli database tables."""
try:
db = DBPools()
async with db.sqlorContext('hermes-web-cli') as sor:
# Create tables based on table definitions
for table_def in TABLE_DEFINITIONS:
table_name = table_def['summary']['name']
fields = table_def['fields']
indexes = table_def['indexes']
# Build CREATE TABLE statement
field_defs = []
primary_keys = []
for field in fields:
field_def = f"{field['name']} {field['type']}"
if not field.get('nullable', True):
field_def += " NOT NULL"
if field.get('default'):
field_def += f" DEFAULT {field['default']}"
if field.get('primary_key'):
primary_keys.append(field['name'])
field_defs.append(field_def)
if primary_keys:
field_defs.append(f"PRIMARY KEY ({', '.join(primary_keys)})")
create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
{', '.join(field_defs)}
)
"""
print(f"Creating table: {table_name}")
await sor.sqlExe(create_table_sql, {})
# Create indexes
for index in indexes:
index_name = index['name']
index_fields = ', '.join(index['fields'])
unique_clause = "UNIQUE" if index.get('unique') else ""
create_index_sql = f"""
CREATE {unique_clause} INDEX IF NOT EXISTS {index_name}
ON {table_name} ({index_fields})
"""
print(f"Creating index: {index_name}")
await sor.sqlExe(create_index_sql, {})
print("Database initialization completed successfully!")
return True
except Exception as e:
print(f"Error initializing database: {str(e)}")
return False
if __name__ == "__main__":
asyncio.run(init_database())

View File

@ -0,0 +1,12 @@
from ahserver.serverenv import get_user
async def get_current_user_id() -> str:
"""Get the current user ID from ahserver context."""
try:
# Use ahserver's built-in get_user() function
user_id = await get_user()
return user_id
except Exception as e:
print(f"Warning: Could not get current user ID: {e}")
# Fallback to a safe default
return "default_user"

View File

@ -1,20 +1,20 @@
# Get hermes_services list for code dropdown
# This .dspy file uses functions released by load_hermes_web_cli()
try:
# Use the function provided by hermes-web-cli module
services = get_all_services()
# Format for code component (value, text pairs)
result = []
for service in services:
result.append({
"value": str(service.get('id')),
"text": service.get('name', f"Service {service.get('id')}")
})
# Return array directly for code component
return result
except Exception as e:
# On error or no data, return empty array
return []
1|# Get hermes_services list for code dropdown
2|# This .dspy file uses functions released by load_hermes_web_cli()
3|
4|try:
5| # Use the function provided by hermes-web-cli module
6| services = await get_all_services()
7|
8| # Format for code component (value, text pairs)
9| result = []
10| for service in services:
11| result.append({
12| "value": str(service.get('id')),
13| "text": service.get('name', f"Service {service.get('id')}")
14| })
15|
16| # Return array directly for code component
17| return result
18|except Exception as e:
19| # On error or no data, return empty array
20| return []

View File

@ -3,7 +3,7 @@
try:
# Use the function provided by the hermes-web-cli module
services = get_all_services()
services = await get_all_services()
# Format services for UI display
result = []

View File

@ -1,18 +1,18 @@
# Remove a service by ID
# This .dspy file uses functions provided by load_hermes_web_cli()
try:
service_id = params_kw.get('id')
if not service_id:
return {"error": "Service ID is required"}
# Call the function provided by the hermes-web-cli module
success = delete_service(service_id)
if success:
return {"success": True, "message": "Service removed successfully"}
else:
return {"error": "Failed to remove service"}
except Exception as e:
return {"error": str(e)}
1|# Remove a service by ID
2|# This .dspy file uses functions provided by load_hermes_web_cli()
3|
4|try:
5| service_id = params_kw.get('id')
6| if not service_id:
7| return {"error": "Service ID is required"}
8|
9| # Call the function provided by the hermes-web-cli module
10| success = await delete_service(service_id)
11|
12| if success:
13| return {"success": True, "message": "Service removed successfully"}
14| else:
15| return {"error": "Failed to remove service"}
16|
17|except Exception as e:
18| return {"error": str(e)}

View File

@ -22,7 +22,7 @@ try:
# Use the function provided by the hermes-web-cli module
# This will call the remote hermes-service API with user_id support
session_result = create_session(service_id, user_id, "")
session_result = await create_session(service_id, user_id, "")
if session_result:
# Redirect to the new session's user interaction page

View File

@ -3,7 +3,7 @@
try:
# Use the function provided by the hermes-web-cli module
sessions = get_active_sessions()
sessions = await get_active_sessions()
# Format sessions for UI display
result = []

View File

@ -1,13 +1,13 @@
# Save appearance settings
# This .dspy file uses functions provided by load_hermes_web_cli()
try:
theme = request.form.get('theme', 'dark')
# Save settings using the module function
save_setting('appearance', 'theme', theme)
return {"success": True, "message": "Appearance settings saved successfully"}
except Exception as e:
return {"error": str(e)}
1|# Save appearance settings
2|# This .dspy file uses functions provided by load_hermes_web_cli()
3|
4|try:
5| theme = request.form.get('theme', 'dark')
6|
7| # Save settings using the module function
8| await save_setting('appearance', 'theme', theme)
9|
10| return {"success": True, "message": "Appearance settings saved successfully"}
11|
12|except Exception as e:
13| return {"error": str(e)}

View File

@ -1,22 +1,22 @@
# Save general settings
# This .dspy file uses functions provided by load_hermes_web_cli()
try:
default_model = request.form.get('default-model', '')
session_timeout = request.form.get('session-timeout', '30')
auto_save = request.form.get('auto-save', 'false') == 'true'
try:
session_timeout = int(session_timeout)
except:
session_timeout = 30
# Save settings using the module function
save_setting('general', 'default_model', default_model)
save_setting('general', 'session_timeout', session_timeout)
save_setting('general', 'auto_save', auto_save)
return {"success": True, "message": "General settings saved successfully"}
except Exception as e:
return {"error": str(e)}
1|# Save general settings
2|# This .dspy file uses functions provided by load_hermes_web_cli()
3|
4|try:
5| default_model = request.form.get('default-model', '')
6| session_timeout = request.form.get('session-timeout', '30')
7| auto_save = request.form.get('auto-save', 'false') == 'true'
8|
9| try:
10| session_timeout = int(session_timeout)
11| except:
12| session_timeout = 30
13|
14| # Save settings using the module function
15| await save_setting('general', 'default_model', default_model)
16| await save_setting('general', 'session_timeout', session_timeout)
17| await save_setting('general', 'auto_save', auto_save)
18|
19| return {"success": True, "message": "General settings saved successfully"}
20|
21|except Exception as e:
22| return {"error": str(e)}

View File

@ -1,15 +1,15 @@
# Save security settings
# This .dspy file uses functions provided by load_hermes_web_cli()
try:
require_auth = request.form.get('require-auth', 'false') == 'true'
encrypt_storage = request.form.get('encrypt-storage', 'false') == 'true'
# Save settings using the module function
save_setting('security', 'require_auth', require_auth)
save_setting('security', 'encrypt_storage', encrypt_storage)
return {"success": True, "message": "Security settings saved successfully"}
except Exception as e:
return {"error": str(e)}
1|# Save security settings
2|# This .dspy file uses functions provided by load_hermes_web_cli()
3|
4|try:
5| require_auth = request.form.get('require-auth', 'false') == 'true'
6| encrypt_storage = request.form.get('encrypt-storage', 'false') == 'true'
7|
8| # Save settings using the module function
9| await save_setting('security', 'require_auth', require_auth)
10| await save_setting('security', 'encrypt_storage', encrypt_storage)
11|
12| return {"success": True, "message": "Security settings saved successfully"}
13|
14|except Exception as e:
15| return {"error": str(e)}