scios-cli 0.1.9__tar.gz → 0.3.2__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.
- {scios_cli-0.1.9 → scios_cli-0.3.2}/PKG-INFO +43 -9
- {scios_cli-0.1.9 → scios_cli-0.3.2}/README.md +34 -0
- {scios_cli-0.1.9 → scios_cli-0.3.2}/pyproject.toml +12 -9
- scios_cli-0.3.2/scios_cli/__init__.py +2 -0
- scios_cli-0.3.2/scios_cli/ag/__init__.py +17 -0
- scios_cli-0.3.2/scios_cli/ag/api_client.py +276 -0
- scios_cli-0.3.2/scios_cli/ag/create_flow.py +213 -0
- scios_cli-0.3.2/scios_cli/ag/dashboards.py +116 -0
- scios_cli-0.3.2/scios_cli/ag/local_runner.py +406 -0
- scios_cli-0.3.2/scios_cli/ag/manifest.py +197 -0
- scios_cli-0.3.2/scios_cli/ag/plugin.py +283 -0
- scios_cli-0.3.2/scios_cli/ag/scaffold.py +350 -0
- scios_cli-0.3.2/scios_cli/ag/sync_core.py +314 -0
- scios_cli-0.3.2/scios_cli/app.py +243 -0
- scios_cli-0.3.2/scios_cli/auth_store.py +104 -0
- scios_cli-0.3.2/scios_cli/commands/__init__.py +1 -0
- scios_cli-0.3.2/scios_cli/commands/ag.py +1312 -0
- scios_cli-0.3.2/scios_cli/commands/agent.py +137 -0
- scios_cli-0.3.2/scios_cli/commands/bench.py +796 -0
- scios_cli-0.3.2/scios_cli/commands/chat.py +108 -0
- scios_cli-0.3.2/scios_cli/commands/db.py +482 -0
- scios_cli-0.3.2/scios_cli/commands/deploy.py +189 -0
- scios_cli-0.3.2/scios_cli/commands/export.py +149 -0
- scios_cli-0.3.2/scios_cli/commands/houdini.py +574 -0
- scios_cli-0.3.2/scios_cli/commands/langfuse.py +133 -0
- scios_cli-0.3.2/scios_cli/commands/login.py +407 -0
- scios_cli-0.3.2/scios_cli/commands/logs.py +91 -0
- scios_cli-0.3.2/scios_cli/commands/profile.py +263 -0
- scios_cli-0.3.2/scios_cli/commands/run.py +748 -0
- scios_cli-0.3.2/scios_cli/commands/setup.py +427 -0
- scios_cli-0.3.2/scios_cli/commands/sync.py +66 -0
- scios_cli-0.3.2/scios_cli/commands/train.py +289 -0
- scios_cli-0.3.2/scios_cli/commands/trust.py +104 -0
- scios_cli-0.3.2/scios_cli/commands/video.py +100 -0
- scios_cli-0.3.2/scios_cli/commands/workspace.py +197 -0
- scios_cli-0.3.2/scios_cli/config.py +351 -0
- scios_cli-0.3.2/scios_cli/houdini.py +222 -0
- scios_cli-0.3.2/scios_cli/houdini_recorder.py +261 -0
- {scios_cli-0.1.9 → scios_cli-0.3.2}/scios_cli/main.py +176 -10
- scios_cli-0.3.2/scios_cli/mcp/__init__.py +32 -0
- scios_cli-0.3.2/scios_cli/mcp/server_core.py +138 -0
- scios_cli-0.3.2/scios_cli/mcp/server_maxwell.py +115 -0
- {scios_cli-0.1.9 → scios_cli-0.3.2}/scios_cli.egg-info/PKG-INFO +43 -9
- scios_cli-0.3.2/scios_cli.egg-info/SOURCES.txt +52 -0
- {scios_cli-0.1.9 → scios_cli-0.3.2}/scios_cli.egg-info/entry_points.txt +2 -0
- scios_cli-0.3.2/scios_cli.egg-info/requires.txt +11 -0
- scios_cli-0.3.2/setup.py +26 -0
- scios_cli-0.3.2/tests/test_ag.py +226 -0
- scios_cli-0.1.9/scios_cli/__init__.py +0 -2
- scios_cli-0.1.9/scios_cli.egg-info/SOURCES.txt +0 -14
- scios_cli-0.1.9/scios_cli.egg-info/requires.txt +0 -2
- scios_cli-0.1.9/setup.py +0 -19
- {scios_cli-0.1.9 → scios_cli-0.3.2}/LICENSE +0 -0
- {scios_cli-0.1.9 → scios_cli-0.3.2}/scios_cli/__main__.py +0 -0
- {scios_cli-0.1.9 → scios_cli-0.3.2}/scios_cli/install.py +0 -0
- {scios_cli-0.1.9 → scios_cli-0.3.2}/scios_cli.egg-info/dependency_links.txt +0 -0
- {scios_cli-0.1.9 → scios_cli-0.3.2}/scios_cli.egg-info/top_level.txt +0 -0
- {scios_cli-0.1.9 → scios_cli-0.3.2}/setup.cfg +0 -0
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scios-cli
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Scios
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: Scios unified CLI — dev environment, deployment, database, logs, and more
|
|
5
5
|
Author: Scios Team
|
|
6
6
|
License: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/google-octo/science-studio
|
|
8
8
|
Project-URL: Repository, https://github.com/google-octo/science-studio
|
|
9
|
-
Project-URL: Documentation, https://github.com/google-octo/science-studio/tree/main/packages/cli
|
|
10
9
|
Keywords: scios,cli,agent,sync,science,workflow
|
|
11
10
|
Classifier: Development Status :: 4 - Beta
|
|
12
11
|
Classifier: Intended Audience :: Developers
|
|
13
12
|
Classifier: Intended Audience :: Science/Research
|
|
14
13
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
14
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
21
15
|
Classifier: Topic :: Scientific/Engineering
|
|
22
|
-
Classifier: Topic :: Software Development :: Libraries
|
|
23
16
|
Classifier: Operating System :: OS Independent
|
|
24
17
|
Requires-Python: >=3.9
|
|
25
18
|
Description-Content-Type: text/markdown
|
|
26
19
|
License-File: LICENSE
|
|
20
|
+
Requires-Dist: typer[all]>=0.12.0
|
|
21
|
+
Requires-Dist: rich>=13.0.0
|
|
22
|
+
Requires-Dist: questionary>=2.0.0
|
|
27
23
|
Requires-Dist: requests>=2.31.0
|
|
28
24
|
Requires-Dist: watchdog>=3.0.0
|
|
25
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
26
|
+
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
27
|
+
Requires-Dist: tomli-w>=1.0.0
|
|
28
|
+
Requires-Dist: mcp>=1.0.0
|
|
29
29
|
Dynamic: license-file
|
|
30
30
|
|
|
31
31
|
# Scios CLI 🚀
|
|
@@ -105,6 +105,40 @@ scios-sync 1234-5678-your-idea-id --endpoint "https://api.scios.ai/v1" --token "
|
|
|
105
105
|
```
|
|
106
106
|
Passing `--local` directly triggers the Cloud APIs to launch that pipeline on the heavy GPUs immediately without ever having to touch a web button.
|
|
107
107
|
|
|
108
|
+
### 4. Record the Houdini app while it runs (`--record-video`)
|
|
109
|
+
When your workflow drives a **local** application — for example controlling Houdini through the Houdini MCP — you can have `scios-sync` screen-record the desktop locally for the entire execution, producing a single `.mp4` of the run.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
scios-sync <idea_id> \
|
|
113
|
+
--endpoint "https://api.scios.ai/v1" \
|
|
114
|
+
--token "eyJh..." \
|
|
115
|
+
--local \
|
|
116
|
+
--record-video
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Behaviour:
|
|
120
|
+
1. Starts an `ffmpeg` screen capture **before** posting the execution (no missed frames).
|
|
121
|
+
2. Triggers the workflow as usual and prints the `execution_id`.
|
|
122
|
+
3. Polls `GET /workflows/executions/{execution_id}` every `--poll-interval` seconds (default 2s), printing each status transition.
|
|
123
|
+
4. When the execution reaches a terminal status (`completed`, `failed`, `cancelled`, `error`, `stopped`), sends `q` to ffmpeg so it finalises the mp4 cleanly.
|
|
124
|
+
5. Renames the file to `recordings/execution_<execution_id>.mp4` next to your current directory (or writes to `--video-out` if you passed it).
|
|
125
|
+
|
|
126
|
+
Requirements:
|
|
127
|
+
- `ffmpeg` on `PATH` (`brew install ffmpeg` / `apt install ffmpeg`).
|
|
128
|
+
- macOS: grant Terminal (or your shell) **Screen Recording** permission under *System Settings → Privacy & Security*.
|
|
129
|
+
- Linux: an X11 (or XWayland) display reachable at `:0.0`.
|
|
130
|
+
|
|
131
|
+
Useful flags:
|
|
132
|
+
| Flag | Default | Notes |
|
|
133
|
+
|------|---------|-------|
|
|
134
|
+
| `--video-fps` | `24` | Frame rate of the capture. |
|
|
135
|
+
| `--video-out PATH` | auto | Override the mp4 output path. |
|
|
136
|
+
| `--ffmpeg-input "..."` | platform default | Full ffmpeg input args, e.g. `"-f avfoundation -framerate 24 -i 2:none"` to record a specific display. List devices on macOS with `ffmpeg -f avfoundation -list_devices true -i ""`. |
|
|
137
|
+
| `--poll-interval` | `2.0` | Seconds between execution-status polls. |
|
|
138
|
+
| `--max-wait` | `3600` | Hard cap on the recording length in seconds. |
|
|
139
|
+
|
|
140
|
+
Make sure the Houdini app + the `houdini_addon.py` MCP listener (port 9876) are already running before you fire the CLI — the Scios agents will reach into Houdini over MCP while the recorder is rolling.
|
|
141
|
+
|
|
108
142
|
---
|
|
109
143
|
|
|
110
144
|
### Manual Fallback Commands
|
|
@@ -75,6 +75,40 @@ scios-sync 1234-5678-your-idea-id --endpoint "https://api.scios.ai/v1" --token "
|
|
|
75
75
|
```
|
|
76
76
|
Passing `--local` directly triggers the Cloud APIs to launch that pipeline on the heavy GPUs immediately without ever having to touch a web button.
|
|
77
77
|
|
|
78
|
+
### 4. Record the Houdini app while it runs (`--record-video`)
|
|
79
|
+
When your workflow drives a **local** application — for example controlling Houdini through the Houdini MCP — you can have `scios-sync` screen-record the desktop locally for the entire execution, producing a single `.mp4` of the run.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
scios-sync <idea_id> \
|
|
83
|
+
--endpoint "https://api.scios.ai/v1" \
|
|
84
|
+
--token "eyJh..." \
|
|
85
|
+
--local \
|
|
86
|
+
--record-video
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Behaviour:
|
|
90
|
+
1. Starts an `ffmpeg` screen capture **before** posting the execution (no missed frames).
|
|
91
|
+
2. Triggers the workflow as usual and prints the `execution_id`.
|
|
92
|
+
3. Polls `GET /workflows/executions/{execution_id}` every `--poll-interval` seconds (default 2s), printing each status transition.
|
|
93
|
+
4. When the execution reaches a terminal status (`completed`, `failed`, `cancelled`, `error`, `stopped`), sends `q` to ffmpeg so it finalises the mp4 cleanly.
|
|
94
|
+
5. Renames the file to `recordings/execution_<execution_id>.mp4` next to your current directory (or writes to `--video-out` if you passed it).
|
|
95
|
+
|
|
96
|
+
Requirements:
|
|
97
|
+
- `ffmpeg` on `PATH` (`brew install ffmpeg` / `apt install ffmpeg`).
|
|
98
|
+
- macOS: grant Terminal (or your shell) **Screen Recording** permission under *System Settings → Privacy & Security*.
|
|
99
|
+
- Linux: an X11 (or XWayland) display reachable at `:0.0`.
|
|
100
|
+
|
|
101
|
+
Useful flags:
|
|
102
|
+
| Flag | Default | Notes |
|
|
103
|
+
|------|---------|-------|
|
|
104
|
+
| `--video-fps` | `24` | Frame rate of the capture. |
|
|
105
|
+
| `--video-out PATH` | auto | Override the mp4 output path. |
|
|
106
|
+
| `--ffmpeg-input "..."` | platform default | Full ffmpeg input args, e.g. `"-f avfoundation -framerate 24 -i 2:none"` to record a specific display. List devices on macOS with `ffmpeg -f avfoundation -list_devices true -i ""`. |
|
|
107
|
+
| `--poll-interval` | `2.0` | Seconds between execution-status polls. |
|
|
108
|
+
| `--max-wait` | `3600` | Hard cap on the recording length in seconds. |
|
|
109
|
+
|
|
110
|
+
Make sure the Houdini app + the `houdini_addon.py` MCP listener (port 9876) are already running before you fire the CLI — the Scios agents will reach into Houdini over MCP while the recorder is rolling.
|
|
111
|
+
|
|
78
112
|
---
|
|
79
113
|
|
|
80
114
|
### Manual Fallback Commands
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scios-cli"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Scios
|
|
7
|
+
version = "0.3.2"
|
|
8
|
+
description = "Scios unified CLI — dev environment, deployment, database, logs, and more"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
11
11
|
requires-python = ">=3.9"
|
|
@@ -19,28 +19,31 @@ classifiers = [
|
|
|
19
19
|
"Intended Audience :: Science/Research",
|
|
20
20
|
"License :: OSI Approved :: Apache Software License",
|
|
21
21
|
"Programming Language :: Python :: 3",
|
|
22
|
-
"Programming Language :: Python :: 3.9",
|
|
23
|
-
"Programming Language :: Python :: 3.10",
|
|
24
|
-
"Programming Language :: Python :: 3.11",
|
|
25
|
-
"Programming Language :: Python :: 3.12",
|
|
26
|
-
"Programming Language :: Python :: 3.13",
|
|
27
22
|
"Topic :: Scientific/Engineering",
|
|
28
|
-
"Topic :: Software Development :: Libraries",
|
|
29
23
|
"Operating System :: OS Independent",
|
|
30
24
|
]
|
|
31
25
|
dependencies = [
|
|
26
|
+
"typer[all]>=0.12.0",
|
|
27
|
+
"rich>=13.0.0",
|
|
28
|
+
"questionary>=2.0.0",
|
|
32
29
|
"requests>=2.31.0",
|
|
33
30
|
"watchdog>=3.0.0",
|
|
31
|
+
"python-dotenv>=1.0.0",
|
|
32
|
+
"tomli>=2.0.0; python_version < '3.11'",
|
|
33
|
+
"tomli-w>=1.0.0",
|
|
34
|
+
"mcp>=1.0.0",
|
|
34
35
|
]
|
|
35
36
|
|
|
36
37
|
[project.urls]
|
|
37
38
|
Homepage = "https://github.com/google-octo/science-studio"
|
|
38
39
|
Repository = "https://github.com/google-octo/science-studio"
|
|
39
|
-
Documentation = "https://github.com/google-octo/science-studio/tree/main/packages/cli"
|
|
40
40
|
|
|
41
41
|
[project.scripts]
|
|
42
|
+
scios = "scios_cli.app:main"
|
|
43
|
+
# Backward-compat aliases
|
|
42
44
|
scios-sync = "scios_cli.main:cli_entry"
|
|
43
45
|
scios-init = "scios_cli.install:cli_install_protocol"
|
|
46
|
+
scios-houdini = "scios_cli.houdini:cli_houdini"
|
|
44
47
|
|
|
45
48
|
[tool.setuptools.packages.find]
|
|
46
49
|
include = ["scios_cli*"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""scios ag — Antigravity integration package.
|
|
2
|
+
|
|
3
|
+
Projects a Scios idea into a local Antigravity project folder, keeps agent code
|
|
4
|
+
in two-way sync with the backend, and runs workflows locally or in the cloud.
|
|
5
|
+
|
|
6
|
+
All state lives in the backend DB and is reached over the REST API (never the DB
|
|
7
|
+
directly) so the experience works from a plain `pip install scios-cli`.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"api_client",
|
|
13
|
+
"manifest",
|
|
14
|
+
"sync_core",
|
|
15
|
+
"scaffold",
|
|
16
|
+
"local_runner",
|
|
17
|
+
]
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""REST client for the Scios backend used by `scios ag`.
|
|
2
|
+
|
|
3
|
+
Single source of truth for every backend interaction the Antigravity integration
|
|
4
|
+
needs. Uses `requests` (already a CLI dependency) — no direct DB access, so this
|
|
5
|
+
works from a plain `pip install scios-cli`.
|
|
6
|
+
|
|
7
|
+
Auth: a JWT Bearer token (same as `scios sync`). Resolution order:
|
|
8
|
+
1. explicit `token=` argument
|
|
9
|
+
2. `SCIOS_API_TOKEN` environment variable
|
|
10
|
+
3. `[auth].token` in the workspace `scios.toml`
|
|
11
|
+
|
|
12
|
+
All endpoints were verified in Phase 0 (see BUILD_PROGRESS.md).
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import time
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
import requests
|
|
21
|
+
|
|
22
|
+
DEFAULT_ENDPOINT = "https://api.scios.dev/api/v1"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SciosAPIError(RuntimeError):
|
|
26
|
+
"""Raised when the backend returns a non-2xx response."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, status_code: int, message: str, url: str):
|
|
29
|
+
self.status_code = status_code
|
|
30
|
+
self.url = url
|
|
31
|
+
super().__init__(f"[{status_code}] {message} ({url})")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SciosClient:
|
|
35
|
+
"""Thin, typed wrapper over the Scios REST API.
|
|
36
|
+
|
|
37
|
+
`endpoint` must include the `/api/v1` suffix (matching `scios sync`), e.g.
|
|
38
|
+
``https://api.scios.dev/api/v1``.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, endpoint: str = DEFAULT_ENDPOINT, token: str | None = None, timeout: float = 60.0):
|
|
42
|
+
self.endpoint = endpoint.rstrip("/")
|
|
43
|
+
self.token = token or os.environ.get("SCIOS_API_TOKEN")
|
|
44
|
+
self.timeout = timeout
|
|
45
|
+
self._session = requests.Session()
|
|
46
|
+
|
|
47
|
+
# ── low-level ──────────────────────────────────────────────────────────
|
|
48
|
+
@property
|
|
49
|
+
def _headers(self) -> dict[str, str]:
|
|
50
|
+
if not self.token:
|
|
51
|
+
raise SciosAPIError(401, "No auth token. Set SCIOS_API_TOKEN or scios.toml [auth].token", self.endpoint)
|
|
52
|
+
return {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"}
|
|
53
|
+
|
|
54
|
+
def _request(self, method: str, path: str, *, json: Any = None, params: dict | None = None,
|
|
55
|
+
timeout: float | None = None) -> Any:
|
|
56
|
+
url = f"{self.endpoint}{path}"
|
|
57
|
+
resp = self._session.request(
|
|
58
|
+
method, url, json=json, params=params, headers=self._headers,
|
|
59
|
+
timeout=timeout or self.timeout,
|
|
60
|
+
)
|
|
61
|
+
if not resp.ok:
|
|
62
|
+
detail = resp.text
|
|
63
|
+
try:
|
|
64
|
+
body = resp.json()
|
|
65
|
+
detail = body.get("detail") or body.get("message") or detail
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
raise SciosAPIError(resp.status_code, str(detail), url)
|
|
69
|
+
if resp.content and "application/json" in resp.headers.get("content-type", ""):
|
|
70
|
+
return resp.json()
|
|
71
|
+
return resp.content
|
|
72
|
+
|
|
73
|
+
def get(self, path: str, **kw) -> Any:
|
|
74
|
+
return self._request("GET", path, **kw)
|
|
75
|
+
|
|
76
|
+
def post(self, path: str, **kw) -> Any:
|
|
77
|
+
return self._request("POST", path, **kw)
|
|
78
|
+
|
|
79
|
+
def patch(self, path: str, **kw) -> Any:
|
|
80
|
+
return self._request("PATCH", path, **kw)
|
|
81
|
+
|
|
82
|
+
# ── mount / sync (Phase 1) ─────────────────────────────────────────────
|
|
83
|
+
def get_sync_manifest(self, idea_id: str) -> dict:
|
|
84
|
+
"""GET /ideas/{idea_id}/sync-manifest — the one-shot mount payload.
|
|
85
|
+
|
|
86
|
+
Returns {idea_id, active_workflow_id, workflow_definition, agents[],
|
|
87
|
+
execution, datasets[]}.
|
|
88
|
+
"""
|
|
89
|
+
return self.get(f"/ideas/{idea_id}/sync-manifest")
|
|
90
|
+
|
|
91
|
+
def update_agent_code(self, agent_id: str, code_content: str) -> dict:
|
|
92
|
+
"""PATCH /agents/{agent_id}/code — the single write path for agent code."""
|
|
93
|
+
return self.patch(f"/agents/{agent_id}/code", json={"code_content": code_content})
|
|
94
|
+
|
|
95
|
+
def get_idea(self, idea_id: str) -> dict:
|
|
96
|
+
"""GET /ideas/{idea_id} — includes build_execute_progress/status, scios_score."""
|
|
97
|
+
return self.get(f"/ideas/{idea_id}")
|
|
98
|
+
|
|
99
|
+
# ── creation (Phase 2) ─────────────────────────────────────────────────
|
|
100
|
+
def create_workspace(self, name: str, description: str, category_id: str | None = None,
|
|
101
|
+
metadata: dict | None = None) -> dict:
|
|
102
|
+
body: dict[str, Any] = {"name": name, "description": description}
|
|
103
|
+
if category_id:
|
|
104
|
+
body["categoryId"] = category_id
|
|
105
|
+
if metadata:
|
|
106
|
+
body["metadata"] = metadata
|
|
107
|
+
return self.post("/workspaces", json=body)
|
|
108
|
+
|
|
109
|
+
def create_research_goal(self, workspace_id: str, title: str, description: str,
|
|
110
|
+
details: str | None = None, target: str | None = None,
|
|
111
|
+
parameters: dict | None = None, original_prompt: str | None = None) -> dict:
|
|
112
|
+
body: dict[str, Any] = {"title": title, "description": description, "workspaceId": workspace_id}
|
|
113
|
+
if details:
|
|
114
|
+
body["details"] = details
|
|
115
|
+
if target:
|
|
116
|
+
body["target"] = target
|
|
117
|
+
if parameters:
|
|
118
|
+
body["parameters"] = parameters
|
|
119
|
+
if original_prompt:
|
|
120
|
+
body["originalPrompt"] = original_prompt
|
|
121
|
+
return self.post(f"/workspaces/{workspace_id}/goals", json=body)
|
|
122
|
+
|
|
123
|
+
def start_research_goal(self, workspace_id: str, goal_id: str, configuration: dict | None = None) -> dict:
|
|
124
|
+
"""POST /workspaces/{ws}/goals/{goal}/start — generates ideas (Co-Scientist)."""
|
|
125
|
+
body = {"goalId": goal_id, "configuration": configuration or {}}
|
|
126
|
+
return self.post(f"/workspaces/{workspace_id}/goals/{goal_id}/start", json=body, timeout=300.0)
|
|
127
|
+
|
|
128
|
+
def list_session_ideas(self, session_id: str, limit: int = 20) -> list[dict]:
|
|
129
|
+
"""GET /ideas/?session_id=... — ideas generated for a session.
|
|
130
|
+
|
|
131
|
+
Co-Scientist generates ideas asynchronously into the session; this is the
|
|
132
|
+
robust way to discover them (the goal/session detail endpoint is flaky).
|
|
133
|
+
"""
|
|
134
|
+
resp = self.get("/ideas/", params={"session_id": session_id, "limit": limit})
|
|
135
|
+
if isinstance(resp, dict):
|
|
136
|
+
return resp.get("ideas") or resp.get("items") or []
|
|
137
|
+
return resp if isinstance(resp, list) else []
|
|
138
|
+
|
|
139
|
+
# ── build-execute (Phase 2) ────────────────────────────────────────────
|
|
140
|
+
def trigger_build_execute(self, idea_id: str, stage_config: dict | None = None) -> dict:
|
|
141
|
+
"""POST /ideas/build-execute — start Architect→Vibing→Execute→Optimize."""
|
|
142
|
+
body: dict[str, Any] = {"idea_ids": [idea_id]}
|
|
143
|
+
if stage_config is not None:
|
|
144
|
+
body["stage_config"] = stage_config
|
|
145
|
+
return self.post("/ideas/build-execute", json=body)
|
|
146
|
+
|
|
147
|
+
def retry_build_execute(self, idea_id: str, stage_config: dict | None = None,
|
|
148
|
+
force_retry: bool = False, skip_optimization: bool = False) -> dict:
|
|
149
|
+
body: dict[str, Any] = {"forceRetry": force_retry, "skipOptimization": skip_optimization}
|
|
150
|
+
if stage_config is not None:
|
|
151
|
+
body["stage_config"] = stage_config
|
|
152
|
+
return self.post(f"/ideas/{idea_id}/build-execute/retry", json=body)
|
|
153
|
+
|
|
154
|
+
def cancel_build_execute(self, idea_id: str) -> dict:
|
|
155
|
+
return self.post(f"/ideas/{idea_id}/build-execute/cancel")
|
|
156
|
+
|
|
157
|
+
def get_build_execute_progress(self, idea_id: str) -> dict:
|
|
158
|
+
"""Return {status, scios_score, progress} derived from GET /ideas/{id}."""
|
|
159
|
+
idea = self.get_idea(idea_id)
|
|
160
|
+
return {
|
|
161
|
+
"status": idea.get("buildExecuteStatus") or idea.get("build_execute_status"),
|
|
162
|
+
"scios_score": idea.get("sciosScore") or idea.get("scios_score"),
|
|
163
|
+
"progress": idea.get("buildExecuteProgress") or idea.get("build_execute_progress"),
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
def wait_for_build_execute(self, idea_id: str, poll_interval: float = 5.0,
|
|
167
|
+
timeout: float = 3600.0, on_progress=None) -> dict:
|
|
168
|
+
"""Poll build_execute_progress until a terminal status or timeout.
|
|
169
|
+
|
|
170
|
+
Terminal statuses: 'completed', 'failed'. Returns the final progress dict.
|
|
171
|
+
"""
|
|
172
|
+
deadline = time.time() + timeout
|
|
173
|
+
last_status = None
|
|
174
|
+
while time.time() < deadline:
|
|
175
|
+
prog = self.get_build_execute_progress(idea_id)
|
|
176
|
+
status = prog.get("status")
|
|
177
|
+
if on_progress and status != last_status:
|
|
178
|
+
on_progress(prog)
|
|
179
|
+
last_status = status
|
|
180
|
+
elif on_progress:
|
|
181
|
+
on_progress(prog)
|
|
182
|
+
if status in ("completed", "failed"):
|
|
183
|
+
return prog
|
|
184
|
+
time.sleep(poll_interval)
|
|
185
|
+
raise SciosAPIError(408, f"build-execute did not finish within {timeout}s", f"/ideas/{idea_id}")
|
|
186
|
+
|
|
187
|
+
# ── execution (Phase 3 cloud) ──────────────────────────────────────────
|
|
188
|
+
def execute_workflow(self, workflow_id: str, execution_mode: str = "worker",
|
|
189
|
+
inputs: dict | None = None, session_id: str | None = None,
|
|
190
|
+
execution_quantity: int = 1) -> dict:
|
|
191
|
+
"""POST /workflows/{id}/execute. `--mode cloud` ⇒ execution_mode='worker'."""
|
|
192
|
+
body: dict[str, Any] = {
|
|
193
|
+
"execution_mode": execution_mode,
|
|
194
|
+
"execution_quantity": execution_quantity,
|
|
195
|
+
}
|
|
196
|
+
if inputs is not None:
|
|
197
|
+
body["inputs"] = inputs
|
|
198
|
+
if session_id:
|
|
199
|
+
body["session_id"] = session_id
|
|
200
|
+
return self.post(f"/workflows/{workflow_id}/execute", json=body)
|
|
201
|
+
|
|
202
|
+
def list_executions(self, workflow_id: str) -> list[dict]:
|
|
203
|
+
return self.get(f"/workflows/{workflow_id}/executions")
|
|
204
|
+
|
|
205
|
+
def get_execution(self, execution_id: str) -> dict:
|
|
206
|
+
"""GET /workflows/executions/{id} — execution detail incl. results_dashboard
|
|
207
|
+
and result_analysis. (Route is under the /workflows prefix; a bare
|
|
208
|
+
/executions/{id} returns 404.)"""
|
|
209
|
+
return self.get(f"/workflows/executions/{execution_id}")
|
|
210
|
+
|
|
211
|
+
def get_node_data(self, execution_id: str, node_id: str, data_key: str,
|
|
212
|
+
data_category: str = "input") -> Any:
|
|
213
|
+
"""GET /datasets/execution/{exec}/nodes/{node}/data — resolve one offloaded
|
|
214
|
+
node input/output value by its data_key (used to hydrate Zero-Copy start
|
|
215
|
+
inputs for a local run)."""
|
|
216
|
+
return self.get(
|
|
217
|
+
f"/datasets/execution/{execution_id}/nodes/{node_id}/data",
|
|
218
|
+
params={"data_category": data_category, "data_key": data_key},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def get_execution_trace(self, execution_id: str, slim: bool = False) -> Any:
|
|
222
|
+
"""GET /workflows/executions/{id}/trace — hydrates _stored_in_datasets offloads."""
|
|
223
|
+
return self.get(f"/workflows/executions/{execution_id}/trace", params={"slim": str(slim).lower()})
|
|
224
|
+
|
|
225
|
+
def get_execution_logs(self, execution_id: str) -> Any:
|
|
226
|
+
return self.get(f"/workflows/executions/{execution_id}/logs")
|
|
227
|
+
|
|
228
|
+
# ── datasets (Phase 5) ─────────────────────────────────────────────────
|
|
229
|
+
def get_dataset_content(self, dataset_id: str) -> bytes:
|
|
230
|
+
return self.get(f"/datasets/{dataset_id}/content")
|
|
231
|
+
|
|
232
|
+
# ── maxwell assistant (Phase 4) ────────────────────────────────────────
|
|
233
|
+
def maxwell_create_session(self, workspace_id: str | None = None) -> dict:
|
|
234
|
+
"""POST /maxwell/sessions — start a Maxwell assistant session."""
|
|
235
|
+
body: dict[str, Any] = {}
|
|
236
|
+
if workspace_id:
|
|
237
|
+
body["workspace_id"] = workspace_id
|
|
238
|
+
return self.post("/maxwell/sessions", json=body)
|
|
239
|
+
|
|
240
|
+
def suggest_all_fields(self, session_id: str, research_context: str,
|
|
241
|
+
title: str | None = None, description: str | None = None) -> dict:
|
|
242
|
+
"""POST /maxwell/sessions/{id}/suggest-all-fields — the same AI auto-amend
|
|
243
|
+
the /dashboard/plan page uses. Turns a raw goal prompt into structured
|
|
244
|
+
{title, description, targetMetrics, constraints, methodology, category, ...}."""
|
|
245
|
+
body: dict[str, Any] = {"researchContext": research_context}
|
|
246
|
+
if title:
|
|
247
|
+
body["title"] = title
|
|
248
|
+
if description:
|
|
249
|
+
body["description"] = description
|
|
250
|
+
return self.post(f"/maxwell/sessions/{session_id}/suggest-all-fields", json=body, timeout=180.0)
|
|
251
|
+
|
|
252
|
+
def maxwell_query(self, session_id: str, query: str) -> dict:
|
|
253
|
+
"""POST /maxwell/sessions/{id}/query — send a natural-language query."""
|
|
254
|
+
return self.post(f"/maxwell/sessions/{session_id}/query", json={"query": query}, timeout=300.0)
|
|
255
|
+
|
|
256
|
+
def maxwell_history(self, session_id: str) -> dict:
|
|
257
|
+
return self.get(f"/maxwell/sessions/{session_id}/history")
|
|
258
|
+
|
|
259
|
+
# ── mcp tool call (Phase 3/4) ──────────────────────────────────────────
|
|
260
|
+
def call_tool(self, tool_name: str, arguments: dict, server_name: str = "science",
|
|
261
|
+
poll_interval: float = 2.0) -> Any:
|
|
262
|
+
"""POST /mcp/tools/call, transparently awaiting a promoted background job."""
|
|
263
|
+
data = self.post("/mcp/tools/call", json={
|
|
264
|
+
"server_name": server_name, "tool_name": tool_name, "arguments": arguments,
|
|
265
|
+
}, timeout=900.0)
|
|
266
|
+
if isinstance(data, dict) and "job_id" in data:
|
|
267
|
+
job_id = data["job_id"]
|
|
268
|
+
while True:
|
|
269
|
+
time.sleep(poll_interval)
|
|
270
|
+
job = self.get(f"/mcp/tools/status/{job_id}")
|
|
271
|
+
status = job.get("status")
|
|
272
|
+
if status == "completed":
|
|
273
|
+
return job.get("result")
|
|
274
|
+
if status == "failed":
|
|
275
|
+
raise SciosAPIError(500, f"tool '{tool_name}' failed: {job.get('error')}", "/mcp/tools/call")
|
|
276
|
+
return data
|