@sequenceholdings/agent-spec 0.1.3 → 0.1.4

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 CHANGED
@@ -68,6 +68,45 @@ with no such identity, such as a Lattice step without
68
68
  `run_as.service_account`, gets no function tools and records each function as
69
69
  unavailable (`no_user_identity`).
70
70
 
71
+ ## Author client proposals (preview)
72
+
73
+ The upcoming `0.1.4` release adds optional `clientProposals` to text agent
74
+ definitions. Each literal descriptor supplies its exact tool name, description
75
+ and a bounded JSON input schema:
76
+
77
+ ```ts
78
+ clientProposals: [{
79
+ name: 'propose_label',
80
+ description: 'Propose a support label for the user to review.',
81
+ inputSchema: {
82
+ type: 'object',
83
+ properties: { label: { type: 'string', enum: ['ready', 'needs-review'] } },
84
+ required: ['label'],
85
+ additionalProperties: false,
86
+ },
87
+ }],
88
+ ```
89
+
90
+ `clientProposalSchema`, `clientProposalsSchema` and the `ClientProposal` type
91
+ are root exports. Definitions accept at most 64 uniquely named proposals,
92
+ 32,000 characters per description, 128 KiB per input schema and 512 KiB for all
93
+ descriptors. Schemas are limited to depth 32 and 10,000 nodes. They use a finite
94
+ JSON Schema subset: executable values and unsupported keywords such as
95
+ `pattern`, `format`, `default` and `$ref` are rejected. Schema URL annotations
96
+ are never fetched. Keep descriptors as authored literals so their exact
97
+ instructions and schemas enter the captured execution definition. Omitting
98
+ the field preserves existing definition hashes.
99
+
100
+ The matching Vercel-framework runtime validates arguments, bounded to 256 KiB,
101
+ depth 64, 50,000 nodes and 100,000 validation work units, then returns
102
+ `{ status: 'pending_client_review', applied: false }`. This acknowledgement
103
+ proves neither a backend apply nor client delivery. It does not execute a
104
+ provider call or authorize a mutation.
105
+ The application owns proposal rendering, validation, human review and any
106
+ separate authorized write. Proposal names that collide with another resolved
107
+ tool are rejected. Confirm package publication and matching runtime support
108
+ before relying on this preview contract.
109
+
71
110
  ## Exports
72
111
 
73
112
  - `@sequenceholdings/agent-spec` provides `defineAgent`,
@@ -78,6 +117,26 @@ unavailable (`no_user_identity`).
78
117
  - `@sequenceholdings/agent-spec/yaml` provides `parseYamlAgent` only for
79
118
  migrating legacy YAML definitions.
80
119
 
120
+ ## Job request fingerprints (preview)
121
+
122
+ The upcoming `0.1.4` release adds `hashJobCreateRequest` at the package root.
123
+ Confirm registry publication before installing that version. This pure helper
124
+ hashes the job kind, resource ID, expected source identity and exact parsed
125
+ payload using the platform's versioned request format. It sorts object keys
126
+ while preserving array order and string bytes. `normalizeJobRequestJson`
127
+ normalizes dates to ISO strings and omits undefined object fields; non-JSON,
128
+ cyclic, excessively nested or oversized input is refused with
129
+ `JobRequestHashError` (`status` 400 or 413). The serialized input is bounded
130
+ to 16 MiB.
131
+
132
+ The fingerprint excludes transport credentials and caller identity. It is not
133
+ an authorization grant, an admission receipt, proof that a job ran, or an
134
+ application's business-source association. An application comparing an
135
+ authorized admission receipt must still verify the actual caller, current
136
+ protected intent, expected source identity and recorded job separately. Use
137
+ the exact parsed request payload, not a display projection or reconstructed
138
+ subset.
139
+
81
140
  ## Voice definitions (preview)
82
141
 
83
142
  Choose either `text` or `voice` when creating an Agent. Definitions without `mode` retain their existing text defaults, source format, and bundle hashes. `validateAgentModeUpdate` rejects changing an existing Agent's mode in either direction. Voice uses the same identity, prompt, model, native Skill references, permissions, source compiler, and bundle hashing as text; its supported authoring fields are deliberately smaller.
@@ -59,6 +59,11 @@ export declare const textAgentDefinitionSchema: z.ZodObject<{
59
59
  temperature: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
60
60
  systemPrompt: z.ZodOptional<z.ZodString>;
61
61
  tools: z.ZodDefault<z.ZodArray<z.ZodString>>;
62
+ clientProposals: z.ZodOptional<z.ZodArray<z.ZodObject<{
63
+ name: z.ZodString;
64
+ description: z.ZodString;
65
+ inputSchema: z.ZodPipe<z.ZodUnknown, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
66
+ }, z.core.$strict>>>;
62
67
  mcpServers: z.ZodDefault<z.ZodArray<z.ZodString>>;
63
68
  requiredMcpServers: z.ZodDefault<z.ZodArray<z.ZodString>>;
64
69
  requiredReferences: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -338,6 +343,11 @@ export declare const agentDefinitionSchema: z.ZodDiscriminatedUnion<[z.ZodObject
338
343
  temperature: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
339
344
  systemPrompt: z.ZodOptional<z.ZodString>;
340
345
  tools: z.ZodDefault<z.ZodArray<z.ZodString>>;
346
+ clientProposals: z.ZodOptional<z.ZodArray<z.ZodObject<{
347
+ name: z.ZodString;
348
+ description: z.ZodString;
349
+ inputSchema: z.ZodPipe<z.ZodUnknown, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
350
+ }, z.core.$strict>>>;
341
351
  mcpServers: z.ZodDefault<z.ZodArray<z.ZodString>>;
342
352
  requiredMcpServers: z.ZodDefault<z.ZodArray<z.ZodString>>;
343
353
  requiredReferences: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -3,6 +3,7 @@ import { z } from 'zod';
3
3
  import { inputSchemaSchema } from './input-schema.js';
4
4
  import { canonicalJson, sortJson } from './canonical-json.js';
5
5
  import { voiceDeclarationSchema } from './voice-authoring.js';
6
+ import { clientProposalsSchema } from './client-proposals.js';
6
7
  export { canonicalJson } from './canonical-json.js';
7
8
  export { voiceDeclarationSchema } from './voice-authoring.js';
8
9
  const OBJECT_ID_PATTERN = /^[0-9a-f]{24}$/;
@@ -118,6 +119,7 @@ const textDefinitionBaseSchema = z
118
119
  temperature: z.number().min(0).max(1).nullish(),
119
120
  systemPrompt: z.string().optional(),
120
121
  tools: z.array(z.string()).default([]),
122
+ clientProposals: clientProposalsSchema.optional(),
121
123
  mcpServers: z.array(z.string()).default([]),
122
124
  requiredMcpServers: z.array(z.string()).default([]),
123
125
  requiredReferences: z.array(agentReferenceSchema).optional(),
@@ -146,6 +148,14 @@ export const textAgentDefinitionSchema = textDefinitionBaseSchema.safeExtend({
146
148
  mode: z.literal('text').optional(),
147
149
  voice: z.never().optional(),
148
150
  }).superRefine((definition, context) => {
151
+ if (definition.framework === 'claude-agent' && (definition.clientProposals?.length ?? 0) > 0) {
152
+ context.addIssue({ code: 'custom', path: ['clientProposals'], message: 'Client proposals require the Vercel tool runtime' });
153
+ }
154
+ for (const [index, proposal] of (definition.clientProposals ?? []).entries()) {
155
+ if (definition.tools.includes(proposal.name)) {
156
+ context.addIssue({ code: 'custom', path: ['clientProposals', index, 'name'], message: 'Client proposal name collides with a selected built-in tool' });
157
+ }
158
+ }
149
159
  for (const [index, serverId] of definition.requiredMcpServers.entries()) {
150
160
  if (definition.mcpServers.includes(serverId))
151
161
  continue;
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export declare const CLIENT_PROPOSAL_LIMITS: {
3
+ readonly count: 64;
4
+ readonly descriptionLength: 32000;
5
+ readonly schemaBytes: number;
6
+ readonly totalBytes: number;
7
+ readonly schemaDepth: 32;
8
+ readonly schemaNodes: 10000;
9
+ };
10
+ export declare const clientProposalSchema: z.ZodObject<{
11
+ name: z.ZodString;
12
+ description: z.ZodString;
13
+ inputSchema: z.ZodPipe<z.ZodUnknown, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
14
+ }, z.core.$strict>;
15
+ export type ClientProposal = z.infer<typeof clientProposalSchema>;
16
+ export declare const clientProposalsSchema: z.ZodArray<z.ZodObject<{
17
+ name: z.ZodString;
18
+ description: z.ZodString;
19
+ inputSchema: z.ZodPipe<z.ZodUnknown, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
20
+ }, z.core.$strict>>;
@@ -0,0 +1,172 @@
1
+ import { z } from 'zod';
2
+ export const CLIENT_PROPOSAL_LIMITS = {
3
+ count: 64, descriptionLength: 32_000, schemaBytes: 128 * 1024,
4
+ totalBytes: 512 * 1024, schemaDepth: 32, schemaNodes: 10_000,
5
+ };
6
+ const TYPES = new Set(['object', 'array', 'string', 'number', 'integer', 'boolean', 'null']);
7
+ const RESERVED_NAMES = new Set(['__proto__', 'constructor', 'prototype']);
8
+ const ANNOTATIONS = new Set(['$schema', 'title', 'description']);
9
+ const NUMBER_KEYS = new Set(['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum']);
10
+ const SIZE_KEYS = new Set(['minLength', 'maxLength', 'minItems', 'maxItems']);
11
+ const CHILD_KEYS = new Set(['items', 'additionalProperties', 'propertyNames']);
12
+ const UNION_KEYS = new Set(['anyOf', 'oneOf', 'allOf']);
13
+ const KEYWORDS = new Set([...ANNOTATIONS, ...NUMBER_KEYS, ...SIZE_KEYS, ...CHILD_KEYS, ...UNION_KEYS,
14
+ 'type', 'properties', 'required', 'enum', 'const']);
15
+ function isObject(value) {
16
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
17
+ }
18
+ function requireBoundedJson(value) {
19
+ const seen = new Set();
20
+ const pending = [{ value, depth: 0 }];
21
+ let nodes = 0;
22
+ while (pending.length > 0) {
23
+ const item = pending.pop();
24
+ if (!item)
25
+ break;
26
+ if (++nodes > CLIENT_PROPOSAL_LIMITS.schemaNodes || item.depth > CLIENT_PROPOSAL_LIMITS.schemaDepth) {
27
+ throw new Error('Client proposal schema exceeds its depth or node limit');
28
+ }
29
+ const current = item.value;
30
+ if (typeof current === 'string' && Buffer.byteLength(current) > CLIENT_PROPOSAL_LIMITS.schemaBytes) {
31
+ throw new Error('Client proposal schema exceeds its byte limit');
32
+ }
33
+ if (current === null || typeof current === 'string' || typeof current === 'boolean')
34
+ continue;
35
+ if (typeof current === 'number' && Number.isFinite(current))
36
+ continue;
37
+ if (typeof current !== 'object' || current === null)
38
+ throw new Error('Client proposal schema must be literal JSON');
39
+ requireLiteralContainer({ current, seen, pending, depth: item.depth });
40
+ }
41
+ if (Buffer.byteLength(JSON.stringify(value)) > CLIENT_PROPOSAL_LIMITS.schemaBytes) {
42
+ throw new Error('Client proposal schema exceeds its byte limit');
43
+ }
44
+ }
45
+ function requireLiteralContainer({ current, seen, pending, depth }) {
46
+ const prototype = Object.getPrototypeOf(current);
47
+ if ((!Array.isArray(current) && prototype !== Object.prototype && prototype !== null) || seen.has(current)) {
48
+ throw new Error('Client proposal schema must be acyclic literal JSON');
49
+ }
50
+ seen.add(current);
51
+ const keys = Reflect.ownKeys(current);
52
+ if (Array.isArray(current))
53
+ requireLiteralArray({ value: current, keys });
54
+ for (const key of keys) {
55
+ if (Array.isArray(current) && key === 'length')
56
+ continue;
57
+ if (typeof key !== 'string' || RESERVED_NAMES.has(key))
58
+ throw new Error('Client proposal schema contains an unsupported key');
59
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
60
+ if (!descriptor || !('value' in descriptor) || !descriptor.enumerable)
61
+ throw new Error('Client proposal schema must contain JSON values');
62
+ pending.push({ value: descriptor.value, depth: depth + 1 });
63
+ }
64
+ }
65
+ function requireLiteralArray({ value, keys }) {
66
+ if (Object.getPrototypeOf(value) !== Array.prototype || keys.length !== value.length + 1) {
67
+ throw new Error('Client proposal schema arrays must be dense literal JSON');
68
+ }
69
+ for (let index = 0; index < value.length; index++) {
70
+ if (!Object.hasOwn(value, String(index)))
71
+ throw new Error('Client proposal schema arrays must be dense literal JSON');
72
+ }
73
+ }
74
+ function requireType(value) {
75
+ const types = Array.isArray(value) ? value : [value];
76
+ if (types.length === 0 || types.length > TYPES.size || types.some(type => typeof type !== 'string' || !TYPES.has(type))
77
+ || new Set(types).size !== types.length)
78
+ throw new Error('Client proposal schema has an invalid type');
79
+ }
80
+ function requireProperties(value) {
81
+ if (!isObject(value) || Object.keys(value).length > 512)
82
+ throw new Error('Client proposal properties must be a bounded object');
83
+ for (const child of Object.values(value))
84
+ requireSchema(child);
85
+ }
86
+ function requireRequired({ value, properties }) {
87
+ if (!Array.isArray(value) || value.some(name => typeof name !== 'string') || new Set(value).size !== value.length) {
88
+ throw new Error('Client proposal required must contain unique property names');
89
+ }
90
+ if (value.some(name => !isObject(properties) || !Object.hasOwn(properties, name))) {
91
+ throw new Error('Client proposal required must name declared properties');
92
+ }
93
+ }
94
+ function requireBound({ key, value }) {
95
+ if (typeof value !== 'number' || !Number.isFinite(value))
96
+ throw new Error(`Client proposal ${key} must be a finite number`);
97
+ if (SIZE_KEYS.has(key) && (!Number.isSafeInteger(value) || value < 0)) {
98
+ throw new Error(`Client proposal ${key} must be a nonnegative integer`);
99
+ }
100
+ }
101
+ function requireEntry({ key, entry, schema }) {
102
+ if (!KEYWORDS.has(key))
103
+ throw new Error(`Unsupported client proposal schema keyword: ${key}`);
104
+ if (ANNOTATIONS.has(key)) {
105
+ if (typeof entry !== 'string')
106
+ throw new Error(`Client proposal ${key} must be a string`);
107
+ }
108
+ else if (NUMBER_KEYS.has(key) || SIZE_KEYS.has(key))
109
+ requireBound({ key, value: entry });
110
+ else if (key === 'type')
111
+ requireType(entry);
112
+ else if (CHILD_KEYS.has(key))
113
+ requireSchema(entry);
114
+ else if (UNION_KEYS.has(key)) {
115
+ if (!Array.isArray(entry) || entry.length === 0 || entry.length > 32)
116
+ throw new Error(`Client proposal ${key} requires 1–32 schemas`);
117
+ for (const child of entry)
118
+ requireSchema(child);
119
+ }
120
+ else if (key === 'properties')
121
+ requireProperties(entry);
122
+ else if (key === 'required')
123
+ requireRequired({ value: entry, properties: schema.properties });
124
+ else if (key === 'enum') {
125
+ if (!Array.isArray(entry) || entry.length === 0 || entry.length > 512)
126
+ throw new Error('Client proposal enum must be a bounded nonempty array');
127
+ }
128
+ }
129
+ function requireSchema(value) {
130
+ if (typeof value === 'boolean')
131
+ return;
132
+ if (!isObject(value))
133
+ throw new Error('Client proposal schema nodes must be objects or booleans');
134
+ for (const [key, entry] of Object.entries(value))
135
+ requireEntry({ key, entry, schema: value });
136
+ }
137
+ const literalJsonSchema = z.unknown().superRefine((value, context) => {
138
+ try {
139
+ requireBoundedJson(value);
140
+ }
141
+ catch (error) {
142
+ context.addIssue({ code: 'custom', message: error instanceof Error ? error.message : 'Invalid client proposal JSON' });
143
+ }
144
+ });
145
+ const proposalInputSchema = literalJsonSchema.pipe(z.record(z.string(), z.unknown())).superRefine((value, context) => {
146
+ try {
147
+ if (value.type !== 'object')
148
+ throw new Error('Client proposal root schema type must be object');
149
+ requireSchema(value);
150
+ }
151
+ catch (error) {
152
+ context.addIssue({ code: 'custom', message: error instanceof Error ? error.message : 'Invalid client proposal schema' });
153
+ }
154
+ });
155
+ export const clientProposalSchema = z.strictObject({
156
+ name: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}$/).refine(name => !RESERVED_NAMES.has(name), 'Reserved client proposal name'),
157
+ description: z.string().min(1).max(CLIENT_PROPOSAL_LIMITS.descriptionLength),
158
+ inputSchema: proposalInputSchema,
159
+ });
160
+ export const clientProposalsSchema = z.array(clientProposalSchema).max(CLIENT_PROPOSAL_LIMITS.count).superRefine((value, context) => {
161
+ if (new Set(value.map(proposal => proposal.name)).size !== value.length) {
162
+ context.addIssue({ code: 'custom', message: 'Client proposal names must be unique' });
163
+ }
164
+ try {
165
+ if (Buffer.byteLength(JSON.stringify(value)) > CLIENT_PROPOSAL_LIMITS.totalBytes) {
166
+ context.addIssue({ code: 'custom', message: 'Client proposal descriptors exceed their total byte limit' });
167
+ }
168
+ }
169
+ catch {
170
+ context.addIssue({ code: 'custom', message: 'Client proposal descriptors must be literal JSON' });
171
+ }
172
+ });
package/dist/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from './agent-definition.js';
2
2
  export * from './voice.js';
3
+ export * from './job-request-hash.js';
4
+ export * from './client-proposals.js';
package/dist/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from './agent-definition.js';
2
2
  export * from './voice.js';
3
+ export * from './job-request-hash.js';
4
+ export * from './client-proposals.js';
@@ -0,0 +1,27 @@
1
+ import { z } from 'zod';
2
+ export declare const JobCreateKindSchema: z.ZodEnum<{
3
+ agent: "agent";
4
+ workflow: "workflow";
5
+ }>;
6
+ export type JobCreateKind = z.infer<typeof JobCreateKindSchema>;
7
+ export type JobRequestJsonValue = null | boolean | number | string | JobRequestJsonValue[] | {
8
+ [key: string]: JobRequestJsonValue;
9
+ };
10
+ export declare const JobRequestExecutionIdentitySchema: z.ZodObject<{
11
+ id: z.ZodString;
12
+ version: z.ZodString;
13
+ definitionSha256: z.ZodString;
14
+ }, z.core.$strict>;
15
+ export type JobRequestExecutionIdentity = z.infer<typeof JobRequestExecutionIdentitySchema>;
16
+ export interface JobCreateRequestFingerprintInput {
17
+ kind: JobCreateKind;
18
+ resourceId: string;
19
+ expectedIdentity: JobRequestExecutionIdentity;
20
+ payload: unknown;
21
+ }
22
+ export declare class JobRequestHashError extends Error {
23
+ readonly status: 400 | 413;
24
+ constructor(status: 400 | 413, message: string);
25
+ }
26
+ export declare function normalizeJobRequestJson(value: unknown): JobRequestJsonValue;
27
+ export declare function hashJobCreateRequest({ kind, resourceId, expectedIdentity, payload }: JobCreateRequestFingerprintInput): string;
@@ -0,0 +1,54 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { z } from 'zod';
3
+ import { canonicalJson } from './canonical-json.js';
4
+ const MAX_JSON_BYTES = 16 * 1024 * 1024;
5
+ export const JobCreateKindSchema = z.enum(['agent', 'workflow']);
6
+ export const JobRequestExecutionIdentitySchema = z.strictObject({
7
+ id: z.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/),
8
+ version: z.string().min(1).max(128),
9
+ definitionSha256: z.string().regex(/^[a-f0-9]{64}$/),
10
+ });
11
+ export class JobRequestHashError extends Error {
12
+ status;
13
+ constructor(status, message) {
14
+ super(message);
15
+ this.status = status;
16
+ this.name = 'JobRequestHashError';
17
+ }
18
+ }
19
+ function assertJsonValue({ value, ancestors, depth }) {
20
+ if (value === null || typeof value === 'string' || typeof value === 'boolean'
21
+ || typeof value === 'number' && Number.isFinite(value))
22
+ return;
23
+ if (typeof value !== 'object' || !value || depth > 64 || ancestors.has(value)) {
24
+ throw new JobRequestHashError(400, 'Job admission requires bounded JSON values.');
25
+ }
26
+ if (value instanceof Date) {
27
+ if (Number.isNaN(value.getTime()))
28
+ throw new JobRequestHashError(400, 'Job admission contains an invalid date.');
29
+ return;
30
+ }
31
+ if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
32
+ throw new JobRequestHashError(400, 'Job admission requires plain JSON objects.');
33
+ }
34
+ ancestors.add(value);
35
+ const values = Array.isArray(value) ? value : Object.values(value).filter((entry) => entry !== undefined);
36
+ for (const entry of values)
37
+ assertJsonValue({ value: entry, ancestors, depth: depth + 1 });
38
+ ancestors.delete(value);
39
+ }
40
+ export function normalizeJobRequestJson(value) {
41
+ assertJsonValue({ value, ancestors: new Set(), depth: 0 });
42
+ const encoded = JSON.stringify(value);
43
+ if (Buffer.byteLength(encoded) > MAX_JSON_BYTES)
44
+ throw new JobRequestHashError(413, 'Job admission exceeds the 16 MiB JSON budget.');
45
+ return JSON.parse(encoded);
46
+ }
47
+ export function hashJobCreateRequest({ kind, resourceId, expectedIdentity, payload }) {
48
+ const parsed = z.strictObject({ kind: JobCreateKindSchema, resourceId: z.string().min(1).max(128),
49
+ expectedIdentity: JobRequestExecutionIdentitySchema }).safeParse({ kind, resourceId, expectedIdentity });
50
+ if (!parsed.success)
51
+ throw new JobRequestHashError(400, 'Job request fingerprint requires a valid expected execution identity.');
52
+ const envelope = normalizeJobRequestJson({ format: 'agents-job-create-request-v1', ...parsed.data, payload });
53
+ return createHash('sha256').update(canonicalJson(envelope)).digest('hex');
54
+ }
@@ -188,8 +188,8 @@ export declare const voiceVerificationAttemptSchema: z.ZodObject<{
188
188
  attemptedAt: z.ZodISODateTime;
189
189
  attemptNumber: z.ZodNumber;
190
190
  status: z.ZodEnum<{
191
- verified: "verified";
192
191
  pending: "pending";
192
+ verified: "verified";
193
193
  rejected: "rejected";
194
194
  uncertain: "uncertain";
195
195
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sequenceholdings/agent-spec",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Typed agent definition contract, compiler, hashing, and migration utilities",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {