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,123 @@
1
+ # Deployment Reference — slcli webapp commands
2
+
3
+ ## Prerequisites
4
+
5
+ - `slcli` installed and authenticated (`slcli login` or config file present)
6
+ - Angular app built to `dist/<app-name>/browser/`
7
+
8
+ ---
9
+
10
+ ## Build
11
+
12
+ ```bash
13
+ # Run from project root
14
+ node_modules/.bin/ng build --configuration production --output-path dist/<app-name>
15
+ ```
16
+
17
+ **Do NOT pass `--base-href`.** This would reintroduce a `<base>` element that violates SystemLink's CSP.
18
+
19
+ Angular 19 places the browser output at `dist/<app-name>/browser/` — publish that subdirectory, not the parent.
20
+
21
+ ### Background build (if terminal has heredoc/interrupt issues)
22
+
23
+ ```bash
24
+ nohup node_modules/.bin/ng build --configuration production --output-path dist/<app-name> \
25
+ > /tmp/ng-build.log 2>&1 &
26
+ echo "Build PID: $!"
27
+ # Check progress:
28
+ tail -f /tmp/ng-build.log
29
+ ```
30
+
31
+ ---
32
+
33
+ ## First deploy (no existing webapp)
34
+
35
+ ```bash
36
+ slcli webapp publish dist/<app-name>/browser/ -w <workspace-name>
37
+ ```
38
+
39
+ The command prints the new webapp ID. **Save it** — you need it for every future redeploy and for `slcli webapp open`.
40
+
41
+ Example output:
42
+ ```
43
+ Created webapp: 3727d9ac-86e1-4d6e-820e-d2630c1b28e9
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Redeploy (update existing webapp)
49
+
50
+ ```bash
51
+ slcli webapp publish dist/<app-name>/browser/ -w <workspace-name> -i <webapp-id>
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Open in browser
57
+
58
+ ```bash
59
+ slcli webapp open -i <webapp-id>
60
+ ```
61
+
62
+ ---
63
+
64
+ ## List webapps
65
+
66
+ ```bash
67
+ slcli webapp list -w <workspace-name>
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Delete a webapp
73
+
74
+ ```bash
75
+ slcli webapp delete -i <webapp-id>
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Deployment checklist
81
+
82
+ Before publishing, verify:
83
+
84
+ - [ ] `index.html` has **no** `<base href>` tag
85
+ - [ ] `app.module.ts` provides `{ provide: APP_BASE_HREF, useValue: '/' }`
86
+ - [ ] `app-routing.module.ts` uses `useHash: true`
87
+ - [ ] `angular.json` has `inlineCritical: false` in production optimization
88
+ - [ ] `basePath` is `window.location.origin + '/<service-prefix>'` (not just origin)
89
+ - [ ] `credentials: 'include'` (or equivalent) set on API client
90
+ - [ ] Build succeeded with no errors (warnings about bundle size are OK if within 2MB error limit)
91
+
92
+ ---
93
+
94
+ ## Common deployment errors
95
+
96
+ ### App shows blank/white screen
97
+
98
+ - Check browser console for NG04002 → `useHash: true` missing
99
+ - Check for CSP `base-uri` error → remove `<base>` tag
100
+
101
+ ### API calls fail with status 0 (CORS)
102
+
103
+ - `basePath` is pointing to a different origin than where the app is served
104
+ - Fix: use `window.location.origin + '/service-prefix'`
105
+
106
+ ### API calls return 404 on correct paths
107
+
108
+ - `basePath` is missing the service prefix (e.g., `/nitag`)
109
+ - The generated client's `defaultBasePath` is being overridden by your `Configuration` — make sure your `basePath` value includes the full prefix
110
+
111
+ ### Styles look broken or CSP reports `unsafe-inline`
112
+
113
+ - Beasties CSS inliner is injecting `onload` handlers
114
+ - Fix: set `inlineCritical: false` in angular.json
115
+
116
+ ### "Budget exceeded" build error
117
+
118
+ Increase error limits in `angular.json`:
119
+ ```json
120
+ "budgets": [
121
+ { "type": "initial", "maximumWarning": "1MB", "maximumError": "2MB" }
122
+ ]
123
+ ```
@@ -0,0 +1,380 @@
1
+ # Nimble Angular — Template & Usage Reference
2
+
3
+ ## nimble-theme-provider
4
+
5
+ Wrap your entire app. Always place at the root component level.
6
+
7
+ ```html
8
+ <nimble-theme-provider [theme]="currentTheme">
9
+ <router-outlet></router-outlet>
10
+ </nimble-theme-provider>
11
+ ```
12
+
13
+ Themes: `light`, `dark`, `color` (high contrast).
14
+
15
+ For SystemLink-hosted apps, do not hard-code `theme="light"` unless the user explicitly wants a fixed theme. The common pattern is:
16
+
17
+ 1. Detect initial theme from `?theme=...`, then the parent frame's `nimble-theme-provider`, then local storage, then system preference
18
+ 2. If the app is hosted in a same-origin iframe, watch the parent provider's `theme` attribute with `MutationObserver` and update `currentTheme`
19
+ 3. Define theme-aware CSS aliases such as colors and shadows on `nimble-theme-provider`, not on `:root`, so token resolution follows the active Nimble theme
20
+
21
+ When debugging theme mismatches, inspect resolved token values on the provider with `getComputedStyle(provider).getPropertyValue('--ni-nimble-application-background-color')` rather than only checking the `theme` attribute.
22
+
23
+ ---
24
+
25
+ ## nimble-table
26
+
27
+ Displays tabular data. Data must be an `Observable<TableRecord[]>` bound with `[data$]`.
28
+
29
+ ### Module
30
+
31
+ ```typescript
32
+ import { NimbleTableModule } from "@ni/nimble-angular/table";
33
+ ```
34
+
35
+ ### Row type requirement
36
+
37
+ Your row type must satisfy `TableRecord`. Add an index signature:
38
+
39
+ ```typescript
40
+ interface MyRow {
41
+ id: string;
42
+ name: string;
43
+ value: string | undefined;
44
+ [key: string]: FieldValue | undefined; // required for TableRecord compatibility
45
+ }
46
+ ```
47
+
48
+ ### Template
49
+
50
+ ```html
51
+ <nimble-table
52
+ [data$]="rows$"
53
+ id-field-name="id"
54
+ selection-mode="single"
55
+ (selection-change)="onSelectionChange($event)"
56
+ >
57
+ <nimble-table-column-text field-name="name" column-id="col-name"
58
+ >Name</nimble-table-column-text
59
+ >
60
+
61
+ <nimble-table-column-text field-name="value" column-id="col-value"
62
+ >Value</nimble-table-column-text
63
+ >
64
+ </nimble-table>
65
+ ```
66
+
67
+ ### Component wiring
68
+
69
+ ```typescript
70
+ import { TableRecord, TableRowSelectionEventDetail } from '@ni/nimble-angular/table';
71
+ import { BehaviorSubject } from 'rxjs';
72
+
73
+ rows$ = new BehaviorSubject<MyRow[]>([]);
74
+
75
+ onSelectionChange(event: CustomEvent<TableRowSelectionEventDetail<MyRow>>): void {
76
+ const selected = event.detail.selectedRecords[0];
77
+ // ...
78
+ }
79
+ ```
80
+
81
+ ---
82
+
83
+ ## nimble-table-column-text
84
+
85
+ Simple string column. Import: `NimbleTableColumnTextModule` from `@ni/nimble-angular/table-column/text`.
86
+
87
+ ```html
88
+ <nimble-table-column-text field-name="myField" column-id="col-1">
89
+ Column Header
90
+ </nimble-table-column-text>
91
+ ```
92
+
93
+ ---
94
+
95
+ ## nimble-button
96
+
97
+ ```typescript
98
+ import { NimbleButtonModule } from "@ni/nimble-angular";
99
+ ```
100
+
101
+ ```html
102
+ <!-- Default -->
103
+ <nimble-button (click)="doSomething()">Click Me</nimble-button>
104
+
105
+ <!-- Accent/primary style -->
106
+ <nimble-button
107
+ appearance="block"
108
+ appearance-variant="accent"
109
+ (click)="doSomething()"
110
+ >
111
+ Primary Action
112
+ </nimble-button>
113
+
114
+ <!-- Ghost / low-emphasis -->
115
+ <nimble-button appearance="ghost" (click)="cancel()">Cancel</nimble-button>
116
+ ```
117
+
118
+ > **Note:** `appearance="accent"` is NOT valid. Use `appearance="block" appearance-variant="accent"`.
119
+
120
+ ---
121
+
122
+ ## nimble-anchor-tabs + nimble-anchor-tab
123
+
124
+ Use for top-level page navigation. Bind `[activeid]` from component state; update it by tracking `NavigationEnd` router events.
125
+
126
+ ```typescript
127
+ import {
128
+ NimbleAnchorTabsModule,
129
+ NimbleAnchorTabModule,
130
+ } from "@ni/nimble-angular";
131
+ ```
132
+
133
+ ```html
134
+ <nimble-anchor-tabs [activeid]="activeTabId">
135
+ <nimble-anchor-tab id="catalog" nimbleRouterLink="/catalog"
136
+ >Catalog</nimble-anchor-tab
137
+ >
138
+ <nimble-anchor-tab id="installed" nimbleRouterLink="/installed"
139
+ >Installed</nimble-anchor-tab
140
+ >
141
+ <nimble-anchor-tab id="settings" nimbleRouterLink="/settings"
142
+ >Settings</nimble-anchor-tab
143
+ >
144
+ </nimble-anchor-tabs>
145
+ ```
146
+
147
+ Track active tab in the component (see `SKILL.md → Step 9` for full `NavigationEnd` subscription pattern).
148
+
149
+ > Do not use `<nimble-tabs>` + `<nimble-tab-panel>` for navigation — that pattern is for tabbed content within a single page, not routing.
150
+
151
+ ---
152
+
153
+ ## nimble-text-field
154
+
155
+ ```typescript
156
+ import { NimbleTextFieldModule } from "@ni/nimble-angular";
157
+ ```
158
+
159
+ ```html
160
+ <!-- Basic -->
161
+ <nimble-text-field
162
+ [(ngModel)]="filterValue"
163
+ placeholder="Enter filter..."
164
+ (ngModelChange)="onFilterChange()"
165
+ >
166
+ Filter
167
+ </nimble-text-field>
168
+
169
+ <!-- With icon prefix (slot="start") -->
170
+ <nimble-text-field
171
+ placeholder="Search…"
172
+ [(ngModel)]="searchTerm"
173
+ (ngModelChange)="applyFilters()"
174
+ >
175
+ <nimble-icon-magnifying-glass slot="start"></nimble-icon-magnifying-glass>
176
+ </nimble-text-field>
177
+ ```
178
+
179
+ Import icon modules from the **main `@ni/nimble-angular` barrel** — icon-specific sub-paths do not exist:
180
+
181
+ ```typescript
182
+ import { NimbleIconMagnifyingGlassModule } from "@ni/nimble-angular";
183
+ ```
184
+
185
+ > Use `(ngModelChange)` rather than `(change)` for reactive value handling. `(change)` fires on blur only; `(ngModelChange)` fires on every keystroke.
186
+
187
+ ---
188
+
189
+ ## nimble-select + nimble-list-option
190
+
191
+ ```typescript
192
+ import { NimbleSelectModule, NimbleListOptionModule } from "@ni/nimble-angular";
193
+ ```
194
+
195
+ ```html
196
+ <!-- Basic -->
197
+ <nimble-select [(ngModel)]="selectedType" (ngModelChange)="onTypeChange()">
198
+ <nimble-list-option value="">All types</nimble-list-option>
199
+ <nimble-list-option value="DOUBLE">Double</nimble-list-option>
200
+ <nimble-list-option value="STRING">String</nimble-list-option>
201
+ <nimble-list-option value="BOOLEAN">Boolean</nimble-list-option>
202
+ </nimble-select>
203
+
204
+ <!-- With built-in filter (useful for long lists) -->
205
+ <nimble-select filter-mode="standard" [(ngModel)]="selectedWorkspace">
206
+ <nimble-list-option *ngFor="let ws of workspaces" [value]="ws.id"
207
+ >{{ ws.name }}</nimble-list-option
208
+ >
209
+ </nimble-select>
210
+ ```
211
+
212
+ > Use `filter-mode="standard"` to add a built-in text filter to the dropdown — no custom search logic needed for long option lists.
213
+
214
+ ---
215
+
216
+ ## nimble-drawer
217
+
218
+ Side panel for details or config. Control with `#drawerRef` template variable.
219
+
220
+ ```typescript
221
+ import { NimbleDrawerModule } from "@ni/nimble-angular";
222
+ ```
223
+
224
+ ```html
225
+ <nimble-drawer #detailDrawer location="right">
226
+ <h3 slot="header">Detail</h3>
227
+ <div>{{ selectedItem?.name }}</div>
228
+ <nimble-button slot="footer" (click)="detailDrawer.hide()"
229
+ >Close</nimble-button
230
+ >
231
+ </nimble-drawer>
232
+
233
+ <nimble-button (click)="detailDrawer.show()">Open Detail</nimble-button>
234
+ ```
235
+
236
+ ---
237
+
238
+ ## nimble-spinner
239
+
240
+ ```typescript
241
+ import { NimbleSpinnerModule } from "@ni/nimble-angular";
242
+ ```
243
+
244
+ ```html
245
+ <nimble-spinner *ngIf="loading"></nimble-spinner>
246
+ ```
247
+
248
+ ---
249
+
250
+ ## nimble-banner
251
+
252
+ For in-page error/warning/info messages.
253
+
254
+ ```typescript
255
+ import { NimbleBannerModule } from "@ni/nimble-angular";
256
+ ```
257
+
258
+ ```html
259
+ <nimble-banner *ngIf="error" severity="error" [open]="!!error">
260
+ {{ error }}
261
+ </nimble-banner>
262
+ ```
263
+
264
+ ---
265
+
266
+ ## nimble-dialog
267
+
268
+ **Critical:** never put `*ngIf` on a `nimble-dialog`. The `*ngIf="false"` removes the element from the DOM, making `@ViewChild` return `undefined` and `.show()` impossible to invoke.
269
+
270
+ ```typescript
271
+ import { NimbleDialogModule } from "@ni/nimble-angular";
272
+ ```
273
+
274
+ ```html
275
+ <!-- Keep the dialog always in the DOM -->
276
+ <nimble-dialog #confirmDialog>
277
+ <span slot="title">Confirm Action</span>
278
+ <span slot="subtitle">This cannot be undone.</span>
279
+
280
+ <p>Are you sure you want to proceed?</p>
281
+
282
+ <!-- Multiple slot="footer" elements are displayed side by side -->
283
+ <nimble-button slot="footer" (click)="closeDialog()">Cancel</nimble-button>
284
+ <nimble-button
285
+ slot="footer"
286
+ appearance="block"
287
+ appearance-variant="accent"
288
+ (click)="confirm()"
289
+ >Confirm</nimble-button
290
+ >
291
+ </nimble-dialog>
292
+ ```
293
+
294
+ ```typescript
295
+ import { ElementRef, ViewChild } from '@angular/core';
296
+
297
+ @ViewChild('confirmDialog') private dialogEl?: ElementRef;
298
+
299
+ openDialog(): void { this.dialogEl?.nativeElement.show(); }
300
+ closeDialog(): void { this.dialogEl?.nativeElement.close(); }
301
+ ```
302
+
303
+ **Available slots:** `title` (required text), `subtitle` (optional descriptive text), `footer` (action buttons — multiple allowed).
304
+
305
+ ---
306
+
307
+ ## Layout patterns
308
+
309
+ Nimble doesn't ship a grid/layout component. Use flexbox in SCSS:
310
+
311
+ ```scss
312
+ // component.scss
313
+ :host {
314
+ display: flex;
315
+ flex-direction: column;
316
+ height: 100%;
317
+ padding: 16px;
318
+ box-sizing: border-box;
319
+ }
320
+
321
+ .toolbar {
322
+ display: flex;
323
+ gap: 8px;
324
+ align-items: flex-end;
325
+ margin-bottom: 12px;
326
+ flex-wrap: wrap;
327
+ }
328
+
329
+ .table-container {
330
+ flex: 1;
331
+ min-height: 0; // important — lets flex child shrink below its content height
332
+ }
333
+
334
+ nimble-table {
335
+ height: 100%;
336
+ }
337
+ ```
338
+
339
+ ### Clickable card / list-row pattern
340
+
341
+ For any clickable tile or row, use Nimble tokens directly (not the `--sl-app-color-border` alias which is for dividers):
342
+
343
+ ```scss
344
+ .card {
345
+ border: 1px solid var(--ni-nimble-card-border-color);
346
+ background: var(--ni-nimble-section-background-color);
347
+ border-radius: var(--ni-nimble-small-padding, 4px);
348
+ cursor: pointer;
349
+ // Transition both shadow and border for a polished hover
350
+ transition:
351
+ box-shadow var(--ni-nimble-medium-delay, 0.15s) ease,
352
+ border-color var(--ni-nimble-medium-delay, 0.15s) ease;
353
+
354
+ &:hover {
355
+ box-shadow: var(
356
+ --ni-nimble-elevation-2-box-shadow,
357
+ 0 2px 8px rgba(0, 0, 0, 0.12)
358
+ );
359
+ border-color: var(--ni-nimble-border-hover-color);
360
+ }
361
+ }
362
+
363
+ // For equal-height cards in a CSS grid, the host element must fill the cell:
364
+ :host {
365
+ display: flex;
366
+ height: 100%;
367
+ }
368
+
369
+ .card {
370
+ flex: 1; // fills the :host flex container
371
+ }
372
+
373
+ .card-body {
374
+ flex: 1; // pushes footer to the bottom
375
+ }
376
+
377
+ .card-description {
378
+ flex: 1; // equalises description height across cards
379
+ }
380
+ ```
@@ -0,0 +1,192 @@
1
+ # SystemLink Services — API Reference
2
+
3
+ ## How to find a service's OpenAPI spec
4
+
5
+ ```
6
+ https://<server>/<service-prefix>/swagger/v2/<service-name>.yaml
7
+ ```
8
+
9
+ Examples:
10
+ - `https://myserver.com/nitag/swagger/v2/nitag.yaml`
11
+ - `https://myserver.com/nitest/swagger/v2/nitest.yaml`
12
+
13
+ Use the spec URL as the `input` in your `openapi-ts.config.ts`.
14
+
15
+ ---
16
+
17
+ ## Tag Historian (`/nitag`)
18
+
19
+ **Base URL:** `window.location.origin + '/nitag'`
20
+
21
+ ### Key endpoints
22
+
23
+ | Operation | Method | Path |
24
+ |-----------|--------|------|
25
+ | Query tags with current values | POST | `/nitag/v2/query-tags-with-values` |
26
+ | Get single tag | GET | `/nitag/v2/tags/{path}` |
27
+ | Write tag value | PUT | `/nitag/v2/tags/{path}/values/current` |
28
+ | Query tag history | POST | `/nitag/v2/history-data/query-decimated-data` |
29
+
30
+ ### Query body (`POST /query-tags-with-values`)
31
+
32
+ ```typescript
33
+ {
34
+ filter: 'path = "system.*" and type = "DOUBLE"', // LINQ filter string
35
+ take: 1000, // max records to return
36
+ orderBy: 'TIMESTAMP', // PATH | VALUE | TIMESTAMP | WORKSPACE
37
+ descending: true,
38
+ // projection: [...] // AVOID — flattens the response and breaks tag.path / tag.type access
39
+ }
40
+ ```
41
+
42
+ ### Response shape (`TagWithValue`)
43
+
44
+ ```typescript
45
+ {
46
+ tag: {
47
+ path: string,
48
+ type: 'DOUBLE' | 'INT' | 'STRING' | 'BOOLEAN' | 'U_INT64' | 'DATE_TIME',
49
+ keywords: string[],
50
+ properties: Record<string, string>,
51
+ workspace: string,
52
+ lastUpdated: string,
53
+ },
54
+ current: {
55
+ value: { value: string | number | boolean },
56
+ timestamp: string,
57
+ },
58
+ aggregates: { ... }
59
+ }
60
+ ```
61
+
62
+ > **Gotcha:** If you include `projection` in the query, the response is flattened — `tag` nesting is removed and `path`, `type` etc. lift to the top level. This breaks `twv.tag?.path`. Omit `projection` unless you update all mapping code to match the flat shape.
63
+
64
+ ---
65
+
66
+ ## Test Monitor (`/nitest`)
67
+
68
+ **Base URL:** `window.location.origin + '/nitest'`
69
+
70
+ ### Key endpoints
71
+
72
+ | Operation | Method | Path |
73
+ |-----------|--------|------|
74
+ | Query results | POST | `/nitest/v2/query-results` |
75
+ | Get result by ID | GET | `/nitest/v2/results/{id}` |
76
+ | Query steps | POST | `/nitest/v2/query-steps` |
77
+ | Get products | GET | `/nitest/v2/products` |
78
+
79
+ ### Query body
80
+
81
+ ```typescript
82
+ {
83
+ filter: 'startedWithin = "7d"',
84
+ orderBy: 'startedAt',
85
+ descending: true,
86
+ take: 200,
87
+ }
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Asset Management (`/niapm`)
93
+
94
+ **Base URL:** `window.location.origin + '/niapm'`
95
+
96
+ ### Key endpoints
97
+
98
+ | Operation | Method | Path |
99
+ |-----------|--------|------|
100
+ | Query assets | POST | `/niapm/v1/query-assets` |
101
+ | Get asset | GET | `/niapm/v1/assets/{id}` |
102
+ | Get calibration forecast | GET | `/niapm/v1/assets/{id}/policies` |
103
+
104
+ ---
105
+
106
+ ## Systems Management (`/nisysmgmt`)
107
+
108
+ **Base URL:** `window.location.origin + '/nisysmgmt'`
109
+
110
+ ### Key endpoints
111
+
112
+ | Operation | Method | Path |
113
+ |-----------|--------|------|
114
+ | Get systems | GET | `/nisysmgmt/v1/systems` |
115
+ | Query systems | POST | `/nisysmgmt/v1/query-systems` |
116
+
117
+ ---
118
+
119
+ ## Work Orders (`/niworkorder`)
120
+
121
+ **Base URL:** `window.location.origin + '/niworkorder'`
122
+
123
+ ### Key endpoints
124
+
125
+ | Operation | Method | Path |
126
+ |-----------|--------|------|
127
+ | Query work orders | POST | `/niworkorder/v1/query` |
128
+ | Update work order | PATCH | `/niworkorder/v1/work-orders/{id}` |
129
+
130
+ ---
131
+
132
+ ## Authentication patterns
133
+
134
+ ### Same-origin (recommended for deployed webapps)
135
+
136
+ The app is served by SystemLink and calls SystemLink. Browser session cookies are sent automatically if `credentials: 'include'` is set on fetch requests.
137
+
138
+ ```typescript
139
+ // hey-api client setup
140
+ import { createClient } from '@hey-api/client-fetch';
141
+
142
+ export const apiClient = createClient({
143
+ baseUrl: `${window.location.origin}/nitag`,
144
+ credentials: 'include',
145
+ });
146
+ ```
147
+
148
+ No API key needed. The user must be logged in to SystemLink in that browser tab.
149
+
150
+ ### API key (for dev/remote scenarios)
151
+
152
+ ```typescript
153
+ export const apiClient = createClient({
154
+ baseUrl: `${window.location.origin}/nitag`,
155
+ headers: {
156
+ 'x-ni-api-key': localStorage.getItem('sl_api_key') ?? '',
157
+ },
158
+ });
159
+ ```
160
+
161
+ Let the user enter their API key in a config UI (a Nimble drawer works well). Store in `localStorage`. Never hardcode keys.
162
+
163
+ ---
164
+
165
+ ## LINQ filter syntax (used by tags, test, and other services)
166
+
167
+ ```
168
+ path = "system.cpu.usage" exact match
169
+ path.StartsWith("system.") prefix match
170
+ type = "DOUBLE" enum match
171
+ lastUpdated > "2024-01-01" date comparison
172
+ path = "cpu" and type = "DOUBLE" AND
173
+ path = "cpu" or path = "mem" OR
174
+ keywords.Contains("production") array contains
175
+ ```
176
+
177
+ String values must be double-quoted in the filter string. Build filters by joining parts with ` and `.
178
+
179
+ ---
180
+
181
+ ## Error handling
182
+
183
+ SystemLink APIs return standard HTTP status codes. Common ones:
184
+
185
+ | Code | Meaning |
186
+ |------|---------|
187
+ | 401 | Not authenticated — user needs to log in, or API key is invalid |
188
+ | 403 | Authenticated but not authorized for this workspace/resource |
189
+ | 404 | Resource not found |
190
+ | 422 | Invalid request body (e.g., bad LINQ filter) |
191
+
192
+ Show errors in a `<nimble-banner severity="error">` with the status code and message from the response body.