@mnemom/mnemom 0.10.0 → 0.12.0
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 +25 -25
- package/dist/commands/agents.js +2 -7
- package/dist/commands/api-key.d.ts +37 -0
- package/dist/commands/api-key.js +179 -0
- package/dist/commands/auth.js +1 -2
- package/dist/commands/card.js +33 -22
- package/dist/commands/governance.d.ts +85 -0
- package/dist/commands/governance.js +329 -0
- package/dist/commands/integrity.js +1 -2
- package/dist/commands/license.js +13 -4
- package/dist/commands/listen.d.ts +29 -0
- package/dist/commands/listen.js +201 -0
- package/dist/commands/logs.js +1 -3
- package/dist/commands/org.js +4 -6
- package/dist/commands/protection.js +70 -23
- package/dist/commands/recipes.d.ts +34 -0
- package/dist/commands/recipes.js +80 -0
- package/dist/commands/status.js +6 -169
- package/dist/commands/team.js +5 -10
- package/dist/commands/validate.js +7 -4
- package/dist/commands/webhooks.d.ts +61 -0
- package/dist/commands/webhooks.js +328 -0
- package/dist/index.js +489 -1
- package/dist/lib/api.d.ts +199 -2
- package/dist/lib/api.js +207 -0
- package/dist/lib/auth.js +1 -5
- package/dist/lib/format.d.ts +4 -0
- package/dist/lib/format.js +6 -0
- package/dist/lib/listen-stream.d.ts +40 -0
- package/dist/lib/listen-stream.js +101 -0
- package/dist/lib/webhooks-api.d.ts +80 -0
- package/dist/lib/webhooks-api.js +172 -0
- package/dist/smoltbot-shim.js +3 -3
- package/package.json +1 -1
- package/dist/lib/model-cache.d.ts +0 -16
- package/dist/lib/model-cache.js +0 -137
- package/dist/lib/models.d.ts +0 -41
- package/dist/lib/models.js +0 -357
- package/dist/lib/openclaw.d.ts +0 -221
- package/dist/lib/openclaw.js +0 -474
package/dist/lib/api.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface Agent {
|
|
|
18
18
|
created_at: string;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Per
|
|
21
|
+
* Per api.mnemom.ai/openapi.json#components/schemas/IntegrityScore:
|
|
22
22
|
*
|
|
23
23
|
* { agent_id, total_traces, verified_traces, violation_count, integrity_score }
|
|
24
24
|
*
|
|
@@ -344,7 +344,7 @@ export declare function testPolicyHistorical(agentId: string, policyJson: Record
|
|
|
344
344
|
agent_id: string;
|
|
345
345
|
policy_name: string;
|
|
346
346
|
total_traces: number;
|
|
347
|
-
results:
|
|
347
|
+
results: unknown[];
|
|
348
348
|
summary: {
|
|
349
349
|
pass: number;
|
|
350
350
|
warn: number;
|
|
@@ -645,4 +645,201 @@ export declare function getSafeHouseHarnessState(): Promise<{
|
|
|
645
645
|
full: HarnessRunSummary | null;
|
|
646
646
|
fast: HarnessRunSummary | null;
|
|
647
647
|
}>;
|
|
648
|
+
export type GovernanceSignalScope = "platform" | "org" | "team" | "agent";
|
|
649
|
+
export type GovernanceSignalSource = "sideband.drift" | "sideband.coherence" | "sideband.fault_line" | "sideband.fleet";
|
|
650
|
+
export type GovernanceSignalSeverity = "info" | "warn" | "high" | "critical";
|
|
651
|
+
export type GovernanceSignalStatus = "open" | "acknowledged" | "resolved" | "dismissed" | "expired";
|
|
652
|
+
export type GovernanceResolutionStatus = "action_taken" | "wont_fix" | "duplicate" | "false_positive" | "self_resolved";
|
|
653
|
+
export type GovernanceNotificationChannel = "webhook" | "slack" | "email" | "pagerduty";
|
|
654
|
+
export interface GovernanceSignal {
|
|
655
|
+
id: string;
|
|
656
|
+
scope: GovernanceSignalScope;
|
|
657
|
+
scope_id: string;
|
|
658
|
+
source: GovernanceSignalSource;
|
|
659
|
+
pattern_type: string;
|
|
660
|
+
severity: GovernanceSignalSeverity;
|
|
661
|
+
detected_at: string;
|
|
662
|
+
detected_by: string;
|
|
663
|
+
org_id: string;
|
|
664
|
+
team_id: string | null;
|
|
665
|
+
agent_ids: string[];
|
|
666
|
+
detail: Record<string, unknown>;
|
|
667
|
+
source_ref: Record<string, unknown>;
|
|
668
|
+
status: GovernanceSignalStatus;
|
|
669
|
+
acknowledged_by: string | null;
|
|
670
|
+
acknowledged_at: string | null;
|
|
671
|
+
acknowledged_actor_role: string | null;
|
|
672
|
+
resolution_status: GovernanceResolutionStatus | null;
|
|
673
|
+
action_taken: string | null;
|
|
674
|
+
resolved_by: string | null;
|
|
675
|
+
resolved_at: string | null;
|
|
676
|
+
expires_at: string | null;
|
|
677
|
+
notification_state: Record<string, unknown>;
|
|
678
|
+
created_at: string;
|
|
679
|
+
updated_at: string;
|
|
680
|
+
}
|
|
681
|
+
export interface GovernanceDestination {
|
|
682
|
+
id: string;
|
|
683
|
+
org_id: string;
|
|
684
|
+
channel: GovernanceNotificationChannel;
|
|
685
|
+
config: Record<string, unknown>;
|
|
686
|
+
filter: Record<string, unknown>;
|
|
687
|
+
enabled: boolean;
|
|
688
|
+
display_name: string | null;
|
|
689
|
+
last_tested_at: string | null;
|
|
690
|
+
last_test_status: "ok" | "failed" | null;
|
|
691
|
+
last_test_error: string | null;
|
|
692
|
+
created_at: string;
|
|
693
|
+
updated_at: string;
|
|
694
|
+
}
|
|
695
|
+
export interface GovernanceRule {
|
|
696
|
+
id: string;
|
|
697
|
+
org_id: string;
|
|
698
|
+
name: string;
|
|
699
|
+
predicate: Record<string, unknown>;
|
|
700
|
+
destination_ids: string[];
|
|
701
|
+
enabled: boolean;
|
|
702
|
+
last_fired_at: string | null;
|
|
703
|
+
fire_count: number;
|
|
704
|
+
created_at: string;
|
|
705
|
+
updated_at: string;
|
|
706
|
+
}
|
|
707
|
+
interface SignalListFilters {
|
|
708
|
+
source?: string;
|
|
709
|
+
severity?: string;
|
|
710
|
+
status?: string;
|
|
711
|
+
scope?: string;
|
|
712
|
+
pattern_type?: string;
|
|
713
|
+
since?: string;
|
|
714
|
+
limit?: number;
|
|
715
|
+
}
|
|
716
|
+
export declare function listGovernanceSignalsForOrg(orgId: string, opts?: SignalListFilters): Promise<{
|
|
717
|
+
org_id: string;
|
|
718
|
+
signals: GovernanceSignal[];
|
|
719
|
+
}>;
|
|
720
|
+
export declare function listGovernanceSignalsForTeam(teamId: string, opts?: SignalListFilters): Promise<{
|
|
721
|
+
team_id: string;
|
|
722
|
+
signals: GovernanceSignal[];
|
|
723
|
+
}>;
|
|
724
|
+
export declare function listGovernanceSignalsForAgent(agentId: string, opts?: SignalListFilters): Promise<{
|
|
725
|
+
agent_id: string;
|
|
726
|
+
signals: GovernanceSignal[];
|
|
727
|
+
}>;
|
|
728
|
+
export declare function getGovernanceSignal(id: string): Promise<GovernanceSignal>;
|
|
729
|
+
export declare function acknowledgeGovernanceSignal(id: string, body?: {
|
|
730
|
+
action_taken?: string;
|
|
731
|
+
}): Promise<GovernanceSignal>;
|
|
732
|
+
export declare function resolveGovernanceSignal(id: string, body: {
|
|
733
|
+
resolution_status: GovernanceResolutionStatus;
|
|
734
|
+
action_taken?: string;
|
|
735
|
+
}): Promise<GovernanceSignal>;
|
|
736
|
+
export declare function dismissGovernanceSignal(id: string, body?: {
|
|
737
|
+
reason?: string;
|
|
738
|
+
}): Promise<GovernanceSignal>;
|
|
739
|
+
export declare function listGovernanceDestinations(orgId: string): Promise<{
|
|
740
|
+
org_id: string;
|
|
741
|
+
destinations: GovernanceDestination[];
|
|
742
|
+
}>;
|
|
743
|
+
export declare function createGovernanceDestination(orgId: string, body: {
|
|
744
|
+
channel: GovernanceNotificationChannel;
|
|
745
|
+
config: Record<string, unknown>;
|
|
746
|
+
filter?: Record<string, unknown>;
|
|
747
|
+
display_name?: string;
|
|
748
|
+
enabled?: boolean;
|
|
749
|
+
}): Promise<GovernanceDestination>;
|
|
750
|
+
export declare function deleteGovernanceDestination(orgId: string, destinationId: string): Promise<{
|
|
751
|
+
ok: true;
|
|
752
|
+
}>;
|
|
753
|
+
export declare function testGovernanceDestination(orgId: string, destinationId: string): Promise<{
|
|
754
|
+
destination_id: string;
|
|
755
|
+
channel: GovernanceNotificationChannel;
|
|
756
|
+
result: {
|
|
757
|
+
ok: boolean;
|
|
758
|
+
attempts: number;
|
|
759
|
+
delivered_at?: string;
|
|
760
|
+
last_error?: string;
|
|
761
|
+
};
|
|
762
|
+
}>;
|
|
763
|
+
export declare function listGovernanceRules(orgId: string): Promise<{
|
|
764
|
+
org_id: string;
|
|
765
|
+
rules: GovernanceRule[];
|
|
766
|
+
}>;
|
|
767
|
+
export declare function createGovernanceRule(orgId: string, body: {
|
|
768
|
+
name: string;
|
|
769
|
+
predicate: Record<string, unknown>;
|
|
770
|
+
destination_ids: string[];
|
|
771
|
+
enabled?: boolean;
|
|
772
|
+
}): Promise<GovernanceRule>;
|
|
773
|
+
export declare function deleteGovernanceRule(orgId: string, ruleId: string): Promise<{
|
|
774
|
+
ok: true;
|
|
775
|
+
}>;
|
|
776
|
+
/**
|
|
777
|
+
* Capability-based scope vocabulary (ADR-049). Mirrors the API's
|
|
778
|
+
* VALID_SCOPES exactly. Update both surfaces together when the
|
|
779
|
+
* vocabulary changes.
|
|
780
|
+
*/
|
|
781
|
+
export declare const API_KEY_SCOPES: readonly ["gateway", "api:read", "api:write", "admin:org", "admin:platform"];
|
|
782
|
+
export type ApiKeyScope = (typeof API_KEY_SCOPES)[number];
|
|
783
|
+
export declare const DEFAULT_API_KEY_SCOPES: ApiKeyScope[];
|
|
784
|
+
/**
|
|
785
|
+
* Recognize legacy two-scope sets so the CLI can annotate pre-ADR-049
|
|
786
|
+
* keys appropriately. Mirror of mnemom-api `expandLegacyScopes` logic
|
|
787
|
+
* for display purposes; the auth gate handles the actual aliasing.
|
|
788
|
+
*/
|
|
789
|
+
export declare function isLegacyScopeSet(scopes: string[] | undefined): boolean;
|
|
790
|
+
export interface ApiKeyListItem {
|
|
791
|
+
key_id: string;
|
|
792
|
+
key_prefix: string;
|
|
793
|
+
name: string;
|
|
794
|
+
scopes: string[];
|
|
795
|
+
created_at: string;
|
|
796
|
+
last_used_at: string | null;
|
|
797
|
+
org_id?: string | null;
|
|
798
|
+
}
|
|
799
|
+
export interface ApiKeyCreated {
|
|
800
|
+
key_id: string;
|
|
801
|
+
key: string;
|
|
802
|
+
key_prefix: string;
|
|
803
|
+
name: string;
|
|
804
|
+
scopes: string[];
|
|
805
|
+
created_at: string;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* GET /v1/api-keys — list the caller's active personal API keys.
|
|
809
|
+
*/
|
|
810
|
+
export declare function listApiKeys(): Promise<ApiKeyListItem[]>;
|
|
811
|
+
/**
|
|
812
|
+
* POST /v1/api-keys — mint a new personal API key with explicit scopes.
|
|
813
|
+
*
|
|
814
|
+
* Returns the full secret only on this call. The mint-time ceiling
|
|
815
|
+
* rejects admin scopes the caller is not eligible for (admin:platform
|
|
816
|
+
* for non-staff, admin:org for users not in any org-admin role).
|
|
817
|
+
*/
|
|
818
|
+
export declare function createApiKey(name: string, scopes: ApiKeyScope[]): Promise<ApiKeyCreated>;
|
|
819
|
+
/**
|
|
820
|
+
* POST /v1/api-keys/{key_id}/rotate — atomic mint-new + revoke-old.
|
|
821
|
+
* Returns the full new secret only on this call. The new key inherits
|
|
822
|
+
* the old key's name and scopes verbatim; the old key is revoked the
|
|
823
|
+
* moment this returns.
|
|
824
|
+
*/
|
|
825
|
+
export declare function rotateApiKey(keyId: string): Promise<ApiKeyCreated>;
|
|
826
|
+
/**
|
|
827
|
+
* DELETE /v1/api-keys/{key_id} — soft-revoke. The key row stays for
|
|
828
|
+
* audit; `is_active` flips to false and `revoked_at` is timestamped.
|
|
829
|
+
*/
|
|
830
|
+
export declare function revokeApiKey(keyId: string): Promise<void>;
|
|
831
|
+
export interface RecipeReportInput {
|
|
832
|
+
type: "fn" | "fp";
|
|
833
|
+
summary: string;
|
|
834
|
+
evidence?: string;
|
|
835
|
+
agent_id?: string;
|
|
836
|
+
checkpoint_id?: string;
|
|
837
|
+
}
|
|
838
|
+
export interface RecipeReportResult {
|
|
839
|
+
ok: true;
|
|
840
|
+
candidate_id: string;
|
|
841
|
+
type: "fn" | "fp";
|
|
842
|
+
related_recipe_id: string;
|
|
843
|
+
}
|
|
844
|
+
export declare function reportRecipeFnFp(recipeId: string, input: RecipeReportInput): Promise<RecipeReportResult>;
|
|
648
845
|
export {};
|
package/dist/lib/api.js
CHANGED
|
@@ -1162,3 +1162,210 @@ export async function getSafeHouseHarnessState() {
|
|
|
1162
1162
|
}
|
|
1163
1163
|
return (await response.json());
|
|
1164
1164
|
}
|
|
1165
|
+
function buildSignalListQuery(opts = {}) {
|
|
1166
|
+
const params = new URLSearchParams();
|
|
1167
|
+
if (opts.source)
|
|
1168
|
+
params.set("source", opts.source);
|
|
1169
|
+
if (opts.severity)
|
|
1170
|
+
params.set("severity", opts.severity);
|
|
1171
|
+
if (opts.status)
|
|
1172
|
+
params.set("status", opts.status);
|
|
1173
|
+
if (opts.scope)
|
|
1174
|
+
params.set("scope", opts.scope);
|
|
1175
|
+
if (opts.pattern_type)
|
|
1176
|
+
params.set("pattern_type", opts.pattern_type);
|
|
1177
|
+
if (opts.since)
|
|
1178
|
+
params.set("since", opts.since);
|
|
1179
|
+
if (opts.limit)
|
|
1180
|
+
params.set("limit", String(opts.limit));
|
|
1181
|
+
const qs = params.toString();
|
|
1182
|
+
return qs ? `?${qs}` : "";
|
|
1183
|
+
}
|
|
1184
|
+
async function gFetch(path, init = {}, notFoundLabel) {
|
|
1185
|
+
const url = validateUrl(`${API_BASE}${path}`);
|
|
1186
|
+
const response = await fetchWithAuthRetry(url, async () => ({
|
|
1187
|
+
method: init.method ?? "GET",
|
|
1188
|
+
headers: {
|
|
1189
|
+
...(await authHeaders()),
|
|
1190
|
+
Accept: "application/json",
|
|
1191
|
+
...(init.body ? { "Content-Type": "application/json" } : {}),
|
|
1192
|
+
...(init.headers ?? {}),
|
|
1193
|
+
},
|
|
1194
|
+
body: init.body,
|
|
1195
|
+
}));
|
|
1196
|
+
if (!response.ok) {
|
|
1197
|
+
if (response.status === 401)
|
|
1198
|
+
throw new Error("Not authenticated.");
|
|
1199
|
+
if (response.status === 403)
|
|
1200
|
+
throw new Error("Permission denied (need org admin / membership).");
|
|
1201
|
+
if (response.status === 404 && notFoundLabel)
|
|
1202
|
+
throw new Error(`${notFoundLabel} not found.`);
|
|
1203
|
+
const err = (await response.json().catch(() => ({ error: "unknown" })));
|
|
1204
|
+
throw new Error(err.message || `Request failed: ${response.status}`);
|
|
1205
|
+
}
|
|
1206
|
+
return (await response.json());
|
|
1207
|
+
}
|
|
1208
|
+
export async function listGovernanceSignalsForOrg(orgId, opts = {}) {
|
|
1209
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/signals${buildSignalListQuery(opts)}`, {}, "Org");
|
|
1210
|
+
}
|
|
1211
|
+
export async function listGovernanceSignalsForTeam(teamId, opts = {}) {
|
|
1212
|
+
return gFetch(`/v1/teams/${encodeURIComponent(teamId)}/governance/signals${buildSignalListQuery(opts)}`, {}, "Team");
|
|
1213
|
+
}
|
|
1214
|
+
export async function listGovernanceSignalsForAgent(agentId, opts = {}) {
|
|
1215
|
+
return gFetch(`/v1/agents/${encodeURIComponent(agentId)}/governance/signals${buildSignalListQuery(opts)}`, {}, "Agent");
|
|
1216
|
+
}
|
|
1217
|
+
export async function getGovernanceSignal(id) {
|
|
1218
|
+
return gFetch(`/v1/governance/signals/${encodeURIComponent(id)}`, {}, "Signal");
|
|
1219
|
+
}
|
|
1220
|
+
export async function acknowledgeGovernanceSignal(id, body = {}) {
|
|
1221
|
+
return gFetch(`/v1/governance/signals/${encodeURIComponent(id)}/acknowledge`, { method: "POST", body: JSON.stringify(body) }, "Signal");
|
|
1222
|
+
}
|
|
1223
|
+
export async function resolveGovernanceSignal(id, body) {
|
|
1224
|
+
return gFetch(`/v1/governance/signals/${encodeURIComponent(id)}/resolve`, { method: "POST", body: JSON.stringify(body) }, "Signal");
|
|
1225
|
+
}
|
|
1226
|
+
export async function dismissGovernanceSignal(id, body = {}) {
|
|
1227
|
+
return gFetch(`/v1/governance/signals/${encodeURIComponent(id)}/dismiss`, { method: "POST", body: JSON.stringify(body) }, "Signal");
|
|
1228
|
+
}
|
|
1229
|
+
export async function listGovernanceDestinations(orgId) {
|
|
1230
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/notification-destinations`, {}, "Org");
|
|
1231
|
+
}
|
|
1232
|
+
export async function createGovernanceDestination(orgId, body) {
|
|
1233
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/notification-destinations`, { method: "POST", body: JSON.stringify(body) }, "Org");
|
|
1234
|
+
}
|
|
1235
|
+
export async function deleteGovernanceDestination(orgId, destinationId) {
|
|
1236
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/notification-destinations/${encodeURIComponent(destinationId)}`, { method: "DELETE" }, "Destination");
|
|
1237
|
+
}
|
|
1238
|
+
export async function testGovernanceDestination(orgId, destinationId) {
|
|
1239
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/notification-destinations/${encodeURIComponent(destinationId)}/test`, { method: "POST", body: JSON.stringify({}) }, "Destination");
|
|
1240
|
+
}
|
|
1241
|
+
export async function listGovernanceRules(orgId) {
|
|
1242
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/escalation-rules`, {}, "Org");
|
|
1243
|
+
}
|
|
1244
|
+
export async function createGovernanceRule(orgId, body) {
|
|
1245
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/escalation-rules`, { method: "POST", body: JSON.stringify(body) }, "Org");
|
|
1246
|
+
}
|
|
1247
|
+
export async function deleteGovernanceRule(orgId, ruleId) {
|
|
1248
|
+
return gFetch(`/v1/orgs/${encodeURIComponent(orgId)}/governance/escalation-rules/${encodeURIComponent(ruleId)}`, { method: "DELETE" }, "Rule");
|
|
1249
|
+
}
|
|
1250
|
+
// ─── api-keys (ADR-049) ─────────────────────────────────────────────────
|
|
1251
|
+
/**
|
|
1252
|
+
* Capability-based scope vocabulary (ADR-049). Mirrors the API's
|
|
1253
|
+
* VALID_SCOPES exactly. Update both surfaces together when the
|
|
1254
|
+
* vocabulary changes.
|
|
1255
|
+
*/
|
|
1256
|
+
export const API_KEY_SCOPES = [
|
|
1257
|
+
"gateway",
|
|
1258
|
+
"api:read",
|
|
1259
|
+
"api:write",
|
|
1260
|
+
"admin:org",
|
|
1261
|
+
"admin:platform",
|
|
1262
|
+
];
|
|
1263
|
+
export const DEFAULT_API_KEY_SCOPES = ["gateway", "api:read", "api:write"];
|
|
1264
|
+
/**
|
|
1265
|
+
* Recognize legacy two-scope sets so the CLI can annotate pre-ADR-049
|
|
1266
|
+
* keys appropriately. Mirror of mnemom-api `expandLegacyScopes` logic
|
|
1267
|
+
* for display purposes; the auth gate handles the actual aliasing.
|
|
1268
|
+
*/
|
|
1269
|
+
export function isLegacyScopeSet(scopes) {
|
|
1270
|
+
if (!scopes || scopes.length === 0)
|
|
1271
|
+
return false;
|
|
1272
|
+
if (scopes.length === 2 && scopes.includes("gateway") && scopes.includes("api")) {
|
|
1273
|
+
return true;
|
|
1274
|
+
}
|
|
1275
|
+
if (scopes.length === 1 && scopes[0] === "api")
|
|
1276
|
+
return true;
|
|
1277
|
+
return false;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* GET /v1/api-keys — list the caller's active personal API keys.
|
|
1281
|
+
*/
|
|
1282
|
+
export async function listApiKeys() {
|
|
1283
|
+
const url = validateUrl(`${API_BASE}/v1/api-keys`);
|
|
1284
|
+
const response = await fetchWithAuthRetry(url, async () => ({
|
|
1285
|
+
headers: await authHeaders(),
|
|
1286
|
+
}));
|
|
1287
|
+
if (!response.ok) {
|
|
1288
|
+
if (response.status === 401) {
|
|
1289
|
+
throw new Error("Not authenticated. Run `mnemom login` or set MNEMOM_API_KEY.");
|
|
1290
|
+
}
|
|
1291
|
+
const err = (await response.json().catch(() => ({ error: "unknown" })));
|
|
1292
|
+
throw new Error(err.message || `Failed to list api keys: ${response.status}`);
|
|
1293
|
+
}
|
|
1294
|
+
const data = (await response.json());
|
|
1295
|
+
return data.keys ?? [];
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* POST /v1/api-keys — mint a new personal API key with explicit scopes.
|
|
1299
|
+
*
|
|
1300
|
+
* Returns the full secret only on this call. The mint-time ceiling
|
|
1301
|
+
* rejects admin scopes the caller is not eligible for (admin:platform
|
|
1302
|
+
* for non-staff, admin:org for users not in any org-admin role).
|
|
1303
|
+
*/
|
|
1304
|
+
export async function createApiKey(name, scopes) {
|
|
1305
|
+
const url = validateUrl(`${API_BASE}/v1/api-keys`);
|
|
1306
|
+
const response = await fetchWithAuthRetry(url, async () => ({
|
|
1307
|
+
method: "POST",
|
|
1308
|
+
headers: { ...(await authHeaders()), "Content-Type": "application/json" },
|
|
1309
|
+
body: JSON.stringify({ name, scopes }),
|
|
1310
|
+
}));
|
|
1311
|
+
if (!response.ok) {
|
|
1312
|
+
if (response.status === 401) {
|
|
1313
|
+
throw new Error("Not authenticated. Run `mnemom login` or set MNEMOM_API_KEY.");
|
|
1314
|
+
}
|
|
1315
|
+
const body = await response.text().catch(() => "");
|
|
1316
|
+
if (response.status === 403) {
|
|
1317
|
+
throw new Error(`Mint-time ceiling rejected scope: ${body}`);
|
|
1318
|
+
}
|
|
1319
|
+
if (response.status === 400) {
|
|
1320
|
+
throw new Error(`Invalid scope(s): ${body}`);
|
|
1321
|
+
}
|
|
1322
|
+
throw new Error(`Failed to create api key: ${response.status} ${body}`);
|
|
1323
|
+
}
|
|
1324
|
+
return (await response.json());
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* POST /v1/api-keys/{key_id}/rotate — atomic mint-new + revoke-old.
|
|
1328
|
+
* Returns the full new secret only on this call. The new key inherits
|
|
1329
|
+
* the old key's name and scopes verbatim; the old key is revoked the
|
|
1330
|
+
* moment this returns.
|
|
1331
|
+
*/
|
|
1332
|
+
export async function rotateApiKey(keyId) {
|
|
1333
|
+
const url = validateUrl(`${API_BASE}/v1/api-keys/${encodeURIComponent(keyId)}/rotate`);
|
|
1334
|
+
const response = await fetchWithAuthRetry(url, async () => ({
|
|
1335
|
+
method: "POST",
|
|
1336
|
+
headers: { ...(await authHeaders()), "Content-Type": "application/json" },
|
|
1337
|
+
}));
|
|
1338
|
+
if (!response.ok) {
|
|
1339
|
+
if (response.status === 401) {
|
|
1340
|
+
throw new Error("Not authenticated. Run `mnemom login` or set MNEMOM_API_KEY.");
|
|
1341
|
+
}
|
|
1342
|
+
if (response.status === 404)
|
|
1343
|
+
throw new Error(`Key not found: ${keyId}`);
|
|
1344
|
+
const body = await response.text().catch(() => "");
|
|
1345
|
+
throw new Error(`Failed to rotate api key: ${response.status} ${body}`);
|
|
1346
|
+
}
|
|
1347
|
+
return (await response.json());
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* DELETE /v1/api-keys/{key_id} — soft-revoke. The key row stays for
|
|
1351
|
+
* audit; `is_active` flips to false and `revoked_at` is timestamped.
|
|
1352
|
+
*/
|
|
1353
|
+
export async function revokeApiKey(keyId) {
|
|
1354
|
+
const url = validateUrl(`${API_BASE}/v1/api-keys/${encodeURIComponent(keyId)}`);
|
|
1355
|
+
const response = await fetchWithAuthRetry(url, async () => ({
|
|
1356
|
+
method: "DELETE",
|
|
1357
|
+
headers: await authHeaders(),
|
|
1358
|
+
}));
|
|
1359
|
+
if (!response.ok) {
|
|
1360
|
+
if (response.status === 401) {
|
|
1361
|
+
throw new Error("Not authenticated. Run `mnemom login` or set MNEMOM_API_KEY.");
|
|
1362
|
+
}
|
|
1363
|
+
if (response.status === 404)
|
|
1364
|
+
throw new Error(`Key not found: ${keyId}`);
|
|
1365
|
+
const body = await response.text().catch(() => "");
|
|
1366
|
+
throw new Error(`Failed to revoke api key: ${response.status} ${body}`);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
export async function reportRecipeFnFp(recipeId, input) {
|
|
1370
|
+
return postApi(`/v1/recipes/${encodeURIComponent(recipeId)}/report`, input);
|
|
1371
|
+
}
|
package/dist/lib/auth.js
CHANGED
|
@@ -311,11 +311,7 @@ async function startCallbackServer(expectedState) {
|
|
|
311
311
|
};
|
|
312
312
|
}
|
|
313
313
|
function openBrowser(url) {
|
|
314
|
-
const cmd = process.platform === "darwin"
|
|
315
|
-
? "open"
|
|
316
|
-
: process.platform === "win32"
|
|
317
|
-
? "start"
|
|
318
|
-
: "xdg-open";
|
|
314
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
319
315
|
exec(`${cmd} ${JSON.stringify(url)}`);
|
|
320
316
|
}
|
|
321
317
|
// ============================================================================
|
package/dist/lib/format.d.ts
CHANGED
package/dist/lib/format.js
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE event-stream consumer for `mnemom listen` (Track 2 W3.4b).
|
|
3
|
+
*
|
|
4
|
+
* Parses `text/event-stream` framing into `SseFrame` objects per the
|
|
5
|
+
* W3C EventSource spec subset we need:
|
|
6
|
+
*
|
|
7
|
+
* - Events delimited by `\n\n`.
|
|
8
|
+
* - Lines starting with `:` are comments (heartbeats).
|
|
9
|
+
* - `event: <name>` sets the frame's event name (default "message").
|
|
10
|
+
* - `data: <json>` accumulates into a multi-line payload.
|
|
11
|
+
* - `id: <cursor>` sets the frame's last-event-id.
|
|
12
|
+
*
|
|
13
|
+
* Exposed as a generator so callers can iterate with `for await`.
|
|
14
|
+
*/
|
|
15
|
+
export interface SseFrame {
|
|
16
|
+
event: string;
|
|
17
|
+
/** Multi-line `data:` payload joined with `\n`. */
|
|
18
|
+
data: string;
|
|
19
|
+
/** The `id:` cursor (for Last-Event-ID reconnect). */
|
|
20
|
+
id?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Consume a fetch Response body and yield SSE frames.
|
|
24
|
+
*
|
|
25
|
+
* Buffers across chunk boundaries — SSE delimiters can land mid-chunk.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseSseStream(body: ReadableStream<Uint8Array>): AsyncGenerator<SseFrame, void, void>;
|
|
28
|
+
/**
|
|
29
|
+
* Re-sign a webhook event body with a local-only secret. Mirrors the
|
|
30
|
+
* platform's HMAC-SHA256 over `${ts}.${rawBody}` convention so the
|
|
31
|
+
* receiver verifies cleanly with the same scheme.
|
|
32
|
+
*
|
|
33
|
+
* Used by `mnemom listen --forward-to <url> --secret <hex>` to make
|
|
34
|
+
* the local receiver behave identically to a production webhook
|
|
35
|
+
* subscriber.
|
|
36
|
+
*/
|
|
37
|
+
export declare function reSignDelivery(rawBody: string, signingSecret: string, nowSeconds?: number): {
|
|
38
|
+
timestamp: string;
|
|
39
|
+
signature: string;
|
|
40
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE event-stream consumer for `mnemom listen` (Track 2 W3.4b).
|
|
3
|
+
*
|
|
4
|
+
* Parses `text/event-stream` framing into `SseFrame` objects per the
|
|
5
|
+
* W3C EventSource spec subset we need:
|
|
6
|
+
*
|
|
7
|
+
* - Events delimited by `\n\n`.
|
|
8
|
+
* - Lines starting with `:` are comments (heartbeats).
|
|
9
|
+
* - `event: <name>` sets the frame's event name (default "message").
|
|
10
|
+
* - `data: <json>` accumulates into a multi-line payload.
|
|
11
|
+
* - `id: <cursor>` sets the frame's last-event-id.
|
|
12
|
+
*
|
|
13
|
+
* Exposed as a generator so callers can iterate with `for await`.
|
|
14
|
+
*/
|
|
15
|
+
import { createHmac } from "node:crypto";
|
|
16
|
+
/**
|
|
17
|
+
* Consume a fetch Response body and yield SSE frames.
|
|
18
|
+
*
|
|
19
|
+
* Buffers across chunk boundaries — SSE delimiters can land mid-chunk.
|
|
20
|
+
*/
|
|
21
|
+
export async function* parseSseStream(body) {
|
|
22
|
+
const reader = body.getReader();
|
|
23
|
+
const decoder = new TextDecoder();
|
|
24
|
+
let buf = "";
|
|
25
|
+
try {
|
|
26
|
+
while (true) {
|
|
27
|
+
const { done, value } = await reader.read();
|
|
28
|
+
if (done)
|
|
29
|
+
break;
|
|
30
|
+
buf += decoder.decode(value, { stream: true });
|
|
31
|
+
// Frames end at \n\n. Process all complete frames; keep the trailing partial.
|
|
32
|
+
let idx;
|
|
33
|
+
while ((idx = buf.indexOf("\n\n")) !== -1) {
|
|
34
|
+
const frameRaw = buf.slice(0, idx);
|
|
35
|
+
buf = buf.slice(idx + 2);
|
|
36
|
+
const frame = parseFrame(frameRaw);
|
|
37
|
+
if (frame)
|
|
38
|
+
yield frame;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Drain trailing partial — usually a comment line right before the
|
|
42
|
+
// server-side close. Process if it parses cleanly.
|
|
43
|
+
if (buf.length > 0) {
|
|
44
|
+
const frame = parseFrame(buf);
|
|
45
|
+
if (frame)
|
|
46
|
+
yield frame;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
try {
|
|
51
|
+
await reader.cancel();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* socket already torn down */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function parseFrame(raw) {
|
|
59
|
+
const lines = raw.split("\n");
|
|
60
|
+
let event = "message";
|
|
61
|
+
let id;
|
|
62
|
+
const dataLines = [];
|
|
63
|
+
let hadDataField = false;
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
if (line.length === 0)
|
|
66
|
+
continue;
|
|
67
|
+
if (line.startsWith(":"))
|
|
68
|
+
continue; // heartbeat comment
|
|
69
|
+
const colonIdx = line.indexOf(":");
|
|
70
|
+
if (colonIdx === -1)
|
|
71
|
+
continue;
|
|
72
|
+
const field = line.slice(0, colonIdx);
|
|
73
|
+
const value = line.slice(colonIdx + 1).replace(/^ /, ""); // strip optional leading space
|
|
74
|
+
if (field === "event")
|
|
75
|
+
event = value;
|
|
76
|
+
else if (field === "data") {
|
|
77
|
+
dataLines.push(value);
|
|
78
|
+
hadDataField = true;
|
|
79
|
+
}
|
|
80
|
+
else if (field === "id")
|
|
81
|
+
id = value;
|
|
82
|
+
}
|
|
83
|
+
if (!hadDataField)
|
|
84
|
+
return null;
|
|
85
|
+
return { event, data: dataLines.join("\n"), id };
|
|
86
|
+
}
|
|
87
|
+
// ─── HMAC re-signing for local forwarding ───────────────────────────────
|
|
88
|
+
/**
|
|
89
|
+
* Re-sign a webhook event body with a local-only secret. Mirrors the
|
|
90
|
+
* platform's HMAC-SHA256 over `${ts}.${rawBody}` convention so the
|
|
91
|
+
* receiver verifies cleanly with the same scheme.
|
|
92
|
+
*
|
|
93
|
+
* Used by `mnemom listen --forward-to <url> --secret <hex>` to make
|
|
94
|
+
* the local receiver behave identically to a production webhook
|
|
95
|
+
* subscriber.
|
|
96
|
+
*/
|
|
97
|
+
export function reSignDelivery(rawBody, signingSecret, nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
98
|
+
const timestamp = String(nowSeconds);
|
|
99
|
+
const signature = "v1=" + createHmac("sha256", signingSecret).update(`${timestamp}.${rawBody}`).digest("hex");
|
|
100
|
+
return { timestamp, signature };
|
|
101
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export interface WebhookEndpoint {
|
|
2
|
+
endpoint_id: string;
|
|
3
|
+
billing_account_id: string;
|
|
4
|
+
url: string;
|
|
5
|
+
description: string;
|
|
6
|
+
signing_secret?: string;
|
|
7
|
+
event_types: string[];
|
|
8
|
+
is_active: boolean;
|
|
9
|
+
consecutive_failures: number;
|
|
10
|
+
disabled_at: string | null;
|
|
11
|
+
disabled_reason: string | null;
|
|
12
|
+
created_at: string;
|
|
13
|
+
updated_at: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WebhookDelivery {
|
|
16
|
+
delivery_id: string;
|
|
17
|
+
event_id: string;
|
|
18
|
+
endpoint_id: string;
|
|
19
|
+
status: string;
|
|
20
|
+
attempt_count?: number;
|
|
21
|
+
last_attempt_at?: string | null;
|
|
22
|
+
next_attempt_at?: string | null;
|
|
23
|
+
last_status_code?: number | null;
|
|
24
|
+
last_error?: string | null;
|
|
25
|
+
created_at?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface WebhookReplayResponse {
|
|
28
|
+
event_id: string;
|
|
29
|
+
event_type: string;
|
|
30
|
+
deliveries: Array<{
|
|
31
|
+
delivery_id: string;
|
|
32
|
+
endpoint_id: string;
|
|
33
|
+
}>;
|
|
34
|
+
failed_endpoints?: Array<{
|
|
35
|
+
endpoint_id: string;
|
|
36
|
+
error: string;
|
|
37
|
+
}>;
|
|
38
|
+
message?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface TestDeliveryResult {
|
|
41
|
+
success: boolean;
|
|
42
|
+
status: number | null;
|
|
43
|
+
latency_ms: number | null;
|
|
44
|
+
error: string | null;
|
|
45
|
+
}
|
|
46
|
+
export declare function listWebhookEndpoints(orgId: string): Promise<WebhookEndpoint[]>;
|
|
47
|
+
export declare function getWebhookEndpoint(orgId: string, endpointId: string): Promise<WebhookEndpoint>;
|
|
48
|
+
export declare function createWebhookEndpoint(orgId: string, body: {
|
|
49
|
+
url: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
event_types?: string[];
|
|
52
|
+
}): Promise<WebhookEndpoint>;
|
|
53
|
+
export declare function updateWebhookEndpoint(orgId: string, endpointId: string, body: {
|
|
54
|
+
url?: string;
|
|
55
|
+
description?: string;
|
|
56
|
+
event_types?: string[];
|
|
57
|
+
is_active?: boolean;
|
|
58
|
+
}): Promise<WebhookEndpoint>;
|
|
59
|
+
export declare function deleteWebhookEndpoint(orgId: string, endpointId: string): Promise<{
|
|
60
|
+
deleted: true;
|
|
61
|
+
endpoint_id: string;
|
|
62
|
+
}>;
|
|
63
|
+
export declare function rotateWebhookSecret(orgId: string, endpointId: string): Promise<{
|
|
64
|
+
endpoint_id: string;
|
|
65
|
+
signing_secret: string;
|
|
66
|
+
}>;
|
|
67
|
+
export declare function testWebhookEndpoint(orgId: string, endpointId: string): Promise<TestDeliveryResult>;
|
|
68
|
+
export declare function listWebhookDeliveries(orgId: string, opts?: {
|
|
69
|
+
endpointId?: string;
|
|
70
|
+
limit?: number;
|
|
71
|
+
offset?: number;
|
|
72
|
+
}): Promise<WebhookDelivery[]>;
|
|
73
|
+
export declare function redeliverWebhookEvent(orgId: string, deliveryId: string): Promise<{
|
|
74
|
+
delivery_id: string;
|
|
75
|
+
event_id: string;
|
|
76
|
+
status: string;
|
|
77
|
+
}>;
|
|
78
|
+
export declare function replayWebhookEvent(orgId: string, eventId: string, opts?: {
|
|
79
|
+
endpointIds?: string[];
|
|
80
|
+
}): Promise<WebhookReplayResponse>;
|