@mnemom/mnemom 0.16.2 → 0.16.3
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/README.md +1 -0
- package/dist/commands/agents.d.ts +14 -0
- package/dist/commands/agents.js +100 -2
- package/dist/commands/card.d.ts +43 -0
- package/dist/commands/card.js +153 -102
- package/dist/commands/logs.js +11 -1
- package/dist/commands/onboard.d.ts +59 -0
- package/dist/commands/onboard.js +395 -0
- package/dist/commands/org.d.ts +13 -0
- package/dist/commands/org.js +63 -2
- package/dist/commands/protection.d.ts +10 -0
- package/dist/commands/protection.js +109 -0
- package/dist/commands/status.js +5 -0
- package/dist/commands/try-me.js +9 -0
- package/dist/commands/usage.d.ts +35 -0
- package/dist/commands/usage.js +265 -0
- package/dist/commands/wrap.d.ts +25 -0
- package/dist/commands/wrap.js +331 -0
- package/dist/index.js +192 -6
- package/dist/lib/agent-config.d.ts +27 -0
- package/dist/lib/agent-config.js +86 -0
- package/dist/lib/api.d.ts +122 -1
- package/dist/lib/api.js +128 -183
- package/dist/lib/auth.js +21 -1
- package/dist/lib/cli-config.d.ts +33 -0
- package/dist/lib/cli-config.js +70 -0
- package/dist/lib/config.d.ts +10 -0
- package/dist/lib/config.js +39 -3
- package/dist/lib/keyed-identity.d.ts +35 -0
- package/dist/lib/keyed-identity.js +363 -0
- package/dist/lib/oauth.d.ts +26 -4
- package/dist/lib/oauth.js +98 -29
- package/dist/lib/protection-drift.d.ts +117 -0
- package/dist/lib/protection-drift.js +180 -0
- package/dist/lib/skills.js +25 -12
- package/dist/lib/version-gate.d.ts +37 -0
- package/dist/lib/version-gate.js +84 -0
- package/package.json +7 -7
package/dist/lib/api.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getApiUrl } from "./config.js";
|
|
3
3
|
import { forceRefreshAccessToken, resolveAuth } from "./auth.js";
|
|
4
|
+
import { mnemomFetch } from "./version-gate.js";
|
|
4
5
|
export const API_BASE = getApiUrl();
|
|
6
|
+
const NOT_AUTHENTICATED_MSG = "Not authenticated. Run `mnemom login` or set MNEMOM_API_KEY.";
|
|
5
7
|
/** Sanitize file-sourced data before use in outbound HTTP requests. */
|
|
6
8
|
function sanitizeForHttp(data) {
|
|
7
9
|
return String(data).trim();
|
|
@@ -112,7 +114,7 @@ export async function readApiError(response, fallback) {
|
|
|
112
114
|
}
|
|
113
115
|
async function fetchApi(endpoint) {
|
|
114
116
|
const url = validateUrl(`${API_BASE}${endpoint}`);
|
|
115
|
-
const response = await
|
|
117
|
+
const response = await mnemomFetch(url);
|
|
116
118
|
if (!response.ok) {
|
|
117
119
|
throw await readApiError(response, "API request failed");
|
|
118
120
|
}
|
|
@@ -120,18 +122,12 @@ async function fetchApi(endpoint) {
|
|
|
120
122
|
}
|
|
121
123
|
export async function postApi(endpoint, body, opts = {}) {
|
|
122
124
|
const url = validateUrl(`${API_BASE}${endpoint}`);
|
|
123
|
-
const cred = await resolveAuth();
|
|
124
125
|
const headers = {
|
|
125
126
|
"Content-Type": "application/json",
|
|
126
127
|
"Idempotency-Key": opts.idempotencyKey ?? newIdempotencyKey(),
|
|
128
|
+
...(await authHeaders()),
|
|
127
129
|
};
|
|
128
|
-
|
|
129
|
-
headers["Authorization"] = `Bearer ${cred.token}`;
|
|
130
|
-
}
|
|
131
|
-
else if (cred.type === "api-key") {
|
|
132
|
-
headers["X-Mnemom-Api-Key"] = cred.key;
|
|
133
|
-
}
|
|
134
|
-
const response = await fetch(url, {
|
|
130
|
+
const response = await mnemomFetch(url, {
|
|
135
131
|
// lgtm[js/file-data-url]
|
|
136
132
|
method: "POST",
|
|
137
133
|
headers,
|
|
@@ -193,13 +189,13 @@ async function authHeaders() {
|
|
|
193
189
|
* server-side reservation by design.
|
|
194
190
|
*/
|
|
195
191
|
async function fetchWithAuthRetry(url, buildInit) {
|
|
196
|
-
const first = await
|
|
192
|
+
const first = await mnemomFetch(url, await buildInit());
|
|
197
193
|
if (first.status !== 401)
|
|
198
194
|
return first;
|
|
199
195
|
const refreshed = await forceRefreshAccessToken();
|
|
200
196
|
if (!refreshed)
|
|
201
197
|
return first;
|
|
202
|
-
return
|
|
198
|
+
return mnemomFetch(url, await buildInit());
|
|
203
199
|
}
|
|
204
200
|
export async function getAgent(id) {
|
|
205
201
|
return fetchApi(`/v1/agents/${id}`);
|
|
@@ -212,17 +208,7 @@ export async function getAgent(id) {
|
|
|
212
208
|
* routing here avoids the false "Not authenticated" error (MNE-194 F10).
|
|
213
209
|
*/
|
|
214
210
|
export async function listAgents() {
|
|
215
|
-
const
|
|
216
|
-
const response = await fetchWithAuthRetry(url, async () => ({
|
|
217
|
-
headers: await authHeaders(),
|
|
218
|
-
}));
|
|
219
|
-
if (!response.ok) {
|
|
220
|
-
if (response.status === 401) {
|
|
221
|
-
throw new MnemomApiError(401, "Not authenticated. Run `mnemom login` or set MNEMOM_API_KEY.");
|
|
222
|
-
}
|
|
223
|
-
throw await readApiError(response, "Failed to list agents");
|
|
224
|
-
}
|
|
225
|
-
const data = (await response.json());
|
|
211
|
+
const data = await gFetch("/v1/agents?limit=100", {}, { fallback: "Failed to list agents" });
|
|
226
212
|
return data.agents ?? [];
|
|
227
213
|
}
|
|
228
214
|
/**
|
|
@@ -231,17 +217,7 @@ export async function listAgents() {
|
|
|
231
217
|
* first; multi-user orgs follow.
|
|
232
218
|
*/
|
|
233
219
|
export async function listMyOrgs() {
|
|
234
|
-
const
|
|
235
|
-
const response = await fetchWithAuthRetry(url, async () => ({
|
|
236
|
-
headers: await authHeaders(),
|
|
237
|
-
}));
|
|
238
|
-
if (!response.ok) {
|
|
239
|
-
if (response.status === 401) {
|
|
240
|
-
throw new MnemomApiError(401, "Not authenticated. Run `mnemom login` or set MNEMOM_API_KEY.");
|
|
241
|
-
}
|
|
242
|
-
throw await readApiError(response, "API request failed");
|
|
243
|
-
}
|
|
244
|
-
const data = (await response.json());
|
|
220
|
+
const data = await gFetch("/v1/orgs", {}, { fallback: "API request failed" });
|
|
245
221
|
return data.orgs ?? [];
|
|
246
222
|
}
|
|
247
223
|
/**
|
|
@@ -249,17 +225,7 @@ export async function listMyOrgs() {
|
|
|
249
225
|
* member of the org may read it; non-members get 403.
|
|
250
226
|
*/
|
|
251
227
|
export async function fetchOrgFleet(orgId) {
|
|
252
|
-
const
|
|
253
|
-
const response = await fetchWithAuthRetry(url, async () => ({
|
|
254
|
-
headers: { ...(await authHeaders()), Accept: "application/json" },
|
|
255
|
-
}));
|
|
256
|
-
if (!response.ok) {
|
|
257
|
-
if (response.status === 401) {
|
|
258
|
-
throw new MnemomApiError(401, "Not authenticated. Run `mnemom login` or set MNEMOM_API_KEY.");
|
|
259
|
-
}
|
|
260
|
-
throw await readApiError(response, "Failed to list org agents");
|
|
261
|
-
}
|
|
262
|
-
const data = (await response.json());
|
|
228
|
+
const data = await gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/agents`, {}, { fallback: "Failed to list org agents" });
|
|
263
229
|
return data.agents ?? [];
|
|
264
230
|
}
|
|
265
231
|
function fleetToRow(a, org) {
|
|
@@ -351,7 +317,7 @@ export async function claimAgent(agentId, body, opts = {}) {
|
|
|
351
317
|
}));
|
|
352
318
|
if (!response.ok) {
|
|
353
319
|
if (response.status === 401) {
|
|
354
|
-
throw new MnemomApiError(401,
|
|
320
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
355
321
|
}
|
|
356
322
|
throw await readApiError(response, "Failed to claim agent");
|
|
357
323
|
}
|
|
@@ -366,22 +332,48 @@ export async function claimAgent(agentId, body, opts = {}) {
|
|
|
366
332
|
};
|
|
367
333
|
}
|
|
368
334
|
/**
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
335
|
+
* POST /v1/agents/:id/move — role-based relocation between orgs.
|
|
336
|
+
*
|
|
337
|
+
* The role-based counterpart to claimAgent's re-claim: the server requires
|
|
338
|
+
* the caller to be an owner/admin of BOTH the agent's current org and
|
|
339
|
+
* `destOrgId` — no hash proof / agent key involved. Moving to the current
|
|
340
|
+
* org is an idempotent no-op (`moved: false`). An Idempotency-Key is minted
|
|
341
|
+
* (held across the 401-refresh retry) like every mutation.
|
|
372
342
|
*/
|
|
373
|
-
export async function
|
|
374
|
-
const url = validateUrl(`${API_BASE}/v1/
|
|
343
|
+
export async function moveAgent(agentId, destOrgId, opts = {}) {
|
|
344
|
+
const url = validateUrl(`${API_BASE}/v1/agents/${encodeURIComponent(agentId)}/move`);
|
|
345
|
+
const idempotencyKey = opts.idempotencyKey ?? newIdempotencyKey();
|
|
375
346
|
const response = await fetchWithAuthRetry(url, async () => ({
|
|
376
|
-
|
|
347
|
+
method: "POST",
|
|
348
|
+
headers: {
|
|
349
|
+
...(await authHeaders()),
|
|
350
|
+
"Content-Type": "application/json",
|
|
351
|
+
Accept: "application/json",
|
|
352
|
+
"Idempotency-Key": idempotencyKey,
|
|
353
|
+
},
|
|
354
|
+
body: JSON.stringify({ dest_org_id: sanitizeForHttp(destOrgId) }),
|
|
377
355
|
}));
|
|
378
356
|
if (!response.ok) {
|
|
379
357
|
if (response.status === 401) {
|
|
380
|
-
throw new MnemomApiError(401,
|
|
358
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
381
359
|
}
|
|
382
|
-
throw await readApiError(response, "
|
|
360
|
+
throw await readApiError(response, "Failed to move agent");
|
|
383
361
|
}
|
|
384
|
-
|
|
362
|
+
const data = (await response.json());
|
|
363
|
+
return {
|
|
364
|
+
moved: data.moved ?? true,
|
|
365
|
+
agent_id: data.agent_id ?? agentId,
|
|
366
|
+
from_org_id: data.from_org_id ?? null,
|
|
367
|
+
to_org_id: data.to_org_id ?? null,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* GET /v1/auth/me/personal-org — accessor for the user's personal org.
|
|
372
|
+
* Idempotent: lazily provisions for legacy accounts that pre-date the
|
|
373
|
+
* mnemom-api migration 161 backfill.
|
|
374
|
+
*/
|
|
375
|
+
export async function getMyPersonalOrg() {
|
|
376
|
+
return gFetch("/v1/auth/me/personal-org", {}, { fallback: "API request failed" });
|
|
385
377
|
}
|
|
386
378
|
/**
|
|
387
379
|
* GET /v1/orgs/:org_id/teams for every org the user is a member of,
|
|
@@ -421,7 +413,7 @@ export async function getTeam(teamId) {
|
|
|
421
413
|
}));
|
|
422
414
|
if (!response.ok) {
|
|
423
415
|
if (response.status === 401) {
|
|
424
|
-
throw new MnemomApiError(401,
|
|
416
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
425
417
|
}
|
|
426
418
|
if (response.status === 404) {
|
|
427
419
|
throw new MnemomApiError(404, `Team '${teamId}' not found or not accessible.`);
|
|
@@ -443,7 +435,7 @@ export async function getTeamTemplate(teamId, kind) {
|
|
|
443
435
|
}));
|
|
444
436
|
if (!response.ok) {
|
|
445
437
|
if (response.status === 401) {
|
|
446
|
-
throw new MnemomApiError(401,
|
|
438
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
447
439
|
}
|
|
448
440
|
if (response.status === 403) {
|
|
449
441
|
throw new MnemomApiError(403, `Forbidden: not a member of team '${teamId}'s org.`);
|
|
@@ -479,7 +471,7 @@ export async function putTeamTemplate(teamId, kind, yamlBody) {
|
|
|
479
471
|
}));
|
|
480
472
|
if (!response.ok) {
|
|
481
473
|
if (response.status === 401) {
|
|
482
|
-
throw new MnemomApiError(401,
|
|
474
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
483
475
|
}
|
|
484
476
|
if (response.status === 403) {
|
|
485
477
|
throw new MnemomApiError(403, `Forbidden: org admin or owner role required to write a team template.`);
|
|
@@ -512,7 +504,7 @@ export async function deleteTeamTemplate(teamId, kind) {
|
|
|
512
504
|
}));
|
|
513
505
|
if (!response.ok) {
|
|
514
506
|
if (response.status === 401) {
|
|
515
|
-
throw new MnemomApiError(401,
|
|
507
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
516
508
|
}
|
|
517
509
|
if (response.status === 403) {
|
|
518
510
|
throw new MnemomApiError(403, `Forbidden: org admin or owner role required to clear a team template.`);
|
|
@@ -570,7 +562,7 @@ export async function grantTeamAdmin(teamId, userId) {
|
|
|
570
562
|
}));
|
|
571
563
|
if (!response.ok) {
|
|
572
564
|
if (response.status === 401) {
|
|
573
|
-
throw new MnemomApiError(401,
|
|
565
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
574
566
|
}
|
|
575
567
|
if (response.status === 403) {
|
|
576
568
|
throw new MnemomApiError(403, `Forbidden: org owner or admin role required, and target user must be in the team's org.`);
|
|
@@ -602,7 +594,7 @@ export async function revokeTeamAdmin(teamId, userId) {
|
|
|
602
594
|
}));
|
|
603
595
|
if (!response.ok) {
|
|
604
596
|
if (response.status === 401) {
|
|
605
|
-
throw new MnemomApiError(401,
|
|
597
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
606
598
|
}
|
|
607
599
|
if (response.status === 403) {
|
|
608
600
|
throw new MnemomApiError(403, `Forbidden: revoke requires org owner/admin, or self-revoke with an active grant.`);
|
|
@@ -625,7 +617,7 @@ export async function listTeamAdmins(teamId) {
|
|
|
625
617
|
}));
|
|
626
618
|
if (!response.ok) {
|
|
627
619
|
if (response.status === 401) {
|
|
628
|
-
throw new MnemomApiError(401,
|
|
620
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
629
621
|
}
|
|
630
622
|
if (response.status === 403) {
|
|
631
623
|
throw new MnemomApiError(403, `Forbidden: requires membership in the team's org.`);
|
|
@@ -672,17 +664,10 @@ export async function getAgentByName(name) {
|
|
|
672
664
|
* path from PR #200 so a stale local expiresAt heals transparently.
|
|
673
665
|
*/
|
|
674
666
|
export async function getIntegrity(id) {
|
|
675
|
-
const
|
|
676
|
-
const response = await fetchWithAuthRetry(url, async () => ({
|
|
677
|
-
headers: await authHeaders(),
|
|
678
|
-
}));
|
|
679
|
-
if (!response.ok) {
|
|
680
|
-
throw await readApiError(response, "API request failed");
|
|
681
|
-
}
|
|
667
|
+
const body = await gFetch(`/v1/integrity/${id}`, {}, { fallback: "API request failed" });
|
|
682
668
|
// Accept both the canonical docs shape (object) and the RPC-wrapped shape
|
|
683
669
|
// (array of one row). The latter is what prod actually returns today on
|
|
684
670
|
// the RPC path; flagged for a follow-up API normalization.
|
|
685
|
-
const body = (await response.json());
|
|
686
671
|
const row = Array.isArray(body) ? (body[0] ?? emptyIntegrityRow(id)) : body;
|
|
687
672
|
// Fill in agent_id when the API didn't (RPC path) so the field is always
|
|
688
673
|
// populated for consumers regardless of which API path served the request.
|
|
@@ -714,14 +699,7 @@ function emptyIntegrityRow(agentId) {
|
|
|
714
699
|
* don't need a special case for it.
|
|
715
700
|
*/
|
|
716
701
|
export async function getTraces(id, limit = 10) {
|
|
717
|
-
const
|
|
718
|
-
const response = await fetchWithAuthRetry(url, async () => ({
|
|
719
|
-
headers: await authHeaders(),
|
|
720
|
-
}));
|
|
721
|
-
if (!response.ok) {
|
|
722
|
-
throw await readApiError(response, "API request failed");
|
|
723
|
-
}
|
|
724
|
-
const data = (await response.json());
|
|
702
|
+
const data = await gFetch(`/v1/traces?agent_id=${id}&limit=${limit}`, {}, { fallback: "API request failed" });
|
|
725
703
|
// Accept both the envelope shape (current API) and a bare array (defensive
|
|
726
704
|
// — staging is mid-deploy when a CLI smoke runs against an older API).
|
|
727
705
|
if (Array.isArray(data))
|
|
@@ -741,7 +719,7 @@ export async function getAlignmentCard(agentId, format = "yaml") {
|
|
|
741
719
|
// W1.2b). The legacy `/v1/agents/:id/alignment-card` only 308-redirects to
|
|
742
720
|
// this — first-party callers target canonical directly (no legacy shapes).
|
|
743
721
|
const url = validateUrl(`${API_BASE}/v1/alignment/agent/${encodeURIComponent(agentId)}`);
|
|
744
|
-
const response = await
|
|
722
|
+
const response = await mnemomFetch(url, {
|
|
745
723
|
headers: { Accept: accept, ...(await authHeaders()) },
|
|
746
724
|
});
|
|
747
725
|
if (!response.ok) {
|
|
@@ -801,7 +779,7 @@ export async function getProtectionCard(agentId, format = "yaml") {
|
|
|
801
779
|
// Canonical surface (ADR-062 / W1.2b); legacy `/v1/agents/:id/protection-card`
|
|
802
780
|
// only 308-redirects here. First-party callers use canonical directly.
|
|
803
781
|
const url = validateUrl(`${API_BASE}/v1/protection/agent/${encodeURIComponent(agentId)}`);
|
|
804
|
-
const response = await
|
|
782
|
+
const response = await mnemomFetch(url, {
|
|
805
783
|
headers: { Accept: accept, ...(await authHeaders()) },
|
|
806
784
|
});
|
|
807
785
|
if (!response.ok) {
|
|
@@ -869,7 +847,7 @@ export async function previewComposeAgentCard(agentId, kind, body, contentType =
|
|
|
869
847
|
}
|
|
870
848
|
// 401 → unauthenticated: signal the caller to fall back to offline validation.
|
|
871
849
|
if (response.status === 401) {
|
|
872
|
-
throw new MnemomApiError(401,
|
|
850
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
873
851
|
}
|
|
874
852
|
// 400 / 422 → the card is invalid: an expected validation outcome, not an
|
|
875
853
|
// exception. Surface the care-framed findings structurally.
|
|
@@ -917,7 +895,7 @@ export async function resolveAgentId(agentName) {
|
|
|
917
895
|
catch (err) {
|
|
918
896
|
const msg = err instanceof Error ? err.message : String(err);
|
|
919
897
|
if (msg.includes("Not authenticated")) {
|
|
920
|
-
console.error(`\
|
|
898
|
+
console.error(`\n${NOT_AUTHENTICATED_MSG}\n`);
|
|
921
899
|
process.exit(1);
|
|
922
900
|
}
|
|
923
901
|
}
|
|
@@ -945,7 +923,7 @@ export async function listPostures(opts) {
|
|
|
945
923
|
}));
|
|
946
924
|
if (!response.ok) {
|
|
947
925
|
if (response.status === 401) {
|
|
948
|
-
throw new MnemomApiError(401,
|
|
926
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
949
927
|
}
|
|
950
928
|
if (response.status === 403) {
|
|
951
929
|
throw new MnemomApiError(403, `Forbidden: not a member of org '${opts.orgId}'.`);
|
|
@@ -963,7 +941,7 @@ export async function getPosture(postureId) {
|
|
|
963
941
|
}));
|
|
964
942
|
if (!response.ok) {
|
|
965
943
|
if (response.status === 401) {
|
|
966
|
-
throw new MnemomApiError(401,
|
|
944
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
967
945
|
}
|
|
968
946
|
if (response.status === 404)
|
|
969
947
|
throw new MnemomApiError(404, `Posture '${postureId}' not found.`);
|
|
@@ -975,18 +953,7 @@ export async function getPosture(postureId) {
|
|
|
975
953
|
}
|
|
976
954
|
/** GET /v1/postures/:id/revisions — list revisions, newest first. */
|
|
977
955
|
export async function listPostureRevisions(postureId) {
|
|
978
|
-
const
|
|
979
|
-
const response = await fetchWithAuthRetry(url, async () => ({
|
|
980
|
-
headers: { ...(await authHeaders()), Accept: "application/json" },
|
|
981
|
-
}));
|
|
982
|
-
if (!response.ok) {
|
|
983
|
-
if (response.status === 401)
|
|
984
|
-
throw new MnemomApiError(401, "Not authenticated.");
|
|
985
|
-
if (response.status === 404)
|
|
986
|
-
throw new MnemomApiError(404, `Posture '${postureId}' not found.`);
|
|
987
|
-
throw await readApiError(response, "API request failed");
|
|
988
|
-
}
|
|
989
|
-
const data = (await response.json());
|
|
956
|
+
const data = await gFetch(`/v1/postures/${postureId}/revisions`, {}, { notFoundLabel: `Posture '${postureId}'`, fallback: "API request failed" });
|
|
990
957
|
return data.revisions ?? [];
|
|
991
958
|
}
|
|
992
959
|
/** GET /v1/postures/:id/diff?from=N&to=M — structural diff. */
|
|
@@ -997,7 +964,7 @@ export async function diffPostureRevisions(postureId, fromNo, toNo) {
|
|
|
997
964
|
}));
|
|
998
965
|
if (!response.ok) {
|
|
999
966
|
if (response.status === 401)
|
|
1000
|
-
throw new MnemomApiError(401,
|
|
967
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1001
968
|
if (response.status === 404) {
|
|
1002
969
|
const errBody = await parseApiErrorBody(response);
|
|
1003
970
|
throw new MnemomApiError(404, errBody.message || `Posture or revision not found.`, {
|
|
@@ -1025,7 +992,7 @@ export async function createPosture(input) {
|
|
|
1025
992
|
}));
|
|
1026
993
|
if (!response.ok) {
|
|
1027
994
|
if (response.status === 401)
|
|
1028
|
-
throw new MnemomApiError(401,
|
|
995
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1029
996
|
if (response.status === 403) {
|
|
1030
997
|
throw new MnemomApiError(403, `Forbidden: org owner/admin role required (or scope=platform reserved for migrations).`);
|
|
1031
998
|
}
|
|
@@ -1052,7 +1019,7 @@ export async function updatePosture(postureId, input) {
|
|
|
1052
1019
|
}));
|
|
1053
1020
|
if (!response.ok) {
|
|
1054
1021
|
if (response.status === 401)
|
|
1055
|
-
throw new MnemomApiError(401,
|
|
1022
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1056
1023
|
if (response.status === 403) {
|
|
1057
1024
|
throw new MnemomApiError(403, `Forbidden: org owner/admin required, or this is a Mnemom-shipped platform default (immutable — clone first).`);
|
|
1058
1025
|
}
|
|
@@ -1079,7 +1046,7 @@ export async function clonePosture(postureId, input) {
|
|
|
1079
1046
|
}));
|
|
1080
1047
|
if (!response.ok) {
|
|
1081
1048
|
if (response.status === 401)
|
|
1082
|
-
throw new MnemomApiError(401,
|
|
1049
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1083
1050
|
if (response.status === 403)
|
|
1084
1051
|
throw new MnemomApiError(403, `Forbidden: org owner/admin required on target org.`);
|
|
1085
1052
|
if (response.status === 404)
|
|
@@ -1103,7 +1070,7 @@ export async function deletePosture(postureId) {
|
|
|
1103
1070
|
}));
|
|
1104
1071
|
if (!response.ok) {
|
|
1105
1072
|
if (response.status === 401)
|
|
1106
|
-
throw new MnemomApiError(401,
|
|
1073
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1107
1074
|
if (response.status === 403)
|
|
1108
1075
|
throw new MnemomApiError(403, `Forbidden: platform defaults are immutable.`);
|
|
1109
1076
|
if (response.status === 404)
|
|
@@ -1129,7 +1096,7 @@ export async function assignPosture(postureId, teamId, pinRevisionNo) {
|
|
|
1129
1096
|
}));
|
|
1130
1097
|
if (!response.ok) {
|
|
1131
1098
|
if (response.status === 401)
|
|
1132
|
-
throw new MnemomApiError(401,
|
|
1099
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1133
1100
|
if (response.status === 403)
|
|
1134
1101
|
throw new MnemomApiError(403, `Forbidden: org owner/admin required on team's org.`);
|
|
1135
1102
|
if (response.status === 404) {
|
|
@@ -1153,7 +1120,7 @@ export async function unassignPosture(postureId, teamId) {
|
|
|
1153
1120
|
}));
|
|
1154
1121
|
if (!response.ok) {
|
|
1155
1122
|
if (response.status === 401)
|
|
1156
|
-
throw new MnemomApiError(401,
|
|
1123
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1157
1124
|
if (response.status === 403)
|
|
1158
1125
|
throw new MnemomApiError(403, `Forbidden: org owner/admin required.`);
|
|
1159
1126
|
if (response.status === 404) {
|
|
@@ -1176,7 +1143,7 @@ export async function previewComposePosture(postureId, teamId) {
|
|
|
1176
1143
|
}));
|
|
1177
1144
|
if (!response.ok) {
|
|
1178
1145
|
if (response.status === 401)
|
|
1179
|
-
throw new MnemomApiError(401,
|
|
1146
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1180
1147
|
if (response.status === 404) {
|
|
1181
1148
|
const errBody = await parseApiErrorBody(response);
|
|
1182
1149
|
throw new MnemomApiError(404, errBody.message || `Posture or team not found.`, {
|
|
@@ -1202,21 +1169,7 @@ export async function listSidebandAdvisoriesForAgent(agentId, opts = {}) {
|
|
|
1202
1169
|
if (opts.since)
|
|
1203
1170
|
params.set("since", opts.since);
|
|
1204
1171
|
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1205
|
-
|
|
1206
|
-
const response = await fetchWithAuthRetry(url, async () => ({
|
|
1207
|
-
method: "GET",
|
|
1208
|
-
headers: { ...(await authHeaders()), Accept: "application/json" },
|
|
1209
|
-
}));
|
|
1210
|
-
if (!response.ok) {
|
|
1211
|
-
if (response.status === 401)
|
|
1212
|
-
throw new MnemomApiError(401, "Not authenticated.");
|
|
1213
|
-
if (response.status === 403)
|
|
1214
|
-
throw new MnemomApiError(403, "Permission denied (need org membership).");
|
|
1215
|
-
if (response.status === 404)
|
|
1216
|
-
throw new MnemomApiError(404, `Agent '${agentId}' not found.`);
|
|
1217
|
-
throw await readApiError(response, "List advisories failed");
|
|
1218
|
-
}
|
|
1219
|
-
return (await response.json());
|
|
1172
|
+
return gFetch(`/v1/agents/${encodeURIComponent(agentId)}/sideband-advisories${qs}`, {}, { notFoundLabel: `Agent '${agentId}'`, fallback: "List advisories failed" });
|
|
1220
1173
|
}
|
|
1221
1174
|
/**
|
|
1222
1175
|
* GET /v1/teams/:id/sideband-advisories
|
|
@@ -1231,21 +1184,7 @@ export async function listSidebandAdvisoriesForTeam(teamId, opts = {}) {
|
|
|
1231
1184
|
if (opts.since)
|
|
1232
1185
|
params.set("since", opts.since);
|
|
1233
1186
|
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1234
|
-
|
|
1235
|
-
const response = await fetchWithAuthRetry(url, async () => ({
|
|
1236
|
-
method: "GET",
|
|
1237
|
-
headers: { ...(await authHeaders()), Accept: "application/json" },
|
|
1238
|
-
}));
|
|
1239
|
-
if (!response.ok) {
|
|
1240
|
-
if (response.status === 401)
|
|
1241
|
-
throw new MnemomApiError(401, "Not authenticated.");
|
|
1242
|
-
if (response.status === 403)
|
|
1243
|
-
throw new MnemomApiError(403, "Permission denied (need org membership).");
|
|
1244
|
-
if (response.status === 404)
|
|
1245
|
-
throw new MnemomApiError(404, `Team '${teamId}' not found.`);
|
|
1246
|
-
throw await readApiError(response, "List advisories failed");
|
|
1247
|
-
}
|
|
1248
|
-
return (await response.json());
|
|
1187
|
+
return gFetch(`/v1/teams/${encodeURIComponent(teamId)}/sideband-advisories${qs}`, {}, { notFoundLabel: `Team '${teamId}'`, fallback: "List advisories failed" });
|
|
1249
1188
|
}
|
|
1250
1189
|
/**
|
|
1251
1190
|
* GET /v1/teams/:id/sideband-coverage
|
|
@@ -1255,21 +1194,7 @@ export async function listSidebandAdvisoriesForTeam(teamId, opts = {}) {
|
|
|
1255
1194
|
* raw rows.
|
|
1256
1195
|
*/
|
|
1257
1196
|
export async function getTeamSidebandCoverage(teamId) {
|
|
1258
|
-
|
|
1259
|
-
const response = await fetchWithAuthRetry(url, async () => ({
|
|
1260
|
-
method: "GET",
|
|
1261
|
-
headers: { ...(await authHeaders()), Accept: "application/json" },
|
|
1262
|
-
}));
|
|
1263
|
-
if (!response.ok) {
|
|
1264
|
-
if (response.status === 401)
|
|
1265
|
-
throw new MnemomApiError(401, "Not authenticated.");
|
|
1266
|
-
if (response.status === 403)
|
|
1267
|
-
throw new MnemomApiError(403, "Permission denied (need org membership).");
|
|
1268
|
-
if (response.status === 404)
|
|
1269
|
-
throw new MnemomApiError(404, `Team '${teamId}' not found.`);
|
|
1270
|
-
throw await readApiError(response, "Coverage fetch failed");
|
|
1271
|
-
}
|
|
1272
|
-
return (await response.json());
|
|
1197
|
+
return gFetch(`/v1/teams/${encodeURIComponent(teamId)}/sideband-coverage`, {}, { notFoundLabel: `Team '${teamId}'`, fallback: "Coverage fetch failed" });
|
|
1273
1198
|
}
|
|
1274
1199
|
/**
|
|
1275
1200
|
* GET /v1/admin/safe-house/harness-state — fetch the most recent
|
|
@@ -1284,7 +1209,7 @@ export async function getSafeHouseHarnessState() {
|
|
|
1284
1209
|
}));
|
|
1285
1210
|
if (!response.ok) {
|
|
1286
1211
|
if (response.status === 401)
|
|
1287
|
-
throw new MnemomApiError(401,
|
|
1212
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1288
1213
|
if (response.status === 403)
|
|
1289
1214
|
throw new MnemomApiError(403, "Permission denied — `mnemom validate safe-house` is mnemom-staff only.");
|
|
1290
1215
|
throw await readApiError(response, "Harness state fetch failed");
|
|
@@ -1310,7 +1235,7 @@ function buildSignalListQuery(opts = {}) {
|
|
|
1310
1235
|
const qs = params.toString();
|
|
1311
1236
|
return qs ? `?${qs}` : "";
|
|
1312
1237
|
}
|
|
1313
|
-
async function gFetch(path, init = {},
|
|
1238
|
+
async function gFetch(path, init = {}, opts = {}) {
|
|
1314
1239
|
const url = validateUrl(`${API_BASE}${path}`);
|
|
1315
1240
|
const response = await fetchWithAuthRetry(url, async () => ({
|
|
1316
1241
|
method: init.method ?? "GET",
|
|
@@ -1324,56 +1249,56 @@ async function gFetch(path, init = {}, notFoundLabel) {
|
|
|
1324
1249
|
}));
|
|
1325
1250
|
if (!response.ok) {
|
|
1326
1251
|
if (response.status === 401)
|
|
1327
|
-
throw new MnemomApiError(401,
|
|
1252
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1328
1253
|
if (response.status === 403)
|
|
1329
1254
|
throw new MnemomApiError(403, "Permission denied (need org admin / membership).");
|
|
1330
|
-
if (response.status === 404 && notFoundLabel)
|
|
1331
|
-
throw new MnemomApiError(404, `${notFoundLabel} not found.`);
|
|
1332
|
-
throw await readApiError(response, "Request failed");
|
|
1255
|
+
if (response.status === 404 && opts.notFoundLabel)
|
|
1256
|
+
throw new MnemomApiError(404, `${opts.notFoundLabel} not found.`);
|
|
1257
|
+
throw await readApiError(response, opts.fallback ?? "Request failed");
|
|
1333
1258
|
}
|
|
1334
1259
|
return (await response.json());
|
|
1335
1260
|
}
|
|
1336
1261
|
export async function listGovernanceSignalsForOrg(orgId, opts = {}) {
|
|
1337
|
-
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/signals${buildSignalListQuery(opts)}`, {}, "Org");
|
|
1262
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/signals${buildSignalListQuery(opts)}`, {}, { notFoundLabel: "Org" });
|
|
1338
1263
|
}
|
|
1339
1264
|
export async function listGovernanceSignalsForTeam(teamId, opts = {}) {
|
|
1340
|
-
return gFetch(`/v1/teams/${encodeURIComponent(teamId)}/governance/signals${buildSignalListQuery(opts)}`, {}, "Team");
|
|
1265
|
+
return gFetch(`/v1/teams/${encodeURIComponent(teamId)}/governance/signals${buildSignalListQuery(opts)}`, {}, { notFoundLabel: "Team" });
|
|
1341
1266
|
}
|
|
1342
1267
|
export async function listGovernanceSignalsForAgent(agentId, opts = {}) {
|
|
1343
|
-
return gFetch(`/v1/agents/${encodeURIComponent(agentId)}/governance/signals${buildSignalListQuery(opts)}`, {}, "Agent");
|
|
1268
|
+
return gFetch(`/v1/agents/${encodeURIComponent(agentId)}/governance/signals${buildSignalListQuery(opts)}`, {}, { notFoundLabel: "Agent" });
|
|
1344
1269
|
}
|
|
1345
1270
|
export async function getGovernanceSignal(id) {
|
|
1346
|
-
return gFetch(`/v1/governance/signals/${encodeURIComponent(id)}`, {}, "Signal");
|
|
1271
|
+
return gFetch(`/v1/governance/signals/${encodeURIComponent(id)}`, {}, { notFoundLabel: "Signal" });
|
|
1347
1272
|
}
|
|
1348
1273
|
export async function acknowledgeGovernanceSignal(id, body = {}) {
|
|
1349
|
-
return gFetch(`/v1/governance/signals/${encodeURIComponent(id)}/acknowledge`, { method: "POST", body: JSON.stringify(body) }, "Signal");
|
|
1274
|
+
return gFetch(`/v1/governance/signals/${encodeURIComponent(id)}/acknowledge`, { method: "POST", body: JSON.stringify(body) }, { notFoundLabel: "Signal" });
|
|
1350
1275
|
}
|
|
1351
1276
|
export async function resolveGovernanceSignal(id, body) {
|
|
1352
|
-
return gFetch(`/v1/governance/signals/${encodeURIComponent(id)}/resolve`, { method: "POST", body: JSON.stringify(body) }, "Signal");
|
|
1277
|
+
return gFetch(`/v1/governance/signals/${encodeURIComponent(id)}/resolve`, { method: "POST", body: JSON.stringify(body) }, { notFoundLabel: "Signal" });
|
|
1353
1278
|
}
|
|
1354
1279
|
export async function dismissGovernanceSignal(id, body = {}) {
|
|
1355
|
-
return gFetch(`/v1/governance/signals/${encodeURIComponent(id)}/dismiss`, { method: "POST", body: JSON.stringify(body) }, "Signal");
|
|
1280
|
+
return gFetch(`/v1/governance/signals/${encodeURIComponent(id)}/dismiss`, { method: "POST", body: JSON.stringify(body) }, { notFoundLabel: "Signal" });
|
|
1356
1281
|
}
|
|
1357
1282
|
export async function listGovernanceDestinations(orgId) {
|
|
1358
|
-
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/notification-destinations`, {}, "Org");
|
|
1283
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/notification-destinations`, {}, { notFoundLabel: "Org" });
|
|
1359
1284
|
}
|
|
1360
1285
|
export async function createGovernanceDestination(orgId, body) {
|
|
1361
|
-
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/notification-destinations`, { method: "POST", body: JSON.stringify(body) }, "Org");
|
|
1286
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/notification-destinations`, { method: "POST", body: JSON.stringify(body) }, { notFoundLabel: "Org" });
|
|
1362
1287
|
}
|
|
1363
1288
|
export async function deleteGovernanceDestination(orgId, destinationId) {
|
|
1364
|
-
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/notification-destinations/${encodeURIComponent(destinationId)}`, { method: "DELETE" }, "Destination");
|
|
1289
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/notification-destinations/${encodeURIComponent(destinationId)}`, { method: "DELETE" }, { notFoundLabel: "Destination" });
|
|
1365
1290
|
}
|
|
1366
1291
|
export async function testGovernanceDestination(orgId, destinationId) {
|
|
1367
|
-
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/notification-destinations/${encodeURIComponent(destinationId)}/test`, { method: "POST", body: JSON.stringify({}) }, "Destination");
|
|
1292
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/notification-destinations/${encodeURIComponent(destinationId)}/test`, { method: "POST", body: JSON.stringify({}) }, { notFoundLabel: "Destination" });
|
|
1368
1293
|
}
|
|
1369
1294
|
export async function listGovernanceRules(orgId) {
|
|
1370
|
-
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/escalation-rules`, {}, "Org");
|
|
1295
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/escalation-rules`, {}, { notFoundLabel: "Org" });
|
|
1371
1296
|
}
|
|
1372
1297
|
export async function createGovernanceRule(orgId, body) {
|
|
1373
|
-
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/escalation-rules`, { method: "POST", body: JSON.stringify(body) }, "Org");
|
|
1298
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/escalation-rules`, { method: "POST", body: JSON.stringify(body) }, { notFoundLabel: "Org" });
|
|
1374
1299
|
}
|
|
1375
1300
|
export async function deleteGovernanceRule(orgId, ruleId) {
|
|
1376
|
-
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/escalation-rules/${encodeURIComponent(ruleId)}`, { method: "DELETE" }, "Rule");
|
|
1301
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/escalation-rules/${encodeURIComponent(ruleId)}`, { method: "DELETE" }, { notFoundLabel: "Rule" });
|
|
1377
1302
|
}
|
|
1378
1303
|
// ─── api-keys (ADR-049) ─────────────────────────────────────────────────
|
|
1379
1304
|
/**
|
|
@@ -1408,17 +1333,7 @@ export function isLegacyScopeSet(scopes) {
|
|
|
1408
1333
|
* GET /v1/api-keys — list the caller's active personal API keys.
|
|
1409
1334
|
*/
|
|
1410
1335
|
export async function listApiKeys() {
|
|
1411
|
-
const
|
|
1412
|
-
const response = await fetchWithAuthRetry(url, async () => ({
|
|
1413
|
-
headers: await authHeaders(),
|
|
1414
|
-
}));
|
|
1415
|
-
if (!response.ok) {
|
|
1416
|
-
if (response.status === 401) {
|
|
1417
|
-
throw new MnemomApiError(401, "Not authenticated. Run `mnemom login` or set MNEMOM_API_KEY.");
|
|
1418
|
-
}
|
|
1419
|
-
throw await readApiError(response, "Failed to list api keys");
|
|
1420
|
-
}
|
|
1421
|
-
const data = (await response.json());
|
|
1336
|
+
const data = await gFetch("/v1/api-keys", {}, { fallback: "Failed to list api keys" });
|
|
1422
1337
|
return data.keys ?? [];
|
|
1423
1338
|
}
|
|
1424
1339
|
/**
|
|
@@ -1437,7 +1352,7 @@ export async function createApiKey(name, scopes) {
|
|
|
1437
1352
|
}));
|
|
1438
1353
|
if (!response.ok) {
|
|
1439
1354
|
if (response.status === 401) {
|
|
1440
|
-
throw new MnemomApiError(401,
|
|
1355
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1441
1356
|
}
|
|
1442
1357
|
const parsed = await parseApiErrorBody(response);
|
|
1443
1358
|
const detail = parsed.message || `HTTP ${response.status}`;
|
|
@@ -1465,7 +1380,7 @@ export async function rotateApiKey(keyId) {
|
|
|
1465
1380
|
}));
|
|
1466
1381
|
if (!response.ok) {
|
|
1467
1382
|
if (response.status === 401) {
|
|
1468
|
-
throw new MnemomApiError(401,
|
|
1383
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1469
1384
|
}
|
|
1470
1385
|
if (response.status === 404)
|
|
1471
1386
|
throw new MnemomApiError(404, `Key not found: ${keyId}`);
|
|
@@ -1486,7 +1401,7 @@ export async function revokeApiKey(keyId) {
|
|
|
1486
1401
|
}));
|
|
1487
1402
|
if (!response.ok) {
|
|
1488
1403
|
if (response.status === 401) {
|
|
1489
|
-
throw new MnemomApiError(401,
|
|
1404
|
+
throw new MnemomApiError(401, NOT_AUTHENTICATED_MSG);
|
|
1490
1405
|
}
|
|
1491
1406
|
if (response.status === 404)
|
|
1492
1407
|
throw new MnemomApiError(404, `Key not found: ${keyId}`);
|
|
@@ -1514,7 +1429,7 @@ export async function revokeApiKey(keyId) {
|
|
|
1514
1429
|
*/
|
|
1515
1430
|
export async function validateLicense(jwt, instanceId, instanceMetadata = {}) {
|
|
1516
1431
|
const url = validateUrl(`${API_BASE}/v1/license/validate`);
|
|
1517
|
-
const response = await
|
|
1432
|
+
const response = await mnemomFetch(url, {
|
|
1518
1433
|
method: "POST",
|
|
1519
1434
|
headers: { "Content-Type": "application/json" },
|
|
1520
1435
|
body: JSON.stringify({
|
|
@@ -1531,3 +1446,33 @@ export async function validateLicense(jwt, instanceId, instanceMetadata = {}) {
|
|
|
1531
1446
|
export async function reportRecipeFnFp(recipeId, input) {
|
|
1532
1447
|
return postApi(`/v1/recipes/${encodeURIComponent(recipeId)}/report`, input);
|
|
1533
1448
|
}
|
|
1449
|
+
/** The only windows the endpoint accepts (`days` enum in `openapi.json`). */
|
|
1450
|
+
export const USAGE_ALLOWED_DAYS = [7, 30, 90];
|
|
1451
|
+
/**
|
|
1452
|
+
* GET /v1/orgs/:org_id/usage/by-person — consumption by person/provider/model.
|
|
1453
|
+
*
|
|
1454
|
+
* Role-gated to owner/admin/auditor; member and viewer get a real 403.
|
|
1455
|
+
*
|
|
1456
|
+
* Gated by USAGE_ATTRIBUTION_API_ENABLED, which returns **404, not 403**, when
|
|
1457
|
+
* off — deliberately, so a disabled endpoint is indistinguishable from one that
|
|
1458
|
+
* does not exist. That makes 404 ambiguous by design (flag off OR unknown org
|
|
1459
|
+
* OR no access), so no `notFoundLabel` is passed here: a caller must not tell
|
|
1460
|
+
* the user "org not found" on what is most often just the flag being off.
|
|
1461
|
+
*/
|
|
1462
|
+
export async function getOrgUsage(orgId, opts = {}) {
|
|
1463
|
+
const params = new URLSearchParams();
|
|
1464
|
+
if (opts.days !== undefined)
|
|
1465
|
+
params.set("days", String(opts.days));
|
|
1466
|
+
if (opts.personId)
|
|
1467
|
+
params.set("person_id", opts.personId);
|
|
1468
|
+
if (opts.provider)
|
|
1469
|
+
params.set("provider", opts.provider);
|
|
1470
|
+
if (opts.model)
|
|
1471
|
+
params.set("model", opts.model);
|
|
1472
|
+
if (opts.limit !== undefined)
|
|
1473
|
+
params.set("limit", String(opts.limit));
|
|
1474
|
+
if (opts.cursor)
|
|
1475
|
+
params.set("cursor", opts.cursor);
|
|
1476
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
1477
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/usage/by-person${qs}`, {}, { fallback: "Failed to fetch usage" });
|
|
1478
|
+
}
|