podojo-cli 0.8.0__tar.gz → 0.8.2__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.
Files changed (39) hide show
  1. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/CHANGELOG.md +10 -0
  2. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/PKG-INFO +13 -2
  3. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/pyproject.toml +15 -2
  4. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/projects.py +37 -0
  5. podojo_cli-0.8.2/tests/test_projects.py +147 -0
  6. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/uv.lock +1 -1
  7. podojo_cli-0.8.0/tests/test_projects.py +0 -72
  8. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/.github/workflows/publish.yml +0 -0
  9. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/.gitignore +0 -0
  10. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/CLAUDE.md +0 -0
  11. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/README.md +0 -0
  12. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/__init__.py +0 -0
  13. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/client.py +0 -0
  14. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/__init__.py +0 -0
  15. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/auth.py +0 -0
  16. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/gdrive.py +0 -0
  17. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/interviews.py +0 -0
  18. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/showreel.py +0 -0
  19. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/synth.py +0 -0
  20. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/transcripts.py +0 -0
  21. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/usertests.py +0 -0
  22. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/videos.py +0 -0
  23. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/config.py +0 -0
  24. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/gdrive/__init__.py +0 -0
  25. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/gdrive/list.py +0 -0
  26. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/gdrive/upload.py +0 -0
  27. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/main.py +0 -0
  28. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/synth/__init__.py +0 -0
  29. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/synth/driver.py +0 -0
  30. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/synth/session.py +0 -0
  31. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/version_check.py +0 -0
  32. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/video/__init__.py +0 -0
  33. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/video/showreel.py +0 -0
  34. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/tests/conftest.py +0 -0
  35. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/tests/test_auth.py +0 -0
  36. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/tests/test_gdrive.py +0 -0
  37. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/tests/test_interviews.py +0 -0
  38. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/tests/test_showreel.py +0 -0
  39. {podojo_cli-0.8.0 → podojo_cli-0.8.2}/tests/test_usertests.py +0 -0
@@ -5,6 +5,16 @@ All notable changes to the Podojo CLI will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org).
7
7
 
8
+ ## [0.8.2] - 2026-05-14
9
+
10
+ ### Changed
11
+ - Enriched PyPI package metadata: added `keywords`, `classifiers`, author email, and a `Changelog` project URL.
12
+
13
+ ## [0.8.1] - 2026-05-13
14
+
15
+ ### Changed
16
+ - `projects upload-doc` strips embedded images (base64 reference definitions, inline `![](...)`, reference-style `![][ref]`, and `<img>` tags) before upload. Google-Docs-exported markdown often inlines images as multi-MB base64 blobs; the CLI now prints the count and size reduction.
17
+
8
18
  ## [0.8.0] - 2026-05-13
9
19
 
10
20
  ### Added
@@ -1,12 +1,23 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: podojo-cli
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: CLI for the Podojo user research platform
5
5
  Project-URL: Homepage, https://github.com/podojo/cli-podojo
6
6
  Project-URL: Source, https://github.com/podojo/cli-podojo
7
7
  Project-URL: Issues, https://github.com/podojo/cli-podojo/issues
8
- Author: Jochen Ade
8
+ Project-URL: Changelog, https://github.com/podojo/cli-podojo/blob/main/CHANGELOG.md
9
+ Author-email: Jochen Ade <jochen.ade@podojo.com>
9
10
  License-Expression: MIT
11
+ Keywords: cli,podojo,transcripts,user-research,ux-research
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Intended Audience :: Other Audience
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Utilities
10
21
  Requires-Python: >=3.12
11
22
  Requires-Dist: google-api-python-client>=2.0
12
23
  Requires-Dist: google-auth>=2.0
@@ -1,11 +1,23 @@
1
1
  [project]
2
2
  name = "podojo-cli"
3
- version = "0.8.0"
3
+ version = "0.8.2"
4
4
  description = "CLI for the Podojo user research platform"
5
5
  readme = "README.md"
6
6
  license = "MIT"
7
7
  requires-python = ">=3.12"
8
- authors = [{ name = "Jochen Ade" }]
8
+ authors = [{ name = "Jochen Ade", email = "jochen.ade@podojo.com" }]
9
+ keywords = ["podojo", "ux-research", "user-research", "cli", "transcripts"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Environment :: Console",
13
+ "Intended Audience :: End Users/Desktop",
14
+ "Intended Audience :: Other Audience",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Topic :: Utilities",
20
+ ]
9
21
  dependencies = [
10
22
  "typer>=0.15",
11
23
  "httpx>=0.28",
@@ -23,6 +35,7 @@ synth = ["playwright>=1.40"]
23
35
  Homepage = "https://github.com/podojo/cli-podojo"
24
36
  Source = "https://github.com/podojo/cli-podojo"
25
37
  Issues = "https://github.com/podojo/cli-podojo/issues"
38
+ Changelog = "https://github.com/podojo/cli-podojo/blob/main/CHANGELOG.md"
26
39
 
27
40
  [project.scripts]
28
41
  podojo = "podojo_cli.main:app"
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from pathlib import Path
2
3
 
3
4
  import httpx
@@ -18,6 +19,33 @@ CLI_TO_API_DOC_TYPE = {
18
19
  UPLOADABLE_DOC_TYPES = {"brief", "final"}
19
20
  MARKDOWN_SUFFIXES = {".md", ".markdown"}
20
21
 
22
+ # Reference-style image definition with a data: URL — `[label]: <data:image/png;base64,...>`
23
+ # These pack base64-encoded images into a single line and dominate file size.
24
+ DATA_URI_REF_DEF_RE = re.compile(
25
+ r"^\s*\[[^\]\n]+\]:\s+<?data:[^\n>]*>?[^\n]*\n?",
26
+ re.MULTILINE,
27
+ )
28
+ INLINE_IMG_RE = re.compile(r"!\[[^\]]*\]\([^)]*\)")
29
+ REF_IMG_RE = re.compile(r"!\[[^\]]*\]\[[^\]]*\]")
30
+ HTML_IMG_RE = re.compile(r"<img\b[^>]*/?>", re.IGNORECASE)
31
+
32
+
33
+ def strip_images(text: str) -> tuple[str, int]:
34
+ """Remove image markdown and HTML img tags. Returns (cleaned_text, count_removed)."""
35
+ count = 0
36
+ for pattern in (DATA_URI_REF_DEF_RE, INLINE_IMG_RE, REF_IMG_RE, HTML_IMG_RE):
37
+ text, n = pattern.subn("", text)
38
+ count += n
39
+ return text, count
40
+
41
+
42
+ def _human_size(n: int) -> str:
43
+ if n >= 1024 * 1024:
44
+ return f"{n / 1024 / 1024:.1f} MB"
45
+ if n >= 1024:
46
+ return f"{n / 1024:.1f} KB"
47
+ return f"{n} B"
48
+
21
49
 
22
50
  @app.command("list")
23
51
  def list_projects(
@@ -86,6 +114,15 @@ def upload_doc(
86
114
  console.print(f"[red]Error:[/red] File is not valid UTF-8 text: {file}")
87
115
  raise typer.Exit(1)
88
116
 
117
+ original_size = len(content.encode("utf-8"))
118
+ content, image_count = strip_images(content)
119
+ new_size = len(content.encode("utf-8"))
120
+ if image_count:
121
+ console.print(
122
+ f"Stripped [bold]{image_count}[/bold] image(s) "
123
+ f"({_human_size(original_size)} → {_human_size(new_size)})"
124
+ )
125
+
89
126
  api_type = CLI_TO_API_DOC_TYPE[doc_type]
90
127
  client = PodojoClient()
91
128
  try:
@@ -0,0 +1,147 @@
1
+ from podojo_cli.commands.projects import strip_images
2
+ from podojo_cli.main import app
3
+
4
+
5
+ def test_list_projects(runner, httpx_mock):
6
+ httpx_mock.add_response(
7
+ url="http://test.local/api/v1/projects?skip=0&limit=50",
8
+ json={
9
+ "projects": [
10
+ {"name": "Alpha", "brief": "First project"},
11
+ {"name": "Beta", "brief": "Second project"},
12
+ ],
13
+ "total": 2,
14
+ "skip": 0,
15
+ "limit": 50,
16
+ },
17
+ )
18
+
19
+ result = runner.invoke(app, ["projects", "list"])
20
+
21
+ assert result.exit_code == 0
22
+ assert "Alpha" in result.output
23
+ assert "Beta" in result.output
24
+
25
+
26
+ def test_list_projects_empty(runner, httpx_mock):
27
+ httpx_mock.add_response(
28
+ url="http://test.local/api/v1/projects?skip=0&limit=50",
29
+ json={
30
+ "projects": [],
31
+ "total": 0,
32
+ "skip": 0,
33
+ "limit": 50,
34
+ },
35
+ )
36
+
37
+ result = runner.invoke(app, ["projects", "list"])
38
+
39
+ assert result.exit_code == 0
40
+
41
+
42
+ def test_create_project(runner, httpx_mock):
43
+ httpx_mock.add_response(
44
+ method="POST",
45
+ url="http://test.local/api/v1/projects",
46
+ match_json={"name": "Gamma", "brief": "Third project"},
47
+ json={
48
+ "id": "abc123",
49
+ "name": "Gamma",
50
+ "brief": "Third project",
51
+ "message": "Project created successfully",
52
+ },
53
+ )
54
+
55
+ result = runner.invoke(app, ["projects", "create", "Gamma", "--brief", "Third project"])
56
+
57
+ assert result.exit_code == 0
58
+ assert "Gamma" in result.output
59
+ assert "abc123" in result.output
60
+
61
+
62
+ def test_create_project_duplicate(runner, httpx_mock):
63
+ httpx_mock.add_response(
64
+ method="POST",
65
+ url="http://test.local/api/v1/projects",
66
+ status_code=409,
67
+ json={"detail": "Project with this name already exists"},
68
+ )
69
+
70
+ result = runner.invoke(app, ["projects", "create", "Gamma"])
71
+
72
+ assert result.exit_code == 1
73
+ assert "already exists" in result.output
74
+
75
+
76
+ def test_strip_images_base64_reference_defs():
77
+ text = (
78
+ "# Title\n\n"
79
+ "Paragraph with ![][image1] inline reference.\n\n"
80
+ "[image1]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA>\n"
81
+ "[image2]: <data:image/jpeg;base64,/9j/4AAQSkZJRg==>\n"
82
+ )
83
+ cleaned, count = strip_images(text)
84
+ assert "data:image" not in cleaned
85
+ assert "![]" not in cleaned
86
+ assert "image1" not in cleaned
87
+ assert "# Title" in cleaned
88
+ assert "Paragraph with inline reference." in cleaned
89
+ assert count == 3
90
+
91
+
92
+ def test_strip_images_inline_and_html():
93
+ text = (
94
+ "Inline: ![alt](https://example.com/x.png)\n"
95
+ 'HTML: <img src="https://example.com/y.jpg" alt="y">\n'
96
+ "Reference: ![alt][ref]\n"
97
+ )
98
+ cleaned, count = strip_images(text)
99
+ assert "example.com" not in cleaned
100
+ assert "<img" not in cleaned
101
+ assert "![alt]" not in cleaned
102
+ assert count == 3
103
+
104
+
105
+ def test_strip_images_keeps_regular_links():
106
+ text = "See [the doc](https://example.com/doc) and [ref][label]."
107
+ cleaned, count = strip_images(text)
108
+ assert cleaned == text
109
+ assert count == 0
110
+
111
+
112
+ def test_upload_doc_strips_images_before_upload(runner, tmp_path, httpx_mock):
113
+ md = (
114
+ "# Brief\n\nBody text ![][image1] continues.\n\n"
115
+ "[image1]: <data:image/png;base64,AAAA>\n"
116
+ )
117
+ file = tmp_path / "brief.md"
118
+ file.write_text(md, encoding="utf-8")
119
+
120
+ httpx_mock.add_response(
121
+ method="PUT",
122
+ url="http://test.local/api/v1/projects/Alpha/documents/research_brief",
123
+ match_json={"content": "# Brief\n\nBody text continues.\n"},
124
+ json={"ok": True},
125
+ )
126
+
127
+ result = runner.invoke(app, ["projects", "upload-doc", "Alpha", str(file), "--type", "brief"])
128
+
129
+ assert result.exit_code == 0
130
+ assert "Stripped" in result.output
131
+ assert "2 image" in result.output
132
+
133
+
134
+ def test_upload_doc_no_images_no_strip_message(runner, tmp_path, httpx_mock):
135
+ file = tmp_path / "brief.md"
136
+ file.write_text("# Brief\n\nJust text, no images.\n", encoding="utf-8")
137
+
138
+ httpx_mock.add_response(
139
+ method="PUT",
140
+ url="http://test.local/api/v1/projects/Alpha/documents/research_brief",
141
+ json={"ok": True},
142
+ )
143
+
144
+ result = runner.invoke(app, ["projects", "upload-doc", "Alpha", str(file), "--type", "brief"])
145
+
146
+ assert result.exit_code == 0
147
+ assert "Stripped" not in result.output
@@ -538,7 +538,7 @@ wheels = [
538
538
 
539
539
  [[package]]
540
540
  name = "podojo-cli"
541
- version = "0.7.0"
541
+ version = "0.8.1"
542
542
  source = { editable = "." }
543
543
  dependencies = [
544
544
  { name = "google-api-python-client" },
@@ -1,72 +0,0 @@
1
- from podojo_cli.main import app
2
-
3
-
4
- def test_list_projects(runner, httpx_mock):
5
- httpx_mock.add_response(
6
- url="http://test.local/api/v1/projects?skip=0&limit=50",
7
- json={
8
- "projects": [
9
- {"name": "Alpha", "brief": "First project"},
10
- {"name": "Beta", "brief": "Second project"},
11
- ],
12
- "total": 2,
13
- "skip": 0,
14
- "limit": 50,
15
- },
16
- )
17
-
18
- result = runner.invoke(app, ["projects", "list"])
19
-
20
- assert result.exit_code == 0
21
- assert "Alpha" in result.output
22
- assert "Beta" in result.output
23
-
24
-
25
- def test_list_projects_empty(runner, httpx_mock):
26
- httpx_mock.add_response(
27
- url="http://test.local/api/v1/projects?skip=0&limit=50",
28
- json={
29
- "projects": [],
30
- "total": 0,
31
- "skip": 0,
32
- "limit": 50,
33
- },
34
- )
35
-
36
- result = runner.invoke(app, ["projects", "list"])
37
-
38
- assert result.exit_code == 0
39
-
40
-
41
- def test_create_project(runner, httpx_mock):
42
- httpx_mock.add_response(
43
- method="POST",
44
- url="http://test.local/api/v1/projects",
45
- match_json={"name": "Gamma", "brief": "Third project"},
46
- json={
47
- "id": "abc123",
48
- "name": "Gamma",
49
- "brief": "Third project",
50
- "message": "Project created successfully",
51
- },
52
- )
53
-
54
- result = runner.invoke(app, ["projects", "create", "Gamma", "--brief", "Third project"])
55
-
56
- assert result.exit_code == 0
57
- assert "Gamma" in result.output
58
- assert "abc123" in result.output
59
-
60
-
61
- def test_create_project_duplicate(runner, httpx_mock):
62
- httpx_mock.add_response(
63
- method="POST",
64
- url="http://test.local/api/v1/projects",
65
- status_code=409,
66
- json={"detail": "Project with this name already exists"},
67
- )
68
-
69
- result = runner.invoke(app, ["projects", "create", "Gamma"])
70
-
71
- assert result.exit_code == 1
72
- assert "already exists" in result.output
File without changes
File without changes
File without changes
File without changes