devtorch-cli 3.0.1__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.
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: devtorch-cli
3
+ Version: 3.0.1
4
+ Summary: DevTorch — command-line interface for the devtorch-core governance SDK
5
+ Author-email: Hemant Joshi <hemant@flotorch.ai>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/hemantcgi/DevTorch
8
+ Project-URL: Repository, https://github.com/hemantcgi/DevTorch
9
+ Project-URL: Documentation, https://github.com/hemantcgi/DevTorch/blob/main/Implementation/README.md
10
+ Keywords: ai,agents,governance,audit,llm,reasoning,devtorch
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: devtorch-core>=3.0.1
23
+ Provides-Extra: cloud
24
+ Requires-Dist: devtorch-core[cloud]; extra == "cloud"
25
+ Provides-Extra: mcp
26
+ Requires-Dist: devtorch-core[mcp]; extra == "mcp"
27
+ Provides-Extra: audit
28
+ Requires-Dist: devtorch-core[audit]; extra == "audit"
29
+ Provides-Extra: proxy
30
+ Requires-Dist: devtorch-core[proxy]; extra == "proxy"
31
+ Provides-Extra: wrapper
32
+ Requires-Dist: devtorch-core[wrapper]; extra == "wrapper"
33
+ Provides-Extra: embeddings
34
+ Requires-Dist: devtorch-core[embeddings]; extra == "embeddings"
35
+ Provides-Extra: broadcast
36
+ Requires-Dist: devtorch-core[broadcast]; extra == "broadcast"
37
+ Provides-Extra: dev
38
+ Requires-Dist: devtorch-core[dev]; extra == "dev"
39
+ Provides-Extra: all
40
+ Requires-Dist: devtorch-core[all]; extra == "all"
41
+
42
+ # devtorch-cli
43
+
44
+ Command-line interface for [DevTorch](https://github.com/hemantcgi/DevTorch), a local-first AI governance platform.
45
+
46
+ This package installs the `devtorch` command and depends on `devtorch-core`, which provides the governance SDK.
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ pip install devtorch-cli
52
+ ```
53
+
54
+ For the full experience including cloud sync, MCP, and IDE wrappers:
55
+
56
+ ```bash
57
+ pip install devtorch-cli[all]
58
+ ```
59
+
60
+ See the main repository README for detailed setup instructions.
@@ -0,0 +1,19 @@
1
+ # devtorch-cli
2
+
3
+ Command-line interface for [DevTorch](https://github.com/hemantcgi/DevTorch), a local-first AI governance platform.
4
+
5
+ This package installs the `devtorch` command and depends on `devtorch-core`, which provides the governance SDK.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install devtorch-cli
11
+ ```
12
+
13
+ For the full experience including cloud sync, MCP, and IDE wrappers:
14
+
15
+ ```bash
16
+ pip install devtorch-cli[all]
17
+ ```
18
+
19
+ See the main repository README for detailed setup instructions.
@@ -0,0 +1,6 @@
1
+ """Make `python -m devtorch_cli` equivalent to the `devtorch` entry point."""
2
+
3
+ from devtorch_cli.main import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
@@ -0,0 +1,221 @@
1
+ """CLI commands for Sprint 30: trust, compliance, and security audit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import datetime
7
+ import json
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ from devtorch_core.audit import (
12
+ AuditFilters,
13
+ AuditService,
14
+ PrivacyConfig,
15
+ ExportSigner,
16
+ )
17
+
18
+
19
+ def _gcc_dir_from_args(args: argparse.Namespace) -> Path:
20
+ return Path(args.path).resolve() / ".GCC"
21
+
22
+
23
+ def _parse_iso(value: str | None) -> datetime.datetime | None:
24
+ if not value:
25
+ return None
26
+ try:
27
+ return datetime.datetime.fromisoformat(value)
28
+ except ValueError:
29
+ # Allow bare dates (YYYY-MM-DD).
30
+ try:
31
+ return datetime.datetime.strptime(value, "%Y-%m-%d")
32
+ except ValueError:
33
+ return None
34
+
35
+
36
+ def cmd_audit_export(args: argparse.Namespace) -> int:
37
+ gcc_dir = _gcc_dir_from_args(args)
38
+ if not gcc_dir.exists():
39
+ print(f"error: {gcc_dir} not found. Run `devtorch init` first.", file=sys.stderr)
40
+ return 1
41
+
42
+ service = AuditService(gcc_dir)
43
+ privacy = service.get_privacy_config()
44
+
45
+ if args.no_redaction:
46
+ privacy.redaction_enabled = False
47
+
48
+ filters = AuditFilters(
49
+ since=_parse_iso(args.since),
50
+ until=_parse_iso(args.until),
51
+ project=args.project,
52
+ workspace=args.workspace,
53
+ user=args.user,
54
+ event_type=args.event_type,
55
+ actor=args.actor,
56
+ limit=args.limit,
57
+ )
58
+
59
+ output_path = Path(args.output).resolve()
60
+ output_path.parent.mkdir(parents=True, exist_ok=True)
61
+
62
+ service = AuditService(gcc_dir)
63
+ result = service.export(
64
+ output_path,
65
+ fmt=args.format,
66
+ filters=filters,
67
+ privacy_config=privacy,
68
+ )
69
+
70
+ print(f"Exported {result['event_count']} event(s) to {result['output_path']} ({result['format']}).")
71
+ return 0
72
+
73
+
74
+ def cmd_audit_sign(args: argparse.Namespace) -> int:
75
+ input_path = Path(args.input).resolve()
76
+ output_path = Path(args.output).resolve()
77
+ key_path = Path(args.key).resolve()
78
+
79
+ if not input_path.exists():
80
+ print(f"error: input file not found: {input_path}", file=sys.stderr)
81
+ return 1
82
+ if not key_path.exists():
83
+ print(f"error: private key not found: {key_path}", file=sys.stderr)
84
+ return 1
85
+
86
+ signer = ExportSigner()
87
+ signer.sign_file(input_path, output_path, key_path)
88
+ print(f"Signed envelope written to {output_path}")
89
+ return 0
90
+
91
+
92
+ def cmd_audit_verify(args: argparse.Namespace) -> int:
93
+ envelope_path = Path(args.input).resolve()
94
+ key_path = Path(args.key).resolve()
95
+
96
+ if not envelope_path.exists():
97
+ print(f"error: envelope file not found: {envelope_path}", file=sys.stderr)
98
+ return 1
99
+ if not key_path.exists():
100
+ print(f"error: public key not found: {key_path}", file=sys.stderr)
101
+ return 1
102
+
103
+ signer = ExportSigner()
104
+ if signer.verify_file(envelope_path, key_path):
105
+ print("Signature verification: OK")
106
+ return 0
107
+ print("Signature verification: FAILED")
108
+ return 1
109
+
110
+
111
+ def _coerce_bool(value):
112
+ if isinstance(value, bool):
113
+ return value
114
+ if isinstance(value, str):
115
+ return value.lower() in ("true", "1", "yes", "on")
116
+ return bool(value)
117
+
118
+
119
+ def cmd_audit_privacy(args: argparse.Namespace) -> int:
120
+ gcc_dir = _gcc_dir_from_args(args)
121
+ if not gcc_dir.exists():
122
+ print(f"error: {gcc_dir} not found. Run `devtorch init` first.", file=sys.stderr)
123
+ return 1
124
+
125
+ service = AuditService(gcc_dir)
126
+ config = service.get_privacy_config()
127
+
128
+ updated = False
129
+ if args.telemetry is not None:
130
+ config.telemetry_enabled = _coerce_bool(args.telemetry)
131
+ updated = True
132
+ if args.prompt_telemetry is not None:
133
+ config.prompt_telemetry_enabled = _coerce_bool(args.prompt_telemetry)
134
+ updated = True
135
+ if args.redaction is not None:
136
+ config.redaction_enabled = _coerce_bool(args.redaction)
137
+ updated = True
138
+ if args.remote_endpoints is not None:
139
+ config.allow_remote_endpoints = _coerce_bool(args.remote_endpoints)
140
+ updated = True
141
+ if args.retention_days is not None:
142
+ config.retention_days = int(args.retention_days)
143
+ updated = True
144
+
145
+ if updated:
146
+ from devtorch_core.audit import save_privacy_config
147
+ save_privacy_config(gcc_dir, config)
148
+ print("Privacy settings updated.")
149
+
150
+ print(json.dumps(config.to_dict(), indent=2))
151
+ return 0
152
+
153
+
154
+ def cmd_audit_keygen(args: argparse.Namespace) -> int:
155
+ """Generate an Ed25519 key pair for signing audit exports."""
156
+ output_dir = Path(args.output_dir).resolve()
157
+ output_dir.mkdir(parents=True, exist_ok=True)
158
+ private_path = output_dir / args.private_key
159
+ public_path = output_dir / args.public_key
160
+
161
+ signer = ExportSigner()
162
+ private_pem, public_pem = signer.generate_keypair()
163
+ signer.save_key(private_path, private_pem)
164
+ signer.save_key(public_path, public_pem)
165
+
166
+ print(f"Generated signing key pair:")
167
+ print(f" Private key: {private_path}")
168
+ print(f" Public key: {public_path}")
169
+ return 0
170
+
171
+
172
+ def build_audit_parser(subparsers):
173
+ audit_parser = subparsers.add_parser("audit", help="Trust, compliance, and security audit tools.")
174
+ audit_sub = audit_parser.add_subparsers(dest="audit_command", required=True)
175
+
176
+ p_export = audit_sub.add_parser("export", help="Export audit events to JSON or PDF.")
177
+ p_export.add_argument("--format", choices=["json", "pdf"], required=True, help="Export format.")
178
+ p_export.add_argument("--output", required=True, help="Output file path.")
179
+ p_export.add_argument("--since", default=None, help="Include events at or after this ISO timestamp.")
180
+ p_export.add_argument("--until", default=None, help="Include events before this ISO timestamp.")
181
+ p_export.add_argument("--project", default=None, help="Filter by payload.project.")
182
+ p_export.add_argument("--workspace", default=None, help="Filter by payload.workspace.")
183
+ p_export.add_argument("--user", default=None, help="Filter by payload.user.")
184
+ p_export.add_argument("--event-type", default=None, help="Filter by event_type.")
185
+ p_export.add_argument("--actor", default=None, help="Filter by actor.")
186
+ p_export.add_argument("--limit", type=int, default=None, help="Maximum number of events to include.")
187
+ p_export.add_argument("--no-redaction", action="store_true", help="Disable PII redaction for this export.")
188
+ p_export.set_defaults(func=cmd_audit_export)
189
+
190
+ p_sign = audit_sub.add_parser("sign", help="Sign an exported JSON audit report.")
191
+ p_sign.add_argument("input", help="Path to exported JSON file.")
192
+ p_sign.add_argument("--output", required=True, help="Path to write signed envelope.")
193
+ p_sign.add_argument("--key", required=True, help="Path to Ed25519 private key (PEM).")
194
+ p_sign.set_defaults(func=cmd_audit_sign)
195
+
196
+ p_verify = audit_sub.add_parser("verify", help="Verify a signed audit report envelope.")
197
+ p_verify.add_argument("input", help="Path to signed envelope.")
198
+ p_verify.add_argument("--key", required=True, help="Path to Ed25519 public key (PEM).")
199
+ p_verify.set_defaults(func=cmd_audit_verify)
200
+
201
+ p_privacy = audit_sub.add_parser("privacy", help="Show or update privacy policy settings.")
202
+ p_privacy.add_argument("--telemetry", type=_str_to_bool, default=None, help="Enable/disable telemetry.")
203
+ p_privacy.add_argument("--prompt-telemetry", type=_str_to_bool, default=None, dest="prompt_telemetry", help="Enable/disable prompt telemetry.")
204
+ p_privacy.add_argument("--redaction", type=_str_to_bool, default=None, help="Enable/disable PII redaction.")
205
+ p_privacy.add_argument("--remote-endpoints", type=_str_to_bool, default=None, dest="remote_endpoints", help="Allow remote endpoints.")
206
+ p_privacy.add_argument("--retention-days", type=int, default=None, dest="retention_days", help="Data retention period in days.")
207
+ p_privacy.set_defaults(func=cmd_audit_privacy)
208
+
209
+ p_keygen = audit_sub.add_parser("keygen", help="Generate an Ed25519 key pair for signing exports.")
210
+ p_keygen.add_argument("--output-dir", default=".", dest="output_dir", help="Directory for key files.")
211
+ p_keygen.add_argument("--private-key", default="audit_private.pem", dest="private_key", help="Private key filename.")
212
+ p_keygen.add_argument("--public-key", default="audit_public.pem", dest="public_key", help="Public key filename.")
213
+ p_keygen.set_defaults(func=cmd_audit_keygen)
214
+
215
+ return audit_parser
216
+
217
+
218
+ def _str_to_bool(value: str | None) -> bool | None:
219
+ if value is None:
220
+ return None
221
+ return value.lower() in ("true", "1", "yes", "on")
@@ -0,0 +1,94 @@
1
+ """S28 CLI handlers for `devtorch capability role`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import List
9
+
10
+ from devtorch_core.capability import (
11
+ AgentRole,
12
+ CapabilityStore,
13
+ apply_default_policy,
14
+ )
15
+
16
+
17
+ def _store(args: argparse.Namespace) -> CapabilityStore:
18
+ gcc_dir = Path(args.path).resolve() / ".GCC"
19
+ return CapabilityStore(gcc_dir / "capabilities")
20
+
21
+
22
+ def _split_csv(value: str | None) -> List[str]:
23
+ if not value:
24
+ return []
25
+ return [p.strip() for p in value.split(",") if p.strip()]
26
+
27
+
28
+ def cmd_capability_role_list(args: argparse.Namespace) -> int:
29
+ store = _store(args)
30
+ role_ids = store.list_roles()
31
+ if not role_ids:
32
+ print("No roles defined.")
33
+ return 0
34
+ roles = [r for r in (store.load_role(rid) for rid in role_ids) if r is not None]
35
+ print(f"{'ID':<16} {'Name':<20} {'Allowed':<30} {'Disallowed'}")
36
+ print("-" * 80)
37
+ for r in roles:
38
+ allowed = ", ".join(r.allowed_concepts) or "(any)"
39
+ disallowed = ", ".join(r.disallowed_concepts) or "(none)"
40
+ print(f"{r.role_id:<16} {r.name:<20} {allowed:<30} {disallowed}")
41
+ return 0
42
+
43
+
44
+ def cmd_capability_role_show(args: argparse.Namespace) -> int:
45
+ store = _store(args)
46
+ role = store.load_role(args.role_id)
47
+ if role is None:
48
+ print(f"error: role '{args.role_id}' not found", file=sys.stderr)
49
+ return 1
50
+ print(f"Role {role.role_id}: {role.name}")
51
+ print(f" allowed_concepts: {role.allowed_concepts or '(any)'}")
52
+ print(f" disallowed_concepts: {role.disallowed_concepts or '(none)'}")
53
+ print(f" required_capabilities: {role.required_capabilities}")
54
+ print(f" max_temperature: {role.max_temperature}")
55
+ return 0
56
+
57
+
58
+ def cmd_capability_role_set(args: argparse.Namespace) -> int:
59
+ store = _store(args)
60
+ role = AgentRole(
61
+ role_id=args.role_id,
62
+ name=args.name or args.role_id,
63
+ allowed_concepts=_split_csv(args.allowed),
64
+ disallowed_concepts=_split_csv(args.disallowed),
65
+ required_capabilities=_split_csv(args.required),
66
+ max_temperature=args.max_temperature,
67
+ )
68
+ store.save_role(role)
69
+ print(f"Saved role {role.role_id}: {role.name}")
70
+ return 0
71
+
72
+
73
+ def cmd_capability_role_policy(args: argparse.Namespace) -> int:
74
+ store = _store(args)
75
+ policy = store.load_policy()
76
+ if policy is None:
77
+ print("No role policy defined.")
78
+ return 0
79
+ print(f"Default role: {policy.default_role}")
80
+ print("Roles:")
81
+ for r in policy.roles:
82
+ print(f" - {r.role_id}: {r.name}")
83
+ return 0
84
+
85
+
86
+ def cmd_capability_role_apply_default_policy(args: argparse.Namespace) -> int:
87
+ store = _store(args)
88
+ apply_default_policy(store)
89
+ policy = store.load_policy()
90
+ print("Applied default role policy.")
91
+ if policy:
92
+ print(f" default_role: {policy.default_role}")
93
+ print(f" roles: {', '.join(r.role_id for r in policy.roles)}")
94
+ return 0