artificial-intelligence-usage 0.0.2__tar.gz → 0.0.4__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.
- {artificial_intelligence_usage-0.0.2 → artificial_intelligence_usage-0.0.4}/PKG-INFO +3 -2
- {artificial_intelligence_usage-0.0.2 → artificial_intelligence_usage-0.0.4}/README.md +2 -1
- {artificial_intelligence_usage-0.0.2 → artificial_intelligence_usage-0.0.4}/pyproject.toml +1 -1
- {artificial_intelligence_usage-0.0.2 → artificial_intelligence_usage-0.0.4}/src/artificial_intelligence_usage.egg-info/PKG-INFO +3 -2
- {artificial_intelligence_usage-0.0.2 → artificial_intelligence_usage-0.0.4}/src/artificial_intelligence_usage.py +60 -10
- {artificial_intelligence_usage-0.0.2 → artificial_intelligence_usage-0.0.4}/tests/test_artificial_intelligence_usage.py +66 -29
- {artificial_intelligence_usage-0.0.2 → artificial_intelligence_usage-0.0.4}/setup.cfg +0 -0
- {artificial_intelligence_usage-0.0.2 → artificial_intelligence_usage-0.0.4}/src/artificial_intelligence_usage.egg-info/SOURCES.txt +0 -0
- {artificial_intelligence_usage-0.0.2 → artificial_intelligence_usage-0.0.4}/src/artificial_intelligence_usage.egg-info/dependency_links.txt +0 -0
- {artificial_intelligence_usage-0.0.2 → artificial_intelligence_usage-0.0.4}/src/artificial_intelligence_usage.egg-info/entry_points.txt +0 -0
- {artificial_intelligence_usage-0.0.2 → artificial_intelligence_usage-0.0.4}/src/artificial_intelligence_usage.egg-info/top_level.txt +0 -0
- {artificial_intelligence_usage-0.0.2 → artificial_intelligence_usage-0.0.4}/tests/test_publish_pypi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: artificial-intelligence-usage
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: Analyze usage of artificial intelligence tool.
|
|
5
5
|
Author: Kaizten Analytics
|
|
6
6
|
License-Expression: LicenseRef-Proprietary
|
|
@@ -17,7 +17,8 @@ latest session followed by daily session, turn, token, and rate-limit usage.
|
|
|
17
17
|
## Behavior
|
|
18
18
|
|
|
19
19
|
- Reads rollout files below `~/.codex/sessions`.
|
|
20
|
-
-
|
|
20
|
+
- Shows the installed Codex and Claude CLI versions when those commands are
|
|
21
|
+
available.
|
|
21
22
|
- Shows status details from the most recently modified session.
|
|
22
23
|
- Reports sessions from the last 14 calendar days by default, including today.
|
|
23
24
|
- Counts user turns and cumulative tokens for each session.
|
|
@@ -6,7 +6,8 @@ latest session followed by daily session, turn, token, and rate-limit usage.
|
|
|
6
6
|
## Behavior
|
|
7
7
|
|
|
8
8
|
- Reads rollout files below `~/.codex/sessions`.
|
|
9
|
-
-
|
|
9
|
+
- Shows the installed Codex and Claude CLI versions when those commands are
|
|
10
|
+
available.
|
|
10
11
|
- Shows status details from the most recently modified session.
|
|
11
12
|
- Reports sessions from the last 14 calendar days by default, including today.
|
|
12
13
|
- Counts user turns and cumulative tokens for each session.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: artificial-intelligence-usage
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: Analyze usage of artificial intelligence tool.
|
|
5
5
|
Author: Kaizten Analytics
|
|
6
6
|
License-Expression: LicenseRef-Proprietary
|
|
@@ -17,7 +17,8 @@ latest session followed by daily session, turn, token, and rate-limit usage.
|
|
|
17
17
|
## Behavior
|
|
18
18
|
|
|
19
19
|
- Reads rollout files below `~/.codex/sessions`.
|
|
20
|
-
-
|
|
20
|
+
- Shows the installed Codex and Claude CLI versions when those commands are
|
|
21
|
+
available.
|
|
21
22
|
- Shows status details from the most recently modified session.
|
|
22
23
|
- Reports sessions from the last 14 calendar days by default, including today.
|
|
23
24
|
- Counts user turns and cumulative tokens for each session.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Analyze recent Codex
|
|
2
|
+
"""Analyze recent sessions of artificial intelligence tools (Codex, Claude, etc.) and report usage information."""
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
import argparse
|
|
7
7
|
import json
|
|
8
8
|
import shutil
|
|
9
|
+
import subprocess
|
|
9
10
|
from collections import Counter, defaultdict
|
|
10
11
|
from datetime import date, datetime, timedelta
|
|
11
12
|
from pathlib import Path
|
|
@@ -13,13 +14,7 @@ from typing import Any
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
DEFAULT_DAYS = 14
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def print_unavailable_tools() -> None:
|
|
19
|
-
"""Print a message for each supported AI CLI that is not on PATH."""
|
|
20
|
-
for command, name in (("codex", "Codex"), ("claude", "Claude")):
|
|
21
|
-
if shutil.which(command) is None:
|
|
22
|
-
print(f"{name} is not available.")
|
|
17
|
+
SUPPORTED_TOOLS = (("Codex", "codex"), ("Claude", "claude"))
|
|
23
18
|
|
|
24
19
|
|
|
25
20
|
def positive_int(value: str) -> int:
|
|
@@ -35,6 +30,53 @@ def positive_int(value: str) -> int:
|
|
|
35
30
|
return number
|
|
36
31
|
|
|
37
32
|
|
|
33
|
+
def installed_tool_version(command: str) -> str | None:
|
|
34
|
+
"""Return an installed command's version output, or ``None`` if absent."""
|
|
35
|
+
executable = shutil.which(command)
|
|
36
|
+
if executable is None:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
result = subprocess.run(
|
|
41
|
+
[executable, "--version"],
|
|
42
|
+
capture_output=True,
|
|
43
|
+
check=False,
|
|
44
|
+
text=True,
|
|
45
|
+
timeout=5,
|
|
46
|
+
)
|
|
47
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
48
|
+
return "unknown"
|
|
49
|
+
|
|
50
|
+
output = result.stdout.strip() or result.stderr.strip()
|
|
51
|
+
if not output:
|
|
52
|
+
return "unknown"
|
|
53
|
+
|
|
54
|
+
return " ".join(output.splitlines()[0].split())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def detect_installed_tool_versions() -> dict[str, str]:
|
|
58
|
+
"""Return versions for the supported AI command-line tools that exist."""
|
|
59
|
+
versions = {}
|
|
60
|
+
for name, command in SUPPORTED_TOOLS:
|
|
61
|
+
version = installed_tool_version(command)
|
|
62
|
+
if version is not None:
|
|
63
|
+
versions[name] = version
|
|
64
|
+
return versions
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def print_installed_tool_versions(versions: dict[str, str]) -> None:
|
|
68
|
+
"""Print installed AI command-line tool versions when any are present."""
|
|
69
|
+
if not versions:
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
print("Installed AI tools")
|
|
73
|
+
print("-" * 57)
|
|
74
|
+
for name, _command in SUPPORTED_TOOLS:
|
|
75
|
+
if name in versions:
|
|
76
|
+
print(f"{name + ':':<16} {versions[name]}")
|
|
77
|
+
print()
|
|
78
|
+
|
|
79
|
+
|
|
38
80
|
def format_timestamp(value: str | int | float | None) -> str:
|
|
39
81
|
"""Format an ISO or Unix timestamp in the local timezone."""
|
|
40
82
|
try:
|
|
@@ -268,7 +310,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
268
310
|
return parser
|
|
269
311
|
|
|
270
312
|
|
|
271
|
-
def run_report(
|
|
313
|
+
def run_report(
|
|
314
|
+
sessions_dir: Path,
|
|
315
|
+
days: int,
|
|
316
|
+
today: date | None = None,
|
|
317
|
+
tool_versions: dict[str, str] | None = None,
|
|
318
|
+
) -> int:
|
|
272
319
|
"""Analyze sessions and print the status and daily usage report."""
|
|
273
320
|
current_day = today or date.today()
|
|
274
321
|
first_day = current_day - timedelta(days=days - 1)
|
|
@@ -277,6 +324,10 @@ def run_report(sessions_dir: Path, days: int, today: date | None = None) -> int:
|
|
|
277
324
|
token_counts: Counter[str] = Counter()
|
|
278
325
|
last_session_by_date: dict[str, Path] = {}
|
|
279
326
|
|
|
327
|
+
if tool_versions is None:
|
|
328
|
+
tool_versions = detect_installed_tool_versions()
|
|
329
|
+
print_installed_tool_versions(tool_versions)
|
|
330
|
+
|
|
280
331
|
all_session_files = list(sessions_dir.glob("*/*/*/rollout-*.jsonl"))
|
|
281
332
|
latest_session = (
|
|
282
333
|
max(all_session_files, key=lambda path: path.stat().st_mtime)
|
|
@@ -360,7 +411,6 @@ def run_report(sessions_dir: Path, days: int, today: date | None = None) -> int:
|
|
|
360
411
|
def main(argv: list[str] | None = None) -> int:
|
|
361
412
|
"""Run the artificial-intelligence-usage command."""
|
|
362
413
|
args = build_parser().parse_args(argv)
|
|
363
|
-
print_unavailable_tools()
|
|
364
414
|
return run_report(Path.home() / ".codex" / "sessions", args.days)
|
|
365
415
|
|
|
366
416
|
|
|
@@ -73,6 +73,63 @@ class ArtificialIntelligenceUsageTests(unittest.TestCase):
|
|
|
73
73
|
with self.assertRaisesRegex(Exception, "must be an integer"):
|
|
74
74
|
artificial_intelligence_usage.positive_int("nope")
|
|
75
75
|
|
|
76
|
+
def test_installed_tool_version_returns_none_when_command_is_absent(self):
|
|
77
|
+
with patch.object(
|
|
78
|
+
artificial_intelligence_usage.shutil, "which", return_value=None
|
|
79
|
+
):
|
|
80
|
+
self.assertIsNone(
|
|
81
|
+
artificial_intelligence_usage.installed_tool_version("codex")
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def test_installed_tool_version_reads_version_from_command(self):
|
|
85
|
+
completed = artificial_intelligence_usage.subprocess.CompletedProcess(
|
|
86
|
+
args=["/tools/codex", "--version"],
|
|
87
|
+
returncode=0,
|
|
88
|
+
stdout="codex-cli 1.2.3\n",
|
|
89
|
+
stderr="",
|
|
90
|
+
)
|
|
91
|
+
with (
|
|
92
|
+
patch.object(
|
|
93
|
+
artificial_intelligence_usage.shutil,
|
|
94
|
+
"which",
|
|
95
|
+
return_value="/tools/codex",
|
|
96
|
+
),
|
|
97
|
+
patch.object(
|
|
98
|
+
artificial_intelligence_usage.subprocess,
|
|
99
|
+
"run",
|
|
100
|
+
return_value=completed,
|
|
101
|
+
) as run,
|
|
102
|
+
):
|
|
103
|
+
version = artificial_intelligence_usage.installed_tool_version("codex")
|
|
104
|
+
|
|
105
|
+
self.assertEqual(version, "codex-cli 1.2.3")
|
|
106
|
+
run.assert_called_once_with(
|
|
107
|
+
["/tools/codex", "--version"],
|
|
108
|
+
capture_output=True,
|
|
109
|
+
check=False,
|
|
110
|
+
text=True,
|
|
111
|
+
timeout=5,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def test_report_includes_installed_codex_and_claude_versions(self):
|
|
115
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
116
|
+
output = io.StringIO()
|
|
117
|
+
with contextlib.redirect_stdout(output):
|
|
118
|
+
artificial_intelligence_usage.run_report(
|
|
119
|
+
Path(directory),
|
|
120
|
+
days=14,
|
|
121
|
+
today=date(2026, 10, 4),
|
|
122
|
+
tool_versions={
|
|
123
|
+
"Codex": "codex-cli 1.2.3",
|
|
124
|
+
"Claude": "2.0.1 (Claude Code)",
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
report = output.getvalue()
|
|
129
|
+
self.assertIn("Installed AI tools", report)
|
|
130
|
+
self.assertIn("Codex: codex-cli 1.2.3", report)
|
|
131
|
+
self.assertIn("Claude: 2.0.1 (Claude Code)", report)
|
|
132
|
+
|
|
76
133
|
def test_analyze_session_ignores_malformed_lines_and_uses_maximum_tokens(self):
|
|
77
134
|
with tempfile.TemporaryDirectory() as directory:
|
|
78
135
|
session_file = Path(directory) / "rollout-test.jsonl"
|
|
@@ -118,7 +175,10 @@ class ArtificialIntelligenceUsageTests(unittest.TestCase):
|
|
|
118
175
|
output = io.StringIO()
|
|
119
176
|
with contextlib.redirect_stdout(output):
|
|
120
177
|
result = artificial_intelligence_usage.run_report(
|
|
121
|
-
Path(directory),
|
|
178
|
+
Path(directory),
|
|
179
|
+
days=14,
|
|
180
|
+
today=date(2026, 10, 4),
|
|
181
|
+
tool_versions={},
|
|
122
182
|
)
|
|
123
183
|
|
|
124
184
|
self.assertEqual(result, 0)
|
|
@@ -170,7 +230,10 @@ class ArtificialIntelligenceUsageTests(unittest.TestCase):
|
|
|
170
230
|
output = io.StringIO()
|
|
171
231
|
with contextlib.redirect_stdout(output):
|
|
172
232
|
result = artificial_intelligence_usage.run_report(
|
|
173
|
-
sessions_dir,
|
|
233
|
+
sessions_dir,
|
|
234
|
+
days=2,
|
|
235
|
+
today=date(2026, 10, 4),
|
|
236
|
+
tool_versions={},
|
|
174
237
|
)
|
|
175
238
|
|
|
176
239
|
report = output.getvalue()
|
|
@@ -184,40 +247,14 @@ class ArtificialIntelligenceUsageTests(unittest.TestCase):
|
|
|
184
247
|
def test_main_can_be_called_repeatedly(self):
|
|
185
248
|
with patch.object(
|
|
186
249
|
artificial_intelligence_usage, "run_report", return_value=0
|
|
187
|
-
) as run_report
|
|
188
|
-
artificial_intelligence_usage, "print_unavailable_tools"
|
|
189
|
-
) as print_unavailable_tools:
|
|
250
|
+
) as run_report:
|
|
190
251
|
self.assertEqual(artificial_intelligence_usage.main(["--days", "3"]), 0)
|
|
191
252
|
self.assertEqual(artificial_intelligence_usage.main(["--days", "5"]), 0)
|
|
192
253
|
|
|
193
254
|
self.assertEqual(run_report.call_count, 2)
|
|
194
|
-
self.assertEqual(print_unavailable_tools.call_count, 2)
|
|
195
255
|
self.assertEqual(run_report.call_args_list[0].args[1], 3)
|
|
196
256
|
self.assertEqual(run_report.call_args_list[1].args[1], 5)
|
|
197
257
|
|
|
198
|
-
def test_unavailable_tools_prints_a_message_for_codex_and_claude(self):
|
|
199
|
-
output = io.StringIO()
|
|
200
|
-
with patch.object(
|
|
201
|
-
artificial_intelligence_usage.shutil, "which", return_value=None
|
|
202
|
-
), contextlib.redirect_stdout(output):
|
|
203
|
-
artificial_intelligence_usage.print_unavailable_tools()
|
|
204
|
-
|
|
205
|
-
self.assertEqual(
|
|
206
|
-
output.getvalue(),
|
|
207
|
-
"Codex is not available.\nClaude is not available.\n",
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
def test_unavailable_tools_does_not_print_for_installed_commands(self):
|
|
211
|
-
output = io.StringIO()
|
|
212
|
-
with patch.object(
|
|
213
|
-
artificial_intelligence_usage.shutil,
|
|
214
|
-
"which",
|
|
215
|
-
side_effect=lambda command: f"/usr/bin/{command}",
|
|
216
|
-
), contextlib.redirect_stdout(output):
|
|
217
|
-
artificial_intelligence_usage.print_unavailable_tools()
|
|
218
|
-
|
|
219
|
-
self.assertEqual(output.getvalue(), "")
|
|
220
|
-
|
|
221
258
|
|
|
222
259
|
if __name__ == "__main__":
|
|
223
260
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|