pygpt-net 2.6.1__py3-none-any.whl → 2.6.6__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.
Files changed (131) hide show
  1. pygpt_net/CHANGELOG.txt +23 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +20 -1
  4. pygpt_net/config.py +55 -65
  5. pygpt_net/controller/__init__.py +5 -2
  6. pygpt_net/controller/calendar/note.py +101 -126
  7. pygpt_net/controller/chat/chat.py +38 -35
  8. pygpt_net/controller/chat/render.py +154 -214
  9. pygpt_net/controller/chat/response.py +5 -3
  10. pygpt_net/controller/chat/stream.py +92 -27
  11. pygpt_net/controller/config/config.py +39 -42
  12. pygpt_net/controller/config/field/checkbox.py +16 -12
  13. pygpt_net/controller/config/field/checkbox_list.py +36 -31
  14. pygpt_net/controller/config/field/cmd.py +51 -57
  15. pygpt_net/controller/config/field/combo.py +33 -16
  16. pygpt_net/controller/config/field/dictionary.py +48 -55
  17. pygpt_net/controller/config/field/input.py +50 -32
  18. pygpt_net/controller/config/field/slider.py +40 -45
  19. pygpt_net/controller/config/field/textarea.py +20 -6
  20. pygpt_net/controller/config/placeholder.py +110 -231
  21. pygpt_net/controller/ctx/common.py +48 -48
  22. pygpt_net/controller/ctx/ctx.py +91 -132
  23. pygpt_net/controller/lang/mapping.py +57 -95
  24. pygpt_net/controller/lang/plugins.py +64 -55
  25. pygpt_net/controller/lang/settings.py +39 -38
  26. pygpt_net/controller/layout/layout.py +176 -109
  27. pygpt_net/controller/mode/mode.py +88 -85
  28. pygpt_net/controller/model/model.py +73 -73
  29. pygpt_net/controller/plugins/plugins.py +209 -223
  30. pygpt_net/controller/plugins/presets.py +54 -55
  31. pygpt_net/controller/plugins/settings.py +54 -69
  32. pygpt_net/controller/presets/editor.py +33 -88
  33. pygpt_net/controller/presets/experts.py +20 -1
  34. pygpt_net/controller/presets/presets.py +293 -298
  35. pygpt_net/controller/settings/profile.py +16 -4
  36. pygpt_net/controller/theme/theme.py +72 -81
  37. pygpt_net/controller/ui/mode.py +118 -186
  38. pygpt_net/controller/ui/tabs.py +69 -90
  39. pygpt_net/controller/ui/ui.py +47 -56
  40. pygpt_net/controller/ui/vision.py +24 -23
  41. pygpt_net/core/agents/runner.py +15 -7
  42. pygpt_net/core/bridge/bridge.py +5 -5
  43. pygpt_net/core/command/command.py +149 -219
  44. pygpt_net/core/ctx/ctx.py +94 -146
  45. pygpt_net/core/debug/debug.py +48 -58
  46. pygpt_net/core/experts/experts.py +3 -3
  47. pygpt_net/core/models/models.py +74 -112
  48. pygpt_net/core/modes/modes.py +13 -21
  49. pygpt_net/core/plugins/plugins.py +154 -177
  50. pygpt_net/core/presets/presets.py +103 -176
  51. pygpt_net/core/render/web/body.py +217 -215
  52. pygpt_net/core/render/web/renderer.py +330 -474
  53. pygpt_net/core/text/utils.py +28 -44
  54. pygpt_net/core/tokens/tokens.py +104 -203
  55. pygpt_net/data/config/config.json +3 -3
  56. pygpt_net/data/config/models.json +3 -3
  57. pygpt_net/data/locale/locale.de.ini +2 -0
  58. pygpt_net/data/locale/locale.en.ini +2 -0
  59. pygpt_net/data/locale/locale.es.ini +2 -0
  60. pygpt_net/data/locale/locale.fr.ini +2 -0
  61. pygpt_net/data/locale/locale.it.ini +2 -0
  62. pygpt_net/data/locale/locale.pl.ini +3 -1
  63. pygpt_net/data/locale/locale.uk.ini +2 -0
  64. pygpt_net/data/locale/locale.zh.ini +2 -0
  65. pygpt_net/item/ctx.py +141 -139
  66. pygpt_net/plugin/agent/plugin.py +2 -1
  67. pygpt_net/plugin/audio_output/plugin.py +5 -2
  68. pygpt_net/plugin/base/plugin.py +101 -85
  69. pygpt_net/plugin/bitbucket/__init__.py +12 -0
  70. pygpt_net/plugin/bitbucket/config.py +267 -0
  71. pygpt_net/plugin/bitbucket/plugin.py +126 -0
  72. pygpt_net/plugin/bitbucket/worker.py +569 -0
  73. pygpt_net/plugin/cmd_code_interpreter/plugin.py +3 -2
  74. pygpt_net/plugin/cmd_custom/plugin.py +3 -2
  75. pygpt_net/plugin/cmd_files/plugin.py +3 -2
  76. pygpt_net/plugin/cmd_history/plugin.py +3 -2
  77. pygpt_net/plugin/cmd_mouse_control/plugin.py +5 -2
  78. pygpt_net/plugin/cmd_serial/plugin.py +3 -2
  79. pygpt_net/plugin/cmd_system/plugin.py +3 -6
  80. pygpt_net/plugin/cmd_web/plugin.py +3 -2
  81. pygpt_net/plugin/experts/plugin.py +2 -2
  82. pygpt_net/plugin/facebook/__init__.py +12 -0
  83. pygpt_net/plugin/facebook/config.py +359 -0
  84. pygpt_net/plugin/facebook/plugin.py +113 -0
  85. pygpt_net/plugin/facebook/worker.py +698 -0
  86. pygpt_net/plugin/github/__init__.py +12 -0
  87. pygpt_net/plugin/github/config.py +441 -0
  88. pygpt_net/plugin/github/plugin.py +126 -0
  89. pygpt_net/plugin/github/worker.py +674 -0
  90. pygpt_net/plugin/google/__init__.py +12 -0
  91. pygpt_net/plugin/google/config.py +367 -0
  92. pygpt_net/plugin/google/plugin.py +126 -0
  93. pygpt_net/plugin/google/worker.py +826 -0
  94. pygpt_net/plugin/idx_llama_index/plugin.py +3 -2
  95. pygpt_net/plugin/mailer/plugin.py +3 -5
  96. pygpt_net/plugin/openai_vision/plugin.py +3 -2
  97. pygpt_net/plugin/real_time/plugin.py +52 -60
  98. pygpt_net/plugin/slack/__init__.py +12 -0
  99. pygpt_net/plugin/slack/config.py +349 -0
  100. pygpt_net/plugin/slack/plugin.py +115 -0
  101. pygpt_net/plugin/slack/worker.py +639 -0
  102. pygpt_net/plugin/telegram/__init__.py +12 -0
  103. pygpt_net/plugin/telegram/config.py +308 -0
  104. pygpt_net/plugin/telegram/plugin.py +117 -0
  105. pygpt_net/plugin/telegram/worker.py +563 -0
  106. pygpt_net/plugin/twitter/__init__.py +12 -0
  107. pygpt_net/plugin/twitter/config.py +491 -0
  108. pygpt_net/plugin/twitter/plugin.py +125 -0
  109. pygpt_net/plugin/twitter/worker.py +837 -0
  110. pygpt_net/provider/agents/llama_index/legacy/openai_assistant.py +35 -3
  111. pygpt_net/tools/code_interpreter/tool.py +0 -1
  112. pygpt_net/tools/translator/tool.py +1 -1
  113. pygpt_net/ui/base/config_dialog.py +86 -100
  114. pygpt_net/ui/base/context_menu.py +48 -46
  115. pygpt_net/ui/dialog/preset.py +34 -77
  116. pygpt_net/ui/layout/ctx/ctx_list.py +10 -6
  117. pygpt_net/ui/layout/toolbox/presets.py +41 -41
  118. pygpt_net/ui/main.py +49 -31
  119. pygpt_net/ui/tray.py +61 -60
  120. pygpt_net/ui/widget/calendar/select.py +86 -70
  121. pygpt_net/ui/widget/lists/attachment.py +86 -44
  122. pygpt_net/ui/widget/lists/base_list_combo.py +85 -33
  123. pygpt_net/ui/widget/lists/context.py +135 -188
  124. pygpt_net/ui/widget/lists/preset.py +59 -61
  125. pygpt_net/ui/widget/textarea/web.py +161 -48
  126. pygpt_net/utils.py +8 -1
  127. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/METADATA +164 -2
  128. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/RECORD +131 -103
  129. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/LICENSE +0 -0
  130. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/WHEEL +0 -0
  131. {pygpt_net-2.6.1.dist-info → pygpt_net-2.6.6.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.06.30 02:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import json
@@ -21,7 +21,6 @@ from pygpt_net.item.ctx import CtxItem
21
21
  from pygpt_net.core.bridge.context import BridgeContext
22
22
 
23
23
  from .config import Config
24
- from .worker import Worker
25
24
 
26
25
  class Plugin(BasePlugin):
27
26
  def __init__(self, *args, **kwargs):
@@ -318,6 +317,8 @@ class Plugin(BasePlugin):
318
317
  :param ctx: CtxItem
319
318
  :param cmds: commands dict
320
319
  """
320
+ from .worker import Worker
321
+
321
322
  is_cmd = False
322
323
  my_commands = []
323
324
  for item in cmds:
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.12.15 04:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.plugin.base.plugin import BasePlugin
@@ -15,10 +15,6 @@ from pygpt_net.item.ctx import CtxItem
15
15
 
16
16
  from .config import Config
17
17
  from .runner import Runner
18
- from .worker import Worker
19
-
20
- from pygpt_net.utils import trans
21
-
22
18
 
23
19
  class Plugin(BasePlugin):
24
20
  def __init__(self, *args, **kwargs):
@@ -88,6 +84,8 @@ class Plugin(BasePlugin):
88
84
  :param cmds: commands dict
89
85
  :param silent: silent mode
90
86
  """
87
+ from .worker import Worker
88
+
91
89
  is_cmd = False
92
90
  force = False
93
91
  my_commands = []
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.07.30 00:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.core.types import (
@@ -25,7 +25,6 @@ from pygpt_net.item.ctx import CtxItem
25
25
  from pygpt_net.core.events import Event
26
26
 
27
27
  from .config import Config
28
- from .worker import Worker
29
28
 
30
29
  class Plugin(BasePlugin):
31
30
  def __init__(self, *args, **kwargs):
@@ -199,6 +198,8 @@ class Plugin(BasePlugin):
199
198
  :param ctx: CtxItem
200
199
  :param cmds: commands dict
201
200
  """
201
+ from .worker import Worker
202
+
202
203
  is_cmd = False
203
204
  needed_lock = False
204
205
  my_commands = []
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.11.29 23:00:00 #
9
+ # Updated Date: 2025.08.15 23:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from datetime import datetime
@@ -51,31 +51,18 @@ class Plugin(BasePlugin):
51
51
  ctx = event.ctx
52
52
 
53
53
  if name == Event.POST_PROMPT_END:
54
- silent = False
55
- if 'silent' in data and data['silent']:
56
- silent = True
57
- data['value'] = self.on_system_prompt(
58
- data['value'],
59
- silent,
60
- )
61
-
62
- if name == Event.AGENT_PROMPT:
63
- silent = False
64
- if 'silent' in data and data['silent']:
65
- silent = True
66
- data['value'] = self.on_agent_prompt(
67
- data['value'],
68
- silent,
69
- )
54
+ silent = bool(data.get('silent'))
55
+ data['value'] = self.on_system_prompt(data['value'], silent)
56
+
57
+ elif name == Event.AGENT_PROMPT:
58
+ silent = bool(data.get('silent'))
59
+ data['value'] = self.on_agent_prompt(data['value'], silent)
70
60
 
71
61
  elif name == Event.CMD_SYNTAX:
72
62
  self.cmd_syntax(data)
73
63
 
74
64
  elif name == Event.CMD_EXECUTE:
75
- self.cmd(
76
- ctx,
77
- data['commands'],
78
- )
65
+ self.cmd(ctx, data['commands'])
79
66
 
80
67
  def cmd_syntax(self, data: dict):
81
68
  """
@@ -83,9 +70,10 @@ class Plugin(BasePlugin):
83
70
 
84
71
  :param data: event data dict
85
72
  """
73
+ append = data['cmd'].append
86
74
  for option in self.allowed_cmds:
87
75
  if self.has_cmd(option):
88
- data['cmd'].append(self.get_cmd(option)) # append command
76
+ append(self.get_cmd(option)) # append command
89
77
 
90
78
  def cmd(self, ctx: CtxItem, cmds: list):
91
79
  """
@@ -94,28 +82,25 @@ class Plugin(BasePlugin):
94
82
  :param ctx: CtxItem
95
83
  :param cmds: commands dict
96
84
  """
97
- is_cmd = False
98
- my_commands = []
99
- for item in cmds:
100
- if item["cmd"] in self.allowed_cmds:
101
- my_commands.append(item)
102
- is_cmd = True
103
-
104
- if not is_cmd:
85
+ my_commands = [item for item in cmds if item.get("cmd") in self.allowed_cmds]
86
+ if not my_commands:
105
87
  return
106
88
 
107
89
  response = None
90
+ now_str = None
91
+
108
92
  for item in my_commands:
109
93
  try:
110
- if item["cmd"] in self.allowed_cmds and self.has_cmd(item["cmd"]):
111
- # get time
112
- if item["cmd"] == "get_time":
113
- time = datetime.now().strftime('%A, %Y-%m-%d %H:%M:%S')
94
+ cmd_name = item.get("cmd")
95
+ if cmd_name in self.allowed_cmds and self.has_cmd(cmd_name):
96
+ if cmd_name == "get_time":
97
+ if now_str is None:
98
+ now_str = datetime.now().strftime('%A, %Y-%m-%d %H:%M:%S')
114
99
  response = {
115
100
  "request": {
116
- "cmd": item["cmd"]
101
+ "cmd": cmd_name
117
102
  },
118
- "result": time,
103
+ "result": now_str,
119
104
  }
120
105
  except Exception as e:
121
106
  self.error(e)
@@ -131,18 +116,23 @@ class Plugin(BasePlugin):
131
116
  :param silent: silent mode (no logs)
132
117
  :return: updated prompt
133
118
  """
134
- if self.get_option_value("hour") or self.get_option_value("date"):
135
- if prompt.strip() != "":
119
+ get_opt = self.get_option_value
120
+ hour = get_opt("hour")
121
+ date = get_opt("date")
122
+
123
+ if hour or date:
124
+ if prompt and not prompt.isspace():
136
125
  prompt += "\n\n"
137
- if self.get_option_value("hour") and self.get_option_value("date"):
138
- prompt += self.get_option_value("tpl").\
139
- format(time=datetime.now().strftime('%A, %Y-%m-%d %H:%M:%S'))
140
- elif self.get_option_value("hour"):
141
- prompt += self.get_option_value("tpl").\
142
- format(time=datetime.now().strftime('%H:%M:%S'))
143
- elif self.get_option_value("date"):
144
- prompt += self.get_option_value("tpl").\
145
- format(time=datetime.now().strftime('%A, %Y-%m-%d'))
126
+
127
+ tpl = get_opt("tpl")
128
+ now = datetime.now()
129
+
130
+ if hour and date:
131
+ prompt += tpl.format(time=now.strftime('%A, %Y-%m-%d %H:%M:%S'))
132
+ elif hour:
133
+ prompt += tpl.format(time=now.strftime('%H:%M:%S'))
134
+ elif date:
135
+ prompt += tpl.format(time=now.strftime('%A, %Y-%m-%d'))
146
136
  return prompt
147
137
 
148
138
  def on_agent_prompt(self, prompt: str, silent: bool = False) -> str:
@@ -153,16 +143,18 @@ class Plugin(BasePlugin):
153
143
  :param silent: silent mode (no logs)
154
144
  :return: updated prompt
155
145
  """
156
- if self.get_option_value("hour") or self.get_option_value("date"):
157
- if self.get_option_value("hour") and self.get_option_value("date"):
158
- prompt = self.get_option_value("tpl").\
159
- format(time=datetime.now().strftime('%A, %Y-%m-%d %H:%M:%S')) + "\n\n" + prompt
160
- elif self.get_option_value("hour"):
161
- prompt = self.get_option_value("tpl").\
162
- format(time=datetime.now().strftime('%H:%M:%S') + "\n\n" + prompt)
163
- elif self.get_option_value("date"):
164
- prompt = self.get_option_value("tpl").\
165
- format(time=datetime.now().strftime('%A, %Y-%m-%d')) + "\n\n" + prompt
166
- return prompt
167
-
168
-
146
+ get_opt = self.get_option_value
147
+ hour = get_opt("hour")
148
+ date = get_opt("date")
149
+
150
+ if hour or date:
151
+ tpl = get_opt("tpl")
152
+ now = datetime.now()
153
+
154
+ if hour and date:
155
+ prompt = tpl.format(time=now.strftime('%A, %Y-%m-%d %H:%M:%S')) + "\n\n" + prompt
156
+ elif hour:
157
+ prompt = tpl.format(time=now.strftime('%H:%M:%S') + "\n\n" + prompt)
158
+ elif date:
159
+ prompt = tpl.format(time=now.strftime('%A, %Y-%m-%d')) + "\n\n" + prompt
160
+ return prompt
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2025.06.30 02:00:00 #
10
+ # ================================================== #
11
+
12
+ from .plugin import *
@@ -0,0 +1,349 @@
1
+ # config.py
2
+ #!/usr/bin/env python3
3
+ # -*- coding: utf-8 -*-
4
+ # ================================================== #
5
+ # This file is a part of PYGPT package #
6
+ # Website: https://pygpt.net #
7
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
8
+ # MIT License #
9
+ # Created By : Marcin Szczygliński #
10
+ # Updated Date: 2025.08.14 00:00:00 #
11
+ # ================================================== #
12
+
13
+ from pygpt_net.plugin.base.config import BaseConfig, BasePlugin
14
+
15
+
16
+ class Config(BaseConfig):
17
+ def __init__(self, plugin: BasePlugin = None, *args, **kwargs):
18
+ super(Config, self).__init__(plugin)
19
+ self.plugin = plugin
20
+
21
+ def from_defaults(self, plugin: BasePlugin = None):
22
+ # Endpoints / HTTP
23
+ plugin.add_option(
24
+ "api_base",
25
+ type="text",
26
+ value="https://slack.com/api",
27
+ label="API base",
28
+ description="Base Slack Web API URL.",
29
+ )
30
+ plugin.add_option(
31
+ "oauth_base",
32
+ type="text",
33
+ value="https://slack.com",
34
+ label="OAuth base",
35
+ description="Base for OAuth authorize (default https://slack.com).",
36
+ )
37
+ plugin.add_option(
38
+ "http_timeout",
39
+ type="int",
40
+ value=30,
41
+ label="HTTP timeout (s)",
42
+ description="Requests timeout in seconds.",
43
+ )
44
+
45
+ # OAuth2 (Slack)
46
+ plugin.add_option(
47
+ "oauth2_client_id",
48
+ type="text",
49
+ value="",
50
+ label="OAuth2 Client ID",
51
+ description="Client ID from Slack App.",
52
+ secret=True,
53
+ )
54
+ plugin.add_option(
55
+ "oauth2_client_secret",
56
+ type="text",
57
+ value="",
58
+ label="OAuth2 Client Secret",
59
+ description="Client Secret from Slack App.",
60
+ secret=True,
61
+ )
62
+ plugin.add_option(
63
+ "oauth2_redirect_uri",
64
+ type="text",
65
+ value="http://127.0.0.1:8733/callback",
66
+ label="Redirect URI",
67
+ description="Must match one of the redirect URLs in your Slack App.",
68
+ )
69
+ plugin.add_option(
70
+ "bot_scopes",
71
+ type="text",
72
+ value="chat:write,users:read,users:read.email,channels:read,groups:read,im:read,mpim:read,channels:history,groups:history,im:history,mpim:history,files:write",
73
+ label="Bot scopes (comma-separated)",
74
+ description="Scopes for bot token.",
75
+ )
76
+ plugin.add_option(
77
+ "user_scopes",
78
+ type="text",
79
+ value="",
80
+ label="User scopes (comma-separated)",
81
+ description="Optional user scopes (if you need a user token).",
82
+ )
83
+
84
+ # Tokens/cache
85
+ plugin.add_option(
86
+ "bot_token",
87
+ type="textarea",
88
+ value="",
89
+ label="(auto/manual) Bot token",
90
+ description="xoxb-... You can paste manually or obtain via OAuth.",
91
+ secret=True,
92
+ )
93
+ plugin.add_option(
94
+ "user_token",
95
+ type="textarea",
96
+ value="",
97
+ label="(auto) User token (optional)",
98
+ description="xoxp-... from OAuth (if user scopes requested).",
99
+ secret=True,
100
+ )
101
+ plugin.add_option(
102
+ "oauth2_refresh_token",
103
+ type="textarea",
104
+ value="",
105
+ label="(auto) Refresh token (if rotation enabled)",
106
+ description="Stored refresh token (bot or user).",
107
+ secret=True,
108
+ )
109
+ plugin.add_option(
110
+ "oauth2_expires_at",
111
+ type="text",
112
+ value="0",
113
+ label="(auto) Expires at (unix)",
114
+ description="Auto-calculated expiry time.",
115
+ )
116
+ plugin.add_option(
117
+ "team_id",
118
+ type="text",
119
+ value="",
120
+ label="(auto) Team ID",
121
+ description="Cached after auth.test or OAuth.",
122
+ )
123
+ plugin.add_option(
124
+ "bot_user_id",
125
+ type="text",
126
+ value="",
127
+ label="(auto) Bot user ID",
128
+ description="Cached after OAuth exchange.",
129
+ )
130
+ plugin.add_option(
131
+ "authed_user_id",
132
+ type="text",
133
+ value="",
134
+ label="(auto) Authed user ID",
135
+ description="Cached after auth.test/OAuth.",
136
+ )
137
+
138
+ plugin.add_option(
139
+ "oauth_auto_begin",
140
+ type="bool",
141
+ value=True,
142
+ label="Auto-start OAuth when required",
143
+ description="If a command needs token, begin OAuth flow automatically.",
144
+ )
145
+ plugin.add_option(
146
+ "oauth_open_browser",
147
+ type="bool",
148
+ value=True,
149
+ label="Open browser automatically",
150
+ description="Open authorize URL in default browser.",
151
+ )
152
+ plugin.add_option(
153
+ "oauth_local_server",
154
+ type="bool",
155
+ value=True,
156
+ label="Use local server for OAuth",
157
+ description="Start local HTTP server to capture redirect (requires local redirect).",
158
+ )
159
+ plugin.add_option(
160
+ "oauth_local_timeout",
161
+ type="int",
162
+ value=180,
163
+ label="OAuth local timeout (s)",
164
+ description="How long to wait for redirect with code.",
165
+ )
166
+ plugin.add_option(
167
+ "oauth_success_html",
168
+ type="textarea",
169
+ value="<html><body><h3>Authorization complete. You can close this window.</h3></body></html>",
170
+ label="Success HTML",
171
+ description="HTML shown on local callback success.",
172
+ )
173
+ plugin.add_option(
174
+ "oauth_fail_html",
175
+ type="textarea",
176
+ value="<html><body><h3>Authorization failed.</h3></body></html>",
177
+ label="Fail HTML",
178
+ description="HTML shown on local callback error.",
179
+ )
180
+ plugin.add_option(
181
+ "oauth_local_port",
182
+ type="int",
183
+ value=8733,
184
+ label="OAuth local port (0=auto)",
185
+ description="Local HTTP port for callback; use >1024. Must be registered in Slack App.",
186
+ )
187
+ plugin.add_option(
188
+ "oauth_allow_port_fallback",
189
+ type="bool",
190
+ value=True,
191
+ label="Allow fallback port if busy",
192
+ description="If preferred port is busy/forbidden, pick a free local port.",
193
+ )
194
+
195
+ # ---------------- Commands ----------------
196
+
197
+ # Auth
198
+ plugin.add_cmd(
199
+ "slack_oauth_begin",
200
+ instruction="Begin OAuth2 flow (returns authorize URL).",
201
+ params=[],
202
+ enabled=True,
203
+ description="Auth: begin OAuth2",
204
+ tab="auth",
205
+ )
206
+ plugin.add_cmd(
207
+ "slack_oauth_exchange",
208
+ instruction="Exchange authorization code for tokens.",
209
+ params=[
210
+ {"name": "code", "type": "str", "required": True, "description": "Authorization code"},
211
+ {"name": "state", "type": "str", "required": False, "description": "State (if used)"},
212
+ ],
213
+ enabled=True,
214
+ description="Auth: exchange code",
215
+ tab="auth",
216
+ )
217
+ plugin.add_cmd(
218
+ "slack_oauth_refresh",
219
+ instruction="Refresh token (if rotation enabled).",
220
+ params=[],
221
+ enabled=True,
222
+ description="Auth: refresh token",
223
+ tab="auth",
224
+ )
225
+ plugin.add_cmd(
226
+ "slack_auth_test",
227
+ instruction="Test authentication and get ids.",
228
+ params=[],
229
+ enabled=True,
230
+ description="Auth: test",
231
+ tab="auth",
232
+ )
233
+
234
+ # Users
235
+ plugin.add_cmd(
236
+ "slack_users_list",
237
+ instruction="List workspace users (contacts).",
238
+ params=[
239
+ {"name": "limit", "type": "int", "required": False, "description": "Default Slack pagination"},
240
+ {"name": "cursor", "type": "str", "required": False, "description": "Cursor for next page"},
241
+ {"name": "include_locale", "type": "bool", "required": False, "description": "Include locale"},
242
+ ],
243
+ enabled=True,
244
+ description="Users: list",
245
+ tab="users",
246
+ )
247
+
248
+ # Conversations
249
+ plugin.add_cmd(
250
+ "slack_conversations_list",
251
+ instruction="List channels/DMs visible to the token.",
252
+ params=[
253
+ {"name": "types", "type": "str", "required": False, "description": "public_channel,private_channel,im,mpim"},
254
+ {"name": "exclude_archived", "type": "bool", "required": False, "description": "Default true"},
255
+ {"name": "limit", "type": "int", "required": False, "description": "Default Slack pagination"},
256
+ {"name": "cursor", "type": "str", "required": False, "description": "Cursor for next page"},
257
+ ],
258
+ enabled=True,
259
+ description="Conversations: list",
260
+ tab="conversations",
261
+ )
262
+ plugin.add_cmd(
263
+ "slack_conversations_history",
264
+ instruction="Fetch channel/DM history.",
265
+ params=[
266
+ {"name": "channel", "type": "str", "required": True, "description": "Conversation ID"},
267
+ {"name": "limit", "type": "int", "required": False, "description": "Default Slack pagination"},
268
+ {"name": "cursor", "type": "str", "required": False, "description": "Cursor for next page"},
269
+ {"name": "oldest", "type": "str", "required": False, "description": "TS lower bound"},
270
+ {"name": "latest", "type": "str", "required": False, "description": "TS upper bound"},
271
+ {"name": "inclusive", "type": "bool", "required": False, "description": "Include bounds"},
272
+ ],
273
+ enabled=True,
274
+ description="Conversations: history",
275
+ tab="conversations",
276
+ )
277
+ plugin.add_cmd(
278
+ "slack_conversations_replies",
279
+ instruction="Fetch a thread by root ts.",
280
+ params=[
281
+ {"name": "channel", "type": "str", "required": True, "description": "Conversation ID"},
282
+ {"name": "ts", "type": "str", "required": True, "description": "Root thread ts"},
283
+ {"name": "limit", "type": "int", "required": False, "description": "Default Slack pagination"},
284
+ {"name": "cursor", "type": "str", "required": False, "description": "Cursor for next page"},
285
+ ],
286
+ enabled=True,
287
+ description="Conversations: replies",
288
+ tab="conversations",
289
+ )
290
+ plugin.add_cmd(
291
+ "slack_conversations_open",
292
+ instruction="Open/resume DM or MPDM.",
293
+ params=[
294
+ {"name": "users", "type": "list|str", "required": False, "description": "User IDs (1..8)."},
295
+ {"name": "channel", "type": "str", "required": False, "description": "Existing IM/MPIM id to resume"},
296
+ {"name": "return_im", "type": "bool", "required": False, "description": "Return full obj (default true)"},
297
+ ],
298
+ enabled=True,
299
+ description="Conversations: open",
300
+ tab="conversations",
301
+ )
302
+
303
+ # Chat
304
+ plugin.add_cmd(
305
+ "slack_chat_post_message",
306
+ instruction="Post message to channel/DM.",
307
+ params=[
308
+ {"name": "channel", "type": "str", "required": True, "description": "Channel/DM ID"},
309
+ {"name": "text", "type": "str", "required": False, "description": "Message text"},
310
+ {"name": "thread_ts", "type": "str", "required": False, "description": "Reply to thread"},
311
+ {"name": "blocks", "type": "list|dict", "required": False, "description": "Block Kit"},
312
+ {"name": "attachments", "type": "list", "required": False, "description": "Legacy attachments"},
313
+ {"name": "mrkdwn", "type": "bool", "required": False, "description": "Enable markdown"},
314
+ {"name": "unfurl_links", "type": "bool", "required": False, "description": "Unfurl links"},
315
+ {"name": "unfurl_media", "type": "bool", "required": False, "description": "Unfurl media"},
316
+ {"name": "reply_broadcast", "type": "bool", "required": False, "description": "Broadcast reply"},
317
+ ],
318
+ enabled=True,
319
+ description="Chat: post message",
320
+ tab="chat",
321
+ )
322
+ plugin.add_cmd(
323
+ "slack_chat_delete",
324
+ instruction="Delete a message.",
325
+ params=[
326
+ {"name": "channel", "type": "str", "required": True, "description": "Channel/DM ID"},
327
+ {"name": "ts", "type": "str", "required": True, "description": "Message ts"},
328
+ ],
329
+ enabled=True,
330
+ description="Chat: delete",
331
+ tab="chat",
332
+ )
333
+
334
+ # Files (new upload flow)
335
+ plugin.add_cmd(
336
+ "slack_files_upload",
337
+ instruction="Upload file (External flow) and share.",
338
+ params=[
339
+ {"name": "path", "type": "str", "required": True, "description": "Local file path"},
340
+ {"name": "channel", "type": "str", "required": False, "description": "Share to channel id"},
341
+ {"name": "title", "type": "str", "required": False, "description": "Title"},
342
+ {"name": "initial_comment", "type": "str", "required": False, "description": "Comment with file"},
343
+ {"name": "thread_ts", "type": "str", "required": False, "description": "Share in thread"},
344
+ {"name": "alt_text", "type": "str", "required": False, "description": "Alt text (images)"},
345
+ ],
346
+ enabled=True,
347
+ description="Files: upload (getUploadURLExternal + completeUploadExternal)",
348
+ tab="files",
349
+ )