tipalti 0.0.1__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.
tipalti/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """tipalti — agent-first CLI package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import PackageNotFoundError
6
+ from importlib.metadata import version as _pkg_version
7
+
8
+ try:
9
+ __version__ = _pkg_version("tipalti")
10
+ except PackageNotFoundError: # pragma: no cover
11
+ __version__ = "0.0.0"
12
+
13
+ __all__ = ["__version__"]
tipalti/__main__.py ADDED
@@ -0,0 +1,10 @@
1
+ """Entry point for ``python -m tipalti``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ from tipalti.cli import main
8
+
9
+ if __name__ == "__main__":
10
+ sys.exit(main())
@@ -0,0 +1,84 @@
1
+ """Unified CLI entry point for tipalti.
2
+
3
+ Noun-based command groups and globals are registered here. Top-level globals
4
+ (``learn``, ``explain``) live under :mod:`tipalti.cli._commands`; per-noun
5
+ groups follow the same pattern.
6
+
7
+ Error-propagation contract: every handler raises
8
+ :class:`tipalti.cli._errors.AfiError` on failure; :func:`main` catches it
9
+ via :func:`_dispatch` and routes through :mod:`tipalti.cli._output`.
10
+ Unknown exceptions are wrapped so no Python traceback leaks.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import sys
17
+
18
+ from tipalti import __version__
19
+ from tipalti.cli._commands import explain as _explain_cmd
20
+ from tipalti.cli._commands import learn as _learn_cmd
21
+ from tipalti.cli._commands import whoami as _whoami_cmd
22
+ from tipalti.cli._errors import EXIT_USER_ERROR, AfiError
23
+ from tipalti.cli._output import emit_error
24
+
25
+
26
+ class _ArgumentParser(argparse.ArgumentParser):
27
+ """ArgumentParser that emits errors via our structured format."""
28
+
29
+ def error(self, message: str) -> None: # type: ignore[override]
30
+ err = AfiError(
31
+ code=EXIT_USER_ERROR,
32
+ message=message,
33
+ remediation=f"run '{self.prog} --help' to see valid arguments",
34
+ )
35
+ emit_error(err, json_mode=False)
36
+ raise SystemExit(err.code)
37
+
38
+
39
+ def _build_parser() -> argparse.ArgumentParser:
40
+ parser = _ArgumentParser(
41
+ prog="tipalti",
42
+ description="tipalti — agent-first CLI.",
43
+ )
44
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
45
+ sub = parser.add_subparsers(dest="command")
46
+
47
+ _learn_cmd.register(sub)
48
+ _explain_cmd.register(sub)
49
+ _whoami_cmd.register(sub)
50
+ # Register noun groups here:
51
+ # from tipalti.cli._commands import my_noun as _my_noun_group
52
+ # _my_noun_group.register(sub)
53
+
54
+ return parser
55
+
56
+
57
+ def _dispatch(args: argparse.Namespace) -> int:
58
+ json_mode = bool(getattr(args, "json", False))
59
+ try:
60
+ return args.func(args)
61
+ except AfiError as err:
62
+ emit_error(err, json_mode=json_mode)
63
+ return err.code
64
+ except Exception as err: # noqa: BLE001 - last-resort
65
+ wrapped = AfiError(
66
+ code=EXIT_USER_ERROR,
67
+ message=f"unexpected: {err.__class__.__name__}: {err}",
68
+ remediation="file a bug",
69
+ )
70
+ emit_error(wrapped, json_mode=json_mode)
71
+ return wrapped.code
72
+
73
+
74
+ def main(argv: list[str] | None = None) -> int:
75
+ parser = _build_parser()
76
+ args = parser.parse_args(argv)
77
+ if args.command is None:
78
+ parser.print_help()
79
+ return 0
80
+ return _dispatch(args)
81
+
82
+
83
+ if __name__ == "__main__":
84
+ sys.exit(main())
File without changes
@@ -0,0 +1,38 @@
1
+ """``tipalti explain <path>...`` — global markdown catalog lookup (stable-contract).
2
+
3
+ ``explain`` is global (not nested under a noun). It takes zero or more path
4
+ tokens and resolves them via the catalog in :mod:`tipalti.explain`.
5
+ Unknown paths raise :class:`AfiError` with a remediation hint.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+
12
+ from tipalti.cli._output import emit_result
13
+ from tipalti.explain import resolve
14
+
15
+
16
+ def cmd_explain(args: argparse.Namespace) -> int:
17
+ path = tuple(args.path) if args.path else ()
18
+ markdown = resolve(path)
19
+ json_mode = bool(getattr(args, "json", False))
20
+ if json_mode:
21
+ emit_result({"path": list(path), "markdown": markdown}, json_mode=True)
22
+ else:
23
+ emit_result(markdown, json_mode=False)
24
+ return 0
25
+
26
+
27
+ def register(sub: argparse._SubParsersAction) -> None:
28
+ p = sub.add_parser(
29
+ "explain",
30
+ help="Print markdown docs for a noun/verb path (e.g. 'tipalti explain cli foo').",
31
+ )
32
+ p.add_argument(
33
+ "path",
34
+ nargs="*",
35
+ help="Command path tokens; empty = root (same as 'tipalti').",
36
+ )
37
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
38
+ p.set_defaults(func=cmd_explain)
@@ -0,0 +1,82 @@
1
+ """``tipalti learn`` — the learnability affordance (shape-adapt)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from tipalti import __version__
8
+ from tipalti.cli._output import emit_result
9
+
10
+ _TEXT = """\
11
+ tipalti — CLI for Tipalti Solutions.
12
+
13
+ Purpose
14
+ -------
15
+ tipalti is a command-line interface for working with Tipalti Solutions.
16
+ The v0.0.1 surface ships only the agent-first affordances (`learn`,
17
+ `explain`) and an auth-probe stub (`whoami`); domain verbs that talk to
18
+ the Tipalti API land in subsequent releases.
19
+
20
+ Commands
21
+ --------
22
+ tipalti learn Print this self-teaching prompt. Supports --json.
23
+ tipalti explain <path>... Print markdown docs for any noun/verb path.
24
+ Supports --json.
25
+ tipalti whoami Print the active Tipalti principal. Stub for
26
+ now (always reports "unauthenticated") until
27
+ real Tipalti API auth is wired. Supports --json.
28
+
29
+ Machine-readable output
30
+ -----------------------
31
+ Every command that produces a listing or report supports --json. Errors in
32
+ JSON mode emit {"code", "message", "remediation"} to stderr. Stdout and
33
+ stderr are never mixed.
34
+
35
+ Exit-code policy
36
+ ----------------
37
+ 0 success
38
+ 1 user-input error (bad flag, bad path, missing arg)
39
+ 2 environment / setup error
40
+ 3+ reserved
41
+
42
+ More detail
43
+ -----------
44
+ tipalti explain tipalti
45
+ """
46
+
47
+
48
+ def _as_json_payload() -> dict[str, object]:
49
+ return {
50
+ "tool": "tipalti",
51
+ "version": __version__,
52
+ "purpose": "CLI for Tipalti Solutions.",
53
+ "commands": [
54
+ {"path": ["learn"], "summary": "Self-teaching prompt."},
55
+ {"path": ["explain"], "summary": "Markdown docs by path."},
56
+ {"path": ["whoami"], "summary": "Auth probe (stub in v0.0.1)."},
57
+ ],
58
+ "exit_codes": {
59
+ "0": "success",
60
+ "1": "user-input error",
61
+ "2": "environment/setup error",
62
+ },
63
+ "json_support": True,
64
+ "explain_pointer": "tipalti explain <path>",
65
+ }
66
+
67
+
68
+ def cmd_learn(args: argparse.Namespace) -> int:
69
+ if getattr(args, "json", False):
70
+ emit_result(_as_json_payload(), json_mode=True)
71
+ else:
72
+ emit_result(_TEXT, json_mode=False)
73
+ return 0
74
+
75
+
76
+ def register(sub: argparse._SubParsersAction) -> None:
77
+ p = sub.add_parser(
78
+ "learn",
79
+ help="Print a structured self-teaching prompt for agent consumers.",
80
+ )
81
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
82
+ p.set_defaults(func=cmd_learn)
@@ -0,0 +1,29 @@
1
+ """``tipalti whoami`` — auth probe stub (Tipalti-specific).
2
+
3
+ Returns ``unauthenticated`` until real Tipalti API auth lands. Supports
4
+ ``--json``. Exit code is always ``0`` (probe, not gate).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+
11
+ from tipalti.cli._output import emit_result
12
+
13
+
14
+ def cmd_whoami(args: argparse.Namespace) -> int:
15
+ payload: dict[str, object] = {"status": "unauthenticated", "principal": None}
16
+ if getattr(args, "json", False):
17
+ emit_result(payload, json_mode=True)
18
+ else:
19
+ emit_result("unauthenticated", json_mode=False)
20
+ return 0
21
+
22
+
23
+ def register(sub: argparse._SubParsersAction) -> None:
24
+ p = sub.add_parser(
25
+ "whoami",
26
+ help="Print the active Tipalti principal (stub in v0.0.1).",
27
+ )
28
+ p.add_argument("--json", action="store_true", help="Emit structured JSON.")
29
+ p.set_defaults(func=cmd_whoami)
tipalti/cli/_errors.py ADDED
@@ -0,0 +1,41 @@
1
+ """AfiError and exit-code policy (stable-contract — copy verbatim).
2
+
3
+ Every failure inside tipalti raises :class:`AfiError`. The CLI entry
4
+ point catches it and exits with :attr:`AfiError.code`. Guarantees:
5
+
6
+ * no Python traceback leaks to stderr;
7
+ * every error has shape ``{code, message, remediation}``;
8
+ * the exit-code policy is centralised.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+
15
+ # Exit-code policy (documented in ``tipalti learn`` output).
16
+ # 0 = success
17
+ # 1 = user-input error (bad flag, bad path, missing arg)
18
+ # 2 = environment / setup error
19
+ # 3+ = reserved
20
+ EXIT_SUCCESS = 0
21
+ EXIT_USER_ERROR = 1
22
+ EXIT_ENV_ERROR = 2
23
+
24
+
25
+ @dataclass
26
+ class AfiError(Exception):
27
+ """Structured error with a remediation hint for agents."""
28
+
29
+ code: int
30
+ message: str
31
+ remediation: str = ""
32
+
33
+ def __post_init__(self) -> None:
34
+ super().__init__(self.message)
35
+
36
+ def to_dict(self) -> dict[str, object]:
37
+ return {
38
+ "code": self.code,
39
+ "message": self.message,
40
+ "remediation": self.remediation,
41
+ }
tipalti/cli/_output.py ADDED
@@ -0,0 +1,53 @@
1
+ """stdout / stderr helpers with a strict split (stable-contract).
2
+
3
+ Rule: **results go to stdout, diagnostics and errors go to stderr.** Agents
4
+ parsing output can rely on this invariant. JSON mode routes structured
5
+ payloads to the same streams — never mixes them.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import sys
12
+ from typing import Any, TextIO
13
+
14
+ from tipalti.cli._errors import AfiError
15
+
16
+
17
+ def emit_result(data: Any, *, json_mode: bool, stream: TextIO | None = None) -> None:
18
+ """Write a command result to stdout (or ``stream``)."""
19
+ s = stream if stream is not None else sys.stdout
20
+ if json_mode:
21
+ json.dump(data, s, ensure_ascii=False)
22
+ s.write("\n")
23
+ return
24
+ text = data if isinstance(data, str) else str(data)
25
+ s.write(text)
26
+ if not text.endswith("\n"):
27
+ s.write("\n")
28
+
29
+
30
+ def emit_error(err: AfiError, *, json_mode: bool, stream: TextIO | None = None) -> None:
31
+ """Write an :class:`AfiError` to stderr.
32
+
33
+ Text mode renders as two lines when a remediation is present::
34
+
35
+ error: <message>
36
+ hint: <remediation>
37
+
38
+ The ``hint:`` prefix is required by the agent-first error rubric.
39
+ """
40
+ s = stream if stream is not None else sys.stderr
41
+ if json_mode:
42
+ json.dump(err.to_dict(), s, ensure_ascii=False)
43
+ s.write("\n")
44
+ return
45
+ s.write(f"error: {err.message}\n")
46
+ if err.remediation:
47
+ s.write(f"hint: {err.remediation}\n")
48
+
49
+
50
+ def emit_diagnostic(message: str, *, stream: TextIO | None = None) -> None:
51
+ """Write a human diagnostic (progress, summary) to stderr."""
52
+ s = stream if stream is not None else sys.stderr
53
+ s.write(message if message.endswith("\n") else message + "\n")
@@ -0,0 +1,24 @@
1
+ """Explain catalog — markdown keyed by command-path tuples (stable-contract).
2
+
3
+ Every noun/verb registered in the CLI should have a catalog entry.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from tipalti.cli._errors import EXIT_USER_ERROR, AfiError
9
+ from tipalti.explain.catalog import ENTRIES
10
+
11
+
12
+ def resolve(path: tuple[str, ...]) -> str:
13
+ if path in ENTRIES:
14
+ return ENTRIES[path]
15
+ display = " ".join(path) if path else "<root>"
16
+ raise AfiError(
17
+ code=EXIT_USER_ERROR,
18
+ message=f"no explain entry for: {display}",
19
+ remediation="list known entries with: tipalti explain tipalti",
20
+ )
21
+
22
+
23
+ def known_paths() -> list[tuple[str, ...]]:
24
+ return list(ENTRIES.keys())
@@ -0,0 +1,88 @@
1
+ """Markdown catalog for ``tipalti explain <path>``.
2
+
3
+ Each entry is verbatim markdown. Keys are command-path tuples. The empty
4
+ tuple and ``("tipalti",)`` both resolve to the root entry.
5
+
6
+ Keep bodies self-contained: an agent reading one entry should get enough
7
+ context without chaining reads.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ _ROOT = """\
13
+ # tipalti
14
+
15
+ `tipalti` is the command-line interface for Tipalti Solutions. v0.0.1
16
+ ships the agent-first affordances (`learn`, `explain`) and an auth-probe
17
+ stub (`whoami`); domain verbs that exercise the Tipalti API land in later
18
+ releases. The CLI is scaffolded from the AgentCulture sibling pattern.
19
+
20
+ ## Verbs
21
+
22
+ - `tipalti learn` — structured self-teaching prompt.
23
+ - `tipalti explain <path>` — markdown docs for any noun/verb.
24
+ - `tipalti whoami` — auth probe stub (returns `unauthenticated` for now).
25
+
26
+ ## Exit-code policy
27
+
28
+ - `0` success
29
+ - `1` user-input error
30
+ - `2` environment / setup error
31
+ - `3+` reserved
32
+
33
+ ## See also
34
+
35
+ - `tipalti explain learn`
36
+ - `tipalti explain explain`
37
+ - `tipalti explain whoami`
38
+ """
39
+
40
+ _LEARN = """\
41
+ # tipalti learn
42
+
43
+ Prints a structured self-teaching prompt covering tipalti's purpose,
44
+ command map, exit-code policy, `--json` support, and `explain` pointer.
45
+
46
+ ## Usage
47
+
48
+ tipalti learn
49
+ tipalti learn --json
50
+ """
51
+
52
+ _EXPLAIN = """\
53
+ # tipalti explain <path>
54
+
55
+ Prints markdown documentation for any noun/verb path. Unlike `--help`
56
+ (terse, positional), `explain` is global and addressable by path.
57
+
58
+ ## Usage
59
+
60
+ tipalti explain tipalti
61
+ tipalti explain learn
62
+ tipalti explain --json <path>
63
+ """
64
+
65
+ _WHOAMI = """\
66
+ # tipalti whoami
67
+
68
+ Auth probe. Reports the active Tipalti principal — or
69
+ `unauthenticated` when no credentials are configured.
70
+
71
+ In v0.0.1 this is a stub: it always reports `unauthenticated` until real
72
+ Tipalti API authentication lands. Exit code is always `0` (probe, not
73
+ gate).
74
+
75
+ ## Usage
76
+
77
+ tipalti whoami
78
+ tipalti whoami --json
79
+ """
80
+
81
+
82
+ ENTRIES: dict[tuple[str, ...], str] = {
83
+ (): _ROOT,
84
+ ("tipalti",): _ROOT,
85
+ ("learn",): _LEARN,
86
+ ("explain",): _EXPLAIN,
87
+ ("whoami",): _WHOAMI,
88
+ }
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: tipalti
3
+ Version: 0.0.1
4
+ Summary: Tipalti — CLI for Tipalti Solutions.
5
+ Project-URL: Homepage, https://github.com/agentculture/tipalti
6
+ Project-URL: Issues, https://github.com/agentculture/tipalti/issues
7
+ Author: AgentCulture
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Software Development
15
+ Requires-Python: >=3.12
16
+ Description-Content-Type: text/markdown
17
+
18
+ # tipalti
19
+
20
+ CLI for Tipalti Solutions — scaffolded from the AgentCulture sibling pattern.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ uv tool install tipalti
26
+ # or
27
+ pipx install tipalti
28
+ ```
29
+
30
+ ## Quickstart
31
+
32
+ ```bash
33
+ tipalti --version # 0.0.1
34
+ tipalti learn # structured self-teaching prompt for agents
35
+ tipalti learn --json # machine-readable form
36
+ tipalti explain tipalti # root markdown doc
37
+ tipalti explain whoami # docs for the whoami verb
38
+ tipalti whoami # auth probe (v0.0.1: always "unauthenticated")
39
+ ```
40
+
41
+ ## Status
42
+
43
+ Pre-1.0. v0.0.1 ships only the agent-first affordances (`learn`, `explain`)
44
+ and an auth-probe stub (`whoami`). Domain verbs that exercise the Tipalti
45
+ API land in later releases.
46
+
47
+ ## Development
48
+
49
+ ```bash
50
+ uv sync
51
+ uv run pytest -n auto -v
52
+ uv run tipalti --version
53
+ ```
54
+
55
+ See [`CLAUDE.md`](CLAUDE.md) for the full project shape, conventions, and
56
+ publish flow. The repo follows the AgentCulture sibling pattern — see
57
+ [`agentculture/steward`](https://github.com/agentculture/steward) and
58
+ [`agentculture/afi-cli`](https://github.com/agentculture/afi-cli) for the
59
+ canonical templates.
60
+
61
+ ## License
62
+
63
+ MIT — see [`LICENSE`](LICENSE).
@@ -0,0 +1,16 @@
1
+ tipalti/__init__.py,sha256=-H-5b8b8V2Rn7I1E3ZlJze5KLw2yr-Nf8r1F7S2h5Ok,337
2
+ tipalti/__main__.py,sha256=Bl8Q62cwGXpU4pEJiFj06w0B6c0hwsFFxxZLP0eYsqE,172
3
+ tipalti/cli/__init__.py,sha256=CEJpNAtRxtgGL1tDHOmE6yzQ4X2ZTeskX5n4mNVhixk,2653
4
+ tipalti/cli/_errors.py,sha256=q7hKpKRQOFJz7fT-gb6xUQFZJvtHfT7kmZoA7tP15zI,1082
5
+ tipalti/cli/_output.py,sha256=k0qJSqklpjLkyaxQUxnIcengu6zV9pkHlkDMHxl9JEw,1707
6
+ tipalti/cli/_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ tipalti/cli/_commands/explain.py,sha256=9SyHgKlg4mxplWBHFKxVnAP41nT8h4MPKh0-qzoiHJ8,1228
8
+ tipalti/cli/_commands/learn.py,sha256=j3JEi1ZnuhT98ik0Jpk6Z70gZR8piZIhM7gMHpSTCjc,2553
9
+ tipalti/cli/_commands/whoami.py,sha256=yhScSykttCUkJkHOI5-d0EhR_cuin0R3ZPfP--TQJCI,878
10
+ tipalti/explain/__init__.py,sha256=fKs4EXaxOjSxXcu5W5IJWvDj-DGVuY9LPjh3wek_zSU,698
11
+ tipalti/explain/catalog.py,sha256=szniTmRNqGqJBdXMF3-VfYzaVsCEKplTjtKjrRS3BMI,2086
12
+ tipalti-0.0.1.dist-info/METADATA,sha256=m2HKSLYwrJZJr8Trtyzykwfv1PTUpbLYg-o-oX8h0b8,1764
13
+ tipalti-0.0.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
14
+ tipalti-0.0.1.dist-info/entry_points.txt,sha256=0m3thyJ9MSyzfW-42hHKXEO6s4etTBgyf4NmSSM5vyg,45
15
+ tipalti-0.0.1.dist-info/licenses/LICENSE,sha256=d-o3g1Varo3ruZBbZUlkdoDVJBoIhutjarID0c0OIyU,1069
16
+ tipalti-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tipalti = tipalti.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 agentculture
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.