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 +3 -2
- cmdbox/app/common.py +28 -12
- cmdbox/app/features/cli/cmdbox_audit_search.py +6 -0
- cmdbox/app/features/cli/cmdbox_client_http.py +154 -0
- cmdbox/app/features/cli/cmdbox_cmd_list.py +11 -7
- cmdbox/app/features/cli/cmdbox_web_start.py +1 -1
- cmdbox/app/mcp.py +227 -133
- cmdbox/app/options.py +2 -1
- cmdbox/app/web.py +17 -2
- cmdbox/extensions/features.yml +3 -0
- cmdbox/extensions/sample_project/sample/extensions/features.yml +3 -12
- cmdbox/extensions/sample_project/sample/extensions/user_list.yml +25 -1
- cmdbox/version.py +2 -2
- cmdbox/web/assets/cmdbox/agent.js +1 -0
- cmdbox/web/assets/cmdbox/view_result.js +2 -1
- {cmdbox-0.6.2.3.dist-info → cmdbox-0.6.3.dist-info}/METADATA +1 -1
- {cmdbox-0.6.2.3.dist-info → cmdbox-0.6.3.dist-info}/RECORD +21 -20
- {cmdbox-0.6.2.3.dist-info → cmdbox-0.6.3.dist-info}/LICENSE +0 -0
- {cmdbox-0.6.2.3.dist-info → cmdbox-0.6.3.dist-info}/WHEEL +0 -0
- {cmdbox-0.6.2.3.dist-info → cmdbox-0.6.3.dist-info}/entry_points.txt +0 -0
- {cmdbox-0.6.2.3.dist-info → cmdbox-0.6.3.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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
|
-
|
|
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
|
|
506
|
-
|
|
507
|
-
if
|
|
508
|
-
txt = tabulate(
|
|
509
|
-
elif
|
|
510
|
-
txt = tabulate([
|
|
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(
|
|
513
|
-
elif
|
|
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
|
|
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
|
|
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
|
|
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=
|
|
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=
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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=
|
|
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
|
|
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[
|
|
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__,
|
|
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__
|
|
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
|
|
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
|
-
|
|
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) ->
|
|
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
|
-
|
|
380
|
+
ToolList: ToolListのリスト
|
|
424
381
|
"""
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
|
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=
|
|
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
|
-
|
|
565
|
-
|
|
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.
|
|
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:
|
cmdbox/extensions/features.yml
CHANGED
|
@@ -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:
|
|
53
|
-
cmds: [
|
|
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: [
|
|
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, /
|
|
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,
|
|
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.
|
|
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(
|
|
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
|
}
|
|
@@ -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=
|
|
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=
|
|
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=
|
|
23
|
-
cmdbox/app/options.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
127
|
-
cmdbox/extensions/sample_project/sample/extensions/user_list.yml,sha256=
|
|
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=
|
|
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=
|
|
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.
|
|
382
|
-
cmdbox-0.6.
|
|
383
|
-
cmdbox-0.6.
|
|
384
|
-
cmdbox-0.6.
|
|
385
|
-
cmdbox-0.6.
|
|
386
|
-
cmdbox-0.6.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|