neatlogs-cli 0.1.0-team-test.1

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ All notable changes to `neatlogs-cli` are documented in this file.
4
+
5
+ ## 0.1.0-team-test.1
6
+
7
+ - Team-testing prerelease of the NeatLogs public API CLI.
8
+
9
+ ## 0.1.0
10
+
11
+ - Initial public release of the NeatLogs public API CLI.
package/README.md ADDED
@@ -0,0 +1,602 @@
1
+ # NeatLogs CLI
2
+
3
+ Use the CLI to inspect and manage your NeatLogs projects from a terminal or an automation agent.
4
+
5
+ ## Choose the right credential
6
+
7
+ ```mermaid
8
+ flowchart LR
9
+ Start{"Who is using the CLI?"}
10
+ Human["Human"]
11
+ Agent["AI agent or CI"]
12
+ App["Application sending traces"]
13
+ OAuth["Browser login or Device Flow"]
14
+ ServiceToken["Service-account token"]
15
+ IngestKey["Ingest credential"]
16
+ Vault["Stored in the OS credential vault"]
17
+ SecretStore["Provided by an agent or CI secret store"]
18
+ AppSecret["Provided to the SDK as an application secret"]
19
+
20
+ Start --> Human --> OAuth --> Vault
21
+ Start --> Agent --> ServiceToken --> SecretStore
22
+ Start --> App --> IngestKey --> AppSecret
23
+ ```
24
+
25
+ - Humans use browser OAuth or Device Flow.
26
+ - Codex, Claude Code, CI, and other unattended clients use a service-account token.
27
+ - SDKs and applications use an ingest credential to send traces.
28
+
29
+ ## Human quickstart
30
+
31
+ 1. Install the CLI and confirm that it runs.
32
+
33
+ ```bash
34
+ npm install --global neatlogs-cli@team-test
35
+ neatlogs --help
36
+ ```
37
+
38
+ 2. Save the host and sign in through your browser.
39
+
40
+ ```bash
41
+ neatlogs profile set work --host '<https-app-origin>'
42
+ neatlogs --profile work auth login
43
+ # Remote/headless alternative:
44
+ neatlogs --profile work auth login --device
45
+ ```
46
+
47
+ 3. List the projects you can access.
48
+
49
+ ```bash
50
+ neatlogs --profile work projects list
51
+ ```
52
+
53
+ 4. Save the project you want this profile to use.
54
+
55
+ ```bash
56
+ neatlogs profile set work --host '<https-app-origin>' --project '<project-uuid>'
57
+ ```
58
+
59
+ 5. Check your identity and read recent traces.
60
+
61
+ ```bash
62
+ neatlogs --profile work whoami
63
+ neatlogs --profile work traces list --limit 10
64
+ ```
65
+
66
+ `profile set` stores non-secret host and project preferences. Login stores the
67
+ OAuth credentials separately in the native operating-system vault.
68
+
69
+ ## Request only the permissions you need
70
+
71
+ ```bash
72
+ # Default read-oriented login
73
+ neatlogs --profile work auth login
74
+
75
+ # Add only the write permissions required for evaluation work
76
+ neatlogs --profile work auth login \
77
+ --scope context:read observability:read evaluation:read evaluation:write offline_access
78
+ ```
79
+
80
+ The consent page lists only the permissions requested by this login command.
81
+ `offline_access` allows refresh tokens; it is not an API permission shown by
82
+ `whoami`.
83
+
84
+ ## Agent and CI automation
85
+
86
+ In **Settings → Service Accounts**, create a dedicated account, bind only the
87
+ required projects and role, and choose the minimum scopes. Inject
88
+ `NEATLOGS_TOKEN` through the agent or CI secret store; environment tokens are
89
+ never written to the OAuth keychain.
90
+
91
+ ```bash
92
+ export NEATLOGS_TOKEN='<service-account-token>'
93
+ export NEATLOGS_PROJECT_ID='<project-uuid>'
94
+ export NEATLOGS_HOST='<https-api-origin>'
95
+
96
+ neatlogs projects current --json
97
+ neatlogs whoami --json
98
+ neatlogs traces list --limit 10 --json
99
+ ```
100
+
101
+ Revoking the access token immediately stops later requests without disabling
102
+ the service account. `NEATLOGS_API_KEY` remains a legacy fallback; if it and
103
+ `NEATLOGS_TOKEN` are both set, they must contain the same value.
104
+
105
+ ## Credential storage and security
106
+
107
+ Human login uses Authorization Code with PKCE and an exact `127.0.0.1`
108
+ callback. `--device` explicitly selects Device Flow for a remote or headless
109
+ terminal. OAuth access and refresh tokens are bound to the exact host, issuer,
110
+ and profile, then stored in macOS Keychain, Windows Credential Manager, or
111
+ Linux Secret Service.
112
+
113
+ The CLI does not consult Git credential configuration and never silently falls
114
+ back to a file. Linux requires libsecret and a running Secret Service
115
+ implementation; the CLI does not fall back to the kernel keyring or a file when
116
+ that service is unavailable. On POSIX, protected plaintext storage is available
117
+ only with `--allow-file-credentials`; it uses directory mode `0700`, file mode
118
+ `0600`, and warns whenever it is used. Windows rejects that option because
119
+ equivalent ACL enforcement is not implemented.
120
+
121
+ Credentials are never accepted as command-line flags. The CLI never follows
122
+ redirects, executes arbitrary API URLs, or fetches a live API schema. Recognized
123
+ `nloa_`, `nlsa_`, and `nlpk_` prefixes select exactly one credential class; a
124
+ foreign or mismatched prefix is rejected without fallback.
125
+
126
+ `schema` inspects the release-embedded contract, not active deployment flags.
127
+ Each operation carries `x-neatlogs-operation-gate`; only `always` is enabled by
128
+ default, while every other gate must be enabled by the server before the
129
+ operation is callable.
130
+
131
+ ## Commands
132
+
133
+ ```text
134
+ neatlogs --profile <name> auth login [--device] [--allow-file-credentials] [--scope <scope...>]
135
+ neatlogs --profile <name> auth status
136
+ neatlogs --profile <name> auth logout [--all] [--local-only]
137
+ neatlogs profile list
138
+ neatlogs profile show [name]
139
+ neatlogs profile set <name> --host <https-origin> [--organization <uuid>] [--project <uuid>]
140
+ neatlogs profile use <name>
141
+ # Authenticated profiles must be logged out before deletion.
142
+ neatlogs profile delete <name>
143
+ neatlogs projects current
144
+ neatlogs projects create --organization <uuid> --name <name> [--timezone <iana>] [--idempotency-key <uuid>]
145
+ neatlogs projects update [--name <name>] [--timezone <iana-or-null>] [--idempotency-key <uuid>]
146
+ neatlogs account profile show
147
+ neatlogs notifications preferences show
148
+ neatlogs orgs list [--limit 1-100] [--cursor <opaque-cursor>]
149
+ neatlogs orgs get <organization-uuid>
150
+ neatlogs orgs members list <organization-uuid> [--limit 1-100] [--cursor <opaque-cursor>]
151
+ neatlogs orgs members get <organization-uuid> <user-uuid>
152
+ neatlogs orgs members update-role <organization-uuid> <user-uuid> --membership-id <membership-uuid> --expected-role owner|admin|member|guest --role admin|member|guest [--idempotency-key <uuid>]
153
+ neatlogs orgs members remove <organization-uuid> <user-uuid> --membership-id <membership-uuid> --expected-role owner|admin|member|guest --confirm <membership-uuid> [--idempotency-key <uuid>]
154
+ neatlogs orgs invitations list <organization-uuid> [--state all|pending|expired] [--limit 1-100] [--cursor <opaque-cursor>]
155
+ neatlogs orgs invitations invite <organization-uuid> --email <email> --role admin|member|guest [--idempotency-key <uuid>]
156
+ neatlogs orgs invitations resend <organization-uuid> <invitation-uuid> --email <email> [--idempotency-key <uuid>]
157
+ neatlogs orgs invitations revoke <organization-uuid> <invitation-uuid> --email <email> --confirm <invitation-uuid> [--idempotency-key <uuid>]
158
+ printf '%s' "$INVITATION_TOKEN" | neatlogs orgs invitations accept --token-stdin [--idempotency-key <uuid>]
159
+ neatlogs orgs roles list <organization-uuid> --action invitation|member-role-change
160
+ neatlogs orgs audit list <organization-uuid> [--limit 1-100] [--cursor <opaque-cursor>]
161
+ neatlogs service-accounts create <organization-uuid> --name <name> --binding <project-uuid=project_admin|project_member|project_viewer> [--binding ...] [--description <text>] [--idempotency-key <uuid>]
162
+ neatlogs service-accounts bindings add <service-account-uuid> --project-id <project-uuid> --role project_admin|project_member|project_viewer [--idempotency-key <uuid>]
163
+ neatlogs service-accounts bindings set-role <service-account-uuid> <binding-uuid> --role project_admin|project_member|project_viewer [--idempotency-key <uuid>]
164
+ neatlogs service-accounts bindings remove <service-account-uuid> <binding-uuid> --confirm <binding-uuid> [--idempotency-key <uuid>]
165
+ neatlogs billing subscription <organization-uuid>
166
+ neatlogs billing plans <organization-uuid>
167
+ neatlogs usage show <organization-uuid>
168
+ neatlogs usage alerts <organization-uuid> [--state all|unread|unacknowledged|unread-and-unacknowledged] [--limit 1-100] [--cursor <opaque-cursor>]
169
+ neatlogs projects list [--organization <uuid>] [--include-archived] [--limit 1-100] [--cursor <opaque-cursor>]
170
+ neatlogs skills list [--include-archived] [--limit 1-100] [--cursor <opaque-cursor>]
171
+ neatlogs skills get <skill-uuid>
172
+ neatlogs skills import --content-stdin [--name <lowercase-kebab>] [--description <text>] [--idempotency-key <uuid>]
173
+ neatlogs skills update <skill-uuid> --body-stdin [--idempotency-key <uuid>]
174
+ neatlogs skills delete <skill-uuid> --confirm <skill-uuid> [--idempotency-key <uuid>]
175
+ neatlogs whoami
176
+ neatlogs alerts list [--enabled all|true|false] [--rule-type threshold|per_event|compound|ingestion_health|metric_threshold] [--limit 1-100] [--cursor <opaque-cursor>]
177
+ neatlogs alerts get <alert-rule-uuid>
178
+ printf '%s' "$ALERT_JSON" | neatlogs alerts create --body-stdin [--idempotency-key <uuid>]
179
+ printf '%s' "$ALERT_PATCH_JSON" | neatlogs alerts update <alert-rule-uuid> --body-stdin [--idempotency-key <uuid>]
180
+ neatlogs alerts enable|disable <alert-rule-uuid> [--idempotency-key <uuid>]
181
+ neatlogs alerts delete <alert-rule-uuid> --confirm <alert-rule-uuid> [--idempotency-key <uuid>]
182
+ neatlogs alerts history [--alert-rule-id <uuid>] [--start-time <utc>] [--end-time <utc>] [--limit 1-100] [--cursor <opaque-cursor>]
183
+ neatlogs alerts changes <alert-rule-uuid> [--limit 1-100] [--cursor <opaque-cursor>]
184
+ neatlogs detections list [--status active|inactive|all] [--group <uuid>] [--limit 1-100] [--cursor <opaque-cursor>]
185
+ neatlogs detections get <detection-uuid>
186
+ neatlogs detections hits <detection-uuid> [--limit 1-100] [--cursor <opaque-cursor>]
187
+ neatlogs detections history <detection-uuid> [--limit 1-100] [--cursor <opaque-cursor>]
188
+ neatlogs detections trends <detection-uuid> --from <utc> --to <utc> [--granularity hour|day|week]
189
+ neatlogs prompts list [--name <exact-name>] [--source <source>] [--label <label>] [--limit 1-100] [--cursor <opaque-cursor>]
190
+ neatlogs prompts get <prompt-version-uuid>
191
+ neatlogs prompts history <prompt-version-uuid> [--limit 1-100] [--cursor <opaque-cursor>]
192
+ neatlogs prompts diff <from-version-uuid> <to-version-uuid>
193
+ neatlogs prompts create (--body-stdin | --name <name> (--content <text> | --content-stdin | --messages <json>)) [--config <json>] [--variables <json>] [--display-name <name>] [--role <role>] [--tags <csv>] [--commit-message <message>] [--idempotency-key <uuid>]
194
+ neatlogs prompts label <prompt-version-uuid> <label> [--idempotency-key <uuid>]
195
+ neatlogs prompts tag-add <prompt-version-uuid> <tag> [--idempotency-key <uuid>]
196
+ neatlogs prompts tag-remove <prompt-version-uuid> <tag> [--idempotency-key <uuid>]
197
+ neatlogs prompts delete <prompt-version-uuid> --confirm <prompt-version-uuid> [--idempotency-key <uuid>]
198
+ neatlogs evals list [--status draft|active|completed|archived|all] [--limit 1-100] [--cursor <opaque-cursor>]
199
+ neatlogs evals create --body-stdin [--idempotency-key <uuid>]
200
+ neatlogs evals launch <evaluation-uuid> --assign-me [--review-duration <duration>] [--idempotency-key <uuid>]
201
+ neatlogs evals close <evaluation-uuid> --confirm <evaluation-uuid> [--idempotency-key <uuid>]
202
+ neatlogs evals get <evaluation-uuid>
203
+ neatlogs evals form get <evaluation-uuid>
204
+ neatlogs evals form set <evaluation-uuid> --body-stdin [--idempotency-key <uuid>]
205
+ neatlogs evals update <evaluation-uuid> --body-stdin [--idempotency-key <uuid>]
206
+ neatlogs evals progress <evaluation-uuid>
207
+ neatlogs evals items list <evaluation-uuid> [--limit 1-100] [--cursor <opaque-cursor>]
208
+ neatlogs evals items get <evaluation-uuid> <item-uuid>
209
+ neatlogs evals assignments list <evaluation-uuid> <item-uuid>
210
+ neatlogs evals review get <evaluation-uuid> <item-uuid> [--assignment-id <uuid>]
211
+ neatlogs evals review draft <evaluation-uuid> <item-uuid> --body-stdin [--assignment-id <uuid>] [--idempotency-key <key>]
212
+ neatlogs evals review submit <evaluation-uuid> <item-uuid> --body-stdin [--assignment-id <uuid>] [--idempotency-key <key>]
213
+ neatlogs evals schedules list [--status draft|active|completed|archived|all] [--limit 1-100] [--cursor <opaque-cursor>]
214
+ neatlogs evals schedules get <schedule-uuid>
215
+ neatlogs evals schedules update <schedule-uuid> --batch-schedule <duration> [--idempotency-key <uuid>]
216
+ neatlogs analytics views save --body-stdin [--idempotency-key <uuid>]
217
+ neatlogs feedback create --trace <trace-id> --body-stdin [--idempotency-key <uuid>]
218
+ neatlogs feedback update <feedback-uuid> --trace <trace-id> --body-stdin [--idempotency-key <uuid>]
219
+ neatlogs feedback delete <feedback-uuid> --trace <trace-id> --confirm <feedback-uuid> [--idempotency-key <uuid>]
220
+ neatlogs feedback span-set <span-id> --trace <trace-id> --body-stdin [--idempotency-key <uuid>]
221
+ neatlogs feedback span-delete <span-id> --trace <trace-id> --confirm <span-id> [--idempotency-key <uuid>]
222
+ neatlogs comments create --trace <trace-id> --thread <thread-uuid> --content-stdin [--idempotency-key <uuid>]
223
+ neatlogs comments update <message-uuid> --trace <trace-id> --thread <thread-uuid> --content-stdin [--idempotency-key <uuid>]
224
+ neatlogs comments delete <message-uuid> --trace <trace-id> --thread <thread-uuid> --confirm <message-uuid> [--idempotency-key <uuid>]
225
+ neatlogs integrations providers
226
+ neatlogs integrations list
227
+ neatlogs webhooks create --body-stdin [--idempotency-key <uuid>]
228
+ neatlogs webhooks update <webhook-uuid> --body-stdin [--idempotency-key <uuid>]
229
+ neatlogs webhooks enable <webhook-uuid> [--idempotency-key <uuid>]
230
+ neatlogs webhooks disable <webhook-uuid> [--idempotency-key <uuid>]
231
+ neatlogs webhooks delete <webhook-uuid> --confirm <webhook-uuid> [--idempotency-key <uuid>]
232
+ neatlogs filters list [--limit 1-100] [--cursor <opaque-cursor>]
233
+ neatlogs filters get <saved-filter-uuid>
234
+ neatlogs traces list [--from <rfc3339>] [--to <rfc3339>] [--limit 1-50] [--cursor <opaque-cursor>]
235
+ neatlogs traces get <trace-id>
236
+ neatlogs traces export <trace-id> [--format json|markdown|csv]
237
+ neatlogs traces update-metadata <trace-id> --body-stdin [--idempotency-key <uuid>]
238
+ neatlogs traces search <query> [--from <rfc3339>] [--to <rfc3339>] [--workflow <name>] [--session <id>] [--limit 1-50] [--cursor <opaque-cursor>]
239
+ neatlogs traces semantic-search <query> [--from <rfc3339>] [--to <rfc3339>] [--workflow <name>] [--session <id>] [--limit 1-50] [--cursor <opaque-cursor>]
240
+ neatlogs traces spans list <trace-id> [--status <status>] [--type <type>] [--limit 1-50] [--cursor <opaque-cursor>]
241
+ neatlogs traces spans get <trace-id> <span-id>
242
+ neatlogs traces raw-spans <trace-id> [--status <status>] [--type <type>] [--limit 1-50] [--cursor <opaque-cursor>]
243
+ neatlogs traces payload <trace-id> <span-id> --fields input[,output,error]
244
+ neatlogs sessions list [--from <rfc3339>] [--to <rfc3339>] [--limit 1-50] [--cursor <opaque-cursor>]
245
+ neatlogs sessions get <session-id> [--limit 1-50] [--cursor <opaque-cursor>]
246
+ neatlogs sessions export <session-id> [--format json|markdown|csv]
247
+ neatlogs guest-links list [--limit 1-50] [--cursor <opaque-cursor>]
248
+ neatlogs guest-links get <guest-link-uuid>
249
+ neatlogs guest-links mint --evaluation <evaluation-uuid> --recipient-email <email> [--idempotency-key <uuid>]
250
+ neatlogs guest-links rotate <guest-link-uuid> [--idempotency-key <uuid>]
251
+ neatlogs guest-links revoke <guest-link-uuid> --confirm <guest-link-uuid> [--idempotency-key <uuid>]
252
+ neatlogs guest context --request-stdin [--cursor <cursor>] [--limit 1-25]
253
+ neatlogs guest submit <item-id> --request-stdin [--idempotency-key <uuid>]
254
+ neatlogs sharing traces get <trace-id>
255
+ neatlogs api-keys list [--limit 1-50] [--cursor <opaque-cursor>]
256
+ neatlogs ingest-credentials list [--limit 1-50] [--cursor <opaque-cursor>]
257
+ neatlogs api-keys create --name <name> --scope <scope> [--scope <scope>] [--expires-in-days 1-365] [--rate-limit 1-600]
258
+ neatlogs api-keys rotate <credential-uuid> --confirm <credential-uuid>
259
+ neatlogs api-keys revoke <credential-uuid> --confirm <credential-uuid>
260
+ neatlogs ingest-credentials create --name <name> [--expires-in-days 1-365]
261
+ neatlogs ingest-credentials rotate <credential-uuid> --confirm <credential-uuid>
262
+ neatlogs ingest-credentials revoke <credential-uuid> --confirm <credential-uuid>
263
+ neatlogs mcp status
264
+ neatlogs coding-tools status
265
+ neatlogs --project <uuid> api <operationId> [--param <name=value...>] [--query <name=value...>]
266
+ printf '%s' '{"q":"failed checkout"}' | neatlogs --project <uuid> api post_api_v1_public_search_traces --body-stdin
267
+ neatlogs schema list
268
+ neatlogs schema search <query>
269
+ neatlogs schema show <operationId>
270
+ ```
271
+
272
+ For confidential or multiline prompt text, prefer bounded stdin so content is
273
+ not exposed in shell history or process arguments. Prompt content is limited to
274
+ 60 KiB within the Public API's 64 KiB JSON request ceiling:
275
+
276
+ ```bash
277
+ neatlogs prompts create --name support-answer --content-stdin < prompt.txt
278
+ neatlogs prompts create --body-stdin < complete-prompt-request.json
279
+ ```
280
+
281
+ Use `--body-stdin` when messages, variables, configuration, or commit metadata
282
+ are also sensitive. It is mutually exclusive with individual prompt field
283
+ options and validates the complete request before sending it.
284
+
285
+ Version creation and label activation are intentionally separate authorization
286
+ steps: `prompts create` requires prompt write access, while `prompts label`
287
+ requires prompt management access.
288
+
289
+ `api` accepts only operation IDs with an explicit handler compiled into this
290
+ CLI release. `--param` and `--query` accept bounded `name=value` pairs, reject
291
+ duplicates, and are checked against that operation's embedded parameter
292
+ allowlist before any request. Body-bearing safe reads accept one object from
293
+ `--body-stdin`, bounded to 64 KiB and validated against the embedded request
294
+ schema before any request. `schema` commands are offline and do not require credentials.
295
+ `schema list` and `schema search` return concise descriptors. `schema show`
296
+ returns the normalized embedded operation contract, including request and
297
+ response schemas, authentication requirements, and execution/risk metadata.
298
+ New schema operations default to non-executable until an explicit handler and
299
+ risk classification are added.
300
+
301
+ Raw `api` execution fails closed for operations that require target-specific
302
+ destructive confirmation. Use the operation's curated command instead; raw
303
+ execution intentionally has no generic `--confirm` flag because confirmation
304
+ bindings differ by resource.
305
+
306
+ Feedback and webhook bodies use bounded stdin so comments, expected responses,
307
+ destination URLs, and write-only webhook secrets do not appear in process
308
+ arguments. Integration status is local and redacted; it does not discover
309
+ providers, decrypt credentials, or contact external services.
310
+
311
+ Organization discovery is OAuth-only. Organization list requires
312
+ `context:read` plus current `org:read`; detail requires `organization:read`
313
+ plus current `org:read`. Project discovery accepts OAuth or service-account
314
+ tokens, requires `context:read` plus current `project:read`, and sends no
315
+ `x-project-id`. Legacy credentials and an explicitly selected project are
316
+ rejected locally for all discovery commands.
317
+
318
+ Account profile and notification-preference reads are OAuth-only and require
319
+ `profile:read`. Profile ignores a globally configured project and never sends
320
+ `x-project-id`; it exposes `avatarConfigured`, never an avatar URL. Notification
321
+ preferences require `--project` or `NEATLOGS_PROJECT_ID`, bind the current user
322
+ to that selected project, and print all channel, digest, Slack-DM, and
323
+ quiet-hours settings. Service-account and legacy credentials are rejected
324
+ locally before a request.
325
+
326
+ Saved-filter reads are OAuth-only and require a selected project,
327
+ `observability:read`, and current `trace:read`. Lists omit filter criteria;
328
+ detail returns only the caller-owned, project-bound filter tree. Trees are
329
+ bounded to 8 group levels, 100 total nodes, and 256 KiB. Opaque list cursors
330
+ bind the operation, selected project, OAuth actor, and normalized list filters.
331
+
332
+ Organization-access reads are also OAuth-only, forbid project selection, and
333
+ require `organization:read` plus the caller's current `member:read` permission.
334
+ Member responses expose only safe identity and active-membership fields.
335
+ Invitation pages omit tokens and accepted invitations, default to `pending`, and
336
+ can filter pending or expired rows. Role results are action-specific assignment
337
+ options; ownership transfer is not exposed.
338
+
339
+ Invitation mutations are OAuth-only, forbid project selection, and reuse a
340
+ stable idempotency key for retries. Invite accepts only generated assignable
341
+ roles. Resend and revoke bind both the organization and exact invitation UUID;
342
+ revoke additionally requires repeating that UUID with `--confirm`. Acceptance
343
+ reads the confidential token only from bounded non-interactive stdin via
344
+ `--token-stdin`; the token is never accepted as an argument or option value and
345
+ is not emitted in CLI output or diagnostics.
346
+
347
+ Alert-rule reads accept OAuth users and project-bound service accounts with
348
+ `configuration:read`, current `alert:read`, and an exact selected project.
349
+ `alerts list` returns bounded summaries with an opaque cursor; `alerts get`
350
+ returns typed rule configuration plus only a channel-presence mask. Neither
351
+ command exposes destination IDs, webhook configuration, creator identity,
352
+ tenant IDs, nor the internal property-filter DSL. Detail reports explicit
353
+ description/condition truncation flags and the original condition count.
354
+ `alerts history` reads a maximum 30-day fire window with an optional rule
355
+ filter, signed cursor, five bounded sample trace IDs, delivery-kind allowlist,
356
+ and truncation counts; it excludes private rule-change history, payloads,
357
+ destination identifiers, project IDs, actor data, and breaching span IDs.
358
+ `alerts changes` reads a rule's privacy-safe change audit, including after soft
359
+ deletion. It returns only strict change types, allowlisted public field names,
360
+ and bounded redaction counts; actor identity and raw before/after values are
361
+ never returned.
362
+
363
+ Detection read commands validate UUIDs, page bounds, status, path
364
+ parameters, response size, and the generated response schema before emitting
365
+ output. Their server operations accept OAuth or service-account tokens and
366
+ require a selected project plus `configuration:read` and `detection:read`; a
367
+ service account must also have an active binding to that project.
368
+ Lists return compact summaries and an opaque `nextCursor` when another page is
369
+ available. Pass that value unchanged with `--cursor`; page size may change
370
+ between requests. `detections get` returns the typed detector
371
+ configuration under a 768 KiB server budget and a 1 MiB client ceiling.
372
+ `detections history` returns created/updated/toggled/deleted events with only
373
+ the actor category and an allowlisted changed-field list. It never returns
374
+ user IDs, email addresses, service-account IDs, or before/after diff values.
375
+ Detection-result commands use the data-plane authorization boundary instead:
376
+ `observability:read` plus current `trace:read`. `detections hits` returns only
377
+ bounded trace/span references and scores, never detector metadata or trace
378
+ payloads, and uses a signed project-and-detection-bound cursor. `detections
379
+ trends` accepts a maximum 90-day window and returns UTC hour/day/week buckets.
380
+
381
+ With the server mutation gate enabled, managed detection commands are:
382
+
383
+ ```bash
384
+ neatlogs detections create --group <group-id> --name "PII" --type pii --config '{"entities":["EMAIL_ADDRESS"]}'
385
+ neatlogs detections update <detection-id> --threshold 0.8
386
+ neatlogs detections disable <detection-id>
387
+ neatlogs detections enable <detection-id>
388
+ neatlogs detections delete <detection-id> --confirm <detection-id>
389
+ ```
390
+
391
+ Create/update validate bounded type-specific JSON on the server, require
392
+ `configuration:write`, current `detection:manage`, an exact selected project,
393
+ and an idempotency key. The CLI generates a UUID unless a retry deliberately
394
+ reuses `--idempotency-key`. Enable/disable set the desired state rather than
395
+ toggling, so replay is safe. Delete is non-interactive, requires the exact ID
396
+ twice, and refuses while any live ID-bound or legacy name-bound alert rule
397
+ depends on the detection. Configuration changes invalidate prior results and
398
+ schedule a backfill after the transaction commits.
399
+
400
+ Detection-group catalog commands are `groups list` and `groups get`. With the
401
+ server mutation gate enabled, `groups create --name`, `groups update <id>
402
+ --name`, and `groups delete <id> --confirm <id>` provide managed-group parity.
403
+ Create/update/delete accept an optional `--idempotency-key`; otherwise the CLI
404
+ generates a fresh UUID. Delete is non-interactive and requires the exact group
405
+ ID twice, which is safe for coding agents and CI. These operations require
406
+ `configuration:write`, current `detection:manage`, and a selected project; an
407
+ OAuth user or project-bound service account may call them.
408
+
409
+ Evaluation commands accept OAuth or service-account tokens. They require a
410
+ selected project, `evaluation:read`, and current `eval:read`; a service account
411
+ must also have an active binding to that project. Lists are
412
+ compact and project-scoped; `evals get` returns the bounded evaluation
413
+ configuration under a 512 KiB server budget and the same 1 MiB client ceiling.
414
+ Progress uses one tenant-scoped database snapshot. Item commands expose bounded
415
+ metadata only—not trace payloads, forms, answers, evaluator identity, or
416
+ internal revision fields—and item pages have a 768 KiB server ceiling.
417
+ `evals form get` returns only the current bounded question projection. With the
418
+ mutation gate enabled, a creator can use `evals form set --body-stdin` to append
419
+ and activate an immutable draft-form version; internal evaluator assignments
420
+ and AI verdict keys are never exposed. Replacement is human-evaluation-only and
421
+ returns a conflict when current questions carry private evaluator routing or AI
422
+ verdict metadata, preventing a read/edit/write cycle from widening access.
423
+
424
+ The evaluation creator can launch an existing-trace human evaluation and assign
425
+ it to themselves:
426
+
427
+ ```bash
428
+ neatlogs evals launch '<evaluation-uuid>' --assign-me --review-duration 7d
429
+ ```
430
+
431
+ This OAuth-user-only action requires creator ownership and sends no email or
432
+ Slack notification; the assignment remains available in the NeatLogs app.
433
+
434
+ Assigned-review discovery and reads are OAuth-only because an assignment belongs
435
+ to a real user. Draft and submit additionally require `evaluation:write`,
436
+ current `eval:submit`, and `PUBLIC_API_MUTATIONS_ENABLED=true` on the server.
437
+ The CLI reads a bounded JSON object from stdin and creates a high-entropy
438
+ idempotency key unless `--idempotency-key` is supplied for a deliberate retry.
439
+ If an automatically generated key reaches an ambiguous timeout, network, or
440
+ server-contract failure, the CLI prints that non-secret UUID in its diagnostic;
441
+ reuse it with `--idempotency-key` instead of minting a new mutation.
442
+ The same key and same request replay the stored result; reusing a key for a
443
+ different body fails with `IDEMPOTENCY_KEY_CONFLICT`. Service accounts cannot
444
+ impersonate a human reviewer.
445
+
446
+ Reusable evaluator definitions use `evaluators list|get|create|update|archive|
447
+ restore|duplicate`. Reads require `evaluation:read` and current `eval:read`;
448
+ writes require `evaluation:write`, current `eval:manage`, and the server
449
+ mutation gate. Create/update accept bounded JSON only through `--body-stdin`,
450
+ so prompts and tool configuration do not appear in shell history. Tool config
451
+ is write-only and is never printed; detail output reports only whether each
452
+ grant is configured. Archive/restore are explicit state-setting patches, and
453
+ duplicate atomically copies the definition, hidden grant configuration, and
454
+ pinned skills under a new non-conflicting name.
455
+
456
+ Trace/session commands accept legacy registry-v2 keys, OAuth, or service-account
457
+ tokens. OAuth/service-account credentials require a selected project; a legacy
458
+ key uses its bound project and may omit one. All require `observability:read`
459
+ and current `trace:read`. Search is bounded to 30 days and 16 terms; keyword and semantic
460
+ pagination uses actor/project/query-bound opaque snapshots. Raw previews are
461
+ byte-capped. `traces payload` requires a unique explicit field allowlist and
462
+ never returns object-storage locations or presigned URLs.
463
+ Search results expose at most 20 matched span identities per trace and only
464
+ their ID, name, and type. Trace/session exports support bounded JSON, Markdown,
465
+ and CSV. Human terminal output prints Markdown/CSV directly; `--json` preserves
466
+ the response envelope for pipelines. Export composition uses public projections
467
+ only and excludes raw payloads, private attributes, snippets, and S3 locations.
468
+
469
+ Guest-link and trace-sharing reads accept OAuth or service-account tokens,
470
+ require a selected project, `sharing:read`, and current `sharing:read`. They do
471
+ not accept legacy keys. Responses expose only bounded metadata and state—never
472
+ guest tokens, token hashes, public URLs or IDs, recipient email, creator
473
+ identity, or tenant identifiers. `guest-links list` cursors bind the operation,
474
+ selected project, and authenticated actor.
475
+
476
+ Guest-link mint requires `--recipient-email <email>`, normalizes the recipient,
477
+ and reveals the opaque token once. Rotation preserves the recipient and reveals
478
+ only the replacement token; metadata reads never reread either token. Guest
479
+ review uses no stored credential, profile, selected project, or environment
480
+ token. The capability token and request must arrive together as strict JSON on
481
+ stdin:
482
+
483
+ ```bash
484
+ printf '%s' '{"token":"<guest-token>"}' |
485
+ neatlogs guest context --request-stdin
486
+
487
+ printf '%s' '{"token":"<guest-token>","answers":[{"questionId":"<question-uuid>","answerValue":"yes"}]}' |
488
+ neatlogs guest submit '<item-uuid>' --request-stdin
489
+ ```
490
+
491
+ Context accepts 1–25 items per page (default 25), at most 100 questions, 16 KiB
492
+ UTF-8 input and output previews with truncation flags, and a 1 MiB final
493
+ envelope. Submission accepts at most 100 distinct answers, 32 KiB per answer,
494
+ and 64 KiB raw JSON. It performs full answer replacement, including clearing
495
+ omitted optional answers. The CLI generates a high-entropy idempotency key
496
+ unless the caller supplies one for an intentional retry. Expired or revoked
497
+ tokens fail on fresh execution and replay. The generic `neatlogs api` executor
498
+ cannot run guest operations. Browser guest routes remain compatible because
499
+ they use the same context and submission services.
500
+
501
+ Credential inventory commands accept OAuth or service-account tokens, require
502
+ the selected project's current `api_key:read` permission plus `access:read`,
503
+ and reject legacy keys as callers. `api-keys list` reports safe Public API key
504
+ prefix metadata and whether the broad legacy project key is configured.
505
+ `ingest-credentials list` reports dedicated prefix/last-four hints and, for an
506
+ OAuth caller only, whether that caller has a legacy personal write key for the
507
+ selected organization. Neither command can reread plaintext or verifier hashes.
508
+
509
+ MCP and coding-tool status commands are OAuth-user-only, require a selected
510
+ project and `configuration:read`, and apply current `mcp:use` or `tool:use`
511
+ RBAC. MCP status also enforces the organization's plan entitlement. Responses
512
+ contain only aggregate configuration/run state; they never contain stored
513
+ tokens, URLs, headers, prompts, repositories, provider IDs, task IDs, or error
514
+ messages. Service-account and legacy credentials are rejected locally before a
515
+ request.
516
+
517
+ ## Configuration and output
518
+
519
+ - `NEATLOGS_TOKEN` is the primary credential source; `NEATLOGS_API_KEY` is the
520
+ legacy fallback. An environment credential takes precedence over a selected
521
+ stored profile and is identified as `environment` by `auth status`.
522
+ - `--profile <name>` selects non-secret host/project preferences and its
523
+ separately stored OAuth credential. Stored tokens are never sent when a
524
+ `--host` or `NEATLOGS_HOST` override differs from the credential-bound host.
525
+ - Expiring OAuth access tokens are refreshed before an API request. Refresh
526
+ rotation is persisted back to the same credential store. Normal logout first
527
+ revokes the refresh family on the server and only then deletes the local
528
+ secret; `--local-only` is an explicit recovery escape hatch.
529
+ - Product reads and selected-project context commands require `--project
530
+ <uuid>` or `NEATLOGS_PROJECT_ID` for OAuth/service-account credentials.
531
+ `--project` takes precedence. A project is optional for legacy credentials.
532
+ Discovery commands instead forbid any selected project.
533
+ - A service-account token is sent only as `Authorization: Bearer` and the
534
+ selected project only as `x-project-id`. A legacy key is sent only as
535
+ `x-api-key`.
536
+ - `--host <origin>` overrides `NEATLOGS_HOST`. One of them is required for
537
+ online commands; the CLI never guesses a deployment domain. Offline `schema`
538
+ commands require neither.
539
+ - Hosts must be HTTPS origins with no path, user information, query, or hash.
540
+ - Browser PKCE is the default login. `--device` is the explicit fallback for a
541
+ remote/headless terminal. Both request reviewed OAuth scopes; current RBAC is
542
+ still evaluated by the server for every API request.
543
+ - `--json` or non-TTY stdout produces stable JSON. Data uses stdout and
544
+ diagnostics use stderr.
545
+ - Requests time out after 10 seconds and never follow redirects.
546
+ - Idempotent GET requests and explicitly marked safe POST reads retry at most
547
+ twice after a `429` or `503`, honoring a safe, bounded `Retry-After` value.
548
+ - Idempotent mutating operations retry at most twice after a `429` or `503`.
549
+ Other mutating operations are never retried.
550
+
551
+ ## Guarded live-local context and organization-access smoke
552
+
553
+ This developer-only smoke packs the CLI, uses the exact current worktree
554
+ backend, exercises context discovery, saved filters, organization
555
+ member/invitation/role reads, and all three guest-link/sharing reads, and
556
+ creates short-lived fixtures only in the local Docker PostgreSQL database. It
557
+ never targets staging or production.
558
+
559
+ The command verifies generated API assets and rebuilds the distributable before
560
+ packing, so the exercised binary cannot lag behind the current source/spec.
561
+
562
+ Prerequisites:
563
+
564
+ - the worktree backend is listening on `127.0.0.1:4100` in `development` mode,
565
+ with local PostgreSQL and Redis configuration;
566
+ - local PostgreSQL is `neatlogs` on `127.0.0.1:5432` as role `neatlogs`;
567
+ - `psql`, `openssl`, `lsof`, `git`, Node, pnpm, and npm are available; and
568
+ - the backend listener PID and current 40-character worktree HEAD are supplied
569
+ explicitly. The smoke verifies the listener PID, backend working directory,
570
+ HEAD, and `/health` environment before fixture writes.
571
+
572
+ ```bash
573
+ cd /path/to/neatlogs-app-worktree/cli
574
+ RUN_NEATLOGS_CLI_LIVE_LOCAL=1 \
575
+ NEATLOGS_LIVE_LOCAL_BACKEND_PID="$(lsof -nP -iTCP:4100 -sTCP:LISTEN -t)" \
576
+ NEATLOGS_LIVE_LOCAL_EXPECTED_HEAD="$(git -C .. rev-parse HEAD)" \
577
+ pnpm smoke:context-discovery:live-local
578
+ ```
579
+
580
+ The HTTPS loopback proxy trusts only its generated loopback certificate through
581
+ the packed CLI child's `NODE_EXTRA_CA_CERTS`; TLS verification is never
582
+ disabled. External commands and network operations have deadlines. The smoke
583
+ waits for every successful request's audit row to become durable, deletes only
584
+ its exact request IDs/fixture IDs/marker, and requires zero residue. Signals
585
+ abort active work and unwind normal cleanup, including the packed-consumer and
586
+ certificate temporary directories.
587
+
588
+ ## Exit codes
589
+
590
+ | Code | Meaning |
591
+ | ---: | -------------------------------------------- |
592
+ | 0 | Success |
593
+ | 2 | Invalid arguments, configuration, or request |
594
+ | 3 | Authentication is missing or invalid |
595
+ | 4 | Authorization or scope denied |
596
+ | 5 | Resource not found |
597
+ | 6 | Conflict |
598
+ | 7 | Rate limited or temporarily unavailable |
599
+ | 8 | Network, TLS, timeout, or redirect failure |
600
+ | 9 | Partial bulk-operation success |
601
+ | 10 | Destructive confirmation required |
602
+ | 11 | Server or response-contract failure |