base-cli 0.1.0__tar.gz → 0.3.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.
- {base_cli-0.1.0/lib/python/base_cli.egg-info → base_cli-0.3.0}/PKG-INFO +166 -140
- {base_cli-0.1.0 → base_cli-0.3.0}/README.md +161 -138
- base_cli-0.3.0/VERSION +1 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli/__init__.py +32 -10
- base_cli-0.3.0/lib/python/base_cli/_dependencies.py +15 -0
- base_cli-0.3.0/lib/python/base_cli/_private_files.py +51 -0
- base_cli-0.3.0/lib/python/base_cli/_runtime.py +131 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli/app.py +74 -87
- base_cli-0.3.0/lib/python/base_cli/command_filters.py +70 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli/command_protocol.py +60 -61
- base_cli-0.3.0/lib/python/base_cli/config.py +28 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli/context.py +7 -8
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli/exit_codes.py +1 -1
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli/history.py +63 -144
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli/logging.py +22 -13
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli/output.py +6 -10
- base_cli-0.3.0/lib/python/base_cli/paths.py +90 -0
- base_cli-0.3.0/lib/python/base_cli/profile.py +147 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli/testing.py +21 -25
- {base_cli-0.1.0 → base_cli-0.3.0/lib/python/base_cli.egg-info}/PKG-INFO +166 -140
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli.egg-info/SOURCES.txt +8 -6
- {base_cli-0.1.0 → base_cli-0.3.0}/pyproject.toml +4 -1
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_app_log_retention.py +84 -19
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_app_run.py +45 -11
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_app_runtime_boundary.py +32 -15
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_app_runtime_errors.py +18 -16
- base_cli-0.3.0/tests/test_command_filters.py +36 -0
- base_cli-0.3.0/tests/test_command_protocol.py +202 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_context_workspace.py +25 -14
- base_cli-0.3.0/tests/test_generic_core.py +80 -0
- base_cli-0.3.0/tests/test_history.py +61 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_logging.py +26 -3
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_output.py +8 -0
- base_cli-0.3.0/tests/test_paths.py +86 -0
- base_cli-0.3.0/tests/test_profile.py +119 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_public_api.py +29 -13
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_testing.py +83 -17
- base_cli-0.1.0/VERSION +0 -1
- base_cli-0.1.0/lib/python/base_cli/_runtime.py +0 -89
- base_cli-0.1.0/lib/python/base_cli/command_filters.py +0 -37
- base_cli-0.1.0/lib/python/base_cli/config.py +0 -270
- base_cli-0.1.0/lib/python/base_cli/ide_schema.py +0 -74
- base_cli-0.1.0/lib/python/base_cli/paths.py +0 -136
- base_cli-0.1.0/tests/test_base_cli.py +0 -1015
- base_cli-0.1.0/tests/test_command_protocol.py +0 -147
- base_cli-0.1.0/tests/test_history.py +0 -398
- base_cli-0.1.0/tests/test_ide_schema.py +0 -48
- base_cli-0.1.0/tests/test_user_config_github.py +0 -95
- base_cli-0.1.0/tests/test_user_config_workspace.py +0 -24
- {base_cli-0.1.0 → base_cli-0.3.0}/LICENSE +0 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli/inspection.py +0 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli/py.typed +0 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli/redaction.py +0 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli.egg-info/dependency_links.txt +0 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli.egg-info/requires.txt +0 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/lib/python/base_cli.egg-info/top_level.txt +0 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/setup.cfg +0 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_app_dry_run.py +0 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_app_quiet.py +0 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_app_subcommands.py +0 -0
- {base_cli-0.1.0 → base_cli-0.3.0}/tests/test_inspection.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: base-cli
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A small,
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A small, consumer-neutral Python CLI framework
|
|
5
5
|
Author: Base Foundry
|
|
6
6
|
License: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/basefoundry/base-cli
|
|
@@ -10,6 +10,9 @@ Project-URL: Issues, https://github.com/basefoundry/base-cli/issues
|
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
16
|
Classifier: Programming Language :: Python :: 3
|
|
14
17
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -44,18 +47,18 @@ documented in [`docs/releasing.md`](docs/releasing.md). The package exposes
|
|
|
44
47
|
The package is distributed under the Apache License 2.0. Base itself remains
|
|
45
48
|
licensed separately under AGPL-3.0-or-later.
|
|
46
49
|
|
|
47
|
-
`base_cli` is
|
|
48
|
-
|
|
50
|
+
`base_cli` is a small Python framework for writing command-line tools with
|
|
51
|
+
a consistent lifecycle. It is designed to be embedded by applications rather
|
|
52
|
+
than to define an application's project model. Base is one consumer of the
|
|
53
|
+
library, not part of its generic contract.
|
|
49
54
|
|
|
50
55
|
It is intentionally thin. Click still owns argument parsing and command
|
|
51
|
-
execution, while `base_cli`
|
|
52
|
-
should get by default:
|
|
56
|
+
execution, while `base_cli` provides reusable lifecycle behavior:
|
|
53
57
|
|
|
54
58
|
- standard command options such as `--debug`, `--quiet`, `--environment`,
|
|
55
59
|
`--config`, `--keep-temp`, and `--log-file`
|
|
56
60
|
- structured logging to stderr and, by default, to a persistent per-run log file
|
|
57
|
-
-
|
|
58
|
-
- config loading with predictable precedence
|
|
61
|
+
- optional project discovery and configuration policies supplied by the consumer
|
|
59
62
|
- per-run temp directories, persistent cache directories, and cleanup hooks
|
|
60
63
|
- sensitive argument redaction in debug invocation logs
|
|
61
64
|
- a command context object shared by command code and helper functions
|
|
@@ -63,7 +66,7 @@ should get by default:
|
|
|
63
66
|
|
|
64
67
|
## Design Goals
|
|
65
68
|
|
|
66
|
-
|
|
69
|
+
CLI tools should be easy to write, but not magical. A command should be
|
|
67
70
|
explicitly registered, receive an explicit `Context`, and use standard Python
|
|
68
71
|
functions instead of import-time side effects.
|
|
69
72
|
|
|
@@ -73,22 +76,59 @@ The package follows these rules:
|
|
|
73
76
|
decorating a function.
|
|
74
77
|
- **Logs go to stderr**: user-facing program output can stay on stdout, while
|
|
75
78
|
logs remain redirectable and skippable.
|
|
76
|
-
- **Every run has a context**: logs, paths,
|
|
77
|
-
cleanup are available through one object.
|
|
79
|
+
- **Every run has a context**: logs, paths, configuration, environment, and
|
|
80
|
+
cleanup are available through one object. Project metadata is available when
|
|
81
|
+
the selected consumer profile supplies it.
|
|
78
82
|
- **No import-time filesystem writes**: state directories are created only when
|
|
79
83
|
a command runs.
|
|
80
|
-
- **
|
|
81
|
-
concepts such as options and arguments.
|
|
84
|
+
- **Consumer-neutral, Click-compatible**: command authors keep using familiar
|
|
85
|
+
Click concepts such as options and arguments.
|
|
86
|
+
|
|
87
|
+
## Consumer Profiles
|
|
88
|
+
|
|
89
|
+
`App` accepts a `CliProfile` that supplies the policies which vary
|
|
90
|
+
between applications: project discovery, configuration, runtime placement, and
|
|
91
|
+
optional history persistence.
|
|
92
|
+
|
|
93
|
+
Standalone consumers use the generic profile by default. It can also be passed
|
|
94
|
+
explicitly when making the policy boundary visible:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
app = base_cli.App(
|
|
98
|
+
name="hello",
|
|
99
|
+
version="0.1.0",
|
|
100
|
+
profile=base_cli.CliProfile.generic(),
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The generic profile has no manifest filename convention, no product-owned
|
|
105
|
+
configuration directory, and no implicit history writer. Applications can
|
|
106
|
+
provide those policies through callbacks or build their own profile. The
|
|
107
|
+
consumer-owned adapters should supply any product-specific policies. See
|
|
108
|
+
[`docs/consumer-profiles.md`](docs/consumer-profiles.md) for the boundary and
|
|
109
|
+
migration guidance.
|
|
82
110
|
|
|
83
111
|
## Public API
|
|
84
112
|
|
|
85
113
|
The supported facade is `import base_cli`. It exports the command lifecycle
|
|
86
114
|
(`App`, `Context`, `run_app`, decorators, and logging helpers), command filters,
|
|
87
|
-
the structured command protocol helpers
|
|
88
|
-
|
|
115
|
+
and the structured command protocol helpers. Consumer-owned user configuration
|
|
116
|
+
is passed through `Context.user_config`; the library does not impose a schema.
|
|
117
|
+
The corresponding modules are also available as
|
|
89
118
|
`base_cli.command_filters`, `base_cli.command_protocol`, and
|
|
90
119
|
`base_cli.history`.
|
|
91
120
|
|
|
121
|
+
The command protocol owns only generic framing, field validation, and schema
|
|
122
|
+
registration. It ships with no application record types and uses
|
|
123
|
+
`COMMAND_PROTOCOL_V1` by default. A consumer can register its own schemas and
|
|
124
|
+
pass a compatibility `protocol_header` when it must interoperate with an
|
|
125
|
+
existing peer protocol.
|
|
126
|
+
|
|
127
|
+
Command filters use consumer-neutral name normalization by default. Consumers
|
|
128
|
+
with legacy command names can pass a `normalizer` callback to
|
|
129
|
+
`normalize_command_filter`, `normalize_command_filters`, and
|
|
130
|
+
`command_matches` to define compatibility aliases or prefixes.
|
|
131
|
+
|
|
92
132
|
Low-level implementation helpers are intentionally not included in the
|
|
93
133
|
module `__all__` surfaces. Downstream code should use the documented facade or
|
|
94
134
|
the explicitly supported symbols from those modules.
|
|
@@ -101,7 +141,11 @@ from __future__ import annotations
|
|
|
101
141
|
import base_cli
|
|
102
142
|
|
|
103
143
|
|
|
104
|
-
app = base_cli.App(
|
|
144
|
+
app = base_cli.App(
|
|
145
|
+
name="hello",
|
|
146
|
+
version="0.1.0",
|
|
147
|
+
profile=base_cli.CliProfile.generic(),
|
|
148
|
+
)
|
|
105
149
|
|
|
106
150
|
|
|
107
151
|
@app.command()
|
|
@@ -116,7 +160,7 @@ if __name__ == "__main__":
|
|
|
116
160
|
```
|
|
117
161
|
|
|
118
162
|
Running this command directly as a Python package automatically adds the
|
|
119
|
-
standard
|
|
163
|
+
standard options:
|
|
120
164
|
|
|
121
165
|
```bash
|
|
122
166
|
hello --name Ada
|
|
@@ -129,19 +173,16 @@ hello --log-file /tmp/hello.log --name Ada
|
|
|
129
173
|
|
|
130
174
|
Long options with values use space-separated syntax. `base_cli.run_app()` rejects
|
|
131
175
|
equals-form values such as `--name=Ada` before Click parses arguments.
|
|
132
|
-
These
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
`--log-file`, `--config`, or `--environment` as public `basectl` options.
|
|
136
|
-
The wrapper-level `basectl --keep-temp <command>` option preserves the
|
|
137
|
-
complete temporary tree for that run.
|
|
176
|
+
These options belong to the application-level lifecycle. A consumer may expose
|
|
177
|
+
them through its own launcher or compose them with a higher-level command
|
|
178
|
+
wrapper.
|
|
138
179
|
|
|
139
180
|
## Command Registration
|
|
140
181
|
|
|
141
182
|
Use `App` when you want a named command:
|
|
142
183
|
|
|
143
184
|
```python
|
|
144
|
-
app = base_cli.App(name="
|
|
185
|
+
app = base_cli.App(name="workspace-tools", version="0.1.0")
|
|
145
186
|
```
|
|
146
187
|
|
|
147
188
|
Register the command function explicitly:
|
|
@@ -153,7 +194,7 @@ def main(ctx: base_cli.Context) -> None:
|
|
|
153
194
|
```
|
|
154
195
|
|
|
155
196
|
The command function always receives `ctx` as its first argument. User-defined
|
|
156
|
-
options and arguments are passed after the
|
|
197
|
+
options and arguments are passed after the standard lifecycle options have been
|
|
157
198
|
removed from Click's keyword arguments.
|
|
158
199
|
|
|
159
200
|
For small scripts, the module-level decorators are available:
|
|
@@ -164,10 +205,10 @@ def main(ctx: base_cli.Context) -> None:
|
|
|
164
205
|
...
|
|
165
206
|
```
|
|
166
207
|
|
|
167
|
-
|
|
168
|
-
|
|
208
|
+
Prefer an explicit `App` when command names, versions, or consumer
|
|
209
|
+
policies should be visible at the top of the module.
|
|
169
210
|
|
|
170
|
-
Use `@app.subcommand()` when one CLI needs multiple verbs while keeping
|
|
211
|
+
Use `@app.subcommand()` when one CLI needs multiple verbs while keeping the
|
|
171
212
|
standard context, logging, redaction, and cleanup lifecycle for each invocation:
|
|
172
213
|
|
|
173
214
|
```python
|
|
@@ -193,7 +234,7 @@ def sync_project(ctx: base_cli.Context, dry_run: bool) -> None:
|
|
|
193
234
|
|
|
194
235
|
Subcommands use the same `base_cli.option()` and `base_cli.argument()` metadata
|
|
195
236
|
as single commands. `App(help=...)` appears in the command group's `--help`
|
|
196
|
-
output. For subcommand apps, prefer standard
|
|
237
|
+
output. For subcommand apps, prefer standard options before the subcommand
|
|
197
238
|
name, for example `workspace-tools --debug status demo`. The post-subcommand
|
|
198
239
|
form, such as `workspace-tools status --debug demo`, remains accepted for
|
|
199
240
|
compatibility. Use either `@app.command()` for a single-command CLI or
|
|
@@ -222,11 +263,11 @@ def main(ctx: base_cli.Context, token: str) -> None:
|
|
|
222
263
|
```
|
|
223
264
|
|
|
224
265
|
Both `--token secret` and an externally supplied `--token=secret` token are
|
|
225
|
-
redacted in debug logs
|
|
226
|
-
|
|
266
|
+
redacted in debug logs. The lifecycle rejects equals-form option values before
|
|
267
|
+
Click parses them.
|
|
227
268
|
|
|
228
269
|
Use `dry_run=True` when a nonstandard option should drive `ctx.dry_run` and
|
|
229
|
-
|
|
270
|
+
the lifecycle's default durable-write suppression:
|
|
230
271
|
|
|
231
272
|
```python
|
|
232
273
|
@base_cli.option("--preview", is_flag=True, dry_run=True)
|
|
@@ -258,7 +299,7 @@ are consumed before the command function is called.
|
|
|
258
299
|
|
|
259
300
|
## Exit Codes
|
|
260
301
|
|
|
261
|
-
Use `base_cli.ExitCode` when command code or tests need to name
|
|
302
|
+
Use `base_cli.ExitCode` when command code or tests need to name standard
|
|
262
303
|
command result meanings:
|
|
263
304
|
|
|
264
305
|
- `ExitCode.SUCCESS` (`0`): the command completed successfully.
|
|
@@ -272,28 +313,30 @@ constants when it makes intent clearer:
|
|
|
272
313
|
|
|
273
314
|
```python
|
|
274
315
|
if ctx.project_root is None:
|
|
275
|
-
ctx.log.error("run this command from a
|
|
316
|
+
ctx.log.error("run this command from a project recognized by the consumer")
|
|
276
317
|
return base_cli.ExitCode.USAGE_ERROR
|
|
277
318
|
```
|
|
278
319
|
|
|
279
320
|
## Context
|
|
280
321
|
|
|
281
322
|
`Context` is the object command code should pass around instead of rediscovering
|
|
282
|
-
|
|
323
|
+
runtime paths or global settings.
|
|
283
324
|
|
|
284
325
|
Important fields include:
|
|
285
326
|
|
|
286
327
|
- `ctx.cli_name`: normalized CLI name used for state paths and logger names.
|
|
287
328
|
- `ctx.run_id`: timestamp plus short random suffix for this invocation.
|
|
288
|
-
- `ctx.
|
|
289
|
-
- `ctx.project_root`:
|
|
290
|
-
- `ctx.workspace_root`:
|
|
291
|
-
- `ctx.manifest_path`:
|
|
292
|
-
- `ctx.history_scope`:
|
|
293
|
-
|
|
294
|
-
- `ctx.history_parent_run_id`:
|
|
295
|
-
|
|
296
|
-
- `ctx.
|
|
329
|
+
- `ctx.application_home`: optional application home supplied by the profile.
|
|
330
|
+
- `ctx.project_root`: project root returned by the profile, when any.
|
|
331
|
+
- `ctx.workspace_root`: optional workspace root supplied by user configuration.
|
|
332
|
+
- `ctx.manifest_path`: project metadata path returned by the profile, when any.
|
|
333
|
+
- `ctx.history_scope`: history scope supplied by the profile or its
|
|
334
|
+
compatibility adapter.
|
|
335
|
+
- `ctx.history_parent_run_id`: optional parent invocation ID supplied by
|
|
336
|
+
the consumer.
|
|
337
|
+
- `ctx.runtime_owner`: consumer-defined runtime owner; the generic
|
|
338
|
+
profile uses `default`.
|
|
339
|
+
- `ctx.owner_root`: application namespace root under the configured cache root.
|
|
297
340
|
- `ctx.run_root`: this invocation's run bundle.
|
|
298
341
|
- `ctx.state_dir`: owner root (compatibility alias).
|
|
299
342
|
- `ctx.log_dir`: run-bundle log directory.
|
|
@@ -302,13 +345,14 @@ Important fields include:
|
|
|
302
345
|
- `ctx.log_file`: the run's shared `logs/primary.log`, or `None` when persistent
|
|
303
346
|
logging is disabled.
|
|
304
347
|
- `ctx.config`: merged configuration dictionary.
|
|
305
|
-
- `ctx.user_config`:
|
|
348
|
+
- `ctx.user_config`: opaque consumer-owned user configuration returned by the
|
|
349
|
+
profile, or `None` for the generic default.
|
|
306
350
|
- `ctx.environment`: active environment, defaulting to `dev`.
|
|
307
351
|
- `ctx.debug`: whether debug logging is enabled for the stderr stream.
|
|
308
352
|
- `ctx.quiet`: whether INFO logs are suppressed on the stderr stream.
|
|
309
353
|
- `ctx.dry_run`: whether the command is running in a no-durable-write mode.
|
|
310
354
|
- `ctx.keep_temp`: whether `ctx.temp_dir` should survive cleanup.
|
|
311
|
-
- `ctx.log`: standard Python logger configured by
|
|
355
|
+
- `ctx.log`: standard Python logger configured by `base_cli`.
|
|
312
356
|
|
|
313
357
|
Helpers can retrieve the active context without threading it through every call:
|
|
314
358
|
|
|
@@ -335,37 +379,39 @@ def helper() -> None:
|
|
|
335
379
|
`--quiet` suppresses INFO output on the user-facing stream but still shows
|
|
336
380
|
warnings and errors. `--debug` and `--quiet` cannot be used together. Persistent
|
|
337
381
|
log files still receive DEBUG-level detail, including INFO messages suppressed
|
|
338
|
-
from stderr.
|
|
339
|
-
|
|
340
|
-
|
|
382
|
+
from stderr. User-facing logs use colors automatically on interactive terminals;
|
|
383
|
+
persistent log files remain plain text. Set `NO_COLOR=1` or
|
|
384
|
+
`BASE_CLI_COLOR=0` to disable colors. A consumer wrapper may add its own color
|
|
385
|
+
option and map it to the environment variable.
|
|
386
|
+
|
|
387
|
+
Click also provides shell completion. For an app named `hello`, request a
|
|
388
|
+
completion script with `_HELLO_COMPLETE=bash_source hello`, replacing `bash`
|
|
389
|
+
with `zsh` or `fish` as needed. `base_cli` leaves installation to the caller so
|
|
390
|
+
shell startup files remain under user control.
|
|
341
391
|
|
|
342
392
|
Advanced tests and CI wrappers can call `base_cli.configure_logger(...,
|
|
343
393
|
stream=..., formatter=...)` to capture user-facing logs or apply a custom
|
|
344
|
-
formatter
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
`basectl --utc-wrapper`), they use UTC and include an explicit `UTC` marker.
|
|
394
|
+
formatter. Leave those arguments as `None` to keep the default stderr stream
|
|
395
|
+
and formatter. Log timestamps use the host's local timezone and include its
|
|
396
|
+
numeric offset by default. A consumer can set `LOG_UTC=1` to use UTC and
|
|
397
|
+
include an explicit `UTC` marker.
|
|
349
398
|
|
|
350
399
|
This setting affects log presentation only. Run metadata, history records, and
|
|
351
400
|
run IDs retain their canonical UTC representation.
|
|
352
401
|
|
|
353
402
|
Commands that inspect runtime artifacts can use `base_cli.App(log_to_file=False)`
|
|
354
403
|
to keep the standard context, `--debug`, and `--quiet` behavior without creating
|
|
355
|
-
default `logs/`, `cache/`, or `tmp/<run-id>/` directories.
|
|
356
|
-
|
|
357
|
-
the same for `basectl history`. An explicit `--log-file <path>` still enables
|
|
358
|
-
file logging for that invocation.
|
|
404
|
+
default `logs/`, `cache/`, or `tmp/<run-id>/` directories. An explicit
|
|
405
|
+
`--log-file <path>` still enables file logging for that invocation.
|
|
359
406
|
|
|
360
407
|
Commands running with `ctx.dry_run` also skip default `logs/`, `cache/`, and
|
|
361
408
|
`tmp/<run-id>/` creation. Passing `--log-file <path>` still writes to that
|
|
362
409
|
explicit file so tests and diagnostics can inspect dry-run logs when needed.
|
|
363
410
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
only and do not fail the user command when the index cannot be updated.
|
|
411
|
+
The generic profile does not write command history. A profile may provide a
|
|
412
|
+
history writer to persist redacted command metadata, timing, exit status,
|
|
413
|
+
project context, and a pointer to the raw log file. History writes should be
|
|
414
|
+
best-effort and should not fail the user command when an index cannot be updated.
|
|
369
415
|
|
|
370
416
|
High-frequency tools can set `base_cli.App(max_log_files=<count>)` to keep at
|
|
371
417
|
most that many default persistent log files across the owner's run bundles.
|
|
@@ -374,10 +420,10 @@ resolved, and the current run's log file is never pruned. The policy is skipped
|
|
|
374
420
|
for `ctx.dry_run`,
|
|
375
421
|
`log_to_file=False`, and explicit `--log-file` paths so no-durable-write modes
|
|
376
422
|
and caller-selected log locations stay under caller control. Use this as a
|
|
377
|
-
small guardrail for busy local tools;
|
|
378
|
-
maintenance
|
|
423
|
+
small guardrail for busy local tools; an application can provide broader
|
|
424
|
+
maintenance commands for caches, logs, and retained temp files.
|
|
379
425
|
|
|
380
|
-
Logs use
|
|
426
|
+
Logs use a stable, human-readable shape:
|
|
381
427
|
|
|
382
428
|
```text
|
|
383
429
|
2026-05-26 12:34:56 INFO path/to/file.py:42 message
|
|
@@ -404,65 +450,52 @@ the real command output.
|
|
|
404
450
|
|
|
405
451
|
## Config Precedence
|
|
406
452
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
5. direct command-line standard options
|
|
414
|
-
|
|
415
|
-
Environment variables currently recognized by the config layer:
|
|
453
|
+
The generic profile has no implicit configuration files. It loads the file
|
|
454
|
+
passed through `--config`, when present, and otherwise starts with an empty
|
|
455
|
+
configuration dictionary. Standard command-line options are applied by the
|
|
456
|
+
lifecycle after the profile's configuration is loaded; for example,
|
|
457
|
+
`--environment prod` overrides `environment: dev` from an explicit
|
|
458
|
+
configuration file.
|
|
416
459
|
|
|
417
|
-
- `BASE_CLI_ENVIRONMENT`
|
|
418
|
-
- `BASE_CLI_LOG_LEVEL`
|
|
419
|
-
- `BASE_CLI_KEEP_TEMP`
|
|
420
460
|
|
|
421
|
-
`
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
`--environment prod` overrides `environment: dev` from config.
|
|
427
|
-
|
|
428
|
-
`ctx.config` exposes the merged raw configuration after user, project,
|
|
429
|
-
explicit, and environment layers are applied. `ctx.user_config` exposes only the
|
|
430
|
-
typed machine-local user config, including `workspace.root`,
|
|
431
|
-
`workspace.manifest`, and IDE
|
|
432
|
-
preferences, so command code does not need to re-read `~/.base.d/config.yaml`
|
|
433
|
-
for those structured values.
|
|
434
|
-
|
|
435
|
-
The user config file is machine-local by default. Base owns the semantics of
|
|
436
|
-
`~/.base.d/config.yaml`, while users own backup and sync choices such as iCloud,
|
|
437
|
-
chezmoi, dotfiles repositories, Time Machine, or manual copy. See
|
|
438
|
-
`docs/local-config.md` for the product-level boundary.
|
|
461
|
+
`ctx.config` exposes the dictionary returned by the profile. `ctx.user_config`
|
|
462
|
+
exposes the opaque user-configuration value returned by the profile. Consumers
|
|
463
|
+
that need user files, project files, environment variables, or a merge
|
|
464
|
+
precedence must implement those policies in `CliProfile.load_config` and
|
|
465
|
+
`CliProfile.load_user_config`; `base_cli` does not define the value's fields.
|
|
439
466
|
|
|
440
467
|
## Project Discovery
|
|
441
468
|
|
|
442
|
-
|
|
443
|
-
|
|
469
|
+
The generic profile does not discover projects or assume a manifest filename.
|
|
470
|
+
Its `ctx.project_root` and `ctx.manifest_path` fields are `None` unless the
|
|
471
|
+
consumer supplies a `discover_project` policy. A profile can discover projects
|
|
472
|
+
from a manifest, workspace, repository metadata, or any other application-owned
|
|
473
|
+
source and return a `ProjectInfo` value.
|
|
444
474
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
- `ctx.manifest_path` points to the manifest
|
|
448
|
-
- `ctx.project_root` points to the manifest's parent directory
|
|
449
|
-
|
|
450
|
-
If no manifest is found, both fields are `None`. Commands that require a Base
|
|
451
|
-
project should validate this explicitly and return a clear usage error or
|
|
452
|
-
actionable message.
|
|
475
|
+
Commands that require a project should validate the profile-provided value
|
|
476
|
+
explicitly and return a clear usage error or actionable message.
|
|
453
477
|
|
|
454
478
|
## Runtime Directories
|
|
455
479
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
`
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
480
|
+
The generic profile uses the configured cache root and an application namespace
|
|
481
|
+
to create per-run logs, caches, and temporary directories. Pass
|
|
482
|
+
`cache_root` to `CliProfile.generic()` for deterministic placement in tests or
|
|
483
|
+
applications; otherwise the platform cache directory is used. Linux and WSL2
|
|
484
|
+
follow `XDG_CACHE_HOME` or `~/.cache`, macOS uses `~/Library/Caches`, and
|
|
485
|
+
Windows uses `%LOCALAPPDATA%` (falling back to `~/AppData/Local`). Set
|
|
486
|
+
`BASE_CLI_CACHE_DIR` to override the default on any platform. The generic
|
|
487
|
+
profile does not prescribe a product-wide cache name or cleanup command.
|
|
488
|
+
|
|
489
|
+
Each invocation is a run bundle containing a private `run.json`, `logs/`, and
|
|
490
|
+
`tmp/`, while persistent component caches live in the bundle's cache directory.
|
|
491
|
+
On POSIX, base-cli enforces owner-only `0600`/`0700` modes. On Windows, the
|
|
492
|
+
default user-local cache root relies on inherited user-profile ACLs; consumers
|
|
493
|
+
using a custom cache root must provide the appropriate ACL themselves.
|
|
494
|
+
|
|
495
|
+
See [Platform support](docs/platform-support.md) for the supported Linux,
|
|
496
|
+
WSL2, macOS, and native Windows boundaries. Native Windows support covers the
|
|
497
|
+
generic `base-cli` framework; it does not imply native Windows support for
|
|
498
|
+
Base or `basectl`.
|
|
466
499
|
|
|
467
500
|
Use `ctx.on_cleanup()` for cleanup work that should happen even when helper code
|
|
468
501
|
does not own the main command wrapper:
|
|
@@ -497,39 +530,32 @@ def test_command(tmp_path: Path) -> None:
|
|
|
497
530
|
["--name", "Ada"],
|
|
498
531
|
home=tmp_path,
|
|
499
532
|
cwd=project,
|
|
500
|
-
manifest={"project": {"name": "demo"}, "artifacts": []},
|
|
501
533
|
)
|
|
502
534
|
|
|
503
535
|
assert result.exit_code == 0
|
|
504
536
|
assert "hello Ada" in result.stdout
|
|
505
537
|
```
|
|
506
538
|
|
|
507
|
-
The helper wraps Click's `CliRunner`, sets `HOME`
|
|
508
|
-
`
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
`
|
|
513
|
-
|
|
539
|
+
The helper wraps Click's `CliRunner`, sets `HOME` plus the relevant
|
|
540
|
+
`USERPROFILE`, `LOCALAPPDATA`, and `XDG_CACHE_HOME` values when requested, and
|
|
541
|
+
supplies `cwd` to the invocation for the duration of the test. Calls that use
|
|
542
|
+
`cwd` are serialized and the caller's cwd is restored afterward, but this
|
|
543
|
+
remains process-global: do not use it concurrently with code that changes cwd
|
|
544
|
+
outside `invoke()` or from threads spawned by the invoked command. A
|
|
545
|
+
generic profile should receive project fixtures through its
|
|
546
|
+
`discover_project` callback. The helper does not create or interpret any
|
|
547
|
+
product-specific manifest fixture.
|
|
514
548
|
|
|
515
|
-
When `home` is supplied, `invoke()`
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
explicit cache location.
|
|
549
|
+
When `home` is supplied, `invoke()` provides an isolated default cache
|
|
550
|
+
environment for tests. Pass `env={"BASE_CLI_CACHE_DIR": str(path)}` when a test
|
|
551
|
+
needs an explicit cache location.
|
|
519
552
|
|
|
520
553
|
## When To Use `base_cli`
|
|
521
554
|
|
|
522
|
-
Use `base_cli` for Python commands that
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
instantiate `base_cli.App` so standard options, logging, redaction, runtime
|
|
527
|
-
state, and local command history stay consistent. If a future public Python
|
|
528
|
-
engine intentionally bypasses this lifecycle, document the reason in code and
|
|
529
|
-
in this guide, then add it as an explicit lifecycle-audit exemption. Shell-only
|
|
530
|
-
helpers that avoid Python startup, such as `basectl config path`, do not create
|
|
531
|
-
Python logs or history records; once a `basectl` path enters a Python command
|
|
532
|
-
package, it should participate in `base_cli.App`.
|
|
555
|
+
Use `base_cli` for Python commands that need a predictable command
|
|
556
|
+
lifecycle: standard options, logging, redaction, runtime state, cleanup, and
|
|
557
|
+
test helpers. Standalone consumers should use `CliProfile.generic()` or
|
|
558
|
+
provide an explicit profile with their own project and configuration policies.
|
|
533
559
|
|
|
534
560
|
It is a good fit for:
|
|
535
561
|
|
|
@@ -539,5 +565,5 @@ It is a good fit for:
|
|
|
539
565
|
- CLIs that need predictable logs, temp directories, and config precedence
|
|
540
566
|
|
|
541
567
|
It is not meant to replace Click, Typer, argparse, or rich terminal UI
|
|
542
|
-
frameworks. It is the
|
|
568
|
+
frameworks. It is the reusable layer around command lifecycle, context, logging,
|
|
543
569
|
configuration, and state.
|