diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..592d624 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,22 @@ +# Labeler configuration +# Refer to https://github.com/actions/labeler for documentation + +rust: + - changed-files: + - any-glob-to-any-file: ['Cargo.toml', 'Cargo.lock', 'src/**/*.rs', 'proto/**/*.proto'] + +python: + - changed-files: + - any-glob-to-any-file: ['python/**/*.py', 'requirements.txt'] + +documentation: + - changed-files: + - any-glob-to-any-file: ['*.md', 'locales/**/*.md', 'docs/**/*.md'] + +enhancement: + - changed-files: + - any-glob-to-any-file: ['src/**/*', 'proto/**/*'] + +chore: + - changed-files: + - any-glob-to-any-file: ['.github/**/*', '.gitignore', '.pre-commit-config.yaml'] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9fb86c..ed3b193 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,9 @@ jobs: uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy + - name: Install protoc + if: steps.guard.outputs.skip != 'true' + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler - name: Check formatting if: steps.guard.outputs.skip != 'true' run: cargo fmt --all --check @@ -66,6 +69,9 @@ jobs: - name: Setup Rust if: steps.guard.outputs.skip != 'true' uses: dtolnay/rust-toolchain@stable + - name: Install protoc + if: steps.guard.outputs.skip != 'true' + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler - name: Cache cargo if: steps.guard.outputs.skip != 'true' uses: actions/cache@v4 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index a203cd0..8ed4047 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -47,6 +47,7 @@ jobs: permissions: contents: read security-events: write + pull-requests: write steps: - uses: actions/checkout@v4 - name: Skip if no Cargo.toml @@ -63,6 +64,9 @@ jobs: uses: dtolnay/rust-toolchain@stable with: components: clippy + - name: Install protoc + if: steps.guard.outputs.skip != 'true' + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler - name: Install SARIF tools if: steps.guard.outputs.skip != 'true' run: cargo install clippy-sarif sarif-fmt diff --git a/.gitignore b/.gitignore index e178883..125f7c8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ Cargo.lock __pycache__/ *.py[cod] *$py.class +.superpowers/ +*.log docs/ diff --git a/CLAUDE.md b/CLAUDE.md index 35ed8ca..5ad2f29 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,6 +23,14 @@ This file is the operational core for Claude. Gemini CLI and Claude MUST follow - Trigger stack-specific formatting (e.g., `cargo fmt`). - Run `pre-commit run --all-files` if available. +## 🌐 Ecosystem Interaction Protocol +1. **Multi-Repo Boundaries:** You MUST NOT directly modify code in other `vtuber-*` repositories (especially `vtuber-contracts`). +2. **Issue-Based Communication:** When a change or resource is needed from another repository, you MUST: + - Draft the requirements locally in `docs/specs/ecosystem/`. + - Create a GitHub Issue in the target repository using `gh issue create`. + - Reference the Issue URL in your local progress reports. +3. **Dependency Sync:** Only implement features depending on external changes (like new Schemas) after the corresponding Issue is resolved and released. + ## 🛠️ Tooling & Standards - **Translation:** All technical specifications are English. `locales/` MUST be kept in sync and translated for users documentation. - **Workflow Mastery:** Use `/superpower:executing-plans` for feature work. diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..aa947ae --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "vtuber-image" +version = "0.1.0" +edition = "2021" + +[dependencies] +tonic = "0.11" +prost = "0.12" +tokio = { version = "1.0", features = ["full"] } +tokio-stream = "0.1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +anyhow = "1.0" + +[build-dependencies] +tonic-build = "0.11" diff --git a/GEMINI.md b/GEMINI.md index 308ce74..95b4430 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -23,6 +23,14 @@ This file is the operational core. Gemini CLI MUST follow these protocols to mai - Trigger stack-specific formatting (e.g., `cargo fmt`). - Run `pre-commit run --all-files` if available. +## 🌐 Ecosystem Interaction Protocol +1. **Multi-Repo Boundaries:** You MUST NOT directly modify code in other `vtuber-*` repositories (especially `vtuber-contracts`). +2. **Issue-Based Communication:** When a change or resource is needed from another repository, you MUST: + - Draft the requirements locally in `docs/specs/ecosystem/`. + - Create a GitHub Issue in the target repository using `gh issue create`. + - Reference the Issue URL in your local progress reports. +3. **Dependency Sync:** Only implement features depending on external changes (like new Schemas) after the corresponding Issue is resolved and released. + ## 🛠️ Tooling & Standards - **Translation:** All technical specifications are English. `locales/` MUST be kept in sync and translated for users documentation. - **Workflow Mastery:** Use `/superpower:executing-plans` for feature work. diff --git a/STRUCTURE.tree b/STRUCTURE.tree index 0078b08..b1b701c 100644 --- a/STRUCTURE.tree +++ b/STRUCTURE.tree @@ -1,35 +1,62 @@ -/ -├── GEMINI.md -├── CLAUDE.md -├── README.md -├── STRUCTURE.tree +. ├── ARCHITECTURE.md -├── ROADMAP.md -├── CONTRIBUTING.md +├── build.rs +├── Cargo.toml +├── ci_fail.log +├── CLAUDE.md ├── CODE_OF_CONDUCT.md -├── SECURITY.md -├── LICENSE.md +├── CONTRIBUTING.md ├── DEPLOYMENT_GUIDE.md ├── DESIGN_DECISIONS.md +├── docker-compose.yml +├── docs +│   └── superpowers +│   ├── plans +│   │   └── 2026-04-30-vtuber-image-foundation.md +│   └── specs +│   └── 2026-04-30-vtuber-image-foundation-design.md ├── FAQ.md +├── GEMINI.md +├── .github +│   ├── ISSUE_TEMPLATE +│   │   ├── bug_report.yml +│   │   └── feature_request.yml +│   ├── labeler.yml +│   ├── PULL_REQUEST_TEMPLATE.md +│   └── workflows +│   ├── badges.yml +│   ├── ci.yml +│   ├── pr_automation.yml +│   └── security.yml +├── .gitignore ├── GOVERNANCE.md -├── SUPPORT.md -├── TROUBLESHOOTING.md -├── PHILOSOPHY.md +├── LICENSE.md +├── locales +│   ├── README.ja.md +│   ├── README.th.md +│   └── README.zh.md ├── MANIFESTO.md -├── VISION.md -├── STRATEGY.md +├── PHILOSOPHY.md +├── pr_auto_fail.log +├── .pre-commit-config.yaml ├── PRINCIPLES.md -├── locales/ -│ ├── README.th.md -│ ├── README.ja.md -│ └── README.zh.md -└── .github/ - ├── ISSUE_TEMPLATE/ - │ ├── bug_report.yml - │ └── feature_request.yml - ├── PULL_REQUEST_TEMPLATE.md - └── workflows/ - ├── ci.yml - ├── security.yml - └── pr_automation.yml +├── proto +│   └── vtuber_image +│   └── v1 +│   └── image.proto +├── python +│   ├── comfy_client.py +│   └── requirements.txt +├── README.md +├── ROADMAP.md +├── security_fail.log +├── SECURITY.md +├── src +│   └── main.rs +├── STRATEGY.md +├── STRUCTURE.tree +├── SUPPORT.md +├── TROUBLESHOOTING.md +└── VISION.md + +14 directories, 46 files diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..515b289 --- /dev/null +++ b/build.rs @@ -0,0 +1,4 @@ +fn main() -> Result<(), Box> { + tonic_build::configure().compile(&["proto/vtuber_image/v1/image.proto"], &["proto"])?; + Ok(()) +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fc94146 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' + +services: + master: + image: chrislusf/seaweedfs + ports: + - 9333:9333 + command: "master -ip=master" + volume: + image: chrislusf/seaweedfs + ports: + - 8080:8080 + command: "volume -mserver=master:9333 -port=8080" + s3: + image: chrislusf/seaweedfs + ports: + - 8333:8333 + command: "s3 -master=master:9333" diff --git a/proto/vtuber_image/v1/image.proto b/proto/vtuber_image/v1/image.proto new file mode 100644 index 0000000..729646a --- /dev/null +++ b/proto/vtuber_image/v1/image.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; +package vtuber_image.v1; + +service ImageGenerator { + rpc Generate(GenerationRequest) returns (GenerationResponse); +} + +message GenerationRequest { + string persona_id = 1; + PersonaOverrides overrides = 2; +} + +message PersonaOverrides { + string hair_style = 1; + string eye_color = 2; + string outfit = 3; +} + +message GenerationResponse { + string image_url = 1; + map metadata = 2; +} diff --git a/python/comfy_client.py b/python/comfy_client.py new file mode 100644 index 0000000..ed513c2 --- /dev/null +++ b/python/comfy_client.py @@ -0,0 +1,18 @@ +import requests +import json +import uuid + +class ComfyClient: + def __init__(self, server_address="http://localhost:8188"): + self.server_address = server_address + self.client_id = str(uuid.uuid4()) + + def queue_prompt(self, prompt): + p = {"prompt": prompt, "client_id": self.client_id} + data = json.dumps(p).encode('utf-8') + response = requests.post(f"{self.server_address}/prompt", data=data) + return response.json() + +if __name__ == "__main__": + client = ComfyClient() + print(f"Client initialized with ID: {client.client_id}") diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 0000000..043c3b8 --- /dev/null +++ b/python/requirements.txt @@ -0,0 +1,2 @@ +requests==2.31.0 +websocket-client==1.7.0 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..22ef2a8 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,56 @@ +use tonic::{transport::Server, Request, Response, Status}; +use vtuber_image::v1::image_generator_server::{ImageGenerator, ImageGeneratorServer}; +use vtuber_image::v1::{GenerationRequest, GenerationResponse}; + +pub mod vtuber_image { + pub mod v1 { + tonic::include_proto!("vtuber_image.v1"); + } +} + +#[derive(Default)] +pub struct MyImageGenerator {} + +#[tonic::async_trait] +impl ImageGenerator for MyImageGenerator { + async fn generate( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + println!("Received request for persona: {}", req.persona_id); + + // Simple bridge to Python worker + let output = std::process::Command::new("python3") + .arg("python/comfy_client.py") + .output() + .map_err(|e| Status::internal(format!("Failed to execute python worker: {}", e)))?; + + println!( + "Python output: {:?}", + String::from_utf8_lossy(&output.stdout) + ); + + let reply = GenerationResponse { + image_url: "http://placeholder.com/image.png".to_string(), + metadata: std::collections::HashMap::new(), + }; + + Ok(Response::new(reply)) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "[::1]:8083".parse()?; + let generator = MyImageGenerator::default(); + + println!("ImageGenerator server listening on {}", addr); + + Server::builder() + .add_service(ImageGeneratorServer::new(generator)) + .serve(addr) + .await?; + + Ok(()) +}