systemlink-cli 1.4.4__tar.gz → 1.4.7__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 (74) hide show
  1. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/PKG-INFO +1 -1
  2. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/pyproject.toml +1 -1
  3. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/_version.py +1 -1
  4. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/skills/systemlink-webapp/SKILL.md +47 -34
  5. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/user_click.py +43 -49
  6. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/LICENSE +0 -0
  7. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/dff-editor/editor.js +0 -0
  8. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/dff-editor/index.html +0 -0
  9. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/__init__.py +0 -0
  10. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/__main__.py +0 -0
  11. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/asset_click.py +0 -0
  12. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/cli_formatters.py +0 -0
  13. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/cli_utils.py +0 -0
  14. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/comment_click.py +0 -0
  15. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/completion_click.py +0 -0
  16. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/config.py +0 -0
  17. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/config_click.py +0 -0
  18. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/dff_click.py +0 -0
  19. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/dff_decorators.py +0 -0
  20. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/example_click.py +0 -0
  21. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/example_loader.py +0 -0
  22. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/example_provisioner.py +0 -0
  23. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/README.md +0 -0
  24. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/_schema/schema-v1.0.json +0 -0
  25. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/demo-complete-workflow/README.md +0 -0
  26. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/demo-complete-workflow/config.yaml +0 -0
  27. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/demo-test-plans/README.md +0 -0
  28. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/demo-test-plans/config.yaml +0 -0
  29. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
  30. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
  31. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
  32. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
  33. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
  34. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
  35. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
  36. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
  37. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
  38. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
  39. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/feed_click.py +0 -0
  40. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/file_click.py +0 -0
  41. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/function_click.py +0 -0
  42. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/function_templates.py +0 -0
  43. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/main.py +0 -0
  44. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/mcp_click.py +0 -0
  45. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/mcp_server.py +0 -0
  46. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/notebook_click.py +0 -0
  47. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/platform.py +0 -0
  48. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/policy_click.py +0 -0
  49. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/policy_utils.py +0 -0
  50. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/profiles.py +0 -0
  51. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/response_handlers.py +0 -0
  52. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/routine_click.py +0 -0
  53. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/skill_click.py +0 -0
  54. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/skills/slcli/SKILL.md +0 -0
  55. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
  56. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/skills/slcli/references/filtering.md +0 -0
  57. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/skills/systemlink-webapp/references/deployment.md +0 -0
  58. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/skills/systemlink-webapp/references/nimble-angular.md +0 -0
  59. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/skills/systemlink-webapp/references/systemlink-services.md +0 -0
  60. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/ssl_trust.py +0 -0
  61. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/system_click.py +0 -0
  62. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/table_utils.py +0 -0
  63. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/tag_click.py +0 -0
  64. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/templates_click.py +0 -0
  65. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/testmonitor_click.py +0 -0
  66. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/universal_handlers.py +0 -0
  67. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/utils.py +0 -0
  68. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/web_editor.py +0 -0
  69. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/webapp_click.py +0 -0
  70. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/workflow_preview.py +0 -0
  71. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/workflows_click.py +0 -0
  72. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/workitem_click.py +0 -0
  73. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/workspace_click.py +0 -0
  74. {systemlink_cli-1.4.4 → systemlink_cli-1.4.7}/slcli/workspace_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: systemlink-cli
3
- Version: 1.4.4
3
+ Version: 1.4.7
4
4
  Summary: SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates.
5
5
  License-File: LICENSE
6
6
  Author: Fred Visser
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "systemlink-cli"
3
- version = "1.4.4"
3
+ version = "1.4.7"
4
4
  description = "SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates."
5
5
  authors = ["Fred Visser <fred.visser@emerson.com>"]
6
6
  packages = [{ include = "slcli" }]
@@ -1,4 +1,4 @@
1
1
  """Version information for slcli."""
2
2
 
3
3
  # This file is auto-generated. Do not edit manually.
4
- __version__ = "1.4.4"
4
+ __version__ = "1.4.7"
@@ -30,14 +30,14 @@ Ask before generating any code:
30
30
  3. **Starting point** — Fresh Angular project, or do they have existing code?
31
31
  4. **Auth context** — Will the app run on the same SystemLink instance it calls (same-origin cookie auth), or does it need an API key for a remote server?
32
32
 
33
- You do NOT need to ask about Angular version or Nimble versions — always use the latest (Angular 19, @ni/nimble-angular latest).
33
+ You do NOT need to ask about Angular version or Nimble versions — always use Angular 20 and the latest compatible `@ni/nimble-angular`.
34
34
 
35
35
  ---
36
36
 
37
37
  ## Step 2: Scaffold the Angular project
38
38
 
39
39
  ```bash
40
- npx -y @angular/cli@latest new <app-name> --routing --style=scss --skip-git --no-standalone
40
+ npx -y @angular/cli@20 new <app-name> --routing --style=scss --skip-git --no-standalone
41
41
  cd <app-name>
42
42
  npm install @ni/nimble-angular
43
43
  ```
@@ -58,22 +58,23 @@ npm install @ni/systemlink-clients-ts
58
58
 
59
59
  ### Available services (import paths)
60
60
 
61
- | Service | Import path | `baseUrl` (append to `window.location.origin`) |
62
- |---------|-------------|------------------------------------------------|
63
- | Feeds | `@ni/systemlink-clients-ts/feeds` | (none — spec paths already include `/nifeed/v1/`) |
64
- | Tags | `@ni/systemlink-clients-ts/tags` | `+ '/nitag'` |
65
- | User / Workspaces | `@ni/systemlink-clients-ts/user` | `+ '/niuser/v1'` |
66
- | Web Application | `@ni/systemlink-clients-ts/web-application` | `+ '/niapp/v1'` |
67
- | File Ingestion | `@ni/systemlink-clients-ts/file-ingestion` | `+ '/nifile'` |
68
- | Test Monitor | `@ni/systemlink-clients-ts/test-monitor` | `+ '/nitest'` |
69
- | Asset Management | `@ni/systemlink-clients-ts/asset-management` | `+ '/niapm'` |
70
- | Work Items | `@ni/systemlink-clients-ts/work-item` | `+ '/niworkorder'` |
71
- | Systems Management | `@ni/systemlink-clients-ts/systems-management` | `+ '/nisysmgmt'` |
72
- | Notebooks | `@ni/systemlink-clients-ts/notebook` | `+ '/ninotebook'` |
61
+ | Service | Import path | Client `baseUrl` |
62
+ |---------|-------------|------------------|
63
+ | Feeds | `@ni/systemlink-clients-ts/feeds` | `window.location.origin` |
64
+ | Tags | `@ni/systemlink-clients-ts/tags` | `window.location.origin + '/nitag'` |
65
+ | User / Workspaces | `@ni/systemlink-clients-ts/user` | `window.location.origin + '/niuser/v1'` |
66
+ | Web Application | `@ni/systemlink-clients-ts/web-application` | `window.location.origin + '/niapp/v1'` |
67
+ | File Ingestion | `@ni/systemlink-clients-ts/file-ingestion` | `window.location.origin + '/nifile'` |
68
+ | Test Monitor | `@ni/systemlink-clients-ts/test-monitor` | `window.location.origin + '/nitestmonitor'` |
69
+ | Asset Management | `@ni/systemlink-clients-ts/asset-management` | `window.location.origin` |
70
+ | Work Items | `@ni/systemlink-clients-ts/work-item` | `window.location.origin` |
71
+ | Work Orders | `@ni/systemlink-clients-ts/work-order` | `window.location.origin` |
72
+ | Systems Management | `@ni/systemlink-clients-ts/systems-management` | `window.location.origin` |
73
+ | Notebooks | `@ni/systemlink-clients-ts/notebook` | `window.location.origin` |
73
74
 
74
75
  The client factory for each service lives at `@ni/systemlink-clients-ts/<service>/client`.
75
76
 
76
- > **Base URL gotcha:** Each generated client's path depends on its OpenAPI spec base. For **Feeds**, the spec base is `https://host/` and operation paths already include `/nifeed/v1/...`, so use `baseUrl: window.location.origin` with no suffix. For all other services the spec root matches the service prefix (`https://host/nitag`, `https://host/niuser/v1`, etc.), so set `baseUrl: window.location.origin + '/<prefix>'` as shown above.
77
+ > **Base URL gotcha:** Verify the generated operation URLs, not just the service name. In the published `@ni/systemlink-clients-ts` package, `tags` uses `/v2/...` with `baseUrl: origin + '/nitag'`, `user` uses `/users` and `/workspaces` with `baseUrl: origin + '/niuser/v1'`, `web-application` uses `/webapps/...` with `baseUrl: origin + '/niapp/v1'`, `file-ingestion` uses `/v1/...` with `baseUrl: origin + '/nifile'`, and `test-monitor` uses `/v2/...` with `baseUrl: origin + '/nitestmonitor'`. Services like `feeds`, `asset-management`, `systems-management`, `work-item`, `work-order`, and `notebook` already include `/nifeed`, `/niapm`, `/nisysmgmt`, `/niworkitem`, `/niworkorder`, or `/ninotebook` in each operation path, so those clients should use `baseUrl: window.location.origin`.
77
78
 
78
79
  > **SDK type mismatch fallback:** If a generated SDK function causes `InputFieldValidationError`, verify the actual request body the server expects with a raw `curl` POST. Sometimes the generated types wrap the body in a `{ request: { ... } }` envelope that the server does not accept (or expect a flat body the type shows as nested). Use direct `fetch` with a manually constructed body as a reliable fallback when the SDK types are wrong.
79
80
 
@@ -390,10 +391,18 @@ const { data, error } = await buildClient().post<MyResponse, unknown>({
390
391
  Always compute the base URL from `window.location.origin` — never hardcode a hostname:
391
392
 
392
393
  ```typescript
393
- const BASE_URL = `${window.location.origin}/nitag`; // Tags
394
- const BASE_URL = `${window.location.origin}/nitest`; // Test Monitor
395
- const BASE_URL = `${window.location.origin}/niapm`; // Asset Management
396
- const BASE_URL = `${window.location.origin}/nifile`; // File Ingestion
394
+ const tagsBaseUrl = `${window.location.origin}/nitag`; // tags.js -> /v2/...
395
+ const userBaseUrl = `${window.location.origin}/niuser/v1`; // user.js -> /users, /workspaces
396
+ const webAppBaseUrl = `${window.location.origin}/niapp/v1`; // web-application.js -> /webapps/...
397
+ const fileIngestionBaseUrl = `${window.location.origin}/nifile`; // file-ingestion.js -> /v1/...
398
+ const testMonitorBaseUrl = `${window.location.origin}/nitestmonitor`; // test-monitor.js -> /v2/...
399
+
400
+ const feedsBaseUrl = window.location.origin; // feeds.js -> /nifeed/v1/...
401
+ const assetBaseUrl = window.location.origin; // asset-management.js -> /niapm/v1/...
402
+ const systemsBaseUrl = window.location.origin; // systems-management.js -> /nisysmgmt/v1/...
403
+ const workItemBaseUrl = window.location.origin; // work-item.js -> /niworkitem/v1/...
404
+ const workOrderBaseUrl = window.location.origin; // work-order.js -> /niworkorder/v1/...
405
+ const notebookBaseUrl = window.location.origin; // notebook.js -> /ninotebook/v1/...
397
406
  ```
398
407
 
399
408
  ### Authentication
@@ -608,7 +617,7 @@ node_modules/.bin/ng build --configuration production --output-path dist/<app-na
608
617
  ```
609
618
 
610
619
  - Do **not** pass `--base-href` — that would re-introduce the `<base>` element
611
- - Output goes to `dist/<app-name>/browser/` (Angular 19)
620
+ - Output goes to `dist/<app-name>/browser/` (Angular 20)
612
621
 
613
622
  If you hit budget errors, increase limits in `angular.json`:
614
623
 
@@ -647,8 +656,8 @@ Save the returned webapp ID — you'll need it for every subsequent redeploy.
647
656
  | CSP `unsafe-inline` error | Beasties injects `onload` in style tags | `inlineCritical: false` in angular.json optimization |
648
657
  | App stays light inside dark SystemLink shell | Theme-aware aliases defined on `:root` or embedded app not watching host provider | Define color/shadow aliases on `nimble-theme-provider`; sync `currentTheme` from parent provider |
649
658
  | `theme="dark"` is set but colors still look light | Checked the attribute only, not the resolved tokens | Inspect `getComputedStyle(themeProvider).getPropertyValue('--ni-nimble-application-background-color')` in the hosted iframe |
650
- | CORS / status 0 | `basePath` points to different origin | Set `basePath = window.location.origin + '/service-prefix'` |
651
- | 404 on API calls | Missing service prefix in base URL | e.g., `/nitag` not just `window.location.origin`; Feeds service paths already include `/nifeed/v1/` so use origin alone |
659
+ | CORS / status 0 | `baseUrl` points to the wrong origin or wrong service root | Match the generated client: for example `test-monitor` uses `${window.location.origin}/nitestmonitor`, while `work-item` uses `window.location.origin` |
660
+ | 404 on API calls | Wrong `baseUrl` for the selected client | Only `tags`, `user`, `web-application`, `file-ingestion`, and `test-monitor` need a prefixed `baseUrl`. `feeds`, `asset-management`, `systems-management`, `work-item`, `work-order`, and `notebook` use bare origin because their operation URLs already include the service prefix |
652
661
  | `InputFieldValidationError` on API call | SDK-generated request body has wrong shape | Inspect raw API; the generated type may add or omit a `request: {}` wrapper. Use direct `fetch` with manually constructed body |
653
662
  | nimble-dialog does not open | `*ngIf` destroys element before `ViewChild` can resolve | Remove `*ngIf` from the dialog element; use `@ViewChild` + `ElementRef` and call `nativeElement.show()` / `nativeElement.close()` |
654
663
  | Icon module import fails | Icon sub-path `@ni/nimble-angular/icons/...` does not exist | Import icon modules from the main `@ni/nimble-angular` barrel only |
@@ -670,18 +679,22 @@ If the host and iframe are same-origin, Playwright or DevTools can inspect `ifra
670
679
 
671
680
  ---
672
681
 
673
- ## Known SystemLink service prefixes
674
-
675
- | Service | URL prefix |
676
- |---------|-----------|
677
- | Tag Historian | `/nitag/v2` |
678
- | Test Monitor | `/nitest` |
679
- | Asset Management | `/niapm` |
680
- | Systems Management | `/nisysmgmt` |
681
- | Work Orders | `/niworkorder` |
682
- | Feeds (Package Manager) | `/nifeed` |
683
- | Files | `/nifile` |
684
- | Notebooks | `/ninotebook` |
682
+ ## Known SystemLink client base URLs
683
+
684
+ | Service | Client `baseUrl` |
685
+ |---------|------------------|
686
+ | Tag Historian | `window.location.origin + '/nitaghistorian'` |
687
+ | Tags | `window.location.origin + '/nitag'` |
688
+ | User / Workspaces | `window.location.origin + '/niuser/v1'` |
689
+ | Web Application | `window.location.origin + '/niapp/v1'` |
690
+ | File Ingestion | `window.location.origin + '/nifile'` |
691
+ | Test Monitor | `window.location.origin + '/nitestmonitor'` |
692
+ | Asset Management | `window.location.origin` |
693
+ | Systems Management | `window.location.origin` |
694
+ | Work Items | `window.location.origin` |
695
+ | Work Orders | `window.location.origin` |
696
+ | Feeds (Package Manager) | `window.location.origin` |
697
+ | Notebooks | `window.location.origin` |
685
698
 
686
699
  See `references/systemlink-services.md` for full API details.
687
700
 
@@ -13,6 +13,7 @@ from uuid import uuid4
13
13
 
14
14
  import click
15
15
  import questionary
16
+ from click.core import ParameterSource
16
17
 
17
18
  from .cli_utils import paginate_list_output, validate_output_format
18
19
  from .utils import (
@@ -25,6 +26,10 @@ from .utils import (
25
26
  from .workspace_utils import get_workspace_display_name, resolve_workspace_id
26
27
 
27
28
 
29
+ USER_QUERY_PAGE_SIZE = 100
30
+ USER_JSON_DEFAULT_TAKE = 1000
31
+
32
+
28
33
  def _get_policy_details(policy_id: str) -> Optional[dict]:
29
34
  """Fetch policy details from the Auth service.
30
35
 
@@ -412,6 +417,7 @@ def _query_all_users(
412
417
  sortby: str = "firstName",
413
418
  order: str = "asc",
414
419
  include_disabled: bool = False,
420
+ max_items: Optional[int] = None,
415
421
  ) -> list:
416
422
  """Query all users from the API with server-side pagination using continuation tokens.
417
423
 
@@ -430,6 +436,7 @@ def _query_all_users(
430
436
  sortby: Field to sort by
431
437
  order: Sort order ('asc' or 'desc')
432
438
  include_disabled: Whether to include disabled users
439
+ max_items: Maximum number of users to return. When omitted, fetches all pages.
433
440
 
434
441
  Returns:
435
442
  List of all users
@@ -437,7 +444,7 @@ def _query_all_users(
437
444
  url = f"{get_base_url()}/niuser/v1/users/query"
438
445
  all_users = []
439
446
  continuation_token = None
440
- page_size = 100 # API maximum take limit is 100
447
+ remaining_items = max_items if max_items and max_items > 0 else None
441
448
 
442
449
  # Build the base filter - combine user filter with active status filter if needed
443
450
  combined_filter = filter_str
@@ -451,8 +458,13 @@ def _query_all_users(
451
458
  combined_filter = active_filter
452
459
 
453
460
  while True:
461
+ request_take = (
462
+ USER_QUERY_PAGE_SIZE
463
+ if remaining_items is None
464
+ else min(USER_QUERY_PAGE_SIZE, remaining_items)
465
+ )
454
466
  payload = {
455
- "take": page_size,
467
+ "take": request_take,
456
468
  "sortby": sortby,
457
469
  "order": "ascending" if order == "asc" else "descending",
458
470
  }
@@ -472,6 +484,11 @@ def _query_all_users(
472
484
 
473
485
  all_users.extend(users)
474
486
 
487
+ if remaining_items is not None:
488
+ remaining_items -= len(users)
489
+ if remaining_items <= 0:
490
+ return all_users[:max_items]
491
+
475
492
  # Check for continuation token to get next page
476
493
  continuation_token = data.get("continuationToken")
477
494
  if not continuation_token:
@@ -499,8 +516,10 @@ def register_user_commands(cli: click.Group) -> None:
499
516
  "-t",
500
517
  type=int,
501
518
  default=25,
502
- show_default=True,
503
- help="Maximum number of users to return",
519
+ help=(
520
+ "Table page size (default 25). JSON returns up to 1000 users by default; "
521
+ "use --take to override."
522
+ ),
504
523
  )
505
524
  @click.option(
506
525
  "--format",
@@ -573,59 +592,34 @@ def register_user_commands(cli: click.Group) -> None:
573
592
  if user_type != "all":
574
593
  type_filter = f'type = "{user_type}"'
575
594
 
576
- # For JSON format, we can respect the take parameter and use server-side pagination
577
- # For table format, we fetch all users and do client-side pagination for better UX
578
- if format_output.lower() == "json":
579
- # Use server-side pagination for JSON output
580
- url = f"{get_base_url()}/niuser/v1/users/query"
581
-
582
- # Build the filter - combine search filter with active status filter if needed
583
- combined_filter = search_filter
584
- if not include_disabled:
585
- # Add active status filter to the query using correct Dynamic LINQ syntax
586
- # Note: User API uses 'status' field with values 'pending' or 'active'
587
- active_filter = 'status = "active"'
588
- if combined_filter:
589
- combined_filter = f"({combined_filter}) and {active_filter}"
590
- else:
591
- combined_filter = active_filter
592
-
593
- # Add type filter
594
- if type_filter:
595
- if combined_filter:
596
- combined_filter = f"({combined_filter}) and {type_filter}"
597
- else:
598
- combined_filter = type_filter
599
-
600
- payload = {
601
- "take": take,
602
- "sortby": sortby,
603
- "order": "ascending" if order == "asc" else "descending",
604
- }
605
-
595
+ combined_filter = search_filter
596
+ if type_filter:
606
597
  if combined_filter:
607
- payload["filter"] = combined_filter
598
+ combined_filter = f"({combined_filter}) and {type_filter}"
599
+ else:
600
+ combined_filter = type_filter
608
601
 
609
- resp = make_api_request("POST", url, payload=payload)
610
- data = resp.json()
611
- users = data.get("users", [])
602
+ if format_output.lower() == "json":
603
+ ctx = click.get_current_context()
604
+ take_source = ctx.get_parameter_source("take")
605
+ json_take = (
606
+ USER_JSON_DEFAULT_TAKE if take_source == ParameterSource.DEFAULT else take
607
+ )
608
+
609
+ users = _query_all_users(
610
+ filter_str=combined_filter,
611
+ sortby=sortby,
612
+ order=order,
613
+ include_disabled=include_disabled,
614
+ max_items=json_take,
615
+ )
612
616
 
613
617
  click.echo(json.dumps(users, indent=2))
614
618
  return
615
619
  else:
616
620
  # For table format, fetch all users for proper client-side pagination
617
- # Combine filters for table output
618
- combined_filter_for_table = search_filter
619
- if type_filter:
620
- if combined_filter_for_table:
621
- combined_filter_for_table = (
622
- f"({combined_filter_for_table}) and {type_filter}"
623
- )
624
- else:
625
- combined_filter_for_table = type_filter
626
-
627
621
  all_users = _query_all_users(
628
- filter_str=combined_filter_for_table,
622
+ filter_str=combined_filter,
629
623
  sortby=sortby,
630
624
  order=order,
631
625
  include_disabled=include_disabled,
File without changes