janito 3.16.1__py3-none-any.whl → 3.16.3__py3-none-any.whl

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.
janito/cli/main_cli.py CHANGED
@@ -1,534 +1,528 @@
1
- import argparse
2
- import sys
3
- import enum
4
- from janito.cli.core.setters import handle_api_key_set, handle_set
5
- from janito.cli.core.getters import handle_getter
6
- from janito.cli.core.runner import (
7
- prepare_llm_driver_config,
8
- handle_runner,
9
- get_prompt_mode,
10
- )
11
- from janito.cli.core.event_logger import (
12
- setup_event_logger_if_needed,
13
- inject_debug_event_bus_if_needed,
14
- )
15
-
16
-
17
- definition = [
18
- (
19
- ["-u", "--unrestricted"],
20
- {
21
- "action": "store_true",
22
- "help": "Unrestricted mode: disable path security and URL whitelist restrictions (DANGEROUS)",
23
- },
24
- ),
25
- (
26
- ["--multi"],
27
- {
28
- "action": "store_true",
29
- "help": "Start chat mode with multi-line input as default (no need for /multi command)",
30
- },
31
- ),
32
- (
33
- ["--profile"],
34
- {
35
- "metavar": "PROFILE",
36
- "help": "Select the profile name for the system prompt (e.g. 'developer').",
37
- "default": None,
38
- },
39
- ),
40
- (
41
- ["--developer"],
42
- {
43
- "action": "store_true",
44
- "help": "Start with the Python developer profile (equivalent to --profile 'Developer')",
45
- },
46
- ),
47
- (
48
- ["--market"],
49
- {
50
- "action": "store_true",
51
- "help": "Start with the Market Analyst profile (equivalent to --profile 'Market Analyst')",
52
- },
53
- ),
54
- (
55
- ["-W", "--workdir"],
56
- {
57
- "metavar": "WORKDIR",
58
- "help": "Working directory to chdir to before tool execution",
59
- "default": None,
60
- },
61
- ),
62
- (
63
- ["--verbose-api"],
64
- {
65
- "action": "store_true",
66
- "help": "Print API calls and responses of LLM driver APIs for debugging/tracing.",
67
- },
68
- ),
69
- (
70
- ["--verbose-tools"],
71
- {
72
- "action": "store_true",
73
- "help": "Print info messages for tool execution in tools adapter.",
74
- },
75
- ),
76
- (
77
- ["--verbose-agent"],
78
- {
79
- "action": "store_true",
80
- "help": "Print info messages for agent event and message part handling.",
81
- },
82
- ),
83
- (
84
- ["-z", "--zero"],
85
- {
86
- "action": "store_true",
87
- "help": "IDE zero mode: disables system prompt & all tools for raw LLM interaction",
88
- },
89
- ),
90
- (
91
- ["-x", "--exec"],
92
- {
93
- "action": "store_true",
94
- "help": "Enable execution/run tools (allows running code or shell tools from the CLI)",
95
- },
96
- ),
97
- (
98
- ["-r", "--read"],
99
- {
100
- "action": "store_true",
101
- "help": "Enable tools that require read permissions",
102
- },
103
- ),
104
- (
105
- ["-w", "--write"],
106
- {
107
- "action": "store_true",
108
- "help": "Enable tools that require write permissions",
109
- },
110
- ),
111
- (["--unset"], {"metavar": "KEY", "help": "Unset (remove) a config key"}),
112
- (["--version"], {"action": "version", "version": None}),
113
- (["--list-tools"], {"action": "store_true", "help": "List all registered tools"}),
114
- (["--show-config"], {"action": "store_true", "help": "Show the current config"}),
115
- (
116
- ["--list-config"],
117
- {"action": "store_true", "help": "List all configuration files"},
118
- ),
119
- (
120
- ["--list-profiles"],
121
- {"action": "store_true", "help": "List available system prompt profiles"},
122
- ),
123
- (
124
- ["--list-providers"],
125
- {"action": "store_true", "help": "List supported LLM providers"},
126
- ),
127
- (
128
- ["--ping"],
129
- {
130
- "action": "store_true",
131
- "help": "Ping/test connectivity for all providers (use with --list-providers)",
132
- },
133
- ),
134
- (
135
- ["--list-drivers"],
136
- {
137
- "action": "store_true",
138
- "help": "List available LLM drivers and their dependencies",
139
- },
140
- ),
141
- (
142
- ["--region-info"],
143
- {
144
- "action": "store_true",
145
- "help": "Show current region information and location",
146
- },
147
- ),
148
- (
149
- ["--list-providers-region"],
150
- {
151
- "action": "store_true",
152
- "help": "List all providers with their regional API information",
153
- },
154
- ),
155
- (
156
- ["-l", "--list-models"],
157
- {"action": "store_true", "help": "List all supported models"},
158
- ),
159
- (
160
- ["--set-api-key"],
161
- {
162
- "metavar": "API_KEY",
163
- "help": "Set API key for the provider (requires -p PROVIDER)",
164
- },
165
- ),
166
- (["--set"], {"metavar": "KEY=VALUE", "help": "Set a config key"}),
167
- (["-s", "--system"], {"metavar": "SYSTEM_PROMPT", "help": "Set a system prompt"}),
168
- (
169
- ["-S", "--show-system"],
170
- {
171
- "action": "store_true",
172
- "help": "Show the resolved system prompt for the main agent",
173
- },
174
- ),
175
- (["-p", "--provider"], {"metavar": "PROVIDER", "help": "Select the provider"}),
176
- (["-m", "--model"], {"metavar": "MODEL", "help": "Select the model (can use model@provider syntax)"}),
177
- (
178
- ["-t", "--temperature"],
179
- {"type": float, "default": None, "help": "Set the temperature"},
180
- ),
181
- (
182
- ["-v", "--verbose"],
183
- {"action": "store_true", "help": "Print extra information before answering"},
184
- ),
185
- (
186
- ["-R", "--raw"],
187
- {
188
- "action": "store_true",
189
- "help": "Print the raw JSON response from the OpenAI API (if applicable)",
190
- },
191
- ),
192
- (
193
- ["--effort"],
194
- {
195
- "choices": ["low", "medium", "high", "none"],
196
- "default": None,
197
- "help": "Set the reasoning effort for models that support it (low, medium, high, none)",
198
- },
199
- ),
200
- (
201
- ["--emoji"],
202
- {
203
- "action": "store_true",
204
- "help": "Enable emoji usage in responses to make output more engaging and expressive",
205
- },
206
- ),
207
- (
208
- ["-i", "--interactive"],
209
- {
210
- "action": "store_true",
211
- "help": "Signal that this is an interactive chat session",
212
- },
213
- ),
214
- (["user_prompt"], {"nargs": argparse.REMAINDER, "help": "Prompt to submit"}),
215
- (
216
- ["-e", "--event-log"],
217
- {"action": "store_true", "help": "Enable event logging to the system bus"},
218
- ),
219
- (
220
- ["--event-debug"],
221
- {
222
- "action": "store_true",
223
- "help": "Print debug info on event subscribe/submit methods",
224
- },
225
- ),
226
- (
227
- ["-c", "--config"],
228
- {
229
- "metavar": "NAME",
230
- "help": "Use custom configuration file ~/.janito/configs/NAME.json instead of default config.json",
231
- },
232
- ),
233
- (
234
- ["--list-plugins"],
235
- {"action": "store_true", "help": "List all loaded plugins"},
236
- ),
237
- (
238
- ["--list-plugins-available"],
239
- {"action": "store_true", "help": "List all available plugins"},
240
- ),
241
- (
242
- ["--list-resources"],
243
- {
244
- "action": "store_true",
245
- "help": "List all resources (tools, commands, config) from loaded plugins",
246
- },
247
- ),
248
- ]
249
-
250
- MODIFIER_KEYS = [
251
- "provider",
252
- "model",
253
- "profile",
254
- "developer",
255
- "market",
256
- "system",
257
- "temperature",
258
- "verbose",
259
- "raw",
260
- "verbose_api",
261
- "verbose_tools",
262
- "exec",
263
- "read",
264
- "write",
265
- "emoji",
266
- "interactive",
267
- ]
268
- SETTER_KEYS = ["set", "set_provider", "set_api_key", "unset"]
269
- GETTER_KEYS = [
270
- "show_config",
271
- "list_providers",
272
- "list_profiles",
273
- "list_models",
274
- "list_tools",
275
- "list_config",
276
- "list_drivers",
277
- "region_info",
278
- "list_providers_region",
279
- "ping",
280
- ]
281
- GETTER_KEYS = [
282
- "show_config",
283
- "list_providers",
284
- "list_profiles",
285
- "list_models",
286
- "list_tools",
287
- "list_config",
288
- "list_drivers",
289
- "region_info",
290
- "list_providers_region",
291
- "ping",
292
- ]
293
-
294
-
295
- class RunMode(enum.Enum):
296
- GET = "get"
297
- SET = "set"
298
- RUN = "run"
299
-
300
-
301
- class JanitoCLI:
302
- def __init__(self):
303
- import janito.tools
304
-
305
- self.parser = argparse.ArgumentParser(
306
- description="Janito CLI - A tool for running LLM-powered workflows from the command line."
307
- "\n\nExample usage: janito -p moonshot -m kimi-k1-8k 'Your prompt here'\n"
308
- "Example usage: janito -m model@provider 'Your prompt here'\n\n"
309
- "Use -m or --model to set the model for the session.",
310
- )
311
- self._define_args()
312
- self.args = self.parser.parse_args()
313
- self._parse_model_provider_syntax()
314
- self._set_all_arg_defaults()
315
- # Support custom config file via -c/--config
316
- if getattr(self.args, "config", None):
317
- from janito import config as global_config
318
- from janito.config_manager import ConfigManager
319
- import sys
320
- import importlib
321
-
322
- config_name = self.args.config
323
- # Re-initialize the global config singleton
324
- new_config = ConfigManager(config_name=config_name)
325
- # Ensure the config path is updated when the singleton already existed
326
- from pathlib import Path
327
-
328
- new_config.config_path = (
329
- Path.home() / ".janito" / "configs" / f"{config_name}.json"
330
- )
331
- # Reload config from the selected file
332
- new_config._load_file_config()
333
- # Patch the global singleton reference
334
- import janito.config as config_module
335
-
336
- config_module.config = new_config
337
- sys.modules["janito.config"].config = new_config
338
- # Support reading prompt from stdin if no user_prompt is given
339
- import sys
340
-
341
- if not sys.stdin.isatty():
342
- stdin_input = sys.stdin.read().strip()
343
- if stdin_input:
344
- if self.args.user_prompt and len(self.args.user_prompt) > 0:
345
- # Prefix the prompt argument to the stdin input
346
- combined = " ".join(self.args.user_prompt) + " " + stdin_input
347
- self.args.user_prompt = [combined]
348
- else:
349
- self.args.user_prompt = [stdin_input]
350
- from janito.cli.rich_terminal_reporter import RichTerminalReporter
351
-
352
- self.rich_reporter = RichTerminalReporter(raw_mode=self.args.raw)
353
-
354
- def _parse_model_provider_syntax(self):
355
- """Parse -m model@provider syntax to split into model and provider."""
356
- model = getattr(self.args, "model", None)
357
- if model and "@" in model:
358
- model_part, provider_part = model.rsplit("@", 1)
359
- if model_part and provider_part:
360
- # Only set provider if not already explicitly set via -p flag
361
- if getattr(self.args, "provider", None) is None:
362
- self.args.provider = provider_part
363
- # Always set the model part (without the @provider suffix)
364
- self.args.model = model_part
365
-
366
- def _define_args(self):
367
- for argnames, argkwargs in definition:
368
- # Patch version argument dynamically with real version
369
- if "--version" in argnames:
370
- from janito import __version__ as janito_version
371
-
372
- argkwargs["version"] = f"Janito {janito_version}"
373
- self.parser.add_argument(*argnames, **argkwargs)
374
-
375
- def _set_all_arg_defaults(self):
376
- # Gather all possible keys from definition, MODIFIER_KEYS, SETTER_KEYS, GETTER_KEYS
377
- all_keys = set()
378
- for argnames, argkwargs in definition:
379
- for name in argnames:
380
- key = name.lstrip("-").replace("-", "_")
381
- all_keys.add(key)
382
- all_keys.update(MODIFIER_KEYS)
383
- all_keys.update(SETTER_KEYS)
384
- all_keys.update(GETTER_KEYS)
385
- # Set defaults for all keys if not present
386
- for key in all_keys:
387
- if not hasattr(self.args, key):
388
- setattr(self.args, key, None)
389
-
390
- def collect_modifiers(self):
391
- modifiers = {
392
- k: getattr(self.args, k)
393
- for k in MODIFIER_KEYS
394
- if getattr(self.args, k, None) is not None
395
- }
396
-
397
- return modifiers
398
-
399
- def classify(self):
400
- if any(getattr(self.args, k, None) for k in SETTER_KEYS):
401
- return RunMode.SET
402
- if any(getattr(self.args, k, None) for k in GETTER_KEYS):
403
- return RunMode.GET
404
- return RunMode.RUN
405
-
406
- def run(self):
407
- # Handle --show-system/-S before anything else
408
- if getattr(self.args, "show_system", False):
409
- from janito.cli.cli_commands.show_system_prompt import (
410
- handle_show_system_prompt,
411
- )
412
-
413
- handle_show_system_prompt(self.args)
414
- return
415
- run_mode = self.classify()
416
- if run_mode == RunMode.SET:
417
- if self._run_set_mode():
418
- return
419
- # Special handling: provider is not required for list commands
420
- if run_mode == RunMode.GET and (
421
- self.args.list_providers
422
- or self.args.list_models
423
- or self.args.list_tools
424
- or self.args.list_profiles
425
- or self.args.show_config
426
- or self.args.list_config
427
- or self.args.list_drivers
428
- or self.args.list_plugins
429
- or self.args.list_plugins_available
430
- or self.args.list_resources
431
- or self.args.ping
432
- ):
433
- self._maybe_print_verbose_provider_model()
434
- handle_getter(self.args)
435
- return
436
- # Handle /rwx prefix for enabling all permissions
437
- if self.args.user_prompt and self.args.user_prompt[0] == "/rwx":
438
- self.args.read = True
439
- self.args.write = True
440
- self.args.exec = True
441
- # Remove the /rwx prefix from the prompt
442
- self.args.user_prompt = self.args.user_prompt[1:]
443
-
444
- # If running in single shot mode and --profile is not provided, default to 'developer' profile
445
- # Skip profile selection for list commands that don't need it
446
- # Also skip if interactive mode is enabled (forces chat mode)
447
- if get_prompt_mode(self.args) == "single_shot" and not getattr(
448
- self.args, "profile", None
449
- ):
450
- self.args.profile = "developer"
451
- provider = self._get_provider_or_default()
452
- if provider is None:
453
- print(
454
- "Error: No provider selected and no provider found in config. Please set a provider using '-p PROVIDER', '--set provider=name', or configure a provider."
455
- )
456
- sys.exit(1)
457
- modifiers = self.collect_modifiers()
458
- self._maybe_print_verbose_modifiers(modifiers)
459
- setup_event_logger_if_needed(self.args)
460
- inject_debug_event_bus_if_needed(self.args)
461
- provider, llm_driver_config, agent_role = prepare_llm_driver_config(
462
- self.args, modifiers
463
- )
464
- if provider is None or llm_driver_config is None:
465
- return
466
- self._maybe_print_verbose_llm_config(llm_driver_config, run_mode)
467
- if run_mode == RunMode.RUN:
468
- self._maybe_print_verbose_run_mode()
469
- # DEBUG: Print exec_enabled propagation at main_cli
470
-
471
- handle_runner(
472
- self.args,
473
- provider,
474
- llm_driver_config,
475
- agent_role,
476
- verbose_tools=self.args.verbose_tools,
477
- )
478
-
479
- def _run_set_mode(self):
480
- if handle_api_key_set(self.args):
481
- return True
482
- if handle_set(self.args):
483
- return True
484
- from janito.cli.core.unsetters import handle_unset
485
-
486
- if handle_unset(self.args):
487
- return True
488
- return False
489
-
490
- def _get_provider_or_default(self):
491
- provider = self.args.provider
492
- if provider is None:
493
- from janito.provider_config import get_config_provider
494
-
495
- provider = get_config_provider()
496
- return provider
497
-
498
- def _maybe_print_verbose_modifiers(self, modifiers):
499
- if self.args.verbose:
500
- from janito.cli.verbose_output import print_verbose_info
501
-
502
- print_verbose_info("Modifiers collected", modifiers, style="blue")
503
-
504
- def _maybe_print_verbose_provider_model(self):
505
- if self.args.verbose:
506
- from janito.cli.verbose_output import print_verbose_info
507
-
508
- print_verbose_info(
509
- "Validated provider/model",
510
- f"Provider: {self.args.provider} | Model: {self.args.model}",
511
- style="blue",
512
- )
513
-
514
- def _maybe_print_verbose_llm_config(self, llm_driver_config, run_mode):
515
- if self.args.verbose:
516
- from janito.cli.verbose_output import print_verbose_info
517
-
518
- print_verbose_info("LLMDriverConfig", llm_driver_config, style="cyan")
519
- print_verbose_info(
520
- "Dispatch branch", run_mode, style="cyan", align_content=True
521
- )
522
-
523
- def _maybe_print_verbose_run_mode(self):
524
- if self.args.verbose:
525
- from janito.cli.verbose_output import print_verbose_info
526
-
527
- print_verbose_info(
528
- "Run mode", get_prompt_mode(self.args), style="cyan", align_content=True
529
- )
530
-
531
-
532
- if __name__ == "__main__":
533
- cli = JanitoCLI()
534
- cli.run()
1
+ import argparse
2
+ import sys
3
+ import enum
4
+ from janito.cli.core.setters import handle_api_key_set, handle_set
5
+ from janito.cli.core.getters import handle_getter
6
+ from janito.cli.core.runner import (
7
+ prepare_llm_driver_config,
8
+ handle_runner,
9
+ get_prompt_mode,
10
+ )
11
+ from janito.cli.core.event_logger import (
12
+ setup_event_logger_if_needed,
13
+ inject_debug_event_bus_if_needed,
14
+ )
15
+
16
+
17
+ definition = [
18
+ (
19
+ ["-u", "--unrestricted"],
20
+ {
21
+ "action": "store_true",
22
+ "help": "Unrestricted mode: disable path security and URL whitelist restrictions (DANGEROUS)",
23
+ },
24
+ ),
25
+ (
26
+ ["--multi"],
27
+ {
28
+ "action": "store_true",
29
+ "help": "Start chat mode with multi-line input as default (no need for /multi command)",
30
+ },
31
+ ),
32
+ (
33
+ ["--profile"],
34
+ {
35
+ "metavar": "PROFILE",
36
+ "help": "Select the profile name for the system prompt (e.g. 'developer').",
37
+ "default": None,
38
+ },
39
+ ),
40
+ (
41
+ ["--developer"],
42
+ {
43
+ "action": "store_true",
44
+ "help": "Start with the Python developer profile (equivalent to --profile 'Developer')",
45
+ },
46
+ ),
47
+ (
48
+ ["--market"],
49
+ {
50
+ "action": "store_true",
51
+ "help": "Start with the Market Analyst profile (equivalent to --profile 'Market Analyst')",
52
+ },
53
+ ),
54
+ (
55
+ ["-W", "--workdir"],
56
+ {
57
+ "metavar": "WORKDIR",
58
+ "help": "Working directory to chdir to before tool execution",
59
+ "default": None,
60
+ },
61
+ ),
62
+ (
63
+ ["--verbose-api"],
64
+ {
65
+ "action": "store_true",
66
+ "help": "Print API calls and responses of LLM driver APIs for debugging/tracing.",
67
+ },
68
+ ),
69
+ (
70
+ ["--verbose-tools"],
71
+ {
72
+ "action": "store_true",
73
+ "help": "Print info messages for tool execution in tools adapter.",
74
+ },
75
+ ),
76
+ (
77
+ ["--verbose-agent"],
78
+ {
79
+ "action": "store_true",
80
+ "help": "Print info messages for agent event and message part handling.",
81
+ },
82
+ ),
83
+ (
84
+ ["-z", "--zero"],
85
+ {
86
+ "action": "store_true",
87
+ "help": "IDE zero mode: disables system prompt & all tools for raw LLM interaction",
88
+ },
89
+ ),
90
+ (
91
+ ["-x", "--exec"],
92
+ {
93
+ "action": "store_true",
94
+ "help": "Enable execution/run tools (allows running code or shell tools from the CLI)",
95
+ },
96
+ ),
97
+ (
98
+ ["-r", "--read"],
99
+ {
100
+ "action": "store_true",
101
+ "help": "Enable tools that require read permissions",
102
+ },
103
+ ),
104
+ (
105
+ ["-w", "--write"],
106
+ {
107
+ "action": "store_true",
108
+ "help": "Enable tools that require write permissions",
109
+ },
110
+ ),
111
+ (["--unset"], {"metavar": "KEY", "help": "Unset (remove) a config key"}),
112
+ (["--version"], {"action": "version", "version": None}),
113
+ (["--list-tools"], {"action": "store_true", "help": "List all registered tools"}),
114
+ (["--show-config"], {"action": "store_true", "help": "Show the current config"}),
115
+ (
116
+ ["--list-config"],
117
+ {"action": "store_true", "help": "List all configuration files"},
118
+ ),
119
+ (
120
+ ["--list-profiles"],
121
+ {"action": "store_true", "help": "List available system prompt profiles"},
122
+ ),
123
+ (
124
+ ["--list-providers"],
125
+ {"action": "store_true", "help": "List supported LLM providers"},
126
+ ),
127
+ (
128
+ ["--ping"],
129
+ {
130
+ "action": "store_true",
131
+ "help": "Ping/test connectivity for all providers (use with --list-providers)",
132
+ },
133
+ ),
134
+ (
135
+ ["--list-drivers"],
136
+ {
137
+ "action": "store_true",
138
+ "help": "List available LLM drivers and their dependencies",
139
+ },
140
+ ),
141
+ (
142
+ ["--region-info"],
143
+ {
144
+ "action": "store_true",
145
+ "help": "Show current region information and location",
146
+ },
147
+ ),
148
+ (
149
+ ["--list-providers-region"],
150
+ {
151
+ "action": "store_true",
152
+ "help": "List all providers with their regional API information",
153
+ },
154
+ ),
155
+ (
156
+ ["-l", "--list-models"],
157
+ {"action": "store_true", "help": "List all supported models"},
158
+ ),
159
+ (
160
+ ["--set-api-key"],
161
+ {
162
+ "metavar": "API_KEY",
163
+ "help": "Set API key for the provider (requires -p PROVIDER)",
164
+ },
165
+ ),
166
+ (["--set"], {"metavar": "KEY=VALUE", "help": "Set a config key"}),
167
+ (["-s", "--system"], {"metavar": "SYSTEM_PROMPT", "help": "Set a system prompt"}),
168
+ (
169
+ ["-S", "--show-system"],
170
+ {
171
+ "action": "store_true",
172
+ "help": "Show the resolved system prompt for the main agent",
173
+ },
174
+ ),
175
+ (["-p", "--provider"], {"metavar": "PROVIDER", "help": "Select the provider"}),
176
+ (["-m", "--model"], {"metavar": "MODEL", "help": "Select the model (can use model@provider syntax)"}),
177
+ (
178
+ ["-t", "--temperature"],
179
+ {"type": float, "default": None, "help": "Set the temperature"},
180
+ ),
181
+ (
182
+ ["-v", "--verbose"],
183
+ {"action": "store_true", "help": "Print extra information before answering"},
184
+ ),
185
+ (
186
+ ["-R", "--raw"],
187
+ {
188
+ "action": "store_true",
189
+ "help": "Print the raw JSON response from the OpenAI API (if applicable)",
190
+ },
191
+ ),
192
+ (
193
+ ["--effort"],
194
+ {
195
+ "choices": ["low", "medium", "high", "none"],
196
+ "default": None,
197
+ "help": "Set the reasoning effort for models that support it (low, medium, high, none)",
198
+ },
199
+ ),
200
+
201
+ (
202
+ ["-i", "--interactive"],
203
+ {
204
+ "action": "store_true",
205
+ "help": "Signal that this is an interactive chat session",
206
+ },
207
+ ),
208
+ (["user_prompt"], {"nargs": argparse.REMAINDER, "help": "Prompt to submit"}),
209
+ (
210
+ ["-e", "--event-log"],
211
+ {"action": "store_true", "help": "Enable event logging to the system bus"},
212
+ ),
213
+ (
214
+ ["--event-debug"],
215
+ {
216
+ "action": "store_true",
217
+ "help": "Print debug info on event subscribe/submit methods",
218
+ },
219
+ ),
220
+ (
221
+ ["-c", "--config"],
222
+ {
223
+ "metavar": "NAME",
224
+ "help": "Use custom configuration file ~/.janito/configs/NAME.json instead of default config.json",
225
+ },
226
+ ),
227
+ (
228
+ ["--list-plugins"],
229
+ {"action": "store_true", "help": "List all loaded plugins"},
230
+ ),
231
+ (
232
+ ["--list-plugins-available"],
233
+ {"action": "store_true", "help": "List all available plugins"},
234
+ ),
235
+ (
236
+ ["--list-resources"],
237
+ {
238
+ "action": "store_true",
239
+ "help": "List all resources (tools, commands, config) from loaded plugins",
240
+ },
241
+ ),
242
+ ]
243
+
244
+ MODIFIER_KEYS = [
245
+ "provider",
246
+ "model",
247
+ "profile",
248
+ "developer",
249
+ "market",
250
+ "system",
251
+ "temperature",
252
+ "verbose",
253
+ "raw",
254
+ "verbose_api",
255
+ "verbose_tools",
256
+ "exec",
257
+ "read",
258
+ "write",
259
+ "emoji",
260
+ "interactive",
261
+ ]
262
+ SETTER_KEYS = ["set", "set_provider", "set_api_key", "unset"]
263
+ GETTER_KEYS = [
264
+ "show_config",
265
+ "list_providers",
266
+ "list_profiles",
267
+ "list_models",
268
+ "list_tools",
269
+ "list_config",
270
+ "list_drivers",
271
+ "region_info",
272
+ "list_providers_region",
273
+ "ping",
274
+ ]
275
+ GETTER_KEYS = [
276
+ "show_config",
277
+ "list_providers",
278
+ "list_profiles",
279
+ "list_models",
280
+ "list_tools",
281
+ "list_config",
282
+ "list_drivers",
283
+ "region_info",
284
+ "list_providers_region",
285
+ "ping",
286
+ ]
287
+
288
+
289
+ class RunMode(enum.Enum):
290
+ GET = "get"
291
+ SET = "set"
292
+ RUN = "run"
293
+
294
+
295
+ class JanitoCLI:
296
+ def __init__(self):
297
+ import janito.tools
298
+
299
+ self.parser = argparse.ArgumentParser(
300
+ description="Janito CLI - A tool for running LLM-powered workflows from the command line."
301
+ "\n\nExample usage: janito -p moonshot -m kimi-k1-8k 'Your prompt here'\n"
302
+ "Example usage: janito -m model@provider 'Your prompt here'\n\n"
303
+ "Use -m or --model to set the model for the session.",
304
+ )
305
+ self._define_args()
306
+ self.args = self.parser.parse_args()
307
+ self._parse_model_provider_syntax()
308
+ self._set_all_arg_defaults()
309
+ # Support custom config file via -c/--config
310
+ if getattr(self.args, "config", None):
311
+ from janito import config as global_config
312
+ from janito.config_manager import ConfigManager
313
+ import sys
314
+ import importlib
315
+
316
+ config_name = self.args.config
317
+ # Re-initialize the global config singleton
318
+ new_config = ConfigManager(config_name=config_name)
319
+ # Ensure the config path is updated when the singleton already existed
320
+ from pathlib import Path
321
+
322
+ new_config.config_path = (
323
+ Path.home() / ".janito" / "configs" / f"{config_name}.json"
324
+ )
325
+ # Reload config from the selected file
326
+ new_config._load_file_config()
327
+ # Patch the global singleton reference
328
+ import janito.config as config_module
329
+
330
+ config_module.config = new_config
331
+ sys.modules["janito.config"].config = new_config
332
+ # Support reading prompt from stdin if no user_prompt is given
333
+ import sys
334
+
335
+ if not sys.stdin.isatty():
336
+ stdin_input = sys.stdin.read().strip()
337
+ if stdin_input:
338
+ if self.args.user_prompt and len(self.args.user_prompt) > 0:
339
+ # Prefix the prompt argument to the stdin input
340
+ combined = " ".join(self.args.user_prompt) + " " + stdin_input
341
+ self.args.user_prompt = [combined]
342
+ else:
343
+ self.args.user_prompt = [stdin_input]
344
+ from janito.cli.rich_terminal_reporter import RichTerminalReporter
345
+
346
+ self.rich_reporter = RichTerminalReporter(raw_mode=self.args.raw)
347
+
348
+ def _parse_model_provider_syntax(self):
349
+ """Parse -m model@provider syntax to split into model and provider."""
350
+ model = getattr(self.args, "model", None)
351
+ if model and "@" in model:
352
+ model_part, provider_part = model.rsplit("@", 1)
353
+ if model_part and provider_part:
354
+ # Only set provider if not already explicitly set via -p flag
355
+ if getattr(self.args, "provider", None) is None:
356
+ self.args.provider = provider_part
357
+ # Always set the model part (without the @provider suffix)
358
+ self.args.model = model_part
359
+
360
+ def _define_args(self):
361
+ for argnames, argkwargs in definition:
362
+ # Patch version argument dynamically with real version
363
+ if "--version" in argnames:
364
+ from janito import __version__ as janito_version
365
+
366
+ argkwargs["version"] = f"Janito {janito_version}"
367
+ self.parser.add_argument(*argnames, **argkwargs)
368
+
369
+ def _set_all_arg_defaults(self):
370
+ # Gather all possible keys from definition, MODIFIER_KEYS, SETTER_KEYS, GETTER_KEYS
371
+ all_keys = set()
372
+ for argnames, argkwargs in definition:
373
+ for name in argnames:
374
+ key = name.lstrip("-").replace("-", "_")
375
+ all_keys.add(key)
376
+ all_keys.update(MODIFIER_KEYS)
377
+ all_keys.update(SETTER_KEYS)
378
+ all_keys.update(GETTER_KEYS)
379
+ # Set defaults for all keys if not present
380
+ for key in all_keys:
381
+ if not hasattr(self.args, key):
382
+ setattr(self.args, key, None)
383
+
384
+ def collect_modifiers(self):
385
+ modifiers = {
386
+ k: getattr(self.args, k)
387
+ for k in MODIFIER_KEYS
388
+ if getattr(self.args, k, None) is not None
389
+ }
390
+
391
+ return modifiers
392
+
393
+ def classify(self):
394
+ if any(getattr(self.args, k, None) for k in SETTER_KEYS):
395
+ return RunMode.SET
396
+ if any(getattr(self.args, k, None) for k in GETTER_KEYS):
397
+ return RunMode.GET
398
+ return RunMode.RUN
399
+
400
+ def run(self):
401
+ # Handle --show-system/-S before anything else
402
+ if getattr(self.args, "show_system", False):
403
+ from janito.cli.cli_commands.show_system_prompt import (
404
+ handle_show_system_prompt,
405
+ )
406
+
407
+ handle_show_system_prompt(self.args)
408
+ return
409
+ run_mode = self.classify()
410
+ if run_mode == RunMode.SET:
411
+ if self._run_set_mode():
412
+ return
413
+ # Special handling: provider is not required for list commands
414
+ if run_mode == RunMode.GET and (
415
+ self.args.list_providers
416
+ or self.args.list_models
417
+ or self.args.list_tools
418
+ or self.args.list_profiles
419
+ or self.args.show_config
420
+ or self.args.list_config
421
+ or self.args.list_drivers
422
+ or self.args.list_plugins
423
+ or self.args.list_plugins_available
424
+ or self.args.list_resources
425
+ or self.args.ping
426
+ ):
427
+ self._maybe_print_verbose_provider_model()
428
+ handle_getter(self.args)
429
+ return
430
+ # Handle /rwx prefix for enabling all permissions
431
+ if self.args.user_prompt and self.args.user_prompt[0] == "/rwx":
432
+ self.args.read = True
433
+ self.args.write = True
434
+ self.args.exec = True
435
+ # Remove the /rwx prefix from the prompt
436
+ self.args.user_prompt = self.args.user_prompt[1:]
437
+
438
+ # If running in single shot mode and --profile is not provided, default to 'developer' profile
439
+ # Skip profile selection for list commands that don't need it
440
+ # Also skip if interactive mode is enabled (forces chat mode)
441
+ if get_prompt_mode(self.args) == "single_shot" and not getattr(
442
+ self.args, "profile", None
443
+ ):
444
+ self.args.profile = "developer"
445
+ provider = self._get_provider_or_default()
446
+ if provider is None:
447
+ print(
448
+ "Error: No provider selected and no provider found in config. Please set a provider using '-p PROVIDER', '--set provider=name', or configure a provider."
449
+ )
450
+ sys.exit(1)
451
+ modifiers = self.collect_modifiers()
452
+ self._maybe_print_verbose_modifiers(modifiers)
453
+ setup_event_logger_if_needed(self.args)
454
+ inject_debug_event_bus_if_needed(self.args)
455
+ provider, llm_driver_config, agent_role = prepare_llm_driver_config(
456
+ self.args, modifiers
457
+ )
458
+ if provider is None or llm_driver_config is None:
459
+ return
460
+ self._maybe_print_verbose_llm_config(llm_driver_config, run_mode)
461
+ if run_mode == RunMode.RUN:
462
+ self._maybe_print_verbose_run_mode()
463
+ # DEBUG: Print exec_enabled propagation at main_cli
464
+
465
+ handle_runner(
466
+ self.args,
467
+ provider,
468
+ llm_driver_config,
469
+ agent_role,
470
+ verbose_tools=self.args.verbose_tools,
471
+ )
472
+
473
+ def _run_set_mode(self):
474
+ if handle_api_key_set(self.args):
475
+ return True
476
+ if handle_set(self.args):
477
+ return True
478
+ from janito.cli.core.unsetters import handle_unset
479
+
480
+ if handle_unset(self.args):
481
+ return True
482
+ return False
483
+
484
+ def _get_provider_or_default(self):
485
+ provider = self.args.provider
486
+ if provider is None:
487
+ from janito.provider_config import get_config_provider
488
+
489
+ provider = get_config_provider()
490
+ return provider
491
+
492
+ def _maybe_print_verbose_modifiers(self, modifiers):
493
+ if self.args.verbose:
494
+ from janito.cli.verbose_output import print_verbose_info
495
+
496
+ print_verbose_info("Modifiers collected", modifiers, style="blue")
497
+
498
+ def _maybe_print_verbose_provider_model(self):
499
+ if self.args.verbose:
500
+ from janito.cli.verbose_output import print_verbose_info
501
+
502
+ print_verbose_info(
503
+ "Validated provider/model",
504
+ f"Provider: {self.args.provider} | Model: {self.args.model}",
505
+ style="blue",
506
+ )
507
+
508
+ def _maybe_print_verbose_llm_config(self, llm_driver_config, run_mode):
509
+ if self.args.verbose:
510
+ from janito.cli.verbose_output import print_verbose_info
511
+
512
+ print_verbose_info("LLMDriverConfig", llm_driver_config, style="cyan")
513
+ print_verbose_info(
514
+ "Dispatch branch", run_mode, style="cyan", align_content=True
515
+ )
516
+
517
+ def _maybe_print_verbose_run_mode(self):
518
+ if self.args.verbose:
519
+ from janito.cli.verbose_output import print_verbose_info
520
+
521
+ print_verbose_info(
522
+ "Run mode", get_prompt_mode(self.args), style="cyan", align_content=True
523
+ )
524
+
525
+
526
+ if __name__ == "__main__":
527
+ cli = JanitoCLI()
528
+ cli.run()