dulus 0.2.17__tar.gz → 0.2.19__tar.gz
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.
- {dulus-0.2.17/dulus.egg-info → dulus-0.2.19}/PKG-INFO +12 -4
- {dulus-0.2.17 → dulus-0.2.19}/README.md +10 -3
- {dulus-0.2.17 → dulus-0.2.19}/data/plugins/composio/composio_plugin/__init__.py +5 -5
- {dulus-0.2.17 → dulus-0.2.19}/data/plugins/composio/composio_plugin/session_manager.py +71 -71
- {dulus-0.2.17 → dulus-0.2.19}/data/plugins/composio/composio_plugin/tool_generator.py +156 -156
- {dulus-0.2.17 → dulus-0.2.19}/data/plugins/composio/plugin.json +11 -11
- {dulus-0.2.17 → dulus-0.2.19}/data/plugins/composio/plugin_tool.py +434 -434
- {dulus-0.2.17 → dulus-0.2.19}/docs/news.md +7 -0
- {dulus-0.2.17 → dulus-0.2.19/dulus.egg-info}/PKG-INFO +12 -4
- {dulus-0.2.17 → dulus-0.2.19}/dulus.egg-info/requires.txt +1 -0
- {dulus-0.2.17 → dulus-0.2.19}/dulus.py +195 -1
- {dulus-0.2.17 → dulus-0.2.19}/pyproject.toml +4 -1
- {dulus-0.2.17 → dulus-0.2.19}/LICENSE +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/MANIFEST.in +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/agent.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/backend/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/backend/compressor.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/backend/context.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/backend/githook.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/backend/marketplace.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/backend/mempalace_bridge.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/backend/personas.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/backend/plugins.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/backend/server.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/backend/tasks.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/batch_api.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/checkpoint/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/checkpoint/hooks.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/checkpoint/store.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/checkpoint/types.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/claude_code_watcher.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/clipboard_utils.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/cloudsave.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/common.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/compaction.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/config.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/context.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/data/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/data/active_persona.json +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/data/context.json +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/data/marketplace.json +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/data/personas.json +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/data/plugins/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/data/plugins/composio/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/data/tasks.json +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/README.md +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/api.html +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/architecture.md +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/azure-speech-template.json +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/dashboard/index.html +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/divider.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/generate.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/hero.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/index.html +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/nvidia-models.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/particle-playground.html +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/personas/index.html +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/poetry-banner.png +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/preview.html +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/sec-agents.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/sec-brainstorm.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/sec-bridges.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/sec-features.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/sec-freetier.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/sec-memory.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/sec-models.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/sec-perms.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/sec-plugins.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/sec-quickstart.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/sec-ssj.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/spinners.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/split-pane.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/terminal-boot.svg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/docs/uploads/particle-playground.html +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/dulus.egg-info/SOURCES.txt +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/dulus.egg-info/top_level.txt +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/dulus_gui.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/dulus_mcp/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/dulus_mcp/client.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/dulus_mcp/config.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/dulus_mcp/tools.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/dulus_mcp/types.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/gui/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/gui/agent_bridge.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/gui/chat_widget.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/gui/main_window.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/gui/personas.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/gui/session_utils.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/gui/settings_dialog.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/gui/sidebar.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/gui/tasks_view.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/gui/themes.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/gui/tool_panel.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/input.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/license_manager.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/memory/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/memory/audit.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/memory/consolidator.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/memory/context.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/memory/offload.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/memory/palace.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/memory/scan.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/memory/sessions.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/memory/store.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/memory/tools.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/memory/types.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/memory/vector_search.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/multi_agent/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/multi_agent/subagent.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/multi_agent/tools.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/offload_helper.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/plugin/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/plugin/autoadapter.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/plugin/loader.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/plugin/recommend.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/plugin/store.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/plugin/types.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/providers.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/setup.cfg +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/skill/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/skill/builtin.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/skill/clawhub.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/skill/executor.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/skill/loader.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/skill/tools.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/skills.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/spinner.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/string_utils.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/subagent.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/task/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/task/store.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/task/tools.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/task/types.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_checkpoint.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_compaction.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_diff_view.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_injection_fix.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_license.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_mcp.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_memory.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_plugin.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_skills.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_subagent.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_task.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_telegram_buffer.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_tool_registry.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tests/test_voice.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tmux_offloader.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tmux_tools.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tool_registry.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/tools.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/ui/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/ui/input.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/ui/render.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/voice/__init__.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/voice/keyterms.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/voice/recorder.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/voice/stt.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/voice/tts.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/webchat.py +0 -0
- {dulus-0.2.17 → dulus-0.2.19}/webchat_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.19
|
|
4
4
|
Summary: Spanish-first multi-provider AI CLI — 14 NVIDIA models free, Mesa Redonda, voice, TTS, RTK token reducer, MemPalace
|
|
5
5
|
Author: KevRojo
|
|
6
6
|
License: GPL-3.0
|
|
@@ -35,6 +35,7 @@ Requires-Dist: customtkinter>=5.2.0
|
|
|
35
35
|
Requires-Dist: Pillow>=10.0.0
|
|
36
36
|
Requires-Dist: typing-extensions>=4.10.0
|
|
37
37
|
Requires-Dist: composio>=1.0.0rc2
|
|
38
|
+
Requires-Dist: beautifulsoup4>=4.12.0
|
|
38
39
|
Provides-Extra: memory
|
|
39
40
|
Requires-Dist: mempalace>=3.3.4; extra == "memory"
|
|
40
41
|
Provides-Extra: voice
|
|
@@ -68,7 +69,7 @@ SET /sticky_input ON since the first run for the best experience!
|
|
|
68
69
|
<a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
|
|
69
70
|
<img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
|
|
70
71
|
<img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
|
|
71
|
-
<img src="https://img.shields.io/badge/version-v0.2.
|
|
72
|
+
<img src="https://img.shields.io/badge/version-v0.2.19-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
|
|
72
73
|
<img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
|
|
73
74
|
<img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
|
|
74
75
|
<img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
|
|
@@ -95,14 +96,21 @@ Use claude-code as an API without the new 'extra-usage' wall <3
|
|
|
95
96
|
|
|
96
97
|
<p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/poetry-banner.png" alt="Anthropic only sees text while you and Claude are writing poetry" width="100%"></p>
|
|
97
98
|
|
|
99
|
+
<img width="1240" height="882" alt="image" src="https://github.com/user-attachments/assets/27dd76bc-8919-4bb9-b3c3-38ae7d92e482" />
|
|
100
|
+
|
|
101
|
+
|
|
98
102
|
<p align="center">
|
|
99
103
|
<sub>⚡ <b>Saves you Claude tokens?</b> Throw a sat — BTC: <code>1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN</code></sub>
|
|
100
104
|
</p>
|
|
101
105
|
|
|
102
106
|
Dulus is a **lightweight Python reimplementation of Claude Code** that isn't locked to Claude. It ships the whole loop — REPL, tool dispatch, streaming, context compaction, checkpoints, sub-agents, voice, Telegram bridge, MCP, plugins — in roughly **12K lines you can actually read**. Fork it. Bend it. Run it offline against Qwen on your M2.
|
|
103
107
|
|
|
104
|
-
> **v0.2.
|
|
105
|
-
>
|
|
108
|
+
> **v0.2.17 — May 9, 2026** — Mega-release: Composio plugin bundled (1000+ apps, no MCP), `/skill list` interactive picker (awesome / composio / local), awesome skills live from GitHub (no Claude Code needed), lite mode finally functional, system prompt rewritten in English, `VERSION` auto-syncs from pyproject.
|
|
109
|
+
> **v0.2.16 — May 9, 2026** — MemPalace per-session dedup. No more re-injecting the same memory every turn — content-hash cache saves ~8K tokens in a 20-turn conversation. `/mem_palace reset` clears it on demand.
|
|
110
|
+
> **v0.2.15 — May 9, 2026** — Banner image hosted locally so PyPI renders it correctly.
|
|
111
|
+
> **v0.2.14 — May 9, 2026** — Multi-user Telegram bridge: `telegram_chat_ids: "123,456,,"` supported. Replies route to the user who sent each message.
|
|
112
|
+
> **v0.2.13 — May 8, 2026** — Internal robustness fixes for Ollama streaming.
|
|
113
|
+
> Type `/news` to see the full changelog.
|
|
106
114
|
|
|
107
115
|
---
|
|
108
116
|
|
|
@@ -22,7 +22,7 @@ SET /sticky_input ON since the first run for the best experience!
|
|
|
22
22
|
<a href="https://pypi.org/project/dulus/"><img src="https://static.pepy.tech/badge/dulus?style=flat-square" alt="downloads"/></a>
|
|
23
23
|
<img src="https://img.shields.io/badge/python-3.11+-ff6b1f?style=flat-square&labelColor=07070a" alt="python"/>
|
|
24
24
|
<img src="https://img.shields.io/badge/license-GPLv3-ff6b1f?style=flat-square&labelColor=07070a" alt="license"/>
|
|
25
|
-
<img src="https://img.shields.io/badge/version-v0.2.
|
|
25
|
+
<img src="https://img.shields.io/badge/version-v0.2.19-ff6b1f?style=flat-square&labelColor=07070a" alt="version"/>
|
|
26
26
|
<img src="https://img.shields.io/badge/providers-11-ff6b1f?style=flat-square&labelColor=07070a" alt="providers"/>
|
|
27
27
|
<img src="https://img.shields.io/badge/tools-27-ff6b1f?style=flat-square&labelColor=07070a" alt="tools"/>
|
|
28
28
|
<img src="https://img.shields.io/badge/tests-263+-ff6b1f?style=flat-square&labelColor=07070a" alt="tests"/>
|
|
@@ -49,14 +49,21 @@ Use claude-code as an API without the new 'extra-usage' wall <3
|
|
|
49
49
|
|
|
50
50
|
<p align="center"><img src="https://raw.githubusercontent.com/KevRojo/Dulus/main/docs/poetry-banner.png" alt="Anthropic only sees text while you and Claude are writing poetry" width="100%"></p>
|
|
51
51
|
|
|
52
|
+
<img width="1240" height="882" alt="image" src="https://github.com/user-attachments/assets/27dd76bc-8919-4bb9-b3c3-38ae7d92e482" />
|
|
53
|
+
|
|
54
|
+
|
|
52
55
|
<p align="center">
|
|
53
56
|
<sub>⚡ <b>Saves you Claude tokens?</b> Throw a sat — BTC: <code>1JzatQDn9fMLnKTd3KYgztsLHC95bJEzSN</code></sub>
|
|
54
57
|
</p>
|
|
55
58
|
|
|
56
59
|
Dulus is a **lightweight Python reimplementation of Claude Code** that isn't locked to Claude. It ships the whole loop — REPL, tool dispatch, streaming, context compaction, checkpoints, sub-agents, voice, Telegram bridge, MCP, plugins — in roughly **12K lines you can actually read**. Fork it. Bend it. Run it offline against Qwen on your M2.
|
|
57
60
|
|
|
58
|
-
> **v0.2.
|
|
59
|
-
>
|
|
61
|
+
> **v0.2.17 — May 9, 2026** — Mega-release: Composio plugin bundled (1000+ apps, no MCP), `/skill list` interactive picker (awesome / composio / local), awesome skills live from GitHub (no Claude Code needed), lite mode finally functional, system prompt rewritten in English, `VERSION` auto-syncs from pyproject.
|
|
62
|
+
> **v0.2.16 — May 9, 2026** — MemPalace per-session dedup. No more re-injecting the same memory every turn — content-hash cache saves ~8K tokens in a 20-turn conversation. `/mem_palace reset` clears it on demand.
|
|
63
|
+
> **v0.2.15 — May 9, 2026** — Banner image hosted locally so PyPI renders it correctly.
|
|
64
|
+
> **v0.2.14 — May 9, 2026** — Multi-user Telegram bridge: `telegram_chat_ids: "123,456,,"` supported. Replies route to the user who sent each message.
|
|
65
|
+
> **v0.2.13 — May 8, 2026** — Internal robustness fixes for Ollama streaming.
|
|
66
|
+
> Type `/news` to see the full changelog.
|
|
60
67
|
|
|
61
68
|
---
|
|
62
69
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"""Composio plugin helpers for Falcon."""
|
|
2
|
-
from .session_manager import get_client, get_or_create_session, list_accounts
|
|
3
|
-
from .tool_generator import generate_tool_py
|
|
4
|
-
|
|
5
|
-
__all__ = ["get_client", "get_or_create_session", "list_accounts", "generate_tool_py"]
|
|
1
|
+
"""Composio plugin helpers for Falcon."""
|
|
2
|
+
from .session_manager import get_client, get_or_create_session, list_accounts
|
|
3
|
+
from .tool_generator import generate_tool_py
|
|
4
|
+
|
|
5
|
+
__all__ = ["get_client", "get_or_create_session", "list_accounts", "generate_tool_py"]
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
"""Session manager for Composio integration."""
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Any, Dict, List, Optional
|
|
6
|
-
|
|
7
|
-
_composio_client = None
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _load_api_key() -> str:
|
|
11
|
-
"""Load Composio API key from Dulus config (with Falcon fallback) or env."""
|
|
12
|
-
api_key = os.environ.get("COMPOSIO_API_KEY", "")
|
|
13
|
-
if not api_key:
|
|
14
|
-
for cfg_path in (Path.home() / ".dulus" / "config.json",
|
|
15
|
-
Path.home() / ".falcon" / "config.json"):
|
|
16
|
-
if cfg_path.exists():
|
|
17
|
-
try:
|
|
18
|
-
with open(cfg_path, "r", encoding="utf-8") as f:
|
|
19
|
-
config = json.load(f)
|
|
20
|
-
api_key = config.get("composio_api_key", "")
|
|
21
|
-
if api_key:
|
|
22
|
-
break
|
|
23
|
-
except Exception:
|
|
24
|
-
pass
|
|
25
|
-
return api_key
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def get_client():
|
|
29
|
-
"""Get or create Composio client."""
|
|
30
|
-
global _composio_client
|
|
31
|
-
if _composio_client is not None:
|
|
32
|
-
return _composio_client
|
|
33
|
-
|
|
34
|
-
api_key = _load_api_key()
|
|
35
|
-
if not api_key:
|
|
36
|
-
raise RuntimeError("COMPOSIO_API_KEY not found. Set it in ~/.falcon/config.json or env.")
|
|
37
|
-
|
|
38
|
-
os.environ["COMPOSIO_API_KEY"] = api_key
|
|
39
|
-
|
|
40
|
-
from composio import Composio
|
|
41
|
-
_composio_client = Composio()
|
|
42
|
-
return _composio_client
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def get_or_create_session(user_id: str, toolkits: List[str], connected_accounts: Optional[Dict[str, str]] = None):
|
|
46
|
-
"""Create a Composio session with given toolkits."""
|
|
47
|
-
client = get_client()
|
|
48
|
-
kwargs = {
|
|
49
|
-
"user_id": user_id,
|
|
50
|
-
"toolkits": toolkits,
|
|
51
|
-
"manage_connections": {"wait_for_connections": True},
|
|
52
|
-
}
|
|
53
|
-
if connected_accounts:
|
|
54
|
-
kwargs["connected_accounts"] = connected_accounts
|
|
55
|
-
return client.create(**kwargs)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def list_accounts() -> List[Dict[str, Any]]:
|
|
59
|
-
"""List all connected accounts."""
|
|
60
|
-
client = get_client()
|
|
61
|
-
accounts = client.connected_accounts.list()
|
|
62
|
-
result = []
|
|
63
|
-
for acc in accounts.items:
|
|
64
|
-
result.append({
|
|
65
|
-
"id": getattr(acc, "id", "N/A"),
|
|
66
|
-
"app": getattr(acc, "appName", getattr(acc, "app_name", "N/A")),
|
|
67
|
-
"status": getattr(acc, "status", "N/A"),
|
|
68
|
-
"toolkit": acc.dict().get("toolkit", {}).get("slug", "N/A") if hasattr(acc, "dict") else "N/A",
|
|
69
|
-
"auth_scheme": getattr(acc, "authScheme", "N/A"),
|
|
70
|
-
})
|
|
71
|
-
return result
|
|
1
|
+
"""Session manager for Composio integration."""
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
_composio_client = None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _load_api_key() -> str:
|
|
11
|
+
"""Load Composio API key from Dulus config (with Falcon fallback) or env."""
|
|
12
|
+
api_key = os.environ.get("COMPOSIO_API_KEY", "")
|
|
13
|
+
if not api_key:
|
|
14
|
+
for cfg_path in (Path.home() / ".dulus" / "config.json",
|
|
15
|
+
Path.home() / ".falcon" / "config.json"):
|
|
16
|
+
if cfg_path.exists():
|
|
17
|
+
try:
|
|
18
|
+
with open(cfg_path, "r", encoding="utf-8") as f:
|
|
19
|
+
config = json.load(f)
|
|
20
|
+
api_key = config.get("composio_api_key", "")
|
|
21
|
+
if api_key:
|
|
22
|
+
break
|
|
23
|
+
except Exception:
|
|
24
|
+
pass
|
|
25
|
+
return api_key
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_client():
|
|
29
|
+
"""Get or create Composio client."""
|
|
30
|
+
global _composio_client
|
|
31
|
+
if _composio_client is not None:
|
|
32
|
+
return _composio_client
|
|
33
|
+
|
|
34
|
+
api_key = _load_api_key()
|
|
35
|
+
if not api_key:
|
|
36
|
+
raise RuntimeError("COMPOSIO_API_KEY not found. Set it in ~/.falcon/config.json or env.")
|
|
37
|
+
|
|
38
|
+
os.environ["COMPOSIO_API_KEY"] = api_key
|
|
39
|
+
|
|
40
|
+
from composio import Composio
|
|
41
|
+
_composio_client = Composio()
|
|
42
|
+
return _composio_client
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_or_create_session(user_id: str, toolkits: List[str], connected_accounts: Optional[Dict[str, str]] = None):
|
|
46
|
+
"""Create a Composio session with given toolkits."""
|
|
47
|
+
client = get_client()
|
|
48
|
+
kwargs = {
|
|
49
|
+
"user_id": user_id,
|
|
50
|
+
"toolkits": toolkits,
|
|
51
|
+
"manage_connections": {"wait_for_connections": True},
|
|
52
|
+
}
|
|
53
|
+
if connected_accounts:
|
|
54
|
+
kwargs["connected_accounts"] = connected_accounts
|
|
55
|
+
return client.create(**kwargs)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def list_accounts() -> List[Dict[str, Any]]:
|
|
59
|
+
"""List all connected accounts."""
|
|
60
|
+
client = get_client()
|
|
61
|
+
accounts = client.connected_accounts.list()
|
|
62
|
+
result = []
|
|
63
|
+
for acc in accounts.items:
|
|
64
|
+
result.append({
|
|
65
|
+
"id": getattr(acc, "id", "N/A"),
|
|
66
|
+
"app": getattr(acc, "appName", getattr(acc, "app_name", "N/A")),
|
|
67
|
+
"status": getattr(acc, "status", "N/A"),
|
|
68
|
+
"toolkit": acc.dict().get("toolkit", {}).get("slug", "N/A") if hasattr(acc, "dict") else "N/A",
|
|
69
|
+
"auth_scheme": getattr(acc, "authScheme", "N/A"),
|
|
70
|
+
})
|
|
71
|
+
return result
|
|
@@ -1,156 +1,156 @@
|
|
|
1
|
-
"""Tool generator - creates native Falcon .py files from Composio tool schemas."""
|
|
2
|
-
import json
|
|
3
|
-
import re
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Any, Dict
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def _slug_to_func_name(slug: str) -> str:
|
|
9
|
-
"""Convert a Composio tool slug to a valid Python function name."""
|
|
10
|
-
return re.sub(r"[^a-zA-Z0-9_]", "_", slug.lower()).strip("_")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _build_param_signature(params: Dict[str, Any]) -> str:
|
|
14
|
-
"""Build Python function parameter signature from JSON schema."""
|
|
15
|
-
if not params:
|
|
16
|
-
return ""
|
|
17
|
-
parts = []
|
|
18
|
-
for name, spec in params.items():
|
|
19
|
-
ptype = spec.get("type", "Any")
|
|
20
|
-
default = spec.get("default")
|
|
21
|
-
desc = spec.get("description", "")
|
|
22
|
-
py_type = {"string": "str", "integer": "int", "number": "float", "boolean": "bool", "array": "list", "object": "dict"}.get(ptype, "Any")
|
|
23
|
-
if default is not None:
|
|
24
|
-
parts.append(f"{name}: {py_type} = {repr(default)}")
|
|
25
|
-
else:
|
|
26
|
-
parts.append(f"{name}: {py_type}")
|
|
27
|
-
return ", ".join(parts)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def generate_tool_py(
|
|
31
|
-
tool_slug: str,
|
|
32
|
-
schema: Dict[str, Any],
|
|
33
|
-
output_dir: Path,
|
|
34
|
-
session_id: Optional[str] = None,
|
|
35
|
-
user_id: Optional[str] = None,
|
|
36
|
-
) -> Path:
|
|
37
|
-
"""Generate a standalone Falcon tool .py file for a Composio tool."""
|
|
38
|
-
func_name = _slug_to_func_name(tool_slug)
|
|
39
|
-
file_name = f"{func_name}.py"
|
|
40
|
-
output_path = output_dir / file_name
|
|
41
|
-
|
|
42
|
-
description = schema.get("description", f"Execute {tool_slug} via Composio")
|
|
43
|
-
params = schema.get("parameters", schema.get("input_schema", {})).get("properties", {})
|
|
44
|
-
required = schema.get("parameters", schema.get("input_schema", {})).get("required", [])
|
|
45
|
-
|
|
46
|
-
param_sig = _build_param_signature(params)
|
|
47
|
-
param_unpack = ", ".join([f'{name}=params.get("{name}")' for name in params.keys()])
|
|
48
|
-
|
|
49
|
-
code = f'''"""Auto-generated Falcon tool for Composio: {tool_slug}
|
|
50
|
-
Generated by Falcon Composio Plugin
|
|
51
|
-
"""
|
|
52
|
-
import sys
|
|
53
|
-
import json
|
|
54
|
-
from pathlib import Path
|
|
55
|
-
|
|
56
|
-
# Ensure composio is available
|
|
57
|
-
sys.path.insert(0, str(Path.home() / ".falcon" / "plugins" / "composio"))
|
|
58
|
-
from composio_plugin.session_manager import get_or_create_session
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def {func_name}(params: dict, config: dict) -> str:
|
|
62
|
-
"""
|
|
63
|
-
{description.replace(chr(10), chr(10) + " ")}
|
|
64
|
-
"""
|
|
65
|
-
session = get_or_create_session(
|
|
66
|
-
user_id={repr(user_id or "kevrojo_falcon")},
|
|
67
|
-
toolkits=["gmail"], # TODO: infer from tool slug
|
|
68
|
-
)
|
|
69
|
-
result = session.execute(
|
|
70
|
-
tool_slug={repr(tool_slug)},
|
|
71
|
-
arguments= {{{param_unpack}}}
|
|
72
|
-
)
|
|
73
|
-
data = result.data if hasattr(result, "data") else result
|
|
74
|
-
return json.dumps(data, indent=2, default=str)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
# Standalone test
|
|
78
|
-
if __name__ == "__main__":
|
|
79
|
-
test_params = {{
|
|
80
|
-
# Fill with required params
|
|
81
|
-
}}
|
|
82
|
-
print({func_name}(test_params, {{}}))
|
|
83
|
-
'''
|
|
84
|
-
output_path.write_text(code, encoding="utf-8")
|
|
85
|
-
return output_path
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def generate_plugin_tool_py(
|
|
89
|
-
tool_defs: list,
|
|
90
|
-
output_path: Path,
|
|
91
|
-
) -> Path:
|
|
92
|
-
"""Generate a plugin_tool.py exporting multiple ToolDefs."""
|
|
93
|
-
header = '''"""Auto-generated Composio plugin_tool.py
|
|
94
|
-
Generated by Falcon Composio Plugin
|
|
95
|
-
"""
|
|
96
|
-
import sys
|
|
97
|
-
import json
|
|
98
|
-
from pathlib import Path
|
|
99
|
-
|
|
100
|
-
PLUGIN_DIR = Path(__file__).parent.absolute()
|
|
101
|
-
if str(PLUGIN_DIR) not in sys.path:
|
|
102
|
-
sys.path.insert(0, str(PLUGIN_DIR))
|
|
103
|
-
|
|
104
|
-
from tool_registry import ToolDef
|
|
105
|
-
from composio_plugin.session_manager import get_or_create_session, get_client
|
|
106
|
-
|
|
107
|
-
'''
|
|
108
|
-
|
|
109
|
-
functions = []
|
|
110
|
-
tooldefs = []
|
|
111
|
-
|
|
112
|
-
for td in tool_defs:
|
|
113
|
-
slug = td["slug"]
|
|
114
|
-
func_name = _slug_to_func_name(slug)
|
|
115
|
-
desc = td.get("description", f"Execute {slug}")
|
|
116
|
-
params = td.get("schema", {}).get("properties", {})
|
|
117
|
-
required = td.get("schema", {}).get("required", [])
|
|
118
|
-
|
|
119
|
-
param_sig = _build_param_signature(params)
|
|
120
|
-
param_unpack = ", ".join([f'{name}=params.get("{name}")' for name in params.keys()])
|
|
121
|
-
|
|
122
|
-
func = f'''
|
|
123
|
-
def {func_name}(params: dict, config: dict) -> str:
|
|
124
|
-
"""{desc.replace(chr(10), " ")}"""
|
|
125
|
-
session = get_or_create_session(user_id="kevrojo_falcon", toolkits=["gmail"])
|
|
126
|
-
result = session.execute(tool_slug={repr(slug)}, arguments={{{param_unpack}}})
|
|
127
|
-
data = result.data if hasattr(result, "data") else result
|
|
128
|
-
return json.dumps(data, indent=2, default=str)
|
|
129
|
-
'''
|
|
130
|
-
functions.append(func)
|
|
131
|
-
|
|
132
|
-
schema = {
|
|
133
|
-
"name": slug,
|
|
134
|
-
"description": desc,
|
|
135
|
-
"input_schema": {
|
|
136
|
-
"type": "object",
|
|
137
|
-
"properties": params,
|
|
138
|
-
"required": required
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
tooldefs.append(f''' ToolDef(
|
|
142
|
-
name={repr(slug)},
|
|
143
|
-
schema={schema},
|
|
144
|
-
func={func_name}
|
|
145
|
-
)''')
|
|
146
|
-
|
|
147
|
-
footer = f'''
|
|
148
|
-
|
|
149
|
-
TOOL_DEFS = [
|
|
150
|
-
{",".join(tooldefs)}
|
|
151
|
-
]
|
|
152
|
-
TOOL_SCHEMAS = [t.schema for t in TOOL_DEFS]
|
|
153
|
-
'''
|
|
154
|
-
|
|
155
|
-
output_path.write_text(header + "".join(functions) + footer, encoding="utf-8")
|
|
156
|
-
return output_path
|
|
1
|
+
"""Tool generator - creates native Falcon .py files from Composio tool schemas."""
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _slug_to_func_name(slug: str) -> str:
|
|
9
|
+
"""Convert a Composio tool slug to a valid Python function name."""
|
|
10
|
+
return re.sub(r"[^a-zA-Z0-9_]", "_", slug.lower()).strip("_")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _build_param_signature(params: Dict[str, Any]) -> str:
|
|
14
|
+
"""Build Python function parameter signature from JSON schema."""
|
|
15
|
+
if not params:
|
|
16
|
+
return ""
|
|
17
|
+
parts = []
|
|
18
|
+
for name, spec in params.items():
|
|
19
|
+
ptype = spec.get("type", "Any")
|
|
20
|
+
default = spec.get("default")
|
|
21
|
+
desc = spec.get("description", "")
|
|
22
|
+
py_type = {"string": "str", "integer": "int", "number": "float", "boolean": "bool", "array": "list", "object": "dict"}.get(ptype, "Any")
|
|
23
|
+
if default is not None:
|
|
24
|
+
parts.append(f"{name}: {py_type} = {repr(default)}")
|
|
25
|
+
else:
|
|
26
|
+
parts.append(f"{name}: {py_type}")
|
|
27
|
+
return ", ".join(parts)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def generate_tool_py(
|
|
31
|
+
tool_slug: str,
|
|
32
|
+
schema: Dict[str, Any],
|
|
33
|
+
output_dir: Path,
|
|
34
|
+
session_id: Optional[str] = None,
|
|
35
|
+
user_id: Optional[str] = None,
|
|
36
|
+
) -> Path:
|
|
37
|
+
"""Generate a standalone Falcon tool .py file for a Composio tool."""
|
|
38
|
+
func_name = _slug_to_func_name(tool_slug)
|
|
39
|
+
file_name = f"{func_name}.py"
|
|
40
|
+
output_path = output_dir / file_name
|
|
41
|
+
|
|
42
|
+
description = schema.get("description", f"Execute {tool_slug} via Composio")
|
|
43
|
+
params = schema.get("parameters", schema.get("input_schema", {})).get("properties", {})
|
|
44
|
+
required = schema.get("parameters", schema.get("input_schema", {})).get("required", [])
|
|
45
|
+
|
|
46
|
+
param_sig = _build_param_signature(params)
|
|
47
|
+
param_unpack = ", ".join([f'{name}=params.get("{name}")' for name in params.keys()])
|
|
48
|
+
|
|
49
|
+
code = f'''"""Auto-generated Falcon tool for Composio: {tool_slug}
|
|
50
|
+
Generated by Falcon Composio Plugin
|
|
51
|
+
"""
|
|
52
|
+
import sys
|
|
53
|
+
import json
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
|
|
56
|
+
# Ensure composio is available
|
|
57
|
+
sys.path.insert(0, str(Path.home() / ".falcon" / "plugins" / "composio"))
|
|
58
|
+
from composio_plugin.session_manager import get_or_create_session
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def {func_name}(params: dict, config: dict) -> str:
|
|
62
|
+
"""
|
|
63
|
+
{description.replace(chr(10), chr(10) + " ")}
|
|
64
|
+
"""
|
|
65
|
+
session = get_or_create_session(
|
|
66
|
+
user_id={repr(user_id or "kevrojo_falcon")},
|
|
67
|
+
toolkits=["gmail"], # TODO: infer from tool slug
|
|
68
|
+
)
|
|
69
|
+
result = session.execute(
|
|
70
|
+
tool_slug={repr(tool_slug)},
|
|
71
|
+
arguments= {{{param_unpack}}}
|
|
72
|
+
)
|
|
73
|
+
data = result.data if hasattr(result, "data") else result
|
|
74
|
+
return json.dumps(data, indent=2, default=str)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# Standalone test
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
test_params = {{
|
|
80
|
+
# Fill with required params
|
|
81
|
+
}}
|
|
82
|
+
print({func_name}(test_params, {{}}))
|
|
83
|
+
'''
|
|
84
|
+
output_path.write_text(code, encoding="utf-8")
|
|
85
|
+
return output_path
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def generate_plugin_tool_py(
|
|
89
|
+
tool_defs: list,
|
|
90
|
+
output_path: Path,
|
|
91
|
+
) -> Path:
|
|
92
|
+
"""Generate a plugin_tool.py exporting multiple ToolDefs."""
|
|
93
|
+
header = '''"""Auto-generated Composio plugin_tool.py
|
|
94
|
+
Generated by Falcon Composio Plugin
|
|
95
|
+
"""
|
|
96
|
+
import sys
|
|
97
|
+
import json
|
|
98
|
+
from pathlib import Path
|
|
99
|
+
|
|
100
|
+
PLUGIN_DIR = Path(__file__).parent.absolute()
|
|
101
|
+
if str(PLUGIN_DIR) not in sys.path:
|
|
102
|
+
sys.path.insert(0, str(PLUGIN_DIR))
|
|
103
|
+
|
|
104
|
+
from tool_registry import ToolDef
|
|
105
|
+
from composio_plugin.session_manager import get_or_create_session, get_client
|
|
106
|
+
|
|
107
|
+
'''
|
|
108
|
+
|
|
109
|
+
functions = []
|
|
110
|
+
tooldefs = []
|
|
111
|
+
|
|
112
|
+
for td in tool_defs:
|
|
113
|
+
slug = td["slug"]
|
|
114
|
+
func_name = _slug_to_func_name(slug)
|
|
115
|
+
desc = td.get("description", f"Execute {slug}")
|
|
116
|
+
params = td.get("schema", {}).get("properties", {})
|
|
117
|
+
required = td.get("schema", {}).get("required", [])
|
|
118
|
+
|
|
119
|
+
param_sig = _build_param_signature(params)
|
|
120
|
+
param_unpack = ", ".join([f'{name}=params.get("{name}")' for name in params.keys()])
|
|
121
|
+
|
|
122
|
+
func = f'''
|
|
123
|
+
def {func_name}(params: dict, config: dict) -> str:
|
|
124
|
+
"""{desc.replace(chr(10), " ")}"""
|
|
125
|
+
session = get_or_create_session(user_id="kevrojo_falcon", toolkits=["gmail"])
|
|
126
|
+
result = session.execute(tool_slug={repr(slug)}, arguments={{{param_unpack}}})
|
|
127
|
+
data = result.data if hasattr(result, "data") else result
|
|
128
|
+
return json.dumps(data, indent=2, default=str)
|
|
129
|
+
'''
|
|
130
|
+
functions.append(func)
|
|
131
|
+
|
|
132
|
+
schema = {
|
|
133
|
+
"name": slug,
|
|
134
|
+
"description": desc,
|
|
135
|
+
"input_schema": {
|
|
136
|
+
"type": "object",
|
|
137
|
+
"properties": params,
|
|
138
|
+
"required": required
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
tooldefs.append(f''' ToolDef(
|
|
142
|
+
name={repr(slug)},
|
|
143
|
+
schema={schema},
|
|
144
|
+
func={func_name}
|
|
145
|
+
)''')
|
|
146
|
+
|
|
147
|
+
footer = f'''
|
|
148
|
+
|
|
149
|
+
TOOL_DEFS = [
|
|
150
|
+
{",".join(tooldefs)}
|
|
151
|
+
]
|
|
152
|
+
TOOL_SCHEMAS = [t.schema for t in TOOL_DEFS]
|
|
153
|
+
'''
|
|
154
|
+
|
|
155
|
+
output_path.write_text(header + "".join(functions) + footer, encoding="utf-8")
|
|
156
|
+
return output_path
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "composio",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Composio integration for Dulus. Connect to 1000+ apps via Composio Tool Router (no MCP needed).",
|
|
5
|
-
"author": "KevRojo",
|
|
6
|
-
"tags": ["automation", "integration", "composio", "mcp"],
|
|
7
|
-
"tools": ["plugin_tool"],
|
|
8
|
-
"skills": [],
|
|
9
|
-
"dependencies": ["composio"],
|
|
10
|
-
"homepage": "https://composio.dev"
|
|
11
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "composio",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Composio integration for Dulus. Connect to 1000+ apps via Composio Tool Router (no MCP needed).",
|
|
5
|
+
"author": "KevRojo",
|
|
6
|
+
"tags": ["automation", "integration", "composio", "mcp"],
|
|
7
|
+
"tools": ["plugin_tool"],
|
|
8
|
+
"skills": [],
|
|
9
|
+
"dependencies": ["composio"],
|
|
10
|
+
"homepage": "https://composio.dev"
|
|
11
|
+
}
|