@robosystems/core 0.6.1 → 0.6.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robosystems/core",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Shared RoboSystems frontend core library (auth, contexts, task monitoring, UI components) for the RoboSystems Next.js apps",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -2,5 +2,10 @@ import React from 'react';
2
2
  export interface ApiKeysCardProps {
3
3
  theme?: any;
4
4
  className?: string;
5
+ /**
6
+ * When set, the card links MCP users to the app's Connect page (the
7
+ * ready-made connector-URL flow) — e.g. "/connect" in robosystems-app.
8
+ */
9
+ connectHref?: string;
5
10
  }
6
11
  export declare const ApiKeysCard: React.FC<ApiKeysCardProps>;
@@ -2,14 +2,21 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import * as SDK from '@robosystems/client';
4
4
  import { Button } from 'flowbite-react';
5
- import { useEffect, useState } from 'react';
5
+ import { useContext, useEffect, useState } from 'react';
6
6
  import { HiKey, HiPlusCircle } from 'react-icons/hi';
7
+ import { GraphContext } from '../../contexts';
7
8
  import { Spinner } from '../Spinner';
8
9
  import { SettingsCard } from '../forms/SettingsCard';
9
10
  import { StatusAlert } from '../forms/StatusAlert';
10
11
  import { ApiKeyTable } from './ApiKeyTable';
11
12
  import { CreateApiKeyModal } from './CreateApiKeyModal';
12
- export const ApiKeysCard = ({ theme, className = '', }) => {
13
+ export const ApiKeysCard = ({ theme, className = '', connectHref, }) => {
14
+ var _a;
15
+ // Read the graph context leniently: outside a GraphProvider the scope
16
+ // selector simply doesn't render, preserving the card's old standalone
17
+ // contract instead of throwing.
18
+ const graphContext = useContext(GraphContext);
19
+ const availableGraphs = (_a = graphContext === null || graphContext === void 0 ? void 0 : graphContext.state.graphs) !== null && _a !== void 0 ? _a : [];
13
20
  const [apiKeys, setApiKeys] = useState([]);
14
21
  const [isLoading, setIsLoading] = useState(false);
15
22
  const [isOpenCreateModal, setIsOpenCreateModal] = useState(false);
@@ -49,12 +56,13 @@ export const ApiKeysCard = ({ theme, className = '', }) => {
49
56
  }
50
57
  };
51
58
  const handleCreateKey = async (data) => {
52
- var _a;
59
+ var _a, _b;
53
60
  // Always use SDK to create key
54
61
  const response = await SDK.createUserApiKey({
55
62
  body: {
56
63
  name: data.name,
57
64
  description: `API key for ${data.name}`,
65
+ graph_id: data.graphId,
58
66
  },
59
67
  });
60
68
  await fetchApiKeys(); // Refresh the list
@@ -64,11 +72,12 @@ export const ApiKeysCard = ({ theme, className = '', }) => {
64
72
  return {
65
73
  id: (apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.id) || '',
66
74
  name: (apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.name) || data.name,
75
+ graphId: (_a = apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.graph_id) !== null && _a !== void 0 ? _a : data.graphId,
67
76
  key: (responseData === null || responseData === void 0 ? void 0 : responseData.key) || '', // The actual API key value
68
77
  createdAt: (apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.created_at) || new Date().toISOString(),
69
78
  lastUsedAt: (apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.last_used_at) || null,
70
79
  expiresAt: null,
71
- isActive: (_a = apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.is_active) !== null && _a !== void 0 ? _a : true,
80
+ isActive: (_b = apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.is_active) !== null && _b !== void 0 ? _b : true,
72
81
  isSystem: false,
73
82
  };
74
83
  };
@@ -93,5 +102,11 @@ export const ApiKeysCard = ({ theme, className = '', }) => {
93
102
  day: 'numeric',
94
103
  });
95
104
  };
96
- return (_jsxs(SettingsCard, { title: "API Keys", description: "Manage your API keys for programmatic access", icon: HiKey, theme: theme === null || theme === void 0 ? void 0 : theme.card, className: className, children: [_jsx("div", { className: "mb-4 flex items-center justify-end", children: _jsxs(Button, { theme: theme === null || theme === void 0 ? void 0 : theme.button, color: "blue", onClick: () => setIsOpenCreateModal(true), children: [_jsx(HiPlusCircle, { className: "mr-2 h-5 w-5" }), "Create API Key"] }) }), error && (_jsx(StatusAlert, { type: "error", message: error, theme: theme === null || theme === void 0 ? void 0 : theme.alert })), isLoading ? (_jsx("div", { className: "flex justify-center py-4", children: _jsx(Spinner, { size: "lg" }) })) : !Array.isArray(apiKeys) || apiKeys.length === 0 ? (_jsxs("div", { className: "rounded-lg border border-zinc-200 bg-zinc-50 p-8 text-center dark:border-zinc-700 dark:bg-zinc-800", children: [_jsx(HiKey, { className: "mx-auto mb-3 h-12 w-12 text-zinc-400" }), _jsx("h3", { className: "mb-2 text-lg font-semibold text-zinc-900 dark:text-zinc-100", children: "No API keys yet" }), _jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Create an API key to access your graphs programmatically" })] })) : (_jsx(ApiKeyTable, { apiKeys: apiKeys, onRevokeKey: handleRevokeKey, theme: theme, formatDate: formatDate })), _jsx(CreateApiKeyModal, { isOpen: isOpenCreateModal, onClose: () => setIsOpenCreateModal(false), onCreateKey: handleCreateKey, theme: theme })] }));
105
+ return (_jsxs(SettingsCard, { title: "API Keys", description: "Manage your API keys for programmatic access", icon: HiKey, theme: theme === null || theme === void 0 ? void 0 : theme.card, className: className, children: [_jsx("div", { className: "mb-4 flex items-center justify-end", children: _jsxs(Button, { theme: theme === null || theme === void 0 ? void 0 : theme.button, color: "blue", onClick: () => setIsOpenCreateModal(true), children: [_jsx(HiPlusCircle, { className: "mr-2 h-5 w-5" }), "Create API Key"] }) }), error && (_jsx(StatusAlert, { type: "error", message: error, theme: theme === null || theme === void 0 ? void 0 : theme.alert })), isLoading ? (_jsx("div", { className: "flex justify-center py-4", children: _jsx(Spinner, { size: "lg" }) })) : !Array.isArray(apiKeys) || apiKeys.length === 0 ? (_jsxs("div", { className: "rounded-lg border border-zinc-200 bg-zinc-50 p-8 text-center dark:border-zinc-700 dark:bg-zinc-800", children: [_jsx(HiKey, { className: "mx-auto mb-3 h-12 w-12 text-zinc-400" }), _jsx("h3", { className: "mb-2 text-lg font-semibold text-zinc-900 dark:text-zinc-100", children: "No API keys yet" }), _jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Create an API key to access your graphs programmatically" })] })) : (_jsx(ApiKeyTable, { apiKeys: apiKeys, onRevokeKey: handleRevokeKey, theme: theme, formatDate: formatDate })), connectHref && (_jsxs("p", { className: "mt-4 text-sm text-zinc-500 dark:text-zinc-400", children: ["Connecting Claude or another MCP client? Generate a ready-made connector URL from the", ' ', _jsx("a", { href: connectHref, className: "text-primary-600 dark:text-primary-400 font-medium hover:underline", children: "Connect page" }), "."] })), _jsx(CreateApiKeyModal, { isOpen: isOpenCreateModal, onClose: () => setIsOpenCreateModal(false), onCreateKey: handleCreateKey, theme: theme, graphs: availableGraphs.map((g) => {
106
+ var _a;
107
+ return ({
108
+ graphId: g.graphId,
109
+ graphName: (_a = g.graphName) !== null && _a !== void 0 ? _a : g.graphId,
110
+ });
111
+ }) })] }));
97
112
  };
@@ -1,11 +1,18 @@
1
1
  import React from 'react';
2
2
  import type { ApiKeyWithValue } from '../types';
3
+ export interface CreateApiKeyModalGraph {
4
+ graphId: string;
5
+ graphName: string;
6
+ }
3
7
  export interface CreateApiKeyModalProps {
4
8
  isOpen: boolean;
5
9
  onClose: () => void;
6
10
  onCreateKey: (data: {
7
11
  name: string;
12
+ graphId?: string;
8
13
  }) => Promise<ApiKeyWithValue>;
9
14
  theme?: any;
15
+ /** Graphs offered as scope choices; omit to create account-wide keys only */
16
+ graphs?: CreateApiKeyModalGraph[];
10
17
  }
11
18
  export declare const CreateApiKeyModal: React.FC<CreateApiKeyModalProps>;
@@ -1,15 +1,17 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { Button, Label, Modal, ModalBody, ModalFooter, ModalHeader, } from 'flowbite-react';
3
+ import { Button, Label, Modal, ModalBody, ModalFooter, ModalHeader, Select, } from 'flowbite-react';
4
4
  import { useState } from 'react';
5
5
  import { Spinner } from '../Spinner';
6
6
  import { SettingsFormField } from '../forms/SettingsFormField';
7
7
  import { StatusAlert } from '../forms/StatusAlert';
8
8
  import { ApiKeyDisplay } from './ApiKeyDisplay';
9
- export const CreateApiKeyModal = ({ isOpen, onClose, onCreateKey, theme, }) => {
9
+ export const CreateApiKeyModal = ({ isOpen, onClose, onCreateKey, theme, graphs, }) => {
10
+ var _a;
10
11
  const [isCreating, setIsCreating] = useState(false);
11
12
  const [newApiKey, setNewApiKey] = useState(null);
12
13
  const [keyName, setKeyName] = useState('');
14
+ const [scopeGraphId, setScopeGraphId] = useState('');
13
15
  const [error, setError] = useState(null);
14
16
  const handleSubmit = async (e) => {
15
17
  e.preventDefault();
@@ -18,6 +20,7 @@ export const CreateApiKeyModal = ({ isOpen, onClose, onCreateKey, theme, }) => {
18
20
  try {
19
21
  const result = await onCreateKey({
20
22
  name: keyName,
23
+ graphId: scopeGraphId || undefined,
21
24
  });
22
25
  setNewApiKey(result);
23
26
  }
@@ -31,6 +34,7 @@ export const CreateApiKeyModal = ({ isOpen, onClose, onCreateKey, theme, }) => {
31
34
  const handleCloseModal = () => {
32
35
  onClose();
33
36
  setKeyName('');
37
+ setScopeGraphId('');
34
38
  setNewApiKey(null);
35
39
  setError(null);
36
40
  };
@@ -43,5 +47,7 @@ export const CreateApiKeyModal = ({ isOpen, onClose, onCreateKey, theme, }) => {
43
47
  day: 'numeric',
44
48
  });
45
49
  };
46
- return (_jsxs(Modal, { theme: theme === null || theme === void 0 ? void 0 : theme.modal, show: isOpen, onClose: handleCloseModal, children: [_jsx(ModalHeader, { children: "Create API Key" }), _jsx(ModalBody, { children: newApiKey ? (_jsxs("div", { children: [_jsx(StatusAlert, { type: "success", message: "Your API key has been created.", theme: theme === null || theme === void 0 ? void 0 : theme.alert }), _jsxs("div", { className: "mb-4 space-y-4", children: [_jsx(ApiKeyDisplay, { label: "ROBOSYSTEMS_API_KEY", value: newApiKey.key, keyId: newApiKey.id, theme: theme }), _jsx("p", { className: "text-sm font-medium text-red-600 dark:text-red-400", children: "\u26A0\uFE0F Important: Save this API key somewhere safe. For security reasons, you cannot view it again after closing this dialog." })] }), _jsxs("div", { className: "mb-2", children: [_jsx(Label, { theme: theme === null || theme === void 0 ? void 0 : theme.label, htmlFor: "keyDetails", children: "Key Details" }), _jsxs("div", { className: "mt-2 space-y-2 rounded-lg border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-800", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-sm font-medium text-zinc-500 dark:text-zinc-400", children: "Name:" }), _jsx("span", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: newApiKey.name })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-sm font-medium text-zinc-500 dark:text-zinc-400", children: "Created:" }), _jsx("span", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: formatDate(newApiKey.createdAt) })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-sm font-medium text-zinc-500 dark:text-zinc-400", children: "Status:" }), _jsx("span", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Active" })] })] })] })] })) : (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsx(SettingsFormField, { id: "keyName", label: "API Key Name", placeholder: "My API Key", value: keyName, onChange: (e) => setKeyName(e.target.value), required: true, theme: theme }), _jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Give your API key a memorable name to identify it later" }), _jsx("div", { className: "bg-primary-50 dark:bg-primary-900/30 rounded-lg p-3", children: _jsxs("p", { className: "text-primary-800 dark:text-primary-300 text-sm", children: [_jsx("strong", { children: "Note:" }), " API keys do not expire by date. Keys that haven't been used for 90 days will be automatically deactivated for security."] }) }), error && (_jsx(StatusAlert, { type: "error", message: error, theme: theme === null || theme === void 0 ? void 0 : theme.alert }))] })) }), _jsx(ModalFooter, { children: newApiKey ? (_jsx(Button, { theme: theme === null || theme === void 0 ? void 0 : theme.button, color: "blue", onClick: handleCloseModal, children: "Done" })) : (_jsxs(_Fragment, { children: [_jsx(Button, { theme: theme === null || theme === void 0 ? void 0 : theme.button, color: "gray", onClick: handleCloseModal, children: "Cancel" }), _jsx(Button, { theme: theme === null || theme === void 0 ? void 0 : theme.button, color: "primary", onClick: handleSubmit, disabled: isCreating || !keyName, children: isCreating ? (_jsx(Spinner, { size: "sm", className: "text-white" })) : ('Create API Key') })] })) })] }));
50
+ return (_jsxs(Modal, { theme: theme === null || theme === void 0 ? void 0 : theme.modal, show: isOpen, onClose: handleCloseModal, children: [_jsx(ModalHeader, { children: "Create API Key" }), _jsx(ModalBody, { children: newApiKey ? (_jsxs("div", { children: [_jsx(StatusAlert, { type: "success", message: "Your API key has been created.", theme: theme === null || theme === void 0 ? void 0 : theme.alert }), _jsxs("div", { className: "mb-4 space-y-4", children: [_jsx(ApiKeyDisplay, { label: "ROBOSYSTEMS_API_KEY", value: newApiKey.key, keyId: newApiKey.id, theme: theme }), _jsx("p", { className: "text-sm font-medium text-red-600 dark:text-red-400", children: "\u26A0\uFE0F Important: Save this API key somewhere safe. For security reasons, you cannot view it again after closing this dialog." })] }), _jsxs("div", { className: "mb-2", children: [_jsx(Label, { theme: theme === null || theme === void 0 ? void 0 : theme.label, htmlFor: "keyDetails", children: "Key Details" }), _jsxs("div", { className: "mt-2 space-y-2 rounded-lg border border-zinc-200 bg-zinc-50 p-3 dark:border-zinc-700 dark:bg-zinc-800", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-sm font-medium text-zinc-500 dark:text-zinc-400", children: "Name:" }), _jsx("span", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: newApiKey.name })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-sm font-medium text-zinc-500 dark:text-zinc-400", children: "Scope:" }), _jsx("span", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: (_a = newApiKey.graphId) !== null && _a !== void 0 ? _a : 'Account-wide' })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-sm font-medium text-zinc-500 dark:text-zinc-400", children: "Created:" }), _jsx("span", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: formatDate(newApiKey.createdAt) })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-sm font-medium text-zinc-500 dark:text-zinc-400", children: "Status:" }), _jsx("span", { className: "text-sm font-semibold text-zinc-900 dark:text-zinc-100", children: "Active" })] })] })] })] })) : (_jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsx(SettingsFormField, { id: "keyName", label: "API Key Name", placeholder: "My API Key", value: keyName, onChange: (e) => setKeyName(e.target.value), required: true, theme: theme }), _jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Give your API key a memorable name to identify it later" }), graphs && graphs.length > 0 && (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { theme: theme === null || theme === void 0 ? void 0 : theme.label, htmlFor: "keyScope", children: "Scope" }), _jsxs(Select, { id: "keyScope", theme: theme === null || theme === void 0 ? void 0 : theme.select, value: scopeGraphId, onChange: (e) => setScopeGraphId(e.target.value), children: [_jsx("option", { value: "", children: "Account-wide (all graphs)" }), graphs.map((graph) => (_jsxs("option", { value: graph.graphId, children: [graph.graphName, " (", graph.graphId, ")"] }, graph.graphId)))] }), _jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: scopeGraphId
51
+ ? 'Graph-scoped: works only for this graph (and its subgraphs), and is the only kind of key accepted inside an MCP connector URL.'
52
+ : 'Account-wide: works for every graph via the X-API-Key header and the SDKs. Not accepted inside MCP connector URLs — pick a graph scope for that, or use the Connect page.' })] })), _jsx("div", { className: "bg-primary-50 dark:bg-primary-900/30 rounded-lg p-3", children: _jsxs("p", { className: "text-primary-800 dark:text-primary-300 text-sm", children: [_jsx("strong", { children: "Note:" }), " API keys do not expire by date. Keys that haven't been used for 90 days will be automatically deactivated for security."] }) }), error && (_jsx(StatusAlert, { type: "error", message: error, theme: theme === null || theme === void 0 ? void 0 : theme.alert }))] })) }), _jsx(ModalFooter, { children: newApiKey ? (_jsx(Button, { theme: theme === null || theme === void 0 ? void 0 : theme.button, color: "blue", onClick: handleCloseModal, children: "Done" })) : (_jsxs(_Fragment, { children: [_jsx(Button, { theme: theme === null || theme === void 0 ? void 0 : theme.button, color: "gray", onClick: handleCloseModal, children: "Cancel" }), _jsx(Button, { theme: theme === null || theme === void 0 ? void 0 : theme.button, color: "primary", onClick: handleSubmit, disabled: isCreating || !keyName, children: isCreating ? (_jsx(Spinner, { size: "sm", className: "text-white" })) : ('Create API Key') })] })) })] }));
47
53
  };