systemlink-cli 1.3.1__py3-none-any.whl

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 (74) hide show
  1. slcli/__init__.py +1 -0
  2. slcli/__main__.py +23 -0
  3. slcli/_version.py +4 -0
  4. slcli/asset_click.py +1289 -0
  5. slcli/cli_formatters.py +218 -0
  6. slcli/cli_utils.py +504 -0
  7. slcli/comment_click.py +602 -0
  8. slcli/completion_click.py +418 -0
  9. slcli/config.py +81 -0
  10. slcli/config_click.py +498 -0
  11. slcli/dff_click.py +979 -0
  12. slcli/dff_decorators.py +24 -0
  13. slcli/example_click.py +404 -0
  14. slcli/example_loader.py +274 -0
  15. slcli/example_provisioner.py +2777 -0
  16. slcli/examples/README.md +134 -0
  17. slcli/examples/_schema/schema-v1.0.json +169 -0
  18. slcli/examples/demo-complete-workflow/README.md +323 -0
  19. slcli/examples/demo-complete-workflow/config.yaml +638 -0
  20. slcli/examples/demo-test-plans/README.md +132 -0
  21. slcli/examples/demo-test-plans/config.yaml +154 -0
  22. slcli/examples/exercise-5-1-parametric-insights/README.md +101 -0
  23. slcli/examples/exercise-5-1-parametric-insights/config.yaml +1589 -0
  24. slcli/examples/exercise-7-1-test-plans/README.md +93 -0
  25. slcli/examples/exercise-7-1-test-plans/config.yaml +323 -0
  26. slcli/examples/spec-compliance-notebooks/README.md +140 -0
  27. slcli/examples/spec-compliance-notebooks/config.yaml +112 -0
  28. slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +1553 -0
  29. slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +1577 -0
  30. slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +912 -0
  31. slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
  32. slcli/feed_click.py +892 -0
  33. slcli/file_click.py +932 -0
  34. slcli/function_click.py +1400 -0
  35. slcli/function_templates.py +85 -0
  36. slcli/main.py +406 -0
  37. slcli/mcp_click.py +269 -0
  38. slcli/mcp_server.py +748 -0
  39. slcli/notebook_click.py +1770 -0
  40. slcli/platform.py +345 -0
  41. slcli/policy_click.py +679 -0
  42. slcli/policy_utils.py +411 -0
  43. slcli/profiles.py +411 -0
  44. slcli/response_handlers.py +359 -0
  45. slcli/routine_click.py +763 -0
  46. slcli/skill_click.py +253 -0
  47. slcli/skills/slcli/SKILL.md +713 -0
  48. slcli/skills/slcli/references/analysis-recipes.md +474 -0
  49. slcli/skills/slcli/references/filtering.md +236 -0
  50. slcli/skills/systemlink-webapp/SKILL.md +744 -0
  51. slcli/skills/systemlink-webapp/references/deployment.md +123 -0
  52. slcli/skills/systemlink-webapp/references/nimble-angular.md +380 -0
  53. slcli/skills/systemlink-webapp/references/systemlink-services.md +192 -0
  54. slcli/ssl_trust.py +93 -0
  55. slcli/system_click.py +2216 -0
  56. slcli/table_utils.py +124 -0
  57. slcli/tag_click.py +794 -0
  58. slcli/templates_click.py +599 -0
  59. slcli/testmonitor_click.py +1667 -0
  60. slcli/universal_handlers.py +305 -0
  61. slcli/user_click.py +1218 -0
  62. slcli/utils.py +832 -0
  63. slcli/web_editor.py +295 -0
  64. slcli/webapp_click.py +981 -0
  65. slcli/workflow_preview.py +287 -0
  66. slcli/workflows_click.py +988 -0
  67. slcli/workitem_click.py +2258 -0
  68. slcli/workspace_click.py +576 -0
  69. slcli/workspace_utils.py +206 -0
  70. systemlink_cli-1.3.1.dist-info/METADATA +20 -0
  71. systemlink_cli-1.3.1.dist-info/RECORD +74 -0
  72. systemlink_cli-1.3.1.dist-info/WHEEL +4 -0
  73. systemlink_cli-1.3.1.dist-info/entry_points.txt +7 -0
  74. systemlink_cli-1.3.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,85 @@
1
+ """Helpers for initializing local function templates (TypeScript / Python).
2
+
3
+ This module encapsulates downloading and extracting subfolders from the
4
+ SystemLink Enterprise examples repository, adding safety checks against
5
+ path traversal, symlinks, and unexpected archive contents.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import io
11
+ import sys
12
+ import tarfile
13
+ from pathlib import Path
14
+ from typing import Dict
15
+
16
+ import click
17
+ import requests
18
+
19
+ from .utils import ExitCodes
20
+
21
+ TEMPLATE_REPO = "ni/systemlink-enterprise-examples"
22
+ TEMPLATE_BRANCH = "function-examples" # Treated as stable per user direction
23
+ TEMPLATE_SUBFOLDERS: Dict[str, str] = {
24
+ "typescript": "function-examples/typescript-hono-function",
25
+ "python": "function-examples/python-http-function",
26
+ }
27
+
28
+ _DOWNLOAD_TIMEOUT_SECONDS = 60
29
+
30
+
31
+ def download_and_extract_template(language: str, destination: Path) -> None:
32
+ """Download and extract the specified language template into destination.
33
+
34
+ Args:
35
+ language: Normalized language key ('typescript' or 'python').
36
+ destination: Directory to populate (must already exist).
37
+ """
38
+ if language not in TEMPLATE_SUBFOLDERS:
39
+ click.echo(f"✗ Unsupported template language: {language}", err=True)
40
+ sys.exit(ExitCodes.INVALID_INPUT)
41
+
42
+ subfolder = TEMPLATE_SUBFOLDERS[language]
43
+ tarball_url = f"https://codeload.github.com/{TEMPLATE_REPO}/tar.gz/{TEMPLATE_BRANCH}"
44
+ resp = None
45
+ try:
46
+ resp = requests.get(tarball_url, timeout=_DOWNLOAD_TIMEOUT_SECONDS)
47
+ except requests.RequestException as exc: # noqa: BLE001
48
+ click.echo(f"✗ Network error downloading template: {exc}", err=True)
49
+ sys.exit(ExitCodes.NETWORK_ERROR)
50
+ if resp.status_code != 200:
51
+ click.echo(
52
+ f"✗ Failed to download template (HTTP {resp.status_code}) from {tarball_url}",
53
+ err=True,
54
+ )
55
+ sys.exit(ExitCodes.NETWORK_ERROR)
56
+
57
+ try:
58
+ with tarfile.open(fileobj=io.BytesIO(resp.content), mode="r:gz") as tf: # type: ignore[arg-type]
59
+ for member in tf.getmembers():
60
+ # Skip symlinks / hard links for safety
61
+ if member.issym() or member.islnk(): # pragma: no cover - defensive
62
+ continue
63
+ parts = member.name.split("/", 1)
64
+ if len(parts) < 2:
65
+ continue
66
+ remainder = parts[1]
67
+ if not remainder.startswith(subfolder.rstrip("/")):
68
+ continue
69
+ # Compute relative path inside desired subfolder
70
+ relative_path = Path(remainder).relative_to(subfolder)
71
+ if any(p == ".." for p in relative_path.parts): # Path traversal guard
72
+ continue
73
+ target_path = destination / relative_path
74
+ if member.isdir():
75
+ target_path.mkdir(parents=True, exist_ok=True)
76
+ continue
77
+ target_path.parent.mkdir(parents=True, exist_ok=True)
78
+ extracted = tf.extractfile(member)
79
+ if not extracted:
80
+ continue
81
+ with open(target_path, "wb") as f_out:
82
+ f_out.write(extracted.read())
83
+ except Exception as exc: # noqa: BLE001
84
+ click.echo(f"✗ Error extracting template: {exc}", err=True)
85
+ sys.exit(ExitCodes.GENERAL_ERROR)
slcli/main.py ADDED
@@ -0,0 +1,406 @@
1
+ """slcli entry points."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ import click
8
+ import keyring
9
+ import questionary
10
+ import tomllib
11
+
12
+ from .asset_click import register_asset_commands
13
+ from .comment_click import register_comment_commands
14
+ from .completion_click import register_completion_command
15
+ from .config_click import register_config_commands
16
+ from .dff_click import register_dff_commands
17
+ from .example_click import register_example_commands
18
+ from .feed_click import register_feed_commands
19
+ from .file_click import register_file_commands
20
+ from .function_click import register_function_commands
21
+ from .mcp_click import register_mcp_commands
22
+ from .notebook_click import register_notebook_commands
23
+ from .platform import (
24
+ PLATFORM_UNKNOWN,
25
+ get_platform_info,
26
+ )
27
+ from .policy_click import register_policy_commands
28
+ from .profiles import set_profile_override
29
+ from .routine_click import register_routine_commands
30
+ from .skill_click import register_skill_commands
31
+ from .ssl_trust import OS_TRUST_INJECTED, OS_TRUST_REASON
32
+ from .system_click import register_system_commands
33
+ from .tag_click import register_tag_commands
34
+ from .templates_click import register_templates_commands
35
+ from .testmonitor_click import register_testmonitor_commands
36
+ from .user_click import register_user_commands
37
+ from .webapp_click import register_webapp_commands
38
+ from .workitem_click import register_workitem_commands
39
+ from .workspace_click import register_workspace_commands
40
+
41
+
42
+ def get_version() -> str:
43
+ """Get version from _version.py (built binary) or pyproject.toml (development)."""
44
+ try:
45
+ # Try to import from _version.py first (works in built binary)
46
+ from ._version import __version__
47
+
48
+ return __version__
49
+ except ImportError:
50
+ # Fall back to reading pyproject.toml (works in development)
51
+ try:
52
+ current_dir = Path(__file__).parent
53
+ pyproject_path = current_dir.parent / "pyproject.toml"
54
+
55
+ with open(pyproject_path, "rb") as f:
56
+ pyproject_data = tomllib.load(f)
57
+
58
+ return pyproject_data["tool"]["poetry"]["version"]
59
+ except Exception:
60
+ return "unknown"
61
+
62
+
63
+ CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
64
+
65
+
66
+ def get_ascii_art() -> str:
67
+ """Return ASCII art for SystemLink CLI."""
68
+ return """
69
+ ███████╗██╗ ██╗███████╗████████╗███████╗███╗ ███╗██╗ ██╗███╗ ██╗██╗ ██╗ ██████╗██╗ ██╗
70
+ ██╔════╝╚██╗ ██╔╝██╔════╝╚══██╔══╝██╔════╝████╗ ████║██║ ██║████╗ ██║██║ ██╔╝ ██╔════╝██║ ██║
71
+ ███████╗ ╚████╔╝ ███████╗ ██║ █████╗ ██╔████╔██║██║ ██║██╔██╗ ██║█████╔╝ ██║ ██║ ██║
72
+ ╚════██║ ╚██╔╝ ╚════██║ ██║ ██╔══╝ ██║╚██╔╝██║██║ ██║██║╚██╗██║██╔═██╗ ██║ ██║ ██║
73
+ ███████║ ██║ ███████║ ██║ ███████╗██║ ╚═╝ ██║███████╗██║██║ ╚████║██║ ██╗ ╚██████╗███████╗██║
74
+ ╚══════╝ ╚═╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝╚══════╝╚═╝
75
+ """
76
+
77
+
78
+ @click.group(context_settings=CONTEXT_SETTINGS, invoke_without_command=True)
79
+ @click.option("--version", "-v", is_flag=True, help="Show version and exit")
80
+ @click.option(
81
+ "--profile",
82
+ "-p",
83
+ envvar="SLCLI_PROFILE",
84
+ help="Use a specific profile for this command",
85
+ )
86
+ @click.pass_context
87
+ def cli(ctx: click.Context, version: bool, profile: Optional[str]) -> None:
88
+ """SystemLink CLI for managing SystemLink resources.""" # noqa: D403
89
+ if version:
90
+ click.echo(f"slcli version {get_version()}")
91
+ ctx.exit()
92
+
93
+ # Set profile override if specified (applies to all subcommands)
94
+ if profile:
95
+ set_profile_override(profile)
96
+
97
+ # Check for mandatory migration BEFORE any command runs
98
+ # Skip migration check only for version flag and config migrate command
99
+ if ctx.invoked_subcommand not in (None, "config"):
100
+ from .profiles import ProfileConfig, has_keyring_credentials, migrate_from_keyring
101
+
102
+ config_path = ProfileConfig.get_config_path()
103
+ if not config_path.exists() and has_keyring_credentials():
104
+ click.echo("⚠️ Migration Required")
105
+ click.echo("")
106
+ click.echo("slcli now uses profile-based configuration.")
107
+ click.echo("Existing keyring credentials detected and will be migrated to:")
108
+ click.echo(f" {config_path}")
109
+ click.echo("")
110
+ click.echo("Migrating credentials...")
111
+
112
+ try:
113
+ migrated_profile = migrate_from_keyring(profile_name="default", delete_keyring=True)
114
+ if migrated_profile:
115
+ click.echo(f"✓ Migrated credentials to profile 'default'")
116
+ click.echo(f" Server: {migrated_profile.server}")
117
+ if migrated_profile.web_url:
118
+ click.echo(f" Web URL: {migrated_profile.web_url}")
119
+ if migrated_profile.platform:
120
+ click.echo(f" Platform: {migrated_profile.platform}")
121
+ click.echo("✓ Deleted keyring entries")
122
+ click.echo("")
123
+ click.echo("Migration complete! Continuing with your command...")
124
+ click.echo("")
125
+ else:
126
+ click.echo(
127
+ "✗ Migration failed: No valid credentials found in keyring.", err=True
128
+ )
129
+ ctx.exit(1)
130
+ except Exception as e:
131
+ click.echo(f"✗ Migration failed: {e}", err=True)
132
+ click.echo("Run 'slcli config migrate' to try again.", err=True)
133
+ ctx.exit(1)
134
+
135
+ if ctx.invoked_subcommand is None:
136
+ click.echo(get_ascii_art())
137
+ click.echo(ctx.get_help())
138
+
139
+
140
+ @cli.command(hidden=True, name="_ca-info")
141
+ def ca_info() -> None:
142
+ """Show TLS CA trust source (hidden diagnostic)."""
143
+ if OS_TRUST_INJECTED:
144
+ click.echo(f"CA Source: system (reason={OS_TRUST_REASON})")
145
+ else:
146
+ # Determine if custom verify path set via env
147
+ import os
148
+
149
+ verify_env = os.environ.get("REQUESTS_CA_BUNDLE") or os.environ.get("SSL_CERT_FILE")
150
+ if verify_env:
151
+ click.echo(f"CA Source: custom-pem ({verify_env})")
152
+ else:
153
+ click.echo(f"CA Source: certifi (reason={OS_TRUST_REASON})")
154
+
155
+
156
+ @cli.command()
157
+ @click.option("--profile", "-p", help="Profile name (default: 'default')")
158
+ @click.option("--url", help="SystemLink API URL")
159
+ @click.option("--api-key", help="SystemLink API key")
160
+ @click.option("--web-url", help="SystemLink Web UI base URL")
161
+ @click.option("--workspace", "-w", help="Default workspace for this profile")
162
+ @click.option(
163
+ "--set-current/--no-set-current",
164
+ default=True,
165
+ help="Set as current profile (default: yes)",
166
+ )
167
+ @click.option(
168
+ "--readonly",
169
+ is_flag=True,
170
+ help=(
171
+ "Enable readonly mode (disables create, update, delete, import, upload, "
172
+ "publish, and disable commands)"
173
+ ),
174
+ )
175
+ def login(
176
+ profile: Optional[str],
177
+ url: Optional[str],
178
+ api_key: Optional[str],
179
+ web_url: Optional[str],
180
+ workspace: Optional[str],
181
+ set_current: bool,
182
+ readonly: bool,
183
+ ) -> None:
184
+ """Save SystemLink credentials to a profile.
185
+
186
+ This is an alias for 'slcli config add'. Use that command
187
+ for the same functionality and more configuration options.
188
+
189
+ Profiles allow you to configure multiple SystemLink environments and switch
190
+ between them. Credentials are stored in ~/.config/slcli/config.json.
191
+
192
+ Examples:
193
+ slcli login --profile dev
194
+ slcli login -p prod --url https://prod-api.example.com
195
+ slcli login --profile test --workspace "Testing" --readonly
196
+ """
197
+ from .config_click import _add_profile_impl
198
+
199
+ # Invoke the shared implementation
200
+ _add_profile_impl(
201
+ profile=profile,
202
+ url=url,
203
+ api_key=api_key,
204
+ web_url=web_url,
205
+ workspace=workspace,
206
+ set_current=set_current,
207
+ readonly=readonly,
208
+ )
209
+
210
+
211
+ @cli.command()
212
+ @click.option("--profile", "-p", help="Profile to remove (default: current profile)")
213
+ @click.option("--all", "remove_all", is_flag=True, help="Remove all profiles")
214
+ @click.option("--force", "-f", is_flag=True, help="Skip confirmation prompt")
215
+ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
216
+ """Remove stored SystemLink credentials.
217
+
218
+ By default, removes the current profile. Use --profile to remove a specific
219
+ profile, or --all to remove all profiles.
220
+
221
+ Also cleans up any legacy keyring entries.
222
+ """
223
+ from .profiles import ProfileConfig
224
+
225
+ cfg = ProfileConfig.load()
226
+
227
+ if remove_all:
228
+ if not force:
229
+ if not questionary.confirm(
230
+ "Remove all profiles and legacy keyring entries?",
231
+ default=False,
232
+ ).ask():
233
+ click.echo("Aborted.")
234
+ return
235
+
236
+ # Clear all profiles
237
+ cfg.profiles.clear()
238
+ cfg.current_profile = None
239
+ cfg.save()
240
+ click.echo("✓ All profiles removed.")
241
+
242
+ elif profile:
243
+ # Remove specific profile
244
+ if profile not in cfg.profiles:
245
+ click.echo(f"✗ Profile '{profile}' not found.", err=True)
246
+ return
247
+
248
+ if not force:
249
+ if not questionary.confirm(
250
+ f"Remove profile '{profile}'?",
251
+ default=False,
252
+ ).ask():
253
+ click.echo("Aborted.")
254
+ return
255
+
256
+ cfg.delete_profile(profile)
257
+ cfg.save()
258
+ click.echo(f"✓ Profile '{profile}' removed.")
259
+
260
+ else:
261
+ # Remove current profile
262
+ if not cfg.current_profile:
263
+ click.echo("No current profile set.", err=True)
264
+ return
265
+
266
+ current = cfg.current_profile
267
+ if not force:
268
+ if not questionary.confirm(
269
+ f"Remove current profile '{current}'?",
270
+ default=False,
271
+ ).ask():
272
+ click.echo("Aborted.")
273
+ return
274
+
275
+ cfg.delete_profile(current)
276
+ cfg.save()
277
+ click.echo(f"✓ Profile '{current}' removed.")
278
+ if cfg.current_profile:
279
+ click.echo(f" Current profile is now: {cfg.current_profile}")
280
+
281
+ # Also clean up legacy keyring entries
282
+ try:
283
+ keyring.delete_password("systemlink-cli", "SYSTEMLINK_API_KEY")
284
+ except Exception:
285
+ pass
286
+ try:
287
+ keyring.delete_password("systemlink-cli", "SYSTEMLINK_API_URL")
288
+ except Exception:
289
+ pass
290
+ try:
291
+ keyring.delete_password("systemlink-cli", "SYSTEMLINK_CONFIG")
292
+ except Exception:
293
+ pass
294
+
295
+
296
+ @cli.command()
297
+ @click.option("--format", "-f", type=click.Choice(["table", "json"]), default="table")
298
+ def info(format: str) -> None:
299
+ """Show current configuration and detected platform."""
300
+ from .profiles import ProfileConfig, get_active_profile
301
+
302
+ platform_info = get_platform_info()
303
+
304
+ # Add profile information
305
+ cfg = ProfileConfig.load()
306
+ active_profile = get_active_profile()
307
+ platform_info["current_profile"] = cfg.current_profile
308
+ platform_info["profile_count"] = len(cfg.profiles)
309
+ if active_profile:
310
+ platform_info["active_profile_workspace"] = active_profile.workspace
311
+ platform_info["active_profile_name"] = active_profile.name
312
+
313
+ if format == "json":
314
+ click.echo(json.dumps(platform_info, indent=2))
315
+ return
316
+
317
+ # Table format using box-drawing characters for key-value display.
318
+ # Note: This uses a custom layout rather than table_utils because table_utils
319
+ # is designed for list-style output (multiple uniform rows), while this command
320
+ # displays a single record with key-value pairs and feature availability.
321
+ # All text fields are truncated to prevent formatting issues with long values.
322
+ max_value_width = 45 # Maximum width for values before truncation
323
+ content_width = 61 # Total width inside the box
324
+
325
+ def truncate(value: str, max_len: int = max_value_width) -> str:
326
+ """Truncate a string with ellipsis if it exceeds max length."""
327
+ if len(value) > max_len:
328
+ return value[: max_len - 3] + "..."
329
+ return value
330
+
331
+ click.echo("\n┌" + "─" * content_width + "┐")
332
+ click.echo("│" + "SystemLink CLI Info".center(content_width) + "│")
333
+ click.echo("├" + "─" * content_width + "┤")
334
+
335
+ # Connection status
336
+ status = "✓ Connected" if platform_info["logged_in"] else "✗ Not logged in"
337
+ click.echo(f"│ Status: {status:<48}│")
338
+
339
+ # Profile information
340
+ profile_display = platform_info.get("active_profile_name", "None")
341
+ if platform_info.get("profile_count", 0) > 1:
342
+ profile_display = f"{profile_display} (1 of {platform_info['profile_count']})"
343
+ profile_display = truncate(profile_display)
344
+ click.echo(f"│ Profile: {profile_display:<48}│")
345
+
346
+ # Platform
347
+ platform_display = truncate(platform_info.get("platform_display", "Unknown"))
348
+ click.echo(f"│ Platform: {platform_display:<48}│")
349
+
350
+ # API URL
351
+ api_url = truncate(platform_info.get("api_url", "Not configured"))
352
+ click.echo(f"│ API URL: {api_url:<48}│")
353
+
354
+ # Web URL
355
+ web_url = truncate(platform_info.get("web_url", "Not configured"))
356
+ click.echo(f"│ Web URL: {web_url:<48}│")
357
+
358
+ # Default workspace
359
+ workspace = platform_info.get("active_profile_workspace")
360
+ if workspace:
361
+ workspace_display = truncate(workspace)
362
+ click.echo(f"│ Workspace: {workspace_display:<48}│")
363
+
364
+ click.echo("├" + "─" * content_width + "┤")
365
+ click.echo("│" + "Feature Availability".center(content_width) + "│")
366
+ click.echo("├" + "─" * content_width + "┤")
367
+
368
+ features = platform_info.get("features", {})
369
+ if features:
370
+ for feature_name, available in features.items():
371
+ status_icon = "✓" if available else "✗"
372
+ status_text = "Available" if available else "Not available"
373
+ # Truncate feature name if needed
374
+ display_name = truncate(feature_name, 29)
375
+ click.echo(f"│ {status_icon} {display_name:<30} {status_text:<26}│")
376
+ else:
377
+ if platform_info["platform"] == PLATFORM_UNKNOWN:
378
+ click.echo("│ Run 'slcli login' to detect platform features. │")
379
+ else:
380
+ click.echo("│ No feature information available. │")
381
+
382
+ click.echo("└" + "─" * content_width + "┘\n")
383
+
384
+
385
+ register_completion_command(cli)
386
+ register_asset_commands(cli)
387
+ register_comment_commands(cli)
388
+ register_dff_commands(cli)
389
+ register_config_commands(cli)
390
+ register_example_commands(cli)
391
+ register_feed_commands(cli)
392
+ register_file_commands(cli)
393
+ register_function_commands(cli)
394
+ register_mcp_commands(cli)
395
+ register_templates_commands(cli)
396
+ register_notebook_commands(cli)
397
+ register_policy_commands(cli)
398
+ register_routine_commands(cli)
399
+ register_system_commands(cli)
400
+ register_tag_commands(cli)
401
+ register_testmonitor_commands(cli)
402
+ register_webapp_commands(cli)
403
+ register_skill_commands(cli)
404
+ register_user_commands(cli)
405
+ register_workitem_commands(cli)
406
+ register_workspace_commands(cli)