mkit-cli 0.4.0__tar.gz → 0.5.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.
Files changed (88) hide show
  1. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/CHANGELOG.md +9 -0
  2. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/PKG-INFO +1 -1
  3. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/docs/commands.md +1 -1
  4. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/docs/config.md +18 -5
  5. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/__init__.py +1 -1
  6. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/guide.py +2 -1
  7. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/guides.py +42 -8
  8. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_guides.py +31 -0
  9. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/.gitattributes +0 -0
  10. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  11. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  12. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/.github/workflows/publish.yml +0 -0
  13. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/.github/workflows/tests.yml +0 -0
  14. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/.gitignore +0 -0
  15. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/AGENTS.md +0 -0
  16. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/CONTRIBUTING.md +0 -0
  17. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/LICENSE +0 -0
  18. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/README.md +0 -0
  19. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/SECURITY.md +0 -0
  20. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/docs/about-screen.md +0 -0
  21. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/docs/app-ui.md +0 -0
  22. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/docs/aso.md +0 -0
  23. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/docs/in-app-review.md +0 -0
  24. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/docs/in-app-update.md +0 -0
  25. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/docs/news-topics.md +0 -0
  26. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/docs/store-art.md +0 -0
  27. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/docs/store.md +0 -0
  28. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/docs/support-replies.md +0 -0
  29. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/pyproject.toml +0 -0
  30. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/scripts/gen_docs.py +0 -0
  31. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/scripts/release.py +0 -0
  32. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/art.py +0 -0
  33. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/aso.py +0 -0
  34. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/cli.py +0 -0
  35. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/cloud/__init__.py +0 -0
  36. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/cloud/auth.py +0 -0
  37. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/cloud/fbproject.py +0 -0
  38. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/cloud/firebase.py +0 -0
  39. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/cloud/insight.py +0 -0
  40. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/cloud/money.py +0 -0
  41. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/cloud/play.py +0 -0
  42. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/cloud/store.py +0 -0
  43. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/__init__.py +0 -0
  44. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/art.py +0 -0
  45. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/aso.py +0 -0
  46. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/dev.py +0 -0
  47. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/doctor.py +0 -0
  48. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/firebase_cmds.py +0 -0
  49. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/init.py +0 -0
  50. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/insight.py +0 -0
  51. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/meta.py +0 -0
  52. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/money.py +0 -0
  53. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/release.py +0 -0
  54. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/selfcmd.py +0 -0
  55. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/commands/store.py +0 -0
  56. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/config.py +0 -0
  57. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/data/art/art.toml +0 -0
  58. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/data/art/en.json +0 -0
  59. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/data/datasafety-questions.csv +0 -0
  60. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/datasafety.py +0 -0
  61. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/device.py +0 -0
  62. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/docgen.py +0 -0
  63. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/errors.py +0 -0
  64. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/git.py +0 -0
  65. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/hooks.py +0 -0
  66. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/kinds.py +0 -0
  67. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/notify.py +0 -0
  68. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/output.py +0 -0
  69. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/proc.py +0 -0
  70. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/registry.py +0 -0
  71. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/safety.py +0 -0
  72. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/secrets.py +0 -0
  73. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/src/mkit/toolchain.py +0 -0
  74. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/conftest.py +0 -0
  75. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/fakeplay.py +0 -0
  76. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_art.py +0 -0
  77. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_aso.py +0 -0
  78. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_core.py +0 -0
  79. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_datasafety.py +0 -0
  80. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_device_logs.py +0 -0
  81. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_docs.py +0 -0
  82. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_firebase_apps.py +0 -0
  83. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_network_errors.py +0 -0
  84. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_products.py +0 -0
  85. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_release.py +0 -0
  86. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_store.py +0 -0
  87. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/tests/test_studio.py +0 -0
  88. {mkit_cli-0.4.0 → mkit_cli-0.5.0}/uv.lock +0 -0
@@ -7,6 +7,15 @@ it rather than doing the wrong thing.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.5.0 — 2026-10-23
11
+
12
+ **An app can keep its own version of any guide.** A file named like the guide
13
+ in the app's repo — `docs/guides/about-screen.md`, `docs/guides/app-ui.md`, or
14
+ under `[guide] app_dir` — is added after the guide and the studio's own rules,
15
+ under *This app's own rules*, and wins over both. `[guide] replace = ["ui"]` in
16
+ the app's `mkit.toml` makes that file the whole guide. Placeholders work in it,
17
+ `{{app.name}}` included, and `mkit guide --json` reports `app` and `replaced`.
18
+
10
19
  ## 0.4.0 — 2026-10-23
11
20
 
12
21
  **Guides.** `mkit guide` reads the guides every app follows — the About screen,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mkit-cli
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Release Android apps to Google Play from the command line: build, upload, listing, screenshots, data safety, in-app products, vitals and Firebase, in one tool built for humans and AI agents.
5
5
  Project-URL: Homepage, https://github.com/almasumdev/mkit-cli
6
6
  Project-URL: Documentation, https://github.com/almasumdev/mkit-cli/blob/main/docs/commands.md
@@ -1,6 +1,6 @@
1
1
  # mkit command reference
2
2
 
3
- Generated from the command registry (mkit 0.4.x). Do not edit by hand: run `python scripts/gen_docs.py`.
3
+ Generated from the command registry (mkit 0.5.x). Do not edit by hand: run `python scripts/gen_docs.py`.
4
4
 
5
5
  Global options, accepted before or after any command: `--json`, `--human`, `--project PATH`, `--dry-run`, `--strict`, `--yes --confirm PACKAGE`, `--no-color`.
6
6
 
@@ -128,13 +128,26 @@ signoff = "The <App> team" # the line above the email address
128
128
 
129
129
  [guide]
130
130
  house_dir = "~/studio/house" # this studio's own rules, one file per guide
131
+ # In an app's mkit.toml:
132
+ # app_dir = "docs/guides" # this app's own rules (the default)
133
+ # replace = ["ui"] # guides this app does its own way, start to finish
131
134
  ```
132
135
 
133
- A studio's own decisions beside a guide — its worked examples, a look its apps
134
- share, a rule it chose differently — go in `house_dir`, in a file named like the
135
- guide's (`about-screen.md`, `in-app-review.md`, ...). `mkit guide` appends it
136
- under *This studio's own rules*, which win where the two disagree. Without
137
- `house_dir`, mkit looks in `docs/house/` beside its own source checkout.
136
+ A guide is read in three layers, each winning over the one before:
137
+
138
+ 1. **The guide** — the same for everyone.
139
+ 2. **The studio's rules** — `house_dir/<file>`: its worked examples, a look its
140
+ apps share, a rule it chose differently. Appended under *This studio's own
141
+ rules*. Without `house_dir`, mkit looks in `docs/house/` beside its own
142
+ source checkout.
143
+ 3. **The app's rules** — `<app>/docs/guides/<file>` in the app's own repo, or
144
+ `app_dir`. Appended last, under *This app's own rules*: its own layout idea,
145
+ a part it does differently, the components to use. An app that lists the
146
+ guide in `replace` uses its file alone, and neither layer above is read.
147
+
148
+ The file is named like the guide's: `about-screen.md`, `app-ui.md`,
149
+ `in-app-review.md` — `mkit guide` lists them. Placeholders work in all three,
150
+ `{{app.name}}` included.
138
151
 
139
152
  ## Store listing file
140
153
 
@@ -2,4 +2,4 @@
2
2
 
3
3
  # The one place the engine version lives (pyproject.toml reads it from here).
4
4
  # Semantic versioning; see CONTRIBUTING.md.
5
- __version__ = '0.4.0'
5
+ __version__ = '0.5.0'
@@ -25,7 +25,8 @@ def guide(ctx, args):
25
25
  out.table([(r['name'], r['about']) for r in rows], ('name', 'what it covers'))
26
26
  out.line()
27
27
  out.line('Read one with `mkit guide <name>`. Your details come from [studio] and [about] '
28
- 'in ~/.mkit/config.toml or this app\'s mkit.toml.', 'dim')
28
+ 'in ~/.mkit/config.toml or this app\'s mkit.toml; an app\'s own rules go in '
29
+ 'its docs/guides/<file>.', 'dim')
29
30
  return {'guides': rows}
30
31
 
31
32
  try:
@@ -25,6 +25,10 @@ for it and nobody else - in a house folder: the file of the same name, which is
25
25
  appended after the guide under a heading saying it overrides what it
26
26
  contradicts. By default that folder is docs/house in the engine's own git
27
27
  checkout; [guide] house_dir points anywhere else.
28
+
29
+ An app may keep its own on top, in its own repo: docs/guides/<file> (or
30
+ [guide] app_dir), appended last and winning over both. An app that names a
31
+ guide in [guide] replace uses its own file alone.
28
32
  """
29
33
  import os
30
34
  import re
@@ -129,20 +133,50 @@ def _keep_parts(text, enabled):
129
133
  return '\n'.join(out)
130
134
 
131
135
 
136
+ def app_file(filename, project):
137
+ """The app's own version of a guide, in its repo: docs/guides/ unless [guide] app_dir says."""
138
+ if project is None:
139
+ return None
140
+ folder = project.root / project.get('guide.app_dir', 'docs/guides')
141
+ found = folder / filename
142
+ return found if found.is_file() else None
143
+
144
+
145
+ def _append(text, heading, note, extra):
146
+ return (text.rstrip('\n') + f'\n\n---\n\n## {heading}\n\n*{note}*\n\n'
147
+ + extra.read_text(encoding='utf-8').strip() + '\n')
148
+
149
+
132
150
  def render(name, project=None, raw=False):
133
- """The guide as text, filled in for this studio. Returns (text, info)."""
151
+ """The guide as text, filled in for this studio and app. Returns (text, info).
152
+
153
+ Three layers, each winning over the one before: the guide, the studio's
154
+ house rules, the app's own file. An app that lists the guide in
155
+ [guide] replace uses its own file alone.
156
+ """
134
157
  filename, _ = GUIDES[name]
135
158
  path = _locate(filename)
136
159
  if path is None:
137
160
  raise FileNotFoundError(filename)
138
- text = path.read_text(encoding='utf-8')
139
161
  get = settings(project)
140
- house = house_file(filename, get)
141
- if house:
142
- text = (text.rstrip('\n') + '\n\n---\n\n## This studio\'s own rules\n\n'
143
- '*Kept by this studio, beside the guide above. Where the two disagree, '
144
- 'these win.*\n\n' + house.read_text(encoding='utf-8').strip() + '\n')
145
- info = {'guide': name, 'file': str(path), 'house': str(house) if house else None, 'unset': []}
162
+ app = app_file(filename, project)
163
+ replaced = app is not None and name in (get('guide.replace') or ())
164
+ if replaced:
165
+ text, house = app.read_text(encoding='utf-8'), None
166
+ else:
167
+ text = path.read_text(encoding='utf-8')
168
+ house = house_file(filename, get)
169
+ if house:
170
+ text = _append(text, "This studio's own rules",
171
+ 'Kept by this studio, beside the guide above. Where the two disagree, '
172
+ 'these win.', house)
173
+ if app:
174
+ text = _append(text, "This app's own rules",
175
+ "Kept in this app's repo. Where they disagree with anything above, "
176
+ 'they win.', app)
177
+ info = {'guide': name, 'file': str(app if replaced else path),
178
+ 'house': str(house) if house else None, 'app': str(app) if app else None,
179
+ 'replaced': replaced, 'unset': []}
146
180
  if raw:
147
181
  return text, info
148
182
 
@@ -109,3 +109,34 @@ def test_the_wheel_packs_every_guide():
109
109
  pytest.skip('this build does not package the guides')
110
110
  for filename, _ in guides.GUIDES.values():
111
111
  assert f'"docs/{filename}" = "mkit/_guides/{filename}"' in text, filename
112
+
113
+
114
+ def test_an_app_adds_its_own_rules_last(capsys, isolated, project, monkeypatch, tmp_path):
115
+ house = tmp_path / 'house'
116
+ house.mkdir()
117
+ (house / 'app-ui.md').write_text('Studio look.\n', encoding='utf-8')
118
+ monkeypatch.setenv('MKIT_GUIDE_HOUSE_DIR', str(house))
119
+ (project / 'docs' / 'guides').mkdir(parents=True)
120
+ (project / 'docs' / 'guides' / 'app-ui.md').write_text('Cards everywhere in {{app.name}}.\n',
121
+ encoding='utf-8')
122
+ monkeypatch.chdir(project)
123
+ code, env = run(capsys, 'guide', 'ui')
124
+ text = env['result']['text']
125
+ assert code == 0 and env['result']['app'] and not env['result']['replaced']
126
+ assert '## Colours from one place' in text
127
+ assert text.index('Studio look.') < text.index("## This app's own rules")
128
+ assert text.rstrip().endswith('Cards everywhere in Demo.')
129
+
130
+
131
+ def test_an_app_can_replace_a_guide_outright(capsys, isolated, no_house, project, monkeypatch):
132
+ with open(project / 'mkit.toml', 'a', encoding='utf-8') as f:
133
+ f.write('[guide]\napp_dir = "design"\nreplace = ["about"]\n')
134
+ (project / 'design').mkdir()
135
+ (project / 'design' / 'about-screen.md').write_text('# Our About page\n\nOne line.\n',
136
+ encoding='utf-8')
137
+ monkeypatch.chdir(project)
138
+ code, env = run(capsys, 'guide', 'about')
139
+ assert code == 0 and env['result']['replaced']
140
+ assert env['result']['text'].strip() == '# Our About page\n\nOne line.'
141
+ code, env = run(capsys, 'guide', 'ui')
142
+ assert env['result']['app'] is None and '## Colours from one place' in env['result']['text']
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes