Initial BeeOS simulator and asset pipeline

This commit is contained in:
RayPals
2026-08-25 20:29:05 +00:00
commit 7d4d683a8f
265 changed files with 16676 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
from __future__ import annotations
"""Clean generated Python cache files from the BeeOS project."""
import argparse
import shutil
from pathlib import Path
def main() -> None:
parser = argparse.ArgumentParser(description="Remove __pycache__ and .pyc files.")
parser.add_argument("--project-root", default=".", help="BeeOS project root")
args = parser.parse_args()
project_root = Path(args.project_root).resolve()
removed = 0
for cache_dir in project_root.rglob("__pycache__"):
shutil.rmtree(cache_dir, ignore_errors=True)
removed += 1
for pyc in project_root.rglob("*.pyc"):
pyc.unlink(missing_ok=True)
removed += 1
print(f"Cleaned {removed} cache item(s).")
if __name__ == "__main__":
main()
+364
View File
@@ -0,0 +1,364 @@
from __future__ import annotations
"""BeeOS icon asset conversion tool.
Converts the curated BeeOS/Lucide PNG icon subset into firmware-friendly formats.
Outputs:
- JSON monochrome bitmap map
- generic packed 1-bit C buffers
- LVGL v8 image descriptors
- LVGL v9 image descriptors
- preview sheets
The LVGL descriptor exports are intentionally generated as separate v8/v9 files
because LVGL image descriptor APIs changed between major versions.
"""
import argparse
import json
from pathlib import Path
from PIL import Image, ImageDraw
DEFAULT_SIZES = [18, 24, 34, 48]
def load_icon_ids(svg_manifest_path: Path) -> list[str]:
data = json.loads(svg_manifest_path.read_text(encoding="utf-8"))
return [item["id"] for item in data.get("icons", [])]
def image_to_mono_rows(path: Path, threshold: int = 24) -> tuple[int, int, list[str], list[int], int]:
img = Image.open(path).convert("RGBA")
width, height = img.size
stride = (width + 7) // 8
rows: list[str] = []
packed: list[int] = []
for y in range(height):
bits = []
byte = 0
count = 0
for x in range(width):
r, g, b, a = img.getpixel((x, y))
on = a > threshold and (r + g + b) > threshold
bits.append("1" if on else "0")
byte = (byte << 1) | (1 if on else 0)
count += 1
if count == 8:
packed.append(byte)
byte = 0
count = 0
if count:
byte = byte << (8 - count)
packed.append(byte)
rows.append("".join(bits))
return width, height, rows, packed, stride
def c_array_name(icon_id: str, size: int) -> str:
clean = icon_id.replace("-", "_").replace(".", "_")
return f"beeos_icon_{clean}_{size}"
def export_json(icon_ids: list[str], png_dir: Path, output_dir: Path, size: int) -> Path:
icons = {}
for icon_id in icon_ids:
path = png_dir / str(size) / f"{icon_id}.png"
if not path.exists():
continue
width, height, rows, packed, stride = image_to_mono_rows(path)
icons[icon_id] = {
"width": width,
"height": height,
"stride": stride,
"rows": rows,
"packed_bytes": packed,
}
output_dir.mkdir(parents=True, exist_ok=True)
out = output_dir / f"beeos_icons_{size}.json"
out.write_text(json.dumps({"size": size, "icons": icons}, indent=2), encoding="utf-8")
return out
def collect_icon_data(icon_ids: list[str], png_dir: Path, size: int) -> list[dict]:
rows = []
for icon_id in icon_ids:
path = png_dir / str(size) / f"{icon_id}.png"
if not path.exists():
continue
width, height, _rows, packed, stride = image_to_mono_rows(path)
name = c_array_name(icon_id, size)
rows.append(
{
"id": icon_id,
"name": name,
"width": width,
"height": height,
"stride": stride,
"data": packed,
}
)
return rows
def export_generic_c(icon_rows: list[dict], output_dir: Path, size: int) -> tuple[Path, Path]:
output_dir.mkdir(parents=True, exist_ok=True)
header = output_dir / f"beeos_icons_{size}.h"
source = output_dir / f"beeos_icons_{size}.c"
header_lines = [
"/* Auto-generated BeeOS icon declarations. */",
"#pragma once",
"",
"#include <stdint.h>",
"",
"typedef struct BeeOSIconAsset {",
" const char *name;",
" uint16_t width;",
" uint16_t height;",
" uint16_t stride;",
" const uint8_t *data;",
" uint32_t data_len;",
"} BeeOSIconAsset;",
"",
]
source_lines = [
"/* Auto-generated BeeOS 1-bit icon data. */",
"",
f'#include "beeos_icons_{size}.h"',
"",
]
for row in icon_rows:
header_lines.append(f"extern const uint8_t {row['name']}_data[{len(row['data'])}];")
source_lines.append(f"const uint8_t {row['name']}_data[{len(row['data'])}] = {{")
for i in range(0, len(row["data"]), 12):
chunk = row["data"][i : i + 12]
source_lines.append(" " + ", ".join(f"0x{b:02x}" for b in chunk) + ",")
source_lines.append("};")
source_lines.append("")
header_lines.extend(
[
"",
f"extern const BeeOSIconAsset beeos_icons_{size}[];",
f"extern const uint32_t beeos_icons_{size}_count;",
"",
]
)
source_lines.append(f"const BeeOSIconAsset beeos_icons_{size}[] = {{")
for row in icon_rows:
source_lines.append(
f' {{"{row["id"]}", {row["width"]}, {row["height"]}, {row["stride"]}, {row["name"]}_data, {len(row["data"])}}},'
)
source_lines.append("};")
source_lines.append("")
source_lines.append(f"const uint32_t beeos_icons_{size}_count = {len(icon_rows)};")
source_lines.append("")
header.write_text("\\n".join(header_lines), encoding="utf-8")
source.write_text("\\n".join(source_lines), encoding="utf-8")
return header, source
def export_lvgl_v8(icon_rows: list[dict], output_dir: Path, size: int) -> tuple[Path, Path]:
output_dir.mkdir(parents=True, exist_ok=True)
header = output_dir / f"beeos_icons_{size}_lvgl_v8.h"
source = output_dir / f"beeos_icons_{size}_lvgl_v8.c"
header_lines = [
"/* Auto-generated BeeOS LVGL v8 icon descriptors. */",
"#pragma once",
"",
"#include <lvgl.h>",
"",
]
source_lines = [
"/* Auto-generated BeeOS LVGL v8 icon descriptors.",
" Uses LV_IMG_CF_ALPHA_1BIT for monochrome/alpha icon masks.",
" Style code should recolor these with image recolor/opacity if needed. */",
"",
f'#include "beeos_icons_{size}_lvgl_v8.h"',
"",
]
for row in icon_rows:
dsc_name = f"{row['name']}_dsc"
data_name = f"{row['name']}_data"
header_lines.append(f"LV_IMG_DECLARE({dsc_name});")
source_lines.append(f"static const uint8_t {data_name}[] = {{")
for i in range(0, len(row["data"]), 12):
chunk = row["data"][i : i + 12]
source_lines.append(" " + ", ".join(f"0x{b:02x}" for b in chunk) + ",")
source_lines.append("};")
source_lines.append("")
source_lines.append(f"const lv_img_dsc_t {dsc_name} = {{")
source_lines.append(" .header.always_zero = 0,")
source_lines.append(" .header.reserved = 0,")
source_lines.append(" .header.cf = LV_IMG_CF_ALPHA_1BIT,")
source_lines.append(f" .header.w = {row['width']},")
source_lines.append(f" .header.h = {row['height']},")
source_lines.append(f" .data_size = {len(row['data'])},")
source_lines.append(f" .data = {data_name},")
source_lines.append("};")
source_lines.append("")
header.write_text("\\n".join(header_lines), encoding="utf-8")
source.write_text("\\n".join(source_lines), encoding="utf-8")
return header, source
def export_lvgl_v9(icon_rows: list[dict], output_dir: Path, size: int) -> tuple[Path, Path]:
output_dir.mkdir(parents=True, exist_ok=True)
header = output_dir / f"beeos_icons_{size}_lvgl_v9.h"
source = output_dir / f"beeos_icons_{size}_lvgl_v9.c"
header_lines = [
"/* Auto-generated BeeOS LVGL v9 icon descriptors. */",
"#pragma once",
"",
"#include <lvgl.h>",
"",
]
source_lines = [
"/* Auto-generated BeeOS LVGL v9 icon descriptors.",
" Uses LV_COLOR_FORMAT_I1 for monochrome/alpha icon masks.",
" Some LVGL v9 ports may need color-format adjustments depending on display setup. */",
"",
f'#include "beeos_icons_{size}_lvgl_v9.h"',
"",
]
for row in icon_rows:
dsc_name = f"{row['name']}_dsc"
data_name = f"{row['name']}_data"
header_lines.append(f"extern const lv_image_dsc_t {dsc_name};")
source_lines.append(f"static const uint8_t {data_name}[] = {{")
for i in range(0, len(row["data"]), 12):
chunk = row["data"][i : i + 12]
source_lines.append(" " + ", ".join(f"0x{b:02x}" for b in chunk) + ",")
source_lines.append("};")
source_lines.append("")
source_lines.append(f"const lv_image_dsc_t {dsc_name} = {{")
source_lines.append(" .header.magic = LV_IMAGE_HEADER_MAGIC,")
source_lines.append(" .header.cf = LV_COLOR_FORMAT_I1,")
source_lines.append(" .header.flags = 0,")
source_lines.append(f" .header.w = {row['width']},")
source_lines.append(f" .header.h = {row['height']},")
source_lines.append(f" .header.stride = {row['stride']},")
source_lines.append(f" .data_size = {len(row['data'])},")
source_lines.append(f" .data = {data_name},")
source_lines.append("};")
source_lines.append("")
header.write_text("\\n".join(header_lines), encoding="utf-8")
source.write_text("\\n".join(source_lines), encoding="utf-8")
return header, source
def export_preview(icon_ids: list[str], png_dir: Path, output_dir: Path, size: int) -> Path:
cols = 5
cell_w = 96
cell_h = 84
rows = (len(icon_ids) + cols - 1) // cols
sheet = Image.new("RGBA", (cols * cell_w, max(1, rows) * cell_h), (0, 0, 0, 255))
draw = ImageDraw.Draw(sheet)
for idx, icon_id in enumerate(icon_ids):
x = (idx % cols) * cell_w
y = (idx // cols) * cell_h
draw.rounded_rectangle((x + 18, y + 6, x + 78, y + 66), radius=18, fill=(20, 22, 28, 255), outline=(255, 204, 0, 255))
path = png_dir / str(size) / f"{icon_id}.png"
if path.exists():
icon = Image.open(path).convert("RGBA")
sheet.alpha_composite(icon, (x + (cell_w - icon.width) // 2, y + 18))
draw.text((x + 6, y + 68), icon_id[:14], fill=(220, 220, 220, 255))
output_dir.mkdir(parents=True, exist_ok=True)
out = output_dir / f"beeos_icons_{size}_preview.png"
sheet.save(out)
return out
def convert(project_root: Path, sizes: list[int]) -> dict:
svg_manifest = project_root / "beeos" / "assets" / "icons" / "lucide" / "manifest.json"
png_dir = project_root / "beeos" / "assets" / "icons" / "lucide_png"
generated_root = project_root / "beeos" / "generated" / "icons"
json_dir = generated_root / "bitmap_json"
generic_dir = generated_root / "lvgl"
v8_dir = generated_root / "lvgl_v8"
v9_dir = generated_root / "lvgl_v9"
preview_dir = generated_root / "previews"
icon_ids = load_icon_ids(svg_manifest)
result = {
"icon_count": len(icon_ids),
"sizes": {},
}
for size in sizes:
icon_rows = collect_icon_data(icon_ids, png_dir, size)
json_out = export_json(icon_ids, png_dir, json_dir, size)
generic_h, generic_c = export_generic_c(icon_rows, generic_dir, size)
v8_h, v8_c = export_lvgl_v8(icon_rows, v8_dir, size)
v9_h, v9_c = export_lvgl_v9(icon_rows, v9_dir, size)
preview = export_preview(icon_ids, png_dir, preview_dir, size)
result["sizes"][str(size)] = {
"json": str(json_out.relative_to(project_root)),
"generic_header": str(generic_h.relative_to(project_root)),
"generic_source": str(generic_c.relative_to(project_root)),
"lvgl_v8_header": str(v8_h.relative_to(project_root)),
"lvgl_v8_source": str(v8_c.relative_to(project_root)),
"lvgl_v9_header": str(v9_h.relative_to(project_root)),
"lvgl_v9_source": str(v9_c.relative_to(project_root)),
"preview": str(preview.relative_to(project_root)),
}
index_out = generated_root / "icon_build_index.json"
index_out.parent.mkdir(parents=True, exist_ok=True)
index_out.write_text(json.dumps(result, indent=2), encoding="utf-8")
return result
def main() -> None:
parser = argparse.ArgumentParser(description="Convert BeeOS curated icons into firmware-friendly assets.")
parser.add_argument("--project-root", default=".", help="BeeOS project root")
parser.add_argument("--sizes", default="24,34", help="Comma-separated icon sizes to export")
args = parser.parse_args()
root = Path(args.project_root).resolve()
sizes = [int(part.strip()) for part in args.sizes.split(",") if part.strip()]
result = convert(root, sizes)
print("BeeOS icon conversion complete.")
print(f"Icons: {result['icon_count']}")
for size, outputs in result["sizes"].items():
print(f"Size {size}:")
for key, path in outputs.items():
print(f" {key}: {path}")
if __name__ == "__main__":
main()
+57
View File
@@ -0,0 +1,57 @@
from __future__ import annotations
"""Create a starter HiveStore repo index.
Usage:
py tools/create_hivestore_repo.py ./my-hivestore "My HiveStore Repo"
Then commit the generated hivestore_index.json to GitHub.
"""
import json
import sys
from pathlib import Path
def create_repo(path: Path, name: str) -> None:
path.mkdir(parents=True, exist_ok=True)
index = {
"schema": 1,
"source_name": name,
"source_url": "https://github.com/example/my-hivestore",
"packages": [
{
"id": "example.app.hello",
"name": "Hello Bee",
"type": "app",
"category": "Apps",
"version": "0.1.0",
"author": "Example Developer",
"description": "A simple starter BeeOS app listing.",
"repository": "https://github.com/example/hello-bee",
"entry": "app.py",
"permissions": ["display", "storage"],
"rating": "0.0",
"color": "#ffcc00",
"icon": "▣",
"curated": True
}
]
}
(path / "hivestore_index.json").write_text(json.dumps(index, indent=2), encoding="utf-8")
(path / "README.md").write_text(
f"# {name}\\n\\nThis is a BeeOS HiveStore repo.\\n\\nThe package index lives at `hivestore_index.json`.\\n",
encoding="utf-8",
)
print(f"Created HiveStore repo starter at: {path}")
def main() -> None:
if len(sys.argv) < 3:
print('Usage: py tools/create_hivestore_repo.py <folder> "Repo Name"')
raise SystemExit(1)
create_repo(Path(sys.argv[1]), " ".join(sys.argv[2:]))
if __name__ == "__main__":
main()
+92
View File
@@ -0,0 +1,92 @@
from __future__ import annotations
import json
import sys
from pathlib import Path
VALID_TYPES = {"app", "script", "script_pack", "watchface", "theme", "plugin"}
def create_template(package_type: str, package_id: str, name: str) -> None:
if package_type not in VALID_TYPES:
raise SystemExit(f"Invalid package type. Use one of: {', '.join(sorted(VALID_TYPES))}")
root = Path("beeos") / "sideload" / package_id
root.mkdir(parents=True, exist_ok=True)
entry = "app.py"
if package_type == "theme":
entry = "theme.json"
elif package_type == "watchface":
entry = "face.py"
elif package_type in {"script", "script_pack"}:
entry = "script.py"
elif package_type == "plugin":
entry = "plugin.py"
manifest = {
"id": package_id,
"name": name,
"type": package_type,
"version": "0.1.0",
"author": "Local Developer",
"description": f"A local BeeOS {package_type} package.",
"entry": entry,
"permissions": ["display", "storage"],
"repository": "",
"category": package_type,
"color": "#ffcc00",
"icon": "⬢",
"curated": False,
}
(root / "manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
if package_type == "app":
app_code = f"""class BeeApp:
def __init__(self, ctx):
self.ctx = ctx
def on_open(self):
launches = self.ctx.storage.get("launches", 0) + 1
self.ctx.storage.set("launches", launches)
def on_draw(self, display):
display.title({name!r})
display.card("BeeOS App", "Created from the Phase 5 template.")
display.list_item("Launches", str(self.ctx.storage.get("launches", 0)))
display.list_item("Touches", str(self.ctx.storage.get("touches", 0)))
display.progress("Battery", self.ctx.battery_percent())
def on_touch(self, event):
touches = self.ctx.storage.get("touches", 0) + 1
self.ctx.storage.set("touches", touches)
def on_close(self):
self.ctx.notify({name!r}, "Session closed")
"""
(root / entry).write_text(app_code, encoding="utf-8")
elif package_type == "theme":
(root / entry).write_text(json.dumps({"accent": "#ffcc00", "background": "#000000"}, indent=2), encoding="utf-8")
else:
script_code = f"""def main(ctx):
ctx.display.title({name!r})
ctx.display.text("Hello from a BeeOS {package_type} package.")
ctx.notify({name!r}, "Package ran through BeeOS Runtime.")
"""
(root / entry).write_text(script_code, encoding="utf-8")
print(f"Created BeeOS package template: {root}")
def main() -> None:
if len(sys.argv) < 4:
print("Usage: py tools/create_package_template.py <type> <package.id> <Name>")
print("Example: py tools/create_package_template.py app local.test.app \"Test App\"")
raise SystemExit(1)
create_template(sys.argv[1], sys.argv[2], " ".join(sys.argv[3:]))
if __name__ == "__main__":
main()
+109
View File
@@ -0,0 +1,109 @@
from __future__ import annotations
"""Export BeeOS themes into firmware-friendly C and JSON assets."""
import argparse
import json
from pathlib import Path
def hex_to_int(value: str) -> int:
value = value.strip().lstrip("#")
return int(value, 16)
def load_themes(project_root: Path) -> list[dict]:
path = project_root / "beeos" / "data" / "themes.json"
data = json.loads(path.read_text(encoding="utf-8"))
return list(data.get("themes", []))
def export_themes(project_root: Path) -> dict:
themes = load_themes(project_root)
out_dir = project_root / "beeos" / "generated" / "themes"
out_dir.mkdir(parents=True, exist_ok=True)
json_out = out_dir / "beeos_themes.json"
json_out.write_text(json.dumps({"themes": themes}, indent=2), encoding="utf-8")
header = out_dir / "beeos_themes.h"
source = out_dir / "beeos_themes.c"
header_lines = [
"/* Auto-generated BeeOS theme declarations. */",
"#pragma once",
"",
"#include <stdint.h>",
"",
"typedef struct BeeOSTheme {",
" const char *id;",
" const char *name;",
" uint32_t accent;",
" uint32_t background;",
" uint32_t card;",
" uint32_t text;",
" uint32_t muted;",
"} BeeOSTheme;",
"",
"extern const BeeOSTheme beeos_themes[];",
"extern const uint32_t beeos_themes_count;",
"",
]
source_lines = [
"/* Auto-generated BeeOS theme table. */",
"",
'#include "beeos_themes.h"',
"",
"const BeeOSTheme beeos_themes[] = {",
]
for theme in themes:
source_lines.append(
' {"%s", "%s", 0x%06x, 0x%06x, 0x%06x, 0x%06x, 0x%06x},'
% (
theme["id"],
theme["name"],
hex_to_int(theme["accent"]),
hex_to_int(theme["background"]),
hex_to_int(theme["card"]),
hex_to_int(theme["text"]),
hex_to_int(theme["muted"]),
)
)
source_lines.append("};")
source_lines.append("")
source_lines.append(f"const uint32_t beeos_themes_count = {len(themes)};")
source_lines.append("")
header.write_text("\\n".join(header_lines), encoding="utf-8")
source.write_text("\\n".join(source_lines), encoding="utf-8")
index = {
"theme_count": len(themes),
"outputs": {
"json": str(json_out.relative_to(project_root)),
"header": str(header.relative_to(project_root)),
"source": str(source.relative_to(project_root)),
},
}
index_out = out_dir / "theme_build_index.json"
index_out.write_text(json.dumps(index, indent=2), encoding="utf-8")
return index
def main() -> None:
parser = argparse.ArgumentParser(description="Export BeeOS themes into firmware-friendly assets.")
parser.add_argument("--project-root", default=".", help="BeeOS project root")
args = parser.parse_args()
result = export_themes(Path(args.project_root).resolve())
print("BeeOS theme export complete.")
print(f"Themes: {result['theme_count']}")
for key, value in result["outputs"].items():
print(f" {key}: {value}")
if __name__ == "__main__":
main()
+73
View File
@@ -0,0 +1,73 @@
from __future__ import annotations
"""Import a HiveStore repo into BeeOS.
Supports:
- local JSON file path
- raw GitHub JSON URL
- normal GitHub repo URL, normalized to /raw/main/hivestore_index.json
"""
import argparse
import json
import shutil
import sys
import urllib.request
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from beeos.core.hivestore_sources import HiveStoreSourceManager, normalize_github_url, source_id_from_url
def fetch_or_copy_index(url_or_path: str, cache_path: Path) -> dict:
cache_path.parent.mkdir(parents=True, exist_ok=True)
local = Path(url_or_path)
if local.exists():
shutil.copyfile(local, cache_path)
return json.loads(cache_path.read_text(encoding="utf-8"))
normalized = normalize_github_url(url_or_path)
with urllib.request.urlopen(normalized, timeout=20) as response:
data = response.read()
cache_path.write_bytes(data)
return json.loads(data.decode("utf-8"))
def main() -> None:
parser = argparse.ArgumentParser(description="Import a BeeOS HiveStore repo source.")
parser.add_argument("source", help="Local hivestore_index.json, raw URL, or GitHub repo URL")
parser.add_argument("--project-root", default=".", help="BeeOS project root")
parser.add_argument("--name", default="", help="Display name override")
parser.add_argument("--no-fetch", action="store_true", help="Add source record without fetching/cache validation")
args = parser.parse_args()
project_root = Path(args.project_root).resolve()
data_dir = project_root / "beeos" / "data"
manager = HiveStoreSourceManager(data_dir)
normalized = normalize_github_url(args.source)
source_id = source_id_from_url(normalized)
cache_path = data_dir / "hivestore_cache" / f"{source_id}.json"
name = args.name or "Imported HiveStore Repo"
if not args.no_fetch:
data = fetch_or_copy_index(args.source, cache_path)
name = args.name or data.get("source_name", name)
index = str(cache_path.relative_to(data_dir))
else:
index = normalized
source = manager.add_source(name=name, url=args.source, index=index, enabled=True)
print("Imported HiveStore source:")
print(f" id: {source.source_id}")
print(f" name: {source.name}")
print(f" index: {source.index}")
if __name__ == "__main__":
main()
+104
View File
@@ -0,0 +1,104 @@
from __future__ import annotations
"""Validate the BeeOS project tree.
Checks:
- Python syntax
- JSON syntax
- HiveStore source loading
- package manifest loading
- generated asset indexes
"""
import argparse
import json
import py_compile
import sys
from pathlib import Path
def fail(message: str) -> None:
raise RuntimeError(message)
def check_python(project_root: Path) -> int:
count = 0
for py_file in project_root.rglob("*.py"):
if "__pycache__" in py_file.parts:
continue
py_compile.compile(str(py_file), doraise=True)
count += 1
return count
def check_json(project_root: Path) -> int:
count = 0
for json_file in project_root.rglob("*.json"):
json.loads(json_file.read_text(encoding="utf-8"))
count += 1
return count
def check_hivestore(project_root: Path) -> tuple[int, int]:
if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))
from beeos.core.hivestore import HiveStoreIndex
index = HiveStoreIndex(project_root / "beeos" / "data" / "hivestore_index.json")
index.load()
if not index.source_manager.sources:
fail("No HiveStore sources configured.")
if not index.listings:
fail("HiveStore loaded zero package listings.")
return len(index.source_manager.sources), len(index.listings)
def check_generated_assets(project_root: Path) -> dict:
icon_index = project_root / "beeos" / "generated" / "icons" / "icon_build_index.json"
theme_index = project_root / "beeos" / "generated" / "themes" / "theme_build_index.json"
result = {
"icon_index": icon_index.exists(),
"theme_index": theme_index.exists(),
"icon_count": 0,
"theme_count": 0,
}
if icon_index.exists():
data = json.loads(icon_index.read_text(encoding="utf-8"))
result["icon_count"] = int(data.get("icon_count", 0))
if theme_index.exists():
data = json.loads(theme_index.read_text(encoding="utf-8"))
result["theme_count"] = int(data.get("theme_count", 0))
return result
def main() -> None:
parser = argparse.ArgumentParser(description="Validate the BeeOS project.")
parser.add_argument("--project-root", default=".", help="BeeOS project root")
args = parser.parse_args()
project_root = Path(args.project_root).resolve()
python_count = check_python(project_root)
json_count = check_json(project_root)
source_count, listing_count = check_hivestore(project_root)
assets = check_generated_assets(project_root)
print("BeeOS validation passed.")
print(f"Python files: {python_count}")
print(f"JSON files: {json_count}")
print(f"HiveStore sources: {source_count}")
print(f"HiveStore listings: {listing_count}")
print(f"Generated icons: {assets['icon_count']}")
print(f"Generated themes: {assets['theme_count']}")
if __name__ == "__main__":
main()