flowly-code 1.0.0__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 (86) hide show
  1. flowly_code/__init__.py +30 -0
  2. flowly_code/__main__.py +8 -0
  3. flowly_code/activity/__init__.py +1 -0
  4. flowly_code/activity/bus.py +91 -0
  5. flowly_code/activity/events.py +40 -0
  6. flowly_code/agent/__init__.py +8 -0
  7. flowly_code/agent/context.py +485 -0
  8. flowly_code/agent/loop.py +1349 -0
  9. flowly_code/agent/memory.py +109 -0
  10. flowly_code/agent/skills.py +259 -0
  11. flowly_code/agent/subagent.py +249 -0
  12. flowly_code/agent/tools/__init__.py +6 -0
  13. flowly_code/agent/tools/base.py +55 -0
  14. flowly_code/agent/tools/delegate.py +194 -0
  15. flowly_code/agent/tools/dispatch.py +840 -0
  16. flowly_code/agent/tools/docker.py +609 -0
  17. flowly_code/agent/tools/filesystem.py +280 -0
  18. flowly_code/agent/tools/mcp.py +85 -0
  19. flowly_code/agent/tools/message.py +235 -0
  20. flowly_code/agent/tools/registry.py +257 -0
  21. flowly_code/agent/tools/screenshot.py +444 -0
  22. flowly_code/agent/tools/shell.py +166 -0
  23. flowly_code/agent/tools/spawn.py +65 -0
  24. flowly_code/agent/tools/system.py +917 -0
  25. flowly_code/agent/tools/trello.py +420 -0
  26. flowly_code/agent/tools/web.py +139 -0
  27. flowly_code/agent/tools/x.py +399 -0
  28. flowly_code/bus/__init__.py +6 -0
  29. flowly_code/bus/events.py +37 -0
  30. flowly_code/bus/queue.py +81 -0
  31. flowly_code/channels/__init__.py +6 -0
  32. flowly_code/channels/base.py +121 -0
  33. flowly_code/channels/manager.py +135 -0
  34. flowly_code/channels/telegram.py +1132 -0
  35. flowly_code/cli/__init__.py +1 -0
  36. flowly_code/cli/commands.py +1831 -0
  37. flowly_code/cli/setup.py +1356 -0
  38. flowly_code/compaction/__init__.py +39 -0
  39. flowly_code/compaction/estimator.py +88 -0
  40. flowly_code/compaction/pruning.py +223 -0
  41. flowly_code/compaction/service.py +297 -0
  42. flowly_code/compaction/summarizer.py +384 -0
  43. flowly_code/compaction/types.py +71 -0
  44. flowly_code/config/__init__.py +6 -0
  45. flowly_code/config/loader.py +102 -0
  46. flowly_code/config/schema.py +324 -0
  47. flowly_code/exec/__init__.py +39 -0
  48. flowly_code/exec/approvals.py +288 -0
  49. flowly_code/exec/executor.py +184 -0
  50. flowly_code/exec/safety.py +247 -0
  51. flowly_code/exec/types.py +88 -0
  52. flowly_code/gateway/__init__.py +5 -0
  53. flowly_code/gateway/server.py +103 -0
  54. flowly_code/heartbeat/__init__.py +5 -0
  55. flowly_code/heartbeat/service.py +130 -0
  56. flowly_code/multiagent/README.md +248 -0
  57. flowly_code/multiagent/__init__.py +1 -0
  58. flowly_code/multiagent/invoke.py +210 -0
  59. flowly_code/multiagent/orchestrator.py +156 -0
  60. flowly_code/multiagent/router.py +156 -0
  61. flowly_code/multiagent/setup.py +171 -0
  62. flowly_code/pairing/__init__.py +21 -0
  63. flowly_code/pairing/store.py +343 -0
  64. flowly_code/providers/__init__.py +6 -0
  65. flowly_code/providers/base.py +69 -0
  66. flowly_code/providers/litellm_provider.py +178 -0
  67. flowly_code/providers/transcription.py +64 -0
  68. flowly_code/session/__init__.py +5 -0
  69. flowly_code/session/manager.py +249 -0
  70. flowly_code/skills/README.md +24 -0
  71. flowly_code/skills/compact/SKILL.md +27 -0
  72. flowly_code/skills/github/SKILL.md +48 -0
  73. flowly_code/skills/skill-creator/SKILL.md +371 -0
  74. flowly_code/skills/summarize/SKILL.md +67 -0
  75. flowly_code/skills/tmux/SKILL.md +121 -0
  76. flowly_code/skills/tmux/scripts/find-sessions.sh +112 -0
  77. flowly_code/skills/tmux/scripts/wait-for-text.sh +83 -0
  78. flowly_code/skills/weather/SKILL.md +49 -0
  79. flowly_code/utils/__init__.py +5 -0
  80. flowly_code/utils/helpers.py +91 -0
  81. flowly_code-1.0.0.dist-info/METADATA +724 -0
  82. flowly_code-1.0.0.dist-info/RECORD +86 -0
  83. flowly_code-1.0.0.dist-info/WHEEL +4 -0
  84. flowly_code-1.0.0.dist-info/entry_points.txt +2 -0
  85. flowly_code-1.0.0.dist-info/licenses/LICENSE +191 -0
  86. flowly_code-1.0.0.dist-info/licenses/NOTICE +74 -0
@@ -0,0 +1,166 @@
1
+ """Secure shell execution tool with approval system."""
2
+
3
+ import asyncio
4
+ from typing import Any, Callable, Awaitable
5
+
6
+ from loguru import logger
7
+
8
+ from flowly_code.agent.tools.base import Tool
9
+ from flowly_code.exec import (
10
+ ExecConfig,
11
+ ExecRequest,
12
+ ExecResult,
13
+ ExecApprovalStore,
14
+ ExecApprovalDecision,
15
+ analyze_command,
16
+ execute_command,
17
+ )
18
+ from flowly_code.exec.types import PendingApproval
19
+
20
+
21
+ class SecureExecTool(Tool):
22
+ """
23
+ Secure shell command execution tool.
24
+
25
+ Features:
26
+ - Security modes: deny, allowlist, full
27
+ - Ask modes: off, on-miss, always
28
+ - Command analysis for dangerous patterns
29
+ - Safe bins (jq, grep, etc.) always allowed
30
+ - Allowlist with glob pattern matching
31
+ - Approval system via callback (Telegram)
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ config: ExecConfig,
37
+ approval_callback: Callable[[PendingApproval], Awaitable[ExecApprovalDecision | None]] | None = None,
38
+ working_dir: str | None = None,
39
+ ):
40
+ self.config = config
41
+ self.working_dir = working_dir
42
+ self._store = ExecApprovalStore()
43
+ self._store.load()
44
+
45
+ if approval_callback:
46
+ self._store.set_approval_callback(approval_callback)
47
+
48
+ @property
49
+ def name(self) -> str:
50
+ return "exec"
51
+
52
+ @property
53
+ def description(self) -> str:
54
+ if not self.config.enabled:
55
+ return "Execute shell commands. CURRENTLY DISABLED."
56
+
57
+ security = self._store.config.security
58
+ ask = self._store.config.ask
59
+
60
+ desc = "Execute a shell command and return its output.\n\n"
61
+ desc += f"Security: {security}, Ask: {ask}\n"
62
+
63
+ if security == "deny":
64
+ desc += "WARNING: Command execution is currently denied."
65
+ elif security == "allowlist":
66
+ desc += "Only allowlisted commands and safe bins (grep, jq, etc.) are permitted.\n"
67
+ desc += "Other commands require user approval."
68
+ elif security == "full":
69
+ desc += "Full access mode - all commands allowed."
70
+
71
+ return desc
72
+
73
+ @property
74
+ def parameters(self) -> dict[str, Any]:
75
+ return {
76
+ "type": "object",
77
+ "properties": {
78
+ "command": {
79
+ "type": "string",
80
+ "description": "The shell command to execute"
81
+ },
82
+ "working_dir": {
83
+ "type": "string",
84
+ "description": "Optional working directory for the command"
85
+ },
86
+ "timeout": {
87
+ "type": "integer",
88
+ "description": f"Optional timeout in seconds (default: {self.config.timeout_seconds})"
89
+ },
90
+ "background": {
91
+ "type": "boolean",
92
+ "description": "Run in background and return immediately with PID. Use for long-running processes (servers, tunnels, watchers). Stop later with: exec(command='kill <PID>')"
93
+ }
94
+ },
95
+ "required": ["command"]
96
+ }
97
+
98
+ def set_approval_callback(
99
+ self,
100
+ callback: Callable[[PendingApproval], Awaitable[ExecApprovalDecision | None]]
101
+ ) -> None:
102
+ """Set the approval callback (for Telegram integration)."""
103
+ self._store.set_approval_callback(callback)
104
+
105
+ async def execute(
106
+ self,
107
+ command: str,
108
+ working_dir: str | None = None,
109
+ timeout: int | None = None,
110
+ background: bool = False,
111
+ session_key: str | None = None,
112
+ **kwargs: Any
113
+ ) -> str:
114
+ """Execute a command with full security checks."""
115
+
116
+ # Create request
117
+ request = ExecRequest(
118
+ command=command.strip(),
119
+ cwd=working_dir or self.working_dir,
120
+ timeout=timeout,
121
+ session_key=session_key,
122
+ background=background,
123
+ )
124
+
125
+ # Analyze command first (for logging)
126
+ analysis = analyze_command(command)
127
+ logger.info(f"Exec request: {command[:50]}... (safe_bin={analysis.is_safe_bin}, resolved={analysis.resolved_path})")
128
+
129
+ # Execute with security checks
130
+ result = await execute_command(request, self.config, self._store)
131
+
132
+ # Format result
133
+ if result.denied:
134
+ return f"❌ Command denied: {result.error}"
135
+
136
+ if result.pid is not None and background:
137
+ return f"✅ Background process started (PID: {result.pid}). To stop: exec(command='kill {result.pid}')"
138
+
139
+ if result.timed_out:
140
+ return f"⏰ Command timed out after {timeout or self.config.timeout_seconds} seconds"
141
+
142
+ if result.error:
143
+ return f"❌ Error: {result.error}"
144
+
145
+ # Build output
146
+ output_parts = []
147
+
148
+ if result.stdout:
149
+ output_parts.append(result.stdout)
150
+
151
+ if result.stderr:
152
+ output_parts.append(f"STDERR:\n{result.stderr}")
153
+
154
+ if result.exit_code is not None and result.exit_code != 0:
155
+ output_parts.append(f"\nExit code: {result.exit_code}")
156
+
157
+ return "\n".join(output_parts) if output_parts else "(no output)"
158
+
159
+ @property
160
+ def store(self) -> ExecApprovalStore:
161
+ """Get the approval store for external management."""
162
+ return self._store
163
+
164
+
165
+ # Keep backward compatibility alias
166
+ ExecTool = SecureExecTool
@@ -0,0 +1,65 @@
1
+ """Spawn tool for creating background subagents."""
2
+
3
+ from typing import Any, TYPE_CHECKING
4
+
5
+ from flowly_code.agent.tools.base import Tool
6
+
7
+ if TYPE_CHECKING:
8
+ from flowly_code.agent.subagent import SubagentManager
9
+
10
+
11
+ class SpawnTool(Tool):
12
+ """
13
+ Tool to spawn a subagent for background task execution.
14
+
15
+ The subagent runs asynchronously and announces its result back
16
+ to the main agent when complete.
17
+ """
18
+
19
+ def __init__(self, manager: "SubagentManager"):
20
+ self._manager = manager
21
+ self._origin_channel = "cli"
22
+ self._origin_chat_id = "direct"
23
+
24
+ def set_context(self, channel: str, chat_id: str) -> None:
25
+ """Set the origin context for subagent announcements."""
26
+ self._origin_channel = channel
27
+ self._origin_chat_id = chat_id
28
+
29
+ @property
30
+ def name(self) -> str:
31
+ return "spawn"
32
+
33
+ @property
34
+ def description(self) -> str:
35
+ return (
36
+ "Spawn a subagent to handle a task in the background. "
37
+ "Use this for complex or time-consuming tasks that can run independently. "
38
+ "The subagent will complete the task and report back when done."
39
+ )
40
+
41
+ @property
42
+ def parameters(self) -> dict[str, Any]:
43
+ return {
44
+ "type": "object",
45
+ "properties": {
46
+ "task": {
47
+ "type": "string",
48
+ "description": "The task for the subagent to complete",
49
+ },
50
+ "label": {
51
+ "type": "string",
52
+ "description": "Optional short label for the task (for display)",
53
+ },
54
+ },
55
+ "required": ["task"],
56
+ }
57
+
58
+ async def execute(self, task: str, label: str | None = None, **kwargs: Any) -> str:
59
+ """Spawn a subagent to execute the given task."""
60
+ return await self._manager.spawn(
61
+ task=task,
62
+ label=label,
63
+ origin_channel=self._origin_channel,
64
+ origin_chat_id=self._origin_chat_id,
65
+ )