openworkgraph 0.2.12 → 0.3.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/dist/api.d.ts +1 -0
- package/dist/api.js +6 -0
- package/dist/backend/adapter.d.ts +1 -0
- package/dist/backend/adapter.js +161 -8
- package/dist/backend/stdio.d.ts +1 -1
- package/dist/backend/stdio.js +2 -2
- package/dist/backend/types.d.ts +12 -0
- package/dist/builtin-skills.d.ts +56 -0
- package/dist/builtin-skills.js +514 -0
- package/dist/cli.js +0 -0
- package/dist/graph-transfer.js +7 -2
- package/dist/inputs.d.ts +1 -0
- package/dist/inputs.js +1 -1
- package/dist/plugins.d.ts +4 -0
- package/dist/plugins.js +33 -1
- package/dist/runtime-backend.d.ts +3 -1
- package/dist/runtime-backend.js +11 -2
- package/dist/runtime.d.ts +7 -0
- package/dist/runtime.js +17 -2
- package/dist/service.js +7 -5
- package/dist/skill-configs.d.ts +43 -0
- package/dist/skill-configs.js +554 -0
- package/dist/skill-schema.d.ts +10 -0
- package/dist/skill-schema.js +144 -0
- package/dist/skill-sessions.d.ts +24 -0
- package/dist/skill-sessions.js +165 -0
- package/dist/skill-source.d.ts +37 -0
- package/dist/skill-source.js +104 -0
- package/dist/skills-api.d.ts +8 -4
- package/dist/skills-api.js +48 -27
- package/dist/skills-management-api.d.ts +12 -0
- package/dist/skills-management-api.js +139 -0
- package/dist/vendor/protocol/index.d.ts +3 -1
- package/dist/vendor/protocol/index.js +2 -1
- package/dist/vendor/protocol/skills.d.ts +107 -0
- package/dist/vendor/protocol/skills.js +33 -0
- package/package.json +2 -2
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Auth } from './auth.js';
|
|
2
|
+
import { ServiceError } from './errors.js';
|
|
3
|
+
import { readJson } from './http.js';
|
|
4
|
+
export class SkillsManagementApi {
|
|
5
|
+
auth;
|
|
6
|
+
runtime;
|
|
7
|
+
constructor(auth, runtime) {
|
|
8
|
+
this.auth = auth;
|
|
9
|
+
this.runtime = runtime;
|
|
10
|
+
}
|
|
11
|
+
async decorate(item) {
|
|
12
|
+
if (!item.installed)
|
|
13
|
+
return item;
|
|
14
|
+
const entry = await this.runtime.skills.packageEntry(item.skillId, item.packageVersion);
|
|
15
|
+
if (!entry.config.environment.length)
|
|
16
|
+
return { ...item, configuration: 'none' };
|
|
17
|
+
const config = await this.runtime.skillConfigs.read(item.skillId, entry.config);
|
|
18
|
+
const ready = entry.config.environment.filter(field => field.required).every(field => config.values.some(value => value.name === field.name && value.configured));
|
|
19
|
+
return { ...item, configuration: ready ? 'ready' : 'required' };
|
|
20
|
+
}
|
|
21
|
+
async handle(request, rawPath, token, origin) {
|
|
22
|
+
const pathname = rawPath.split('?')[0];
|
|
23
|
+
if (pathname !== '/v1/skills' && !pathname.startsWith('/v1/skills/'))
|
|
24
|
+
return { handled: false };
|
|
25
|
+
this.auth.withSession(token, origin, () => undefined);
|
|
26
|
+
const url = new URL(rawPath, 'http://localhost');
|
|
27
|
+
const match = /^\/v1\/skills(?:\/([^/]+)(?:\/(install|update|uninstall|config|files))?)?$/.exec(pathname);
|
|
28
|
+
if (!match)
|
|
29
|
+
throw new ServiceError('NOT_FOUND', '技能管理接口不存在。');
|
|
30
|
+
let id;
|
|
31
|
+
if (match[1]) {
|
|
32
|
+
try {
|
|
33
|
+
id = decodeURIComponent(match[1]);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new ServiceError('INVALID_REQUEST', '技能身份编码无效。');
|
|
37
|
+
}
|
|
38
|
+
if (!id || id.length > 1024 || /[\x00-\x1f\x7f]/.test(id))
|
|
39
|
+
throw new ServiceError('INVALID_REQUEST', '技能身份无效。');
|
|
40
|
+
}
|
|
41
|
+
const action = match[2];
|
|
42
|
+
const allowed = !id ? ['refresh', 'offset', 'limit', 'installed', 'query'] : !action ? ['locale'] : action === 'files' ? ['version', 'path'] : [];
|
|
43
|
+
if ([...url.searchParams.keys()].some(key => !allowed.includes(key) || url.searchParams.getAll(key).length !== 1))
|
|
44
|
+
throw new ServiceError('INVALID_REQUEST', '技能查询参数无效。');
|
|
45
|
+
let result;
|
|
46
|
+
if (request.method === 'GET' && !id) {
|
|
47
|
+
if (url.searchParams.has('refresh') && url.searchParams.get('refresh') !== '1')
|
|
48
|
+
throw new ServiceError('INVALID_REQUEST', 'refresh 必须为 1。');
|
|
49
|
+
const integer = (name, fallback, minimum, maximum) => {
|
|
50
|
+
const raw = url.searchParams.get(name);
|
|
51
|
+
if (raw === null)
|
|
52
|
+
return fallback;
|
|
53
|
+
const value = Number(raw);
|
|
54
|
+
if (!/^(0|[1-9][0-9]*)$/.test(raw) || !Number.isSafeInteger(value) || value < minimum || value > maximum)
|
|
55
|
+
throw new ServiceError('INVALID_REQUEST', '技能分页参数无效。');
|
|
56
|
+
return value;
|
|
57
|
+
};
|
|
58
|
+
const offset = integer('offset', 0, 0, Number.MAX_SAFE_INTEGER);
|
|
59
|
+
const limit = integer('limit', 100, 1, offset === 0 ? 100 : 50);
|
|
60
|
+
const installed = url.searchParams.get('installed');
|
|
61
|
+
if (installed !== null && installed !== '1')
|
|
62
|
+
throw new ServiceError('INVALID_REQUEST', 'installed 必须为 1。');
|
|
63
|
+
if (installed && (url.searchParams.has('offset') || url.searchParams.has('limit')))
|
|
64
|
+
throw new ServiceError('INVALID_REQUEST', '已安装技能列表不接受分页参数。');
|
|
65
|
+
const query = (url.searchParams.get('query') ?? '').trim().toLocaleLowerCase();
|
|
66
|
+
if (query.length > 1024)
|
|
67
|
+
throw new ServiceError('INVALID_REQUEST', '技能搜索内容过长。');
|
|
68
|
+
const catalog = await this.runtime.skills.catalog(url.searchParams.get('refresh') === '1');
|
|
69
|
+
const filtered = catalog.items.filter(item => (!installed || item.installed) && (item.name + ' ' + item.description).toLocaleLowerCase().includes(query));
|
|
70
|
+
// Unpaged calls remain compatible with welcome setup and skill suggestions.
|
|
71
|
+
const paged = !installed && (url.searchParams.has('offset') || url.searchParams.has('limit'));
|
|
72
|
+
const items = paged ? filtered.slice(offset, offset + limit) : filtered;
|
|
73
|
+
const installedSignature = JSON.stringify(catalog.items.filter(item => item.installed).map(item => [item.skillId, item.packageVersion, item.revision]).sort());
|
|
74
|
+
result = { ...catalog, installedSignature, total: filtered.length, ...(paged && offset + items.length < filtered.length ? { nextOffset: offset + items.length } : {}), items: await Promise.all(items.map(async (item) => {
|
|
75
|
+
try {
|
|
76
|
+
return await this.decorate(item);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return { ...item, error: '已安装技能或配置不可用,请检查工作空间。' };
|
|
80
|
+
}
|
|
81
|
+
})) };
|
|
82
|
+
}
|
|
83
|
+
else if (request.method === 'GET' && id && !action) {
|
|
84
|
+
const detail = await this.runtime.skills.detail(id, url.searchParams.get('locale') ?? 'en');
|
|
85
|
+
result = { ...detail, item: await this.decorate(detail.item) };
|
|
86
|
+
}
|
|
87
|
+
else if (request.method === 'GET' && id && action === 'files') {
|
|
88
|
+
const version = url.searchParams.get('version');
|
|
89
|
+
const path = url.searchParams.get('path');
|
|
90
|
+
if (!version || !path)
|
|
91
|
+
throw new ServiceError('INVALID_REQUEST', '技能资源需要固定版本与包内路径。');
|
|
92
|
+
const bytes = await this.runtime.skills.readPackageFile(id, version, path);
|
|
93
|
+
this.auth.withSession(token, origin, () => undefined);
|
|
94
|
+
const mime = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp', avif: 'image/avif' };
|
|
95
|
+
const type = mime[path.split('.').pop().toLowerCase()];
|
|
96
|
+
return { handled: true, binary: bytes, headers: { 'Content-Type': type ?? 'application/octet-stream', 'Content-Disposition': (type ? 'inline' : 'attachment'), 'Cache-Control': 'private, no-store', 'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "sandbox; default-src 'none'" } };
|
|
97
|
+
}
|
|
98
|
+
else if (id && action === 'config' && ['GET', 'PUT', 'DELETE'].includes(request.method ?? '')) {
|
|
99
|
+
if (request.method === 'DELETE') {
|
|
100
|
+
const body = await readJson(request, 256 * 1024);
|
|
101
|
+
this.revisionBody(body);
|
|
102
|
+
this.auth.withSession(token, origin, () => undefined);
|
|
103
|
+
result = await this.runtime.skillConfigs.clear(id, body.expectedRevision);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
if (request.method === 'GET') {
|
|
107
|
+
const entry = await this.runtime.skills.packageEntry(id);
|
|
108
|
+
result = { ...await this.runtime.skillConfigs.read(id, entry.config), packageVersion: entry.item.packageVersion };
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const body = await readJson(request, 256 * 1024);
|
|
112
|
+
if (typeof body.expectedPackageVersion !== 'string' || !/^[a-f0-9]{64}$/.test(body.expectedPackageVersion) || Object.keys(body).some(key => !['expectedPackageVersion', 'expectedRevision', 'values'].includes(key)))
|
|
113
|
+
throw new ServiceError('INVALID_REQUEST', '保存技能配置需要固定的 expectedPackageVersion。');
|
|
114
|
+
this.auth.withSession(token, origin, () => undefined);
|
|
115
|
+
const { expectedPackageVersion, ...write } = body;
|
|
116
|
+
result = await this.runtime.skills.withPackageEntry(id, expectedPackageVersion, async (entry) => ({
|
|
117
|
+
...await this.runtime.skillConfigs.write(id, entry.config, write), packageVersion: entry.item.packageVersion,
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else if (request.method === 'POST' && id && ['install', 'update', 'uninstall'].includes(action ?? '')) {
|
|
123
|
+
const body = await readJson(request, 16 * 1024);
|
|
124
|
+
this.revisionBody(body);
|
|
125
|
+
this.auth.withSession(token, origin, () => undefined);
|
|
126
|
+
const item = action === 'uninstall' ? await this.runtime.skills.uninstall(id, body.expectedRevision) : await this.runtime.skills.install(id, body.expectedRevision);
|
|
127
|
+
result = await this.decorate(item);
|
|
128
|
+
}
|
|
129
|
+
else
|
|
130
|
+
throw new ServiceError('NOT_FOUND', '技能管理接口不存在。');
|
|
131
|
+
this.auth.withSession(token, origin, () => undefined);
|
|
132
|
+
return { handled: true, body: result };
|
|
133
|
+
}
|
|
134
|
+
revisionBody(body) {
|
|
135
|
+
if (Object.keys(body).length !== 1 || !Number.isSafeInteger(body.expectedRevision) || Number(body.expectedRevision) < 0)
|
|
136
|
+
throw new ServiceError('INVALID_REQUEST', '技能操作只接受有效的 expectedRevision。');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=skills-management-api.js.map
|
|
@@ -5,7 +5,7 @@ export * from './execution-chain.js';
|
|
|
5
5
|
export * from './graph-title.js';
|
|
6
6
|
export * from './graph-binary.js';
|
|
7
7
|
export declare const PROTOCOL_VERSION: '1.0';
|
|
8
|
-
export declare const SERVICE_VERSION: '0.
|
|
8
|
+
export declare const SERVICE_VERSION: '0.3.0';
|
|
9
9
|
/** Work Graph uploads use the UI's binary MB convention. */
|
|
10
10
|
export declare const WORKGRAPH_UPLOAD_MAX_BYTES: number;
|
|
11
11
|
export declare const WORKGRAPH_TRANSFER_TOTAL_BYTES: number;
|
|
@@ -458,6 +458,7 @@ export interface InputSnapshotBase {
|
|
|
458
458
|
prompt: string;
|
|
459
459
|
resources: ResourceEnvelope[];
|
|
460
460
|
projectFiles?: ProjectFileInput[];
|
|
461
|
+
skills?: import('./skills.js').FrozenSkill[];
|
|
461
462
|
}
|
|
462
463
|
export type InputSnapshot = InputSnapshotBase & ({
|
|
463
464
|
model: ModelSelection;
|
|
@@ -615,3 +616,4 @@ export interface LocalRestoreControl {
|
|
|
615
616
|
restore(path: string, expectedSha256: string): Promise<void>;
|
|
616
617
|
}
|
|
617
618
|
export { previewEdgeError } from "./preview.js";
|
|
619
|
+
export * from './skills.js';
|
|
@@ -5,7 +5,7 @@ export * from './execution-chain.js';
|
|
|
5
5
|
export * from './graph-title.js';
|
|
6
6
|
export * from './graph-binary.js';
|
|
7
7
|
export const PROTOCOL_VERSION = '1.0';
|
|
8
|
-
export const SERVICE_VERSION = '0.
|
|
8
|
+
export const SERVICE_VERSION = '0.3.0';
|
|
9
9
|
/** Work Graph uploads use the UI's binary MB convention. */
|
|
10
10
|
export const WORKGRAPH_UPLOAD_MAX_BYTES = 300 * 1024 * 1024;
|
|
11
11
|
export const WORKGRAPH_TRANSFER_TOTAL_BYTES = 1024 * 1024 * 1024;
|
|
@@ -17,4 +17,5 @@ export function isTerminalRunStatus(status) { return TERMINAL_RUN_STATUSES.inclu
|
|
|
17
17
|
export const ERROR_CODES = ['INVALID_REQUEST', 'UNAUTHENTICATED', 'ORIGIN_DENIED', 'HOST_DENIED', 'PAIRING_CODE_INVALID', 'PAIRING_CODE_EXPIRED', 'PAIRING_CODE_USED', 'SESSION_EXPIRED', 'SESSION_REVOKED', 'PAYLOAD_TOO_LARGE', 'UNSUPPORTED_MEDIA_TYPE', 'SERVICE_NOT_RUNNING', 'SERVICE_MISMATCH', 'PROTOCOL_INCOMPATIBLE', 'NOT_FOUND', 'NOT_IMPLEMENTED', 'CONFLICT', 'IDEMPOTENCY_CONFLICT', 'PROJECT_INACTIVE', 'PROJECT_UNAVAILABLE', 'ACTIVE_RUN', 'REVISION_CONFLICT', 'NODE_LOCKED', 'INVALID_EDGE', 'INPUT_BLOCKED', 'INPUT_BUDGET_EXCEEDED', 'PLUGIN_UNAVAILABLE', 'MODEL_UNAVAILABLE', 'ALREADY_HANDLED', 'CURSOR_EXPIRED', 'MAINTENANCE', 'SCHEMA_UNSUPPORTED', 'MIGRATION_FAILED', 'INTERNAL_ERROR'];
|
|
18
18
|
export function isProtocolCompatible(version) { return /^1\.\d+$/.test(version); }
|
|
19
19
|
export { previewEdgeError } from "./preview.js";
|
|
20
|
+
export * from './skills.js';
|
|
20
21
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** Skill identities are portable; paths and configuration values never belong in graph packages. */
|
|
2
|
+
export declare const SKILL_NAME_PATTERN = "[A-Za-z0-9_.:-]+";
|
|
3
|
+
export declare function isSkillName(value: unknown): value is string;
|
|
4
|
+
/** Exact catalog names win, including names ending in dots. Otherwise trailing
|
|
5
|
+
* dots are sentence punctuation; internal dots remain part of the name. */
|
|
6
|
+
export declare function legacySkillMentions(prompt: string, knownNames?: readonly string[]): {
|
|
7
|
+
name: string;
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
}[];
|
|
11
|
+
export interface SkillReference {
|
|
12
|
+
skillId: string;
|
|
13
|
+
source: 'openworkgraph' | 'codex';
|
|
14
|
+
name: string;
|
|
15
|
+
start: number;
|
|
16
|
+
end: number;
|
|
17
|
+
}
|
|
18
|
+
export interface SkillCandidate {
|
|
19
|
+
skillId: string;
|
|
20
|
+
source: 'openworkgraph' | 'codex';
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
}
|
|
24
|
+
export interface SkillEnvironmentField {
|
|
25
|
+
name: string;
|
|
26
|
+
label: string;
|
|
27
|
+
description: string;
|
|
28
|
+
required: boolean;
|
|
29
|
+
secret: boolean;
|
|
30
|
+
default?: string;
|
|
31
|
+
translations?: Record<string, {
|
|
32
|
+
label: string;
|
|
33
|
+
description: string;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
export interface SkillConfigSchema {
|
|
37
|
+
version: 1;
|
|
38
|
+
environment: SkillEnvironmentField[];
|
|
39
|
+
}
|
|
40
|
+
export interface SkillSource {
|
|
41
|
+
id: string;
|
|
42
|
+
repository: string;
|
|
43
|
+
branch: string;
|
|
44
|
+
directory: string;
|
|
45
|
+
}
|
|
46
|
+
export interface SkillCatalogItem {
|
|
47
|
+
skillId: string;
|
|
48
|
+
source: SkillSource;
|
|
49
|
+
directory: string;
|
|
50
|
+
name: string;
|
|
51
|
+
description: string;
|
|
52
|
+
installed: boolean;
|
|
53
|
+
revision: number;
|
|
54
|
+
packageVersion?: string;
|
|
55
|
+
commitSha?: string;
|
|
56
|
+
updateAvailable: boolean;
|
|
57
|
+
configuration: 'none' | 'required' | 'ready';
|
|
58
|
+
error?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface SkillCatalogQuery {
|
|
61
|
+
offset?: number;
|
|
62
|
+
limit?: number;
|
|
63
|
+
installedOnly?: boolean;
|
|
64
|
+
query?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface SkillCatalog {
|
|
67
|
+
items: SkillCatalogItem[];
|
|
68
|
+
stale: boolean;
|
|
69
|
+
error?: string;
|
|
70
|
+
total?: number;
|
|
71
|
+
nextOffset?: number;
|
|
72
|
+
installedSignature?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface SkillDetail {
|
|
75
|
+
item: SkillCatalogItem;
|
|
76
|
+
readme: string;
|
|
77
|
+
config: SkillConfigSchema;
|
|
78
|
+
}
|
|
79
|
+
export interface SkillConfigValue {
|
|
80
|
+
name: string;
|
|
81
|
+
configured: boolean;
|
|
82
|
+
value?: string;
|
|
83
|
+
}
|
|
84
|
+
export interface SkillConfiguration {
|
|
85
|
+
skillId: string;
|
|
86
|
+
revision: number;
|
|
87
|
+
schema: SkillConfigSchema;
|
|
88
|
+
values: SkillConfigValue[];
|
|
89
|
+
packageVersion?: string;
|
|
90
|
+
}
|
|
91
|
+
export interface SkillConfigurationWrite {
|
|
92
|
+
expectedRevision: number;
|
|
93
|
+
values: Record<string, string | null>;
|
|
94
|
+
expectedPackageVersion?: string;
|
|
95
|
+
}
|
|
96
|
+
/** Frozen public metadata. Credentials are resolved separately from protected revisions at launch. */
|
|
97
|
+
export interface FrozenSkill {
|
|
98
|
+
skillId: string;
|
|
99
|
+
source: 'openworkgraph' | 'codex';
|
|
100
|
+
name: string;
|
|
101
|
+
description: string;
|
|
102
|
+
path: string;
|
|
103
|
+
packageVersion?: string;
|
|
104
|
+
configRevision?: number;
|
|
105
|
+
explicit: boolean;
|
|
106
|
+
requiresConfiguration: boolean;
|
|
107
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Skill identities are portable; paths and configuration values never belong in graph packages. */
|
|
2
|
+
export const SKILL_NAME_PATTERN = '[A-Za-z0-9_.:-]+';
|
|
3
|
+
const skillName = new RegExp('^' + SKILL_NAME_PATTERN + '$');
|
|
4
|
+
export function isSkillName(value) {
|
|
5
|
+
return typeof value === 'string' && value.length <= 128 && skillName.test(value);
|
|
6
|
+
}
|
|
7
|
+
/** Exact catalog names win, including names ending in dots. Otherwise trailing
|
|
8
|
+
* dots are sentence punctuation; internal dots remain part of the name. */
|
|
9
|
+
export function legacySkillMentions(prompt, knownNames = []) {
|
|
10
|
+
const known = new Set(knownNames);
|
|
11
|
+
const result = [];
|
|
12
|
+
for (const match of prompt.matchAll(new RegExp('(?:^|[^\\w$])\\$(' + SKILL_NAME_PATTERN + ')', 'g'))) {
|
|
13
|
+
const raw = match[1];
|
|
14
|
+
const base = raw.replace(/\.+$/, '');
|
|
15
|
+
let name = raw;
|
|
16
|
+
if (!known.has(raw)) {
|
|
17
|
+
name = base || raw;
|
|
18
|
+
for (let length = Math.min(raw.length - 1, 128); length > base.length; length--) {
|
|
19
|
+
const candidate = raw.slice(0, length);
|
|
20
|
+
if (known.has(candidate)) {
|
|
21
|
+
name = candidate;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (!isSkillName(name))
|
|
27
|
+
continue;
|
|
28
|
+
const start = match.index + match[0].lastIndexOf('$');
|
|
29
|
+
result.push({ name, start, end: start + name.length + 1 });
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=skills.js.map
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "openworkgraph", "version": "0.
|
|
2
|
+
"name": "openworkgraph", "version": "0.3.0", "type": "module",
|
|
3
3
|
"publishConfig": { "access": "public" },
|
|
4
4
|
"engines": { "node": ">=24.0.0" },
|
|
5
5
|
"bin": { "openworkgraph": "dist/cli.js" },
|
|
6
6
|
"files": ["dist/**/*.js", "dist/**/*.d.ts", "README.md"],
|
|
7
7
|
"scripts": { "prebuild": "node ../scripts/check-versions.mjs --quiet", "build": "tsc -p tsconfig.json && node scripts/vendor-protocol.mjs", "prepack": "node scripts/prepare-pack.mjs", "typecheck": "tsc -p tsconfig.json --noEmit", "start": "node dist/cli.js", "test": "node --test test/*.test.mjs" },
|
|
8
|
-
"dependencies": { "sharp": "^0.35.4" }
|
|
8
|
+
"dependencies": { "sharp": "^0.35.4", "yaml": "^2.9.1" }
|
|
9
9
|
}
|