datamasque-cli 1.0.0__tar.gz → 1.1.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 (78) hide show
  1. datamasque_cli-1.1.0/.claude-plugin/marketplace.json +21 -0
  2. datamasque_cli-1.1.0/CHANGELOG.md +33 -0
  3. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/CONTRIBUTING.md +3 -6
  4. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/Makefile +4 -7
  5. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/PKG-INFO +71 -1
  6. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/README.md +70 -0
  7. datamasque_cli-1.1.0/claude-skills/README.md +60 -0
  8. datamasque_cli-1.1.0/claude-skills/datamasque-cli/.claude-plugin/plugin.json +9 -0
  9. datamasque_cli-1.1.0/claude-skills/datamasque-cli/skills/datamasque-cli/SKILL.md +105 -0
  10. datamasque_cli-1.1.0/claude-skills/ruleset-builder/.claude-plugin/plugin.json +9 -0
  11. datamasque_cli-1.1.0/claude-skills/ruleset-splitter/.claude-plugin/plugin.json +9 -0
  12. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/pyproject.toml +3 -1
  13. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/scripts/active_profile_env.py +2 -1
  14. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/client.py +9 -6
  15. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/commands/auth.py +14 -6
  16. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/commands/connections.py +61 -30
  17. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/commands/discovery.py +4 -4
  18. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/commands/files.py +3 -3
  19. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/commands/ruleset_libraries.py +6 -6
  20. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/commands/rulesets.py +18 -6
  21. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/commands/runs.py +40 -21
  22. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/commands/seeds.py +3 -3
  23. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/commands/system.py +3 -3
  24. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/commands/users.py +4 -4
  25. datamasque_cli-1.1.0/src/datamasque_cli/main.py +132 -0
  26. datamasque_cli-1.1.0/src/datamasque_cli/output.py +230 -0
  27. datamasque_cli-1.1.0/tests/commands/test_catalog.py +44 -0
  28. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/conftest.py +13 -0
  29. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/test_client_auth.py +1 -1
  30. datamasque_cli-1.1.0/tests/test_output.py +200 -0
  31. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/uv.lock +4 -4
  32. datamasque_cli-1.0.0/CHANGELOG.md +0 -5
  33. datamasque_cli-1.0.0/claude-skills/README.md +0 -46
  34. datamasque_cli-1.0.0/claude-skills/datamasque-cli/SKILL.md +0 -187
  35. datamasque_cli-1.0.0/src/datamasque_cli/main.py +0 -58
  36. datamasque_cli-1.0.0/src/datamasque_cli/output.py +0 -133
  37. datamasque_cli-1.0.0/tests/test_output.py +0 -74
  38. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/.github/workflows/ci.yml +0 -0
  39. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/.github/workflows/release-testpypi.yml +0 -0
  40. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/.github/workflows/release.yml +0 -0
  41. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/.gitignore +0 -0
  42. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/LICENSE +0 -0
  43. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/NOTICE +0 -0
  44. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/assets/demo.gif +0 -0
  45. {datamasque_cli-1.0.0/claude-skills → datamasque_cli-1.1.0/claude-skills/ruleset-builder/skills}/ruleset-builder/SKILL.md +0 -0
  46. {datamasque_cli-1.0.0/claude-skills → datamasque_cli-1.1.0/claude-skills/ruleset-builder/skills}/ruleset-builder/references/hash-columns-guide.md +0 -0
  47. {datamasque_cli-1.0.0/claude-skills → datamasque_cli-1.1.0/claude-skills/ruleset-builder/skills}/ruleset-builder/references/mask-definitions-guide.md +0 -0
  48. {datamasque_cli-1.0.0/claude-skills → datamasque_cli-1.1.0/claude-skills/ruleset-builder/skills}/ruleset-builder/references/ruleset-libraries-guide.md +0 -0
  49. {datamasque_cli-1.0.0/claude-skills → datamasque_cli-1.1.0/claude-skills/ruleset-builder/skills}/ruleset-builder/references/ruleset-yaml-reference.md +0 -0
  50. {datamasque_cli-1.0.0/claude-skills → datamasque_cli-1.1.0/claude-skills/ruleset-splitter/skills}/ruleset-splitter/SKILL.md +0 -0
  51. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/scripts/bump_version.py +0 -0
  52. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/__init__.py +0 -0
  53. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/commands/__init__.py +0 -0
  54. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/config.py +0 -0
  55. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/src/datamasque_cli/py.typed +0 -0
  56. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/__init__.py +0 -0
  57. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/commands/__init__.py +0 -0
  58. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/commands/test_auth.py +0 -0
  59. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/commands/test_connections.py +0 -0
  60. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/commands/test_discovery.py +0 -0
  61. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/commands/test_files.py +0 -0
  62. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/commands/test_ruleset_libraries.py +0 -0
  63. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/commands/test_rulesets.py +0 -0
  64. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/commands/test_runs.py +0 -0
  65. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/commands/test_seeds.py +0 -0
  66. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/commands/test_system.py +0 -0
  67. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/commands/test_users.py +0 -0
  68. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/integration/README.md +0 -0
  69. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/integration/__init__.py +0 -0
  70. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/integration/conftest.py +0 -0
  71. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/integration/test_connections.py +0 -0
  72. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/integration/test_delete_safety.py +0 -0
  73. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/integration/test_rulesets.py +0 -0
  74. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/integration/test_runs.py +0 -0
  75. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/test_client_env.py +0 -0
  76. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/test_client_profile.py +0 -0
  77. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/test_config.py +0 -0
  78. {datamasque_cli-1.0.0 → datamasque_cli-1.1.0}/tests/test_version.py +0 -0
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "datamasque-tools",
3
+ "owner": { "name": "DataMasque" },
4
+ "plugins": [
5
+ {
6
+ "name": "datamasque-cli",
7
+ "source": "./claude-skills/datamasque-cli",
8
+ "description": "DataMasque command-line interface integration. Drive DataMasque from Claude Code: list connections, kick off runs, stream logs, download reports."
9
+ },
10
+ {
11
+ "name": "ruleset-builder",
12
+ "source": "./claude-skills/ruleset-builder",
13
+ "description": "Convert auto-generated DataMasque rulesets into production-ready form. Validate and iterate."
14
+ },
15
+ {
16
+ "name": "ruleset-splitter",
17
+ "source": "./claude-skills/ruleset-splitter",
18
+ "description": "Consolidate multi-file DataMasque rulesets for editing, then re-split them back out."
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ ## v1.1.0
4
+
5
+ ### Added
6
+ - `dm catalog` command — emits the full subcommand tree as JSON for agent
7
+ introspection. `--compact` for `{path, help}` only (~1.4kB), default for
8
+ full options/arguments.
9
+ - Auto-detection of agent context: output flips to JSON automatically when
10
+ stdout is not a TTY, when `DM_OUTPUT=json` is set, or when the
11
+ vendor-neutral `AI_AGENT` env var is present. `DM_OUTPUT=table` forces
12
+ human output.
13
+ - Structured error envelope on stderr in agent mode:
14
+ `{"error": {"code": "...", "message": "...", "hint": "..."}}` — stdout
15
+ stays empty on failure so downstream pipes don't trip.
16
+
17
+ ### Changed
18
+ - Exit codes are now differentiated by error category. Previously every
19
+ error returned 1; now: `not_found`=3, `invalid_input`=4, `ambiguous`=5,
20
+ `auth_required`=6, `auth_failed`=7, `conflict`=8, `transport_error`=9.
21
+ `error` (unclassified) remains 1; 2 is reserved for typer/click usage
22
+ errors. Stable across minor versions.
23
+ - Long values (UUIDs especially) now fold across lines in table output
24
+ rather than being silently truncated with `…` in narrow terminals.
25
+
26
+ ### Internal
27
+ - `ErrorCode` and `ConnectionType` are now `StrEnum`s; the abort code arg
28
+ is type-checked at edit time and the connection-type "Valid: ..." hint
29
+ is generated from the enum.
30
+
31
+ ## v1.0.0
32
+
33
+ Initial release.
@@ -185,13 +185,10 @@ Each target runs `make check`,
185
185
  bumps the version in `pyproject.toml`,
186
186
  refreshes `uv.lock`,
187
187
  commits, tags, and pushes.
188
- CI handles the publish.
188
+ CI handles the publish to PyPI.
189
189
 
190
- To publish manually without bumping:
191
-
192
- ```console
193
- make publish
194
- ```
190
+ To smoke-test a release against TestPyPI without tagging,
191
+ trigger the `Release (TestPyPI)` workflow manually from the GitHub Actions tab.
195
192
 
196
193
  ## Toolchain
197
194
 
@@ -1,4 +1,4 @@
1
- .PHONY: install lint format mypy test test-integration test-integration-local check build publish release-patch release-minor release-major
1
+ .PHONY: install lint format mypy test test-integration test-integration-local check build release-patch release-minor release-major
2
2
 
3
3
  install:
4
4
  uv sync
@@ -33,9 +33,6 @@ format-check:
33
33
  build:
34
34
  uv build
35
35
 
36
- publish: check build
37
- uv publish --index datamasque-private-pypi -u "" -p "" dist/*
38
-
39
36
  # Bump version, commit, tag, push — CI publishes automatically.
40
37
  # Usage: make release-patch (0.1.0 → 0.1.1)
41
38
  # make release-minor (0.1.0 → 0.2.0)
@@ -47,7 +44,7 @@ release-patch: check
47
44
  git commit -m "Release v$(VERSION)"
48
45
  git tag "v$(VERSION)"
49
46
  git push && git push --tags
50
- @echo "Released v$(VERSION) — CI will publish to pypi.dtq.tools"
47
+ @echo "Released v$(VERSION) — CI will publish to PyPI (https://pypi.org/p/datamasque-cli)"
51
48
 
52
49
  release-minor: check
53
50
  $(eval VERSION := $(shell python3 scripts/bump_version.py minor))
@@ -56,7 +53,7 @@ release-minor: check
56
53
  git commit -m "Release v$(VERSION)"
57
54
  git tag "v$(VERSION)"
58
55
  git push && git push --tags
59
- @echo "Released v$(VERSION) — CI will publish to pypi.dtq.tools"
56
+ @echo "Released v$(VERSION) — CI will publish to PyPI (https://pypi.org/p/datamasque-cli)"
60
57
 
61
58
  release-major: check
62
59
  $(eval VERSION := $(shell python3 scripts/bump_version.py major))
@@ -65,4 +62,4 @@ release-major: check
65
62
  git commit -m "Release v$(VERSION)"
66
63
  git tag "v$(VERSION)"
67
64
  git push && git push --tags
68
- @echo "Released v$(VERSION) — CI will publish to pypi.dtq.tools"
65
+ @echo "Released v$(VERSION) — CI will publish to PyPI (https://pypi.org/p/datamasque-cli)"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datamasque-cli
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: Official command-line interface for the DataMasque data-masking platform.
5
5
  Project-URL: Homepage, https://datamasque.com/
6
6
  Project-URL: Repository, https://github.com/datamasque/datamasque-cli
@@ -89,6 +89,26 @@ dm run logs 42 --follow
89
89
  `dm run start` and `dm run wait` block until the run finishes.
90
90
  Pass `--background` to `start` to skip the wait.
91
91
 
92
+ ## Claude Code skills
93
+
94
+ The [`claude-skills/`](claude-skills/) directory ships three
95
+ [Claude Code](https://claude.com/claude-code) plugins that drive `dm` on your behalf:
96
+
97
+ - **`datamasque-cli`** — operate a DataMasque instance (start runs, list connections, fetch discovery reports).
98
+ - **`ruleset-builder`** — turn auto-generated rulesets into production-ready ones.
99
+ - **`ruleset-splitter`** — join many-file rulesets into one file for editing, then re-split.
100
+
101
+ Install via the Claude Code plugin marketplace:
102
+
103
+ ```
104
+ /plugin marketplace add datamasque/datamasque-cli
105
+ /plugin install datamasque-cli@datamasque-tools
106
+ /plugin install ruleset-builder@datamasque-tools
107
+ /plugin install ruleset-splitter@datamasque-tools
108
+ ```
109
+
110
+ See [`claude-skills/README.md`](claude-skills/README.md) for more.
111
+
92
112
  ## Shell completion
93
113
 
94
114
  `dm` provides built-in completion for bash, zsh, and fish:
@@ -249,6 +269,56 @@ STATUS=$(dm run status 42 --json | jq -r '.status')
249
269
  dm rulesets get myruleset --json | jq -r '.yaml' > ruleset.yaml
250
270
  ```
251
271
 
272
+ JSON is also emitted automatically when:
273
+
274
+ - `stdout` is not a TTY (piped or captured),
275
+ - `DM_OUTPUT=json` is set in the environment, or
276
+ - a vendor-neutral `AI_AGENT` env var is set (e.g. by Claude Code).
277
+
278
+ Set `DM_OUTPUT=table` to force human-readable output regardless of context.
279
+
280
+ ## Agent / scripting interface
281
+
282
+ For programmatic use (CI, AI coding agents, shell scripts), the CLI exposes
283
+ a discovery command and a stable error contract.
284
+
285
+ ### Command catalog
286
+
287
+ `dm catalog` dumps every visible subcommand as JSON so an agent can introspect
288
+ the surface without paging through `--help` screens:
289
+
290
+ ```console
291
+ dm catalog --compact # ~1.4kB — {path, help} per command
292
+ dm catalog # full — also includes options and arguments
293
+ ```
294
+
295
+ ### Structured errors
296
+
297
+ In agent mode, errors are emitted as a JSON envelope on stderr (stdout stays
298
+ empty on failure):
299
+
300
+ ```json
301
+ {"error": {"code": "not_found", "message": "Connection 'foo' not found.", "hint": "Run dm connections list."}}
302
+ ```
303
+
304
+ ### Exit codes
305
+
306
+ | Code | Meaning | When |
307
+ | ---: | ----------------- | ---------------------------------------------- |
308
+ | 0 | success | command completed |
309
+ | 1 | error | unclassified failure |
310
+ | 2 | usage error | unknown flag or missing argument (typer/click) |
311
+ | 3 | not_found | resource lookup failed |
312
+ | 4 | invalid_input | argument values rejected |
313
+ | 5 | ambiguous | name matched multiple resources |
314
+ | 6 | auth_required | no credentials configured |
315
+ | 7 | auth_failed | credentials rejected by server |
316
+ | 8 | conflict | operation rejected by server state |
317
+ | 9 | transport_error | network or TLS failure |
318
+
319
+ Exit codes are stable across minor versions. The `error.code` string in the
320
+ JSON envelope mirrors these names.
321
+
252
322
  ## Documentation
253
323
 
254
324
  Documentation for the DataMasque product,
@@ -59,6 +59,26 @@ dm run logs 42 --follow
59
59
  `dm run start` and `dm run wait` block until the run finishes.
60
60
  Pass `--background` to `start` to skip the wait.
61
61
 
62
+ ## Claude Code skills
63
+
64
+ The [`claude-skills/`](claude-skills/) directory ships three
65
+ [Claude Code](https://claude.com/claude-code) plugins that drive `dm` on your behalf:
66
+
67
+ - **`datamasque-cli`** — operate a DataMasque instance (start runs, list connections, fetch discovery reports).
68
+ - **`ruleset-builder`** — turn auto-generated rulesets into production-ready ones.
69
+ - **`ruleset-splitter`** — join many-file rulesets into one file for editing, then re-split.
70
+
71
+ Install via the Claude Code plugin marketplace:
72
+
73
+ ```
74
+ /plugin marketplace add datamasque/datamasque-cli
75
+ /plugin install datamasque-cli@datamasque-tools
76
+ /plugin install ruleset-builder@datamasque-tools
77
+ /plugin install ruleset-splitter@datamasque-tools
78
+ ```
79
+
80
+ See [`claude-skills/README.md`](claude-skills/README.md) for more.
81
+
62
82
  ## Shell completion
63
83
 
64
84
  `dm` provides built-in completion for bash, zsh, and fish:
@@ -219,6 +239,56 @@ STATUS=$(dm run status 42 --json | jq -r '.status')
219
239
  dm rulesets get myruleset --json | jq -r '.yaml' > ruleset.yaml
220
240
  ```
221
241
 
242
+ JSON is also emitted automatically when:
243
+
244
+ - `stdout` is not a TTY (piped or captured),
245
+ - `DM_OUTPUT=json` is set in the environment, or
246
+ - a vendor-neutral `AI_AGENT` env var is set (e.g. by Claude Code).
247
+
248
+ Set `DM_OUTPUT=table` to force human-readable output regardless of context.
249
+
250
+ ## Agent / scripting interface
251
+
252
+ For programmatic use (CI, AI coding agents, shell scripts), the CLI exposes
253
+ a discovery command and a stable error contract.
254
+
255
+ ### Command catalog
256
+
257
+ `dm catalog` dumps every visible subcommand as JSON so an agent can introspect
258
+ the surface without paging through `--help` screens:
259
+
260
+ ```console
261
+ dm catalog --compact # ~1.4kB — {path, help} per command
262
+ dm catalog # full — also includes options and arguments
263
+ ```
264
+
265
+ ### Structured errors
266
+
267
+ In agent mode, errors are emitted as a JSON envelope on stderr (stdout stays
268
+ empty on failure):
269
+
270
+ ```json
271
+ {"error": {"code": "not_found", "message": "Connection 'foo' not found.", "hint": "Run dm connections list."}}
272
+ ```
273
+
274
+ ### Exit codes
275
+
276
+ | Code | Meaning | When |
277
+ | ---: | ----------------- | ---------------------------------------------- |
278
+ | 0 | success | command completed |
279
+ | 1 | error | unclassified failure |
280
+ | 2 | usage error | unknown flag or missing argument (typer/click) |
281
+ | 3 | not_found | resource lookup failed |
282
+ | 4 | invalid_input | argument values rejected |
283
+ | 5 | ambiguous | name matched multiple resources |
284
+ | 6 | auth_required | no credentials configured |
285
+ | 7 | auth_failed | credentials rejected by server |
286
+ | 8 | conflict | operation rejected by server state |
287
+ | 9 | transport_error | network or TLS failure |
288
+
289
+ Exit codes are stable across minor versions. The `error.code` string in the
290
+ JSON envelope mirrors these names.
291
+
222
292
  ## Documentation
223
293
 
224
294
  Documentation for the DataMasque product,
@@ -0,0 +1,60 @@
1
+ # Claude Code skills
2
+
3
+ - **`datamasque-cli/`** —
4
+ teaches Claude how to operate a DataMasque instance with `dm`:
5
+ - authenticate,
6
+ - list connections,
7
+ - start runs,
8
+ - fetch discovery reports.
9
+ - **`ruleset-builder/`** —
10
+ turns auto-generated rulesets into production-ready ones:
11
+ - extracts a `ruleset_library`,
12
+ - adds `hash_columns`,
13
+ - applies `skip_defaults`,
14
+ - validates.
15
+ - **`ruleset-splitter/`** —
16
+ joins DataMasque's many-file generated rulesets into one file for editing, then re-splits back to the original filenames.
17
+
18
+ ## Install
19
+
20
+ In [Claude Code](https://claude.com/claude-code), add this repo as a plugin marketplace and install the plugins you want:
21
+
22
+ ```
23
+ /plugin marketplace add datamasque/datamasque-cli
24
+ /plugin install datamasque-cli@datamasque-tools
25
+ /plugin install ruleset-builder@datamasque-tools
26
+ /plugin install ruleset-splitter@datamasque-tools
27
+ ```
28
+
29
+ ## Uninstall
30
+
31
+ ```
32
+ /plugin uninstall datamasque-cli@datamasque-tools
33
+ /plugin uninstall ruleset-builder@datamasque-tools
34
+ /plugin uninstall ruleset-splitter@datamasque-tools
35
+ ```
36
+
37
+ ## Working on these skills locally
38
+
39
+ If you're editing these skills in-repo and want Claude Code to pick up changes without reinstalling, symlink each directory into `~/.claude/skills/`:
40
+
41
+ ```bash
42
+ ln -sfn ~/repos/datamasque-cli/claude-skills/datamasque-cli/skills/datamasque-cli \
43
+ ~/.claude/skills/datamasque-cli
44
+
45
+ ln -sfn ~/repos/datamasque-cli/claude-skills/ruleset-builder/skills/ruleset-builder \
46
+ ~/.claude/skills/ruleset-builder
47
+
48
+ ln -sfn ~/repos/datamasque-cli/claude-skills/ruleset-splitter/skills/ruleset-splitter \
49
+ ~/.claude/skills/ruleset-splitter
50
+ ```
51
+
52
+ Reload inside Claude Code (`/reload-plugins` or start a new session). Edits to any `SKILL.md` go live on the next reload — no reinstall.
53
+
54
+ Remove the symlinks when done:
55
+
56
+ ```bash
57
+ rm ~/.claude/skills/datamasque-cli
58
+ rm ~/.claude/skills/ruleset-builder
59
+ rm ~/.claude/skills/ruleset-splitter
60
+ ```
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "datamasque-cli",
3
+ "version": "1.1.0",
4
+ "description": "DataMasque command-line interface integration. Drive DataMasque from Claude Code: list connections, kick off runs, stream logs, download reports.",
5
+ "author": { "name": "DataMasque Ltd" },
6
+ "repository": "https://github.com/datamasque/datamasque-cli",
7
+ "license": "Apache-2.0",
8
+ "keywords": ["datamasque", "data-masking", "cli"]
9
+ }
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: datamasque-cli
3
+ description: Use when the user wants to interact with a DataMasque instance — start masking runs, check run status, list connections or rulesets, manage seeds, manage ruleset libraries, check system health, or any task involving the DataMasque API. Triggers on "mask the data", "start a run", "check the run", "list connections", "list rulesets", "upload a seed", "check DataMasque health", "dm status", "ruleset library", or any request to operate DataMasque programmatically.
4
+ argument-hint: e.g. "start a run with docx_masking on var_input_docx"
5
+ user-invocable: true
6
+ ---
7
+
8
+ # DataMasque CLI
9
+
10
+ Operate a DataMasque instance via the `dm` command-line tool.
11
+
12
+ Run `dm catalog --compact` for a JSON list of every subcommand. The sections
13
+ below cover idioms the catalog can't show you.
14
+
15
+ ## Output and errors
16
+
17
+ In agent mode — auto-detected when stdout is not a TTY, `AI_AGENT` is set, or
18
+ `DM_OUTPUT=json` — output is JSON on stdout, errors are JSON on stderr:
19
+
20
+ ```json
21
+ {"error": {"code": "not_found", "message": "...", "hint": "..."}}
22
+ ```
23
+
24
+ `error.code` is the stable identifier; branch on it rather than the message.
25
+ The set is `not_found`, `invalid_input`, `ambiguous`, `auth_required`,
26
+ `auth_failed`, `conflict`, `transport_error`, `error`. Exit code is non-zero
27
+ on any error; exit 2 specifically means a CLI usage error (unknown flag,
28
+ missing argument) from typer.
29
+
30
+ `DM_OUTPUT=table` forces human-readable output.
31
+
32
+ ## Authentication
33
+
34
+ Set `DATAMASQUE_URL`, `DATAMASQUE_USERNAME`, `DATAMASQUE_PASSWORD` to auth
35
+ without saving anything (right choice for CI / one-offs). For interactive
36
+ use, `dm auth login --profile <name>` prompts and persists to
37
+ `~/.config/datamasque-cli/config.toml`. `--insecure` (on login) or
38
+ `DATAMASQUE_VERIFY_SSL=false` (per call) skip TLS verification.
39
+
40
+ ## Quick start: a masking run
41
+
42
+ ```bash
43
+ dm connections list # find a source
44
+ dm rulesets list # find a ruleset
45
+ dm run start -c <source> -r <ruleset> [-d <dest>] # blocks until done
46
+ ```
47
+
48
+ Add `--background` to return immediately with the run id, then poll with
49
+ `dm run wait <id>`, `dm run status <id>`, or `dm run logs <id> --follow`.
50
+ Pass repeated `--options key=value` for server-side knobs
51
+ (e.g. `--options batch_size=1000 --options dry_run=true`).
52
+
53
+ ## Idioms and gotchas
54
+
55
+ - **Ruleset namespaces.** `database` and `file` rulesets share a name
56
+ namespace, so `customers` can exist in both. `dm run start` reads the
57
+ source connection's type and picks the matching ruleset automatically.
58
+ For `get` / `create` / `delete`, pass `--type file|database` only when
59
+ two rows share the name and you need to disambiguate.
60
+
61
+ - **File masking needs a destination.** Database masking is in-place;
62
+ file masking writes through to a destination connection and fails
63
+ with `invalid_input` without `--destination`.
64
+
65
+ - **`dm run start` blocks by default.** No flag needed for "wait then
66
+ return"; use `--background` only when you genuinely want fire-and-forget.
67
+
68
+ - **`dm run report` is file-masking-only.** The CSV is one row per file
69
+ the worker considered, with `path`, `file_size`, `file_type`, and
70
+ `skip_reason` (e.g. "File archived with Glacier", "File type unsupported
71
+ by data discovery", "Matched a skip filter"). Database runs don't
72
+ produce a report — `not_found` is expected for them, and for any run
73
+ that hasn't reached a terminal state yet.
74
+
75
+ - **`dm libraries delete` refuses to delete libraries imported by a
76
+ ruleset.** Run `dm libraries usage <name>` first to see what depends on
77
+ it; pass `--force` only after you've made an informed decision.
78
+
79
+ - **`dm connections update` preserves the UUID.** Use it to rotate
80
+ passwords or change hosts without invalidating the rulesets and runs
81
+ that already reference the connection.
82
+
83
+ - **`dm rulesets create` is also "update"** — it reads the existing
84
+ `mask_type` from the server, so you only need `--type` for brand-new
85
+ rulesets or to disambiguate a same-name update.
86
+
87
+ - **Discovery is a kind of run.** `dm discover schema <connection>` kicks
88
+ off a discovery run and returns a run id. Poll with `dm run status <id>`,
89
+ then fetch results with `dm discover schema-results <id>` /
90
+ `sdd-report` / `db-report` / `file-report`.
91
+
92
+ - **`dm rulesets validate --file <file> --type <type>`** runs server-side
93
+ validation without committing the ruleset. Use this before `create`
94
+ when you want a clean failure mode for bad YAML.
95
+
96
+ - **"Build a ruleset" usually means the `ruleset-builder` skill, not
97
+ `dm rulesets generate`.** `generate` is server-side scaffolding from a
98
+ JSON generation request. The `ruleset-builder` skill (separate skill in
99
+ this repo) handles the production-quality workflow — hash columns,
100
+ library extraction, refinement — which is what users typically want.
101
+
102
+ - **Names or UUIDs, either works.** `dm connections get <x>`,
103
+ `dm run start -c <x>`, `dm discover schema <x>`, etc. all try the name
104
+ first and fall back to a UUID match. Prefer names for readability;
105
+ reach for UUIDs only when names collide (rare).
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "ruleset-builder",
3
+ "version": "1.0.0",
4
+ "description": "Convert auto-generated DataMasque rulesets into production-ready form. Validate and iterate.",
5
+ "author": { "name": "DataMasque Ltd" },
6
+ "repository": "https://github.com/datamasque/datamasque-cli",
7
+ "license": "Apache-2.0",
8
+ "keywords": ["datamasque", "ruleset", "yaml"]
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "ruleset-splitter",
3
+ "version": "1.0.0",
4
+ "description": "Consolidate multi-file DataMasque rulesets for editing, then re-split them back out.",
5
+ "author": { "name": "DataMasque Ltd" },
6
+ "repository": "https://github.com/datamasque/datamasque-cli",
7
+ "license": "Apache-2.0",
8
+ "keywords": ["datamasque", "ruleset", "yaml"]
9
+ }
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datamasque-cli"
3
- version = "1.0.0"
3
+ version = "1.1.0"
4
4
  description = "Official command-line interface for the DataMasque data-masking platform."
5
5
  authors = [
6
6
  { name = "DataMasque Ltd" },
@@ -99,6 +99,8 @@ ignore = [
99
99
  "S",
100
100
  "SLF001",
101
101
  ]
102
+ # Standalone helper scripts that legitimately print to stdout for shell consumption.
103
+ "scripts/**/*" = ["T20"]
102
104
 
103
105
  [tool.ruff.lint.isort]
104
106
  known-first-party = ["datamasque_cli"]
@@ -19,7 +19,8 @@ except ImportError:
19
19
  path = os.path.expanduser("~/.config/datamasque-cli/config.toml")
20
20
 
21
21
  try:
22
- data = tomllib.loads(open(path).read())
22
+ with open(path, "rb") as f:
23
+ data = tomllib.load(f)
23
24
  except FileNotFoundError:
24
25
  sys.exit(f"No profile config at {path}. Run 'dm auth login' first.")
25
26
 
@@ -13,7 +13,7 @@ from datamasque.client.exceptions import DataMasqueApiError, DataMasqueTransport
13
13
  from datamasque.client.models.dm_instance import DataMasqueInstanceConfig
14
14
 
15
15
  from datamasque_cli.config import Config, Profile, load_config
16
- from datamasque_cli.output import abort
16
+ from datamasque_cli.output import ErrorCode, abort
17
17
 
18
18
  ENV_URL = "DATAMASQUE_URL"
19
19
  ENV_USERNAME = "DATAMASQUE_USERNAME"
@@ -52,9 +52,12 @@ def _resolve_profile(config: Config, profile_name: str | None) -> Profile:
52
52
  if not profile.is_configured:
53
53
  name = profile_name or config.active_profile
54
54
  abort(
55
- f"Profile '{name}' is not configured. "
56
- f"Run: dm auth login --profile {name} --url <URL> --username <USER>\n"
57
- f"Or set {ENV_URL}, {ENV_USERNAME}, and {ENV_PASSWORD} environment variables."
55
+ f"Profile '{name}' is not configured.",
56
+ code=ErrorCode.AUTH_REQUIRED,
57
+ hint=(
58
+ f"Run: dm auth login --profile {name} --url <URL> --username <USER> "
59
+ f"or set {ENV_URL}, {ENV_USERNAME}, and {ENV_PASSWORD}."
60
+ ),
58
61
  )
59
62
  return profile
60
63
 
@@ -90,9 +93,9 @@ def get_client(profile_name: str | None = None) -> DataMasqueClient:
90
93
  try:
91
94
  client.authenticate()
92
95
  except DataMasqueTransportError as e:
93
- abort(_format_transport_error(profile.url, e, verify_ssl=verify_ssl))
96
+ abort(_format_transport_error(profile.url, e, verify_ssl=verify_ssl), code=ErrorCode.TRANSPORT_ERROR)
94
97
  except DataMasqueApiError as e:
95
- abort(f"Authentication failed: {e}")
98
+ abort(f"Authentication failed: {e}", code=ErrorCode.AUTH_FAILED)
96
99
 
97
100
  return client
98
101
 
@@ -6,14 +6,14 @@ import typer
6
6
 
7
7
  from datamasque_cli.client import get_client, profile_from_env
8
8
  from datamasque_cli.config import DEFAULT_PROFILE, Profile, load_config, save_config
9
- from datamasque_cli.output import abort, print_info, print_success, print_table
9
+ from datamasque_cli.output import ErrorCode, abort, print_info, print_success, print_table
10
10
 
11
11
  # `login` and `status` handle connection errors locally
12
12
  # because they need softer behaviour than `get_client`'s hard abort:
13
13
  # login saves credentials even when the server is unreachable,
14
14
  # and status prints profile info before attempting connection.
15
15
 
16
- app = typer.Typer(help="Authentication and profile management.")
16
+ app = typer.Typer(help="Authentication and profile management.", no_args_is_help=True)
17
17
 
18
18
 
19
19
  @app.command()
@@ -36,7 +36,7 @@ def login(
36
36
  """
37
37
  url = typer.prompt("DataMasque URL").rstrip("/")
38
38
  if not url.startswith(("http://", "https://")):
39
- abort(f"URL must start with http:// or https:// (got '{url}').")
39
+ abort(f"URL must start with http:// or https:// (got '{url}').", code=ErrorCode.INVALID_INPUT)
40
40
 
41
41
  username = typer.prompt("Username")
42
42
  password = typer.prompt("Password", hide_input=True)
@@ -68,7 +68,7 @@ def logout(
68
68
  name = profile or config.active_profile
69
69
 
70
70
  if not config.delete_profile(name):
71
- abort(f"Profile '{name}' does not exist.")
71
+ abort(f"Profile '{name}' does not exist.", code=ErrorCode.NOT_FOUND)
72
72
 
73
73
  # If we just deleted the active profile, fall back to another one.
74
74
  if name == config.active_profile:
@@ -87,7 +87,11 @@ def use_profile(
87
87
  config = load_config()
88
88
 
89
89
  if profile not in config.profiles:
90
- abort(f"Profile '{profile}' does not exist. Run: dm auth login --profile {profile}")
90
+ abort(
91
+ f"Profile '{profile}' does not exist.",
92
+ code=ErrorCode.NOT_FOUND,
93
+ hint=f"Run: dm auth login --profile {profile}",
94
+ )
91
95
 
92
96
  config.active_profile = profile
93
97
  save_config(config)
@@ -133,7 +137,11 @@ def status() -> None:
133
137
  config = load_config()
134
138
  profile = config.get_profile()
135
139
  if not profile.is_configured:
136
- abort(f"Profile '{config.active_profile}' is not configured. Run: dm auth login")
140
+ abort(
141
+ f"Profile '{config.active_profile}' is not configured.",
142
+ code=ErrorCode.AUTH_REQUIRED,
143
+ hint="Run: dm auth login",
144
+ )
137
145
  profile_label = config.active_profile
138
146
 
139
147
  print_info(f"Profile: {profile_label}")