vitruvian-tool-kit 0.10.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.
@@ -0,0 +1,2 @@
1
+ .build/
2
+ .swiftpm/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VitruvianSoftware
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.
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.4
2
+ Name: vitruvian-tool-kit
3
+ Version: 0.10.0
4
+ Summary: Write a tool for the Vitruvian app in Python: declare its commands, handle them, call the app.
5
+ Project-URL: Repository, https://github.com/VitruvianSoftware/vitruvian-sdk
6
+ Project-URL: Documentation, https://github.com/VitruvianSoftware/vitruvian-sdk/blob/main/docs/writing-a-tool.md
7
+ Author: VitruvianSoftware
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: json-rpc,macos,tool,vitruvian
11
+ Classifier: Operating System :: MacOS
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+
16
+ # vitruvian-tool-kit
17
+
18
+ Write a tool for the [Vitruvian](https://vitruviansoftware.dev) Mac app in
19
+ Python. A tool is a folder with a `vitruvian-tool.json` and a program; the app
20
+ starts the program and talks to it over its standard input and output. This
21
+ kit is that conversation: you say what each command does, and call the app
22
+ from inside a command as an ordinary function call.
23
+
24
+ ```python
25
+ from vitruvian_tool_kit import Tool
26
+
27
+ tool = Tool(version="0.1.0")
28
+
29
+
30
+ @tool.command("hello")
31
+ def hello(app):
32
+ app.notify.hud(icon="hand.wave", message="Hello")
33
+
34
+
35
+ tool.run()
36
+ ```
37
+
38
+ - **One call per message.** `app.notify.hud(...)`, `app.clipboard.read_text()`,
39
+ `app.storage.get(key=...)`: each is one request of the protocol, named as
40
+ the protocol names it. The app may refuse any of them; the call then raises
41
+ `Refusal`. Raise `CommandRefused("why")` to refuse a command yourself.
42
+ - **Synchronous, on one thread.** A call to the app waits for its answer, ten
43
+ seconds at most. A handler must not be `async`.
44
+ - **Never `print`.** Standard output is the link to the app. Write to
45
+ standard error, or call `app.log(message=...)`.
46
+
47
+ ## Shipping it
48
+
49
+ The app starts a tool with Apple's `python3` (`/usr/bin/python3`, Python
50
+ 3.9), which has none of your packages. So the kit goes inside the tool's
51
+ folder, and the program finds it there:
52
+
53
+ ```sh
54
+ python3 -m pip install --target lib vitruvian-tool-kit
55
+ ```
56
+
57
+ ```python
58
+ import os, sys
59
+
60
+ HERE = os.path.dirname(os.path.realpath(__file__))
61
+ sys.path.insert(0, os.path.join(HERE, "..", "lib"))
62
+ ```
63
+
64
+ `vitruvian-tool init --template python-kit` writes a tool set up this way.
65
+ The kit needs nothing but the standard library and Python 3.9 or later.
66
+
67
+ The whole guide, the protocol and the other kits are in
68
+ [the SDK](https://github.com/VitruvianSoftware/vitruvian-sdk). MIT licence.
@@ -0,0 +1,53 @@
1
+ # vitruvian-tool-kit
2
+
3
+ Write a tool for the [Vitruvian](https://vitruviansoftware.dev) Mac app in
4
+ Python. A tool is a folder with a `vitruvian-tool.json` and a program; the app
5
+ starts the program and talks to it over its standard input and output. This
6
+ kit is that conversation: you say what each command does, and call the app
7
+ from inside a command as an ordinary function call.
8
+
9
+ ```python
10
+ from vitruvian_tool_kit import Tool
11
+
12
+ tool = Tool(version="0.1.0")
13
+
14
+
15
+ @tool.command("hello")
16
+ def hello(app):
17
+ app.notify.hud(icon="hand.wave", message="Hello")
18
+
19
+
20
+ tool.run()
21
+ ```
22
+
23
+ - **One call per message.** `app.notify.hud(...)`, `app.clipboard.read_text()`,
24
+ `app.storage.get(key=...)`: each is one request of the protocol, named as
25
+ the protocol names it. The app may refuse any of them; the call then raises
26
+ `Refusal`. Raise `CommandRefused("why")` to refuse a command yourself.
27
+ - **Synchronous, on one thread.** A call to the app waits for its answer, ten
28
+ seconds at most. A handler must not be `async`.
29
+ - **Never `print`.** Standard output is the link to the app. Write to
30
+ standard error, or call `app.log(message=...)`.
31
+
32
+ ## Shipping it
33
+
34
+ The app starts a tool with Apple's `python3` (`/usr/bin/python3`, Python
35
+ 3.9), which has none of your packages. So the kit goes inside the tool's
36
+ folder, and the program finds it there:
37
+
38
+ ```sh
39
+ python3 -m pip install --target lib vitruvian-tool-kit
40
+ ```
41
+
42
+ ```python
43
+ import os, sys
44
+
45
+ HERE = os.path.dirname(os.path.realpath(__file__))
46
+ sys.path.insert(0, os.path.join(HERE, "..", "lib"))
47
+ ```
48
+
49
+ `vitruvian-tool init --template python-kit` writes a tool set up this way.
50
+ The kit needs nothing but the standard library and Python 3.9 or later.
51
+
52
+ The whole guide, the protocol and the other kits are in
53
+ [the SDK](https://github.com/VitruvianSoftware/vitruvian-sdk). MIT licence.
@@ -0,0 +1,53 @@
1
+ # Copyright (c) 2026 VitruvianSoftware
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ [build-system]
22
+ requires = ["hatchling>=1.27"]
23
+ build-backend = "hatchling.build"
24
+
25
+ [project]
26
+ name = "vitruvian-tool-kit"
27
+ # The SDK's version: every kit is published at it on every release
28
+ # (release-please, component vitruvian-sdk).
29
+ version = "0.10.0"
30
+ description = "Write a tool for the Vitruvian app in Python: declare its commands, handle them, call the app."
31
+ readme = "README.md"
32
+ license = "MIT"
33
+ license-files = ["LICENSE"]
34
+ # 3.9: the python3 the Command Line Tools put on every Mac, which is the one
35
+ # the app finds when it starts a tool. The standard library only.
36
+ requires-python = ">=3.9"
37
+ authors = [{ name = "VitruvianSoftware" }]
38
+ keywords = ["vitruvian", "macos", "tool", "json-rpc"]
39
+ classifiers = [
40
+ "Programming Language :: Python :: 3",
41
+ "Operating System :: MacOS",
42
+ ]
43
+ dependencies = []
44
+
45
+ [project.urls]
46
+ Repository = "https://github.com/VitruvianSoftware/vitruvian-sdk"
47
+ Documentation = "https://github.com/VitruvianSoftware/vitruvian-sdk/blob/main/docs/writing-a-tool.md"
48
+
49
+ [tool.hatch.build.targets.wheel]
50
+ packages = ["vitruvian_tool_kit"]
51
+
52
+ [tool.hatch.build.targets.sdist]
53
+ include = ["vitruvian_tool_kit", "tests", "README.md", "LICENSE"]
@@ -0,0 +1,114 @@
1
+ # Copyright (c) 2026 VitruvianSoftware
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+ """The generated types read every example the protocol calls valid, and give
21
+ back what they read.
22
+
23
+ protocol/examples/ is what the schemas and the Swift types are both held to
24
+ (protocol_examples_test.py, ProtocolExamplesTests.swift). Reading the same
25
+ files here holds the generated Python to them too: a field the generator
26
+ dropped or misnamed fails here, by the example's own words.
27
+ """
28
+
29
+ import json
30
+ import os
31
+ import sys
32
+ import unittest
33
+
34
+ HERE = os.path.dirname(os.path.abspath(__file__))
35
+ sys.path.insert(0, os.path.dirname(HERE))
36
+ PROTOCOL = os.path.join(HERE, "..", "..", "..", "protocol")
37
+
38
+ from vitruvian_tool_kit import METHODS, ShapeError, ToolManifest # noqa: E402
39
+ from vitruvian_tool_kit.generated import messages # noqa: E402
40
+
41
+
42
+ def load(name):
43
+ with open(os.path.join(PROTOCOL, name), encoding="utf-8") as handle:
44
+ return json.load(handle)
45
+
46
+
47
+ def type_named(name):
48
+ title = {"empty": "EmptyObject", "error": "RPCErrorObject"}.get(
49
+ name, name[:1].upper() + name[1:]
50
+ )
51
+ return getattr(messages, title)
52
+
53
+
54
+ class GeneratedTests(unittest.TestCase):
55
+ def test_every_valid_message_reads_and_writes_back(self):
56
+ """What is written back is what was read, less a field the schema does
57
+ not name: the protocol says such a field is ignored."""
58
+ table = {m.method: m for m in METHODS}
59
+ named = {
60
+ name: set(node.get("properties", {}))
61
+ for name, node in load("messages.schema.json")["$defs"].items()
62
+ }
63
+
64
+ def known(name, value):
65
+ return {key: item for key, item in value.items() if key in named[name]}
66
+
67
+ read = 0
68
+ for example in load("examples/messages.json")["examples"]:
69
+ if not example["valid"]:
70
+ continue
71
+ message = example["message"]
72
+ row = table.get(example.get("answers", message.get("method")))
73
+ if row is None:
74
+ continue
75
+ with self.subTest(example["about"]):
76
+ if "method" in message and row.params:
77
+ value = type_named(row.params).from_json(message.get("params"))
78
+ self.assertEqual(
79
+ value.to_json(), known(row.params, message["params"])
80
+ )
81
+ read += 1
82
+ elif "result" in message:
83
+ value = type_named(row.result).from_json(message["result"])
84
+ self.assertEqual(
85
+ value.to_json(), known(row.result, message["result"])
86
+ )
87
+ read += 1
88
+ self.assertGreater(read, 20)
89
+
90
+ def test_a_wrong_field_is_named(self):
91
+ with self.assertRaises(ShapeError) as raised:
92
+ messages.NotifyHUDParams.from_json({"icon": "x", "message": 3})
93
+ self.assertEqual(raised.exception.where, "notifyHUDParams.message")
94
+
95
+ def test_every_valid_manifest_reads(self):
96
+ examples = load("examples/manifests.json")
97
+ read = 0
98
+ for example in examples["examples"]:
99
+ if not example["valid"]:
100
+ continue
101
+ manifest = dict(examples["base"])
102
+ for key, value in example["change"].items():
103
+ if value is None:
104
+ manifest.pop(key, None)
105
+ else:
106
+ manifest[key] = value
107
+ with self.subTest(example["about"]):
108
+ ToolManifest.from_json(manifest)
109
+ read += 1
110
+ self.assertGreater(read, 5)
111
+
112
+
113
+ if __name__ == "__main__":
114
+ unittest.main()