@minionry/app-manifest 0.1.14
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/LICENSE +14 -0
- package/README.md +8 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +256 -0
- package/package.json +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Minionry Commercial License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present Minionry, Inc. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is not open-source. It is provided under a commercial
|
|
6
|
+
license, and your rights to install, use, and (where the Minionry Terms
|
|
7
|
+
of Service expressly permit it) distribute this software are set out
|
|
8
|
+
there, together with all applicable restrictions, termination, and
|
|
9
|
+
disclaimer terms:
|
|
10
|
+
|
|
11
|
+
https://app.minionry.com/terms
|
|
12
|
+
|
|
13
|
+
If you do not agree to the Minionry Terms of Service, you may not
|
|
14
|
+
install or use this software.
|
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare const SDK_VERSION = 1;
|
|
2
|
+
export declare const MANIFEST_PATH = "/.well-known/minionry.json";
|
|
3
|
+
export type Scope = 'identity' | 'storage' | 'files:app' | 'pm:read' | 'agents:run' | 'pm:write' | 'files:workspace:read' | 'files:workspace:write' | 'pm:execute' | 'pm:schedule' | 'agents:sessions' | 'browser:read' | 'browser:control' | 'settings:read' | 'notifications' | 'git:read' | 'git:write' | 'git:remote' | 'terminal:read' | 'terminal:exec' | 'tools:serve' | 'quality:read' | 'quality:run' | 'inference:run' | 'endpoints:register';
|
|
4
|
+
export declare const SCOPES: readonly Scope[];
|
|
5
|
+
export type RiskTier = 'low' | 'medium' | 'high' | 'critical';
|
|
6
|
+
export declare const RISK_TIERS: readonly RiskTier[];
|
|
7
|
+
export declare const SCOPE_RISK: Record<Scope, RiskTier>;
|
|
8
|
+
export declare function isScope(value: unknown): value is Scope;
|
|
9
|
+
export type ManifestPermission = 'microphone' | 'camera';
|
|
10
|
+
export declare const MANIFEST_PERMISSIONS: readonly ManifestPermission[];
|
|
11
|
+
export declare function isManifestPermission(value: unknown): value is ManifestPermission;
|
|
12
|
+
export interface AppManifest {
|
|
13
|
+
name: string;
|
|
14
|
+
entry: string;
|
|
15
|
+
scopes: Scope[];
|
|
16
|
+
sdkVersion: number;
|
|
17
|
+
icon?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
optionalScopes?: Scope[];
|
|
20
|
+
permissions?: ManifestPermission[];
|
|
21
|
+
}
|
|
22
|
+
export type ManifestValidationResult = {
|
|
23
|
+
ok: true;
|
|
24
|
+
manifest: AppManifest;
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
errors: string[];
|
|
28
|
+
};
|
|
29
|
+
export declare function validateManifest(value: unknown): ManifestValidationResult;
|
|
30
|
+
export declare function canonicalJson(value: unknown): string;
|
|
31
|
+
export type Sha256Hex = (input: string) => string | Promise<string>;
|
|
32
|
+
export declare function manifestHash(manifest: unknown, sha256Hex: Sha256Hex): Promise<string>;
|
|
33
|
+
export declare function slugifyAppName(name: string): string;
|
|
34
|
+
export declare function deriveAppId(name: string, origin: string, sha256Hex: Sha256Hex): Promise<string>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
export const SDK_VERSION = 1;
|
|
2
|
+
export const MANIFEST_PATH = '/.well-known/minionry.json';
|
|
3
|
+
export const SCOPES = [
|
|
4
|
+
'identity',
|
|
5
|
+
'storage',
|
|
6
|
+
'files:app',
|
|
7
|
+
'pm:read',
|
|
8
|
+
'agents:run',
|
|
9
|
+
'pm:write',
|
|
10
|
+
'files:workspace:read',
|
|
11
|
+
'files:workspace:write',
|
|
12
|
+
'pm:execute',
|
|
13
|
+
'pm:schedule',
|
|
14
|
+
'agents:sessions',
|
|
15
|
+
'browser:read',
|
|
16
|
+
'browser:control',
|
|
17
|
+
'settings:read',
|
|
18
|
+
'notifications',
|
|
19
|
+
'git:read',
|
|
20
|
+
'git:write',
|
|
21
|
+
'git:remote',
|
|
22
|
+
'terminal:read',
|
|
23
|
+
'terminal:exec',
|
|
24
|
+
'tools:serve',
|
|
25
|
+
'quality:read',
|
|
26
|
+
'quality:run',
|
|
27
|
+
'inference:run',
|
|
28
|
+
'endpoints:register',
|
|
29
|
+
];
|
|
30
|
+
export const RISK_TIERS = ['low', 'medium', 'high', 'critical'];
|
|
31
|
+
export const SCOPE_RISK = {
|
|
32
|
+
identity: 'low',
|
|
33
|
+
storage: 'low',
|
|
34
|
+
'files:app': 'low',
|
|
35
|
+
'pm:read': 'medium',
|
|
36
|
+
'agents:run': 'high',
|
|
37
|
+
'pm:write': 'high',
|
|
38
|
+
'files:workspace:read': 'high',
|
|
39
|
+
'files:workspace:write': 'critical',
|
|
40
|
+
'pm:execute': 'critical',
|
|
41
|
+
'pm:schedule': 'critical',
|
|
42
|
+
'agents:sessions': 'high',
|
|
43
|
+
'browser:read': 'high',
|
|
44
|
+
'browser:control': 'critical',
|
|
45
|
+
'settings:read': 'low',
|
|
46
|
+
notifications: 'medium',
|
|
47
|
+
'git:read': 'medium',
|
|
48
|
+
'git:write': 'high',
|
|
49
|
+
'git:remote': 'critical',
|
|
50
|
+
'terminal:read': 'high',
|
|
51
|
+
'terminal:exec': 'critical',
|
|
52
|
+
'tools:serve': 'medium',
|
|
53
|
+
'quality:read': 'low',
|
|
54
|
+
'quality:run': 'high',
|
|
55
|
+
'inference:run': 'high',
|
|
56
|
+
'endpoints:register': 'high',
|
|
57
|
+
};
|
|
58
|
+
const SCOPE_SET = new Set(SCOPES);
|
|
59
|
+
export function isScope(value) {
|
|
60
|
+
return typeof value === 'string' && SCOPE_SET.has(value);
|
|
61
|
+
}
|
|
62
|
+
export const MANIFEST_PERMISSIONS = ['microphone', 'camera'];
|
|
63
|
+
const MANIFEST_PERMISSION_SET = new Set(MANIFEST_PERMISSIONS);
|
|
64
|
+
export function isManifestPermission(value) {
|
|
65
|
+
return typeof value === 'string' && MANIFEST_PERMISSION_SET.has(value);
|
|
66
|
+
}
|
|
67
|
+
export function validateManifest(value) {
|
|
68
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
69
|
+
return { ok: false, errors: [`manifest: must be a JSON object (got ${describeType(value)})`] };
|
|
70
|
+
}
|
|
71
|
+
const raw = value;
|
|
72
|
+
const errors = [];
|
|
73
|
+
const name = requireNonEmptyString(raw, 'name', errors);
|
|
74
|
+
const entry = requireNonEmptyString(raw, 'entry', errors);
|
|
75
|
+
let sdkVersion = null;
|
|
76
|
+
if (raw.sdkVersion === undefined) {
|
|
77
|
+
errors.push('sdkVersion: required field is missing');
|
|
78
|
+
}
|
|
79
|
+
else if (typeof raw.sdkVersion !== 'number' ||
|
|
80
|
+
!Number.isInteger(raw.sdkVersion) ||
|
|
81
|
+
raw.sdkVersion < 1) {
|
|
82
|
+
errors.push(`sdkVersion: must be a positive integer (got ${formatValue(raw.sdkVersion)})`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
sdkVersion = raw.sdkVersion;
|
|
86
|
+
}
|
|
87
|
+
let scopes = null;
|
|
88
|
+
if (raw.scopes === undefined) {
|
|
89
|
+
errors.push('scopes: required field is missing');
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
scopes = readScopeList(raw.scopes, 'scopes', errors);
|
|
93
|
+
}
|
|
94
|
+
let optionalScopes = null;
|
|
95
|
+
if (raw.optionalScopes !== undefined) {
|
|
96
|
+
optionalScopes = readScopeList(raw.optionalScopes, 'optionalScopes', errors);
|
|
97
|
+
}
|
|
98
|
+
if (scopes !== null && optionalScopes !== null) {
|
|
99
|
+
const declared = new Set(scopes);
|
|
100
|
+
for (const scope of optionalScopes) {
|
|
101
|
+
if (declared.has(scope)) {
|
|
102
|
+
errors.push(`optionalScopes: scope '${scope}' is already declared in scopes`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
let permissions = null;
|
|
107
|
+
if (raw.permissions !== undefined) {
|
|
108
|
+
permissions = readPermissionList(raw.permissions, 'permissions', errors);
|
|
109
|
+
}
|
|
110
|
+
const icon = optionalString(raw, 'icon', errors);
|
|
111
|
+
const description = optionalString(raw, 'description', errors);
|
|
112
|
+
if (errors.length > 0 || name === null || entry === null || scopes === null || sdkVersion === null) {
|
|
113
|
+
return { ok: false, errors };
|
|
114
|
+
}
|
|
115
|
+
const manifest = { name, entry, scopes, sdkVersion };
|
|
116
|
+
if (icon !== undefined)
|
|
117
|
+
manifest.icon = icon;
|
|
118
|
+
if (description !== undefined)
|
|
119
|
+
manifest.description = description;
|
|
120
|
+
if (optionalScopes !== null)
|
|
121
|
+
manifest.optionalScopes = optionalScopes;
|
|
122
|
+
if (permissions !== null)
|
|
123
|
+
manifest.permissions = permissions;
|
|
124
|
+
return { ok: true, manifest };
|
|
125
|
+
}
|
|
126
|
+
function requireNonEmptyString(raw, field, errors) {
|
|
127
|
+
const value = raw[field];
|
|
128
|
+
if (value === undefined) {
|
|
129
|
+
errors.push(`${field}: required field is missing`);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
if (typeof value !== 'string') {
|
|
133
|
+
errors.push(`${field}: must be a string (got ${describeType(value)})`);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
if (value.trim() === '') {
|
|
137
|
+
errors.push(`${field}: must be a non-empty string`);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
function optionalString(raw, field, errors) {
|
|
143
|
+
const value = raw[field];
|
|
144
|
+
if (value === undefined)
|
|
145
|
+
return undefined;
|
|
146
|
+
if (typeof value !== 'string') {
|
|
147
|
+
errors.push(`${field}: must be a string (got ${describeType(value)})`);
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
function readScopeList(value, field, errors) {
|
|
153
|
+
if (!Array.isArray(value)) {
|
|
154
|
+
errors.push(`${field}: must be an array of scope strings (got ${describeType(value)})`);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
const seen = new Set();
|
|
158
|
+
const scopes = [];
|
|
159
|
+
let valid = true;
|
|
160
|
+
value.forEach((entry, index) => {
|
|
161
|
+
if (typeof entry !== 'string') {
|
|
162
|
+
errors.push(`${field}[${index}]: must be a string (got ${describeType(entry)})`);
|
|
163
|
+
valid = false;
|
|
164
|
+
}
|
|
165
|
+
else if (!isScope(entry)) {
|
|
166
|
+
errors.push(`${field}[${index}]: unknown scope '${entry}' (valid scopes: ${SCOPES.join(', ')})`);
|
|
167
|
+
valid = false;
|
|
168
|
+
}
|
|
169
|
+
else if (seen.has(entry)) {
|
|
170
|
+
errors.push(`${field}[${index}]: duplicate scope '${entry}'`);
|
|
171
|
+
valid = false;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
seen.add(entry);
|
|
175
|
+
scopes.push(entry);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
return valid ? scopes : null;
|
|
179
|
+
}
|
|
180
|
+
function readPermissionList(value, field, errors) {
|
|
181
|
+
if (!Array.isArray(value)) {
|
|
182
|
+
errors.push(`${field}: must be an array of permission strings (got ${describeType(value)})`);
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
const seen = new Set();
|
|
186
|
+
const permissions = [];
|
|
187
|
+
let valid = true;
|
|
188
|
+
value.forEach((entry, index) => {
|
|
189
|
+
if (typeof entry !== 'string') {
|
|
190
|
+
errors.push(`${field}[${index}]: must be a string (got ${describeType(entry)})`);
|
|
191
|
+
valid = false;
|
|
192
|
+
}
|
|
193
|
+
else if (!isManifestPermission(entry)) {
|
|
194
|
+
errors.push(`${field}[${index}]: unknown permission '${entry}' (valid permissions: ${MANIFEST_PERMISSIONS.join(', ')})`);
|
|
195
|
+
valid = false;
|
|
196
|
+
}
|
|
197
|
+
else if (seen.has(entry)) {
|
|
198
|
+
errors.push(`${field}[${index}]: duplicate permission '${entry}'`);
|
|
199
|
+
valid = false;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
seen.add(entry);
|
|
203
|
+
permissions.push(entry);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
return valid ? permissions : null;
|
|
207
|
+
}
|
|
208
|
+
function describeType(value) {
|
|
209
|
+
if (value === null)
|
|
210
|
+
return 'null';
|
|
211
|
+
if (Array.isArray(value))
|
|
212
|
+
return 'array';
|
|
213
|
+
return typeof value;
|
|
214
|
+
}
|
|
215
|
+
function formatValue(value) {
|
|
216
|
+
if (typeof value === 'number')
|
|
217
|
+
return String(value);
|
|
218
|
+
if (typeof value === 'string')
|
|
219
|
+
return JSON.stringify(value);
|
|
220
|
+
return describeType(value);
|
|
221
|
+
}
|
|
222
|
+
export function canonicalJson(value) {
|
|
223
|
+
return JSON.stringify(sortKeysDeep(value));
|
|
224
|
+
}
|
|
225
|
+
function sortKeysDeep(value) {
|
|
226
|
+
if (Array.isArray(value))
|
|
227
|
+
return value.map(sortKeysDeep);
|
|
228
|
+
if (value !== null && typeof value === 'object') {
|
|
229
|
+
const source = value;
|
|
230
|
+
const sorted = {};
|
|
231
|
+
for (const key of Object.keys(source).sort()) {
|
|
232
|
+
sorted[key] = sortKeysDeep(source[key]);
|
|
233
|
+
}
|
|
234
|
+
return sorted;
|
|
235
|
+
}
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
238
|
+
export async function manifestHash(manifest, sha256Hex) {
|
|
239
|
+
return (await sha256Hex(canonicalJson(manifest))).toLowerCase();
|
|
240
|
+
}
|
|
241
|
+
export function slugifyAppName(name) {
|
|
242
|
+
const slug = name
|
|
243
|
+
.normalize('NFKD')
|
|
244
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
245
|
+
.toLowerCase()
|
|
246
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
247
|
+
.replace(/^-+|-+$/g, '')
|
|
248
|
+
.slice(0, 48)
|
|
249
|
+
.replace(/-+$/, '');
|
|
250
|
+
return slug === '' ? 'app' : slug;
|
|
251
|
+
}
|
|
252
|
+
export async function deriveAppId(name, origin, sha256Hex) {
|
|
253
|
+
const normalizedOrigin = new URL(origin).origin;
|
|
254
|
+
const originHash = (await sha256Hex(normalizedOrigin)).toLowerCase().slice(0, 12);
|
|
255
|
+
return `${slugifyAppName(name)}-${originHash}`;
|
|
256
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@minionry/app-manifest",
|
|
3
|
+
"version": "0.1.14",
|
|
4
|
+
"description": "Types, validation, and hashing for Minionry app manifests.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"registry": "https://registry.npmjs.org/",
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"gitHead": "b790f8b643da7b63c57bf12e804dd4a0eba7f312"
|
|
24
|
+
}
|