cmdbox 0.6.2.3__py3-none-any.whl → 0.6.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.
cmdbox/app/auth/signin.py CHANGED
@@ -931,7 +931,8 @@ async def create_request_scope(req:Request=None, res:Response=None, websocket:We
931
931
  sess = None
932
932
  if req is not None:
933
933
  sess = req.session if hasattr(req, 'session') else None
934
- request_scope.set(dict(req=req, res=res, websocket=websocket))
934
+ from cmdbox.app.web import Web
935
+ request_scope.set(dict(req=req, res=res, websocket=websocket, web=Web.getInstance()))
935
936
  try:
936
937
  yield # リクエストの処理
937
938
  finally:
@@ -954,4 +955,4 @@ def get_request_scope() -> Dict[str, Any]:
954
955
  Returns:
955
956
  Dict[str, Any]: リクエストとレスポンスとWebSocket接続
956
957
  """
957
- return request_scope.get() if request_scope.get() is not None else dict(req=None, res=None, session=None, websocket=None)
958
+ return request_scope.get() if request_scope.get() is not None else dict(req=None, res=None, session=None, websocket=None, web=None)
cmdbox/app/common.py CHANGED
@@ -13,6 +13,7 @@ import asyncio
13
13
  import datetime
14
14
  import logging
15
15
  import logging.config
16
+ import locale
16
17
  import hashlib
17
18
  import inspect
18
19
  import json
@@ -498,19 +499,23 @@ def print_format(data:dict, format:bool, tm:float, output_json:str=None, output_
498
499
  Returns:
499
500
  str: 生成された文字列
500
501
  """
501
- if type(data) is dict and "success" in data and type(data["success"]) is dict and "performance" in data["success"] and type(data["success"]["performance"]) is list and pf is not None:
502
+ is_data_dict = isinstance(data, dict)
503
+ in_data_success = is_data_dict and 'success' in data
504
+ is_success_dict = in_data_success and isinstance(data['success'], dict)
505
+ is_data_list = isinstance(data, list)
506
+ if is_success_dict and "performance" in data["success"] and isinstance(data["success"]["performance"], list) and pf is not None:
502
507
  data["success"]["performance"] += pf
503
508
  txt = ''
504
509
  if format:
505
- if 'success' in data and type(data["success"]) is dict:
506
- data = data['success']['data'] if 'data' in data['success'] else data['success']
507
- if type(data) == list:
508
- txt = tabulate(data, headers='keys', tablefmt=tablefmt)
509
- elif type(data) == dict:
510
- txt = tabulate([data], headers='keys', tablefmt=tablefmt)
510
+ if is_success_dict:
511
+ _data = data['success']['data'] if 'data' in data['success'] else data['success']
512
+ if isinstance(_data, list):
513
+ txt = tabulate(_data, headers='keys', tablefmt=tablefmt)
514
+ elif isinstance(_data, dict):
515
+ txt = tabulate([_data], headers='keys', tablefmt=tablefmt)
511
516
  else:
512
- txt = str(data)
513
- elif type(data) == list:
517
+ txt = str(_data)
518
+ elif is_data_list:
514
519
  txt = tabulate(data, headers='keys', tablefmt=tablefmt)
515
520
  else:
516
521
  txt = tabulate([data], headers='keys', tablefmt=tablefmt)
@@ -521,13 +526,13 @@ def print_format(data:dict, format:bool, tm:float, output_json:str=None, output_
521
526
  except BrokenPipeError:
522
527
  pass
523
528
  else:
524
- if 'success' in data and type(data['success']) is dict:
529
+ if is_success_dict:
525
530
  if "performance" not in data["success"]:
526
531
  data["success"]["performance"] = []
527
532
  performance = data["success"]["performance"]
528
533
  performance.append(dict(key="app_proc", val=f"{time.perf_counter() - tm:.03f}s"))
529
534
  try:
530
- if type(data) == dict:
535
+ if is_data_dict:
531
536
  txt = json.dumps(data, default=default_json_enc, ensure_ascii=False)
532
537
  else:
533
538
  txt = data
@@ -685,6 +690,17 @@ def chopdq(target:str):
685
690
  return target
686
691
  return target[1:-1] if target.startswith('"') and target.endswith('"') else target
687
692
 
693
+ def is_japan() -> bool:
694
+ """
695
+ 日本語環境かどうかを判定します
696
+
697
+ Returns:
698
+ bool: 日本語環境ならTrue、そうでなければFalse
699
+ """
700
+ language, _ = locale.getlocale()
701
+ is_japan = language.find('Japan') >= 0 or language.find('ja_JP') >= 0
702
+ return is_japan
703
+
688
704
  def is_event_loop_running() -> bool:
689
705
  """
690
706
  イベントループが実行中かどうかを取得します。
@@ -721,7 +737,7 @@ def exec_sync(apprun, logger:logging.Logger, args:argparse.Namespace, tm:float,
721
737
  th.start()
722
738
  th.join()
723
739
  result = ctx[0] if ctx else None
724
- return 0, result, None
740
+ return result
725
741
  return asyncio.run(apprun(logger, args, tm, pf))
726
742
  return apprun(logger, args, tm, pf)
727
743
 
@@ -144,6 +144,12 @@ class AuditSearch(audit_base.AuditBase):
144
144
  Returns:
145
145
  Tuple[int, Dict[str, Any], Any]: 終了コード, 結果, オブジェクト
146
146
  """
147
+ if not hasattr(args, 'format') or not args.format:
148
+ args.format = False
149
+ if not hasattr(args, 'output_json') or not args.output_json:
150
+ args.output_json = None
151
+ if not hasattr(args, 'output_json_append') or not args.output_json_append:
152
+ args.output_json_append = False
147
153
  if args.svname is None:
148
154
  msg = dict(warn=f"Please specify the --svname option.")
149
155
  common.print_format(msg, args.format, tm, None, False, pf=pf)
@@ -0,0 +1,154 @@
1
+ from cmdbox.app import common, client, feature, filer
2
+ from cmdbox.app.commons import convert, redis_client
3
+ from cmdbox.app.options import Options
4
+ from pathlib import Path
5
+ from typing import Dict, Any, Tuple, List, Union
6
+ import argparse
7
+ import logging
8
+ import requests
9
+ import urllib.parse
10
+
11
+
12
+ class ClientHttp(feature.ResultEdgeFeature):
13
+ def get_mode(self) -> Union[str, List[str]]:
14
+ """
15
+ この機能のモードを返します
16
+
17
+ Returns:
18
+ Union[str, List[str]]: モード
19
+ """
20
+ return 'client'
21
+
22
+ def get_cmd(self):
23
+ """
24
+ この機能のコマンドを返します
25
+
26
+ Returns:
27
+ str: コマンド
28
+ """
29
+ return 'http'
30
+
31
+ def get_option(self):
32
+ """
33
+ この機能のオプションを返します
34
+
35
+ Returns:
36
+ Dict[str, Any]: オプション
37
+ """
38
+ return dict(
39
+ use_redis=self.USE_REDIS_MEIGHT, nouse_webmode=False,
40
+ description_ja="HTTPサーバーに対してリクエストを送信し、レスポンスを取得します。",
41
+ description_en="Sends a request to the HTTP server and gets a response.",
42
+ choice=[
43
+ dict(opt="url", type=Options.T_STR, default=None, required=True, multi=False, hide=False, choice=None,
44
+ description_ja="リクエスト先URLを指定します。",
45
+ description_en="Specify the URL to request."),
46
+ dict(opt="proxy", type=Options.T_STR, default="no", required=False, multi=False, hide=False, choice=['no', 'yes'],
47
+ choice_show=dict(no=["send_method", "send_content_type", "send_apikey", "send_header",],
48
+ yes=[]),
49
+ description_ja="webモードで呼び出された場合、受信したリクエストパラメータをリクエスト先URLに送信するかどうかを指定します。",
50
+ description_en="Specifies whether or not to send the received request parameters to the destination URL when invoked in web mode."),
51
+ dict(opt="send_method", type=Options.T_STR, default="GET", required=True, multi=False, hide=False,
52
+ choice=['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'],
53
+ description_ja="リクエストメソッドを指定します。",
54
+ description_en="Specifies the request method."),
55
+ dict(opt="send_content_type", type=Options.T_STR, default=None, required=False, multi=False, hide=False,
56
+ choice=['', 'application/octet-stream', 'application/json', 'multipart/form-data'],
57
+ choice_show={'application/octet-stream':["send_param", "send_data",],
58
+ 'application/json':["send_data",],
59
+ 'multipart/form-data':["send_param",],},
60
+ description_ja="送信するデータのContent-Typeを指定します。",
61
+ description_en="Specifies the Content-Type of the data to be sent."),
62
+ dict(opt="send_apikey", type=Options.T_TEXT, default=None, required=False, multi=False, hide=False, choice=None,
63
+ description_ja="リクエスト先の認証で使用するAPIキーを指定します。",
64
+ description_en="Specify the API key to be used for authentication of the request destination."),
65
+ dict(opt="send_header", type=Options.T_DICT, default=None, required=False, multi=True, hide=False, choice=None,
66
+ description_ja="リクエストヘッダーを指定します。",
67
+ description_en="Specifies the request header."),
68
+ dict(opt="send_param", type=Options.T_DICT, default=None, required=False, multi=True, hide=False, choice=None,
69
+ description_ja="送信するパラメータを指定します。",
70
+ description_en="Specifies parameters to be sent."),
71
+ dict(opt="send_data", type=Options.T_TEXT, default=None, required=False, multi=False, hide=False, choice=None,
72
+ description_ja="送信するデータを指定します。",
73
+ description_en="Specifies the data to be sent."),
74
+ dict(opt="send_verify", type=Options.T_BOOL, default=False, required=False, multi=False, hide=True, choice=[False, True],
75
+ description_ja="レスポンスを受け取るまでのタイムアウトを指定します。",
76
+ description_en="Specifies the timeout before a response is received."),
77
+ dict(opt="send_timeout", type=Options.T_INT, default=30, required=False, multi=False, hide=True, choice=None,
78
+ description_ja="レスポンスを受け取るまでのタイムアウトを指定します。",
79
+ description_en="Specifies the timeout before a response is received."),
80
+ dict(opt="stdout_log", type=Options.T_BOOL, default=True, required=False, multi=False, hide=True, choice=[True, False],
81
+ description_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力をConsole logに出力します。",
82
+ description_en="Available only in GUI mode. Outputs standard output during command execution to Console log."),
83
+ dict(opt="capture_stdout", type=Options.T_BOOL, default=True, required=False, multi=False, hide=True, choice=[True, False],
84
+ description_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力をキャプチャーし、実行結果画面に表示します。",
85
+ description_en="Available only in GUI mode. Captures standard output during command execution and displays it on the execution result screen."),
86
+ dict(opt="capture_maxsize", type=Options.T_INT, default=self.DEFAULT_CAPTURE_MAXSIZE, required=False, multi=False, hide=True, choice=None,
87
+ description_ja="GUIモードでのみ使用可能です。コマンド実行時の標準出力の最大キャプチャーサイズを指定します。",
88
+ description_en="Available only in GUI mode. Specifies the maximum capture size of standard output when executing commands."),
89
+ ]
90
+ )
91
+
92
+ async def apprun(self, logger:logging.Logger, args:argparse.Namespace, tm:float, pf:List[Dict[str, float]]=[]) -> Tuple[int, Dict[str, Any], Any]:
93
+ """
94
+ この機能の実行を行います
95
+
96
+ Args:
97
+ logger (logging.Logger): ロガー
98
+ args (argparse.Namespace): 引数
99
+ tm (float): 実行開始時間
100
+ pf (List[Dict[str, float]]): 呼出元のパフォーマンス情報
101
+
102
+ Returns:
103
+ Tuple[int, Dict[str, Any], Any]: 終了コード, 結果, オブジェクト
104
+ """
105
+ if args.url is None:
106
+ msg = dict(warn=f"Please specify the --url option.")
107
+ common.print_format(msg, args.format, tm, None, False, pf=pf)
108
+ return 1, msg, None
109
+ query_param = {}
110
+ if args.proxy == 'yes':
111
+ from cmdbox.app.auth import signin
112
+ from fastapi import Request
113
+ scope = signin.get_request_scope()
114
+ if scope is None:
115
+ msg = dict(warn=f"Request scope is not set. Please set the request scope.")
116
+ common.print_format(msg, args.format, tm, None, False, pf=pf)
117
+ return 1, msg, None
118
+ req:Request = scope['req']
119
+ args.send_method = req.method
120
+ args.send_content_type = req.headers.get('Content-Type', None)
121
+ args.send_apikey = req.headers.get('Authorization', '').replace('Bearer ', '')
122
+ args.send_header = {k:v for k, v in req.headers.items() \
123
+ if k.lower() not in ['connection', 'proxy-authorization', 'proxy-connection', 'keep-alive',
124
+ 'transfer-encoding', 'te', 'trailer', 'upgrade', 'content-length']}
125
+ query_param = {k:v for k, v in req.query_params.items()}
126
+ args.send_data = await req.body()
127
+
128
+ url = urllib.parse.urlparse(args.url)
129
+ query = urllib.parse.parse_qs(url.query)
130
+ query = {**query, **query_param} if query else query_param
131
+ args.url = urllib.parse.urlunparse((url.scheme, url.netloc, url.path, url.params, urllib.parse.urlencode(query), url.fragment))
132
+ args.send_header = {**args.send_header, 'Authorization':f'Bearer {args.send_apikey}'} if args.send_apikey else args.send_header
133
+ res = requests.request(method=args.send_method, url=args.url, headers=args.send_header,
134
+ verify=args.send_verify, timeout=args.send_timeout, allow_redirects=True,
135
+ data=args.send_data, params=args.send_param)
136
+ if res.status_code != 200:
137
+ msg = dict(error=f"Request failed with status code {res.status_code}.")
138
+ common.print_format(msg, False, tm, None, False, pf=pf)
139
+ return 1, msg, None
140
+ content_type = res.headers.get('Content-Type', '')
141
+ if content_type.startswith('application/json'):
142
+ try:
143
+ msg = res.json()
144
+ except ValueError as e:
145
+ msg = res.text
146
+ common.print_format(msg, False, tm, None, False, pf=pf)
147
+ return 0, msg, None
148
+ elif content_type.startswith('text/'):
149
+ msg = res.text
150
+ common.print_format(msg, False, tm, None, False, pf=pf)
151
+ return 0, msg, None
152
+ common.print_format(res.content, False, tm, None, False, pf=pf)
153
+
154
+ return 0, res.content, None
@@ -1,4 +1,4 @@
1
- from cmdbox.app import common, feature
1
+ from cmdbox.app import common, feature, options
2
2
  from cmdbox.app.auth import signin
3
3
  from cmdbox.app.options import Options
4
4
  from pathlib import Path
@@ -45,10 +45,10 @@ class CmdList(feature.OneshotResultEdgeFeature):
45
45
  dict(opt="kwd", type=Options.T_STR, default=None, required=False, multi=False, hide=False, choice=None,
46
46
  description_ja=f"検索したいコマンド名を指定します。中間マッチで検索します。",
47
47
  description_en=f"Specify the name of the command you want to search. Search with intermediate matches."),
48
- dict(opt="signin_file", type=Options.T_FILE, default=f".{self.ver.__appid__}/user_list.yml", required=False, multi=False, hide=True, choice=None, fileio="in",
48
+ dict(opt="signin_file", type=Options.T_FILE, default=f".{self.ver.__appid__}/user_list.yml", required=False, multi=False, hide=False, choice=None, fileio="in",
49
49
  description_ja="サインイン可能なユーザーとパスワードを記載したファイルを指定します。",
50
50
  description_en="Specify a file containing users and passwords with which they can signin."),
51
- dict(opt="groups", type=Options.T_STR, default=None, required=False, multi=True, hide=True, choice=None,
51
+ dict(opt="groups", type=Options.T_STR, default=None, required=False, multi=True, hide=False, choice=None,
52
52
  description_ja="`signin_file` を指定した場合に、このユーザーグループに許可されているコマンドリストを返すように指定します。",
53
53
  description_en="Specifies that `signin_file`, if specified, should return the list of commands allowed for this user group."),
54
54
  dict(opt="output_json", short="o", type=Options.T_FILE, default=None, required=False, multi=False, hide=True, choice=None, fileio="out",
@@ -92,11 +92,15 @@ class CmdList(feature.OneshotResultEdgeFeature):
92
92
  if not hasattr(self, 'signin_file_data') or self.signin_file_data is None:
93
93
  self.signin_file_data = signin.Signin.load_signin_file(args.signin_file, None, self=self)
94
94
  paths = glob.glob(str(Path(args.data) / ".cmds" / f"cmd-{kwd}.json"))
95
- ret = [common.loadopt(path, True) for path in paths]
96
- ret = sorted(ret, key=lambda cmd: cmd["title"])
97
- ret = [dict(title=r.get('title',''), mode=r.get('mode',''), cmd=r.get('cmd',''), description=r.get('description',''), tag=r.get('tag','')) for r in ret \
95
+ cmd_list = [common.loadopt(path, True) for path in paths]
96
+ cmd_list = sorted(cmd_list, key=lambda cmd: cmd["title"])
97
+ is_japan = common.is_japan()
98
+ options = Options.getInstance()
99
+ cmd_list = [dict(title=r.get('title',''), mode=r['mode'], cmd=r['cmd'],
100
+ description=r.get('description','') + options.get_cmd_attr(r['mode'], r['cmd'], 'description_ja' if is_japan else 'description_en'),
101
+ tag=r.get('tag','')) for r in cmd_list \
98
102
  if signin.Signin._check_cmd(self.signin_file_data, args.groups, r['mode'], r['cmd'], logger)]
99
- ret = dict(success=ret)
103
+ ret = dict(success=cmd_list)
100
104
 
101
105
  common.print_format(ret, args.format, tm, args.output_json, args.output_json_append, pf=pf)
102
106
 
@@ -183,7 +183,7 @@ class WebStart(feature.UnsupportEdgeFeature, agent_base.AgentBase):
183
183
  Returns:
184
184
  web.Web: Webオブジェクト
185
185
  """
186
- w = web.Web(logger, Path(args.data), appcls=self.appcls, ver=self.ver,
186
+ w = web.Web.getInstance(logger, Path(args.data), appcls=self.appcls, ver=self.ver,
187
187
  redis_host=args.host, redis_port=args.port, redis_password=args.password, svname=args.svname,
188
188
  client_only=args.client_only, doc_root=args.doc_root, gui_html=args.gui_html, filer_html=args.filer_html,
189
189
  result_html=args.result_html, users_html=args.users_html,
cmdbox/app/mcp.py CHANGED
@@ -4,6 +4,7 @@ from cmdbox.app.auth import signin
4
4
  from pathlib import Path
5
5
  from typing import Callable, List, Dict, Any, Tuple
6
6
  import argparse
7
+ import glob
7
8
  import logging
8
9
  import locale
9
10
  import json
@@ -35,14 +36,14 @@ class Mcp:
35
36
  self.ver = ver
36
37
  self.signin = sign
37
38
 
38
- def create_mcpserver(self, args:argparse.Namespace, tools:List[Any], web:Any) -> Any:
39
+ def create_mcpserver(self, logger:logging.Logger, args:argparse.Namespace, tools) -> Any:
39
40
  """
40
41
  mcpserverを作成します
41
42
 
42
43
  Args:
44
+ logger (logging.Logger): ロガー
43
45
  args (argparse.Namespace): 引数
44
- tools (List[Any]): ツールのリスト
45
- web (Any): Web関連のオブジェクト
46
+ tools (List[Callable]): ツールのリスト
46
47
 
47
48
  Returns:
48
49
  Any: FastMCP
@@ -60,13 +61,12 @@ class Mcp:
60
61
  issuer=issuer,
61
62
  audience=audience
62
63
  )
63
- mcp = FastMCP(name=self.ver.__appid__, tools=tools, auth=auth)
64
+ mcp = FastMCP(name=self.ver.__appid__, auth=auth, tools=tools)
64
65
  else:
65
66
  self.logger.info(f"Using BearerAuthProvider without public key, issuer, or audience.")
66
- mcp = FastMCP(name=self.ver.__appid__, tools=tools)
67
+ mcp = FastMCP(name=self.ver.__appid__)
67
68
  mcp.add_middleware(self.create_mw_logging(self.logger, args))
68
- mcp.add_middleware(self.create_mw_reqscope(self.logger, args, web))
69
- mcp.add_middleware(self.create_mw_toollist(self.logger, args))
69
+ mcp.add_middleware(self.create_mw_reqscope(self.logger, args))
70
70
  return mcp
71
71
 
72
72
  def create_session_service(self, args:argparse.Namespace) -> Any:
@@ -117,8 +117,7 @@ class Mcp:
117
117
  """
118
118
  if logger.level == logging.DEBUG:
119
119
  logger.debug(f"create_agent processing..")
120
- language, _ = locale.getlocale()
121
- is_japan = language.find('Japan') >= 0 or language.find('ja_JP') >= 0
120
+ is_japan = common.is_japan()
122
121
  description = f"{self.ver.__appid__}に登録されているコマンド提供"
123
122
  instruction = f"あなたはコマンドの意味を熟知しているエキスパートです。" + \
124
123
  f"ユーザーがコマンドを実行したいとき、あなたは以下の手順に従ってコマンドを確実に実行してください。\n" + \
@@ -335,6 +334,8 @@ class Mcp:
335
334
  func_txt += f' opt_path = opt["data"] / ".cmds" / f"cmd-{title}.json"\n'
336
335
  func_txt += f' opt.update(common.loadopt(opt_path))\n'
337
336
  func_txt += f' scope = signin.get_request_scope()\n'
337
+ func_txt += f' if logger.level == logging.DEBUG:\n'
338
+ func_txt += ' logger.debug(f"MCP Call scope={scope}")\n'
338
339
  func_txt += f' opt["mode"] = "{mode}"\n'
339
340
  func_txt += f' opt["cmd"] = "{cmd}"\n'
340
341
  func_txt += f' opt["format"] = False\n'
@@ -356,6 +357,8 @@ class Mcp:
356
357
  func_txt += f' feat = options.get_cmd_attr("{mode}", "{cmd}", "feature")\n'
357
358
  func_txt += f' args.groups = groups\n'
358
359
  func_txt += f' try:\n'
360
+ func_txt += f' if logger.level == logging.DEBUG:\n'
361
+ func_txt += ' logger.debug(f"MCP Call {feat}#apprun, args={args}")\n'
359
362
  func_txt += f' st, ret, _ = feat.apprun(logger, args, time.perf_counter(), [])\n'
360
363
  func_txt += f' return ret\n'
361
364
  func_txt += f' except Exception as e:\n'
@@ -364,133 +367,21 @@ class Mcp:
364
367
  func_txt += f'func_ctx.append({func_name})\n'
365
368
  return func_txt
366
369
 
367
- def create_tools(self, logger:logging.Logger, args:argparse.Namespace) -> List[Any]:
370
+ def create_tools(self, logger:logging.Logger, args:argparse.Namespace, extract_callable:bool) -> Any:
368
371
  """
369
372
  ツールリストを作成します
370
373
 
371
374
  Args:
372
375
  logger (logging.Logger): ロガー
373
376
  args (argparse.Namespace): 引数
374
-
375
- Returns:
376
- List[Any]: fastmcp.tools.FunctionToolのリスト
377
- """
378
- from fastmcp.tools import FunctionTool
379
- options = Options.getInstance()
380
- language, _ = locale.getlocale()
381
- is_japan = language.find('Japan') >= 0 or language.find('ja_JP') >= 0
382
- func_tools:List[FunctionTool] = []
383
- for mode in options.get_mode_keys():
384
- for cmd in options.get_cmd_keys(mode):
385
- if not options.get_cmd_attr(mode, cmd, 'use_agent'):
386
- continue
387
- # コマンドの説明と選択肢を取得
388
- description = options.get_cmd_attr(mode, cmd, 'description_ja' if is_japan else 'description_en')
389
- choices = options.get_cmd_choices(mode, cmd, False)
390
- func_name = f"{mode}_{cmd}"
391
- # 関数の定義を生成
392
- func_txt = self._create_func_txt(func_name, mode, cmd, is_japan, options)
393
- if logger.level == logging.DEBUG:
394
- logger.debug(f"generating agent tool: {func_name}")
395
- func_ctx = []
396
- # 関数を実行してコンテキストに追加
397
- exec(func_txt,
398
- dict(time=time,List=List, Path=Path, argparse=argparse, common=common, options=options, logging=logging, signin=signin,),
399
- dict(func_ctx=func_ctx))
400
- # 関数のスキーマを生成
401
- input_schema = dict(
402
- type="object",
403
- properties={o['opt']: self._to_schema(o, is_japan) for o in choices},
404
- required=[o['opt'] for o in choices if o['required']],
405
- )
406
- output_schema = dict(type="object", properties=dict())
407
- func_tool = FunctionTool(fn=func_ctx[0], name=func_name, title=func_name.title(), description=description,
408
- tags=[f"mode={mode}", f"cmd={cmd}"],
409
- parameters=input_schema, output_schema=output_schema,)
410
- # ツールリストに追加
411
- func_tools.append(func_tool)
412
- return func_tools
413
-
414
- def create_mw_toollist(self, logger:logging.Logger, args:argparse.Namespace) -> Any:
415
- """
416
- ツールリストを作成するミドルウェアを作成します
417
-
418
- Args:
419
- logger (logging.Logger): ロガー
420
- args (argparse.Namespace): 引数
377
+ extract_callable (bool): コール可能な関数を抽出するかどうか
421
378
 
422
379
  Returns:
423
- Any: ミドルウェア
380
+ ToolList: ToolListのリスト
424
381
  """
425
- from cmdbox.app.web import Web
426
- from fastmcp.server.middleware import Middleware, MiddlewareContext, ListToolsResult
427
- from fastmcp.tools import FunctionTool
428
- func_tools:List[FunctionTool] = self.create_tools(logger, args)
429
- options = Options.getInstance()
430
- cmd_list:feature.Feature = options.get_cmd_attr('cmd', 'list', "feature")
431
- language, _ = locale.getlocale()
432
- is_japan = language.find('Japan') >= 0 or language.find('ja_JP') >= 0
433
- mcp = self
434
- class CommandListMiddleware(Middleware):
435
- async def on_list_tools(self, context: MiddlewareContext, call_next):
436
- # 認証情報の取得
437
- scope = signin.get_request_scope()
438
- web:Web = scope["web"]
439
- signin_file = web.signin_file
440
- signin_data = signin.Signin.load_signin_file(signin_file)
441
- if signin.Signin._check_signin(scope["req"], scope["res"], signin_data, logger) is not None:
442
- logger.warning("Unable to execute command because authentication information cannot be obtained")
443
- return dict(warn="Unable to execute command because authentication information cannot be obtained")
444
- groups = scope["req"].session["signin"]["groups"]
445
- ret_tools = []
446
- # システムコマンドリストのフィルタリング
447
- for func in func_tools:
448
- mode = [t.replace('mode=', '') for t in func.tags if t.startswith('mode=')]
449
- mode = mode[0] if mode else None
450
- cmd = [t.replace('cmd=', '') for t in func.tags if t.startswith('cmd=')]
451
- cmd = cmd[0] if cmd else None
452
- if mode is None or cmd is None:
453
- logger.warning(f"Tool {func.name} does not have mode or cmd tag, skipping.")
454
- continue
455
- if not signin.Signin._check_cmd(signin_data, groups, mode, cmd, logger):
456
- logger.warning(f"User does not have permission to use tool {func.name} (mode={mode}, cmd={cmd}), skipping.")
457
- continue
458
- ret_tools.append(func)
459
- # ユーザーコマンドリストの取得
460
- args = argparse.Namespace(data=web.data, signin_file=signin_file, groups=groups, kwd=None,
461
- format=False, output_json=None, output_json_append=False,)
462
- st, ret, _ = cmd_list.apprun(logger, args, time.perf_counter(), [])
463
- if ret is None or 'success' not in ret or not ret['success']:
464
- return ret_tools
465
- for opt in ret['success']:
466
- func_name = f"user_{opt['title']}"
467
- mode, cmd, description = opt['mode'], opt['cmd'], opt['description'] if 'description' in opt and opt['description'] else ''
468
- choices = options.get_cmd_choices(mode, cmd, False)
469
- description += '\n' + options.get_cmd_attr(mode, cmd, 'description_ja' if is_japan else 'description_en')
470
- # 関数の定義を生成
471
- func_txt = mcp._create_func_txt(func_name, mode, cmd, is_japan, options, title=opt['title'])
472
- if logger.level == logging.DEBUG:
473
- logger.debug(f"generating agent tool: {func_name}")
474
- func_ctx = []
475
- # 関数を実行してコンテキストに追加
476
- exec(func_txt,
477
- dict(time=time,List=List, Path=Path, argparse=argparse, common=common, options=options, logging=logging, signin=signin,),
478
- dict(func_ctx=func_ctx))
479
- # 関数のスキーマを生成
480
- input_schema = dict(
481
- type="object",
482
- properties={o['opt']: mcp._to_schema(o, is_japan) for o in choices},
483
- required=[],
484
- )
485
- output_schema = dict(type="object", properties=dict())
486
- func_tool = FunctionTool(fn=func_ctx[0], name=func_name, title=func_name.title(), description=description,
487
- tags=[f"mode={mode}", f"cmd={cmd}"],
488
- parameters=input_schema, output_schema=output_schema,)
489
- # ツールリストに追加
490
- ret_tools.append(func_tool)
491
-
492
- return ret_tools
493
- return CommandListMiddleware()
382
+ tool_list = ToolList(self, logger)
383
+ tool_list.extract_callable = extract_callable
384
+ return tool_list
494
385
 
495
386
  def create_mw_logging(self, logger:logging.Logger, args:argparse.Namespace) -> Any:
496
387
  """
@@ -518,7 +409,7 @@ class Mcp:
518
409
  raise e
519
410
  return LoggingMiddleware()
520
411
 
521
- def create_mw_reqscope(self, logger:logging.Logger, args:argparse.Namespace, web) -> Any:
412
+ def create_mw_reqscope(self, logger:logging.Logger, args:argparse.Namespace) -> Any:
522
413
  """
523
414
  認証用のミドルウェアを作成します
524
415
 
@@ -530,11 +421,12 @@ class Mcp:
530
421
  Returns:
531
422
  Any: ミドルウェア
532
423
  """
424
+ from cmdbox.app.web import Web
533
425
  from fastapi import Response
534
426
  from fastmcp.server.middleware import Middleware, MiddlewareContext
535
427
  class ReqScopeMiddleware(Middleware):
536
428
  async def on_message(self, context: MiddlewareContext, call_next):
537
- signin.request_scope.set(dict(req=context.fastmcp_context.request_context.request, res=Response(), websocket=None, web=web))
429
+ signin.request_scope.set(dict(req=context.fastmcp_context.request_context.request, res=Response(), websocket=None, web=Web.getInstance()))
538
430
  result = await call_next(context)
539
431
  return result
540
432
  return ReqScopeMiddleware()
@@ -561,11 +453,213 @@ class Mcp:
561
453
  from fastmcp import FastMCP
562
454
  from google.adk.sessions import BaseSessionService
563
455
  session_service:BaseSessionService = self.create_session_service(args)
564
- from fastmcp.tools import FunctionTool
565
- tools:List[FunctionTool] = self.create_tools(logger, args)
566
- mcp:FastMCP = self.create_mcpserver(args, tools, web)
567
- root_agent = self.create_agent(logger, args, [t.fn for t in tools])
456
+ mcp:FastMCP = self.create_mcpserver(logger, args, self.create_tools(logger, args, False))
457
+ root_agent = self.create_agent(logger, args, self.create_tools(logger, args, True))
568
458
  runner = self.create_runner(logger, args, session_service, root_agent)
569
459
  if logger.level == logging.DEBUG:
570
460
  logger.debug(f"init_agent_runner complate.")
571
461
  return runner, mcp
462
+
463
+ class ToolList(object):
464
+ def __init__(self, mcp:Mcp, logger:logging.Logger, *args:List):
465
+ """
466
+ ツールリストを初期化します
467
+
468
+ Args:
469
+ mcp (Mcp): MCPインスタンス
470
+ logger (logging.Logger): ロガー
471
+ *args (List): 追加するツールのリスト
472
+ """
473
+ from fastmcp.tools import FunctionTool
474
+ options = Options.getInstance()
475
+ is_japan = common.is_japan()
476
+
477
+ self.tools = []
478
+ self.mcp = mcp
479
+ self.logger = logger
480
+ self.extract_callable = False
481
+ for mode in options.get_mode_keys():
482
+ for cmd in options.get_cmd_keys(mode):
483
+ if not options.get_cmd_attr(mode, cmd, 'use_agent'):
484
+ continue
485
+ # コマンドの説明と選択肢を取得
486
+ description = options.get_cmd_attr(mode, cmd, 'description_ja' if is_japan else 'description_en')
487
+ choices = options.get_cmd_choices(mode, cmd, False)
488
+ func_name = f"{mode}_{cmd}"
489
+ # 関数の定義を生成
490
+ func_txt = self.mcp._create_func_txt(func_name, mode, cmd, is_japan, options)
491
+ if self.logger.level == logging.DEBUG:
492
+ self.logger.debug(f"generating agent tool: {func_name}")
493
+ func_ctx = []
494
+ # 関数を実行してコンテキストに追加
495
+ exec(func_txt,
496
+ dict(time=time,List=List, Path=Path, argparse=argparse, common=common, options=options, logging=logging, signin=signin,),
497
+ dict(func_ctx=func_ctx))
498
+ # 関数のスキーマを生成
499
+ input_schema = dict(
500
+ type="object",
501
+ properties={o['opt']: self.mcp._to_schema(o, is_japan) for o in choices},
502
+ required=[o['opt'] for o in choices if o['required']],
503
+ )
504
+ output_schema = dict(type="object", properties=dict())
505
+ func_tool = FunctionTool(fn=func_ctx[0], name=func_name, title=func_name.title(), description=description,
506
+ tags=[f"mode={mode}", f"cmd={cmd}"],
507
+ parameters=input_schema, output_schema=output_schema,)
508
+ # ツールリストに追加
509
+ self.tools.append(func_tool)
510
+ for tool in args:
511
+ if isinstance(tool, FunctionTool):
512
+ if self.logger.level == logging.DEBUG:
513
+ self.logger.debug(f"adding tool: {tool.name}")
514
+ else:
515
+ raise TypeError(f"Expected FunctionTool, got {type(tool)}")
516
+ self.tools.append(tool)
517
+
518
+ @property
519
+ def extract_callable(self):
520
+ """
521
+ ツールリストから関数を抽出するかどうかを取得します
522
+
523
+ Returns:
524
+ bool: 関数を抽出する場合はTrue、しない場合はFalse
525
+ """
526
+ return self._extract_callable
527
+
528
+ @extract_callable.setter
529
+ def extract_callable(self, value:bool):
530
+ """
531
+ ツールリストから関数を抽出するかどうかを設定します
532
+
533
+ Args:
534
+ value (bool): 関数を抽出する場合はTrue、しない場合はFalse
535
+ """
536
+ if not isinstance(value, bool):
537
+ raise TypeError(f"Expected bool, got {type(value)}")
538
+ self._extract_callable = value
539
+
540
+ def append(self, tool):
541
+ """
542
+ ツールを追加します
543
+
544
+ Args:
545
+ tool (FunctionTool): 追加するツール
546
+ """
547
+ from fastmcp.tools import FunctionTool
548
+ if isinstance(tool, FunctionTool):
549
+ self.tools.append(tool)
550
+ else:
551
+ raise TypeError(f"Expected FunctionTool, got {type(tool)}")
552
+
553
+ def pop(self):
554
+ """
555
+ ツールを取り出します
556
+
557
+ Returns:
558
+ FunctionTool: 取り出したツール
559
+ """
560
+ if len(self.tools) == 0:
561
+ raise IndexError("No tools available to pop.")
562
+ return self.tools.pop()
563
+
564
+ def __repr__(self):
565
+ """
566
+ ツールリストの文字列表現を返します
567
+
568
+ Returns:
569
+ str: ツールリストの文字列表現
570
+ """
571
+ return 'ToolList(' + repr(self.tools) + ')'
572
+
573
+ def __str__(self):
574
+ """
575
+ ツールリストの文字列表現を返します
576
+
577
+ Returns:
578
+ str: ツールリストの文字列表現
579
+ """
580
+ return str(self.tools)
581
+
582
+ def __getitem__(self, key:int):
583
+ """
584
+ ツールリストからツールを取得します
585
+
586
+ Args:
587
+ key (int): インデックス
588
+ Returns:
589
+ FunctionTool: 取得したツール
590
+ """
591
+ return self.tools[key]
592
+
593
+ def __iter__(self):
594
+ """
595
+ ツールリストをイテレータとして返します
596
+
597
+ Returns:
598
+ Iterator[FunctionTool]: ツールリストのイテレータ
599
+ """
600
+ from cmdbox.app.web import Web
601
+ from fastmcp.tools import FunctionTool
602
+ options = Options.getInstance()
603
+ is_japan = common.is_japan()
604
+ ret_tools = self.tools.copy()
605
+ web = Web.getInstance()
606
+ if web.signin.signin_file_data is None:
607
+ # サインインファイルが読み込まれていない場合は登録済みのリストを返す
608
+ if self.extract_callable:
609
+ # 関数を抽出する場合はツールリストから関数を抽出して返す
610
+ return (tool.fn for tool in ret_tools if callable(tool.fn)).__iter__()
611
+ return ret_tools.__iter__()
612
+ try:
613
+ # ユーザーコマンドの読み込み
614
+ paths = glob.glob(str(Path(web.data) / ".cmds" / f"cmd-*.json"))
615
+ cmd_list = [common.loadopt(path, True) for path in paths]
616
+ cmd_list = sorted(cmd_list, key=lambda cmd: cmd["title"])
617
+ # ユーザーコマンドリストの取得(すべてのコマンドを取得するためにgroupsをadminに設定)
618
+ # 実行時にはユーザーのグループに応じて認可する
619
+ cmd_list = [dict(title=r.get('title',''), mode=r['mode'], cmd=r['cmd'],
620
+ description=r.get('description','') + options.get_cmd_attr(r['mode'], r['cmd'], 'description_ja' if is_japan else 'description_en'),
621
+ tag=r.get('tag','')) for r in cmd_list \
622
+ if signin.Signin._check_cmd(web.signin.signin_file_data, ['admin'], r['mode'], r['cmd'], self.logger)]
623
+
624
+ except Exception as e:
625
+ # ユーザーコマンドの読み込みに失敗した場合は警告を出して登録済みのリストを返す
626
+ self.logger.warning(f"Error loading user commands: {e}", exc_info=True)
627
+ if self.extract_callable:
628
+ # 関数を抽出する場合はツールリストから関数を抽出して返す
629
+ return (tool.fn for tool in ret_tools if callable(tool.fn)).__iter__()
630
+ return ret_tools.__iter__()
631
+ _tools_fns = [tool.name for tool in ret_tools]
632
+ # ユーザーコマンドの定義を関数として生成
633
+ for opt in cmd_list:
634
+ func_name = opt['title']
635
+ mode, cmd, description = opt['mode'], opt['cmd'], opt['description'] if 'description' in opt and opt['description'] else ''
636
+ choices = options.get_cmd_choices(mode, cmd, False)
637
+ description += '\n' + options.get_cmd_attr(mode, cmd, 'description_ja' if is_japan else 'description_en')
638
+ # 関数の定義を生成
639
+ func_txt = self.mcp._create_func_txt(func_name, mode, cmd, is_japan, options, title=opt['title'])
640
+ if self.logger.level == logging.DEBUG:
641
+ self.logger.debug(f"generating agent tool: {func_name}")
642
+ func_ctx = []
643
+ # 関数を実行してコンテキストに追加
644
+ exec(func_txt,
645
+ dict(time=time,List=List, Path=Path, argparse=argparse, common=common, options=options, logging=logging, signin=signin,),
646
+ dict(func_ctx=func_ctx))
647
+ # 関数のスキーマを生成
648
+ input_schema = dict(
649
+ type="object",
650
+ properties={o['opt']: self.mcp._to_schema(o, is_japan) for o in choices},
651
+ required=[],
652
+ )
653
+ output_schema = dict(type="object", properties=dict())
654
+ func_tool = FunctionTool(fn=func_ctx[0], name=func_name, title=func_name.title(), description=description,
655
+ tags=[f"mode={mode}", f"cmd={cmd}"],
656
+ parameters=input_schema, output_schema=output_schema,)
657
+ if func_name in _tools_fns:
658
+ # 既に同名の関数が存在する場合は差し替え
659
+ self.logger.warning(f"Function {func_name} already exists, replacing.")
660
+ ret_tools = [tool for tool in ret_tools if tool.name != func_name]
661
+ ret_tools.append(func_tool)
662
+ if self.extract_callable:
663
+ # 関数を抽出する場合はツールリストから関数を抽出して返す
664
+ return (tool.fn for tool in ret_tools if callable(tool.fn)).__iter__()
665
+ return ret_tools.__iter__()
cmdbox/app/options.py CHANGED
@@ -389,7 +389,8 @@ class Options:
389
389
  features_yml = Path(ver.__file__).parent / 'extensions' / 'features.yml'
390
390
  #if not features_yml.exists() or not features_yml.is_file():
391
391
  # features_yml = Path('.samples/features.yml')
392
- logger.info(f"load features.yml: {features_yml}, is_file={features_yml.is_file()}")
392
+ if logger.level == logging.DEBUG:
393
+ logger.debug(f"load features.yml: {features_yml}, is_file={features_yml.is_file()}")
393
394
  if features_yml.exists() and features_yml.is_file():
394
395
  if self.features_yml_data is None:
395
396
  self.features_yml_data = yml = common.load_yml(features_yml)
cmdbox/app/web.py CHANGED
@@ -27,6 +27,21 @@ import webbrowser
27
27
 
28
28
 
29
29
  class Web:
30
+ @classmethod
31
+ def getInstance(cls, *args, **kwargs) -> 'Web':
32
+ """
33
+ Webクラスのインスタンスを取得する
34
+ Args:
35
+ *args: 可変長引数
36
+ **kwargs: キーワード引数
37
+
38
+ Returns:
39
+ Web: Webクラスのインスタンス
40
+ """
41
+ if not hasattr(cls, '_instance'):
42
+ cls._instance = cls(*args, **kwargs)
43
+ return cls._instance
44
+
30
45
  def __init__(self, logger:logging.Logger, data:Path, appcls=None, ver=None,
31
46
  redis_host:str="localhost", redis_port:int=6379, redis_password:str=None, svname:str='server',
32
47
  client_only:bool=False, doc_root:Path=None, gui_html:str=None, filer_html:str=None, result_html:str=None, users_html:str=None,
@@ -274,7 +289,7 @@ class Web:
274
289
  pass_miss_count = self.user_data(None, u['uid'], u['name'], 'password', 'pass_miss_count')
275
290
  pass_miss_last = self.user_data(None, u['uid'], u['name'], 'password', 'pass_miss_last')
276
291
 
277
- if name is None:
292
+ if name is None or name == '':
278
293
  ret.append({**u, **dict(last_signin=signin_last, pass_last_update=pass_last_update,
279
294
  pass_miss_count=pass_miss_count, pass_miss_last=pass_miss_last)})
280
295
  return ret
@@ -515,7 +530,7 @@ class Web:
515
530
  signin_data = self.signin.get_data()
516
531
  if signin_data is None:
517
532
  raise ValueError(f'signin_file_data is None. ({self.signin_file})')
518
- if name is None:
533
+ if name is None or name == '':
519
534
  return copy.deepcopy(signin_data['groups'])
520
535
  for g in copy.deepcopy(signin_data['groups']):
521
536
  if g['name'] == name:
@@ -43,6 +43,9 @@ agentrule: # Specifies a list of rules that determi
43
43
  - mode: cmd # Specify the "mode" as the condition for applying the rule.
44
44
  cmds: [list, load] # Specify the "cmd" to which the rule applies. Multiple items can be specified in a list.
45
45
  rule: allow # Specifies whether the specified command is allowed or not. Values are allow or deny.
46
+ - mode: client
47
+ cmds: [http]
48
+ rule: allow
46
49
  audit:
47
50
  enabled: true # Specify whether to enable the audit function.
48
51
  write:
@@ -49,20 +49,11 @@ aliases: # Specify the alias for the specified co
49
49
  agentrule: # Specifies a list of rules that determine which commands the agent can execute.
50
50
  policy: deny # Specify the default policy for the rule. The value can be allow or deny.
51
51
  rules: # Specify the rules for the commands that the agent can execute according to the group to which the user belongs.
52
- - mode: audit # Specify the "mode" as the condition for applying the rule.
53
- cmds: [search, write] # Specify the "cmd" to which the rule applies. Multiple items can be specified in a list.
52
+ - mode: cmd # Specify the "mode" as the condition for applying the rule.
53
+ cmds: [list, load] # Specify the "cmd" to which the rule applies. Multiple items can be specified in a list.
54
54
  rule: allow # Specifies whether the specified command is allowed or not. Values are allow or deny.
55
55
  - mode: client
56
- cmds: [file_copy, file_download, file_list, file_mkdir, file_move, file_remove, file_rmdir, file_upload, server_info]
57
- rule: allow
58
- - mode: cmd
59
- cmds: [list, load]
60
- rule: allow
61
- - mode: server
62
- cmds: [list]
63
- rule: allow
64
- - mode: web
65
- cmds: [gencert, genpass, group_list, user_list]
56
+ cmds: [http]
66
57
  rule: allow
67
58
  audit:
68
59
  enabled: true # Specify whether to enable the audit function.
@@ -78,7 +78,7 @@ pathrule: # List of RESTAPI rules, rules that determine whe
78
78
  - groups: [user]
79
79
  paths: [/signin, /assets, /bbforce_cmd, /copyright, /dosignin, /dosignout, /password/change,
80
80
  /gui/user_data/load, /gui/user_data/save, /gui/user_data/delete,
81
- /agent, /mcp,
81
+ /agent, /mcpsv,
82
82
  /exec_cmd, /exec_pipe, /filer, /result, /gui, /get_server_opt, /usesignout, /versions_cmdbox, /versions_used]
83
83
  rule: allow
84
84
  - groups: [readonly]
@@ -106,6 +106,30 @@ password: # Password settings.
106
106
  enabled: true # Specify whether or not to enable account lockout.
107
107
  threshold: 5 # Specify the number of failed login attempts before the account is locked.
108
108
  reset: 30 # Specify the number of minutes after which the failed login count will be reset.
109
+ apikey:
110
+ gen_cert: # Specify whether to generate a certificate for API key.
111
+ enabled: true # Specify whether to enable certificate generation for API key.
112
+ privatekey: idp_private.pem # Specify the destination file for the generated private key.
113
+ certificate: idp_cert.pem # Specify the destination file for the generated certificate.
114
+ publickey: idp_public.pem # Specify the destination file for the generated public key.
115
+ gen_jwt: # Specify whether to generate JWT for API key.
116
+ enabled: true # Specify whether to enable JWT generation for API key.
117
+ privatekey: idp_private.pem # Specify the private key file for JWT generation.
118
+ privatekey_passphrase: # Specify the passphrase for the private key file.
119
+ # If the private key is encrypted, specify the passphrase here.
120
+ algorithm: RS256 # Specify the algorithm used to generate the JWT. The value can be RS256, PS256, or ES256.
121
+ claims: # Specify the claims to be included in the JWT.
122
+ iss: identity_provider # Specify the issuer of the JWT. This is usually the name of the identity provider.
123
+ sub: app_user # Specify the subject of the JWT. This is usually the name of the application.
124
+ aud: app_organization # Specify the audience of the JWT. This is usually the name of the organization that will use the application.
125
+ exp: 31536000 # Specify the expiration time of the JWT in seconds. The default is 31536000 seconds (1 year).
126
+ verify_jwt: # Specify whether to verify JWT for API key.
127
+ enabled: true # Specify whether to enable JWT verification for API key.
128
+ certificate: idp_cert.pem # Specify the certificate file for JWT verification.
129
+ publickey: idp_public.pem # Specify the public key file for JWT verification. Not required if certificate exists.
130
+ issuer: identity_provider # Specify the issuer of the JWT. This is usually the name of the identity provider. (If not specified, no verification)
131
+ audience: app_organization # Specify the audience of the JWT. This is usually the name of the organization that will use the application. (If not specified, no verification)
132
+ algorithm: RS256 # Specify the algorithm used to verify the JWT. The value can be RS256, PS256, or ES256.
109
133
  oauth2: # OAuth2 settings.
110
134
  providers: # This is a per-provider setting for OAuth2.
111
135
  google: # Google's OAuth2 configuration.
cmdbox/version.py CHANGED
@@ -1,10 +1,10 @@
1
1
  import datetime
2
2
 
3
- dt_now = datetime.datetime(2025, 7, 13)
3
+ dt_now = datetime.datetime(2025, 7, 28)
4
4
  days_ago = (datetime.datetime.now() - dt_now).days
5
5
  __appid__ = 'cmdbox'
6
6
  __title__ = 'cmdbox (Command Development Application)'
7
- __version__ = '0.6.2.3'
7
+ __version__ = '0.6.3'
8
8
  __copyright__ = f'Copyright © 2023-{dt_now.strftime("%Y")} hamacom2004jp'
9
9
  __pypiurl__ = 'https://pypi.org/project/cmdbox/'
10
10
  __srcurl__ = 'https://github.com/hamacom2004jp/cmdbox'
@@ -253,6 +253,7 @@ agent.create_history = (histories, session_id, msg) => {
253
253
  e.preventDefault();
254
254
  e.stopPropagation();
255
255
  agent.delete_session(session_id).then((res) => {
256
+ const messages = $('#messages');
256
257
  if (res['success']) {
257
258
  history.remove();
258
259
  const sid = messages.attr('data-session_id');
@@ -34,7 +34,8 @@ const render_result_func = (target_elem, result, res_size) => {
34
34
  // list型の結果をテーブルに変換
35
35
  const list2table = (data, table_head, table_body) => {
36
36
  data.forEach((row, i) => {
37
- if(row['success'] && typeof row['success'] == "object" && !Array.isArray(row['success'])){
37
+ if (!row) return;
38
+ if(typeof row == "object" && row['success'] && typeof row['success'] == "object" && !Array.isArray(row['success'])){
38
39
  dict2table(row['success'], i==0?table_head:null, table_body, row['output_image']);
39
40
  return;
40
41
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cmdbox
3
- Version: 0.6.2.3
3
+ Version: 0.6.3
4
4
  Summary: cmdbox: It is a command line application with a plugin mechanism.
5
5
  Home-page: https://github.com/hamacom2004jp/cmdbox
6
6
  Download-URL: https://github.com/hamacom2004jp/cmdbox
@@ -10,25 +10,25 @@ cmdbox/logconf_gui.yml,sha256=-95vyd0q-aB1gsabdk8rg9dJ2zRKAZc8hRxyhNOQboU,1076
10
10
  cmdbox/logconf_mcp.yml,sha256=pED0i1iKP8UoyXE0amFMA5kjV7Qc6_eJCUDVen3L4AU,1069
11
11
  cmdbox/logconf_server.yml,sha256=n3c5-KVzjUzcUX5BQ6uE-PN9rp81yXaJql3whyCcSDQ,1091
12
12
  cmdbox/logconf_web.yml,sha256=pPbdAwckbK0cgduxcVkx2mbk-Ymz5hVzR4guIsfApMQ,1076
13
- cmdbox/version.py,sha256=D2SuxXcqjci3LXjKKBnrM3ZdICKSmwsWfg6308ElaMQ,2110
13
+ cmdbox/version.py,sha256=HTU-X6-2WVXAPOXs7dqpmC2oYd3v69th6uurBQSFxUg,2108
14
14
  cmdbox/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  cmdbox/app/app.py,sha256=_UJqSTs3LStGSu3vLMTqOKmpL4x7NQAQb2hUMbEFcg4,9639
16
16
  cmdbox/app/client.py,sha256=n986lXeV7hhaki4iyyvsfhNptmCXDFphxlNoNe2XhKg,19542
17
- cmdbox/app/common.py,sha256=MQQab5rboKS2oNDAWbKR8LYoSXW_i1vgD1rFAwBMExU,27712
17
+ cmdbox/app/common.py,sha256=iY4Bl9r0MSSDhZ9Ep3Y45MVBapurKpwkBNCHYb-COnY,28130
18
18
  cmdbox/app/edge.py,sha256=2Aav7n4skhP0FUvG6_3JKijHHozA-WcwALgEwNB0DUI,41439
19
19
  cmdbox/app/edge_tool.py,sha256=HXxr4Or8QaZ5ueYIN3huv8GnXSnV28RZCmZBUEfiIk0,8062
20
20
  cmdbox/app/feature.py,sha256=fK7JP1fc8b9k1zhSNOkWq02ad8i-_wuAX5kyyK2TZdE,10391
21
21
  cmdbox/app/filer.py,sha256=L_DSMTvnbN_ffr3JIt0obbOmVoTHEfVm2cAVz3rLH-Q,16059
22
- cmdbox/app/mcp.py,sha256=eLPGd_fWohPalW4ss7UApAfSys40bWknkjdvSI0zyGo,30001
23
- cmdbox/app/options.py,sha256=Wz2GYnuhwBieFbA3zHl2WIXsKANVWvX8Bw3MK408xPs,46220
22
+ cmdbox/app/mcp.py,sha256=ZzpfaZHgNSrTY5Jj5KZzm28LLtW7cAMrS8-NSf6eGMc,32635
23
+ cmdbox/app/options.py,sha256=GVGq9ID46czZsWNlCHkVf33DrM-1oY5ea90ej1-UnFQ,46268
24
24
  cmdbox/app/server.py,sha256=woOmIk901ONn5a_2yz_b3I1JpLYIF8g42uQRd0_MRuQ,10417
25
- cmdbox/app/web.py,sha256=5eUtb60Vb_6RK62IWbJDPFNvAVsf1THw7knp3uGmMGg,53559
25
+ cmdbox/app/web.py,sha256=1T4cRg6TnhB7LA37sePZFgnZAc1hARC7qS6DrRGFcEc,54035
26
26
  cmdbox/app/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  cmdbox/app/auth/azure_signin.py,sha256=jJlIZJIGLZCngnJgoxBajaD2s8nM7QoszuN6-FT-5h8,1953
28
28
  cmdbox/app/auth/azure_signin_saml.py,sha256=oM2buGTK4t6-OsUuiUXTlZk0YXZL01khuPYVB84dMDU,472
29
29
  cmdbox/app/auth/github_signin.py,sha256=dg_3eu7iikTfp9mxYQscQXtFspmJIuasGZ43icCeC3k,1510
30
30
  cmdbox/app/auth/google_signin.py,sha256=LErFkKniumKgfE5jlY9_lAneKlqD6BfLHxg2lZgv_zE,1382
31
- cmdbox/app/auth/signin.py,sha256=8xmj19Dt5DypWqd5yJ0KFgxmL6Vbp-mwZoPKuqEwfmc,59953
31
+ cmdbox/app/auth/signin.py,sha256=wBnZMmi7i4BehlfJtXg4owr5HDLOzQCS6Lc2UO-2dPQ,60022
32
32
  cmdbox/app/auth/signin_saml.py,sha256=MUM_hqCEjT-8xXNMHa-iH2j24SdBls4SU3k4BopRd48,2457
33
33
  cmdbox/app/commons/convert.py,sha256=mkXPNQtX_pEH4L5DzonOY6Dh38SzJ5JQma_EY7UDBqU,7078
34
34
  cmdbox/app/commons/loghandler.py,sha256=bcKTDqSlUcVpO-6vYGijZcLdJlXn7ewx9MT_N7RhgAY,6323
@@ -38,7 +38,7 @@ cmdbox/app/features/cli/agent_base.py,sha256=FShwbXWCfU-pXNTRaWfV_9z5FKeczxWsxid
38
38
  cmdbox/app/features/cli/audit_base.py,sha256=X_FvKsN_j8uVg96yifzmtXuAqGIPklihlfmhb_FvOgM,9405
39
39
  cmdbox/app/features/cli/cmdbox_audit_createdb.py,sha256=S-rE9bUOKQLg8GizVQCKhqf0fr3JapcrqNCzoSzmNLc,12133
40
40
  cmdbox/app/features/cli/cmdbox_audit_delete.py,sha256=OVPu0uIHgkJpeALILPY4lzL-gy61uywMN3KIP7lst8g,18922
41
- cmdbox/app/features/cli/cmdbox_audit_search.py,sha256=eCvBEogZLjqe5QmCtu4ptPKElnvg1fYPvasUL0ArdAQ,31071
41
+ cmdbox/app/features/cli/cmdbox_audit_search.py,sha256=sSwdjaCWGiRkD_WHM4YFZFxv7kO1aY4693DnZHFvK8Y,31400
42
42
  cmdbox/app/features/cli/cmdbox_audit_write.py,sha256=Yv8SwY4nfSE0BsMJ5lz_AfiI0K_wjg6RdIK6L0kwcfU,15541
43
43
  cmdbox/app/features/cli/cmdbox_client_file_copy.py,sha256=wwth8zHslc-mm5iPGJOMXhTqeZ_tZcRSo1LIk_7v53o,13093
44
44
  cmdbox/app/features/cli/cmdbox_client_file_download.py,sha256=YzoqJPvBlg8Kwos-3RjFrPjpVVV5qjNPSB_npofdSL8,13239
@@ -48,8 +48,9 @@ cmdbox/app/features/cli/cmdbox_client_file_move.py,sha256=eSj08rmHseZDrqgit5_Jek
48
48
  cmdbox/app/features/cli/cmdbox_client_file_remove.py,sha256=oupYP04CbAZ-x7MaODjSTuXaHqHTWzuWrOtq00U3rWQ,11554
49
49
  cmdbox/app/features/cli/cmdbox_client_file_rmdir.py,sha256=E32Av7_0g-Rlesr9onc4iu-HZGtjfgaM_frLW2jMF7Y,11530
50
50
  cmdbox/app/features/cli/cmdbox_client_file_upload.py,sha256=-sAqQiIpqH4wWfv_IAN2EYafHzNjFnOOfuLjyjHPwLc,13407
51
+ cmdbox/app/features/cli/cmdbox_client_http.py,sha256=07nR3MU5BMNcdLza3iaj0cvBfpU0F4YKccq3b6tRC8Q,10004
51
52
  cmdbox/app/features/cli/cmdbox_client_server_info.py,sha256=tBGOfFxUQojkVu_o8Jv-VR2QSMnxSLN6EBtHZy5NU6k,9575
52
- cmdbox/app/features/cli/cmdbox_cmd_list.py,sha256=SUieS8cEH3lNHeFxer1m-e8XBTd8_fE2-mrPfcnHw2A,6682
53
+ cmdbox/app/features/cli/cmdbox_cmd_list.py,sha256=CNH8jfviZxH5WH1r1O1E2ilkq0J2JBmFGgHSbYrANnQ,6926
53
54
  cmdbox/app/features/cli/cmdbox_cmd_load.py,sha256=U1NWnJ_bjGOMCp9mtaqt4ujaDtPkhGnybupX-h15Tuw,6747
54
55
  cmdbox/app/features/cli/cmdbox_edge_config.py,sha256=2fppuJ5yAiepq5i4Qj_1w3ciSXhy1sFDK6FFOw5sB68,9472
55
56
  cmdbox/app/features/cli/cmdbox_edge_start.py,sha256=PVkvRBaepxqdjswUp1JzQqfzD5xdiPaZYXy72P0rlDE,3823
@@ -68,7 +69,7 @@ cmdbox/app/features/cli/cmdbox_web_group_add.py,sha256=p4VdIUv-hh0eXqV5ITyBFRUxG
68
69
  cmdbox/app/features/cli/cmdbox_web_group_del.py,sha256=qssAhZmY0wKfkpQ6UycT7G-_qQpyyE_T7w_hupgsajE,6629
69
70
  cmdbox/app/features/cli/cmdbox_web_group_edit.py,sha256=04U741NeIGqeuId8m2oSOFgSguQuTK0_Krt0nSMATrw,7358
70
71
  cmdbox/app/features/cli/cmdbox_web_group_list.py,sha256=4oITsdUEkPW4jZQOTiDQkgawJTUfxEyn1YFxDuf9SVk,6724
71
- cmdbox/app/features/cli/cmdbox_web_start.py,sha256=1enJE1AOzPADpebFjKpGV-QqNFmfXz44W0nknN4bGGE,17836
72
+ cmdbox/app/features/cli/cmdbox_web_start.py,sha256=uNFXWpmrhMY6AEzYgf5RXlkc7yzHJIIJZs5dJ_7TZNU,17848
72
73
  cmdbox/app/features/cli/cmdbox_web_stop.py,sha256=5ja4IiWRSpfRrWfoRncMRmofEYj8MDfIFUcWfDtIOQ4,3567
73
74
  cmdbox/app/features/cli/cmdbox_web_user_add.py,sha256=WsuW1rt27dhxIY7aD_xyZblyEhBqq4Z_doE4Gd75O68,8611
74
75
  cmdbox/app/features/cli/cmdbox_web_user_del.py,sha256=t5CpPO4J9Wl2paBqSY_rA9vEYLXSxrYFB999Cvd-ZMU,6592
@@ -110,7 +111,7 @@ cmdbox/app/features/web/cmdbox_web_users.py,sha256=LZ3BUudBF21wqGO5EWwKvyLMxK_gE
110
111
  cmdbox/app/features/web/cmdbox_web_usesignout.py,sha256=lBjBj8M8e69uXhdv7H92wZfRRWD2j6kmC_WekSCw5yo,682
111
112
  cmdbox/app/features/web/cmdbox_web_versions_cmdbox.py,sha256=hG4ikQc0Qr6He8AhYu8kK1GD5TNjezr-VpmCSAFL7Nk,818
112
113
  cmdbox/app/features/web/cmdbox_web_versions_used.py,sha256=xA368ASudYFIrJjWOC1MGmsaAE3Mdd5i-Y8sZBWL7p4,1322
113
- cmdbox/extensions/features.yml,sha256=j8j3YIy34w0sprI1A1pwklZGH9KN80UdWuCpKcqVkfc,6699
114
+ cmdbox/extensions/features.yml,sha256=Dr8C8x3381oxQmigCM9OEDbOHuYEUCeqbJhIyjSMADk,6749
114
115
  cmdbox/extensions/user_list.yml,sha256=P_KdBu9VmNDEh9rEQ4QGKObpeXb34wMF4__uOShLh44,13098
115
116
  cmdbox/extensions/sample_project/requirements.txt,sha256=z_gUanGVrPeYUExYeU5_gHiOTy8RKZkaJSeKxOM4mqY,18
116
117
  cmdbox/extensions/sample_project/.vscode/launch.json,sha256=Bj_FO1P0lPMfuwZxvyLfwQa0f7Gk276dvcVRjj2aem4,1348
@@ -123,8 +124,8 @@ cmdbox/extensions/sample_project/sample/app/features/cli/__init__.py,sha256=47DE
123
124
  cmdbox/extensions/sample_project/sample/app/features/cli/sample_client_time.py,sha256=276227PBveyHqWXah5CHQUWeJZ1W2AA6ujB9Zw8Ydh8,3131
124
125
  cmdbox/extensions/sample_project/sample/app/features/cli/sample_server_time.py,sha256=vpMBd-7gymFP9qcpFf9uvPp8dxn5qQv6VcoFw4lSZuo,7444
125
126
  cmdbox/extensions/sample_project/sample/app/features/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
126
- cmdbox/extensions/sample_project/sample/extensions/features.yml,sha256=Jn7kZVH95VU0bF6p2K2Wzv7V8u4AkUqi-_Fco5AwLoU,7374
127
- cmdbox/extensions/sample_project/sample/extensions/user_list.yml,sha256=ES9XHqBmD05-PtdCdxnD_0NazJfPR1ZmlL3DHlg-cxk,10577
127
+ cmdbox/extensions/sample_project/sample/extensions/features.yml,sha256=JKPm-yRs3Ik41zmuoHPh5oua-G7ricGMMpMQGP9YrcQ,7084
128
+ cmdbox/extensions/sample_project/sample/extensions/user_list.yml,sha256=P_KdBu9VmNDEh9rEQ4QGKObpeXb34wMF4__uOShLh44,13098
128
129
  cmdbox/extensions/sample_project/sample/web/assets/sample/favicon.ico,sha256=z-jwmfktVBIsYjhUfCOj7ZNiq55GH-uXtbbDRzk7DHQ,9662
129
130
  cmdbox/extensions/sample_project/sample/web/assets/sample/icon.png,sha256=8WmOhepVHG46KG8Sjs4OjZht16dTcgpsNIs972PiVWU,327723
130
131
  cmdbox/licenses/LICENSE_Authlib_1_6_0_BSD_License.txt,sha256=jYKqOj5UKtBY_y34_upU28Iok0WYyCO-8x-80yVPgd4,1543
@@ -318,7 +319,7 @@ cmdbox/web/assets/apexcharts/apexcharts.css,sha256=l-xkqykcV8a22g04B-Vpt4JFWcHlw
318
319
  cmdbox/web/assets/apexcharts/apexcharts.min.js,sha256=zceUTsCKa8Y2SqjqZjLjifXQDnqsvKRTmT8fTIUix_4,570304
319
320
  cmdbox/web/assets/bootstrap/bootstrap.bundle.min.5.3.0.js,sha256=qlPVgvl-tZTCpcxYJFdHB_m6mDe84wRr-l81VoYPTgQ,80421
320
321
  cmdbox/web/assets/bootstrap/bootstrap.min.5.3.0.css,sha256=fx038NkLY4U1TCrBDiu5FWPEa9eiZu01EiLryshJbCo,232914
321
- cmdbox/web/assets/cmdbox/agent.js,sha256=NJwNfnIzTP23qGsuQr7ov-u4Mo9ibU7h3FN66-yTEpA,15763
322
+ cmdbox/web/assets/cmdbox/agent.js,sha256=IsvuprldT7iySd_3FHx63_Rj30f0pKfW51l3s2wzt8I,15808
322
323
  cmdbox/web/assets/cmdbox/audit.js,sha256=YDPQaThJSH-iBHvVK16JO4pyGZBoo-rCZou00D8x3yU,19346
323
324
  cmdbox/web/assets/cmdbox/color_mode.css,sha256=U4UGBnWiBMcrSEEusgT-_o-pt4MP3myoA9Lgnn1g6qE,19803
324
325
  cmdbox/web/assets/cmdbox/common.js,sha256=Aoq7CB9FRxpL_x-7-2a92nUSied9SoDd8B-hG--1qeU,69673
@@ -335,7 +336,7 @@ cmdbox/web/assets/cmdbox/signin.js,sha256=ZEVJJfYlcROSvEfzlnKBU-ZWU89b5YrwOsAWli
335
336
  cmdbox/web/assets/cmdbox/svgicon.js,sha256=zXaAD2CiPW9vBannU1EeM6-QV6H_9tWD_d0461pn6pI,12563
336
337
  cmdbox/web/assets/cmdbox/users.js,sha256=cMWwoEPigCeJVC_HLy2P8CgFek0Zp7oZDu0IZpWMhHY,30195
337
338
  cmdbox/web/assets/cmdbox/view_raw.js,sha256=Cyp3m-BjWGzFXyPCkruQu2d6Wtv5xy2ZGHcVOy0xmSA,2668
338
- cmdbox/web/assets/cmdbox/view_result.js,sha256=CQymfJZ4PvJZex0WJinH88xchF_6wSZYH1bBZ2ZGJ-w,6831
339
+ cmdbox/web/assets/cmdbox/view_result.js,sha256=yfodJByYU5LYf_UTmBa7NoXLsXYIVvsPk2BEHPmaONE,6888
339
340
  cmdbox/web/assets/encodingjs/LICENSE.txt,sha256=mBdECJD6rOMBa4VSrrJFzs67vFDvGuMPVtxvvDbgi9o,1070
340
341
  cmdbox/web/assets/encodingjs/encoding.js,sha256=pMUL__JEPZ0hHv1iH1kt-zwfDwJcDzzrD1JpdhEKBy8,297288
341
342
  cmdbox/web/assets/encodingjs/encoding.min.js,sha256=Q8LqZsCp1UpROsdrkt6K7gL0XriXIxjVrziq5WrDWBM,227869
@@ -378,9 +379,9 @@ cmdbox/web/assets/tree-menu/image/file.png,sha256=Uw4zYkHyuoZ_kSVkesHAeSeA_g9_LP
378
379
  cmdbox/web/assets/tree-menu/image/folder-close.png,sha256=TcgsKTBBF2ejgzekOEDBFBxsJf-Z5u0x9IZVi4GBR-I,284
379
380
  cmdbox/web/assets/tree-menu/image/folder-open.png,sha256=DT7y1GRK4oXJkFvqTN_oSGM5ZYARzPvjoCGL6wqkoo0,301
380
381
  cmdbox/web/assets/tree-menu/js/tree-menu.js,sha256=-GkZxI7xzHuXXHYQBHAVTcuKX4TtoiMuyIms6Xc3pxk,1029
381
- cmdbox-0.6.2.3.dist-info/LICENSE,sha256=sBzzPc5v-5LBuIFi2V4olsnoVg-3EBI0zRX5r19SOxE,1117
382
- cmdbox-0.6.2.3.dist-info/METADATA,sha256=EcpTJ3fQD3zwZYQYX-e7wD8B1lO8DOa2ykM_Qj7OwnA,33524
383
- cmdbox-0.6.2.3.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
384
- cmdbox-0.6.2.3.dist-info/entry_points.txt,sha256=1LdoMUjTD_YdxlsAiAiJ1cREcXFG8-Xg2xQTNYoNpT4,47
385
- cmdbox-0.6.2.3.dist-info/top_level.txt,sha256=eMEkD5jn8_0PkCAL8h5xJu4qAzF2O8Wf3vegFkKUXR4,7
386
- cmdbox-0.6.2.3.dist-info/RECORD,,
382
+ cmdbox-0.6.3.dist-info/LICENSE,sha256=sBzzPc5v-5LBuIFi2V4olsnoVg-3EBI0zRX5r19SOxE,1117
383
+ cmdbox-0.6.3.dist-info/METADATA,sha256=o68uXEPaEhHqDp9HvcnpzP4qXBc9KTt8-sLqUcrJjX4,33522
384
+ cmdbox-0.6.3.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
385
+ cmdbox-0.6.3.dist-info/entry_points.txt,sha256=1LdoMUjTD_YdxlsAiAiJ1cREcXFG8-Xg2xQTNYoNpT4,47
386
+ cmdbox-0.6.3.dist-info/top_level.txt,sha256=eMEkD5jn8_0PkCAL8h5xJu4qAzF2O8Wf3vegFkKUXR4,7
387
+ cmdbox-0.6.3.dist-info/RECORD,,