swiftgate-cli 1.0.4__tar.gz → 1.0.6__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.
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/PKG-INFO +1 -1
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/pyproject.toml +1 -1
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/src/swiftgate_cli/agent_bridge.py +29 -25
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/src/swiftgate_cli/api_client.py +20 -0
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/src/swiftgate_cli/main.py +29 -15
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/.gitignore +0 -0
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/LICENSE +0 -0
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/README.md +0 -0
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/src/swiftgate_cli/__init__.py +0 -0
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/src/swiftgate_cli/__main__.py +0 -0
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/src/swiftgate_cli/config.py +0 -0
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/src/swiftgate_cli/sse_listener.py +0 -0
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/tests/test_agent_bridge.py +0 -0
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/tests/test_config.py +0 -0
- {swiftgate_cli-1.0.4 → swiftgate_cli-1.0.6}/tests/test_sse_listener.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swiftgate-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
4
4
|
Summary: SwiftGate CLI — autonomous bidirectional agent bridge for SwiftGate OS
|
|
5
5
|
Project-URL: Homepage, https://swiftgate.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/thetaroot/swiftgate
|
|
@@ -146,7 +146,7 @@ class AgentBridge:
|
|
|
146
146
|
if not self._running or self._master_fd is None:
|
|
147
147
|
return
|
|
148
148
|
|
|
149
|
-
msg = f"\nSwiftGate: You have {count} pending tasks. Call swiftgate_agent_get_task\n"
|
|
149
|
+
msg = f"\r\nSwiftGate: You have {count} pending tasks. Call swiftgate_agent_get_task\r\n"
|
|
150
150
|
try:
|
|
151
151
|
os.write(self._master_fd, msg.encode("utf-8"))
|
|
152
152
|
logger.debug("Wake injected for %s (%d pending)", self._slug, count)
|
|
@@ -159,6 +159,7 @@ class AgentBridge:
|
|
|
159
159
|
"""Forward stdin → agent pty, agent pty → stdout.
|
|
160
160
|
|
|
161
161
|
Runs in the asyncio event loop. Blocks until agent exits.
|
|
162
|
+
Monitors terminal size every 250ms and propagates to the pty.
|
|
162
163
|
"""
|
|
163
164
|
if not self._running or self._master_fd is None:
|
|
164
165
|
return
|
|
@@ -179,23 +180,26 @@ class AgentBridge:
|
|
|
179
180
|
except (termios.error, OSError):
|
|
180
181
|
pass
|
|
181
182
|
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
183
|
+
# Periodic terminal resize poller — robust across ttyd/container envs
|
|
184
|
+
_last_cols = 0
|
|
185
|
+
_last_rows = 0
|
|
186
|
+
|
|
187
|
+
async def _resize_poller():
|
|
188
|
+
nonlocal _last_cols, _last_rows
|
|
189
|
+
while self._running:
|
|
190
|
+
await asyncio.sleep(0.25)
|
|
191
|
+
try:
|
|
192
|
+
term_size = os.get_terminal_size()
|
|
193
|
+
if (term_size.columns, term_size.lines) != (_last_cols, _last_rows):
|
|
194
|
+
winsize = struct.pack("HHHH", term_size.lines, term_size.columns, 0, 0)
|
|
195
|
+
fcntl.ioctl(master_fd, termios.TIOCSWINSZ, winsize)
|
|
196
|
+
os.environ["COLUMNS"] = str(term_size.columns)
|
|
197
|
+
os.environ["LINES"] = str(term_size.lines)
|
|
198
|
+
_last_cols, _last_rows = term_size.columns, term_size.lines
|
|
199
|
+
except OSError:
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
resize_task = asyncio.create_task(_resize_poller())
|
|
199
203
|
|
|
200
204
|
stop_event = asyncio.Event()
|
|
201
205
|
|
|
@@ -240,6 +244,13 @@ class AgentBridge:
|
|
|
240
244
|
except Exception:
|
|
241
245
|
pass
|
|
242
246
|
|
|
247
|
+
# Cleanup resize poller
|
|
248
|
+
resize_task.cancel()
|
|
249
|
+
try:
|
|
250
|
+
await resize_task
|
|
251
|
+
except (asyncio.CancelledError, Exception):
|
|
252
|
+
pass
|
|
253
|
+
|
|
243
254
|
# Restore terminal
|
|
244
255
|
if old_tc:
|
|
245
256
|
try:
|
|
@@ -247,13 +258,6 @@ class AgentBridge:
|
|
|
247
258
|
except (termios.error, OSError):
|
|
248
259
|
pass
|
|
249
260
|
|
|
250
|
-
# Restore SIGWINCH handler
|
|
251
|
-
if old_winch != signal.SIG_DFL:
|
|
252
|
-
try:
|
|
253
|
-
signal.signal(signal.SIGWINCH, old_winch)
|
|
254
|
-
except OSError:
|
|
255
|
-
pass
|
|
256
|
-
|
|
257
261
|
# ── Lifecycle ────────────────────────────────────────────────────────────
|
|
258
262
|
|
|
259
263
|
async def wait(self) -> int:
|
|
@@ -74,6 +74,26 @@ class APIClient:
|
|
|
74
74
|
except Exception:
|
|
75
75
|
return False
|
|
76
76
|
|
|
77
|
+
async def fetch_agent_token(self, workspace_slug: str) -> dict | None:
|
|
78
|
+
"""Fetch MCP token + agent config from SwiftGate.
|
|
79
|
+
|
|
80
|
+
Authenticated via account token. Returns dict with
|
|
81
|
+
mcp_token, agent_type, server_url or None on failure.
|
|
82
|
+
"""
|
|
83
|
+
url = self._cli_url("start-agent")
|
|
84
|
+
try:
|
|
85
|
+
async with aiohttp.ClientSession() as session:
|
|
86
|
+
async with session.post(
|
|
87
|
+
url, json={"workspace_slug": workspace_slug},
|
|
88
|
+
headers=self._headers(),
|
|
89
|
+
timeout=aiohttp.ClientTimeout(total=10),
|
|
90
|
+
) as resp:
|
|
91
|
+
if resp.status != 200:
|
|
92
|
+
return None
|
|
93
|
+
return await resp.json()
|
|
94
|
+
except Exception:
|
|
95
|
+
return None
|
|
96
|
+
|
|
77
97
|
def get_sse_url(self) -> str:
|
|
78
98
|
"""URL for the CLI SSE wake stream."""
|
|
79
99
|
return self._cli_url("listen")
|
|
@@ -39,7 +39,7 @@ logger = logging.getLogger("swiftgate")
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
@click.group()
|
|
42
|
-
@click.version_option(version="1.0.
|
|
42
|
+
@click.version_option(version="1.0.6", prog_name="swiftgate-cli")
|
|
43
43
|
def cli():
|
|
44
44
|
"""swiftgate-cli — Autonomous bidirectional agent bridge for SwiftGate OS.
|
|
45
45
|
|
|
@@ -84,16 +84,12 @@ def setup(token: str, server: str):
|
|
|
84
84
|
|
|
85
85
|
@cli.command()
|
|
86
86
|
@click.option("--agent", required=True, help="Agent workspace slug")
|
|
87
|
-
@click.option("--mcp-token", default="", help="MCP token
|
|
87
|
+
@click.option("--mcp-token", default="", help="MCP token (optional — auto-fetched if not provided)")
|
|
88
88
|
def start(agent: str, mcp_token: str):
|
|
89
89
|
"""Start an agent and bridge it to SwiftGate.
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
stdin/stdout is forwarded transparently.
|
|
94
|
-
|
|
95
|
-
The MCP token is required on first start. It is stored in
|
|
96
|
-
~/.swiftgate/config.json for subsequent starts.
|
|
91
|
+
MCP token is auto-fetched from SwiftGate using the account token.
|
|
92
|
+
No copy-paste needed — just run: swiftgate-cli start --agent my-agent
|
|
97
93
|
"""
|
|
98
94
|
asyncio.run(_start_async(agent, mcp_token))
|
|
99
95
|
|
|
@@ -103,18 +99,28 @@ async def _start_async(agent: str, mcp_token: str) -> None:
|
|
|
103
99
|
click.echo(click.style("Not set up. Run: swiftgate-cli setup --token sg_acc_...", fg="red"))
|
|
104
100
|
sys.exit(1)
|
|
105
101
|
|
|
102
|
+
client = APIClient()
|
|
103
|
+
|
|
106
104
|
token = mcp_token or _get_stored_token(agent)
|
|
105
|
+
agent_type = _get_stored_type(agent) or "opencode"
|
|
106
|
+
|
|
107
107
|
if not token:
|
|
108
|
-
click.echo(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
click.echo("Fetching agent config from SwiftGate...")
|
|
109
|
+
config = await client.fetch_agent_token(agent)
|
|
110
|
+
if config and config.get("mcp_token"):
|
|
111
|
+
token = config["mcp_token"]
|
|
112
|
+
agent_type = config.get("agent_type") or agent_type
|
|
113
|
+
_store_token(agent, token)
|
|
114
|
+
_store_type(agent, agent_type)
|
|
115
|
+
else:
|
|
116
|
+
click.echo(click.style(
|
|
117
|
+
"Could not fetch agent config. Provide MCP token manually:",
|
|
118
|
+
fg="yellow"))
|
|
119
|
+
click.echo(f"Usage: swiftgate-cli start --agent {agent} --mcp-token sg_ea_...")
|
|
120
|
+
sys.exit(1)
|
|
113
121
|
|
|
114
122
|
if mcp_token and mcp_token.startswith("sg_ea_"):
|
|
115
123
|
_store_token(agent, mcp_token)
|
|
116
|
-
|
|
117
|
-
agent_type = _get_stored_type(agent) or "opencode"
|
|
118
124
|
command = _agent_command(agent_type)
|
|
119
125
|
if not command:
|
|
120
126
|
click.echo(click.style(f"Unknown agent type: {agent_type}", fg="red"))
|
|
@@ -224,6 +230,14 @@ def _store_token(slug: str, token: str) -> None:
|
|
|
224
230
|
save(cfg)
|
|
225
231
|
|
|
226
232
|
|
|
233
|
+
def _store_type(slug: str, agent_type: str) -> None:
|
|
234
|
+
"""Save agent type to local config."""
|
|
235
|
+
from swiftgate_cli.config import load, save
|
|
236
|
+
cfg = load()
|
|
237
|
+
cfg.setdefault("agents", {}).setdefault(slug, {})["type"] = agent_type
|
|
238
|
+
save(cfg)
|
|
239
|
+
|
|
240
|
+
|
|
227
241
|
def _get_stored_type(slug: str) -> str:
|
|
228
242
|
"""Get saved agent type from local config."""
|
|
229
243
|
from swiftgate_cli.config import load
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|