yee88 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- takopi/__init__.py +1 -0
- takopi/api.py +116 -0
- takopi/backends.py +25 -0
- takopi/backends_helpers.py +14 -0
- takopi/cli/__init__.py +228 -0
- takopi/cli/config.py +320 -0
- takopi/cli/doctor.py +173 -0
- takopi/cli/init.py +113 -0
- takopi/cli/onboarding_cmd.py +126 -0
- takopi/cli/plugins.py +196 -0
- takopi/cli/run.py +419 -0
- takopi/cli/topic.py +355 -0
- takopi/commands.py +134 -0
- takopi/config.py +142 -0
- takopi/config_migrations.py +124 -0
- takopi/config_watch.py +146 -0
- takopi/context.py +9 -0
- takopi/directives.py +146 -0
- takopi/engines.py +53 -0
- takopi/events.py +170 -0
- takopi/ids.py +17 -0
- takopi/lockfile.py +158 -0
- takopi/logging.py +283 -0
- takopi/markdown.py +298 -0
- takopi/model.py +77 -0
- takopi/plugins.py +312 -0
- takopi/presenter.py +25 -0
- takopi/progress.py +99 -0
- takopi/router.py +113 -0
- takopi/runner.py +712 -0
- takopi/runner_bridge.py +619 -0
- takopi/runners/__init__.py +1 -0
- takopi/runners/claude.py +483 -0
- takopi/runners/codex.py +656 -0
- takopi/runners/mock.py +221 -0
- takopi/runners/opencode.py +505 -0
- takopi/runners/pi.py +523 -0
- takopi/runners/run_options.py +39 -0
- takopi/runners/tool_actions.py +90 -0
- takopi/runtime_loader.py +207 -0
- takopi/scheduler.py +159 -0
- takopi/schemas/__init__.py +1 -0
- takopi/schemas/claude.py +238 -0
- takopi/schemas/codex.py +169 -0
- takopi/schemas/opencode.py +51 -0
- takopi/schemas/pi.py +117 -0
- takopi/settings.py +360 -0
- takopi/telegram/__init__.py +20 -0
- takopi/telegram/api_models.py +37 -0
- takopi/telegram/api_schemas.py +152 -0
- takopi/telegram/backend.py +163 -0
- takopi/telegram/bridge.py +425 -0
- takopi/telegram/chat_prefs.py +242 -0
- takopi/telegram/chat_sessions.py +112 -0
- takopi/telegram/client.py +409 -0
- takopi/telegram/client_api.py +539 -0
- takopi/telegram/commands/__init__.py +12 -0
- takopi/telegram/commands/agent.py +196 -0
- takopi/telegram/commands/cancel.py +116 -0
- takopi/telegram/commands/dispatch.py +111 -0
- takopi/telegram/commands/executor.py +449 -0
- takopi/telegram/commands/file_transfer.py +586 -0
- takopi/telegram/commands/handlers.py +45 -0
- takopi/telegram/commands/media.py +143 -0
- takopi/telegram/commands/menu.py +139 -0
- takopi/telegram/commands/model.py +215 -0
- takopi/telegram/commands/overrides.py +159 -0
- takopi/telegram/commands/parse.py +30 -0
- takopi/telegram/commands/plan.py +16 -0
- takopi/telegram/commands/reasoning.py +234 -0
- takopi/telegram/commands/reply.py +23 -0
- takopi/telegram/commands/topics.py +332 -0
- takopi/telegram/commands/trigger.py +143 -0
- takopi/telegram/context.py +140 -0
- takopi/telegram/engine_defaults.py +86 -0
- takopi/telegram/engine_overrides.py +105 -0
- takopi/telegram/files.py +178 -0
- takopi/telegram/loop.py +1822 -0
- takopi/telegram/onboarding.py +1088 -0
- takopi/telegram/outbox.py +177 -0
- takopi/telegram/parsing.py +239 -0
- takopi/telegram/render.py +198 -0
- takopi/telegram/state_store.py +88 -0
- takopi/telegram/topic_state.py +334 -0
- takopi/telegram/topics.py +256 -0
- takopi/telegram/trigger_mode.py +68 -0
- takopi/telegram/types.py +63 -0
- takopi/telegram/voice.py +110 -0
- takopi/transport.py +53 -0
- takopi/transport_runtime.py +323 -0
- takopi/transports.py +76 -0
- takopi/utils/__init__.py +1 -0
- takopi/utils/git.py +87 -0
- takopi/utils/json_state.py +21 -0
- takopi/utils/paths.py +47 -0
- takopi/utils/streams.py +44 -0
- takopi/utils/subprocess.py +86 -0
- takopi/worktrees.py +135 -0
- yee88-0.1.0.dist-info/METADATA +116 -0
- yee88-0.1.0.dist-info/RECORD +103 -0
- yee88-0.1.0.dist-info/WHEEL +4 -0
- yee88-0.1.0.dist-info/entry_points.txt +11 -0
- yee88-0.1.0.dist-info/licenses/LICENSE +21 -0
takopi/utils/git.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _run_git(
|
|
9
|
+
args: Sequence[str], *, cwd: Path
|
|
10
|
+
) -> subprocess.CompletedProcess[str] | None:
|
|
11
|
+
try:
|
|
12
|
+
return subprocess.run(
|
|
13
|
+
["git", *args],
|
|
14
|
+
cwd=cwd,
|
|
15
|
+
check=False,
|
|
16
|
+
text=True,
|
|
17
|
+
capture_output=True,
|
|
18
|
+
)
|
|
19
|
+
except FileNotFoundError:
|
|
20
|
+
return None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def git_run(
|
|
24
|
+
args: Sequence[str], *, cwd: Path
|
|
25
|
+
) -> subprocess.CompletedProcess[str] | None:
|
|
26
|
+
return _run_git(args, cwd=cwd)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def git_stdout(args: Sequence[str], *, cwd: Path) -> str | None:
|
|
30
|
+
result = _run_git(args, cwd=cwd)
|
|
31
|
+
if result is None or result.returncode != 0:
|
|
32
|
+
return None
|
|
33
|
+
output = result.stdout.strip()
|
|
34
|
+
return output or None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def git_ok(args: Sequence[str], *, cwd: Path) -> bool:
|
|
38
|
+
result = _run_git(args, cwd=cwd)
|
|
39
|
+
return result is not None and result.returncode == 0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def git_is_worktree(path: Path) -> bool:
|
|
43
|
+
top = git_stdout(
|
|
44
|
+
["rev-parse", "--path-format=absolute", "--show-toplevel"],
|
|
45
|
+
cwd=path,
|
|
46
|
+
)
|
|
47
|
+
if not top:
|
|
48
|
+
return False
|
|
49
|
+
return Path(top).resolve(strict=False) == path.resolve(strict=False)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def resolve_default_base(root: Path) -> str | None:
|
|
53
|
+
origin_head = git_stdout(
|
|
54
|
+
["symbolic-ref", "-q", "refs/remotes/origin/HEAD"],
|
|
55
|
+
cwd=root,
|
|
56
|
+
)
|
|
57
|
+
if origin_head:
|
|
58
|
+
prefix = "refs/remotes/origin/"
|
|
59
|
+
if origin_head.startswith(prefix):
|
|
60
|
+
name = origin_head[len(prefix) :].strip()
|
|
61
|
+
if name:
|
|
62
|
+
return name
|
|
63
|
+
|
|
64
|
+
current = git_stdout(["branch", "--show-current"], cwd=root)
|
|
65
|
+
if current:
|
|
66
|
+
return current
|
|
67
|
+
|
|
68
|
+
if git_ok(["show-ref", "--verify", "--quiet", "refs/heads/master"], cwd=root):
|
|
69
|
+
return "master"
|
|
70
|
+
if git_ok(["show-ref", "--verify", "--quiet", "refs/heads/main"], cwd=root):
|
|
71
|
+
return "main"
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def resolve_main_worktree_root(cwd: Path) -> Path | None:
|
|
76
|
+
common_dir = git_stdout(
|
|
77
|
+
["rev-parse", "--path-format=absolute", "--git-common-dir"],
|
|
78
|
+
cwd=cwd,
|
|
79
|
+
)
|
|
80
|
+
if not common_dir:
|
|
81
|
+
return None
|
|
82
|
+
if git_stdout(["rev-parse", "--is-bare-repository"], cwd=cwd) == "true":
|
|
83
|
+
return cwd
|
|
84
|
+
common_path = Path(common_dir)
|
|
85
|
+
if not common_path.is_absolute():
|
|
86
|
+
common_path = (cwd / common_path).resolve()
|
|
87
|
+
return common_path.parent
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def atomic_write_json(
|
|
10
|
+
path: Path,
|
|
11
|
+
payload: Any,
|
|
12
|
+
*,
|
|
13
|
+
indent: int = 2,
|
|
14
|
+
sort_keys: bool = True,
|
|
15
|
+
) -> None:
|
|
16
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
17
|
+
tmp_path = path.with_suffix(f"{path.suffix}.tmp")
|
|
18
|
+
with open(tmp_path, "w", encoding="utf-8") as handle:
|
|
19
|
+
json.dump(payload, handle, indent=indent, sort_keys=sort_keys)
|
|
20
|
+
handle.write("\n")
|
|
21
|
+
os.replace(tmp_path, path)
|
takopi/utils/paths.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from contextvars import ContextVar, Token
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
_run_base_dir: ContextVar[Path | None] = ContextVar("takopi_run_base_dir", default=None)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_run_base_dir() -> Path | None:
|
|
12
|
+
return _run_base_dir.get()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def set_run_base_dir(base_dir: Path | None) -> Token[Path | None]:
|
|
16
|
+
return _run_base_dir.set(base_dir)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def reset_run_base_dir(token: Token[Path | None]) -> None:
|
|
20
|
+
_run_base_dir.reset(token)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def relativize_path(value: str, *, base_dir: Path | None = None) -> str:
|
|
24
|
+
if not value:
|
|
25
|
+
return value
|
|
26
|
+
base = get_run_base_dir() if base_dir is None else base_dir
|
|
27
|
+
if base is None:
|
|
28
|
+
base = Path.cwd()
|
|
29
|
+
base_str = str(base)
|
|
30
|
+
if not base_str:
|
|
31
|
+
return value
|
|
32
|
+
if value == base_str:
|
|
33
|
+
return "."
|
|
34
|
+
for sep in (os.sep, "/"):
|
|
35
|
+
prefix = base_str if base_str.endswith(sep) else f"{base_str}{sep}"
|
|
36
|
+
if value.startswith(prefix):
|
|
37
|
+
suffix = value[len(prefix) :]
|
|
38
|
+
return suffix or "."
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def relativize_command(value: str, *, base_dir: Path | None = None) -> str:
|
|
43
|
+
base = get_run_base_dir() if base_dir is None else base_dir
|
|
44
|
+
if base is None:
|
|
45
|
+
base = Path.cwd()
|
|
46
|
+
base_with_sep = f"{base}{os.sep}"
|
|
47
|
+
return value.replace(base_with_sep, "")
|
takopi/utils/streams.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import AsyncIterator
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import anyio
|
|
8
|
+
from anyio.abc import ByteReceiveStream
|
|
9
|
+
from anyio.streams.buffered import BufferedByteReceiveStream
|
|
10
|
+
|
|
11
|
+
from ..logging import log_pipeline
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def iter_bytes_lines(stream: ByteReceiveStream) -> AsyncIterator[bytes]:
|
|
15
|
+
buffered = BufferedByteReceiveStream(stream)
|
|
16
|
+
while True:
|
|
17
|
+
try:
|
|
18
|
+
line = await buffered.receive_until(b"\n", sys.maxsize)
|
|
19
|
+
except anyio.IncompleteRead:
|
|
20
|
+
return
|
|
21
|
+
yield line
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def drain_stderr(
|
|
25
|
+
stream: ByteReceiveStream,
|
|
26
|
+
logger: Any,
|
|
27
|
+
tag: str,
|
|
28
|
+
) -> None:
|
|
29
|
+
try:
|
|
30
|
+
async for line in iter_bytes_lines(stream):
|
|
31
|
+
text = line.decode("utf-8", errors="replace")
|
|
32
|
+
log_pipeline(
|
|
33
|
+
logger,
|
|
34
|
+
"subprocess.stderr",
|
|
35
|
+
tag=tag,
|
|
36
|
+
line=text,
|
|
37
|
+
)
|
|
38
|
+
except Exception as exc: # noqa: BLE001
|
|
39
|
+
log_pipeline(
|
|
40
|
+
logger,
|
|
41
|
+
"subprocess.stderr.error",
|
|
42
|
+
tag=tag,
|
|
43
|
+
error=str(exc),
|
|
44
|
+
)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import signal
|
|
5
|
+
from collections.abc import AsyncIterator, Callable, Sequence
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import anyio
|
|
10
|
+
from anyio.abc import Process
|
|
11
|
+
|
|
12
|
+
from ..logging import get_logger
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def wait_for_process(proc: Process, timeout: float) -> bool:
|
|
18
|
+
with anyio.move_on_after(timeout) as scope:
|
|
19
|
+
await proc.wait()
|
|
20
|
+
return scope.cancel_called
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def terminate_process(proc: Process) -> None:
|
|
24
|
+
_signal_process(
|
|
25
|
+
proc,
|
|
26
|
+
signal.SIGTERM,
|
|
27
|
+
fallback=proc.terminate,
|
|
28
|
+
log_event="subprocess.terminate.failed",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def kill_process(proc: Process) -> None:
|
|
33
|
+
_signal_process(
|
|
34
|
+
proc,
|
|
35
|
+
signal.SIGKILL,
|
|
36
|
+
fallback=proc.kill,
|
|
37
|
+
log_event="subprocess.kill.failed",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _signal_process(
|
|
42
|
+
proc: Process,
|
|
43
|
+
sig: signal.Signals,
|
|
44
|
+
*,
|
|
45
|
+
fallback: Callable[[], None],
|
|
46
|
+
log_event: str,
|
|
47
|
+
) -> None:
|
|
48
|
+
if proc.returncode is not None:
|
|
49
|
+
return
|
|
50
|
+
if os.name == "posix" and proc.pid is not None:
|
|
51
|
+
try:
|
|
52
|
+
os.killpg(proc.pid, sig)
|
|
53
|
+
return
|
|
54
|
+
except ProcessLookupError:
|
|
55
|
+
return
|
|
56
|
+
except OSError as exc:
|
|
57
|
+
logger.debug(
|
|
58
|
+
log_event,
|
|
59
|
+
error=str(exc),
|
|
60
|
+
error_type=exc.__class__.__name__,
|
|
61
|
+
pid=proc.pid,
|
|
62
|
+
)
|
|
63
|
+
try:
|
|
64
|
+
fallback()
|
|
65
|
+
except ProcessLookupError:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@asynccontextmanager
|
|
70
|
+
async def manage_subprocess(
|
|
71
|
+
cmd: Sequence[str], **kwargs: Any
|
|
72
|
+
) -> AsyncIterator[Process]:
|
|
73
|
+
"""Ensure subprocesses receive SIGTERM, then SIGKILL after a 2s timeout."""
|
|
74
|
+
if os.name == "posix":
|
|
75
|
+
kwargs.setdefault("start_new_session", True)
|
|
76
|
+
proc = await anyio.open_process(cmd, **kwargs)
|
|
77
|
+
try:
|
|
78
|
+
yield proc
|
|
79
|
+
finally:
|
|
80
|
+
if proc.returncode is None:
|
|
81
|
+
with anyio.CancelScope(shield=True):
|
|
82
|
+
terminate_process(proc)
|
|
83
|
+
timed_out = await wait_for_process(proc, timeout=2.0)
|
|
84
|
+
if timed_out:
|
|
85
|
+
kill_process(proc)
|
|
86
|
+
await proc.wait()
|
takopi/worktrees.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from .config import ProjectConfig, ProjectsConfig
|
|
6
|
+
from .context import RunContext
|
|
7
|
+
from .utils.git import (
|
|
8
|
+
git_is_worktree,
|
|
9
|
+
git_ok,
|
|
10
|
+
git_run,
|
|
11
|
+
git_stdout,
|
|
12
|
+
resolve_default_base,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WorktreeError(RuntimeError):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def resolve_run_cwd(
|
|
21
|
+
context: RunContext | None,
|
|
22
|
+
*,
|
|
23
|
+
projects: ProjectsConfig,
|
|
24
|
+
) -> Path | None:
|
|
25
|
+
if context is None or context.project is None:
|
|
26
|
+
return None
|
|
27
|
+
project = projects.projects.get(context.project)
|
|
28
|
+
if project is None:
|
|
29
|
+
raise WorktreeError(f"unknown project {context.project!r}")
|
|
30
|
+
if context.branch is None:
|
|
31
|
+
return project.path
|
|
32
|
+
branch = _sanitize_branch(context.branch)
|
|
33
|
+
if _matches_project_branch(project.path, branch):
|
|
34
|
+
return project.path
|
|
35
|
+
return ensure_worktree(project, branch)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def ensure_worktree(project: ProjectConfig, branch: str) -> Path:
|
|
39
|
+
root = project.path
|
|
40
|
+
if not root.exists():
|
|
41
|
+
raise WorktreeError(f"project path not found: {root}")
|
|
42
|
+
|
|
43
|
+
branch = _sanitize_branch(branch)
|
|
44
|
+
worktrees_root = project.worktrees_root
|
|
45
|
+
worktree_path = worktrees_root / branch
|
|
46
|
+
_ensure_within_root(worktrees_root, worktree_path)
|
|
47
|
+
|
|
48
|
+
if worktree_path.exists():
|
|
49
|
+
if not git_is_worktree(worktree_path):
|
|
50
|
+
raise WorktreeError(f"{worktree_path} exists but is not a git worktree")
|
|
51
|
+
return worktree_path
|
|
52
|
+
|
|
53
|
+
worktrees_root.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
|
|
55
|
+
if git_ok(
|
|
56
|
+
["show-ref", "--verify", "--quiet", f"refs/heads/{branch}"],
|
|
57
|
+
cwd=root,
|
|
58
|
+
):
|
|
59
|
+
_git_worktree_add(root, worktree_path, branch)
|
|
60
|
+
return worktree_path
|
|
61
|
+
|
|
62
|
+
if git_ok(
|
|
63
|
+
["show-ref", "--verify", "--quiet", f"refs/remotes/origin/{branch}"],
|
|
64
|
+
cwd=root,
|
|
65
|
+
):
|
|
66
|
+
_git_worktree_add(
|
|
67
|
+
root,
|
|
68
|
+
worktree_path,
|
|
69
|
+
branch,
|
|
70
|
+
base_ref=f"origin/{branch}",
|
|
71
|
+
create_branch=True,
|
|
72
|
+
)
|
|
73
|
+
return worktree_path
|
|
74
|
+
|
|
75
|
+
base = project.worktree_base or resolve_default_base(root)
|
|
76
|
+
if not base:
|
|
77
|
+
raise WorktreeError("cannot determine base branch for new worktree")
|
|
78
|
+
|
|
79
|
+
_git_worktree_add(
|
|
80
|
+
root,
|
|
81
|
+
worktree_path,
|
|
82
|
+
branch,
|
|
83
|
+
base_ref=base,
|
|
84
|
+
create_branch=True,
|
|
85
|
+
)
|
|
86
|
+
return worktree_path
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _git_worktree_add(
|
|
90
|
+
root: Path,
|
|
91
|
+
worktree_path: Path,
|
|
92
|
+
branch: str,
|
|
93
|
+
*,
|
|
94
|
+
base_ref: str | None = None,
|
|
95
|
+
create_branch: bool = False,
|
|
96
|
+
) -> None:
|
|
97
|
+
if create_branch:
|
|
98
|
+
if not base_ref:
|
|
99
|
+
raise WorktreeError("missing base ref for worktree creation")
|
|
100
|
+
args = ["worktree", "add", "-b", branch, str(worktree_path), base_ref]
|
|
101
|
+
else:
|
|
102
|
+
args = ["worktree", "add", str(worktree_path), branch]
|
|
103
|
+
|
|
104
|
+
result = git_run(args, cwd=root)
|
|
105
|
+
if result is None:
|
|
106
|
+
raise WorktreeError("git not available on PATH")
|
|
107
|
+
if result.returncode != 0:
|
|
108
|
+
message = result.stderr.strip() or result.stdout.strip()
|
|
109
|
+
raise WorktreeError(message or "git worktree add failed")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _sanitize_branch(branch: str) -> str:
|
|
113
|
+
cleaned = branch.strip()
|
|
114
|
+
if not cleaned:
|
|
115
|
+
raise WorktreeError("branch name cannot be empty")
|
|
116
|
+
if cleaned.startswith("/"):
|
|
117
|
+
raise WorktreeError("branch name cannot start with '/'")
|
|
118
|
+
for part in Path(cleaned).parts:
|
|
119
|
+
if part == "..":
|
|
120
|
+
raise WorktreeError("branch name cannot contain '..'")
|
|
121
|
+
return cleaned
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _matches_project_branch(root: Path, branch: str) -> bool:
|
|
125
|
+
current = git_stdout(["branch", "--show-current"], cwd=root)
|
|
126
|
+
if not current:
|
|
127
|
+
return False
|
|
128
|
+
return current == branch
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _ensure_within_root(root: Path, path: Path) -> None:
|
|
132
|
+
root_resolved = root.resolve(strict=False)
|
|
133
|
+
path_resolved = path.resolve(strict=False)
|
|
134
|
+
if not path_resolved.is_relative_to(root_resolved):
|
|
135
|
+
raise WorktreeError("branch path escapes the worktrees directory")
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yee88
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Telegram bridge for Codex, Claude Code, and other agent CLIs.
|
|
5
|
+
Project-URL: Homepage, https://github.com/banteg/takopi
|
|
6
|
+
Project-URL: Documentation, https://takopi.dev/
|
|
7
|
+
Project-URL: Repository, https://github.com/banteg/takopi
|
|
8
|
+
Project-URL: Issues, https://github.com/banteg/takopi/issues
|
|
9
|
+
Author: yee.wang
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2025 banteg
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Operating System :: OS Independent
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
37
|
+
Requires-Python: >=3.14
|
|
38
|
+
Requires-Dist: anyio>=4.12.0
|
|
39
|
+
Requires-Dist: httpx>=0.28.1
|
|
40
|
+
Requires-Dist: markdown-it-py
|
|
41
|
+
Requires-Dist: msgspec>=0.20.0
|
|
42
|
+
Requires-Dist: openai>=2.15.0
|
|
43
|
+
Requires-Dist: pydantic-settings>=2.12.0
|
|
44
|
+
Requires-Dist: pydantic>=2.12.5
|
|
45
|
+
Requires-Dist: questionary>=2.1.1
|
|
46
|
+
Requires-Dist: rich>=14.2.0
|
|
47
|
+
Requires-Dist: structlog>=25.5.0
|
|
48
|
+
Requires-Dist: sulguk>=0.11.1
|
|
49
|
+
Requires-Dist: tomli-w>=1.2.0
|
|
50
|
+
Requires-Dist: typer>=0.21.0
|
|
51
|
+
Requires-Dist: watchfiles>=0.21.0
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# yee88
|
|
55
|
+
|
|
56
|
+
telegram bridge for codex, claude code, opencode, pi.
|
|
57
|
+
|
|
58
|
+
## quickstart
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
# install
|
|
62
|
+
uv tool install -U yee88
|
|
63
|
+
|
|
64
|
+
# first run - follow the setup wizard
|
|
65
|
+
yee88
|
|
66
|
+
|
|
67
|
+
# in any git repo - create a topic and start
|
|
68
|
+
cd ~/your-project
|
|
69
|
+
yee88 topic init
|
|
70
|
+
yee88
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## features
|
|
74
|
+
|
|
75
|
+
- projects and worktrees: work on multiple repos/branches simultaneously, branches are git worktrees
|
|
76
|
+
- stateless resume: continue in chat or copy the resume line to pick up in terminal
|
|
77
|
+
- progress streaming: commands, tools, file changes, elapsed time
|
|
78
|
+
- parallel runs across agent sessions, per-agent-session queue
|
|
79
|
+
- works with telegram features like voice notes and scheduled messages
|
|
80
|
+
- file transfer: send files to the repo or fetch files/dirs back
|
|
81
|
+
- group chats and topics: map group topics to repo/branch contexts
|
|
82
|
+
- works with existing anthropic and openai subscriptions
|
|
83
|
+
|
|
84
|
+
## requirements
|
|
85
|
+
|
|
86
|
+
`uv` for installation (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
|
|
87
|
+
|
|
88
|
+
python 3.14+ (`uv python install 3.14`)
|
|
89
|
+
|
|
90
|
+
at least one engine on PATH: `codex`, `claude`, `opencode`, or `pi`
|
|
91
|
+
|
|
92
|
+
## usage
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
cd ~/dev/happy-gadgets
|
|
96
|
+
yee88
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
send a message to your bot. prefix with `/codex`, `/claude`, `/opencode`, or `/pi` to pick an engine. reply to continue a thread.
|
|
100
|
+
|
|
101
|
+
register a project with `yee88 init happy-gadgets`, then target it from anywhere with `/happy-gadgets hard reset the timeline`.
|
|
102
|
+
|
|
103
|
+
mention a branch to run an agent in a dedicated worktree `/happy-gadgets @feat/memory-box freeze artifacts forever`.
|
|
104
|
+
|
|
105
|
+
inspect or update settings with `yee88 config list`, `yee88 config get`, and `yee88 config set`.
|
|
106
|
+
|
|
107
|
+
## plugins
|
|
108
|
+
|
|
109
|
+
takopi supports entrypoint-based plugins for engines, transports, and commands.
|
|
110
|
+
|
|
111
|
+
see [`docs/how-to/write-a-plugin.md`](docs/how-to/write-a-plugin.md) and [`docs/reference/plugin-api.md`](docs/reference/plugin-api.md).
|
|
112
|
+
|
|
113
|
+
## development
|
|
114
|
+
|
|
115
|
+
see [`docs/reference/specification.md`](docs/reference/specification.md) and [`docs/developing.md`](docs/developing.md).
|
|
116
|
+
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
takopi/__init__.py,sha256=GyaC3hhx285KqPcqukJ07gRSFkuES5T27aUtAwvYYRw,23
|
|
2
|
+
takopi/api.py,sha256=SnUoG0xnjgaSyqM9retJS9wd3axBEI8MCOkrBNz4w8A,2876
|
|
3
|
+
takopi/backends.py,sha256=80ScxPEse-BxVfK0zA6bAaorp2GpTpCWsgX3TTqmumI,543
|
|
4
|
+
takopi/backends_helpers.py,sha256=d84XilyOzVVCq5akygVbH85JbapjTaSUZKeGcAml15I,368
|
|
5
|
+
takopi/commands.py,sha256=DplCjFC8QQM2MYoblZS8HI6OngKmQLO2emxCi8NIOtY,3298
|
|
6
|
+
takopi/config.py,sha256=FqABa_fsTbI4-7rr82c89OhHMNlLYsUbr1mflD4yTXc,4307
|
|
7
|
+
takopi/config_migrations.py,sha256=H-wJZz0HcwmTcNCulXj1nFR8I2ilUQtjo5upMRLrPWc,3501
|
|
8
|
+
takopi/config_watch.py,sha256=GBu4KrlgKACWalPFK-7C3Qo1v1BiOYOiOtK5OUOqz9c,4390
|
|
9
|
+
takopi/context.py,sha256=2GXdWY8KpWbEARJNo34unLN8nqGBtiE72Z_mjwrJTUM,187
|
|
10
|
+
takopi/directives.py,sha256=28XG1f7yQs8GAgvPoy5g0pTe_aaVQeWFSD4-S5mC5oo,4583
|
|
11
|
+
takopi/engines.py,sha256=uXtGie-XkQDB1LROTjPlCkYKbCu7XgyTr0yGn837gN4,1646
|
|
12
|
+
takopi/events.py,sha256=1yo6l5D4xFreyPoU1d4L1ckaplPizxpFeJJevP8JDtk,4225
|
|
13
|
+
takopi/ids.py,sha256=uU2A7x61Xn3_Pj-YgY5-EruoOtwDSXOp2D-XduTqAEo,534
|
|
14
|
+
takopi/lockfile.py,sha256=xCSsxVQMqnCN2RWFkkDOVg1qm5D-Sb4AMKr6FdP0kbY,4162
|
|
15
|
+
takopi/logging.py,sha256=BylZsaQB4pNhAteTdsXGAl0_PUoY8JQ4EFCvOhuB5bo,8228
|
|
16
|
+
takopi/markdown.py,sha256=YCx81lxBp_fchx3SN4Z-0sXNQGY1YjZJWz0m_xtk-uM,9068
|
|
17
|
+
takopi/model.py,sha256=G8Aj2nyTM6_vmHh12vEXlvhNJHR1ZBpsPBKw-DboDbs,1695
|
|
18
|
+
takopi/plugins.py,sha256=ItXdxlbZaikJOmJiUWQLFS9OvfMvJVy4seOnwDoaMHE,9153
|
|
19
|
+
takopi/presenter.py,sha256=rhPIytd2ANoe5S6kGeVvTzsPSQzO5AEMBXK3F_zJEx4,513
|
|
20
|
+
takopi/progress.py,sha256=n5HmZcw1dSuDfhrOXiZhLXdGMMSJNFpfZ4NR_2-wOL4,3155
|
|
21
|
+
takopi/router.py,sha256=pZkFI7OsUYeqjS2JmnL4gmHSPbEg8WF7_mQhzV45BCE,3853
|
|
22
|
+
takopi/runner.py,sha256=kaXscM6WBXa8TwDhzEyf2am4V95hKXTzfb78F1l3how,21476
|
|
23
|
+
takopi/runner_bridge.py,sha256=XGHpxTvmmk-DfiZCgNztXwboJ5m5z9GPKRK_8MxbRWA,19103
|
|
24
|
+
takopi/runtime_loader.py,sha256=i-4IeZqcez1VxWqfXsgmHhakK5g53ecye1kW46Uzc_c,6242
|
|
25
|
+
takopi/scheduler.py,sha256=ss_90rpZ3aKh_GutKhMwn8KWpg5QykkE2r6-ltimExk,5590
|
|
26
|
+
takopi/settings.py,sha256=uZcTzWdbX65Iy7wgLBPHiE_MalpSamWlOfqBJNE6De8,12064
|
|
27
|
+
takopi/transport.py,sha256=hzVJJO4mk1CaXgzsxSiMDqYA8xKMPD-SSQKhHDnwlzE,1330
|
|
28
|
+
takopi/transport_runtime.py,sha256=QdPEJ2rCeCsbRD_-77ZG74sRa2ws3WLAXptPPgssw2c,10260
|
|
29
|
+
takopi/transports.py,sha256=8igVsWgvx4d743SE98LyNuKYaci0-X61IqNiO4U-_Ck,2013
|
|
30
|
+
takopi/worktrees.py,sha256=KDaT1S0-kQH5v0FmiWgG51jgGviyATVEgfoqRrLmh7Y,3907
|
|
31
|
+
takopi/cli/__init__.py,sha256=YcM5Z_Lavn5PbuDH_0y6o6Fc-0Ca56SBec6XwKhJPLI,6442
|
|
32
|
+
takopi/cli/config.py,sha256=KXofbUieznU4OU0O-Atq57lPrigGmwmWGq27DC0EwmA,9563
|
|
33
|
+
takopi/cli/doctor.py,sha256=YrSRvHvI2Vew5Dw33u3D2OX4SIKKhQ1EDyj5cYQfcPc,6003
|
|
34
|
+
takopi/cli/init.py,sha256=y1Z08rh9t4RPhzsgJRtopgd6UvjEPYi9Wfv_t2KlrxI,3761
|
|
35
|
+
takopi/cli/onboarding_cmd.py,sha256=nj7mj2UuYQ5j0ZryFvb8INn4wDcOQU2_tWGZCcOnSKc,4172
|
|
36
|
+
takopi/cli/plugins.py,sha256=Kadu-EeX_UO9hSgloo7od6gmCzJqpyBX4E0skf_qFno,6320
|
|
37
|
+
takopi/cli/run.py,sha256=2KtKoOT1LGEKlizr8MYaCgxX5j3O7jlfPPgnU1UkSCE,13919
|
|
38
|
+
takopi/cli/topic.py,sha256=VPDCc_P5dFUa-FkvwZK7jEVVR9jHqVXmFwzYemTRMW8,11024
|
|
39
|
+
takopi/runners/__init__.py,sha256=McKaMqLXT9dJlgiEwKf6biD0Ns66Fk7SrxwtcP0ZgzI,30
|
|
40
|
+
takopi/runners/claude.py,sha256=-s88BOSTWxp4e7r6QJgUV3RJsEMEfSjk0Z9OmnZQB9Y,15296
|
|
41
|
+
takopi/runners/codex.py,sha256=oen08lhgaDdEn52x-GDcL7m4Hiaq9Vz3kb4b8KXh3yc,20956
|
|
42
|
+
takopi/runners/mock.py,sha256=zKnxNeFkTWyWXe35wUkm3hwf1vOi_eTXfNwIxaVWIjw,6617
|
|
43
|
+
takopi/runners/opencode.py,sha256=QEvpSSRtP5Uv-kzEp6D-85o2II886IHsEV-Us8vqsjA,15018
|
|
44
|
+
takopi/runners/pi.py,sha256=PH8xw_IBZzDo1KQ6AUQEPhqVRo-kb5I2OVKJrIuJq0w,16087
|
|
45
|
+
takopi/runners/run_options.py,sha256=ZjEm2baRwyJjRGCDTegnNreBA1qzQkaBJO_q4ZSnSw8,916
|
|
46
|
+
takopi/runners/tool_actions.py,sha256=Gcfhl_-W8WtIKwM2d1pkTVnVuk3h310eSD-_DvKfqOc,2732
|
|
47
|
+
takopi/schemas/__init__.py,sha256=gt6BRlYw2nKYLKkidkTDdK9RTmDEEDDpSi3LIr_pccw,46
|
|
48
|
+
takopi/schemas/claude.py,sha256=HqOik1O4u3WcMb4RN2cTVJw6VRYn3EaYj8h5Sevs1XY,5702
|
|
49
|
+
takopi/schemas/codex.py,sha256=bgIsh35LuaqoOYdTl6BWR-mn-mvh3ouwes_Jn9JMVXg,3412
|
|
50
|
+
takopi/schemas/opencode.py,sha256=ODhnKXTzxZ_8qaQ7AYqXB7J-XoAjQnXbGMBVTUEM2qY,1175
|
|
51
|
+
takopi/schemas/pi.py,sha256=e5ETawxk8jrdJbEbeBI_pWQKeCFiBBZLEF-Wo5Fx0XY,2680
|
|
52
|
+
takopi/telegram/__init__.py,sha256=hX_Wvu920dNLTDrKlj0fsZFSewOo-_otN26O1UNPNA4,452
|
|
53
|
+
takopi/telegram/api_models.py,sha256=d3H4o2sRZuFgfZfxF1Y1z_xzzCoOy3bKhMeKggYCW3w,538
|
|
54
|
+
takopi/telegram/api_schemas.py,sha256=Hvym_s2iPEaJmviCRGrLy8JarbcOburdE7aga2XnV_8,3824
|
|
55
|
+
takopi/telegram/backend.py,sha256=lpOGSeYSUY_PuUy4LmWcKf4T76m8f_aYYb49GIglPJA,5783
|
|
56
|
+
takopi/telegram/bridge.py,sha256=UQ4tqYdH8yEme5K6VGTOPaMKo2zIP0hAyJ6ev80izAA,13540
|
|
57
|
+
takopi/telegram/chat_prefs.py,sha256=tH7dXIehYHdemN_JOMhB7VuuFOiSBcjKz3FXpfW77Dk,8441
|
|
58
|
+
takopi/telegram/chat_sessions.py,sha256=7uL5bGX5BaQbQYTEpkWZVMh6HIImWdR_pPbqf6C4k0A,3736
|
|
59
|
+
takopi/telegram/client.py,sha256=GqNSCGA0cICaOC3E-qX7E4pcUTeP8edXS9HILYZNZgU,12800
|
|
60
|
+
takopi/telegram/client_api.py,sha256=6gDCrPQSBHHBhyiXX9cCwvNuQChsFXh0z8BvCgGD8Es,17692
|
|
61
|
+
takopi/telegram/context.py,sha256=owY5ZH2iLlS6kGS0DLPLAwNXkbcn34EfdzpWa9eSVR0,4485
|
|
62
|
+
takopi/telegram/engine_defaults.py,sha256=n6ROkTmP_s-H5AhPz_OdT62oZf0QtZJyFEDjp5gfub4,2594
|
|
63
|
+
takopi/telegram/engine_overrides.py,sha256=kv2j102VP-Bqzbutd5ApBkjW3LmVwvCYixsFewVXVeY,3122
|
|
64
|
+
takopi/telegram/files.py,sha256=Rc_wYdKHDC4hawL7pSuKznPbLiNnhPP4Lp0uGNGESv0,5054
|
|
65
|
+
takopi/telegram/loop.py,sha256=ytp6HGoHxnXOhA-yg2bid0wZmxM15cnAK-RSx4qy_Ak,66912
|
|
66
|
+
takopi/telegram/onboarding.py,sha256=YdrjSwZoebwgDPmuEhpqW0dmXJSHbStBOZxnmrVSMzc,34218
|
|
67
|
+
takopi/telegram/outbox.py,sha256=OcoRyQ7zmQCXR8ZXEMK2f_7-UMRVRAbBgmJGS1u_lcU,5939
|
|
68
|
+
takopi/telegram/parsing.py,sha256=5PvIPns1NnKryt3XNxPCp4BpWX1gI8kjKi4VxcQ0W-Q,7429
|
|
69
|
+
takopi/telegram/render.py,sha256=tZuKUeiG6qBb7S_6Y90MBSuSFRUHVbcL-mXWC1r9fHA,6017
|
|
70
|
+
takopi/telegram/state_store.py,sha256=bo0D0dJvdPIbzb7DE7HELbwnJ-VEw2p8AsPgj99an14,2551
|
|
71
|
+
takopi/telegram/topic_state.py,sha256=I8V6p50Hf7fGMKLCVNMXkqaKI46Yvm80Q5l4LaVHrYU,11444
|
|
72
|
+
takopi/telegram/topics.py,sha256=TP9cFkWvbZXaJ_41Gmwv2vO6vmim1c_RkrtMkQ9RiQE,8129
|
|
73
|
+
takopi/telegram/trigger_mode.py,sha256=4wFjNbEANn45lpctO9uBhjXTGh1K3LOUKWlBEA6m6Ps,2070
|
|
74
|
+
takopi/telegram/types.py,sha256=7MQCR0_k1zDtF32h3VoRxPZM7IZe9K6nCcy0xW11Lus,1540
|
|
75
|
+
takopi/telegram/voice.py,sha256=6GsqgVXSCUCXupyS6S235BXXwTp4gSLmYQLBYWAGdfc,3312
|
|
76
|
+
takopi/telegram/commands/__init__.py,sha256=lZfpONZyhgsc7L02B6ZS4TWyhTShWNxIyfRQLIKvvo8,285
|
|
77
|
+
takopi/telegram/commands/agent.py,sha256=Dtd8N0f3JyJM3gTb1CxQ8-0ez_ZR4DtrnrO9SpKBBQU,6969
|
|
78
|
+
takopi/telegram/commands/cancel.py,sha256=jE93VjztNETlmAgb7FJX0sLR3O-NHy5XcraUbK13TLs,3884
|
|
79
|
+
takopi/telegram/commands/dispatch.py,sha256=zcvX0V6_klf5jXqJlBwAK25e83WC9z3-KoRcDbeWre0,3572
|
|
80
|
+
takopi/telegram/commands/executor.py,sha256=um6KbvneUpzGu8GPujtYJB-tFvWmN95xvLHVN-Vuj8g,14678
|
|
81
|
+
takopi/telegram/commands/file_transfer.py,sha256=yfopf_If9f7Scaz4dlUsfcrVvg24QmQdajLzemaENy0,17697
|
|
82
|
+
takopi/telegram/commands/handlers.py,sha256=2zySjRW49e1iv2QJW6xq2m9j0t-uz9_FCJZpE7NhIDA,1834
|
|
83
|
+
takopi/telegram/commands/media.py,sha256=drSKTf_BbyvXOGhS9UKwey_243Ic5XissoaxCykpd-c,5045
|
|
84
|
+
takopi/telegram/commands/menu.py,sha256=AvEgKQUZageBx7TwV2-V_IGhT7AC0qUTtAHjcLIweNY,4466
|
|
85
|
+
takopi/telegram/commands/model.py,sha256=P6rW_N6Q8OtjU3md3QUMKIgLskLuvJXB2uKoyk5vtfk,7512
|
|
86
|
+
takopi/telegram/commands/overrides.py,sha256=lLlIuCWkKwbS5WlQOOr_Ftv_EvfHR9DhjBpWaf6FBng,4853
|
|
87
|
+
takopi/telegram/commands/parse.py,sha256=0QVW1TVdBWadLbpJ9lRY3s7W4Cm62JJa9jfAaFHQmXU,887
|
|
88
|
+
takopi/telegram/commands/plan.py,sha256=iKsaRBS-qIfvAaxik5ZEA_VzAnFwx7aEED8sKXNq1wE,487
|
|
89
|
+
takopi/telegram/commands/reasoning.py,sha256=UFEJOHm4d0v2jFh5HC3oQGS41NYKbmJHRTaAmu_LiGo,8188
|
|
90
|
+
takopi/telegram/commands/reply.py,sha256=a3zkNjKzn3qZXEZFXuflX-tdhQKQyhYDqZskMy1nS3o,580
|
|
91
|
+
takopi/telegram/commands/topics.py,sha256=A_a6XcA2xXzw9aOpS4_OIEp5XWMGwMZfHBXni3XRg6g,10799
|
|
92
|
+
takopi/telegram/commands/trigger.py,sha256=RgB4V7NoFdfDLk83xl3BPTsIIVr5A2zCNstR_5B_mEw,5201
|
|
93
|
+
takopi/utils/__init__.py,sha256=cV9_7v07Y6XkrUju0lHdO1ia0-Q57NqyFVMaFCg--do,34
|
|
94
|
+
takopi/utils/git.py,sha256=SVKcPNl2mUrv-cVHZQ5b8QXWKi33e_Jc4_vfCLwagkg,2413
|
|
95
|
+
takopi/utils/json_state.py,sha256=cnSvGbB9zj90GdYSyigId1M0nEx54T3A3CqqhkAm9kQ,524
|
|
96
|
+
takopi/utils/paths.py,sha256=HQkXwRdh7coKyeb3lUpgEsLvmQEYd-f9aw7WpIfdu3Q,1319
|
|
97
|
+
takopi/utils/streams.py,sha256=TQezA-A5VCNksLOtwsJplfr8vm1xPTXoGxvik8G2NPI,1121
|
|
98
|
+
takopi/utils/subprocess.py,sha256=2if6IxTZVSB1kDa8SXw3igj3E-zhKB8P4z5MVe-odzY,2169
|
|
99
|
+
yee88-0.1.0.dist-info/METADATA,sha256=Tbk1Ofl-v1xuHJNeiT6M1zs8TDy0K8hloOvDvIxzEPo,4314
|
|
100
|
+
yee88-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
101
|
+
yee88-0.1.0.dist-info/entry_points.txt,sha256=X7Mgvla-mFDUCT_NqPFbuL_82GqVGIMJV2SRLbViBXA,290
|
|
102
|
+
yee88-0.1.0.dist-info/licenses/LICENSE,sha256=poyQ59wnbmL3Ox3TiiephfHvUpLvJl0DwLFFgqBDdHY,1063
|
|
103
|
+
yee88-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[console_scripts]
|
|
2
|
+
yee88 = takopi.cli:main
|
|
3
|
+
|
|
4
|
+
[takopi.engine_backends]
|
|
5
|
+
claude = takopi.runners.claude:BACKEND
|
|
6
|
+
codex = takopi.runners.codex:BACKEND
|
|
7
|
+
opencode = takopi.runners.opencode:BACKEND
|
|
8
|
+
pi = takopi.runners.pi:BACKEND
|
|
9
|
+
|
|
10
|
+
[takopi.transport_backends]
|
|
11
|
+
telegram = takopi.telegram.backend:BACKEND
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 banteg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|