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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swiftgate-cli
3
- Version: 1.0.4
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "swiftgate-cli"
7
- version = "1.0.4"
7
+ version = "1.0.6"
8
8
  description = "SwiftGate CLI — autonomous bidirectional agent bridge for SwiftGate OS"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -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
- # SIGWINCH handler: propagate terminal resize to pty
183
- old_winch = signal.SIG_DFL
184
-
185
- def _resize_pty(_sig: int, _frame) -> None:
186
- try:
187
- cols, rows = os.get_terminal_size()
188
- winsize = struct.pack("HHHH", rows, cols, 0, 0)
189
- fcntl.ioctl(master_fd, termios.TIOCSWINSZ, winsize)
190
- os.environ["COLUMNS"] = str(cols)
191
- os.environ["LINES"] = str(rows)
192
- except OSError:
193
- pass
194
-
195
- try:
196
- old_winch = signal.signal(signal.SIGWINCH, _resize_pty)
197
- except OSError:
198
- pass
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.4", prog_name="swiftgate-cli")
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 from agent registration (sg_ea_...)")
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
- Fetches agent config from SwiftGate, writes MCP configuration,
92
- spawns the agent process, and starts the SSE wake listener.
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(click.style(
109
- "MCP token required. Copy the full command from the agent creation screen.",
110
- fg="yellow"))
111
- click.echo(f"Usage: swiftgate-cli start --agent {agent} --mcp-token sg_ea_...")
112
- sys.exit(1)
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