Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ frontend/test-results/
test-results/
openorbit-state.zip
conversations/
.orbit/conversations/
.orbit/
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ pnpm install
pnpm --filter agent-improvement-console-ui run build
```

For the packaged local-app path, run `orbit run`.
For the packaged local-app path, run `orbit run [PATH]`. Supplying `PATH`
stores the control room's local data in `PATH/.orbit`; omit it to use the
default application-data directory.

On Windows, activate the environment with `.venv\\Scripts\\Activate.ps1` and
use `.venv\\Scripts\\python.exe`.
Expand Down
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ orbit run
The wheel already includes the bundled control-room UI, so Node.js and pnpm are
not required at runtime.

To keep one control room's operational data with a project or another chosen
directory, pass that directory to `run`. OpenOrbit creates and uses its
`.orbit` subdirectory:

```bash
orbit run . # Store data in the current directory's .orbit/
orbit run ./my-project # Store data in ./my-project/.orbit/
```

> PyPI publication is made possible with the support of insighta cloud Inc.

To use the latest development version, install directly from the main OpenOrbit
Expand Down Expand Up @@ -246,13 +255,21 @@ OpenOrbit, create assets, review runs, or use non-browser runners.

## Safety and local data

OpenOrbit is local-first. Operational state is stored outside the repository in platform AppData:
OpenOrbit is local-first. By default, operational state is stored outside the
repository in platform AppData:

- Windows: `%LOCALAPPDATA%\\Orbit`
- macOS: `~/Library/Application Support/Orbit`
- Linux: `${XDG_DATA_HOME:-~/.local/share}/orbit`

Set `ORBIT_APP_DATA` to use another location. Model profiles store the name of the environment variable that contains a secret, never the secret itself. Review workflow commands, approved workspace boundaries, and network exposure before connecting a production AI system.
Use `orbit run PATH` to keep the data in `PATH/.orbit`; this takes precedence
over a previously selected data location and `ORBIT_APP_DATA` for that run. Add
`.orbit/` to the target project's `.gitignore` when it is not meant to be
version-controlled. Set `ORBIT_APP_DATA` to use another location without a
command-line path. Model profiles store the name of the environment variable
that contains a secret, never the secret itself. Review workflow commands,
approved workspace boundaries, and network exposure before connecting a
production AI system.

## API and extensibility

Expand Down
23 changes: 17 additions & 6 deletions bin/orbit.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env node
import { existsSync } from 'node:fs'
import { existsSync, statSync } from 'node:fs'
import net from 'node:net'
import { platform } from 'node:os'
import { join } from 'node:path'
import { join, resolve } from 'node:path'
import { spawn, spawnSync } from 'node:child_process'

const root = new URL('..', import.meta.url).pathname
Expand Down Expand Up @@ -32,14 +32,21 @@ async function nextAvailablePort(startPort, host) {
}

if (process.argv.includes('--help') || process.argv.includes('-h')) {
console.log('Usage: orbit run')
console.log('Usage: orbit run [PATH]')
process.exit(0)
}
if (process.argv[2] !== 'run') {
console.log('Usage: orbit run')
if (process.argv[2] !== 'run' || process.argv.length > 4) {
console.log('Usage: orbit run [PATH]')
process.exit(1)
}

const dataPath = process.argv[3]
const dataRoot = dataPath ? resolve(dataPath) : undefined
if (dataRoot && existsSync(dataRoot) && !statSync(dataRoot).isDirectory()) {
throw new Error(`PATH must be a directory: ${dataRoot}`)
}
const appData = dataRoot ? join(dataRoot, '.orbit') : undefined

if (!existsSync(venvPython)) {
console.log('Creating Python virtual environment…')
run(python, [...pythonVersionArgs, '-m', 'venv', '.venv'])
Expand All @@ -66,7 +73,11 @@ if (port !== requestedPort) {
if (process.env.ORBIT_PUBLIC_URL) {
console.log(`Public URL: ${process.env.ORBIT_PUBLIC_URL.replace('{port}', String(port))}`)
}
const server = spawn(venvPython, ['-m', 'uvicorn', 'app.main:app', '--app-dir', 'backend', '--host', host, '--port', port], { cwd: root, stdio: 'inherit' })
const server = spawn(venvPython, ['-m', 'uvicorn', 'app.main:app', '--app-dir', 'backend', '--host', host, '--port', port], {
cwd: root,
stdio: 'inherit',
env: { ...process.env, ...(appData ? { ORBIT_APP_DATA: appData } : {}) },
})
process.on('SIGINT', () => server.kill('SIGINT'))
process.on('SIGTERM', () => server.kill('SIGTERM'))
server.on('exit', code => process.exit(code ?? 0))
4 changes: 3 additions & 1 deletion i18n/ko/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ pnpm install
pnpm --filter agent-improvement-console-ui run build
```

패키지형 로컬 앱 경로로 실행하려면 `orbit run`을 사용하세요.
패키지형 로컬 앱 경로로 실행하려면 `orbit run [PATH]`를 사용하세요.
`PATH`를 전달하면 컨트롤룸의 로컬 데이터는 `PATH/.orbit`에 저장되고,
생략하면 기본 애플리케이션 데이터 디렉터리를 사용합니다.

Windows에서는 `.venv\Scripts\Activate.ps1`로 환경을 활성화하고
`.venv\Scripts\python.exe`를 사용하세요.
Expand Down
11 changes: 9 additions & 2 deletions i18n/ko/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ orbit run

wheel에는 번들된 컨트롤룸 UI가 이미 포함되어 있으므로, 런타임에 Node.js와 pnpm이 필요하지 않습니다.

프로젝트나 원하는 디렉터리와 함께 하나의 컨트롤룸 운영 데이터를 보관하려면 `run`에 해당 디렉터리를 전달하세요. OpenOrbit는 그 안에 `.orbit` 하위 디렉터리를 만들어 사용합니다.

```bash
orbit run . # 현재 디렉터리의 .orbit/에 저장
orbit run ./my-project # ./my-project/.orbit/에 저장
```

> PyPI 배포는 insighta cloud Inc.의 지원으로 이루어집니다.

최신 개발 버전을 사용하려면 OpenOrbit 메인 저장소에서 직접 설치하세요. 이 소스 설치에는 Node.js 24+와 pnpm이 필요합니다.
Expand Down Expand Up @@ -221,13 +228,13 @@ OpenOrbit는 독립 실행형(standalone)이며 로컬 우선(local-first) 컨

## 안전성과 로컬 데이터

OpenOrbit는 로컬 우선입니다. 운영 상태는 저장소 바깥, 플랫폼 AppData에 저장됩니다.
OpenOrbit는 로컬 우선입니다. 기본적으로 운영 상태는 저장소 바깥, 플랫폼 AppData에 저장됩니다.

- Windows: `%LOCALAPPDATA%\Orbit`
- macOS: `~/Library/Application Support/Orbit`
- Linux: `${XDG_DATA_HOME:-~/.local/share}/orbit`

다른 위치를 사용하려면 `ORBIT_APP_DATA`를 설정하세요. 모델 프로필은 비밀 값이 담긴 환경 변수의 이름을 저장하며, 비밀 값 자체는 절대 저장하지 않습니다. 프로덕션 AI 시스템을 연결하기 전에 워크플로 명령, 승인된 워크스페이스 경계, 네트워크 노출을 검토하세요.
`orbit run PATH`를 사용하면 `PATH/.orbit`에 데이터를 보관합니다. 이 경로는 해당 실행에서 기존에 선택한 데이터 위치와 `ORBIT_APP_DATA`보다 우선합니다. Git으로 관리하지 않을 데이터라면 대상 프로젝트의 `.gitignore`에 `.orbit/`을 추가하세요. 명령줄 경로 없이 다른 위치를 사용하려면 `ORBIT_APP_DATA`를 설정하세요. 모델 프로필은 비밀 값이 담긴 환경 변수의 이름을 저장하며, 비밀 값 자체는 절대 저장하지 않습니다. 프로덕션 AI 시스템을 연결하기 전에 워크플로 명령, 승인된 워크스페이스 경계, 네트워크 노출을 검토하세요.

## API 및 확장성

Expand Down
12 changes: 12 additions & 0 deletions orbit/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import urllib.error
import urllib.request
import webbrowser
from pathlib import Path
from typing import Any


Expand Down Expand Up @@ -60,6 +61,11 @@ def parser() -> argparse.ArgumentParser:
root.add_argument("--url", default=os.environ.get("ORBIT_URL", "http://127.0.0.1:8787"))
commands = root.add_subparsers(dest="command", required=True)
run = commands.add_parser("run", help="Start the local OpenOrbit web server.")
run.add_argument(
"path",
nargs="?",
help="Store OpenOrbit data in PATH/.orbit instead of the default application-data directory.",
)
run.add_argument("--host", default=os.environ.get("ORBIT_HOST", "127.0.0.1"))
run.add_argument("--port", type=int, default=int(os.environ.get("ORBIT_PORT", "3000")))
run.add_argument("--reload", action="store_true", help="Reload the server when Python sources change.")
Expand Down Expand Up @@ -122,6 +128,12 @@ def main() -> None:
if not 1 <= args.port <= 65535:
print("error: --port must be an integer from 1 through 65535.", file=sys.stderr)
raise SystemExit(2)
if args.path:
root = Path(args.path).expanduser().resolve()
if root.exists() and not root.is_dir():
print(f"error: PATH must be a directory: {root}", file=sys.stderr)
raise SystemExit(2)
os.environ["ORBIT_APP_DATA"] = str(root / ".orbit")
if args.open:
webbrowser.open(f"http://{args.host}:{args.port}")
import uvicorn
Expand Down
31 changes: 30 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from orbit.cli import parser
import os
import sys
from types import SimpleNamespace

from orbit.cli import main, parser


def test_cli_exposes_control_room_commands():
Expand All @@ -15,6 +19,31 @@ def test_cli_exposes_local_web_server_command():
assert args.reload is True


def test_cli_accepts_an_optional_data_directory_for_the_local_web_server():
args = parser().parse_args(["run", "."])
assert args.path == "."


def test_cli_run_path_overrides_app_data_for_the_server(monkeypatch, tmp_path):
received = {}

def run(*args, **kwargs):
received["args"] = args
received["kwargs"] = kwargs

monkeypatch.setattr(sys, "argv", ["orbit", "run", str(tmp_path)])
monkeypatch.setitem(sys.modules, "uvicorn", SimpleNamespace(run=run))
monkeypatch.setenv("ORBIT_APP_DATA", "/previous-location")

main()

assert received == {
"args": ("app.main:app",),
"kwargs": {"host": "127.0.0.1", "port": 3000, "reload": False},
}
assert os.environ["ORBIT_APP_DATA"] == str(tmp_path / ".orbit")


def test_cli_exposes_task_test_with_waiting():
args = parser().parse_args(["tasks", "test", "insighta-user-simulation", "--wait", "--timeout", "42"])
assert args.command == "tasks"
Expand Down
Loading