@odigos/ui-kit 0.0.72 → 0.0.74

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.0.74](https://github.com/odigos-io/ui-kit/compare/ui-kit-v0.0.73...ui-kit-v0.0.74) (2025-08-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * bump ([#292](https://github.com/odigos-io/ui-kit/issues/292)) ([e8ba38a](https://github.com/odigos-io/ui-kit/commit/e8ba38a15468f1058ec047c0c594367a3113dece))
9
+
10
+ ## [0.0.73](https://github.com/odigos-io/ui-kit/compare/ui-kit-v0.0.72...ui-kit-v0.0.73) (2025-08-06)
11
+
12
+
13
+ ### Features
14
+
15
+ * source-drawer libraries ([#289](https://github.com/odigos-io/ui-kit/issues/289)) ([fd46802](https://github.com/odigos-io/ui-kit/commit/fd468023aeb5a44a322eb28720020946943c47e9))
16
+
3
17
  ## [0.0.72](https://github.com/odigos-io/ui-kit/compare/ui-kit-v0.0.71...ui-kit-v0.0.72) (2025-08-05)
4
18
 
5
19
 
@@ -2,7 +2,7 @@ import { type FC } from 'react';
2
2
  import { type DescribeSource, type Source, type WorkloadId } from '@/types';
3
3
  interface DescribeProps {
4
4
  source: Source;
5
- fetchDescribeSource: (req: {
5
+ fetchSourceDescribe: (req: {
6
6
  variables: WorkloadId;
7
7
  }) => Promise<{
8
8
  data?: {
@@ -1,16 +1,14 @@
1
1
  import { type FC } from 'react';
2
- import { type DescribeSource, type PersistSources, type SourceFormData, type WorkloadId } from '@/types';
2
+ import { type DescribeProps } from './describe';
3
+ import { type LibrariesProps } from './libraries';
4
+ import { type PersistSources, type SourceFormData, type WorkloadId, type Source } from '@/types';
3
5
  interface SourceDrawerProps {
4
6
  persistSources: PersistSources;
5
7
  updateSource: (sourceId: WorkloadId, payload: SourceFormData) => Promise<void>;
6
- fetchDescribeSource: (req: {
7
- variables: WorkloadId;
8
- }) => Promise<{
9
- data?: {
10
- describeSource: DescribeSource;
11
- };
12
- }>;
13
8
  restartWorkloads: (sourceIds: WorkloadId[]) => Promise<void>;
9
+ fetchSourceById: (id: WorkloadId, bypassPaginationLoader?: boolean) => Promise<Source | undefined>;
10
+ fetchSourceDescribe: DescribeProps['fetchSourceDescribe'];
11
+ fetchSourceLibraries: LibrariesProps['fetchSourceLibraries'];
14
12
  }
15
13
  declare const SourceDrawer: FC<SourceDrawerProps>;
16
14
  export { SourceDrawer, type SourceDrawerProps };
@@ -0,0 +1,14 @@
1
+ import { type FC } from 'react';
2
+ import { type InstrumentationInstanceComponent, type Source, type WorkloadId } from '@/types';
3
+ interface LibrariesProps {
4
+ source: Source;
5
+ fetchSourceLibraries: (req: {
6
+ variables: WorkloadId;
7
+ }) => Promise<{
8
+ data?: {
9
+ instrumentationInstanceComponents: InstrumentationInstanceComponent[];
10
+ };
11
+ }>;
12
+ }
13
+ declare const Libraries: FC<LibrariesProps>;
14
+ export { Libraries, type LibrariesProps };
package/lib/containers.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle, useMemo, Fragment, useCallback, Children } from 'react';
2
2
  import styled, { css } from 'styled-components';
3
3
  import { b as DISPLAY_TITLES, T as Theme, U as usePendingStore, V as useNotificationStore, O as useDrawerStore, B as BUTTON_TEXTS, W as useEntityStore, A as ACTION_OPTIONS, g as getActionIcon, z as useModalStore, F as FORM_ALERTS, X as useFilterStore, D as DISPLAY_LANGUAGES, M as MONITORS_OPTIONS, d as getInstrumentationRuleIcon, Y as useDataStreamStore, Z as useInstrumentStore, c as getEntityId, S as STORAGE_KEYS, a as DEFAULT_DATA_STREAM_NAME, _ as useSetupStore, e as getProgrammingLanguageIcon, I as INSTRUMENTATION_RULE_OPTIONS, $ as useSelectedStore, j as ImageErrorIcon, a0 as useDarkMode } from './index-fdf3a268.js';
4
- import { ActionType, ActionKeyTypes, InputTypes, FieldTypes, EntityTypes, StatusType, Crud, OtherStatus, NodeTypes, EdgeTypes, AddNodeTypes, SignalType, HeadersCollectionKeyTypes, CustomInstrumentationsKeyTypes, CodeAttributesKeyTypes, PayloadCollectionKeyTypes, InstrumentationRuleType, K8sResourceKind, MountMethod, AgentEnvVarsInjectionMethod, Profile, InstallationMethod } from './types.js';
4
+ import { ActionType, ActionKeyTypes, InputTypes, FieldTypes, EntityTypes, StatusType, Crud, OtherStatus, NodeTypes, EdgeTypes, AddNodeTypes, SignalType, HeadersCollectionKeyTypes, CustomInstrumentationsKeyTypes, CodeAttributesKeyTypes, PayloadCollectionKeyTypes, InstrumentationRuleType, MountMethod, AgentEnvVarsInjectionMethod, Profile, InstallationMethod } from './types.js';
5
5
  import { e as DataCardFieldTypes, p as FieldLabel, C as Checkbox, o as FieldError, v as Input, x as InputTable, K as KeyValueInputsList, w as InputList, _ as Text, R as Segment, Q as SectionTitle, j as DocsButton, z as MonitorsCheckboxes, $ as TextArea, k as Drawer, c as ConditionDetails, D as DataCard, a5 as FlexColumn, M as Modal, N as NavigationButtons, a9 as ModalBody, L as NotificationNote, A as AutocompleteInput, i as Divider, W as Status, a4 as FlexRow, a1 as Tooltip, s as IconWrapped, G as MonitorsIcons, aa as TableContainer, ab as TableTitleWrap, r as IconTitleBadge, ac as TableWrap, y as InteractiveTable, a6 as CenterThis, J as NoDataFound, a2 as TraceLoader, a as Badge, E as ExtendArrow, a7 as VerticalScroll, U as SelectionButton, B as Button, n as Dropdown, ad as nodeConfig, ae as useNodesState, af as useEdgesState, ag as Flow, ah as applyNodeChanges, P as Popup, a0 as Toggle, I as IconButton, ai as AddButton, F as FadeLoader, g as DataTab, X as Stepper, d as DataCardFields, Z as Tag, aj as MarkerType, t as IconsNav, ak as CopyText, h as DescribeRow, al as PodContainer, am as SourceContainer, q as IconGroup, O as PopupForm } from './index-a1b59a38.js';
6
6
  import { i as isEmpty, s as safeJsonParse, d as deepClone } from './index-5e5f7bda.js';
7
7
  import { C as CheckCircledIcon, O as OdigosLogo } from './index-1d5c06a0.js';
@@ -4193,26 +4193,59 @@ const SlackInvite = ({}) => {
4193
4193
  React.createElement(SlackLogo, null)));
4194
4194
  };
4195
4195
 
4196
- const Describe$1 = ({ source, fetchDescribeSource }) => {
4196
+ const buildCard = (source) => {
4197
+ const { name, kind, namespace } = source;
4198
+ const arr = [
4199
+ { title: DISPLAY_TITLES.NAMESPACE, value: namespace },
4200
+ { title: DISPLAY_TITLES.KIND, value: kind },
4201
+ { title: DISPLAY_TITLES.NAME, value: name, tooltip: 'K8s resource name' },
4202
+ { type: DataCardFieldTypes.Divider },
4203
+ {
4204
+ type: DataCardFieldTypes.CopyText,
4205
+ value: `kubectl get ${kind} ${name} -n ${namespace}`.toLowerCase(),
4206
+ },
4207
+ ];
4208
+ return arr;
4209
+ };
4210
+
4211
+ const Container$2 = styled.div `
4212
+ display: flex;
4213
+ flex-direction: column;
4214
+ gap: 24px;
4215
+ padding: 4px;
4216
+ `;
4217
+ const SourceForm = ({ formData, handleFormChange }) => {
4218
+ return (React.createElement(Container$2, null,
4219
+ React.createElement(Input, { name: 'sourceName', title: 'Source name', tooltip: 'This overrides the default service name that runs in your cluster.', placeholder: 'Use a name that overrides the source name', value: formData.otelServiceName, onChange: ({ target: { value } }) => handleFormChange('otelServiceName', value) })));
4220
+ };
4221
+
4222
+ const Describe$1 = ({ source, fetchSourceDescribe }) => {
4223
+ const [failed, setFailed] = useState(false);
4197
4224
  const [describe, setDescribe] = useState(null);
4198
4225
  useEffect(() => {
4199
4226
  if (!source)
4200
4227
  return;
4201
- fetchDescribeSource({
4228
+ fetchSourceDescribe({
4202
4229
  variables: {
4203
4230
  namespace: source.namespace,
4204
4231
  name: source.name,
4205
4232
  kind: source.kind,
4206
4233
  },
4207
4234
  }).then(({ data }) => {
4208
- setDescribe(data?.describeSource || null);
4235
+ if (data?.describeSource) {
4236
+ setDescribe(data.describeSource);
4237
+ }
4238
+ else {
4239
+ setFailed(true);
4240
+ }
4209
4241
  });
4210
- }, [fetchDescribeSource, source]);
4211
- if (!describe) {
4242
+ }, [fetchSourceDescribe, source]);
4243
+ if (!describe && !failed) {
4212
4244
  return (React.createElement(CenterThis, null,
4213
4245
  React.createElement(FadeLoader, null)));
4214
4246
  }
4215
- return (React.createElement(FlexColumn, { "$gap": 12 }, !describe.pods?.length ? (React.createElement(CenterThis, null,
4247
+ return (React.createElement(FlexColumn, { "$gap": 12 }, failed ? (React.createElement(CenterThis, null,
4248
+ React.createElement(NoDataFound, { subTitle: 'Could not fetch describe for this source' }))) : !describe?.pods?.length ? (React.createElement(CenterThis, null,
4216
4249
  React.createElement(NoDataFound, { subTitle: 'Check if you have any running pods and try again' }))) : (describe.pods.map(({ podName, nodeName, phase, agentInjected, runningLatestWorkloadRevision, containers }) => {
4217
4250
  const podHasErrors = phase.status !== StatusType.Success || hasUnhealthyInstances(containers);
4218
4251
  const podStatus = podHasErrors ? StatusType.Error : StatusType.Success;
@@ -4248,36 +4281,63 @@ const Describe$1 = ({ source, fetchDescribeSource }) => {
4248
4281
  }))));
4249
4282
  };
4250
4283
 
4251
- const buildCard = (source) => {
4252
- const { name, kind, namespace } = source;
4253
- const arr = [
4254
- { title: DISPLAY_TITLES.NAMESPACE, value: namespace },
4255
- { title: DISPLAY_TITLES.KIND, value: kind },
4256
- { title: DISPLAY_TITLES.NAME, value: name, tooltip: 'K8s resource name' },
4257
- { type: DataCardFieldTypes.Divider },
4258
- {
4259
- type: DataCardFieldTypes.CopyText,
4260
- value: `kubectl get ${kind} ${name} -n ${namespace}`.toLowerCase(),
4261
- },
4262
- ];
4263
- return arr;
4264
- };
4265
-
4266
- const Container$2 = styled.div `
4267
- display: flex;
4268
- flex-direction: column;
4269
- gap: 24px;
4270
- padding: 4px;
4284
+ const Row = styled(FlexRow) `
4285
+ width: 100%;
4286
+ align-items: center;
4287
+ justify-content: space-between;
4271
4288
  `;
4272
- const SourceForm = ({ formData, handleFormChange }) => {
4273
- return (React.createElement(Container$2, null,
4274
- React.createElement(Input, { name: 'sourceName', title: 'Source name', tooltip: 'This overrides the default service name that runs in your cluster.', placeholder: 'Use a name that overrides the source name', value: formData.otelServiceName, onChange: ({ target: { value } }) => handleFormChange('otelServiceName', value) })));
4289
+ const Libraries = ({ source, fetchSourceLibraries }) => {
4290
+ const theme = Theme.useTheme();
4291
+ const [failed, setFailed] = useState(false);
4292
+ const [libraries, setLibraries] = useState(null);
4293
+ useEffect(() => {
4294
+ if (!source)
4295
+ return;
4296
+ fetchSourceLibraries({
4297
+ variables: {
4298
+ namespace: source.namespace,
4299
+ name: source.name,
4300
+ kind: source.kind,
4301
+ },
4302
+ }).then(({ data }) => {
4303
+ if (data?.instrumentationInstanceComponents) {
4304
+ setLibraries(data.instrumentationInstanceComponents.sort((a, b) => a.name.localeCompare(b.name)));
4305
+ }
4306
+ else {
4307
+ setFailed(true);
4308
+ }
4309
+ });
4310
+ }, [fetchSourceLibraries, source]);
4311
+ if (!libraries && !failed) {
4312
+ return (React.createElement(CenterThis, null,
4313
+ React.createElement(FadeLoader, null)));
4314
+ }
4315
+ return (React.createElement(FlexColumn, { "$gap": 12 }, failed ? (React.createElement(CenterThis, null,
4316
+ React.createElement(NoDataFound, { subTitle: 'Could not fetch libraries for this source' }))) : !libraries?.length ? (React.createElement(CenterThis, { "$gap": 12 },
4317
+ React.createElement(NoDataFound, { title: 'No libraries found', subTitle: '' }),
4318
+ React.createElement(NotificationNote, { type: StatusType.Warning, message: 'This feature is in early development, and has very limited support' }))) : (React.createElement(DataCard, { title: 'Instrumented Libraries' }, libraries.map(({ name, nonIdentifyingAttributes }, i) => (React.createElement(Fragment, { key: `library-${name}` },
4319
+ React.createElement(Row, null,
4320
+ React.createElement(Text, { size: 12, color: theme.text.grey }, name),
4321
+ React.createElement(FlexRow, { "$gap": 4 }, nonIdentifyingAttributes.map(({ key, value }) => {
4322
+ if (isStringABoolean(value) && parseBooleanFromString(value)) {
4323
+ switch (key) {
4324
+ case 'is_standard_lib':
4325
+ return React.createElement(Status, { key: `${name}-${key}`, status: StatusType.Default, title: 'STANDARD', withBorder: true });
4326
+ default:
4327
+ console.warn(`Unhandled non-identifying attribute: ${key}`);
4328
+ return null;
4329
+ }
4330
+ }
4331
+ return null;
4332
+ }))),
4333
+ i !== libraries.length - 1 && React.createElement(Divider, { length: '100%', margin: '0' }))))))));
4275
4334
  };
4276
4335
 
4277
4336
  var Tabs;
4278
4337
  (function (Tabs) {
4279
4338
  Tabs["Overview"] = "Overview";
4280
4339
  Tabs["Pods"] = "Pods";
4340
+ Tabs["Libraries"] = "Libraries";
4281
4341
  })(Tabs || (Tabs = {}));
4282
4342
  const FormContainer = styled.div `
4283
4343
  width: 100%;
@@ -4291,7 +4351,7 @@ const DataContainer$2 = styled.div `
4291
4351
  flex-direction: column;
4292
4352
  gap: 12px;
4293
4353
  `;
4294
- const SourceDrawer = ({ persistSources, updateSource, fetchDescribeSource, restartWorkloads }) => {
4354
+ const SourceDrawer = ({ persistSources, updateSource, restartWorkloads, fetchSourceById, fetchSourceDescribe, fetchSourceLibraries }) => {
4295
4355
  const { sources } = useEntityStore();
4296
4356
  const { selectedStreamName } = useDataStreamStore();
4297
4357
  const { drawerType, drawerEntityId } = useDrawerStore();
@@ -4301,15 +4361,23 @@ const SourceDrawer = ({ persistSources, updateSource, fetchDescribeSource, resta
4301
4361
  const [isEditing, setIsEditing] = useState(false);
4302
4362
  const [isFormDirty, setIsFormDirty] = useState(false);
4303
4363
  const [selectedTab, setSelectedTab] = useState(Tabs.Overview);
4364
+ const [fetchedSource, setFetchedSource] = useState(null);
4365
+ // this is used to fetch the source on drawer open, so we ensure we have the latest data
4366
+ useEffect(() => {
4367
+ if (!drawerEntityId)
4368
+ return;
4369
+ fetchSourceById(drawerEntityId).then((source) => setFetchedSource(source || null));
4370
+ }, [drawerEntityId]);
4304
4371
  const { formData, handleFormChange, resetFormData, loadFormWithDrawerItem } = useSourceFormData();
4305
4372
  const thisItem = useMemo(() => {
4306
4373
  if (isOpen)
4307
4374
  return null;
4308
- const found = sourcesByStream?.find((x) => x.namespace === drawerEntityId.namespace && x.name === drawerEntityId.name && x.kind === drawerEntityId.kind);
4375
+ const found = fetchedSource ||
4376
+ sourcesByStream?.find((x) => x.namespace === drawerEntityId.namespace && x.name === drawerEntityId.name && x.kind === drawerEntityId.kind);
4309
4377
  if (!!found)
4310
4378
  loadFormWithDrawerItem(found);
4311
4379
  return found;
4312
- }, [isOpen, drawerEntityId, sourcesByStream]);
4380
+ }, [isOpen, drawerEntityId, sourcesByStream, fetchedSource]);
4313
4381
  const containersData = useMemo(() => {
4314
4382
  const runtimeCondition = thisItem?.conditions?.find(({ type }) => type === 'RuntimeDetection');
4315
4383
  return {
@@ -4318,23 +4386,6 @@ const SourceDrawer = ({ persistSources, updateSource, fetchDescribeSource, resta
4318
4386
  containers: thisItem?.containers || [],
4319
4387
  };
4320
4388
  }, [thisItem]);
4321
- const tabs = useMemo(() => {
4322
- const arr = [
4323
- {
4324
- label: Tabs.Overview,
4325
- onClick: () => setSelectedTab(Tabs.Overview),
4326
- selected: selectedTab === Tabs.Overview,
4327
- },
4328
- ];
4329
- if (thisItem?.kind !== K8sResourceKind.CronJob) {
4330
- arr.push({
4331
- label: Tabs.Pods,
4332
- onClick: () => setSelectedTab(Tabs.Pods),
4333
- selected: selectedTab === Tabs.Pods,
4334
- });
4335
- }
4336
- return arr;
4337
- }, [thisItem?.kind, selectedTab]);
4338
4389
  if (!thisItem)
4339
4390
  return null;
4340
4391
  const handleEdit = (bool) => {
@@ -4361,7 +4412,23 @@ const SourceDrawer = ({ persistSources, updateSource, fetchDescribeSource, resta
4361
4412
  setIsFormDirty(false);
4362
4413
  setIsEditing(false);
4363
4414
  };
4364
- return (React.createElement(OverviewDrawer, { ref: drawerRef, title: thisItem.otelServiceName || thisItem.name, titleTooltip: 'This attribute is used to identify the name of the service (service.name) that is generating telemetry data.', hideEditTitleFromEdit: true, icons: getContainersIcons(thisItem.containers), isEdit: isEditing, isFormDirty: isFormDirty, onEdit: selectedTab === Tabs.Overview ? handleEdit : undefined, onSave: handleSave, onDelete: selectedTab === Tabs.Overview ? handleDelete : undefined, onCancel: handleCancel, isLastItem: sourcesByStream.length === 1, tabs: tabs, headerActionButtons: [
4415
+ return (React.createElement(OverviewDrawer, { ref: drawerRef, title: thisItem.otelServiceName || thisItem.name, titleTooltip: 'This attribute is used to identify the name of the service (service.name) that is generating telemetry data.', hideEditTitleFromEdit: true, icons: getContainersIcons(thisItem.containers), isEdit: isEditing, isFormDirty: isFormDirty, onEdit: selectedTab === Tabs.Overview ? handleEdit : undefined, onSave: handleSave, onDelete: selectedTab === Tabs.Overview ? handleDelete : undefined, onCancel: handleCancel, isLastItem: sourcesByStream.length === 1, tabs: [
4416
+ {
4417
+ label: Tabs.Overview,
4418
+ onClick: () => setSelectedTab(Tabs.Overview),
4419
+ selected: selectedTab === Tabs.Overview,
4420
+ },
4421
+ {
4422
+ label: Tabs.Pods,
4423
+ onClick: () => setSelectedTab(Tabs.Pods),
4424
+ selected: selectedTab === Tabs.Pods,
4425
+ },
4426
+ {
4427
+ label: Tabs.Libraries,
4428
+ onClick: () => setSelectedTab(Tabs.Libraries),
4429
+ selected: selectedTab === Tabs.Libraries,
4430
+ },
4431
+ ], headerActionButtons: [
4365
4432
  {
4366
4433
  'data-id': 'rollout-restart',
4367
4434
  variant: 'tertiary',
@@ -4378,7 +4445,7 @@ const SourceDrawer = ({ persistSources, updateSource, fetchDescribeSource, resta
4378
4445
  } }))) : (React.createElement(DataContainer$2, null,
4379
4446
  React.createElement(ConditionDetails, { conditions: thisItem.conditions || [] }),
4380
4447
  React.createElement(DataCard, { title: DISPLAY_TITLES.SOURCE_DETAILS, data: !!thisItem ? buildCard(thisItem) : [] }),
4381
- React.createElement(DataCard, { title: DISPLAY_TITLES.DETECTED_CONTAINERS, titleBadge: containersData.isLoading ? OtherStatus.Loading : containersData.containers.length, description: containersData.description || DISPLAY_TITLES.DETECTED_CONTAINERS_DESCRIPTION }, containersData.containers.map((container) => (React.createElement(SourceContainer, { key: `source-container-${container.containerName}`, ...container, callbackRuntimeOverride: async (payload) => await updateSource(drawerEntityId, payload) }))))))) : (React.createElement(Describe$1, { source: thisItem, fetchDescribeSource: fetchDescribeSource }))));
4448
+ React.createElement(DataCard, { title: DISPLAY_TITLES.DETECTED_CONTAINERS, titleBadge: containersData.isLoading ? OtherStatus.Loading : containersData.containers.length, description: containersData.description || DISPLAY_TITLES.DETECTED_CONTAINERS_DESCRIPTION }, containersData.containers.map((container) => (React.createElement(SourceContainer, { key: `source-container-${container.containerName}`, ...container, callbackRuntimeOverride: async (payload) => await updateSource(drawerEntityId, payload) }))))))) : selectedTab === Tabs.Pods ? (React.createElement(Describe$1, { source: thisItem, fetchSourceDescribe: fetchSourceDescribe })) : (React.createElement(Libraries, { source: thisItem, fetchSourceLibraries: fetchSourceLibraries }))));
4382
4449
  };
4383
4450
 
4384
4451
  const SearchWrapper = styled.div `
@@ -2,6 +2,7 @@ export * from './actions';
2
2
  export * from './config';
3
3
  export * from './describe';
4
4
  export * from './destinations';
5
+ export * from './instrumentation-instances';
5
6
  export * from './instrumentation-rules';
6
7
  export * from './namespaces';
7
8
  export * from './service-map';
@@ -0,0 +1,2 @@
1
+ import type { InstrumentationInstanceComponent } from '@/types';
2
+ export declare const MOCK_INSTRUMENTATION_INSTANCE_COMPONENTS: InstrumentationInstanceComponent[];
@@ -6,6 +6,7 @@ export * from './data-flow';
6
6
  export * from './data-streams';
7
7
  export * from './describe';
8
8
  export * from './destinations';
9
+ export * from './instrumentation-instances';
9
10
  export * from './instrumentation-rules';
10
11
  export * from './metrics';
11
12
  export * from './namespaces';
@@ -0,0 +1,7 @@
1
+ export interface InstrumentationInstanceComponent {
2
+ name: string;
3
+ nonIdentifyingAttributes: {
4
+ key: string;
5
+ value: string;
6
+ }[];
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@odigos/ui-kit",
3
- "version": "0.0.72",
3
+ "version": "0.0.74",
4
4
  "author": "Odigos",
5
5
  "repository": {
6
6
  "type": "git",