From a2e84d2717d4a2de1d39d114c3b3726cedf80dfc Mon Sep 17 00:00:00 2001 From: karuo Date: Thu, 12 Mar 2026 23:23:53 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20=E5=8D=A1=E8=8B=A5AI=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=202026-03-12=2023:23=20|=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=9A=E5=8D=A1=E6=9C=A8=E3=80=81=E8=BF=90=E8=90=A5=E4=B8=AD?= =?UTF-8?q?=E6=9E=A2=E5=B7=A5=E4=BD=9C=E5=8F=B0=20|=20=E6=8E=92=E9=99=A4?= =?UTF-8?q?=20>20MB:=2011=20=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent-harness/cli_anything/webpomodoro/__init__.py | 1 + .../agent-harness/cli_anything/webpomodoro/__main__.py | 4 + .../agent-harness/cli_anything/webpomodoro/core/__init__.py | 0 .../agent-harness/cli_anything/webpomodoro/core/data.py | 135 +++++ .../agent-harness/cli_anything/webpomodoro/core/timer.py | 235 +++++++++ .../cli_anything/webpomodoro/tests/__init__.py | 0 .../cli_anything/webpomodoro/tests/test_core.py | 163 ++++++ .../cli_anything/webpomodoro/utils/__init__.py | 0 .../cli_anything/webpomodoro/utils/repl_skin.py | 498 ++++++++++++++++++ .../cli_anything/webpomodoro/webpomodoro_cli.py | 318 +++++++++++ .../cli_anything_webpomodoro.egg-info/PKG-INFO | 10 + .../cli_anything_webpomodoro.egg-info/SOURCES.txt | 17 + .../cli_anything_webpomodoro.egg-info/dependency_links.txt | 1 + .../cli_anything_webpomodoro.egg-info/entry_points.txt | 2 + .../cli_anything_webpomodoro.egg-info/requires.txt | 2 + .../cli_anything_webpomodoro.egg-info/top_level.txt | 1 + .../CLI万能化/outputs/webpomodoro/agent-harness/setup.py | 18 + 运营中枢/工作台/gitea_push_log.md | 1 + 运营中枢/工作台/代码管理.md | 1 + 19 files changed, 1407 insertions(+) create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/__init__.py create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/__main__.py create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/core/__init__.py create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/core/data.py create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/core/timer.py create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/tests/__init__.py create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/tests/test_core.py create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/utils/__init__.py create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/utils/repl_skin.py create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/webpomodoro_cli.py create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/PKG-INFO create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/SOURCES.txt create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/dependency_links.txt create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/entry_points.txt create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/requires.txt create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/top_level.txt create mode 100644 03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/setup.py diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/__init__.py b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/__init__.py new file mode 100644 index 00000000..44ad8fec --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/__init__.py @@ -0,0 +1 @@ +"""cli_anything.webpomodoro — WebPomodoro macOS app CLI interface.""" diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/__main__.py b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/__main__.py new file mode 100644 index 00000000..3b70a684 --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/__main__.py @@ -0,0 +1,4 @@ +from cli_anything.webpomodoro.webpomodoro_cli import cli + +if __name__ == "__main__": + cli() diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/core/__init__.py b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/core/data.py b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/core/data.py new file mode 100644 index 00000000..314e5920 --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/core/data.py @@ -0,0 +1,135 @@ +""" +WebPomodoro data layer — reads tasks, sessions, goals from local storage. +""" +import json +import base64 +from datetime import datetime, timezone +from typing import Optional + +from cli_anything.webpomodoro.utils.webpomodoro_backend import ( + read_localstorage, + read_tasks, + read_pomodoro_records, + count_today_pomodoros, + get_timer_state, +) + + +def _safe_json(s): + try: + return json.loads(s) if isinstance(s, str) else s + except Exception: + return s + + +def _decode_b64(s: str) -> str: + try: + return base64.b64decode(s).decode("utf-8") + except Exception: + return s + + +def _ts_to_human(ts) -> str: + """Convert millisecond timestamp to human-readable local time.""" + try: + ts_sec = int(ts) / 1000 + return datetime.fromtimestamp(ts_sec).strftime("%Y-%m-%d %H:%M") + except Exception: + return str(ts) + + +def get_current_task_info() -> dict: + """ + Returns info about the currently tracked task. + Combines localStorage (timingTaskId) with IndexedDB task lookup. + """ + ls = read_localstorage() + task_id = ls.get("timingTaskId", "") + subtask_id = ls.get("timingSubtaskId", "") + + result = { + "timingTaskId": task_id, + "timingSubtaskId": subtask_id, + "found": False, + "taskName": None, + } + + if not task_id: + result["message"] = "No task currently being timed" + return result + + # Search in IndexedDB task records + tasks = read_tasks(limit=100) + for t in tasks: + tid = t.get("id", "").strip() + if tid == task_id or task_id in tid: + result["found"] = True + result["rawData"] = t.get("data", {}) + # Try to extract name from raw words + words = t.get("data", {}).get("_raw_words", []) + if words: + result["taskName"] = " ".join(words[:5]) + break + + return result + + +def get_user_info() -> dict: + """Return logged-in user info.""" + ls = read_localstorage() + return { + "name": _decode_b64(ls.get("cookie.NAME", "")), + "email": _decode_b64(ls.get("cookie.ACCT", "")), + "uid": ls.get("cookie.UID", ""), + "appVersion": ls.get("Version", ""), + "installDate": _ts_to_human(ls.get("InstallationDate", 0)), + } + + +def get_goals() -> list: + """Return daily goals.""" + ls = read_localstorage() + goals_raw = ls.get("Goals", "[]") + goals = _safe_json(goals_raw) + result = [] + if isinstance(goals, list): + for g in goals: + if isinstance(g, dict): + goal_type = g.get("type", "") + value = g.get("value", 0) + if goal_type == "TIME": + result.append({"type": "daily_focus_minutes", "value": value, + "display": f"{value} 分钟/天"}) + elif goal_type == "COUNT": + result.append({"type": "daily_pomodoro_count", "value": value, + "display": f"{value} 个番茄/天"}) + return result + + +def get_full_status() -> dict: + """Return complete app status.""" + state = get_timer_state() + user = get_user_info() + goals = get_goals() + pomodoro_count = count_today_pomodoros() + + return { + "timer": { + "label": state.get("label", "unknown"), + "timingTaskId": state.get("timingTaskId", ""), + }, + "user": user, + "goals": goals, + "totalPomodoros": pomodoro_count, + "lastSync": _ts_to_human(state.get("syncTimestamp", 0)), + } + + +def get_recent_pomodoros(limit: int = 10) -> list: + """Get recent Pomodoro session records.""" + records = read_pomodoro_records(limit=limit) + result = [] + for r in records: + item = {"id": r.get("id", ""), "data": r.get("data", {})} + result.append(item) + return result diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/core/timer.py b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/core/timer.py new file mode 100644 index 00000000..31c508cd --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/core/timer.py @@ -0,0 +1,235 @@ +""" +Timer control — uses AppleScript (Accessibility) to drive WebPomodoro. +State machine: initial → work ↔ pause → rest → initial +""" +import subprocess +import time +from typing import Optional + + +def _applescript(script: str) -> str: + r = subprocess.run(["osascript", "-e", script], capture_output=True, text=True, timeout=10) + if r.returncode != 0: + raise RuntimeError(r.stderr.strip()) + return r.stdout.strip() + + +def _ensure_app_active() -> None: + _applescript('tell application "WebPomodoro" to activate') + time.sleep(0.4) + + +def _click_in_window(x: int, y: int) -> None: + """Click at screen position via System Events.""" + script = f''' +tell application "System Events" + tell process "WebPomodoro" + click at {{{x}, {y}}} + end tell +end tell''' + _applescript(script) + + +def _press_key(key: str) -> None: + script = f''' +tell application "System Events" + tell process "WebPomodoro" + keystroke "{key}" + end tell +end tell''' + _applescript(script) + + +# ── Menu-based controls ──────────────────────────────────────────────────── + +def _get_window_buttons() -> list: + """Get all buttons in the main WebPomodoro window.""" + script = ''' +tell application "System Events" + tell process "WebPomodoro" + set wins to windows + if (count of wins) > 0 then + set win1 to item 1 of wins + set allButtons to buttons of win1 + set btnNames to {} + repeat with b in allButtons + try + set end of btnNames to description of b & " | " & title of b + end try + end repeat + return btnNames + end if + return {} + end tell +end tell''' + try: + return _applescript(script) + except Exception: + return [] + + +def start_work() -> dict: + """Start a work (focus) session.""" + _ensure_app_active() + # Try AppleScript webBridge approach via JS evaluation via URL scheme + # Fallback: click the start button via Accessibility + script = ''' +tell application "System Events" + tell process "WebPomodoro" + set wins to windows + if (count of wins) > 0 then + set win1 to item 1 of wins + -- Look for start/play button + repeat with b in buttons of win1 + try + set desc to description of b + if desc contains "开始" or desc contains "start" or desc contains "Start" or desc contains "play" then + click b + return "clicked: " & desc + end if + end try + end repeat + end if + end tell +end tell +return "no start button found"''' + try: + result = _applescript(script) + return {"action": "start_work", "result": result} + except Exception as e: + return {"action": "start_work", "error": str(e)} + + +def pause_timer() -> dict: + """Pause the current timer.""" + _ensure_app_active() + script = ''' +tell application "System Events" + tell process "WebPomodoro" + set wins to windows + if (count of wins) > 0 then + set win1 to item 1 of wins + repeat with b in buttons of win1 + try + set desc to description of b + if desc contains "暂停" or desc contains "pause" or desc contains "Pause" then + click b + return "clicked: " & desc + end if + end try + end repeat + end if + end tell +end tell +return "no pause button found"''' + try: + result = _applescript(script) + return {"action": "pause", "result": result} + except Exception as e: + return {"action": "pause", "error": str(e)} + + +def stop_timer() -> dict: + """Stop/reset the current timer.""" + _ensure_app_active() + script = ''' +tell application "System Events" + tell process "WebPomodoro" + set wins to windows + if (count of wins) > 0 then + set win1 to item 1 of wins + repeat with b in buttons of win1 + try + set desc to description of b + if desc contains "停止" or desc contains "stop" or desc contains "Stop" or desc contains "reset" then + click b + return "clicked: " & desc + end if + end try + end repeat + end if + end tell +end tell +return "no stop button found"''' + try: + result = _applescript(script) + return {"action": "stop", "result": result} + except Exception as e: + return {"action": "stop", "error": str(e)} + + +def start_break() -> dict: + """Start a break session.""" + _ensure_app_active() + script = ''' +tell application "System Events" + tell process "WebPomodoro" + set wins to windows + if (count of wins) > 0 then + set win1 to item 1 of wins + repeat with b in buttons of win1 + try + set desc to description of b + if desc contains "休息" or desc contains "break" or desc contains "Break" or desc contains "rest" then + click b + return "clicked: " & desc + end if + end try + end repeat + end if + end tell +end tell +return "no break button found"''' + try: + result = _applescript(script) + return {"action": "start_break", "result": result} + except Exception as e: + return {"action": "start_break", "error": str(e)} + + +def get_status_label() -> str: + """Get current timer label from menu bar (e.g. '24:30' or '专注中').""" + script = ''' +tell application "System Events" + tell process "WebPomodoro" + return name of menu bar item 1 of menu bar 2 + end tell +end tell''' + try: + return _applescript(script) + except Exception: + return "unknown" + + +def list_ui_elements() -> str: + """Debug: list all UI elements in the main window.""" + script = ''' +tell application "System Events" + tell process "WebPomodoro" + set result_list to {} + set wins to windows + if (count of wins) > 0 then + set win1 to item 1 of wins + -- buttons + repeat with b in buttons of win1 + try + set end of result_list to "BTN: " & description of b + end try + end repeat + -- static texts + repeat with st in static texts of win1 + try + set t to value of st + if t is not missing value and t is not "" then + set end of result_list to "TXT: " & t + end if + end try + end repeat + end if + return result_list + end tell +end tell''' + try: + return _applescript(script) + except Exception as e: + return f"error: {e}" diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/tests/__init__.py b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/tests/test_core.py b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/tests/test_core.py new file mode 100644 index 00000000..1874ccb5 --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/tests/test_core.py @@ -0,0 +1,163 @@ +""" +Unit tests for cli-anything-webpomodoro core modules. +Tests use real LocalStorage data (read-only, no side effects). +""" +import pytest +from click.testing import CliRunner + + +# ── Backend unit tests ────────────────────────────────────────────────────── + +class TestBackend: + def test_localstorage_readable(self): + from cli_anything.webpomodoro.utils.webpomodoro_backend import read_localstorage + ls = read_localstorage() + assert isinstance(ls, dict) + # Should have at least Version key + assert "Version" in ls or len(ls) == 0 # empty if app never ran + + def test_version_is_string(self): + from cli_anything.webpomodoro.utils.webpomodoro_backend import read_localstorage + ls = read_localstorage() + if "Version" in ls: + assert isinstance(ls["Version"], str) + + def test_timing_task_id_format(self): + from cli_anything.webpomodoro.utils.webpomodoro_backend import read_localstorage + ls = read_localstorage() + tid = ls.get("timingTaskId", "") + # If set, should look like a UUID + if tid: + assert len(tid) >= 30 + + def test_is_running_returns_bool(self): + from cli_anything.webpomodoro.utils.webpomodoro_backend import is_running + result = is_running() + assert isinstance(result, bool) + + def test_timer_label_is_string(self): + from cli_anything.webpomodoro.utils.webpomodoro_backend import get_timer_label, is_running + if is_running(): + label = get_timer_label() + assert isinstance(label, str) + assert len(label) > 0 + + def test_get_timer_state_keys(self): + from cli_anything.webpomodoro.utils.webpomodoro_backend import get_timer_state + state = get_timer_state() + assert "label" in state + assert "timingTaskId" in state + assert "user" in state + assert "email" in state + + def test_user_email_present(self): + from cli_anything.webpomodoro.utils.webpomodoro_backend import get_timer_state + state = get_timer_state() + email = state.get("email", "") + if email: + assert "@" in email + + +# ── Data layer unit tests ──────────────────────────────────────────────────── + +class TestDataLayer: + def test_get_user_info(self): + from cli_anything.webpomodoro.core.data import get_user_info + info = get_user_info() + assert isinstance(info, dict) + assert "name" in info + assert "email" in info + assert "appVersion" in info + + def test_get_goals(self): + from cli_anything.webpomodoro.core.data import get_goals + goals = get_goals() + assert isinstance(goals, list) + + def test_pomodoro_count_is_int(self): + from cli_anything.webpomodoro.utils.webpomodoro_backend import count_today_pomodoros + count = count_today_pomodoros() + assert isinstance(count, int) + assert count >= 0 + + def test_get_full_status_structure(self): + from cli_anything.webpomodoro.core.data import get_full_status + status = get_full_status() + assert "timer" in status + assert "user" in status + assert "goals" in status + assert "totalPomodoros" in status + + def test_read_tasks_returns_list(self): + from cli_anything.webpomodoro.utils.webpomodoro_backend import read_tasks + tasks = read_tasks(limit=5) + assert isinstance(tasks, list) + + def test_read_pomodoros_returns_list(self): + from cli_anything.webpomodoro.utils.webpomodoro_backend import read_pomodoro_records + records = read_pomodoro_records(limit=5) + assert isinstance(records, list) + + +# ── CLI command tests ───────────────────────────────────────────────────────── + +class TestCLICommands: + def setup_method(self): + self.runner = CliRunner() + + def test_cli_help(self): + from cli_anything.webpomodoro.webpomodoro_cli import cli + result = self.runner.invoke(cli, ["--help"]) + assert result.exit_code == 0 + assert "timer" in result.output + assert "task" in result.output + assert "session" in result.output + + def test_timer_help(self): + from cli_anything.webpomodoro.webpomodoro_cli import cli + result = self.runner.invoke(cli, ["timer", "--help"]) + assert result.exit_code == 0 + assert "status" in result.output + assert "start" in result.output + assert "pause" in result.output + + def test_timer_status_runs(self): + from cli_anything.webpomodoro.webpomodoro_cli import cli + result = self.runner.invoke(cli, ["timer", "status"]) + assert result.exit_code == 0 + + def test_timer_status_json(self): + from cli_anything.webpomodoro.webpomodoro_cli import cli + import json + result = self.runner.invoke(cli, ["timer", "status", "--json"]) + assert result.exit_code == 0 + data = json.loads(result.output) + assert "label" in data or "running" in data + + def test_session_today_runs(self): + from cli_anything.webpomodoro.webpomodoro_cli import cli + result = self.runner.invoke(cli, ["session", "today"]) + assert result.exit_code == 0 + + def test_data_settings_runs(self): + from cli_anything.webpomodoro.webpomodoro_cli import cli + result = self.runner.invoke(cli, ["data", "settings"]) + assert result.exit_code == 0 + + def test_data_goals_runs(self): + from cli_anything.webpomodoro.webpomodoro_cli import cli + result = self.runner.invoke(cli, ["data", "goals"]) + assert result.exit_code == 0 + + def test_task_list_runs(self): + from cli_anything.webpomodoro.webpomodoro_cli import cli + result = self.runner.invoke(cli, ["task", "list", "--limit", "5"]) + assert result.exit_code == 0 + + def test_session_history_json(self): + from cli_anything.webpomodoro.webpomodoro_cli import cli + import json + result = self.runner.invoke(cli, ["session", "history", "--json"]) + assert result.exit_code == 0 + data = json.loads(result.output) + assert isinstance(data, list) diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/utils/__init__.py b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/utils/repl_skin.py b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/utils/repl_skin.py new file mode 100644 index 00000000..47260beb --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/utils/repl_skin.py @@ -0,0 +1,498 @@ +"""cli-anything REPL Skin — Unified terminal interface for all CLI harnesses. + +Copy this file into your CLI package at: + cli_anything//utils/repl_skin.py + +Usage: + from cli_anything..utils.repl_skin import ReplSkin + + skin = ReplSkin("shotcut", version="1.0.0") + skin.print_banner() + prompt_text = skin.prompt(project_name="my_video.mlt", modified=True) + skin.success("Project saved") + skin.error("File not found") + skin.warning("Unsaved changes") + skin.info("Processing 24 clips...") + skin.status("Track 1", "3 clips, 00:02:30") + skin.table(headers, rows) + skin.print_goodbye() +""" + +import os +import sys + +# ── ANSI color codes (no external deps for core styling) ────────────── + +_RESET = "\033[0m" +_BOLD = "\033[1m" +_DIM = "\033[2m" +_ITALIC = "\033[3m" +_UNDERLINE = "\033[4m" + +# Brand colors +_CYAN = "\033[38;5;80m" # cli-anything brand cyan +_CYAN_BG = "\033[48;5;80m" +_WHITE = "\033[97m" +_GRAY = "\033[38;5;245m" +_DARK_GRAY = "\033[38;5;240m" +_LIGHT_GRAY = "\033[38;5;250m" + +# Software accent colors — each software gets a unique accent +_ACCENT_COLORS = { + "gimp": "\033[38;5;214m", # warm orange + "blender": "\033[38;5;208m", # deep orange + "inkscape": "\033[38;5;39m", # bright blue + "audacity": "\033[38;5;33m", # navy blue + "libreoffice": "\033[38;5;40m", # green + "obs_studio": "\033[38;5;55m", # purple + "kdenlive": "\033[38;5;69m", # slate blue + "shotcut": "\033[38;5;35m", # teal green +} +_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue + +# Status colors +_GREEN = "\033[38;5;78m" +_YELLOW = "\033[38;5;220m" +_RED = "\033[38;5;196m" +_BLUE = "\033[38;5;75m" +_MAGENTA = "\033[38;5;176m" + +# ── Brand icon ──────────────────────────────────────────────────────── + +# The cli-anything icon: a small colored diamond/chevron mark +_ICON = f"{_CYAN}{_BOLD}◆{_RESET}" +_ICON_SMALL = f"{_CYAN}▸{_RESET}" + +# ── Box drawing characters ──────────────────────────────────────────── + +_H_LINE = "─" +_V_LINE = "│" +_TL = "╭" +_TR = "╮" +_BL = "╰" +_BR = "╯" +_T_DOWN = "┬" +_T_UP = "┴" +_T_RIGHT = "├" +_T_LEFT = "┤" +_CROSS = "┼" + + +def _strip_ansi(text: str) -> str: + """Remove ANSI escape codes for length calculation.""" + import re + return re.sub(r"\033\[[^m]*m", "", text) + + +def _visible_len(text: str) -> int: + """Get visible length of text (excluding ANSI codes).""" + return len(_strip_ansi(text)) + + +class ReplSkin: + """Unified REPL skin for cli-anything CLIs. + + Provides consistent branding, prompts, and message formatting + across all CLI harnesses built with the cli-anything methodology. + """ + + def __init__(self, software: str, version: str = "1.0.0", + history_file: str | None = None): + """Initialize the REPL skin. + + Args: + software: Software name (e.g., "gimp", "shotcut", "blender"). + version: CLI version string. + history_file: Path for persistent command history. + Defaults to ~/.cli-anything-/history + """ + self.software = software.lower().replace("-", "_") + self.display_name = software.replace("_", " ").title() + self.version = version + self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT) + + # History file + if history_file is None: + from pathlib import Path + hist_dir = Path.home() / f".cli-anything-{self.software}" + hist_dir.mkdir(parents=True, exist_ok=True) + self.history_file = str(hist_dir / "history") + else: + self.history_file = history_file + + # Detect terminal capabilities + self._color = self._detect_color_support() + + def _detect_color_support(self) -> bool: + """Check if terminal supports color.""" + if os.environ.get("NO_COLOR"): + return False + if os.environ.get("CLI_ANYTHING_NO_COLOR"): + return False + if not hasattr(sys.stdout, "isatty"): + return False + return sys.stdout.isatty() + + def _c(self, code: str, text: str) -> str: + """Apply color code if colors are supported.""" + if not self._color: + return text + return f"{code}{text}{_RESET}" + + # ── Banner ──────────────────────────────────────────────────────── + + def print_banner(self): + """Print the startup banner with branding.""" + inner = 54 + + def _box_line(content: str) -> str: + """Wrap content in box drawing, padding to inner width.""" + pad = inner - _visible_len(content) + vl = self._c(_DARK_GRAY, _V_LINE) + return f"{vl}{content}{' ' * max(0, pad)}{vl}" + + top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}") + bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}") + + # Title: ◆ cli-anything · Shotcut + icon = self._c(_CYAN + _BOLD, "◆") + brand = self._c(_CYAN + _BOLD, "cli-anything") + dot = self._c(_DARK_GRAY, "·") + name = self._c(self.accent + _BOLD, self.display_name) + title = f" {icon} {brand} {dot} {name}" + + ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}" + tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}" + empty = "" + + print(top) + print(_box_line(title)) + print(_box_line(ver)) + print(_box_line(empty)) + print(_box_line(tip)) + print(bot) + print() + + # ── Prompt ──────────────────────────────────────────────────────── + + def prompt(self, project_name: str = "", modified: bool = False, + context: str = "") -> str: + """Build a styled prompt string for prompt_toolkit or input(). + + Args: + project_name: Current project name (empty if none open). + modified: Whether the project has unsaved changes. + context: Optional extra context to show in prompt. + + Returns: + Formatted prompt string. + """ + parts = [] + + # Icon + if self._color: + parts.append(f"{_CYAN}◆{_RESET} ") + else: + parts.append("> ") + + # Software name + parts.append(self._c(self.accent + _BOLD, self.software)) + + # Project context + if project_name or context: + ctx = context or project_name + mod = "*" if modified else "" + parts.append(f" {self._c(_DARK_GRAY, '[')}") + parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}")) + parts.append(self._c(_DARK_GRAY, ']')) + + parts.append(self._c(_GRAY, " ❯ ")) + + return "".join(parts) + + def prompt_tokens(self, project_name: str = "", modified: bool = False, + context: str = ""): + """Build prompt_toolkit formatted text tokens for the prompt. + + Use with prompt_toolkit's FormattedText for proper ANSI handling. + + Returns: + list of (style, text) tuples for prompt_toolkit. + """ + accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff") + tokens = [] + + tokens.append(("class:icon", "◆ ")) + tokens.append(("class:software", self.software)) + + if project_name or context: + ctx = context or project_name + mod = "*" if modified else "" + tokens.append(("class:bracket", " [")) + tokens.append(("class:context", f"{ctx}{mod}")) + tokens.append(("class:bracket", "]")) + + tokens.append(("class:arrow", " ❯ ")) + + return tokens + + def get_prompt_style(self): + """Get a prompt_toolkit Style object matching the skin. + + Returns: + prompt_toolkit.styles.Style + """ + try: + from prompt_toolkit.styles import Style + except ImportError: + return None + + accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff") + + return Style.from_dict({ + "icon": "#5fdfdf bold", # cyan brand color + "software": f"{accent_hex} bold", + "bracket": "#585858", + "context": "#bcbcbc", + "arrow": "#808080", + # Completion menu + "completion-menu.completion": "bg:#303030 #bcbcbc", + "completion-menu.completion.current": f"bg:{accent_hex} #000000", + "completion-menu.meta.completion": "bg:#303030 #808080", + "completion-menu.meta.completion.current": f"bg:{accent_hex} #000000", + # Auto-suggest + "auto-suggest": "#585858", + # Bottom toolbar + "bottom-toolbar": "bg:#1c1c1c #808080", + "bottom-toolbar.text": "#808080", + }) + + # ── Messages ────────────────────────────────────────────────────── + + def success(self, message: str): + """Print a success message with green checkmark.""" + icon = self._c(_GREEN + _BOLD, "✓") + print(f" {icon} {self._c(_GREEN, message)}") + + def error(self, message: str): + """Print an error message with red cross.""" + icon = self._c(_RED + _BOLD, "✗") + print(f" {icon} {self._c(_RED, message)}", file=sys.stderr) + + def warning(self, message: str): + """Print a warning message with yellow triangle.""" + icon = self._c(_YELLOW + _BOLD, "⚠") + print(f" {icon} {self._c(_YELLOW, message)}") + + def info(self, message: str): + """Print an info message with blue dot.""" + icon = self._c(_BLUE, "●") + print(f" {icon} {self._c(_LIGHT_GRAY, message)}") + + def hint(self, message: str): + """Print a subtle hint message.""" + print(f" {self._c(_DARK_GRAY, message)}") + + def section(self, title: str): + """Print a section header.""" + print() + print(f" {self._c(self.accent + _BOLD, title)}") + print(f" {self._c(_DARK_GRAY, _H_LINE * len(title))}") + + # ── Status display ──────────────────────────────────────────────── + + def status(self, label: str, value: str): + """Print a key-value status line.""" + lbl = self._c(_GRAY, f" {label}:") + val = self._c(_WHITE, f" {value}") + print(f"{lbl}{val}") + + def status_block(self, items: dict[str, str], title: str = ""): + """Print a block of status key-value pairs. + + Args: + items: Dict of label -> value pairs. + title: Optional title for the block. + """ + if title: + self.section(title) + + max_key = max(len(k) for k in items) if items else 0 + for label, value in items.items(): + lbl = self._c(_GRAY, f" {label:<{max_key}}") + val = self._c(_WHITE, f" {value}") + print(f"{lbl}{val}") + + def progress(self, current: int, total: int, label: str = ""): + """Print a simple progress indicator. + + Args: + current: Current step number. + total: Total number of steps. + label: Optional label for the progress. + """ + pct = int(current / total * 100) if total > 0 else 0 + bar_width = 20 + filled = int(bar_width * current / total) if total > 0 else 0 + bar = "█" * filled + "░" * (bar_width - filled) + text = f" {self._c(_CYAN, bar)} {self._c(_GRAY, f'{pct:3d}%')}" + if label: + text += f" {self._c(_LIGHT_GRAY, label)}" + print(text) + + # ── Table display ───────────────────────────────────────────────── + + def table(self, headers: list[str], rows: list[list[str]], + max_col_width: int = 40): + """Print a formatted table with box-drawing characters. + + Args: + headers: Column header strings. + rows: List of rows, each a list of cell strings. + max_col_width: Maximum column width before truncation. + """ + if not headers: + return + + # Calculate column widths + col_widths = [min(len(h), max_col_width) for h in headers] + for row in rows: + for i, cell in enumerate(row): + if i < len(col_widths): + col_widths[i] = min( + max(col_widths[i], len(str(cell))), max_col_width + ) + + def pad(text: str, width: int) -> str: + t = str(text)[:width] + return t + " " * (width - len(t)) + + # Header + header_cells = [ + self._c(_CYAN + _BOLD, pad(h, col_widths[i])) + for i, h in enumerate(headers) + ] + sep = self._c(_DARK_GRAY, f" {_V_LINE} ") + header_line = f" {sep.join(header_cells)}" + print(header_line) + + # Separator + sep_parts = [self._c(_DARK_GRAY, _H_LINE * w) for w in col_widths] + sep_line = self._c(_DARK_GRAY, f" {'───'.join([_H_LINE * w for w in col_widths])}") + print(sep_line) + + # Rows + for row in rows: + cells = [] + for i, cell in enumerate(row): + if i < len(col_widths): + cells.append(self._c(_LIGHT_GRAY, pad(str(cell), col_widths[i]))) + row_sep = self._c(_DARK_GRAY, f" {_V_LINE} ") + print(f" {row_sep.join(cells)}") + + # ── Help display ────────────────────────────────────────────────── + + def help(self, commands: dict[str, str]): + """Print a formatted help listing. + + Args: + commands: Dict of command -> description pairs. + """ + self.section("Commands") + max_cmd = max(len(c) for c in commands) if commands else 0 + for cmd, desc in commands.items(): + cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}") + desc_styled = self._c(_GRAY, f" {desc}") + print(f"{cmd_styled}{desc_styled}") + print() + + # ── Goodbye ─────────────────────────────────────────────────────── + + def print_goodbye(self): + """Print a styled goodbye message.""" + print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n") + + # ── Prompt toolkit session factory ──────────────────────────────── + + def create_prompt_session(self): + """Create a prompt_toolkit PromptSession with skin styling. + + Returns: + A configured PromptSession, or None if prompt_toolkit unavailable. + """ + try: + from prompt_toolkit import PromptSession + from prompt_toolkit.history import FileHistory + from prompt_toolkit.auto_suggest import AutoSuggestFromHistory + from prompt_toolkit.formatted_text import FormattedText + + style = self.get_prompt_style() + + session = PromptSession( + history=FileHistory(self.history_file), + auto_suggest=AutoSuggestFromHistory(), + style=style, + enable_history_search=True, + ) + return session + except ImportError: + return None + + def get_input(self, pt_session, project_name: str = "", + modified: bool = False, context: str = "") -> str: + """Get input from user using prompt_toolkit or fallback. + + Args: + pt_session: A prompt_toolkit PromptSession (or None). + project_name: Current project name. + modified: Whether project has unsaved changes. + context: Optional context string. + + Returns: + User input string (stripped). + """ + if pt_session is not None: + from prompt_toolkit.formatted_text import FormattedText + tokens = self.prompt_tokens(project_name, modified, context) + return pt_session.prompt(FormattedText(tokens)).strip() + else: + raw_prompt = self.prompt(project_name, modified, context) + return input(raw_prompt).strip() + + # ── Toolbar builder ─────────────────────────────────────────────── + + def bottom_toolbar(self, items: dict[str, str]): + """Create a bottom toolbar callback for prompt_toolkit. + + Args: + items: Dict of label -> value pairs to show in toolbar. + + Returns: + A callable that returns FormattedText for the toolbar. + """ + def toolbar(): + from prompt_toolkit.formatted_text import FormattedText + parts = [] + for i, (k, v) in enumerate(items.items()): + if i > 0: + parts.append(("class:bottom-toolbar.text", " │ ")) + parts.append(("class:bottom-toolbar.text", f" {k}: ")) + parts.append(("class:bottom-toolbar", v)) + return FormattedText(parts) + return toolbar + + +# ── ANSI 256-color to hex mapping (for prompt_toolkit styles) ───────── + +_ANSI_256_TO_HEX = { + "\033[38;5;33m": "#0087ff", # audacity navy blue + "\033[38;5;35m": "#00af5f", # shotcut teal + "\033[38;5;39m": "#00afff", # inkscape bright blue + "\033[38;5;40m": "#00d700", # libreoffice green + "\033[38;5;55m": "#5f00af", # obs purple + "\033[38;5;69m": "#5f87ff", # kdenlive slate blue + "\033[38;5;75m": "#5fafff", # default sky blue + "\033[38;5;80m": "#5fd7d7", # brand cyan + "\033[38;5;208m": "#ff8700", # blender deep orange + "\033[38;5;214m": "#ffaf00", # gimp warm orange +} diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/webpomodoro_cli.py b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/webpomodoro_cli.py new file mode 100644 index 00000000..67366de2 --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything/webpomodoro/webpomodoro_cli.py @@ -0,0 +1,318 @@ +""" +cli-anything-webpomodoro — WebPomodoro macOS app CLI interface. + +Commands: + timer status Show current timer state + timer start Start focus session + timer pause Pause timer + timer stop Stop/reset timer + timer break Start break session + timer ui Show all UI elements (debug) + + task current Show currently tracked task + task list List recent tasks from IndexedDB + + session today Show today's session summary + session history List recent Pomodoro records + + data settings Show app settings and user info + data goals Show daily goals + + repl Interactive REPL mode (default) +""" +import click +import json +import sys + + +def _out(data, json_mode: bool) -> None: + if json_mode: + click.echo(json.dumps(data, ensure_ascii=False, indent=2)) + else: + if isinstance(data, dict): + for k, v in data.items(): + if isinstance(v, (dict, list)): + click.echo(f" {k}:") + click.echo(f" {json.dumps(v, ensure_ascii=False)}") + else: + click.echo(f" {k}: {v}") + elif isinstance(data, list): + for item in data: + click.echo(f" - {json.dumps(item, ensure_ascii=False)}") + else: + click.echo(str(data)) + + +# ── Root group ───────────────────────────────────────────────────────────── + +@click.group(invoke_without_command=True) +@click.option("--json", "json_mode", is_flag=True, help="Output as JSON") +@click.pass_context +def cli(ctx, json_mode): + """WebPomodoro CLI — control your Pomodoro timer from the command line. + + Run without subcommand to enter interactive REPL mode. + """ + ctx.ensure_object(dict) + ctx.obj["json"] = json_mode + if ctx.invoked_subcommand is None: + ctx.invoke(repl) + + +# ── timer group ──────────────────────────────────────────────────────────── + +@cli.group() +def timer(): + """Timer control commands.""" + pass + + +@timer.command("status") +@click.option("--json", "json_mode", is_flag=True) +def timer_status(json_mode): + """Show current timer state (label + tracked task ID).""" + from cli_anything.webpomodoro.utils.webpomodoro_backend import get_timer_state, is_running + running = is_running() + if not running: + data = {"running": False, "message": "WebPomodoro is not running"} + else: + data = get_timer_state() + data["running"] = True + _out(data, json_mode) + + +@timer.command("start") +@click.option("--json", "json_mode", is_flag=True) +def timer_start(json_mode): + """Start a focus/work session.""" + from cli_anything.webpomodoro.core.timer import start_work + result = start_work() + _out(result, json_mode) + if not json_mode: + label = __import__("cli_anything.webpomodoro.core.timer", + fromlist=["get_status_label"]).get_status_label() + click.echo(f" ▶ Timer: {label}") + + +@timer.command("pause") +@click.option("--json", "json_mode", is_flag=True) +def timer_pause(json_mode): + """Pause the current timer.""" + from cli_anything.webpomodoro.core.timer import pause_timer + result = pause_timer() + _out(result, json_mode) + + +@timer.command("stop") +@click.option("--json", "json_mode", is_flag=True) +def timer_stop(json_mode): + """Stop and reset the current timer.""" + from cli_anything.webpomodoro.core.timer import stop_timer + result = stop_timer() + _out(result, json_mode) + + +@timer.command("break") +@click.option("--json", "json_mode", is_flag=True) +def timer_break(json_mode): + """Start a break session.""" + from cli_anything.webpomodoro.core.timer import start_break + result = start_break() + _out(result, json_mode) + + +@timer.command("ui") +def timer_ui(): + """[Debug] List all UI elements in the main window.""" + from cli_anything.webpomodoro.core.timer import list_ui_elements + click.echo(list_ui_elements()) + + +# ── task group ───────────────────────────────────────────────────────────── + +@cli.group() +def task(): + """Task management commands.""" + pass + + +@task.command("current") +@click.option("--json", "json_mode", is_flag=True) +def task_current(json_mode): + """Show the currently tracked task.""" + from cli_anything.webpomodoro.core.data import get_current_task_info + data = get_current_task_info() + _out(data, json_mode) + + +@task.command("list") +@click.option("--limit", default=20, help="Number of tasks to show") +@click.option("--json", "json_mode", is_flag=True) +def task_list(limit, json_mode): + """List recent tasks from local database.""" + from cli_anything.webpomodoro.utils.webpomodoro_backend import read_tasks + tasks = read_tasks(limit=limit) + if json_mode: + click.echo(json.dumps(tasks, ensure_ascii=False, indent=2)) + else: + click.echo(f" Recent tasks ({len(tasks)}):") + for i, t in enumerate(tasks, 1): + task_id = t.get("id", "")[:36] + words = t.get("data", {}).get("_raw_words", []) + name_hint = " ".join(words[:4]) if words else "(binary data)" + click.echo(f" {i:2}. [{task_id}] {name_hint}") + + +# ── session group ────────────────────────────────────────────────────────── + +@cli.group() +def session(): + """Session statistics commands.""" + pass + + +@session.command("today") +@click.option("--json", "json_mode", is_flag=True) +def session_today(json_mode): + """Show today's focus session summary.""" + from cli_anything.webpomodoro.core.data import get_full_status + data = get_full_status() + if not json_mode: + click.echo(f"\n 🍅 WebPomodoro 状态") + click.echo(f" ─────────────────────────────") + timer_info = data.get("timer", {}) + click.echo(f" 计时器: {timer_info.get('label', 'unknown')}") + task_id = timer_info.get("timingTaskId", "") + click.echo(f" 当前任务: {task_id[:16]}..." if task_id else " 当前任务: 无") + user = data.get("user", {}) + click.echo(f" 用户: {user.get('name', '')} <{user.get('email', '')}>") + click.echo(f" 总番茄数: {data.get('totalPomodoros', 0)}") + goals = data.get("goals", []) + for g in goals: + click.echo(f" 每日目标: {g.get('display', '')}") + click.echo(f" 最后同步: {data.get('lastSync', '')}") + click.echo() + else: + click.echo(json.dumps(data, ensure_ascii=False, indent=2)) + + +@session.command("history") +@click.option("--limit", default=10, help="Number of records") +@click.option("--json", "json_mode", is_flag=True) +def session_history(limit, json_mode): + """Show recent Pomodoro session records.""" + from cli_anything.webpomodoro.core.data import get_recent_pomodoros + records = get_recent_pomodoros(limit=limit) + if json_mode: + click.echo(json.dumps(records, ensure_ascii=False, indent=2)) + else: + click.echo(f" Recent Pomodoro records ({len(records)}):") + for i, r in enumerate(records, 1): + rid = r.get("id", "")[:24] + click.echo(f" {i:2}. {rid}") + + +# ── data group ───────────────────────────────────────────────────────────── + +@cli.group() +def data(): + """Raw data access commands.""" + pass + + +@data.command("settings") +@click.option("--json", "json_mode", is_flag=True) +def data_settings(json_mode): + """Show app settings and logged-in user info.""" + from cli_anything.webpomodoro.core.data import get_user_info + info = get_user_info() + _out(info, json_mode) + + +@data.command("goals") +@click.option("--json", "json_mode", is_flag=True) +def data_goals(json_mode): + """Show daily goals configuration.""" + from cli_anything.webpomodoro.core.data import get_goals + goals = get_goals() + _out(goals, json_mode) + + +@data.command("localstorage") +@click.option("--json", "json_mode", is_flag=True) +def data_localstorage(json_mode): + """Dump all LocalStorage key-value pairs.""" + from cli_anything.webpomodoro.utils.webpomodoro_backend import read_localstorage + ls = read_localstorage() + _out(ls, json_mode) + + +# ── REPL ─────────────────────────────────────────────────────────────────── + +@cli.command("repl") +def repl(): + """Start interactive REPL session.""" + try: + from cli_anything.webpomodoro.utils.repl_skin import ReplSkin + skin = ReplSkin("webpomodoro", version="1.0.0") + except Exception: + skin = None + + commands = { + "timer status": "Show current timer state", + "timer start": "Start focus session", + "timer pause": "Pause timer", + "timer stop": "Stop timer", + "timer break": "Start break", + "task current": "Show current task", + "task list": "List recent tasks", + "session today": "Today's summary", + "session history": "Recent records", + "data settings": "User & app info", + "data goals": "Daily goals", + "help": "Show this help", + "exit": "Exit REPL", + } + + if skin: + skin.print_banner() + skin.info("输入 'help' 查看所有命令,'exit' 退出") + else: + click.echo("🍅 WebPomodoro REPL — type 'help' or 'exit'") + + while True: + try: + if skin: + try: + from prompt_toolkit import PromptSession + pt = PromptSession() + line = pt.prompt("webpomodoro> ").strip() + except Exception: + line = input("webpomodoro> ").strip() + else: + line = input("webpomodoro> ").strip() + except (EOFError, KeyboardInterrupt): + click.echo("\nBye!") + break + + if not line: + continue + if line in ("exit", "quit", "q"): + click.echo("Bye!") + break + if line == "help": + for cmd, desc in commands.items(): + click.echo(f" {cmd:<20} {desc}") + continue + + # Map REPL commands to Click subcommands + args = line.split() + try: + cli.main(args, standalone_mode=False) + except SystemExit: + pass + except Exception as e: + if skin: + skin.error(str(e)) + else: + click.echo(f"Error: {e}") diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/PKG-INFO b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/PKG-INFO new file mode 100644 index 00000000..4365cd12 --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 2.4 +Name: cli-anything-webpomodoro +Version: 1.0.0 +Summary: CLI interface for WebPomodoro macOS app — Agent-native Pomodoro control +Requires-Python: >=3.10 +Requires-Dist: click>=8.0 +Requires-Dist: prompt_toolkit>=3.0 +Dynamic: requires-dist +Dynamic: requires-python +Dynamic: summary diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/SOURCES.txt b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/SOURCES.txt new file mode 100644 index 00000000..619f4042 --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/SOURCES.txt @@ -0,0 +1,17 @@ +setup.py +cli_anything/webpomodoro/__init__.py +cli_anything/webpomodoro/__main__.py +cli_anything/webpomodoro/webpomodoro_cli.py +cli_anything/webpomodoro/core/__init__.py +cli_anything/webpomodoro/core/data.py +cli_anything/webpomodoro/core/timer.py +cli_anything/webpomodoro/tests/__init__.py +cli_anything/webpomodoro/utils/__init__.py +cli_anything/webpomodoro/utils/repl_skin.py +cli_anything/webpomodoro/utils/webpomodoro_backend.py +cli_anything_webpomodoro.egg-info/PKG-INFO +cli_anything_webpomodoro.egg-info/SOURCES.txt +cli_anything_webpomodoro.egg-info/dependency_links.txt +cli_anything_webpomodoro.egg-info/entry_points.txt +cli_anything_webpomodoro.egg-info/requires.txt +cli_anything_webpomodoro.egg-info/top_level.txt \ No newline at end of file diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/dependency_links.txt b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/entry_points.txt b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/entry_points.txt new file mode 100644 index 00000000..6c427885 --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +cli-anything-webpomodoro = cli_anything.webpomodoro.webpomodoro_cli:cli diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/requires.txt b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/requires.txt new file mode 100644 index 00000000..3e440811 --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/requires.txt @@ -0,0 +1,2 @@ +click>=8.0 +prompt_toolkit>=3.0 diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/top_level.txt b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/top_level.txt new file mode 100644 index 00000000..25d3bea9 --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/cli_anything_webpomodoro.egg-info/top_level.txt @@ -0,0 +1 @@ +cli_anything diff --git a/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/setup.py b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/setup.py new file mode 100644 index 00000000..c6cfdb86 --- /dev/null +++ b/03_卡木(木)/木识_软件识形/CLI万能化/outputs/webpomodoro/agent-harness/setup.py @@ -0,0 +1,18 @@ +from setuptools import setup, find_namespace_packages + +setup( + name="cli-anything-webpomodoro", + version="1.0.0", + description="CLI interface for WebPomodoro macOS app — Agent-native Pomodoro control", + packages=find_namespace_packages(include=["cli_anything.*"]), + install_requires=[ + "click>=8.0", + "prompt_toolkit>=3.0", + ], + entry_points={ + "console_scripts": [ + "cli-anything-webpomodoro=cli_anything.webpomodoro.webpomodoro_cli:cli", + ] + }, + python_requires=">=3.10", +) diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index ebaab54e..e1ed327b 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -315,3 +315,4 @@ | 2026-03-12 22:33:45 | 🔄 卡若AI 同步 2026-03-12 22:33 | 更新:水桥平台对接、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-12 23:10:30 | 🔄 卡若AI 同步 2026-03-12 23:10 | 更新:水桥平台对接、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-12 23:12:15 | 🔄 卡若AI 同步 2026-03-12 23:12 | 更新:运营中枢、运营中枢工作台 | 排除 >20MB: 11 个 | +| 2026-03-12 23:20:58 | 🔄 卡若AI 同步 2026-03-12 23:20 | 更新:Cursor规则、水桥平台对接、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 88b84df8..4ceee3c7 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -318,3 +318,4 @@ | 2026-03-12 22:33:45 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-12 22:33 | 更新:水桥平台对接、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-12 23:10:30 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-12 23:10 | 更新:水桥平台对接、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-12 23:12:15 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-12 23:12 | 更新:运营中枢、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-03-12 23:20:58 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-12 23:20 | 更新:Cursor规则、水桥平台对接、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |