peaches 0.1.0__tar.gz

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.
peaches-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ella Inng
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.
peaches-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.4
2
+ Name: peaches
3
+ Version: 0.1.0
4
+ Summary: A selected text workflow workbench over independent Python composition libraries.
5
+ Author: ellainng
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Dist: wispsy>=0.0.1
9
+ Requires-Dist: scriptty>=0.1.0
10
+ Requires-Dist: scriptty[cli]>=0.1.0 ; extra == 'cli'
11
+ Requires-Dist: scriptty[sequence]>=0.1.0 ; python_full_version >= '3.14' and extra == 'sequence'
12
+ Requires-Dist: wispsy[table]>=0.0.1 ; python_full_version >= '3.14' and extra == 'table'
13
+ Requires-Dist: scriptty[terminal]>=0.1.0 ; python_full_version >= '3.14' and extra == 'terminal'
14
+ Requires-Python: >=3.11
15
+ Project-URL: Source, https://github.com/wispsy/peaches
16
+ Project-URL: Issues, https://github.com/wispsy/peaches/issues
17
+ Project-URL: Project, https://github.com/orgs/wispsy/projects/23
18
+ Provides-Extra: cli
19
+ Provides-Extra: sequence
20
+ Provides-Extra: table
21
+ Provides-Extra: terminal
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Peaches
25
+
26
+ A text workflow workbench: inspect, normalize and report text, then optionally
27
+ review progress in a terminal or consume typed tables and Markdown.
28
+
29
+ ```python
30
+ import asyncio
31
+ from peaches import TextWorkbench
32
+
33
+ workbench = TextWorkbench()
34
+ outcome = asyncio.run(workbench.report(" Hello WORLD "))
35
+ assert outcome["outputs"]["report"] == {"text": "hello world", "words": 2, "characters": 11}
36
+ document = workbench.inspect()
37
+ ```
38
+
39
+ The library and optional CLI support Python 3.11+. Lifecycle, table and terminal
40
+ profiles require Python 3.14+. Install selected source profiles from this checkout:
41
+
42
+ ```sh
43
+ uv run --python 3.11 --extra cli peaches report ' Hello WORLD '
44
+ uv run --python 3.11 --extra cli peaches inspect
45
+ uv run --python 3.14 --extra cli --extra sequence peaches report ' Hello WORLD ' --checkpoint progress.json --pace function
46
+ uv run --python 3.14 --extra cli --extra sequence peaches report ' Hello WORLD ' --checkpoint progress.json --resume
47
+ uv run --python 3.14 --extra cli --extra table peaches markdown --text ' Hello WORLD ' --text ' More WORDS '
48
+ uv run --python 3.14 --extra terminal peaches review ' Hello WORLD ' --checkpoint review.json
49
+ ```
50
+
51
+ Run requires a new checkpoint path; --resume requires existing progress. --retry
52
+ explicitly repeats an uncertain step; --restart repeats the workflow. Outcomes
53
+ use Scriptty's schema-1 JSON contract. Commands keep UTF-8 results on stdout and
54
+ diagnostics/errors on stderr independently of console encoding.
55
+
56
+ `workbench.table(texts)` returns the existing typed Headless Spreadsheets Table;
57
+ `markdown(texts)` selects its detached, escaped renderer. `terminal_model` supplies
58
+ the same Scriptty/Carets model for embedding/testing; `await review(...)` selects
59
+ native terminal ownership. The native host requires a supported POSIX TTY. Every
60
+ surface uses the same curated text functions, explicit mappings and selected
61
+ capabilities. Terminal review requires capability confirmation before execution.
62
+
63
+ Peaches composes Wispsy's typed reference, Hidden Moves catalogs, moveset pipelines
64
+ and Scriptty's passive documents/application API. Func_seq owns optional durable
65
+ execution; Carets owns optional terminal behavior; Headless Spreadsheets owns
66
+ typed tables and rendering. The workbench introduces no second parser/executor
67
+ or new grammar. Inspect returns the existing passive schema-1 document.
68
+
69
+ This is a functional source-development package at 0.0.1.dev0 with immutable
70
+ sibling revisions. It is independently versioned and requires no hosted service.
71
+ Release/license/dependency publication choices remain in the
72
+ [Wispsy Composition Project](https://github.com/orgs/wispsy/projects/23).
73
+ Build with `uv build --out-dir dist`; `python scripts/qualify.py dist` installs
74
+ wheel/source artifacts outside the checkout, checks optional-profile isolation,
75
+ actual commands/restart, synthetic terminal/table equivalence and native POSIX
76
+ keyboard selection/run/quit with terminal restoration. CI qualifies Python 3.11
77
+ on Linux/Windows and Python 3.14 on Linux/macOS/Windows.
@@ -0,0 +1,54 @@
1
+ # Peaches
2
+
3
+ A text workflow workbench: inspect, normalize and report text, then optionally
4
+ review progress in a terminal or consume typed tables and Markdown.
5
+
6
+ ```python
7
+ import asyncio
8
+ from peaches import TextWorkbench
9
+
10
+ workbench = TextWorkbench()
11
+ outcome = asyncio.run(workbench.report(" Hello WORLD "))
12
+ assert outcome["outputs"]["report"] == {"text": "hello world", "words": 2, "characters": 11}
13
+ document = workbench.inspect()
14
+ ```
15
+
16
+ The library and optional CLI support Python 3.11+. Lifecycle, table and terminal
17
+ profiles require Python 3.14+. Install selected source profiles from this checkout:
18
+
19
+ ```sh
20
+ uv run --python 3.11 --extra cli peaches report ' Hello WORLD '
21
+ uv run --python 3.11 --extra cli peaches inspect
22
+ uv run --python 3.14 --extra cli --extra sequence peaches report ' Hello WORLD ' --checkpoint progress.json --pace function
23
+ uv run --python 3.14 --extra cli --extra sequence peaches report ' Hello WORLD ' --checkpoint progress.json --resume
24
+ uv run --python 3.14 --extra cli --extra table peaches markdown --text ' Hello WORLD ' --text ' More WORDS '
25
+ uv run --python 3.14 --extra terminal peaches review ' Hello WORLD ' --checkpoint review.json
26
+ ```
27
+
28
+ Run requires a new checkpoint path; --resume requires existing progress. --retry
29
+ explicitly repeats an uncertain step; --restart repeats the workflow. Outcomes
30
+ use Scriptty's schema-1 JSON contract. Commands keep UTF-8 results on stdout and
31
+ diagnostics/errors on stderr independently of console encoding.
32
+
33
+ `workbench.table(texts)` returns the existing typed Headless Spreadsheets Table;
34
+ `markdown(texts)` selects its detached, escaped renderer. `terminal_model` supplies
35
+ the same Scriptty/Carets model for embedding/testing; `await review(...)` selects
36
+ native terminal ownership. The native host requires a supported POSIX TTY. Every
37
+ surface uses the same curated text functions, explicit mappings and selected
38
+ capabilities. Terminal review requires capability confirmation before execution.
39
+
40
+ Peaches composes Wispsy's typed reference, Hidden Moves catalogs, moveset pipelines
41
+ and Scriptty's passive documents/application API. Func_seq owns optional durable
42
+ execution; Carets owns optional terminal behavior; Headless Spreadsheets owns
43
+ typed tables and rendering. The workbench introduces no second parser/executor
44
+ or new grammar. Inspect returns the existing passive schema-1 document.
45
+
46
+ This is a functional source-development package at 0.0.1.dev0 with immutable
47
+ sibling revisions. It is independently versioned and requires no hosted service.
48
+ Release/license/dependency publication choices remain in the
49
+ [Wispsy Composition Project](https://github.com/orgs/wispsy/projects/23).
50
+ Build with `uv build --out-dir dist`; `python scripts/qualify.py dist` installs
51
+ wheel/source artifacts outside the checkout, checks optional-profile isolation,
52
+ actual commands/restart, synthetic terminal/table equivalence and native POSIX
53
+ keyboard selection/run/quit with terminal restoration. CI qualifies Python 3.11
54
+ on Linux/Windows and Python 3.14 on Linux/macOS/Windows.
@@ -0,0 +1,27 @@
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+ import subprocess
5
+ import sys
6
+ import tempfile
7
+ import unittest
8
+
9
+ from wispsy.reference import run_reference
10
+
11
+
12
+ class InstalledCommandTests(unittest.TestCase):
13
+ def test_module_and_console_entrypoints_match_library_outcomes(self):
14
+ text = " HéLLo 🌍 "
15
+ console = Path(sys.executable).with_name("peaches.exe" if sys.platform == "win32" else "peaches")
16
+ with tempfile.TemporaryDirectory() as outside:
17
+ for prefix in ([sys.executable, "-I", "-m", "peaches"], [str(console)]):
18
+ result = subprocess.run([*prefix, "report", text], cwd=outside, env={**os.environ, "PYTHONIOENCODING": "cp1252"}, capture_output=True)
19
+ self.assertEqual(result.returncode, 0, result.stderr)
20
+ self.assertEqual(json.loads(result.stdout)["outputs"], {"report": run_reference(text)})
21
+ inspected = subprocess.run([str(console), "inspect"], cwd=outside, capture_output=True)
22
+ self.assertEqual(inspected.returncode, 0, inspected.stderr)
23
+ self.assertEqual(json.loads(inspected.stdout)["status"], "inspected")
24
+ failure = subprocess.run([str(console), "report"], cwd=outside, capture_output=True)
25
+ self.assertEqual(failure.returncode, 2)
26
+ self.assertEqual(failure.stdout, b"")
27
+ self.assertEqual(json.loads(failure.stderr.splitlines()[-1])["schema"], 1)
@@ -0,0 +1,57 @@
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+ import subprocess
5
+ import sys
6
+ import tempfile
7
+ import time
8
+ import unittest
9
+
10
+
11
+ @unittest.skipUnless(os.name == "posix", "Native POSIX review host")
12
+ class NativeReviewTests(unittest.TestCase):
13
+ def test_actual_terminal_selection_run_and_quit_restore_the_session(self):
14
+ import fcntl
15
+ import pty
16
+ import select
17
+ import struct
18
+ import termios
19
+
20
+ master, slave = pty.openpty()
21
+ fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack("HHHH", 24, 80, 0, 0))
22
+ original = termios.tcgetattr(slave)
23
+ with tempfile.TemporaryDirectory() as directory:
24
+ checkpoint = Path(directory) / "review.json"
25
+ process = subprocess.Popen([sys.executable, "-I", "-m", "peaches", "review", " Hello WORLD ",
26
+ "--checkpoint", str(checkpoint)], stdin=slave, stderr=slave, stdout=subprocess.PIPE, cwd=directory)
27
+ def drain():
28
+ if select.select([master], [], [], 0.02)[0]:
29
+ os.read(master, 65536)
30
+ try:
31
+ deadline = time.monotonic() + 5
32
+ while termios.tcgetattr(slave)[3] & termios.ICANON and process.poll() is None and time.monotonic() < deadline:
33
+ drain()
34
+ self.assertFalse(termios.tcgetattr(slave)[3] & termios.ICANON, "Native raw session did not start")
35
+ # Input editor → explicitly select both capabilities → actions → run.
36
+ os.write(master, b"\t \x1b[B \t\r")
37
+ completed = False
38
+ deadline = time.monotonic() + 5
39
+ while process.poll() is None and time.monotonic() < deadline:
40
+ drain()
41
+ if checkpoint.exists() and json.loads(checkpoint.read_text())["status"] == "completed":
42
+ completed = True
43
+ break
44
+ self.assertTrue(completed, "Native keyboard execution did not complete")
45
+ os.write(master, b"q")
46
+ output, _ = process.communicate(timeout=5)
47
+ self.assertEqual(process.returncode, 0, output)
48
+ self.assertEqual(json.loads(output)["outputs"], {"report": {"text": "hello world", "words": 2, "characters": 11}})
49
+ restored = termios.tcgetattr(slave)
50
+ mask = termios.ICANON | termios.ECHO
51
+ self.assertEqual(restored[3] & mask, original[3] & mask)
52
+ finally:
53
+ if process.poll() is None:
54
+ process.kill()
55
+ process.communicate()
56
+ os.close(master)
57
+ os.close(slave)
@@ -0,0 +1,40 @@
1
+ import json
2
+ from pathlib import Path
3
+ import subprocess
4
+ import sys
5
+ import tempfile
6
+ import unittest
7
+
8
+ from carets import KeyMsg
9
+ from carets.testing import TestProgram
10
+ from peaches import TextWorkbench
11
+ from wispsy.reference import run_reference
12
+
13
+
14
+ class ProfileTests(unittest.IsolatedAsyncioTestCase):
15
+ async def test_typed_table_markdown_and_terminal_reuse_report_contract(self):
16
+ workbench, text = TextWorkbench(), " Hello WORLD "
17
+ self.assertEqual(workbench.table([text]).to_records(), [run_reference(text)])
18
+ self.assertIn("&lt;script&gt;", workbench.markdown(["<script>"]))
19
+ with tempfile.TemporaryDirectory() as directory:
20
+ model = workbench.terminal_model(text, checkpoint=str(Path(directory) / "terminal.json"))
21
+ test = TestProgram(model)
22
+ model.attach(test.program)
23
+ async with test:
24
+ await test.send(KeyMsg("tab"), KeyMsg(" "), KeyMsg("down"), KeyMsg(" "), KeyMsg("tab"), KeyMsg("enter"))
25
+ await test.wait_for(lambda: model.status == "completed")
26
+ self.assertEqual(model.outcome["outputs"], {"report": run_reference(text)})
27
+
28
+ async def test_installed_commands_pause_and_resume_in_separate_processes(self):
29
+ with tempfile.TemporaryDirectory() as directory:
30
+ checkpoint = str(Path(directory) / "progress.json")
31
+ def command(*options):
32
+ result = subprocess.run([sys.executable, "-I", "-m", "peaches", "report", " Hello WORLD ", "--checkpoint", checkpoint, *options], cwd=directory, capture_output=True)
33
+ self.assertEqual(result.returncode, 0, result.stderr)
34
+ return json.loads(result.stdout)
35
+ first = command("--pace", "function")
36
+ self.assertEqual((first["status"], first["completed"]), ("paused", ["normalize"]))
37
+ resumed = command("--resume")
38
+ self.assertEqual(resumed["status"], "completed")
39
+ self.assertEqual(resumed["run_id"], first["run_id"])
40
+ self.assertEqual(resumed["outputs"], {"report": run_reference(" Hello WORLD ")})
@@ -0,0 +1,43 @@
1
+ [project]
2
+ name = "peaches"
3
+ version = "0.1.0"
4
+ license = "MIT"
5
+ license-files = ["LICENSE"]
6
+ description = "A selected text workflow workbench over independent Python composition libraries."
7
+ readme = "README.md"
8
+ requires-python = ">=3.11"
9
+ dependencies = [
10
+ "wispsy>=0.0.1",
11
+ "scriptty>=0.1.0",
12
+ ]
13
+
14
+ [[project.authors]]
15
+ name = "ellainng"
16
+
17
+ [project.optional-dependencies]
18
+ cli = ["scriptty[cli]>=0.1.0"]
19
+ sequence = ["scriptty[sequence]>=0.1.0 ; python_version >= '3.14'"]
20
+ terminal = ["scriptty[terminal]>=0.1.0 ; python_version >= '3.14'"]
21
+ table = ["wispsy[table]>=0.0.1 ; python_version >= '3.14'"]
22
+
23
+ [project.scripts]
24
+ peaches = "peaches.cli:main"
25
+
26
+ [project.urls]
27
+ Source = "https://github.com/wispsy/peaches"
28
+ Issues = "https://github.com/wispsy/peaches/issues"
29
+ Project = "https://github.com/orgs/wispsy/projects/23"
30
+
31
+ [build-system]
32
+ requires = ["uv_build>=0.12.19,<0.13.0"]
33
+ build-backend = "uv_build"
34
+
35
+ [tool.uv.build-backend]
36
+ source-include = [
37
+ "LICENSE",
38
+ "tests/*.py",
39
+ "cli_tests/*.py",
40
+ "profile_tests/*.py",
41
+ "scripts/*.py",
42
+ "docs/*.md",
43
+ ]
@@ -0,0 +1,34 @@
1
+ [project]
2
+ name = "peaches"
3
+ version = "0.1.0"
4
+ license = "MIT"
5
+ license-files = ["LICENSE"]
6
+ description = "A selected text workflow workbench over independent Python composition libraries."
7
+ readme = "README.md"
8
+ requires-python = ">=3.11"
9
+ authors = [{ name = "ellainng" }]
10
+ dependencies = [
11
+ "wispsy>=0.0.1",
12
+ "scriptty>=0.1.0",
13
+ ]
14
+
15
+ [project.optional-dependencies]
16
+ cli = ["scriptty[cli]>=0.1.0"]
17
+ sequence = ["scriptty[sequence]>=0.1.0 ; python_version >= '3.14'"]
18
+ terminal = ["scriptty[terminal]>=0.1.0 ; python_version >= '3.14'"]
19
+ table = ["wispsy[table]>=0.0.1 ; python_version >= '3.14'"]
20
+
21
+ [project.scripts]
22
+ peaches = "peaches.cli:main"
23
+
24
+ [project.urls]
25
+ Source = "https://github.com/wispsy/peaches"
26
+ Issues = "https://github.com/wispsy/peaches/issues"
27
+ Project = "https://github.com/orgs/wispsy/projects/23"
28
+
29
+ [build-system]
30
+ requires = ["uv_build>=0.12.19,<0.13.0"]
31
+ build-backend = "uv_build"
32
+
33
+ [tool.uv.build-backend]
34
+ source-include = ["LICENSE", "tests/*.py", "cli_tests/*.py", "profile_tests/*.py", "scripts/*.py", "docs/*.md"]
@@ -0,0 +1,46 @@
1
+ """Install both workbench artifacts outside source and select optional profiles."""
2
+
3
+ import hashlib
4
+ import json
5
+ from pathlib import Path
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ import zipfile
10
+ from email.parser import BytesParser
11
+
12
+ ROOT = Path(__file__).resolve().parents[1]
13
+
14
+
15
+ def main():
16
+ directory = Path(sys.argv[1]).resolve()
17
+ artifacts = sorted([*directory.glob("*.whl"), *directory.glob("*.tar.gz")])
18
+ assert len(artifacts) == 2
19
+ wheel, = directory.glob("*.whl")
20
+ with zipfile.ZipFile(wheel) as archive:
21
+ assert "peaches/py.typed" in archive.namelist()
22
+ metadata, = [name for name in archive.namelist() if name.endswith("/METADATA")]
23
+ message = BytesParser().parsebytes(archive.read(metadata))
24
+ assert message["Name"] == "peaches" and message["Requires-Python"] == ">=3.11"
25
+ for artifact in artifacts:
26
+ with tempfile.TemporaryDirectory() as temporary:
27
+ outside = Path(temporary)
28
+ environment = outside / "env"
29
+ subprocess.run(["uv", "venv", str(environment), "--python", sys.executable], check=True)
30
+ python = environment / ("Scripts/python.exe" if sys.platform == "win32" else "bin/python")
31
+ def run(*command):
32
+ subprocess.run(command, check=True, cwd=outside)
33
+ run("uv", "pip", "install", "--python", str(python), str(artifact))
34
+ run(str(python), "-I", "-c", "import importlib.util, peaches; assert all(importlib.util.find_spec(name) is None for name in ('func_seq','carets','headless_spreadsheets','expose_interface'))")
35
+ run(str(python), "-I", "-m", "unittest", "discover", "-s", str(ROOT / "tests"), "-v")
36
+ run("uv", "pip", "install", "--python", str(python), f"{artifact}[cli]")
37
+ run(str(python), "-I", "-m", "unittest", "discover", "-s", str(ROOT / "cli_tests"), "-v")
38
+ if sys.version_info >= (3, 14):
39
+ run("uv", "pip", "install", "--python", str(python), f"{artifact}[cli,sequence,table,terminal]")
40
+ run(str(python), "-I", "-m", "unittest", "discover", "-s", str(ROOT / "profile_tests"), "-v")
41
+ run("uv", "pip", "check", "--python", str(python))
42
+ print(json.dumps({"schema": 1, "artifact": artifact.name, "sha256": hashlib.sha256(artifact.read_bytes()).hexdigest()}))
43
+
44
+
45
+ if __name__ == "__main__":
46
+ main()
@@ -0,0 +1,5 @@
1
+ """A curated text workbench with independently optional presentation/lifecycle."""
2
+
3
+ from .workbench import TextWorkbench
4
+
5
+ __all__ = ["TextWorkbench"]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ main()
@@ -0,0 +1,51 @@
1
+ """Expose the curated workbench as ordinary application commands."""
2
+
3
+ import contextlib
4
+ import sys
5
+
6
+ from expose_interface import Interface, UsageError, option
7
+ from moveset.core import canonical
8
+
9
+ from .workbench import TextWorkbench
10
+
11
+
12
+ def build_interface():
13
+ app = Interface("peaches", description="Inspect and run the selected text workbench.")
14
+
15
+ @app.command(name="inspect")
16
+ def inspect_workflow():
17
+ return {"schema": 1, "status": "inspected", "workflow": TextWorkbench().inspect()}
18
+
19
+ @app.command(name="report")
20
+ async def report(text: str, *, checkpoint: str | None = None, resume: bool = False,
21
+ retry: bool = False, restart: bool = False, pace: str = "auto"):
22
+ """Normalize/report text; optional checkpoints retain explicit recovery."""
23
+ return await TextWorkbench().report(text, checkpoint=checkpoint, resume=resume,
24
+ retry=retry, restart=restart, pace=pace)
25
+
26
+ @app.command(name="markdown")
27
+ @option("--text", dest="texts", multiple=True, required=True)
28
+ def markdown(*, texts: list[str]):
29
+ """Render typed reports through the optional table profile."""
30
+ return {"schema": 1, "status": "rendered", "format": "markdown", "text": TextWorkbench().markdown(texts)}
31
+
32
+ @app.command(name="review")
33
+ async def review(text: str, *, checkpoint: str):
34
+ """Select the optional interactive POSIX terminal profile."""
35
+ return await TextWorkbench().review(text, checkpoint=checkpoint)
36
+
37
+ return app
38
+
39
+
40
+ def main(argv=None):
41
+ try:
42
+ with contextlib.redirect_stdout(sys.stderr):
43
+ outcome = build_interface().invoke(argv)
44
+ if outcome is not None:
45
+ sys.stdout.buffer.write(canonical(outcome) + b"\n")
46
+ sys.stdout.buffer.flush()
47
+ except Exception as error:
48
+ record = {"schema": 1, "status": "error", "error": type(error).__name__, "message": str(error)}
49
+ sys.stderr.buffer.write(b"\n" + canonical(record) + b"\n")
50
+ sys.stderr.buffer.flush()
51
+ raise SystemExit(2 if isinstance(error, UsageError) else 1) from None
File without changes
@@ -0,0 +1,60 @@
1
+ """Application choices over the qualified reference and scripting API."""
2
+
3
+ import json
4
+ import sys
5
+
6
+ from scriptty import loads
7
+ from scriptty.application import execute
8
+ from wispsy.composition import text_workflow_document
9
+
10
+
11
+ class TextWorkbench:
12
+ """Inspect/report text without requiring a terminal, storage or HTTP service."""
13
+
14
+ def __init__(self, *, catalog_version="wispsy-text-v1"):
15
+ self.catalog_version = catalog_version
16
+ self.workflow = loads(json.dumps(text_workflow_document()), source="peaches:text-report")
17
+
18
+ def inspect(self):
19
+ return self.workflow.inspect()
20
+
21
+ def catalog(self):
22
+ """Explicitly assemble only the curated reference capabilities on demand."""
23
+ from wispsy.integration import build_catalog
24
+
25
+ return build_catalog()
26
+
27
+ async def report(self, text: str, *, checkpoint=None, resume=False, retry=False,
28
+ restart=False, pace="auto", control=None, observer=None):
29
+ """Return the same schema-1 outcome used by scripting/terminal hosts."""
30
+ return await execute(self.workflow, self.catalog(), catalog_version=self.catalog_version,
31
+ inputs={"text": text}, checkpoint=checkpoint, resume=resume, retry=retry,
32
+ restart=restart, pace=pace, control=control, observer=observer)
33
+
34
+ def table(self, texts: list[str]):
35
+ if sys.version_info < (3, 14):
36
+ raise ImportError("Install peaches[table] on Python 3.14+")
37
+ try:
38
+ from wispsy.tabular import normalize_table
39
+ except ImportError:
40
+ raise ImportError("Install peaches[table] on Python 3.14+") from None
41
+ return normalize_table(texts)
42
+
43
+ def markdown(self, texts: list[str]) -> str:
44
+ return self.table(texts).to_document().to_markdown()
45
+
46
+ def terminal_model(self, text: str, *, checkpoint):
47
+ try:
48
+ from scriptty.terminal import WorkflowTerminal
49
+ except ImportError:
50
+ raise ImportError("Install peaches[terminal] on Python 3.14+") from None
51
+ return WorkflowTerminal(self.workflow, self.catalog(), catalog_version=self.catalog_version,
52
+ checkpoint=checkpoint, inputs={"text": text})
53
+
54
+ async def review(self, text: str, *, checkpoint, **terminal_options):
55
+ try:
56
+ from scriptty.terminal import run_terminal
57
+ except ImportError:
58
+ raise ImportError("Install peaches[terminal] on Python 3.14+") from None
59
+ return await run_terminal(self.workflow, self.catalog(), catalog_version=self.catalog_version,
60
+ checkpoint=checkpoint, inputs={"text": text}, **terminal_options)
@@ -0,0 +1,22 @@
1
+ import unittest
2
+
3
+ from peaches import TextWorkbench
4
+ from wispsy.reference import run_reference
5
+
6
+
7
+ class WorkbenchTests(unittest.IsolatedAsyncioTestCase):
8
+ async def test_report_reuses_the_same_typed_reference_without_lifecycle(self):
9
+ workbench = TextWorkbench()
10
+ for text in (" Hello WORLD ", " Straße 🌍 ", ""):
11
+ outcome = await workbench.report(text)
12
+ self.assertEqual((outcome["schema"], outcome["status"], outcome["outputs"]),
13
+ (1, "completed", {"report": run_reference(text)}))
14
+ self.assertIsNone(outcome["run_id"])
15
+ with self.assertRaises(ValueError):
16
+ await workbench.report(2)
17
+
18
+ async def test_inspection_is_passive_and_returns_detached_metadata(self):
19
+ workbench = TextWorkbench()
20
+ workbench.inspect()["steps"].clear()
21
+ self.assertEqual(len(workbench.inspect()["steps"]), 2)
22
+ self.assertEqual([step["capability"] for step in workbench.inspect()["steps"]], ["text.normalize", "text.report"])