callai-cli 0.9.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.
callai/runner.py ADDED
@@ -0,0 +1,233 @@
1
+ """Runs a harness command line as a subprocess (DESIGN.md §4, §8).
2
+
3
+ The harness gets its own process group, so a timeout or Ctrl-C reaches the whole tree it
4
+ spawned, and an empty stdin, because the prompt always travels in argv.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import contextlib
10
+ import io
11
+ import os
12
+ import shutil
13
+ import signal
14
+ import subprocess
15
+ import sys
16
+ import threading
17
+ import time
18
+ from collections.abc import Callable, Mapping, Sequence
19
+ from typing import IO
20
+
21
+ from .adapters.base import strip_ansi
22
+ from .errors import HarnessNotFound, HarnessTimeout, Interrupted, UsageError
23
+ from .result import Completed
24
+
25
+ _POSIX = os.name == "posix"
26
+ _GRACE_SECONDS = 3
27
+ # Harnesses running right now, so a fan-out's Ctrl-C can stop the ones in other threads (stop_all).
28
+ _LIVE: set[subprocess.Popen] = set()
29
+ _LIVE_LOCK = threading.Lock()
30
+
31
+
32
+ def resolve_binary(binary: str, harness_name: str | None = None, install_url: str | None = None) -> str:
33
+ """Find a harness executable on PATH.
34
+
35
+ Args:
36
+ binary: Executable name, e.g. ``"claude"``.
37
+ harness_name: The ``--name`` (harness) that needs it (for the error message).
38
+ install_url: Install docs (for the error message).
39
+
40
+ Returns:
41
+ The absolute path of the executable.
42
+
43
+ Raises:
44
+ HarnessNotFound: If it is not on PATH.
45
+ """
46
+ path = shutil.which(binary)
47
+ if path is None:
48
+ raise HarnessNotFound(binary, harness_name=harness_name, install_url=install_url)
49
+ return path
50
+
51
+
52
+ def harness_version(path: str, timeout: float = 10) -> str | None:
53
+ """Ask a harness binary for its version (``BINARY --version``).
54
+
55
+ The version is the first non-empty line of stdout (else stderr), whatever the exit code:
56
+ hermes 0.19.0 prints its version and exits 1.
57
+
58
+ Args:
59
+ path: The executable.
60
+ timeout: Seconds to wait for it.
61
+
62
+ Returns:
63
+ The version line, or ``None`` if it printed nothing, failed to start or timed out.
64
+ """
65
+ try:
66
+ proc = subprocess.run(
67
+ [path, "--version"],
68
+ stdin=subprocess.DEVNULL,
69
+ capture_output=True,
70
+ timeout=timeout,
71
+ check=False,
72
+ )
73
+ except (OSError, subprocess.TimeoutExpired):
74
+ return None
75
+ for raw in (proc.stdout, proc.stderr):
76
+ for line in raw.decode("utf-8", errors="replace").splitlines():
77
+ if line.strip():
78
+ return strip_ansi(line).strip()
79
+ return None
80
+
81
+
82
+ def _pump(stream: IO[str], sink: list[str], tee: IO[str] | None, on_line: Callable[[str], None] | None = None) -> None:
83
+ """Drain a pipe line by line into ``sink``, optionally copying each line to ``tee`` or ``on_line``.
84
+
85
+ Args:
86
+ stream: The harness's stdout or stderr pipe.
87
+ sink: Collected lines.
88
+ tee: Where to echo lines live, or ``None``.
89
+ on_line: Called with each line as it arrives, or ``None``.
90
+ """
91
+ for line in iter(stream.readline, ""):
92
+ sink.append(line)
93
+ if tee is not None:
94
+ tee.write(line)
95
+ tee.flush()
96
+ if on_line is not None:
97
+ on_line(line)
98
+ stream.close()
99
+
100
+
101
+ def _signal_group(proc: subprocess.Popen, sig: int) -> None:
102
+ """Send ``sig`` to the harness's whole process group; a group that already exited is fine.
103
+
104
+ Args:
105
+ proc: The harness process (leader of its own group).
106
+ sig: Signal to send (POSIX); Windows always kills the process.
107
+ """
108
+ if _POSIX:
109
+ with contextlib.suppress(ProcessLookupError):
110
+ os.killpg(proc.pid, sig)
111
+ else: # pragma: no cover - Windows
112
+ proc.kill()
113
+
114
+
115
+ def _kill_group(proc: subprocess.Popen) -> None:
116
+ """SIGTERM the harness's process group, then SIGKILL whatever is left after a grace period.
117
+
118
+ Args:
119
+ proc: The harness process (leader of its own group).
120
+ """
121
+ _signal_group(proc, signal.SIGTERM)
122
+ with contextlib.suppress(subprocess.TimeoutExpired): # harness ignored SIGTERM: SIGKILL below
123
+ proc.wait(timeout=_GRACE_SECONDS)
124
+ if _POSIX:
125
+ _signal_group(proc, signal.SIGKILL) # also reaps grandchildren that outlived the leader
126
+ proc.wait()
127
+
128
+
129
+ def stop_all() -> None:
130
+ """Stop every harness still running (Ctrl-C during a fan-out): SIGINT, then kill after a grace period.
131
+
132
+ Each harness's own :func:`run` call then returns normally with the exit status it got.
133
+ """
134
+ with _LIVE_LOCK:
135
+ procs = list(_LIVE)
136
+ for proc in procs:
137
+ _signal_group(proc, signal.SIGINT)
138
+ deadline = time.monotonic() + _GRACE_SECONDS
139
+ for proc in procs:
140
+ with contextlib.suppress(subprocess.TimeoutExpired): # ignored SIGINT: killed below
141
+ proc.wait(timeout=max(0.0, deadline - time.monotonic()))
142
+ for proc in procs:
143
+ if proc.poll() is None:
144
+ _kill_group(proc)
145
+
146
+
147
+ def run(
148
+ argv: Sequence[str],
149
+ *,
150
+ cwd: str | None = None,
151
+ timeout: float | None = None,
152
+ verbose: bool = False,
153
+ harness_name: str | None = None,
154
+ install_url: str | None = None,
155
+ env: Mapping[str, str] | None = None,
156
+ on_stdout_line: Callable[[str], None] | None = None,
157
+ ) -> Completed:
158
+ """Run a harness and capture its output.
159
+
160
+ Args:
161
+ argv: Command line; ``argv[0]`` is the bare binary name, resolved on PATH.
162
+ cwd: Working directory, or ``None`` for the current one.
163
+ timeout: Seconds before the process group is killed, or ``None`` for no limit.
164
+ verbose: Copy the harness's stderr to ours as it arrives (it is still captured).
165
+ harness_name: The ``--name`` (harness) being run (for error messages).
166
+ install_url: Install docs for the binary (for error messages).
167
+ env: Variables to set for the harness on top of callai's environment (an adapter's ``build_env``).
168
+ on_stdout_line: Called with each stdout line as it arrives (``-o stream-json``), from a reader
169
+ thread; stdout is still captured in full.
170
+
171
+ Returns:
172
+ The captured outcome; ``argv`` is reported exactly as given.
173
+
174
+ Raises:
175
+ UsageError: If ``cwd`` is not a directory.
176
+ HarnessTimeout: If ``timeout`` elapsed (the process group has been killed).
177
+ Interrupted: On Ctrl-C (SIGINT was forwarded to the process group).
178
+ """
179
+ argv = list(argv)
180
+ if cwd is not None and not os.path.isdir(cwd):
181
+ raise UsageError(f"--cwd {cwd!r} is not a directory")
182
+ executable = resolve_binary(argv[0], harness_name=harness_name, install_url=install_url)
183
+ group_kw = {"start_new_session": True} if _POSIX else {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP}
184
+
185
+ start = time.monotonic()
186
+ proc = subprocess.Popen(
187
+ argv,
188
+ executable=executable, # keeps argv[0] as the bare name the harness expects
189
+ cwd=cwd,
190
+ env={**os.environ, **env} if env else None,
191
+ stdin=subprocess.DEVNULL,
192
+ stdout=subprocess.PIPE,
193
+ stderr=subprocess.PIPE,
194
+ **group_kw,
195
+ )
196
+ out: list[str] = []
197
+ err: list[str] = []
198
+ # Decode ourselves with newline="": text=True would translate \r\n to \n, and the output must be
199
+ # the harness's own (hermes marks display lines with \r\n; -o raw must be byte-faithful).
200
+ stdout, stderr = (
201
+ io.TextIOWrapper(s, encoding="utf-8", errors="replace", newline="") for s in (proc.stdout, proc.stderr)
202
+ )
203
+ pumps = [
204
+ threading.Thread(target=_pump, args=(stdout, out, None, on_stdout_line), daemon=True),
205
+ threading.Thread(target=_pump, args=(stderr, err, sys.stderr if verbose else None), daemon=True),
206
+ ]
207
+ for t in pumps:
208
+ t.start()
209
+ with _LIVE_LOCK:
210
+ _LIVE.add(proc)
211
+ try:
212
+ proc.wait(timeout=timeout)
213
+ except subprocess.TimeoutExpired:
214
+ _kill_group(proc)
215
+ raise HarnessTimeout(argv[0], timeout) from None
216
+ except KeyboardInterrupt:
217
+ _signal_group(proc, signal.SIGINT)
218
+ with contextlib.suppress(subprocess.TimeoutExpired): # harness ignored SIGINT: killed below
219
+ proc.wait(timeout=_GRACE_SECONDS)
220
+ _kill_group(proc)
221
+ raise Interrupted() from None
222
+ finally:
223
+ with _LIVE_LOCK:
224
+ _LIVE.discard(proc)
225
+ for t in pumps:
226
+ t.join()
227
+ return Completed(
228
+ argv=argv,
229
+ exit_code=proc.returncode,
230
+ stdout="".join(out),
231
+ stderr="".join(err),
232
+ duration_ms=int((time.monotonic() - start) * 1000),
233
+ )
@@ -0,0 +1,218 @@
1
+ Metadata-Version: 2.4
2
+ Name: callai-cli
3
+ Version: 0.9.0
4
+ Summary: One CLI to run a prompt non-interactively through any coding-agent harness (claude-code, codex, opencode, pi, hermes, grok).
5
+ Author-email: Sugato Ray <sugatoray.dev@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/sugatoray/callai
8
+ Project-URL: Issues, https://github.com/sugatoray/callai/issues
9
+ Project-URL: Documentation, https://github.com/sugatoray/callai/blob/master/docs/callai/README.md
10
+ Project-URL: Source, https://github.com/sugatoray/callai
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Software Development
22
+ Classifier: Topic :: Software Development :: Build Tools
23
+ Classifier: Topic :: Software Development :: Libraries
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Topic :: Software Development :: Testing
26
+ Classifier: Topic :: Utilities
27
+ Classifier: Topic :: Scientific/Engineering
28
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
29
+ Classifier: Typing :: Typed
30
+ Requires-Python: >=3.11
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ Provides-Extra: test
34
+ Requires-Dist: pytest>=7.0; extra == "test"
35
+ Requires-Dist: pytest-cov>=4.0; extra == "test"
36
+ Provides-Extra: dev
37
+ Requires-Dist: ruff>=0.6.0; extra == "dev"
38
+ Requires-Dist: interrogate>=1.7.0; extra == "dev"
39
+ Dynamic: license-file
40
+
41
+ ![callai: one command line for every coding-agent harness](https://raw.githubusercontent.com/sugatoray/callai/master/docs/assets/images/banner_2x.png)
42
+
43
+ # callai
44
+
45
+ `callai` is one command line for every coding-agent harness. It runs a single prompt non-interactively, like `claude -p`, through the harness you pick and prints the result:
46
+
47
+ ```bash
48
+ callai -n {claude-code,codex,opencode,pi,hermes,grok} -p "PROMPT" [-m MODEL]
49
+ ```
50
+
51
+ **User documentation:** [`docs/callai/`](docs/callai/README.md) has a tutorial, how-to guides, concepts and a full reference, with `llms.txt` for AI agents.
52
+
53
+ **Status:** Python implementation, version 0.9.0 (Red/Green TDD). All six harnesses work: `claude-code`, `codex`, `opencode`, `pi`, `grok` and `hermes`. The shared test suite is in [`conformance/`](conformance/README.md). PyPI distribution name: `callai-cli` (command `callai`). See [`SCOPE.md`](SCOPE.md) for scope, language/distribution decisions and the roadmap. See [`docs/DESIGN.md`](docs/DESIGN.md) for the CLI surface, adapter architecture, output schema and open decisions, and [`docs/.scrolls/PLAN.md`](docs/.scrolls/PLAN.md) for the build order.
54
+
55
+ ## Usage
56
+
57
+ ```bash
58
+ callai -n claude-code -p "Summarise README.md" # prints the answer
59
+ callai -n codex -p "Fix the failing test" --permission auto
60
+ git diff | callai -n grok -p "Review this diff" -o json # piped stdin is appended to the prompt
61
+ callai -n codex -p "Fix the test" -o stream-json # JSON-lines events (tool calls, text) as they happen
62
+ callai -n pi -p "hello" -m stub/stub-model --dry-run # show the harness command, don't run it
63
+ callai -n opencode -p "hello" -- --agent build # args after -- go to the harness unchanged
64
+ callai -n codex -p "hello" -m fast # a model alias from the config file
65
+ callai -n claude-code -p "and the tests?" --resume SESSION_ID # continue a session (-o json reports session_id)
66
+ callai -n pi -p "and now?" --continue # continue the harness's most recent session
67
+ callai -n codex/claude-code/grok -p "Explain main.py" # several harnesses at the same time ('/' = parallel)
68
+ callai -n codex,pi -p "Explain main.py" # one after another (',' = sequential)
69
+ callai -n all -p "Explain main.py" -o json # every installed harness, at the same time
70
+ callai --list-harnesses # which harnesses are installed, and their versions
71
+ ```
72
+
73
+ Harnesses: `claude-code`, `codex`, `opencode`, `pi`, `grok`, `hermes`. `-o text|json|stream-json|raw`
74
+ (json is one normalised object: `harness_name`, `text`, `is_error`, `session_id`, `usage`, `cost_usd`, `model`, ...;
75
+ stream-json is one event per line, `start`, then `text`/`tool_use`/`tool_result` as they happen, then `result`,
76
+ the json object; hermes has no event stream, so it gives only `start` and `result`; see DESIGN §5).
77
+ With several harnesses, each answer is printed under a `== name ==` header (`-o json`: `{"results": [...]}`;
78
+ stream-json events carry `harness_name`). One failure doesn't stop the others; the exit code is the first
79
+ failure's. `-n all` skips harnesses that can't honour the options, with a note. Parallel runs share the
80
+ working directory, so prefer `,` for tasks that edit files. See DESIGN §3 "Fan-out".
81
+ Exit codes: the harness's own; 1 if it reported an error but exited 0; 2 usage error; 124 timeout;
82
+ 127 harness not installed; 130 interrupted.
83
+
84
+ Stdin: with `-p TEXT`, piped stdin is optional. If it's a pipe that stays open with nothing on it
85
+ (common when an agent or script runs callai), callai waits 3 seconds (`CALLAI_STDIN_WAIT`), warns and
86
+ runs with the prompt alone, like `claude -p`. Use `< /dev/null` to skip the wait. With `-p -` or no
87
+ `-p`, stdin is the prompt, so callai waits for it to close.
88
+
89
+ ### Defaults: environment and config file
90
+
91
+ Each option comes from the first of: command-line flag, `CALLAI_*` environment variable, config
92
+ file, built-in default.
93
+
94
+ | setting | flag | environment | config file |
95
+ |---|---|---|---|
96
+ | harness | `-n/--name` | `CALLAI_HARNESS_NAME` | `default_harness` |
97
+ | model | `-m/--model` | `CALLAI_MODEL` | `[harnesses.<name>] model`, then top-level `model` |
98
+ | timeout | `--timeout` | `CALLAI_TIMEOUT` | `timeout` |
99
+ | stdin wait | | `CALLAI_STDIN_WAIT` | |
100
+
101
+ The config file is `~/.config/callai/config.toml` (or `$XDG_CONFIG_HOME/callai/config.toml`, or the
102
+ path in `CALLAI_CONFIG`). It is optional; unknown keys are an error (exit 2), so a typo never
103
+ silently does nothing.
104
+
105
+ ```toml
106
+ default_harness = "claude-code"
107
+ model = "fast" # default model for every harness (here an alias)
108
+ timeout = 600
109
+
110
+ [harnesses.codex]
111
+ binary = "~/bin/codex" # run this instead of `codex` from PATH
112
+ model = "gpt-5-codex" # codex's default; beats the top-level model
113
+ extra_args = ["--skip-git-repo-check"] # passed before anything after --
114
+
115
+ [aliases] # -m fast / -m smart, resolved per harness
116
+ fast = { claude-code = "haiku", codex = "gpt-5-codex-mini", opencode = "anthropic/claude-haiku-4-5" }
117
+ smart = { claude-code = "opus", codex = "gpt-5-codex" }
118
+ ```
119
+
120
+ Any model that isn't an alias goes to the harness unchanged. An alias with no entry for the chosen
121
+ harness is an error (`model alias 'smart' has no entry for pi`) rather than passing `smart` on.
122
+
123
+ `callai --list-harnesses` shows each harness's binary (including a configured one), where it is on
124
+ PATH and its `--version`, or where to install it; `-o json` gives the same as JSON.
125
+
126
+ `--permission default|read-only|auto|bypass` is translated per harness; a level a harness can't honour
127
+ is refused (exit 2) rather than run with more access. What each level really allows was checked with
128
+ scripted tool calls; see [DESIGN.md §7](docs/DESIGN.md#7-permissions-and-safety):
129
+
130
+ | level | claude-code | codex | opencode | pi | grok | hermes |
131
+ |---|---|---|---|---|---|---|
132
+ | `read-only` | plan mode: no writes, no shell | read-only sandbox | refused | read tools only | read tools only | refused |
133
+ | `auto` | edits inside the workspace | writes inside the workspace (sandboxed shell) | refused | file writes/edits, no shell | file writes/edits, no shell | file writes/edits, no shell |
134
+ | `bypass` | everything | everything | everything | everything | everything | everything |
135
+
136
+ With `default`, opencode, pi and hermes already write files and run shell commands without asking;
137
+ claude-code, codex and grok don't. `bypass` prints a warning on stderr. As root, claude refuses
138
+ `bypass` unless `IS_SANDBOX=1` is set (callai never sets it).
139
+
140
+ > [!CAUTION]
141
+ > **grok `auto` and `bypass` can write outside the working directory.** callai's `auto` for grok
142
+ > allows only file writes and edits (no shell), but grok will write them **anywhere** the user
143
+ > running it can write, not just in the working directory. Unlike codex's `auto`, which is sandboxed
144
+ > to the workspace. callai does not add a sandbox for you: grok's sandbox needs
145
+ > [bubblewrap](https://github.com/containers/bubblewrap) on Linux, and grok refuses to start without
146
+ > it, so adding it would break `auto` on machines that don't have it.
147
+ >
148
+ > To confine grok's writes to the working directory, opt in with grok's `workspace` sandbox:
149
+ >
150
+ > ```bash
151
+ > sudo apt install -y bubblewrap # Linux only, once
152
+ > callai -n grok -p "Fix the failing test" --permission auto -- --sandbox workspace
153
+ > callai -n grok -p "Refactor utils.py" --permission bypass -C ~/proj -- --sandbox workspace
154
+ > GROK_SANDBOX=workspace callai -n grok -p "Fix the failing test" --permission auto # same, via grok's env var
155
+ > ```
156
+ >
157
+ > Writes inside the working directory (`-C`, or the current directory) still work; writes elsewhere
158
+ > fail with "Permission denied", and so do shell commands that write outside it under `bypass`.
159
+ > Without bubblewrap, grok exits 1 with "bwrap exec failed ... Install bubblewrap" instead of
160
+ > running unconfined. grok's other profiles (`read-only`, `strict`) behave the same way for the
161
+ > working directory: they also allow writes inside it, so `-- --sandbox read-only` does **not** make
162
+ > grok read-only. Use callai's `--permission read-only` for that (no write or shell tools are offered).
163
+
164
+ A few prompts are refused because the harness would not send them as text: pi reads a prompt starting
165
+ with `@` as a file, and grok runs one starting with `/` as a slash command.
166
+
167
+ ## From Python
168
+
169
+ ```python
170
+ import callai
171
+
172
+ result = callai.run("codex", "Fix the failing test", permission="auto", timeout=600)
173
+ print(result.text, result.is_error, result.session_id, result.usage)
174
+
175
+ # Continue that session, and watch tool calls as they happen
176
+ callai.run("codex", "Now add a test for it", resume=result.session_id, on_event=print)
177
+ ```
178
+
179
+ `run()` takes the CLI's options as keyword arguments (`model`, `cwd`, `timeout`, `permission`,
180
+ `system_prompt`, `extra_args`, `resume`, `continue_session`, `binary`, `on_event`) and returns the same
181
+ result as `-o json`. It ignores `CALLAI_*` variables and the config file: pass what you want. A harness
182
+ that fails gives a result with `is_error=True`; `callai.CallaiError` subclasses (`UsageError`,
183
+ `HarnessNotFound`, `HarnessTimeout`, `Interrupted`) mean the run couldn't start or finish.
184
+
185
+ ## Development
186
+
187
+ ```bash
188
+ make install.harnesses # all harness CLIs at their verified versions (or make install.<tool>)
189
+ make docs.html # the docs as one HTML page with tabs and a theme toggle -> build/docs.html
190
+ uv sync --extra test --extra dev # Python >= 3.11, no runtime dependencies; develops on the pinned 3.11 (.python-version)
191
+ make test # tests + coverage gate (95%) on the pinned Python (same as: uv run pytest)
192
+ make test.matrix # the same on every supported Python, 3.11-3.14 (or PYTHON_VERSIONS=3.13)
193
+ make pkg.build # sdist + wheel -> dist/ (uv build)
194
+ make pkg.publish # build, then upload to PyPI (uv publish; set UV_PUBLISH_TOKEN)
195
+ make pkg.install.e # editable install of this checkout into .venv
196
+ make pkg.install.local # install callai-cli from the built dist/
197
+ make pkg.install.pypi # install callai-cli from PyPI (PKG_VERSION=0.9.0 to pin)
198
+ uv run ruff check . && uv run ruff format --check .
199
+ uv run interrogate src # docstring coverage (100%)
200
+ uv run callai --version
201
+ CALLAI_LIVE=claude-code uv run pytest -m live # live smoke test: real harness, real model (or CALLAI_LIVE=all)
202
+ ```
203
+
204
+ Development follows Red/Green TDD. Shared behaviour cases live in [`conformance/`](conformance/README.md).
205
+
206
+ ## Layout
207
+
208
+ - `docs/` — the user documentation in [`docs/callai/`](docs/callai/README.md), [`DESIGN.md`](docs/DESIGN.md), plus the project's own [`docs/.scrolls/`](docs/.scrolls/STARTER.md) working-memory system.
209
+ - `.trash/` — scratch/disposable files local to this repository (git-ignored).
210
+ - `conformance/` — language-agnostic test cases (argv, parse, CLI) shared by every implementation, plus `tools/`: a local stub model (with scripted tool calls) and the permission probe.
211
+ - `src/` — `src/callai/` (CLI, runner, one adapter per harness in `adapters/`) and `src/tests/`.
212
+ - [`CHANGELOG.md`](CHANGELOG.md) — notable changes to callai, [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) style with [SemVer](https://semver.org/) versions.
213
+
214
+ See [SCROLLS.md](SCROLLS.md) for how the memory system works.
215
+
216
+ ## License
217
+
218
+ [Apache-2.0](LICENSE).
@@ -0,0 +1,25 @@
1
+ callai/__init__.py,sha256=0LuTDPxYR3BgpX3BYvRs16UnojqGBRIUWgzRh-bG304,1028
2
+ callai/__main__.py,sha256=xfIr6ypBYmw-uuC00AVHQSTxAOOMAbubZJlypbJtzp0,158
3
+ callai/api.py,sha256=z8tMdrVy6Iv5a9mPhmLN6In9hn61zaV6m9pcBQpjDaM,5081
4
+ callai/cli.py,sha256=ZZJJhanGGlMQXIIlp_N2nApL23AuriZ_2uCYcre8O0M,25750
5
+ callai/config.py,sha256=duKGhbVaKArU_5go8BJXz61Ldu0b1ZXWiIwyidRpRzI,9153
6
+ callai/errors.py,sha256=QG-0e3bRQ6K4lJlz63d2jGVzZBPocjM4g72Xf7KQe3c,1932
7
+ callai/fanout.py,sha256=HHjp_tYxZO0AHF4u0kKsIhOznajDWQcijxkJIFSVHM0,5500
8
+ callai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ callai/request.py,sha256=1vHwcze5i2K7bk3rqu1unDckNiEBz3djzppdnB_oy6Q,3328
10
+ callai/result.py,sha256=YHZoZI16fvMf4Piuiv3Dha5d75jOF48lN45xsV-li2I,3127
11
+ callai/runner.py,sha256=nfru9A7mOuPFOsSdvpxQbzr5QhDfzjo_hIdaoirNKJ8,8356
12
+ callai/adapters/__init__.py,sha256=vkjHzZE2RS9FyaGvAdw9Xj_AZkJDEtehYso2jSeSs88,2322
13
+ callai/adapters/base.py,sha256=BKUgVy3kFFCBVHD7ABtJjF7-veOt7fOWVL0-ykJjQyw,5964
14
+ callai/adapters/claude_code.py,sha256=77xfizhpCeDQADgRi6YHlHSo1dBINgUsKmUL4mEx7YY,6918
15
+ callai/adapters/codex.py,sha256=I_KCbWxpnQWoeTElJeNMx3gN03zRmoHGmLuVvTWVsM4,6658
16
+ callai/adapters/grok.py,sha256=F9jPw07WfxEm70jw8PCZ-PHthvVC0OFTnLorVlhD9eY,8646
17
+ callai/adapters/hermes.py,sha256=ZPHF3sYZwhlvqHYtjvRFFxAi05KwCdG8_Ze7O_8dTdk,4538
18
+ callai/adapters/opencode.py,sha256=crvAlT9yhbKbQNVihqx9e1jsex9kFuiI-Yp490ZULcY,7546
19
+ callai/adapters/pi.py,sha256=W8acocSBL_p8LEU22-9DqE4WL0xSZpquyL-lfUW2oZA,6993
20
+ callai_cli-0.9.0.dist-info/licenses/LICENSE,sha256=ErA_fMqfhQnPFERDycWnCvEzyXkKIamFv10AJbPWI9s,11340
21
+ callai_cli-0.9.0.dist-info/METADATA,sha256=r0uutgBEpx1EIPywO_fgqjPnoY5r8m7nYtzJv-j6Y2k,13280
22
+ callai_cli-0.9.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
23
+ callai_cli-0.9.0.dist-info/entry_points.txt,sha256=21U8Nb-AyWuGAViIdTFZMT601gx3dYspErVfSfYzoGk,43
24
+ callai_cli-0.9.0.dist-info/top_level.txt,sha256=JyuQQSsmwPtkHNaLf1FOKiNWo9TvuvzEDx4AWmODigk,7
25
+ callai_cli-0.9.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ callai = callai.cli:main