commandchat 0.0.13__tar.gz → 0.0.14__tar.gz

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.
Files changed (33) hide show
  1. {commandchat-0.0.13 → commandchat-0.0.14}/PKG-INFO +1 -1
  2. {commandchat-0.0.13 → commandchat-0.0.14}/commandchat.egg-info/PKG-INFO +1 -1
  3. {commandchat-0.0.13 → commandchat-0.0.14}/commandchat.egg-info/SOURCES.txt +9 -0
  4. {commandchat-0.0.13 → commandchat-0.0.14}/occ/CommandChat.py +12 -1
  5. commandchat-0.0.14/occ/command/__main__.py +43 -0
  6. commandchat-0.0.14/occ/command/commands/__init__.py +2 -0
  7. commandchat-0.0.13/occ/command/__main__.py → commandchat-0.0.14/occ/command/commands/chat.py +36 -68
  8. commandchat-0.0.14/occ/command/commands/image.py +30 -0
  9. commandchat-0.0.14/occ/command/commands/profile.py +112 -0
  10. commandchat-0.0.14/occ/command/commands/prompt.py +226 -0
  11. commandchat-0.0.14/occ/command/interactive/__init__.py +2 -0
  12. commandchat-0.0.14/occ/command/interactive/profile_menu.py +91 -0
  13. commandchat-0.0.14/occ/command/interactive/prompt_menu.py +176 -0
  14. {commandchat-0.0.13 → commandchat-0.0.14}/occ/commons/config.py +13 -0
  15. commandchat-0.0.14/occ/commons/prompts.py +146 -0
  16. {commandchat-0.0.13 → commandchat-0.0.14}/occ/configuration/profile_config.py +34 -0
  17. {commandchat-0.0.13 → commandchat-0.0.14}/pyproject.toml +1 -1
  18. {commandchat-0.0.13 → commandchat-0.0.14}/LICENSE +0 -0
  19. {commandchat-0.0.13 → commandchat-0.0.14}/README.md +0 -0
  20. {commandchat-0.0.13 → commandchat-0.0.14}/commandchat.egg-info/dependency_links.txt +0 -0
  21. {commandchat-0.0.13 → commandchat-0.0.14}/commandchat.egg-info/entry_points.txt +0 -0
  22. {commandchat-0.0.13 → commandchat-0.0.14}/commandchat.egg-info/requires.txt +0 -0
  23. {commandchat-0.0.13 → commandchat-0.0.14}/commandchat.egg-info/top_level.txt +0 -0
  24. {commandchat-0.0.13 → commandchat-0.0.14}/occ/ConvertLogToMarkDown.py +0 -0
  25. {commandchat-0.0.13 → commandchat-0.0.14}/occ/__init__.py +0 -0
  26. {commandchat-0.0.13 → commandchat-0.0.14}/occ/command/__init__.py +0 -0
  27. {commandchat-0.0.13 → commandchat-0.0.14}/occ/commons/__init__.py +0 -0
  28. {commandchat-0.0.13 → commandchat-0.0.14}/occ/configuration/__init__.py +0 -0
  29. {commandchat-0.0.13 → commandchat-0.0.14}/occ/utils/CommonUtil.py +0 -0
  30. {commandchat-0.0.13 → commandchat-0.0.14}/occ/utils/__init__.py +0 -0
  31. {commandchat-0.0.13 → commandchat-0.0.14}/occ/utils/logger.py +0 -0
  32. {commandchat-0.0.13 → commandchat-0.0.14}/setup.cfg +0 -0
  33. {commandchat-0.0.13 → commandchat-0.0.14}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commandchat
3
- Version: 0.0.13
3
+ Version: 0.0.14
4
4
  Summary: use command to chat with openai models
5
5
  Home-page: https://github.com/
6
6
  Author: xoto
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commandchat
3
- Version: 0.0.13
3
+ Version: 0.0.14
4
4
  Summary: use command to chat with openai models
5
5
  Home-page: https://github.com/
6
6
  Author: xoto
@@ -13,8 +13,17 @@ occ/ConvertLogToMarkDown.py
13
13
  occ/__init__.py
14
14
  occ/command/__init__.py
15
15
  occ/command/__main__.py
16
+ occ/command/commands/__init__.py
17
+ occ/command/commands/chat.py
18
+ occ/command/commands/image.py
19
+ occ/command/commands/profile.py
20
+ occ/command/commands/prompt.py
21
+ occ/command/interactive/__init__.py
22
+ occ/command/interactive/profile_menu.py
23
+ occ/command/interactive/prompt_menu.py
16
24
  occ/commons/__init__.py
17
25
  occ/commons/config.py
26
+ occ/commons/prompts.py
18
27
  occ/configuration/__init__.py
19
28
  occ/configuration/profile_config.py
20
29
  occ/utils/CommonUtil.py
@@ -58,7 +58,7 @@ class CommandChat:
58
58
  partial_text = []
59
59
  role = None
60
60
 
61
- def __init__(self, profile=None, chat_log_id=None, model=None):
61
+ def __init__(self, profile=None, chat_log_id=None, model=None, system_message=None):
62
62
  now = time.strftime("%Y%m%d", time.localtime())
63
63
  self.profile = profile or DEFAULT_PROFILE
64
64
  self.api_server_type = get_env(self.profile, "api_server_type")
@@ -92,6 +92,17 @@ class CommandChat:
92
92
  except json.JSONDecodeError:
93
93
  continue
94
94
 
95
+ # Add system message if provided
96
+ if system_message:
97
+ # Check if there's already a system message at the beginning
98
+ has_system = len(self.messages) > 0 and self.messages[0].get('role') == 'system'
99
+ if has_system:
100
+ # Replace existing system message
101
+ self.messages[0] = {"role": "system", "content": system_message}
102
+ else:
103
+ # Insert system message at the beginning
104
+ self.messages.insert(0, {"role": "system", "content": system_message})
105
+
95
106
  # Initialize client based on API server type
96
107
  if self.api_server_type == "azure-openai":
97
108
  # For Azure OpenAI, we'll initialize client per model in chat method
@@ -0,0 +1,43 @@
1
+ """
2
+ OpenAI CommandChat - Command Line Interface
3
+
4
+ Main entry point for the occ CLI tool.
5
+ """
6
+
7
+ from __future__ import absolute_import
8
+
9
+ import importlib.metadata
10
+ import click
11
+
12
+ # Import command modules
13
+ from occ.command.commands.prompt import prompt
14
+ from occ.command.commands.profile import configure
15
+ from occ.command.commands.chat import chat
16
+ from occ.command.commands.image import image
17
+
18
+
19
+ VERSION = importlib.metadata.version("commandchat")
20
+
21
+
22
+ @click.group()
23
+ @click.version_option(version=VERSION, prog_name='openai-commandchat')
24
+ def cli():
25
+ """OpenAI CommandChat - AI-powered command-line chat tool"""
26
+ pass
27
+
28
+
29
+ # Register commands
30
+ cli.add_command(configure)
31
+ cli.add_command(chat)
32
+ cli.add_command(prompt)
33
+ cli.add_command(image)
34
+
35
+
36
+ def main():
37
+ """Main entry point"""
38
+ cli()
39
+
40
+
41
+ if __name__ == '__main__':
42
+ main()
43
+
@@ -0,0 +1,2 @@
1
+ """Command modules for occ CLI"""
2
+
@@ -1,50 +1,26 @@
1
- from __future__ import absolute_import
1
+ """Chat command"""
2
2
 
3
- import importlib.metadata
4
3
  import sys
5
-
6
4
  import click
7
5
  from prompt_toolkit import PromptSession, print_formatted_text, HTML
8
6
  from prompt_toolkit.cursor_shapes import ModalCursorShapeConfig
9
7
  from prompt_toolkit.styles import style_from_pygments_cls
10
- from pygments.lexers.markup import MarkdownLexer
11
8
  from pygments.styles.tango import TangoStyle
12
9
 
13
10
  import occ.utils.logger as logger
14
11
  from occ.CommandChat import CommandChat
15
12
 
16
13
 
17
- VERSION = importlib.metadata.version("commandchat")
18
-
19
-
20
- @click.group()
21
- @click.version_option(version=VERSION, prog_name='openai-commandchat')
22
- def commandchat_operator():
23
- pass
24
-
25
-
26
- @click.command()
27
- @click.option('--profile', '-p', help='Specify profile name to configure')
28
- def configure(profile):
29
- """Configure OpenAI or Azure OpenAI profiles"""
30
- if profile is not None:
31
- # If profile is specified, configure it directly
32
- from occ.configuration.profile_config import configure_profile
33
- configure_profile(profile)
34
- else:
35
- # No profile specified, show interactive selection
36
- from occ.configuration.profile_config import select_profile_interactively
37
- select_profile_interactively()
38
-
39
-
40
14
  @click.command()
41
15
  @click.argument('message', required=False)
42
- @click.option('-id', help=' enter chat id, something like context')
16
+ @click.option('-id', help='Enter chat id, something like context')
43
17
  @click.option('--profile', '-p', envvar="OCC_PROFILE", help='Enable profile name')
44
18
  @click.option("--model", "-m", envvar="OCC_MODEL", default="o1-mini",
45
19
  help="Specify the model to use for this chat session")
46
- @click.option('--file', '-f', type=click.Path(exists=True), help='the prompt or message is from a file')
47
- def chat(message, id, profile, model, file):
20
+ @click.option('--file', '-f', type=click.Path(exists=True), help='The prompt or message is from a file')
21
+ @click.option('--prompt', '-pt', help='Use a predefined prompt template (e.g., translate, improve). Use "occ prompt list" to see available prompts.')
22
+ def chat(message, id, profile, model, file, prompt):
23
+ """Start a chat session with the AI"""
48
24
  try:
49
25
  import questionary
50
26
  from occ.commons import config as cfg
@@ -104,6 +80,31 @@ def chat(message, id, profile, model, file):
104
80
  logger.log_r(f"Model '{model}' not found in profile '{active_profile}'. Available models: {', '.join(available_models)}")
105
81
  return
106
82
 
83
+ # Handle prompt template - command line -pt has highest priority
84
+ system_message = None
85
+ prompt_source = None # Track where the prompt came from
86
+
87
+ if prompt:
88
+ # Command line -pt parameter has highest priority
89
+ from occ.commons.prompts import get_prompt_system_message, list_prompts
90
+ system_message = get_prompt_system_message(prompt)
91
+ if not system_message:
92
+ logger.log_r(f"Prompt '{prompt}' not found. Available prompts:")
93
+ for key, value in list_prompts().items():
94
+ logger.log_g(f" - {key}: {value['description']}")
95
+ return
96
+ prompt_source = "command line"
97
+ logger.log_g(f"Using prompt template: {prompt} (from {prompt_source})")
98
+ else:
99
+ # Check for profile default prompt
100
+ profile_default_prompt = cfg.get_profile_default_prompt(active_profile)
101
+ if profile_default_prompt:
102
+ from occ.commons.prompts import get_prompt_system_message
103
+ system_message = get_prompt_system_message(profile_default_prompt)
104
+ if system_message:
105
+ prompt_source = f"profile '{active_profile}'"
106
+ logger.log_g(f"Using default prompt: {profile_default_prompt} (from {prompt_source})")
107
+
107
108
  if file:
108
109
  with open(file, 'r') as f:
109
110
  message = f.read()
@@ -124,52 +125,19 @@ def chat(message, id, profile, model, file):
124
125
  print_formatted_text(HTML(
125
126
  "<b>Help Info: \n</b><ansigreen>Type your message and press ESC+Enter or OPT+Enter to send.\n"
126
127
  "Use /exit or /quit or /q to leave the chat.\n"
127
- "Use /help to show this message again.</ansigreen>\n"))
128
+ "Use /help to show this message again.\n"
129
+ "Use -pt <prompt_key> when starting chat to use a prompt template.\n"
130
+ "Run 'occ prompt list' to see available prompt templates.</ansigreen>\n"))
128
131
  continue
129
132
  if message.lower() in {"/exit", "/quit", "/q"}:
130
133
  print_formatted_text(HTML("<ansired>Bye 👋</ansired>"))
131
134
  exit(0)
132
- CommandChat(profile=active_profile, chat_log_id=id).chat(message, model)
135
+ CommandChat(profile=active_profile, chat_log_id=id, system_message=system_message).chat(message, model)
133
136
  print()
134
137
  except KeyboardInterrupt:
135
138
  print_formatted_text(HTML("<ansired>\n(Interrupted)</ansired>"))
136
139
  exit(0)
137
- CommandChat(profile=active_profile, chat_log_id=id).chat(message, model)
140
+ CommandChat(profile=active_profile, chat_log_id=id, system_message=system_message).chat(message, model)
138
141
  except Exception as e:
139
142
  logger.log_g(str(e))
140
143
 
141
-
142
- size_map = {
143
- "s": "256x256",
144
- "S": "256x256",
145
- "m": "512x512",
146
- "M": "512x512",
147
- "l": "1024x1024",
148
- "L": "1024x1024"
149
- }
150
-
151
-
152
- @click.command()
153
- @click.option('-desc', help=' Enter the description of the images you want')
154
- @click.option('-size',
155
- help=' Enter the size(S/s,M/m,L/l): \n small - 256x256 \n middle - 512x512 \n large - 1024x1024')
156
- @click.option('-num', count=True, help=' Enter the number to generate the specified number of images')
157
- @click.option('-profile', help='Enable profile name')
158
- def image(desc, size, num, profile):
159
- number = num if num > 0 else 1
160
- size = size_map.get(size)
161
- size_value = size if size is not None else "512x512"
162
- CommandChat(profile=profile).image_create(desc, size_value, number if number < 5 else 4)
163
-
164
-
165
- commandchat_operator.add_command(configure)
166
- commandchat_operator.add_command(chat)
167
- commandchat_operator.add_command(image)
168
-
169
-
170
- def main():
171
- commandchat_operator()
172
-
173
-
174
- if __name__ == '__main__':
175
- main()
@@ -0,0 +1,30 @@
1
+ """Image generation command"""
2
+
3
+ import click
4
+ from occ.CommandChat import CommandChat
5
+
6
+
7
+ # Size mapping for convenience
8
+ SIZE_MAP = {
9
+ "s": "256x256",
10
+ "S": "256x256",
11
+ "m": "512x512",
12
+ "M": "512x512",
13
+ "l": "1024x1024",
14
+ "L": "1024x1024"
15
+ }
16
+
17
+
18
+ @click.command()
19
+ @click.option('-desc', help='Enter the description of the images you want')
20
+ @click.option('-size',
21
+ help='Enter the size(S/s,M/m,L/l): \n small - 256x256 \n middle - 512x512 \n large - 1024x1024')
22
+ @click.option('-num', count=True, help='Enter the number to generate the specified number of images')
23
+ @click.option('-profile', help='Enable profile name')
24
+ def image(desc, size, num, profile):
25
+ """Generate images using AI"""
26
+ number = num if num > 0 else 1
27
+ size = SIZE_MAP.get(size)
28
+ size_value = size if size is not None else "512x512"
29
+ CommandChat(profile=profile).image_create(desc, size_value, number if number < 5 else 4)
30
+
@@ -0,0 +1,112 @@
1
+ """Profile configuration commands"""
2
+
3
+ import click
4
+ import occ.utils.logger as logger
5
+
6
+
7
+ @click.group(invoke_without_command=True)
8
+ @click.pass_context
9
+ def configure(ctx):
10
+ """Configure OpenAI or Azure OpenAI profiles"""
11
+ if ctx.invoked_subcommand is None:
12
+ # No subcommand provided, show interactive menu
13
+ from occ.command.interactive.profile_menu import interactive_profile_menu
14
+ interactive_profile_menu()
15
+
16
+
17
+ @click.command(name='profile')
18
+ @click.option('--profile', '-p', help='Specify profile name to configure')
19
+ def profile_cmd(profile):
20
+ """Configure a profile"""
21
+ if profile is not None:
22
+ # If profile is specified, configure it directly
23
+ from occ.configuration.profile_config import configure_profile
24
+ configure_profile(profile)
25
+ else:
26
+ # No profile specified, show interactive selection
27
+ from occ.configuration.profile_config import select_profile_interactively
28
+ select_profile_interactively()
29
+
30
+
31
+ @click.command(name='delete')
32
+ @click.argument('profile', required=False)
33
+ @click.option('--yes', '-y', is_flag=True, help='Skip confirmation')
34
+ def delete_cmd(profile, yes):
35
+ """Delete a profile"""
36
+ import questionary
37
+ from occ.commons import config as cfg
38
+
39
+ # If profile not provided, show interactive selection
40
+ if not profile:
41
+ profiles = [p for p in cfg.get_profiles() if p != 'default']
42
+
43
+ if not profiles:
44
+ logger.log_r("No profiles to delete (cannot delete 'default' profile)")
45
+ return
46
+
47
+ profile = questionary.select(
48
+ "Select a profile to delete:",
49
+ choices=profiles
50
+ ).ask()
51
+
52
+ if not profile:
53
+ logger.log_g("Cancelled")
54
+ return
55
+
56
+ if not cfg.profile_exists(profile):
57
+ logger.log_r(f"Profile '{profile}' does not exist")
58
+ return
59
+
60
+ if profile == 'default':
61
+ logger.log_r("Cannot delete the 'default' profile")
62
+ return
63
+
64
+ # Confirm deletion
65
+ if not yes:
66
+ confirm = questionary.confirm(
67
+ f"Are you sure you want to delete profile '{profile}'? This will also remove all associated models.",
68
+ default=False
69
+ ).ask()
70
+
71
+ if not confirm:
72
+ logger.log_g("Deletion cancelled")
73
+ return
74
+
75
+ cfg.remove_profile(profile)
76
+ logger.log_g(f"Profile '{profile}' deleted successfully")
77
+
78
+
79
+ @click.command(name='list')
80
+ def list_cmd():
81
+ """List all configured profiles"""
82
+ from occ.commons import config as cfg
83
+ from rich.table import Table
84
+ from rich.console import Console
85
+
86
+ profiles = cfg.get_profiles()
87
+ if not profiles:
88
+ logger.log_r("No profiles found. Run 'occ configure profile' to create one.")
89
+ return
90
+
91
+ console = Console()
92
+ table = Table(title="Configured Profiles", show_header=True, header_style="bold magenta")
93
+ table.add_column("Profile", style="cyan", width=20)
94
+ table.add_column("API Type", style="green", width=15)
95
+ table.add_column("Default Prompt", style="yellow", width=20)
96
+ table.add_column("Models", style="blue")
97
+
98
+ for profile in profiles:
99
+ api_type = cfg.get_env(profile, 'api_server_type') or 'N/A'
100
+ default_prompt = cfg.get_profile_default_prompt(profile) or 'None'
101
+ models = cfg.get_profile_models(profile)
102
+ models_str = ', '.join(models) if models else 'N/A'
103
+ table.add_row(profile, api_type, default_prompt, models_str)
104
+
105
+ console.print(table)
106
+
107
+
108
+ # Add subcommands to configure group
109
+ configure.add_command(profile_cmd)
110
+ configure.add_command(delete_cmd)
111
+ configure.add_command(list_cmd)
112
+
@@ -0,0 +1,226 @@
1
+ """Prompt management commands"""
2
+
3
+ import click
4
+ import occ.utils.logger as logger
5
+
6
+
7
+ @click.group(invoke_without_command=True)
8
+ @click.pass_context
9
+ def prompt(ctx):
10
+ """Manage prompt templates"""
11
+ if ctx.invoked_subcommand is None:
12
+ # No subcommand provided, show interactive menu
13
+ from occ.command.interactive.prompt_menu import interactive_prompt_menu
14
+ interactive_prompt_menu()
15
+
16
+
17
+ @click.command(name='list')
18
+ def list_cmd():
19
+ """List all available prompt templates"""
20
+ from occ.commons.prompts import list_prompts
21
+ from rich.table import Table
22
+ from rich.console import Console
23
+
24
+ console = Console()
25
+ table = Table(title="Available Prompt Templates", show_header=True, header_style="bold magenta")
26
+ table.add_column("Key", style="cyan", width=15)
27
+ table.add_column("Name", style="green", width=25)
28
+ table.add_column("Description", style="yellow", width=35)
29
+ table.add_column("Type", style="blue", width=10)
30
+
31
+ for key, value in list_prompts().items():
32
+ prompt_type = "Built-in" if value.get("builtin", False) else "Custom"
33
+ table.add_row(key, value["name"], value["description"], prompt_type)
34
+
35
+ console.print(table)
36
+ console.print("\n[bold green]Usage:[/bold green] occ chat -pt <key> \"your message\"")
37
+ console.print("[bold green]Example:[/bold green] occ chat -pt translate \"你好世界\"")
38
+
39
+
40
+ @click.command(name='add')
41
+ @click.argument('key')
42
+ @click.option('--name', '-n', required=True, help='Display name for the prompt')
43
+ @click.option('--description', '-d', required=True, help='Brief description of the prompt')
44
+ @click.option('--system-prompt', '-s', required=True, help='The system prompt content')
45
+ def add_cmd(key, name, description, system_prompt):
46
+ """Add a new custom prompt template"""
47
+ from occ.commons.prompts import add_prompt
48
+
49
+ success, message = add_prompt(key, name, description, system_prompt)
50
+ if success:
51
+ logger.log_g(message)
52
+ else:
53
+ logger.log_r(message)
54
+
55
+
56
+ @click.command(name='modify')
57
+ @click.argument('key', required=False)
58
+ @click.option('--name', '-n', help='New display name')
59
+ @click.option('--description', '-d', help='New description')
60
+ @click.option('--system-prompt', '-s', help='New system prompt content')
61
+ def modify_cmd(key, name, description, system_prompt):
62
+ """Modify an existing custom prompt template"""
63
+ from occ.commons.prompts import modify_prompt, list_prompts, get_prompt
64
+ import questionary
65
+
66
+ # If key not provided, show interactive selection
67
+ if not key:
68
+ user_prompts = {k: v for k, v in list_prompts().items() if not v.get('builtin', False)}
69
+
70
+ if not user_prompts:
71
+ logger.log_r("No custom prompts to modify. Built-in prompts cannot be modified.")
72
+ return
73
+
74
+ key = questionary.select(
75
+ "Select a prompt to modify:",
76
+ choices=list(user_prompts.keys())
77
+ ).ask()
78
+
79
+ if not key:
80
+ logger.log_g("Cancelled")
81
+ return
82
+
83
+ # If no options provided, ask interactively
84
+ if not any([name, description, system_prompt]):
85
+ current_prompt = get_prompt(key)
86
+
87
+ fields = questionary.checkbox(
88
+ "What would you like to modify?",
89
+ choices=['Name', 'Description', 'System Prompt']
90
+ ).ask()
91
+
92
+ if not fields:
93
+ logger.log_g("Cancelled")
94
+ return
95
+
96
+ if 'Name' in fields:
97
+ name = questionary.text(
98
+ "Enter new name:",
99
+ default=current_prompt['name']
100
+ ).ask()
101
+
102
+ if 'Description' in fields:
103
+ description = questionary.text(
104
+ "Enter new description:",
105
+ default=current_prompt['description']
106
+ ).ask()
107
+
108
+ if 'System Prompt' in fields:
109
+ system_prompt = questionary.text(
110
+ "Enter new system prompt:",
111
+ default=current_prompt['system_prompt'],
112
+ multiline=True
113
+ ).ask()
114
+
115
+ if not any([name, description, system_prompt]):
116
+ logger.log_r("At least one of --name, --description, or --system-prompt must be provided")
117
+ return
118
+
119
+ success, message = modify_prompt(key, name, description, system_prompt)
120
+ if success:
121
+ logger.log_g(message)
122
+ else:
123
+ logger.log_r(message)
124
+
125
+
126
+ @click.command(name='remove')
127
+ @click.argument('key', required=False)
128
+ @click.option('--yes', '-y', is_flag=True, help='Skip confirmation')
129
+ def remove_cmd(key, yes):
130
+ """Remove a custom prompt template"""
131
+ from occ.commons.prompts import remove_prompt, get_prompt, list_prompts
132
+ import questionary
133
+
134
+ # If key not provided, show interactive selection
135
+ if not key:
136
+ user_prompts = {k: v for k, v in list_prompts().items() if not v.get('builtin', False)}
137
+
138
+ if not user_prompts:
139
+ logger.log_r("No custom prompts to remove. Built-in prompts cannot be removed.")
140
+ return
141
+
142
+ key = questionary.select(
143
+ "Select a prompt to remove:",
144
+ choices=list(user_prompts.keys())
145
+ ).ask()
146
+
147
+ if not key:
148
+ logger.log_g("Cancelled")
149
+ return
150
+
151
+ # Check if prompt exists
152
+ prompt = get_prompt(key)
153
+ if not prompt:
154
+ logger.log_r(f"Prompt '{key}' not found")
155
+ return
156
+
157
+ if prompt.get("builtin", False):
158
+ logger.log_r(f"Cannot remove built-in prompt '{key}'")
159
+ return
160
+
161
+ # Confirm removal
162
+ if not yes:
163
+ confirm = questionary.confirm(
164
+ f"Are you sure you want to remove prompt '{key}'?",
165
+ default=False
166
+ ).ask()
167
+
168
+ if not confirm:
169
+ logger.log_g("Removal cancelled")
170
+ return
171
+
172
+ success, message = remove_prompt(key)
173
+ if success:
174
+ logger.log_g(message)
175
+ else:
176
+ logger.log_r(message)
177
+
178
+
179
+ @click.command(name='show')
180
+ @click.argument('key', required=False)
181
+ def show_cmd(key):
182
+ """Show detailed information about a prompt template"""
183
+ from occ.commons.prompts import get_prompt, list_prompts
184
+ from rich.console import Console
185
+ from rich.panel import Panel
186
+ import questionary
187
+
188
+ # If key not provided, show interactive selection
189
+ if not key:
190
+ prompts_dict = list_prompts()
191
+ if not prompts_dict:
192
+ logger.log_r("No prompts available")
193
+ return
194
+
195
+ key = questionary.select(
196
+ "Select a prompt to view:",
197
+ choices=list(prompts_dict.keys())
198
+ ).ask()
199
+
200
+ if not key:
201
+ logger.log_g("Cancelled")
202
+ return
203
+
204
+ prompt = get_prompt(key)
205
+ if not prompt:
206
+ logger.log_r(f"Prompt '{key}' not found")
207
+ return
208
+
209
+ console = Console()
210
+ prompt_type = "Built-in" if prompt.get("builtin", False) else "Custom"
211
+
212
+ console.print(f"\n[bold cyan]Key:[/bold cyan] {key}")
213
+ console.print(f"[bold cyan]Name:[/bold cyan] {prompt['name']}")
214
+ console.print(f"[bold cyan]Type:[/bold cyan] {prompt_type}")
215
+ console.print(f"[bold cyan]Description:[/bold cyan] {prompt['description']}")
216
+ console.print(f"\n[bold cyan]System Prompt:[/bold cyan]")
217
+ console.print(Panel(prompt['system_prompt'], border_style="green"))
218
+
219
+
220
+ # Add subcommands to prompt group
221
+ prompt.add_command(list_cmd)
222
+ prompt.add_command(add_cmd)
223
+ prompt.add_command(modify_cmd)
224
+ prompt.add_command(remove_cmd)
225
+ prompt.add_command(show_cmd)
226
+
@@ -0,0 +1,2 @@
1
+ """Interactive menu modules for occ CLI"""
2
+
@@ -0,0 +1,91 @@
1
+ """Interactive menu for profile configuration"""
2
+
3
+ import questionary
4
+ from questionary import Style
5
+ import occ.utils.logger as logger
6
+
7
+
8
+ # Custom style for questionary
9
+ CUSTOM_STYLE = Style([
10
+ ('qmark', 'fg:#673ab7 bold'),
11
+ ('question', 'bold'),
12
+ ('answer', 'fg:#f44336 bold'),
13
+ ('pointer', 'fg:#673ab7 bold'),
14
+ ('highlighted', 'fg:#673ab7 bold'),
15
+ ])
16
+
17
+
18
+ def interactive_profile_menu():
19
+ """Interactive menu for managing profiles"""
20
+ while True:
21
+ action = questionary.select(
22
+ "Profile Configuration - What would you like to do?",
23
+ choices=[
24
+ 'List all profiles',
25
+ 'Configure profile',
26
+ 'Delete profile',
27
+ 'Exit'
28
+ ],
29
+ style=CUSTOM_STYLE
30
+ ).ask()
31
+
32
+ if action is None or action == 'Exit':
33
+ break
34
+ elif action == 'List all profiles':
35
+ from occ.command.commands.profile import list_cmd
36
+ list_cmd.callback()
37
+ elif action == 'Configure profile':
38
+ interactive_configure_profile()
39
+ elif action == 'Delete profile':
40
+ interactive_delete_profile()
41
+
42
+
43
+ def interactive_configure_profile():
44
+ """Interactive profile configuration"""
45
+ from occ.commons import config as cfg
46
+ from occ.configuration.profile_config import configure_profile
47
+
48
+ profiles = cfg.get_profiles()
49
+
50
+ if profiles:
51
+ choices = profiles + ['[Create New Profile]']
52
+ selection = questionary.select(
53
+ "Select a profile to configure:",
54
+ choices=choices,
55
+ style=CUSTOM_STYLE
56
+ ).ask()
57
+
58
+ if not selection:
59
+ return
60
+
61
+ if selection == '[Create New Profile]':
62
+ profile_name = questionary.text("Enter new profile name:", style=CUSTOM_STYLE).ask()
63
+ if profile_name:
64
+ configure_profile(profile_name)
65
+ else:
66
+ configure_profile(selection)
67
+ else:
68
+ logger.log_g("No profiles found. Creating default profile...")
69
+ configure_profile('default')
70
+
71
+
72
+ def interactive_delete_profile():
73
+ """Interactive profile deletion"""
74
+ from occ.commons import config as cfg
75
+ from occ.command.commands.profile import delete_cmd
76
+
77
+ profiles = [p for p in cfg.get_profiles() if p != 'default']
78
+
79
+ if not profiles:
80
+ logger.log_r("No profiles to delete (cannot delete 'default' profile)")
81
+ return
82
+
83
+ profile = questionary.select(
84
+ "Select a profile to delete:",
85
+ choices=profiles,
86
+ style=CUSTOM_STYLE
87
+ ).ask()
88
+
89
+ if profile:
90
+ delete_cmd.callback(profile, False)
91
+
@@ -0,0 +1,176 @@
1
+ """Interactive menu for prompt management"""
2
+
3
+ import questionary
4
+ from questionary import Style
5
+ import occ.utils.logger as logger
6
+
7
+
8
+ # Custom style for questionary
9
+ CUSTOM_STYLE = Style([
10
+ ('qmark', 'fg:#673ab7 bold'),
11
+ ('question', 'bold'),
12
+ ('answer', 'fg:#f44336 bold'),
13
+ ('pointer', 'fg:#673ab7 bold'),
14
+ ('highlighted', 'fg:#673ab7 bold'),
15
+ ])
16
+
17
+
18
+ def interactive_prompt_menu():
19
+ """Interactive menu for managing prompts"""
20
+ while True:
21
+ action = questionary.select(
22
+ "Prompt Management - What would you like to do?",
23
+ choices=[
24
+ 'List all prompts',
25
+ 'Show prompt details',
26
+ 'Add new prompt',
27
+ 'Modify prompt',
28
+ 'Remove prompt',
29
+ 'Exit'
30
+ ],
31
+ style=CUSTOM_STYLE
32
+ ).ask()
33
+
34
+ if action is None or action == 'Exit':
35
+ break
36
+ elif action == 'List all prompts':
37
+ from occ.command.commands.prompt import list_cmd
38
+ list_cmd.callback()
39
+ elif action == 'Show prompt details':
40
+ interactive_show_prompt()
41
+ elif action == 'Add new prompt':
42
+ interactive_add_prompt()
43
+ elif action == 'Modify prompt':
44
+ interactive_modify_prompt()
45
+ elif action == 'Remove prompt':
46
+ interactive_remove_prompt()
47
+
48
+
49
+ def interactive_show_prompt():
50
+ """Interactive prompt selection for showing details"""
51
+ from occ.commons.prompts import list_prompts
52
+ from occ.command.commands.prompt import show_cmd
53
+
54
+ prompts_dict = list_prompts()
55
+ if not prompts_dict:
56
+ logger.log_r("No prompts available")
57
+ return
58
+
59
+ prompt_key = questionary.select(
60
+ "Select a prompt to view:",
61
+ choices=list(prompts_dict.keys()),
62
+ style=CUSTOM_STYLE
63
+ ).ask()
64
+
65
+ if prompt_key:
66
+ show_cmd.callback(prompt_key)
67
+
68
+
69
+ def interactive_add_prompt():
70
+ """Interactive prompt addition"""
71
+ from occ.command.commands.prompt import add_cmd
72
+
73
+ key = questionary.text("Enter prompt key (e.g., my-prompt):", style=CUSTOM_STYLE).ask()
74
+ if not key:
75
+ return
76
+
77
+ name = questionary.text("Enter prompt name:", style=CUSTOM_STYLE).ask()
78
+ if not name:
79
+ return
80
+
81
+ description = questionary.text("Enter prompt description:", style=CUSTOM_STYLE).ask()
82
+ if not description:
83
+ return
84
+
85
+ system_prompt = questionary.text(
86
+ "Enter system prompt content:",
87
+ multiline=True,
88
+ style=CUSTOM_STYLE
89
+ ).ask()
90
+ if not system_prompt:
91
+ return
92
+
93
+ add_cmd.callback(key, name, description, system_prompt)
94
+
95
+
96
+ def interactive_modify_prompt():
97
+ """Interactive prompt modification"""
98
+ from occ.commons.prompts import list_prompts, get_prompt
99
+ from occ.command.commands.prompt import modify_cmd
100
+
101
+ user_prompts = {k: v for k, v in list_prompts().items() if not v.get('builtin', False)}
102
+
103
+ if not user_prompts:
104
+ logger.log_r("No custom prompts to modify. Built-in prompts cannot be modified.")
105
+ return
106
+
107
+ prompt_key = questionary.select(
108
+ "Select a prompt to modify:",
109
+ choices=list(user_prompts.keys()),
110
+ style=CUSTOM_STYLE
111
+ ).ask()
112
+
113
+ if not prompt_key:
114
+ return
115
+
116
+ current_prompt = get_prompt(prompt_key)
117
+
118
+ # Ask what to modify
119
+ fields = questionary.checkbox(
120
+ "What would you like to modify?",
121
+ choices=['Name', 'Description', 'System Prompt'],
122
+ style=CUSTOM_STYLE
123
+ ).ask()
124
+
125
+ if not fields:
126
+ return
127
+
128
+ name = None
129
+ description = None
130
+ system_prompt = None
131
+
132
+ if 'Name' in fields:
133
+ name = questionary.text(
134
+ "Enter new name:",
135
+ default=current_prompt['name'],
136
+ style=CUSTOM_STYLE
137
+ ).ask()
138
+
139
+ if 'Description' in fields:
140
+ description = questionary.text(
141
+ "Enter new description:",
142
+ default=current_prompt['description'],
143
+ style=CUSTOM_STYLE
144
+ ).ask()
145
+
146
+ if 'System Prompt' in fields:
147
+ system_prompt = questionary.text(
148
+ "Enter new system prompt:",
149
+ default=current_prompt['system_prompt'],
150
+ multiline=True,
151
+ style=CUSTOM_STYLE
152
+ ).ask()
153
+
154
+ modify_cmd.callback(prompt_key, name, description, system_prompt)
155
+
156
+
157
+ def interactive_remove_prompt():
158
+ """Interactive prompt removal"""
159
+ from occ.commons.prompts import list_prompts
160
+ from occ.command.commands.prompt import remove_cmd
161
+
162
+ user_prompts = {k: v for k, v in list_prompts().items() if not v.get('builtin', False)}
163
+
164
+ if not user_prompts:
165
+ logger.log_r("No custom prompts to remove. Built-in prompts cannot be removed.")
166
+ return
167
+
168
+ prompt_key = questionary.select(
169
+ "Select a prompt to remove:",
170
+ choices=list(user_prompts.keys()),
171
+ style=CUSTOM_STYLE
172
+ ).ask()
173
+
174
+ if prompt_key:
175
+ remove_cmd.callback(prompt_key, False)
176
+
@@ -142,6 +142,19 @@ def profile_exists(profile):
142
142
  return config.has_section(profile)
143
143
 
144
144
 
145
+ def get_profile_default_prompt(profile):
146
+ """Get the default prompt for a profile"""
147
+ return get_env(profile, 'default_prompt')
148
+
149
+
150
+ def set_profile_default_prompt(profile, prompt_key):
151
+ """Set the default prompt for a profile"""
152
+ if not profile_exists(profile):
153
+ return False, f"Profile '{profile}' does not exist"
154
+ set_env(profile, 'default_prompt', prompt_key)
155
+ return True, f"Default prompt for profile '{profile}' set to '{prompt_key}'"
156
+
157
+
145
158
  def log_config():
146
159
  level_input = 'DEBUG'
147
160
  if level_input == 'DEBUG':
@@ -0,0 +1,146 @@
1
+ """
2
+ Built-in and custom prompt template management
3
+ """
4
+
5
+ import os
6
+ import json
7
+ import logging
8
+
9
+ # Built-in default prompts (read-only)
10
+ DEFAULT_PROMPTS = {
11
+ "translate": {
12
+ "name": "Translation Assistant",
13
+ "description": "Translate Chinese to English",
14
+ "system_prompt": "You are a professional translation assistant. Please translate the Chinese text provided by the user into idiomatic English. Only output the translation result without additional explanations.",
15
+ "builtin": True
16
+ },
17
+ "improve": {
18
+ "name": "English Writing Assistant",
19
+ "description": "Improve English sentences and identify issues",
20
+ "system_prompt": "You are a professional English writing assistant. Please help users improve their English sentences to make them more natural for native speakers. Format your output as follows:\n\n**Improved Sentence:**\n[The improved English sentence]\n\n**Issues in Original:**\n[List grammar, word choice, and expression issues]",
21
+ "builtin": True
22
+ }
23
+ }
24
+
25
+
26
+ def get_home_path():
27
+ homedir = os.environ.get('HOME', None)
28
+ if os.name == 'nt':
29
+ homedir = os.path.expanduser('~')
30
+ return homedir
31
+
32
+
33
+ def get_prompts_file():
34
+ """Get the path to user prompts configuration file"""
35
+ homedir = get_home_path()
36
+ prompts_dir = os.path.join(homedir, ".occ")
37
+ os.makedirs(prompts_dir, exist_ok=True)
38
+ return os.path.join(prompts_dir, "prompts.json")
39
+
40
+
41
+ def load_user_prompts():
42
+ """Load user-defined prompts from file"""
43
+ prompts_file = get_prompts_file()
44
+ if not os.path.exists(prompts_file):
45
+ return {}
46
+
47
+ try:
48
+ with open(prompts_file, 'r', encoding='utf-8') as f:
49
+ return json.load(f)
50
+ except Exception as e:
51
+ logging.error(f"Error loading user prompts: {e}")
52
+ return {}
53
+
54
+
55
+ def save_user_prompts(prompts):
56
+ """Save user-defined prompts to file"""
57
+ prompts_file = get_prompts_file()
58
+ try:
59
+ with open(prompts_file, 'w', encoding='utf-8') as f:
60
+ json.dump(prompts, f, indent=2, ensure_ascii=False)
61
+ return True
62
+ except Exception as e:
63
+ logging.error(f"Error saving user prompts: {e}")
64
+ return False
65
+
66
+
67
+ def get_prompt(prompt_key):
68
+ """Get a specific prompt template (checks user prompts first, then built-in)"""
69
+ user_prompts = load_user_prompts()
70
+ if prompt_key in user_prompts:
71
+ return user_prompts[prompt_key]
72
+ return DEFAULT_PROMPTS.get(prompt_key)
73
+
74
+
75
+ def list_prompts():
76
+ """List all available prompts (built-in + user-defined)"""
77
+ all_prompts = DEFAULT_PROMPTS.copy()
78
+ user_prompts = load_user_prompts()
79
+ all_prompts.update(user_prompts)
80
+ return all_prompts
81
+
82
+
83
+ def get_prompt_system_message(prompt_key):
84
+ """Get the system message for a prompt"""
85
+ prompt = get_prompt(prompt_key)
86
+ if prompt:
87
+ return prompt["system_prompt"]
88
+ return None
89
+
90
+
91
+ def add_prompt(key, name, description, system_prompt):
92
+ """Add a new user-defined prompt"""
93
+ if key in DEFAULT_PROMPTS:
94
+ return False, f"Cannot override built-in prompt '{key}'"
95
+
96
+ user_prompts = load_user_prompts()
97
+ user_prompts[key] = {
98
+ "name": name,
99
+ "description": description,
100
+ "system_prompt": system_prompt,
101
+ "builtin": False
102
+ }
103
+
104
+ if save_user_prompts(user_prompts):
105
+ return True, f"Prompt '{key}' added successfully"
106
+ return False, "Failed to save prompt"
107
+
108
+
109
+ def modify_prompt(key, name=None, description=None, system_prompt=None):
110
+ """Modify an existing user-defined prompt"""
111
+ if key in DEFAULT_PROMPTS:
112
+ return False, f"Cannot modify built-in prompt '{key}'"
113
+
114
+ user_prompts = load_user_prompts()
115
+ if key not in user_prompts:
116
+ return False, f"Prompt '{key}' not found"
117
+
118
+ # Update only provided fields
119
+ if name is not None:
120
+ user_prompts[key]["name"] = name
121
+ if description is not None:
122
+ user_prompts[key]["description"] = description
123
+ if system_prompt is not None:
124
+ user_prompts[key]["system_prompt"] = system_prompt
125
+
126
+ if save_user_prompts(user_prompts):
127
+ return True, f"Prompt '{key}' modified successfully"
128
+ return False, "Failed to save prompt"
129
+
130
+
131
+ def remove_prompt(key):
132
+ """Remove a user-defined prompt"""
133
+ if key in DEFAULT_PROMPTS:
134
+ return False, f"Cannot remove built-in prompt '{key}'"
135
+
136
+ user_prompts = load_user_prompts()
137
+ if key not in user_prompts:
138
+ return False, f"Prompt '{key}' not found"
139
+
140
+ del user_prompts[key]
141
+
142
+ if save_user_prompts(user_prompts):
143
+ return True, f"Prompt '{key}' removed successfully"
144
+ return False, "Failed to save prompts"
145
+
146
+
@@ -102,6 +102,40 @@ def configure_common_settings(profile_name, is_existing):
102
102
 
103
103
  if limit_history:
104
104
  config.set_env(profile_name, 'limit_history', limit_history)
105
+
106
+ # Configure default prompt
107
+ existing_prompt = config.get_profile_default_prompt(profile_name) if is_existing else None
108
+
109
+ set_prompt = questionary.confirm(
110
+ "Set a default prompt template for this profile?",
111
+ default=False,
112
+ style=custom_style
113
+ ).ask()
114
+
115
+ if set_prompt:
116
+ from occ.commons.prompts import list_prompts
117
+ available_prompts = list(list_prompts().keys())
118
+ if available_prompts:
119
+ default_prompt = questionary.select(
120
+ "Select default prompt:",
121
+ choices=available_prompts + ['[None]'],
122
+ default=existing_prompt if existing_prompt in available_prompts else '[None]',
123
+ style=custom_style
124
+ ).ask()
125
+
126
+ if default_prompt and default_prompt != '[None]':
127
+ config.set_env(profile_name, 'default_prompt', default_prompt)
128
+ logger.log_g(f"✓ Default prompt set to '{default_prompt}'")
129
+ elif default_prompt == '[None]' and existing_prompt:
130
+ # Remove default prompt
131
+ config.config.remove_option(profile_name, 'default_prompt')
132
+ config.write_config()
133
+ logger.log_g("✓ Default prompt removed")
134
+ else:
135
+ logger.log_r("No prompts available. Use 'occ prompts add' to create custom prompts.")
136
+ elif existing_prompt:
137
+ # Show existing default prompt
138
+ logger.log_g(f"Current default prompt: {existing_prompt}")
105
139
 
106
140
 
107
141
  def configure_openai(profile_name, is_existing):
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "commandchat"
7
- version = "0.0.13"
7
+ version = "0.0.14"
8
8
  description = "use command to chat with openai models"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
File without changes
File without changes
File without changes
File without changes