@agentproto/manifest 0.1.0-alpha.1
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 +1 -0
- package/README.md +26 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.mjs +128 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
MIT License — Copyright (c) 2026 agentproto contributors
|
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @agentproto/manifest
|
|
2
|
+
|
|
3
|
+
Generic verbs for AIP doctypes. See [AIP-1](https://agentproto.sh/docs/aip-1).
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createVerbs } from "@agentproto/manifest"
|
|
7
|
+
|
|
8
|
+
const verbs = createVerbs({
|
|
9
|
+
name: "tool",
|
|
10
|
+
aip: 14,
|
|
11
|
+
schemaLiteral: "agentproto/tool/v1",
|
|
12
|
+
pathOf: (h) => `${h.id}/TOOL.md`,
|
|
13
|
+
define: defineTool,
|
|
14
|
+
parse: parseToolManifest,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
await verbs.create({ id: "echo", ... }, { dir: "tools" })
|
|
18
|
+
const echo = await verbs.load("tools/echo/TOOL.md")
|
|
19
|
+
const all = await verbs.list("tools")
|
|
20
|
+
await verbs.update("tools/echo/TOOL.md", (p) => ({ ...p, version: "1.0.1" }))
|
|
21
|
+
const resolved = await verbs.resolve({ ref: "@agentik/runners/python" }, ctx)
|
|
22
|
+
await verbs.delete("tools/echo/TOOL.md")
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentproto/manifest — generic verbs for AIP doctypes.
|
|
3
|
+
*
|
|
4
|
+
* Every per-AIP package (`@agentproto/tool`, `@agentproto/driver`, …)
|
|
5
|
+
* supplies a `DoctypeSpec` describing the per-package knobs (name,
|
|
6
|
+
* AIP number, schema literal, path convention, validator, parser).
|
|
7
|
+
* `createVerbs(spec)` returns the full lifecycle:
|
|
8
|
+
*
|
|
9
|
+
* create(params, opts) params → .md on disk
|
|
10
|
+
* load(path) .md on disk → handle
|
|
11
|
+
* list(dir, filter?) walk → handle[]
|
|
12
|
+
* update(path, mutator) load → patch → write back
|
|
13
|
+
* resolve(block, ctx?) { inline | ref | file } → handle
|
|
14
|
+
* delete(path) fs.unlink
|
|
15
|
+
*
|
|
16
|
+
* Adding a new verb is a single place; every package picks it up
|
|
17
|
+
* automatically — same model as `createDoctype` for the validation
|
|
18
|
+
* pipeline.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Per-package descriptor consumed by `createVerbs`.
|
|
22
|
+
*
|
|
23
|
+
* Generic over the params shape (input to `define`) and the handle
|
|
24
|
+
* shape (output of `define`). For most AIPs `THandle` extends
|
|
25
|
+
* `TParams` — handles are just frozen, defaulted definitions.
|
|
26
|
+
*/
|
|
27
|
+
interface DoctypeSpec<TParams extends {
|
|
28
|
+
id?: string;
|
|
29
|
+
slug?: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
}, THandle> {
|
|
32
|
+
/** Lower-case singular: "tool", "driver", "operator". Used in errors. */
|
|
33
|
+
name: string;
|
|
34
|
+
/** AIP number — surfaces in error messages for provenance. */
|
|
35
|
+
aip: number;
|
|
36
|
+
/** Frontmatter `schema:` literal (e.g. "agentproto/tool/v1"). */
|
|
37
|
+
schemaLiteral: string;
|
|
38
|
+
/**
|
|
39
|
+
* Filename of the doctype's manifest. By default `<DOCTYPE>.md`,
|
|
40
|
+
* derived from `name.toUpperCase()`. Override only when the
|
|
41
|
+
* doctype's filename diverges from convention.
|
|
42
|
+
*/
|
|
43
|
+
filename?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Path-of-handle convention. Given a validated handle, return the
|
|
46
|
+
* workspace-relative path the manifest lives at. Examples:
|
|
47
|
+
* tool: `(h) => `${h.id}/TOOL.md``
|
|
48
|
+
* policy: `(h) => `policies/${h.slug}/POLICY.md``
|
|
49
|
+
* operator: `(h) => `${h.id}/OPERATOR.md``
|
|
50
|
+
*/
|
|
51
|
+
pathOf(handle: THandle): string;
|
|
52
|
+
/** TS authoring path: validates params + applies defaults. */
|
|
53
|
+
define(params: TParams): THandle;
|
|
54
|
+
/** MD authoring path: parses YAML+body. Throws on invalid frontmatter. */
|
|
55
|
+
parse(source: string): {
|
|
56
|
+
frontmatter: Record<string, unknown>;
|
|
57
|
+
body: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Optional projection: handle → manifest-shaped frontmatter object.
|
|
61
|
+
* Default: pass the params through `filterSerializable` (drops zod
|
|
62
|
+
* schemas + functions). Override when the handle and frontmatter
|
|
63
|
+
* shapes diverge non-trivially.
|
|
64
|
+
*/
|
|
65
|
+
toFrontmatter?(params: TParams): Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
interface CreateOptions {
|
|
68
|
+
/** Workspace-relative or absolute base directory. */
|
|
69
|
+
dir: string;
|
|
70
|
+
/** Body markdown after the frontmatter. Defaults to a one-line stub. */
|
|
71
|
+
body?: string;
|
|
72
|
+
/** Render only — don't write. Returns the rendered string. */
|
|
73
|
+
dryRun?: boolean;
|
|
74
|
+
}
|
|
75
|
+
interface VerbResult<THandle> {
|
|
76
|
+
path: string;
|
|
77
|
+
handle: THandle;
|
|
78
|
+
rendered: string;
|
|
79
|
+
}
|
|
80
|
+
interface ListOptions {
|
|
81
|
+
/** Filter the discovered handles. Receives the loaded handle. */
|
|
82
|
+
filter?: <H>(handle: H) => boolean;
|
|
83
|
+
/** Skip subdirectories matching these names. Default: ["node_modules", ".git", "dist", ".next"]. */
|
|
84
|
+
skipDirs?: readonly string[];
|
|
85
|
+
}
|
|
86
|
+
type RefBlock<TParams> = {
|
|
87
|
+
inline: TParams;
|
|
88
|
+
} | {
|
|
89
|
+
ref: string;
|
|
90
|
+
} | {
|
|
91
|
+
file: string;
|
|
92
|
+
};
|
|
93
|
+
interface ResolveContext {
|
|
94
|
+
/** Base dir for `file:` references. Required when resolving file blocks. */
|
|
95
|
+
baseDir?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Registry resolver for `ref:` strings (e.g. `@agentik/runners/python-3.12`
|
|
98
|
+
* → handle). Optional; throws when an unresolvable ref is encountered.
|
|
99
|
+
*/
|
|
100
|
+
resolveRef?: (ref: string) => Promise<unknown> | unknown;
|
|
101
|
+
}
|
|
102
|
+
interface Verbs<TParams, THandle> {
|
|
103
|
+
/** TS-authored params → write `<dir>/<pathOf(handle)>` on disk. */
|
|
104
|
+
create(params: TParams, opts: CreateOptions): Promise<VerbResult<THandle>>;
|
|
105
|
+
/** Read a manifest from disk → handle. */
|
|
106
|
+
load(path: string): Promise<{
|
|
107
|
+
path: string;
|
|
108
|
+
handle: THandle;
|
|
109
|
+
body: string;
|
|
110
|
+
}>;
|
|
111
|
+
/** Walk `dir` for the doctype's `*.md` files; return loaded handles. */
|
|
112
|
+
list(dir: string, opts?: ListOptions): Promise<THandle[]>;
|
|
113
|
+
/** Load → mutate → write back. Mutator receives the loaded params. */
|
|
114
|
+
update(path: string, mutator: (params: TParams, ctx: {
|
|
115
|
+
handle: THandle;
|
|
116
|
+
body: string;
|
|
117
|
+
}) => TParams | Promise<TParams>, opts?: {
|
|
118
|
+
body?: string;
|
|
119
|
+
dryRun?: boolean;
|
|
120
|
+
}): Promise<VerbResult<THandle>>;
|
|
121
|
+
/**
|
|
122
|
+
* Resolve a `{ inline | ref | file }` block to a handle. Used by
|
|
123
|
+
* doctypes that compose other doctypes (AIP-17/30/36 pattern).
|
|
124
|
+
*/
|
|
125
|
+
resolve(block: RefBlock<TParams>, ctx?: ResolveContext): Promise<THandle>;
|
|
126
|
+
/** Remove the manifest file. Does NOT recurse into the containing dir. */
|
|
127
|
+
delete(path: string): Promise<void>;
|
|
128
|
+
}
|
|
129
|
+
declare function createVerbs<TParams extends {
|
|
130
|
+
id?: string;
|
|
131
|
+
slug?: string;
|
|
132
|
+
name?: string;
|
|
133
|
+
}, THandle>(spec: DoctypeSpec<TParams, THandle>): Verbs<TParams, THandle>;
|
|
134
|
+
|
|
135
|
+
export { type CreateOptions, type DoctypeSpec, type ListOptions, type RefBlock, type ResolveContext, type VerbResult, type Verbs, createVerbs };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { filterSerializable } from '@agentproto/define-doctype';
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
import { mkdir, writeFile, readFile, rm, readdir } from 'fs/promises';
|
|
4
|
+
import { join, dirname, isAbsolute } from 'path';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @agentproto/manifest v0.1.0-alpha
|
|
8
|
+
* Generic verbs for AIP doctypes (create, load, list, update, resolve, …).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
var DEFAULT_SKIP_DIRS = [
|
|
12
|
+
"node_modules",
|
|
13
|
+
".git",
|
|
14
|
+
"dist",
|
|
15
|
+
".next",
|
|
16
|
+
".turbo"
|
|
17
|
+
];
|
|
18
|
+
function createVerbs(spec) {
|
|
19
|
+
const filename = spec.filename ?? `${spec.name.toUpperCase()}.md`;
|
|
20
|
+
const toFrontmatter = spec.toFrontmatter ?? ((params) => filterSerializable({
|
|
21
|
+
schema: spec.schemaLiteral,
|
|
22
|
+
...params
|
|
23
|
+
}));
|
|
24
|
+
async function create(params, opts) {
|
|
25
|
+
const handle = spec.define(params);
|
|
26
|
+
const relativePath = spec.pathOf(handle);
|
|
27
|
+
const path = join(opts.dir, relativePath);
|
|
28
|
+
const frontmatter = toFrontmatter(params);
|
|
29
|
+
const rendered = matter.stringify(
|
|
30
|
+
opts.body ?? defaultBody(spec, frontmatter),
|
|
31
|
+
frontmatter
|
|
32
|
+
);
|
|
33
|
+
if (!opts.dryRun) {
|
|
34
|
+
await mkdir(dirname(path), { recursive: true });
|
|
35
|
+
await writeFile(path, rendered, "utf8");
|
|
36
|
+
}
|
|
37
|
+
return { path, handle, rendered };
|
|
38
|
+
}
|
|
39
|
+
async function load(path) {
|
|
40
|
+
const source = await readFile(path, "utf8");
|
|
41
|
+
const { frontmatter, body } = spec.parse(source);
|
|
42
|
+
const handle = spec.define(frontmatter);
|
|
43
|
+
return { path, handle, body };
|
|
44
|
+
}
|
|
45
|
+
async function list(dir, opts = {}) {
|
|
46
|
+
const skip = new Set(opts.skipDirs ?? DEFAULT_SKIP_DIRS);
|
|
47
|
+
const out = [];
|
|
48
|
+
async function walk(current) {
|
|
49
|
+
let entries;
|
|
50
|
+
try {
|
|
51
|
+
entries = await readdir(current, { withFileTypes: true });
|
|
52
|
+
} catch {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const entryName = String(entry.name);
|
|
57
|
+
if (entry.isDirectory()) {
|
|
58
|
+
if (skip.has(entryName)) continue;
|
|
59
|
+
await walk(join(current, entryName));
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (!entry.isFile()) continue;
|
|
63
|
+
if (entryName !== filename) continue;
|
|
64
|
+
try {
|
|
65
|
+
const { handle } = await load(join(current, entryName));
|
|
66
|
+
if (!opts.filter || opts.filter(handle)) out.push(handle);
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
await walk(dir);
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
async function update(path, mutator, opts = {}) {
|
|
75
|
+
const { handle, body } = await load(path);
|
|
76
|
+
const source = await readFile(path, "utf8");
|
|
77
|
+
const { frontmatter } = spec.parse(source);
|
|
78
|
+
const params = frontmatter;
|
|
79
|
+
const mutated = await mutator(params, { handle, body });
|
|
80
|
+
const newHandle = spec.define(mutated);
|
|
81
|
+
const newFrontmatter = toFrontmatter(mutated);
|
|
82
|
+
const rendered = matter.stringify(
|
|
83
|
+
opts.body ?? body,
|
|
84
|
+
newFrontmatter
|
|
85
|
+
);
|
|
86
|
+
if (!opts.dryRun) {
|
|
87
|
+
await mkdir(dirname(path), { recursive: true });
|
|
88
|
+
await writeFile(path, rendered, "utf8");
|
|
89
|
+
}
|
|
90
|
+
return { path, handle: newHandle, rendered };
|
|
91
|
+
}
|
|
92
|
+
async function resolve(block, ctx = {}) {
|
|
93
|
+
if ("inline" in block) {
|
|
94
|
+
return spec.define(block.inline);
|
|
95
|
+
}
|
|
96
|
+
if ("file" in block) {
|
|
97
|
+
const baseDir = ctx.baseDir ?? ".";
|
|
98
|
+
const path = isAbsolute(block.file) ? block.file : join(baseDir, block.file);
|
|
99
|
+
const { handle } = await load(path);
|
|
100
|
+
return handle;
|
|
101
|
+
}
|
|
102
|
+
if ("ref" in block) {
|
|
103
|
+
if (!ctx.resolveRef) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`${spec.name}.resolve (AIP-${spec.aip}): block has \`ref: '${block.ref}'\` but no resolveRef provided in context \u2014 registries must inject their own resolver`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
const resolved = await ctx.resolveRef(block.ref);
|
|
109
|
+
return spec.define(resolved);
|
|
110
|
+
}
|
|
111
|
+
throw new Error(
|
|
112
|
+
`${spec.name}.resolve (AIP-${spec.aip}): block must have one of inline | ref | file`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
async function deleteFn(path) {
|
|
116
|
+
await rm(path, { force: true });
|
|
117
|
+
}
|
|
118
|
+
return { create, load, list, update, resolve, delete: deleteFn };
|
|
119
|
+
}
|
|
120
|
+
function defaultBody(spec, frontmatter) {
|
|
121
|
+
const id = typeof frontmatter.name === "string" ? frontmatter.name : typeof frontmatter.id === "string" ? frontmatter.id : typeof frontmatter.slug === "string" ? frontmatter.slug : spec.name;
|
|
122
|
+
return `# ${id}
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { createVerbs };
|
|
127
|
+
//# sourceMappingURL=index.mjs.map
|
|
128
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @agentproto/manifest — generic verbs for AIP doctypes.\n *\n * Every per-AIP package (`@agentproto/tool`, `@agentproto/driver`, …)\n * supplies a `DoctypeSpec` describing the per-package knobs (name,\n * AIP number, schema literal, path convention, validator, parser).\n * `createVerbs(spec)` returns the full lifecycle:\n *\n * create(params, opts) params → .md on disk\n * load(path) .md on disk → handle\n * list(dir, filter?) walk → handle[]\n * update(path, mutator) load → patch → write back\n * resolve(block, ctx?) { inline | ref | file } → handle\n * delete(path) fs.unlink\n *\n * Adding a new verb is a single place; every package picks it up\n * automatically — same model as `createDoctype` for the validation\n * pipeline.\n */\n\nimport { filterSerializable } from \"@agentproto/define-doctype\"\nimport matter from \"gray-matter\"\nimport type { Dirent } from \"node:fs\"\nimport { mkdir, readFile, readdir, rm, writeFile } from \"node:fs/promises\"\nimport { dirname, isAbsolute, join } from \"node:path\"\n\n/**\n * Per-package descriptor consumed by `createVerbs`.\n *\n * Generic over the params shape (input to `define`) and the handle\n * shape (output of `define`). For most AIPs `THandle` extends\n * `TParams` — handles are just frozen, defaulted definitions.\n */\nexport interface DoctypeSpec<\n TParams extends { id?: string; slug?: string; name?: string },\n THandle,\n> {\n /** Lower-case singular: \"tool\", \"driver\", \"operator\". Used in errors. */\n name: string\n /** AIP number — surfaces in error messages for provenance. */\n aip: number\n /** Frontmatter `schema:` literal (e.g. \"agentproto/tool/v1\"). */\n schemaLiteral: string\n /**\n * Filename of the doctype's manifest. By default `<DOCTYPE>.md`,\n * derived from `name.toUpperCase()`. Override only when the\n * doctype's filename diverges from convention.\n */\n filename?: string\n /**\n * Path-of-handle convention. Given a validated handle, return the\n * workspace-relative path the manifest lives at. Examples:\n * tool: `(h) => `${h.id}/TOOL.md``\n * policy: `(h) => `policies/${h.slug}/POLICY.md``\n * operator: `(h) => `${h.id}/OPERATOR.md``\n */\n pathOf(handle: THandle): string\n /** TS authoring path: validates params + applies defaults. */\n define(params: TParams): THandle\n /** MD authoring path: parses YAML+body. Throws on invalid frontmatter. */\n parse(source: string): { frontmatter: Record<string, unknown>; body: string }\n /**\n * Optional projection: handle → manifest-shaped frontmatter object.\n * Default: pass the params through `filterSerializable` (drops zod\n * schemas + functions). Override when the handle and frontmatter\n * shapes diverge non-trivially.\n */\n toFrontmatter?(params: TParams): Record<string, unknown>\n}\n\nexport interface CreateOptions {\n /** Workspace-relative or absolute base directory. */\n dir: string\n /** Body markdown after the frontmatter. Defaults to a one-line stub. */\n body?: string\n /** Render only — don't write. Returns the rendered string. */\n dryRun?: boolean\n}\n\nexport interface VerbResult<THandle> {\n path: string\n handle: THandle\n rendered: string\n}\n\nexport interface ListOptions {\n /** Filter the discovered handles. Receives the loaded handle. */\n filter?: <H>(handle: H) => boolean\n /** Skip subdirectories matching these names. Default: [\"node_modules\", \".git\", \"dist\", \".next\"]. */\n skipDirs?: readonly string[]\n}\n\nexport type RefBlock<TParams> =\n | { inline: TParams }\n | { ref: string }\n | { file: string }\n\nexport interface ResolveContext {\n /** Base dir for `file:` references. Required when resolving file blocks. */\n baseDir?: string\n /**\n * Registry resolver for `ref:` strings (e.g. `@agentik/runners/python-3.12`\n * → handle). Optional; throws when an unresolvable ref is encountered.\n */\n resolveRef?: (ref: string) => Promise<unknown> | unknown\n}\n\nexport interface Verbs<TParams, THandle> {\n /** TS-authored params → write `<dir>/<pathOf(handle)>` on disk. */\n create(params: TParams, opts: CreateOptions): Promise<VerbResult<THandle>>\n /** Read a manifest from disk → handle. */\n load(path: string): Promise<{ path: string; handle: THandle; body: string }>\n /** Walk `dir` for the doctype's `*.md` files; return loaded handles. */\n list(dir: string, opts?: ListOptions): Promise<THandle[]>\n /** Load → mutate → write back. Mutator receives the loaded params. */\n update(\n path: string,\n mutator: (params: TParams, ctx: { handle: THandle; body: string }) => TParams | Promise<TParams>,\n opts?: { body?: string; dryRun?: boolean },\n ): Promise<VerbResult<THandle>>\n /**\n * Resolve a `{ inline | ref | file }` block to a handle. Used by\n * doctypes that compose other doctypes (AIP-17/30/36 pattern).\n */\n resolve(\n block: RefBlock<TParams>,\n ctx?: ResolveContext,\n ): Promise<THandle>\n /** Remove the manifest file. Does NOT recurse into the containing dir. */\n delete(path: string): Promise<void>\n}\n\nconst DEFAULT_SKIP_DIRS: readonly string[] = [\n \"node_modules\",\n \".git\",\n \"dist\",\n \".next\",\n \".turbo\",\n]\n\nexport function createVerbs<\n TParams extends { id?: string; slug?: string; name?: string },\n THandle,\n>(spec: DoctypeSpec<TParams, THandle>): Verbs<TParams, THandle> {\n const filename = spec.filename ?? `${spec.name.toUpperCase()}.md`\n const toFrontmatter =\n spec.toFrontmatter ??\n ((params: TParams) =>\n filterSerializable({\n schema: spec.schemaLiteral,\n ...params,\n }) as Record<string, unknown>)\n\n async function create(\n params: TParams,\n opts: CreateOptions,\n ): Promise<VerbResult<THandle>> {\n const handle = spec.define(params)\n const relativePath = spec.pathOf(handle)\n const path = join(opts.dir, relativePath)\n const frontmatter = toFrontmatter(params)\n const rendered = matter.stringify(\n opts.body ?? defaultBody(spec, frontmatter),\n frontmatter,\n )\n if (!opts.dryRun) {\n await mkdir(dirname(path), { recursive: true })\n await writeFile(path, rendered, \"utf8\")\n }\n return { path, handle, rendered }\n }\n\n async function load(\n path: string,\n ): Promise<{ path: string; handle: THandle; body: string }> {\n const source = await readFile(path, \"utf8\")\n const { frontmatter, body } = spec.parse(source)\n // The frontmatter is already zod-validated by `parse` (each per-\n // package parser runs the schema). Cast through `define` so the\n // cross-AIP invariants run too — same path the TS authoring uses.\n const handle = spec.define(frontmatter as unknown as TParams)\n return { path, handle, body }\n }\n\n async function list(\n dir: string,\n opts: ListOptions = {},\n ): Promise<THandle[]> {\n const skip = new Set(opts.skipDirs ?? DEFAULT_SKIP_DIRS)\n const out: THandle[] = []\n\n async function walk(current: string): Promise<void> {\n // The Dirent overload returns `Dirent<NonSharedBuffer>[]` in\n // Node 22+'s @types/node when called with non-string buffer\n // input. Coerce by typing the entries as `Dirent[]` (string\n // names by default) since we always pass a string path.\n let entries: Dirent[]\n try {\n entries = (await readdir(current, { withFileTypes: true })) as Dirent[]\n } catch {\n return\n }\n for (const entry of entries) {\n const entryName = String(entry.name)\n if (entry.isDirectory()) {\n if (skip.has(entryName)) continue\n await walk(join(current, entryName))\n continue\n }\n if (!entry.isFile()) continue\n if (entryName !== filename) continue\n try {\n const { handle } = await load(join(current, entryName))\n if (!opts.filter || opts.filter(handle)) out.push(handle)\n } catch {\n // Malformed manifests skip the list — `load` will throw the\n // useful diagnostic when a caller wants to see it.\n }\n }\n }\n\n await walk(dir)\n return out\n }\n\n async function update(\n path: string,\n mutator: (\n params: TParams,\n ctx: { handle: THandle; body: string },\n ) => TParams | Promise<TParams>,\n opts: { body?: string; dryRun?: boolean } = {},\n ): Promise<VerbResult<THandle>> {\n const { handle, body } = await load(path)\n // Reconstruct params from the frontmatter — the load path already\n // validated, so this is just a reverse projection.\n const source = await readFile(path, \"utf8\")\n const { frontmatter } = spec.parse(source)\n const params = frontmatter as unknown as TParams\n const mutated = await mutator(params, { handle, body })\n const newHandle = spec.define(mutated)\n const newFrontmatter = toFrontmatter(mutated)\n const rendered = matter.stringify(\n opts.body ?? body,\n newFrontmatter,\n )\n if (!opts.dryRun) {\n await mkdir(dirname(path), { recursive: true })\n await writeFile(path, rendered, \"utf8\")\n }\n return { path, handle: newHandle, rendered }\n }\n\n async function resolve(\n block: RefBlock<TParams>,\n ctx: ResolveContext = {},\n ): Promise<THandle> {\n if (\"inline\" in block) {\n return spec.define(block.inline)\n }\n if (\"file\" in block) {\n const baseDir = ctx.baseDir ?? \".\"\n const path = isAbsolute(block.file) ? block.file : join(baseDir, block.file)\n const { handle } = await load(path)\n return handle\n }\n if (\"ref\" in block) {\n if (!ctx.resolveRef) {\n throw new Error(\n `${spec.name}.resolve (AIP-${spec.aip}): block has \\`ref: '${block.ref}'\\` but no resolveRef provided in context — registries must inject their own resolver`,\n )\n }\n const resolved = await ctx.resolveRef(block.ref)\n return spec.define(resolved as TParams)\n }\n throw new Error(\n `${spec.name}.resolve (AIP-${spec.aip}): block must have one of inline | ref | file`,\n )\n }\n\n async function deleteFn(path: string): Promise<void> {\n await rm(path, { force: true })\n }\n\n return { create, load, list, update, resolve, delete: deleteFn }\n}\n\nfunction defaultBody<P, H>(\n spec: DoctypeSpec<P extends { id?: string; slug?: string; name?: string } ? P : never, H>,\n frontmatter: Record<string, unknown>,\n): string {\n const id =\n typeof frontmatter.name === \"string\"\n ? frontmatter.name\n : typeof frontmatter.id === \"string\"\n ? frontmatter.id\n : typeof frontmatter.slug === \"string\"\n ? frontmatter.slug\n : spec.name\n return `# ${id}\\n`\n}\n\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentproto/manifest",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "agentproto/manifest — generic verbs for AIP doctypes (create, load, list, update, resolve, delete). Each per-AIP package supplies a DoctypeSpec describing its name / AIP number / schema literal / path convention / validator+parser; createVerbs(spec) returns the full lifecycle. Adding a new verb (hash, sign, migrate, …) is one place; every package picks it up automatically.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agentproto",
|
|
7
|
+
"manifest",
|
|
8
|
+
"io",
|
|
9
|
+
"verbs",
|
|
10
|
+
"createX",
|
|
11
|
+
"loadX",
|
|
12
|
+
"listX",
|
|
13
|
+
"open-standard"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://agentproto.sh",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/agentproto/ts",
|
|
19
|
+
"directory": "packages/manifest"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/agentproto/ts/issues"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "dist/index.mjs",
|
|
27
|
+
"module": "dist/index.mjs",
|
|
28
|
+
"types": "dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.mjs",
|
|
33
|
+
"default": "./dist/index.mjs"
|
|
34
|
+
},
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
],
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"gray-matter": "^4.0.3",
|
|
47
|
+
"@agentproto/define-doctype": "0.1.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^25.6.2",
|
|
51
|
+
"tsup": "^8.5.1",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
|
+
"vitest": "^3.2.4",
|
|
54
|
+
"@agentproto/tooling": "0.1.0-alpha.0"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"dev": "tsup --watch",
|
|
58
|
+
"build": "tsup",
|
|
59
|
+
"clean": "rm -rf dist",
|
|
60
|
+
"check-types": "tsc --noEmit",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:watch": "vitest"
|
|
63
|
+
}
|
|
64
|
+
}
|