@scemoon/cdh 1.0.0 → 1.0.3
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.
- package/cdh/__pycache__/__main__.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
- package/cdh/cli.py +473 -87
- package/cdh/scaffold.py +386 -121
- package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
- package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
- package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
- package/onecode/agent/agents/types.py +123 -79
- package/onecode/agent/cdh_loader.py +26 -13
- package/onecode/agent/engine.py +426 -235
- package/onecode/agent/onecode_agent_acp.py +200 -73
- package/onecode/agent/session.py +8 -13
- package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/agent_tools.py +4 -4
- package/onecode/agent/tools/todo_tools.py +188 -0
- package/onecode/cli.py +3 -3
- package/onecode/commands.py +439 -194
- package/onecode/config.py +3 -3
- package/onecode/config_screen.py +4 -2
- package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
- package/onecode/models/errors.py +11 -1
- package/onecode/models/messages.py +35 -16
- package/onecode/models/provider.py +243 -12
- package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/deepseek_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/anthropic_provider.py +4 -2
- package/onecode/models/providers/deepseek_provider.py +2 -0
- package/onecode/models/providers/glm_provider.py +2 -1
- package/onecode/models/providers/minimax_provider.py +2 -1
- package/onecode/models/providers/minimaxi_provider.py +76 -25
- package/onecode/models/providers/ollama_provider.py +2 -1
- package/onecode/models/providers/openai_provider.py +4 -0
- package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/models.cpython-314.pyc +0 -0
- package/onecode/tasks/manager.py +1 -1
- package/package.json +1 -1
- package/pyproject.toml +10 -0
- package/run.js +24 -7
- package/tui/__pycache__/app.cpython-314.pyc +0 -0
- package/tui/__pycache__/cli.cpython-314.pyc +0 -0
- package/tui/__pycache__/db.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
- package/tui/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/__pycache__/paths.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
- package/tui/acp/agent.py +60 -11
- package/tui/acp/api.py +6 -0
- package/tui/acp/messages.py +11 -0
- package/tui/acp/prompt.py +1 -1
- package/tui/acp/protocol.py +31 -0
- package/tui/app.py +60 -8
- package/tui/db.py +23 -0
- package/tui/directory_watcher.py +35 -1
- package/tui/messages.py +4 -1
- package/tui/paths.py +3 -3
- package/tui/screens/__pycache__/agent_modal.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/component_picker.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_app.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/settings.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
- package/tui/screens/component_picker.py +148 -0
- package/tui/screens/main.py +47 -95
- package/tui/screens/projects.tcss +10 -0
- package/tui/screens/projects_app.py +33 -5
- package/tui/screens/projects_screen.py +171 -9
- package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/ask_user.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_directory_tree.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/question.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent_screen.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
- package/tui/widgets/ask_user.py +1 -1
- package/tui/widgets/conversation.py +99 -42
- package/tui/widgets/modified_files.py +155 -23
- package/tui/widgets/plan.py +22 -0
- package/tui/widgets/project_directory_tree.py +22 -10
- package/tui/widgets/project_grid_select.py +23 -1
- package/tui/widgets/question.py +1 -1
- package/tui/widgets/subagent.py +202 -36
- package/tui/widgets/subagent_screen.py +186 -0
- package/tui/widgets/tool_call.py +5 -8
- package/onecode/agent/tools/task_tools.py +0 -249
package/onecode/commands.py
CHANGED
|
@@ -8,269 +8,514 @@ if TYPE_CHECKING:
|
|
|
8
8
|
from tui.widgets.conversation import Conversation
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
_KNOWN_PROVIDERS = {
|
|
12
|
+
"anthropic": ["claude-3-opus", "claude-3-sonnet", "claude-3-haiku"],
|
|
13
|
+
"openai": ["gpt-4o", "gpt-4-turbo", "gpt-4o-mini"],
|
|
14
|
+
"deepseek": ["deepseek-chat", "deepseek-reasoner"],
|
|
15
|
+
"minimax": ["MiniMax-M2.7", "MiniMax-M2.5"],
|
|
16
|
+
"minimaxi": ["MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M3"],
|
|
17
|
+
"glm": ["glm-4-plus", "glm-4-flash"],
|
|
18
|
+
"ollama": [],
|
|
19
|
+
}
|
|
13
20
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
|
|
22
|
+
# ── small helpers ──────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _load_cfg():
|
|
26
|
+
from onecode.config import load_config
|
|
27
|
+
return load_config()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _save_cfg(cfg):
|
|
31
|
+
from onecode.config import save_config
|
|
20
32
|
save_config(cfg)
|
|
21
|
-
conversation.app.settings.set("mode", mode)
|
|
22
|
-
await conversation.set_mode(mode)
|
|
23
|
-
conversation.flash(f"onecode mode set to [b]{mode}")
|
|
24
33
|
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
from
|
|
35
|
+
def _post_note(conversation, text):
|
|
36
|
+
from tui.widgets.markdown_note import MarkdownNote
|
|
37
|
+
return conversation.post(MarkdownNote(text))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def _post(conversation, text):
|
|
41
|
+
await _post_note(conversation, text)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ── status: one-shot snapshot of every onecode: setting ───────────────
|
|
45
|
+
|
|
28
46
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
47
|
+
async def _status(conversation: "Conversation", parameters: str) -> None:
|
|
48
|
+
"""Render a single capability panel: provider, model, mode, skills, MCP."""
|
|
49
|
+
cfg = _load_cfg()
|
|
50
|
+
from onecode.skills.loader import SkillLoader
|
|
51
|
+
from onecode.mcp.manager import MCPManager
|
|
52
|
+
|
|
53
|
+
skills = SkillLoader().get_all()
|
|
54
|
+
enabled_skills = sorted(n for n, s in skills.items() if s.enabled)
|
|
55
|
+
disabled_skills = sorted(n for n, s in skills.items() if not s.enabled)
|
|
56
|
+
|
|
57
|
+
servers = MCPManager().list()
|
|
58
|
+
enabled_mcps = sorted(s["name"] for s in servers if s.get("enabled", True))
|
|
59
|
+
disabled_mcps = sorted(s["name"] for s in servers if not s.get("enabled", True))
|
|
60
|
+
|
|
61
|
+
lines = [
|
|
62
|
+
"**onecode — current state**\n",
|
|
63
|
+
f"- **provider**: `{cfg.default_provider}`",
|
|
64
|
+
f"- **model**: `{cfg.default_model}`",
|
|
65
|
+
f"- **mode**: `{cfg.default_mode}` (build | plan | solo)",
|
|
66
|
+
f"- **log level**: `{cfg.log_level}`",
|
|
67
|
+
f"- **skills**: {len(enabled_skills)} enabled, {len(disabled_skills)} disabled",
|
|
68
|
+
]
|
|
69
|
+
if enabled_skills:
|
|
70
|
+
lines.append(f" - enabled: {', '.join(f'`{n}`' for n in enabled_skills)}")
|
|
71
|
+
if disabled_skills:
|
|
72
|
+
lines.append(f" - disabled: {', '.join(f'`{n}`' for n in disabled_skills)}")
|
|
73
|
+
lines.append(f"- **mcp servers**: {len(enabled_mcps)} enabled, {len(disabled_mcps)} disabled")
|
|
74
|
+
if enabled_mcps:
|
|
75
|
+
lines.append(f" - enabled: {', '.join(f'`{n}`' for n in enabled_mcps)}")
|
|
76
|
+
if disabled_mcps:
|
|
77
|
+
lines.append(f" - disabled: {', '.join(f'`{n}`' for n in disabled_mcps)}")
|
|
78
|
+
|
|
79
|
+
lines += [
|
|
80
|
+
"\n**What can I do here?**",
|
|
81
|
+
"- `/onecode:provider [set <name|n>]` — show / change LLM provider",
|
|
82
|
+
"- `/onecode:model [set <name|n>]` — show / change LLM model",
|
|
83
|
+
"- `/onecode:skill list|enable|disable|add|remove [name]`",
|
|
84
|
+
"- `/onecode:mcp list|enable|disable|add|remove [name|url]`",
|
|
85
|
+
"- `/onecode:help` — show this overview",
|
|
86
|
+
]
|
|
87
|
+
await _post(conversation, "\n".join(lines))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── provider ───────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async def _provider(conversation: "Conversation", parameters: str) -> None:
|
|
94
|
+
cfg = _load_cfg()
|
|
95
|
+
args = parameters.strip().split()
|
|
96
|
+
sub = args[0].lower() if args else ""
|
|
97
|
+
|
|
98
|
+
if sub in ("", "show", "list"):
|
|
99
|
+
registered = sorted(_KNOWN_PROVIDERS.keys())
|
|
100
|
+
lines = [
|
|
101
|
+
"**LLM Provider**\n",
|
|
102
|
+
f"- current: `{cfg.default_provider}`",
|
|
103
|
+
f"- available ({len(registered)}):",
|
|
104
|
+
]
|
|
105
|
+
for i, name in enumerate(registered, 1):
|
|
106
|
+
marker = " ← active" if name == cfg.default_provider else ""
|
|
107
|
+
models = _KNOWN_PROVIDERS[name]
|
|
108
|
+
model_hint = f" models: {', '.join(models)}" if models else " models: (any local)"
|
|
109
|
+
lines.append(f" `{i}) {name}`{marker}{model_hint}")
|
|
110
|
+
lines += [
|
|
111
|
+
"\n**How to switch**",
|
|
112
|
+
"- `/onecode:provider set <name>` — e.g. `set minimaxi`",
|
|
113
|
+
"- `/onecode:provider set <n>` — by number from the list above",
|
|
114
|
+
"\n*Tip:* the provider change applies to new sessions; the current session keeps the active model until you start a new conversation.",
|
|
115
|
+
]
|
|
116
|
+
await _post(conversation, "\n".join(lines))
|
|
32
117
|
return
|
|
33
|
-
cfg = load_config()
|
|
34
|
-
cfg.default_provider = provider
|
|
35
|
-
save_config(cfg)
|
|
36
|
-
conversation.app.settings.set("provider", provider)
|
|
37
|
-
conversation.flash(f"onecode provider set to [b]{provider}")
|
|
38
118
|
|
|
119
|
+
if sub == "set":
|
|
120
|
+
if len(args) < 2:
|
|
121
|
+
conversation.notify("Usage: /onecode:provider set <name|n>", title="/onecode:provider", severity="error")
|
|
122
|
+
return
|
|
123
|
+
target = args[1].strip()
|
|
124
|
+
registered = sorted(_KNOWN_PROVIDERS.keys())
|
|
125
|
+
if target.isdigit():
|
|
126
|
+
idx = int(target)
|
|
127
|
+
if 1 <= idx <= len(registered):
|
|
128
|
+
target = registered[idx - 1]
|
|
129
|
+
if target not in registered:
|
|
130
|
+
conversation.notify(
|
|
131
|
+
f"Unknown provider '{target}'. Use /onecode:provider to list available ones.",
|
|
132
|
+
title="/onecode:provider",
|
|
133
|
+
severity="error",
|
|
134
|
+
)
|
|
135
|
+
return
|
|
136
|
+
cfg.default_provider = target
|
|
137
|
+
_save_cfg(cfg)
|
|
138
|
+
try:
|
|
139
|
+
conversation.app.settings.set("provider", target)
|
|
140
|
+
except Exception:
|
|
141
|
+
pass
|
|
142
|
+
conversation.flash(f"onecode provider set to [b]{target}")
|
|
143
|
+
await _status(conversation, "")
|
|
144
|
+
return
|
|
39
145
|
|
|
40
|
-
|
|
41
|
-
|
|
146
|
+
await _post(conversation, _HELP_PROVIDER)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
_HELP_PROVIDER = (
|
|
150
|
+
"**/onecode:provider** — show / switch the LLM provider\n\n"
|
|
151
|
+
"**Sub-commands**\n"
|
|
152
|
+
"- `(none)` or `show` — list registered providers with current marker\n"
|
|
153
|
+
"- `set <name>` — switch provider (name or list-number)\n\n"
|
|
154
|
+
"**Examples**\n"
|
|
155
|
+
"- `/onecode:provider` — show the picker\n"
|
|
156
|
+
"- `/onecode:provider set minimaxi` — switch by name\n"
|
|
157
|
+
"- `/onecode:provider set 3` — switch by list number"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ── model ──────────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
async def _model(conversation: "Conversation", parameters: str) -> None:
|
|
165
|
+
cfg = _load_cfg()
|
|
166
|
+
args = parameters.strip().split()
|
|
167
|
+
sub = args[0].lower() if args else ""
|
|
168
|
+
|
|
169
|
+
provider = cfg.default_provider
|
|
170
|
+
known = _KNOWN_PROVIDERS.get(provider, [])
|
|
171
|
+
|
|
172
|
+
if sub in ("", "show", "list"):
|
|
173
|
+
lines = [
|
|
174
|
+
"**LLM Model**\n",
|
|
175
|
+
f"- provider: `{provider}`",
|
|
176
|
+
f"- current: `{cfg.default_model}`",
|
|
177
|
+
]
|
|
178
|
+
if known:
|
|
179
|
+
lines.append(f"- known models for `{provider}` ({len(known)}):")
|
|
180
|
+
for i, name in enumerate(known, 1):
|
|
181
|
+
marker = " ← active" if name == cfg.default_model else ""
|
|
182
|
+
lines.append(f" `{i}) {name}`{marker}")
|
|
183
|
+
else:
|
|
184
|
+
lines.append("- *(no preset list — type any model name)*")
|
|
185
|
+
lines += [
|
|
186
|
+
"\n**How to switch**",
|
|
187
|
+
"- `/onecode:model set <name>` — e.g. `set MiniMax-M2.7`",
|
|
188
|
+
"- `/onecode:model set <n>` — by number from the list above",
|
|
189
|
+
"- `/onecode:model set <any-string>` — for providers without a preset",
|
|
190
|
+
"\n*Tip:* changing the model mid-session does not retroactively update the active request; new turns will use the new model.",
|
|
191
|
+
]
|
|
192
|
+
await _post(conversation, "\n".join(lines))
|
|
193
|
+
return
|
|
42
194
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
195
|
+
if sub == "set":
|
|
196
|
+
if len(args) < 2:
|
|
197
|
+
conversation.notify("Usage: /onecode:model set <name|n>", title="/onecode:model", severity="error")
|
|
198
|
+
return
|
|
199
|
+
target = args[1].strip()
|
|
200
|
+
if target.isdigit() and known:
|
|
201
|
+
idx = int(target)
|
|
202
|
+
if 1 <= idx <= len(known):
|
|
203
|
+
target = known[idx - 1]
|
|
204
|
+
if not target:
|
|
205
|
+
conversation.notify("Model name required", title="/onecode:model", severity="error")
|
|
206
|
+
return
|
|
207
|
+
cfg.default_model = target
|
|
208
|
+
_save_cfg(cfg)
|
|
209
|
+
try:
|
|
210
|
+
conversation.app.settings.set("model", target)
|
|
211
|
+
except Exception:
|
|
212
|
+
pass
|
|
213
|
+
conversation.flash(f"onecode model set to [b]{target}")
|
|
214
|
+
await _status(conversation, "")
|
|
46
215
|
return
|
|
47
|
-
cfg = load_config()
|
|
48
|
-
cfg.default_model = model
|
|
49
|
-
save_config(cfg)
|
|
50
|
-
conversation.app.settings.set("model", model)
|
|
51
|
-
conversation.flash(f"onecode model set to [b]{model}")
|
|
52
216
|
|
|
217
|
+
await _post(conversation, _HELP_MODEL)
|
|
53
218
|
|
|
54
|
-
|
|
55
|
-
|
|
219
|
+
|
|
220
|
+
_HELP_MODEL = (
|
|
221
|
+
"**/onecode:model** — show / switch the LLM model\n\n"
|
|
222
|
+
"**Sub-commands**\n"
|
|
223
|
+
"- `(none)` or `show` — list known models for the current provider\n"
|
|
224
|
+
"- `set <name>` — switch model (name or list-number)\n\n"
|
|
225
|
+
"**Examples**\n"
|
|
226
|
+
"- `/onecode:model` — show the picker for the current provider\n"
|
|
227
|
+
"- `/onecode:model set MiniMax-M2.7` — switch by name\n"
|
|
228
|
+
"- `/onecode:model set 1` — switch by list number"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# ── skill ──────────────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
async def _skill(conversation: "Conversation", parameters: str) -> None:
|
|
56
236
|
from onecode.skills.manager import SkillManager
|
|
57
237
|
from onecode.skills.loader import SkillLoader
|
|
58
238
|
from onecode.skills.create import create_skill_scaffold
|
|
59
239
|
|
|
60
240
|
parts = parameters.strip().split()
|
|
61
|
-
|
|
241
|
+
sub = parts[0].lower() if parts else ""
|
|
62
242
|
rest = " ".join(parts[1:]) if len(parts) > 1 else ""
|
|
63
243
|
|
|
64
|
-
if
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
await conversation.post(MarkdownNote("**No skills found.**\n\nUse `/skill add <name>` to create a new skill."))
|
|
244
|
+
if sub in ("", "list", "show"):
|
|
245
|
+
skills = SkillLoader().get_all()
|
|
246
|
+
if not skills:
|
|
247
|
+
await _post(conversation, "**Skills**\n\n_No skills installed._\n\nUse `/onecode:skill add <name>` to scaffold a new one.")
|
|
69
248
|
return
|
|
249
|
+
lines = ["**Skills**\n"]
|
|
250
|
+
names = sorted(skills.keys())
|
|
251
|
+
for i, name in enumerate(names, 1):
|
|
252
|
+
s = skills[name]
|
|
253
|
+
marker = "✓" if s.enabled else "✗"
|
|
254
|
+
desc = s.description or "(no description)"
|
|
255
|
+
lines.append(f"- `{i}) {marker}` **{name}** — {desc}")
|
|
256
|
+
lines += [
|
|
257
|
+
"\n**How to manage**",
|
|
258
|
+
"- `/onecode:skill enable <name|n>` — turn on",
|
|
259
|
+
"- `/onecode:skill disable <name|n>` — turn off",
|
|
260
|
+
"- `/onecode:skill add <name>` — scaffold a new skill",
|
|
261
|
+
"- `/onecode:skill remove <name|n>` — delete a skill",
|
|
262
|
+
]
|
|
263
|
+
await _post(conversation, "\n".join(lines))
|
|
264
|
+
return
|
|
70
265
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
266
|
+
if sub == "enable" or sub == "disable":
|
|
267
|
+
if not rest.strip():
|
|
268
|
+
conversation.notify(f"Usage: /onecode:skill {sub} <name|n>", title=f"/onecode:skill {sub}", severity="error")
|
|
269
|
+
return
|
|
270
|
+
target = _resolve_indexed_name(conversation, rest.strip(), SkillLoader().get_all(), sub)
|
|
271
|
+
if target is None:
|
|
272
|
+
return
|
|
273
|
+
mgr = SkillManager()
|
|
274
|
+
mgr.enable(target, sub == "enable")
|
|
275
|
+
conversation.flash(f"skill [b]{target}[/b] {sub}d")
|
|
276
|
+
await _skill(conversation, "list")
|
|
277
|
+
return
|
|
79
278
|
|
|
80
|
-
|
|
279
|
+
if sub == "add":
|
|
81
280
|
name = rest.strip()
|
|
82
281
|
if not name:
|
|
83
|
-
conversation.notify("
|
|
282
|
+
conversation.notify("Usage: /onecode:skill add <name>", title="/onecode:skill add", severity="error")
|
|
84
283
|
return
|
|
85
|
-
|
|
86
284
|
mgr = SkillManager()
|
|
87
285
|
if mgr.get(name):
|
|
88
|
-
conversation.notify(f"Skill '{name}' already exists", title="/skill add", severity="error")
|
|
286
|
+
conversation.notify(f"Skill '{name}' already exists", title="/onecode:skill add", severity="error")
|
|
89
287
|
return
|
|
90
|
-
|
|
91
288
|
err = create_skill_scaffold(mgr.skills_dir, name, f"A skill for {name}")
|
|
92
289
|
if err:
|
|
93
|
-
conversation.notify(f"Error: {err}", title="/skill add", severity="error")
|
|
290
|
+
conversation.notify(f"Error: {err}", title="/onecode:skill add", severity="error")
|
|
94
291
|
return
|
|
95
|
-
|
|
96
|
-
|
|
292
|
+
await _post(
|
|
293
|
+
conversation,
|
|
97
294
|
f"**Skill '{name}' created**\n\n"
|
|
98
|
-
f"
|
|
99
|
-
f"
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
name = rest.strip()
|
|
104
|
-
if not name:
|
|
105
|
-
conversation.notify("Skill name required", title="/skill remove", severity="error")
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
mgr = SkillManager()
|
|
109
|
-
err = mgr.remove(name)
|
|
110
|
-
if err:
|
|
111
|
-
conversation.notify(f"Error: {err}", title="/skill remove", severity="error")
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
await conversation.post(MarkdownNote(f"**Skill '{name}' removed.**"))
|
|
295
|
+
f"- location: `{mgr.skills_dir / name}`\n"
|
|
296
|
+
f"- edit `SKILL.md` to add instructions\n"
|
|
297
|
+
f"- then `/onecode:skill list` to confirm it shows up",
|
|
298
|
+
)
|
|
299
|
+
return
|
|
115
300
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
conversation.notify("Skill name required", title="/skill enable", severity="error")
|
|
301
|
+
if sub == "remove":
|
|
302
|
+
if not rest.strip():
|
|
303
|
+
conversation.notify("Usage: /onecode:skill remove <name|n>", title="/onecode:skill remove", severity="error")
|
|
120
304
|
return
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
skill_data = mgr.get(name)
|
|
124
|
-
if not skill_data:
|
|
125
|
-
conversation.notify(f"Skill '{name}' not found", title="/skill enable", severity="error")
|
|
305
|
+
target = _resolve_indexed_name(conversation, rest.strip(), SkillLoader().get_all(), sub)
|
|
306
|
+
if target is None:
|
|
126
307
|
return
|
|
308
|
+
SkillManager().remove(target)
|
|
309
|
+
conversation.flash(f"skill [b]{target}[/b] removed")
|
|
310
|
+
await _skill(conversation, "list")
|
|
311
|
+
return
|
|
127
312
|
|
|
128
|
-
|
|
129
|
-
await conversation.post(MarkdownNote(f"**Skill '{name}' enabled.**"))
|
|
313
|
+
await _post(conversation, _HELP_SKILL)
|
|
130
314
|
|
|
131
|
-
elif subcmd == "disable":
|
|
132
|
-
name = rest.strip()
|
|
133
|
-
if not name:
|
|
134
|
-
conversation.notify("Skill name required", title="/skill disable", severity="error")
|
|
135
|
-
return
|
|
136
315
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
316
|
+
_HELP_SKILL = (
|
|
317
|
+
"**/onecode:skill** — manage installed skills\n\n"
|
|
318
|
+
"**Sub-commands**\n"
|
|
319
|
+
"- `(none)` or `list` — show all skills with status\n"
|
|
320
|
+
"- `enable <name|n>` — turn a skill on\n"
|
|
321
|
+
"- `disable <name|n>` — turn a skill off\n"
|
|
322
|
+
"- `add <name>` — scaffold a new skill\n"
|
|
323
|
+
"- `remove <name|n>` — delete a skill\n\n"
|
|
324
|
+
"**Examples**\n"
|
|
325
|
+
"- `/onecode:skill` — list\n"
|
|
326
|
+
"- `/onecode:skill enable 2` — enable the skill at index 2 from the list\n"
|
|
327
|
+
"- `/onecode:skill add my-skill` — scaffold a new skill named `my-skill`"
|
|
328
|
+
)
|
|
142
329
|
|
|
143
|
-
mgr.enable(name, False)
|
|
144
|
-
await conversation.post(MarkdownNote(f"**Skill '{name}' disabled.**"))
|
|
145
330
|
|
|
146
|
-
|
|
147
|
-
await conversation.post(MarkdownNote(
|
|
148
|
-
"**Skill Management**\n\n"
|
|
149
|
-
"Usage:\n"
|
|
150
|
-
"- `/skill list` - List all skills\n"
|
|
151
|
-
"- `/skill add <name>` - Create a new skill\n"
|
|
152
|
-
"- `/skill remove <name>` - Remove a skill\n"
|
|
153
|
-
"- `/skill enable <name>` - Enable a skill\n"
|
|
154
|
-
"- `/skill disable <name>` - Disable a skill"
|
|
155
|
-
))
|
|
331
|
+
# ── mcp ────────────────────────────────────────────────────────────────
|
|
156
332
|
|
|
157
333
|
|
|
158
|
-
async def _mcp(conversation: Conversation, parameters: str) -> None:
|
|
159
|
-
from tui.widgets.markdown_note import MarkdownNote
|
|
334
|
+
async def _mcp(conversation: "Conversation", parameters: str) -> None:
|
|
160
335
|
from onecode.mcp.manager import MCPManager
|
|
161
336
|
|
|
162
337
|
parts = parameters.strip().split()
|
|
163
|
-
|
|
338
|
+
sub = parts[0].lower() if parts else ""
|
|
164
339
|
rest = " ".join(parts[1:]) if len(parts) > 1 else ""
|
|
165
340
|
|
|
166
|
-
if
|
|
167
|
-
|
|
168
|
-
servers = mgr.list()
|
|
341
|
+
if sub in ("", "list", "show"):
|
|
342
|
+
servers = MCPManager().list()
|
|
169
343
|
if not servers:
|
|
170
|
-
await
|
|
171
|
-
|
|
172
|
-
"
|
|
173
|
-
|
|
344
|
+
await _post(
|
|
345
|
+
conversation,
|
|
346
|
+
"**MCP Servers**\n\n_None configured._\n\n"
|
|
347
|
+
"Use `/onecode:mcp add <name> <url>` for an SSE server, or "
|
|
348
|
+
"`/onecode:mcp add <name> --type stdio --command <cmd>` for stdio.",
|
|
349
|
+
)
|
|
174
350
|
return
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
for s in servers:
|
|
178
|
-
name = s.get("name", "
|
|
351
|
+
lines = ["**MCP Servers**\n"]
|
|
352
|
+
names = [s.get("name", "?") for s in servers]
|
|
353
|
+
for i, s in enumerate(servers, 1):
|
|
354
|
+
name = s.get("name", "?")
|
|
179
355
|
transport = s.get("transport", "sse")
|
|
180
356
|
enabled = s.get("enabled", True)
|
|
181
|
-
|
|
357
|
+
marker = "✓" if enabled else "✗"
|
|
182
358
|
if transport == "sse":
|
|
183
|
-
|
|
184
|
-
lines.append(f"- {status} **{name}** (SSE)")
|
|
185
|
-
if url:
|
|
186
|
-
lines.append(f" - URL: `{url}`")
|
|
359
|
+
lines.append(f"- `{i}) {marker}` **{name}** (SSE) — `{s.get('url', '')}`")
|
|
187
360
|
else:
|
|
188
361
|
cmd = s.get("command", "")
|
|
189
|
-
args = " ".join(s.get("args"
|
|
190
|
-
lines.append(f"- {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
362
|
+
args = " ".join(s.get("args") or [])
|
|
363
|
+
lines.append(f"- `{i}) {marker}` **{name}** (stdio) — `{cmd} {args}`".rstrip())
|
|
364
|
+
lines += [
|
|
365
|
+
"\n**How to manage**",
|
|
366
|
+
"- `/onecode:mcp enable <name|n>`",
|
|
367
|
+
"- `/onecode:mcp disable <name|n>`",
|
|
368
|
+
"- `/onecode:mcp remove <name|n>`",
|
|
369
|
+
]
|
|
370
|
+
await _post(conversation, "\n".join(lines))
|
|
371
|
+
return
|
|
372
|
+
|
|
373
|
+
if sub == "enable" or sub == "disable":
|
|
374
|
+
if not rest.strip():
|
|
375
|
+
conversation.notify(f"Usage: /onecode:mcp {sub} <name|n>", title=f"/onecode:mcp {sub}", severity="error")
|
|
376
|
+
return
|
|
377
|
+
mgr = MCPManager()
|
|
378
|
+
target = _resolve_indexed_name(conversation, rest.strip(), {s["name"]: s for s in mgr.list()}, sub)
|
|
379
|
+
if target is None:
|
|
380
|
+
return
|
|
381
|
+
err = mgr.enable(target, sub == "enable")
|
|
382
|
+
if err:
|
|
383
|
+
conversation.notify(f"Error: {err}", title=f"/onecode:mcp {sub}", severity="error")
|
|
384
|
+
return
|
|
385
|
+
conversation.flash(f"mcp server [b]{target}[/b] {sub}d")
|
|
386
|
+
await _mcp(conversation, "list")
|
|
387
|
+
return
|
|
194
388
|
|
|
195
|
-
|
|
389
|
+
if sub == "add":
|
|
196
390
|
args_parts = rest.split()
|
|
197
391
|
if len(args_parts) < 2:
|
|
198
|
-
|
|
392
|
+
await _post(
|
|
393
|
+
conversation,
|
|
394
|
+
"**/onecode:mcp add** — register a new MCP server\n\n"
|
|
395
|
+
"- **SSE** (default): `/onecode:mcp add <name> <url>`\n"
|
|
396
|
+
"- **stdio**: `/onecode:mcp add <name> --type stdio --command <cmd> --args a,b`",
|
|
397
|
+
)
|
|
199
398
|
return
|
|
200
|
-
|
|
201
399
|
name = args_parts[0]
|
|
202
400
|
url = args_parts[1]
|
|
203
|
-
transport = "sse"
|
|
204
|
-
|
|
205
401
|
mgr = MCPManager()
|
|
206
402
|
if mgr.get(name):
|
|
207
|
-
conversation.notify(f"MCP server '{name}' already exists", title="/mcp add", severity="error")
|
|
403
|
+
conversation.notify(f"MCP server '{name}' already exists", title="/onecode:mcp add", severity="error")
|
|
208
404
|
return
|
|
209
|
-
|
|
210
405
|
mgr.add(name, url, transport="sse")
|
|
211
|
-
await
|
|
212
|
-
|
|
213
|
-
f"URL: `{url}`\n"
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
))
|
|
217
|
-
|
|
218
|
-
elif subcmd == "remove":
|
|
219
|
-
name = rest.strip()
|
|
220
|
-
if not name:
|
|
221
|
-
conversation.notify("Server name required", title="/mcp remove", severity="error")
|
|
222
|
-
return
|
|
223
|
-
|
|
224
|
-
mgr = MCPManager()
|
|
225
|
-
if not mgr.get(name):
|
|
226
|
-
conversation.notify(f"MCP server '{name}' not found", title="/mcp remove", severity="error")
|
|
227
|
-
return
|
|
228
|
-
|
|
229
|
-
mgr.remove(name)
|
|
230
|
-
await conversation.post(MarkdownNote(f"**MCP Server '{name}' removed.**"))
|
|
231
|
-
|
|
232
|
-
elif subcmd == "enable":
|
|
233
|
-
name = rest.strip()
|
|
234
|
-
if not name:
|
|
235
|
-
conversation.notify("Server name required", title="/mcp enable", severity="error")
|
|
236
|
-
return
|
|
237
|
-
|
|
238
|
-
mgr = MCPManager()
|
|
239
|
-
err = mgr.enable(name, True)
|
|
240
|
-
if err:
|
|
241
|
-
conversation.notify(f"Error: {err}", title="/mcp enable", severity="error")
|
|
242
|
-
return
|
|
243
|
-
|
|
244
|
-
await conversation.post(MarkdownNote(f"**MCP Server '{name}' enabled.**"))
|
|
406
|
+
await _post(
|
|
407
|
+
conversation,
|
|
408
|
+
f"**MCP Server '{name}' added**\n\n- transport: SSE\n- URL: `{url}`\n\nUse `/onecode:mcp list` to confirm.",
|
|
409
|
+
)
|
|
410
|
+
return
|
|
245
411
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
conversation.notify("Server name required", title="/mcp disable", severity="error")
|
|
412
|
+
if sub == "remove":
|
|
413
|
+
if not rest.strip():
|
|
414
|
+
conversation.notify("Usage: /onecode:mcp remove <name|n>", title="/onecode:mcp remove", severity="error")
|
|
250
415
|
return
|
|
251
|
-
|
|
252
416
|
mgr = MCPManager()
|
|
253
|
-
|
|
254
|
-
if
|
|
255
|
-
conversation.notify(f"Error: {err}", title="/mcp disable", severity="error")
|
|
417
|
+
target = _resolve_indexed_name(conversation, rest.strip(), {s["name"]: s for s in mgr.list()}, sub)
|
|
418
|
+
if target is None:
|
|
256
419
|
return
|
|
420
|
+
mgr.remove(target)
|
|
421
|
+
conversation.flash(f"mcp server [b]{target}[/b] removed")
|
|
422
|
+
await _mcp(conversation, "list")
|
|
423
|
+
return
|
|
257
424
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
425
|
+
await _post(conversation, _HELP_MCP)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
_HELP_MCP = (
|
|
429
|
+
"**/onecode:mcp** — manage MCP (Model Context Protocol) servers\n\n"
|
|
430
|
+
"**Sub-commands**\n"
|
|
431
|
+
"- `(none)` or `list` — show configured servers\n"
|
|
432
|
+
"- `add <name> <url>` — add an SSE server\n"
|
|
433
|
+
"- `enable <name|n>` / `disable <name|n>`\n"
|
|
434
|
+
"- `remove <name|n>`\n\n"
|
|
435
|
+
"**Examples**\n"
|
|
436
|
+
"- `/onecode:mcp` — list servers\n"
|
|
437
|
+
"- `/onecode:mcp add my-server https://example.com/mcp`\n"
|
|
438
|
+
"- `/onecode:mcp disable 1` — disable the first server in the list"
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
# ── helpers ────────────────────────────────────────────────────────────
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _resolve_indexed_name(conversation, raw, items, sub_label):
|
|
446
|
+
"""Accept either a literal name or a 1-based index from the most recent list.
|
|
447
|
+
|
|
448
|
+
``items`` is either a ``dict[name, ...]`` (skills) or already indexed
|
|
449
|
+
by name (MCP). Returns the resolved name or ``None`` after notifying
|
|
450
|
+
the user on error.
|
|
451
|
+
"""
|
|
452
|
+
raw = (raw or "").strip()
|
|
453
|
+
if not raw:
|
|
454
|
+
return None
|
|
455
|
+
keys = list(items.keys())
|
|
456
|
+
if raw.isdigit():
|
|
457
|
+
idx = int(raw)
|
|
458
|
+
if 1 <= idx <= len(keys):
|
|
459
|
+
return keys[idx - 1]
|
|
460
|
+
conversation.notify(
|
|
461
|
+
f"Index {idx} out of range (1..{len(keys)}). Run `/onecode:{sub_label}` first to see numbers.",
|
|
462
|
+
title=f"/onecode:{sub_label}",
|
|
463
|
+
severity="error",
|
|
464
|
+
)
|
|
465
|
+
return None
|
|
466
|
+
if raw in items:
|
|
467
|
+
return raw
|
|
468
|
+
conversation.notify(
|
|
469
|
+
f"Unknown name '{raw}'. Run `/onecode:{sub_label}` first to see available names.",
|
|
470
|
+
title=f"/onecode:{sub_label}",
|
|
471
|
+
severity="error",
|
|
472
|
+
)
|
|
473
|
+
return None
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
# ── registration ──────────────────────────────────────────────────────
|
|
270
477
|
|
|
271
478
|
|
|
272
479
|
def register_commands() -> None:
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
480
|
+
"""Register the slash commands exposed to the TUI prompt.
|
|
481
|
+
|
|
482
|
+
The help text intentionally covers both **what** the command does and
|
|
483
|
+
**how** to invoke it (sub-command + index syntax), so the
|
|
484
|
+
autocomplete panel is self-documenting.
|
|
485
|
+
"""
|
|
486
|
+
platform_commands.register(
|
|
487
|
+
"help",
|
|
488
|
+
"Show onecode: command overview and current state",
|
|
489
|
+
_status,
|
|
490
|
+
"(no args)",
|
|
491
|
+
)
|
|
492
|
+
platform_commands.register(
|
|
493
|
+
"status",
|
|
494
|
+
"Show current provider, model, mode, skills, and MCP servers",
|
|
495
|
+
_status,
|
|
496
|
+
"(no args)",
|
|
497
|
+
)
|
|
498
|
+
platform_commands.register(
|
|
499
|
+
"provider",
|
|
500
|
+
"Show / switch the LLM provider (current + numbered list)",
|
|
501
|
+
_provider,
|
|
502
|
+
"[set <name|n>]",
|
|
503
|
+
)
|
|
504
|
+
platform_commands.register(
|
|
505
|
+
"model",
|
|
506
|
+
"Show / switch the LLM model for the current provider",
|
|
507
|
+
_model,
|
|
508
|
+
"[set <name|n>]",
|
|
509
|
+
)
|
|
510
|
+
platform_commands.register(
|
|
511
|
+
"skill",
|
|
512
|
+
"Manage skills: list, enable, disable, add, remove (accepts name or list number)",
|
|
513
|
+
_skill,
|
|
514
|
+
"list|enable|disable|add|remove [name|n]",
|
|
515
|
+
)
|
|
516
|
+
platform_commands.register(
|
|
517
|
+
"mcp",
|
|
518
|
+
"Manage MCP servers: list, add, enable, disable, remove (accepts name or list number)",
|
|
519
|
+
_mcp,
|
|
520
|
+
"list|add|enable|disable|remove [name|n]",
|
|
521
|
+
)
|