@archon-claw/cli 0.0.4 → 0.2.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/session.js CHANGED
@@ -1,82 +1,86 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- let sessionsDir = "";
5
- const sessionCache = new Map();
6
- export async function initSessionStore(agentDir) {
7
- sessionsDir = path.join(agentDir, "sessions");
8
- sessionCache.clear();
9
- await fs.mkdir(sessionsDir, { recursive: true });
10
- }
11
- function sessionPath(id) {
12
- return path.join(sessionsDir, `${id}.json`);
13
- }
14
- export async function saveSession(session) {
15
- session.updatedAt = Date.now();
16
- sessionCache.set(session.id, session);
17
- await fs.writeFile(sessionPath(session.id), JSON.stringify(session, null, 2));
18
- }
19
- export async function createSession() {
20
- const now = Date.now();
21
- const session = {
22
- id: crypto.randomUUID(),
23
- messages: [],
24
- createdAt: now,
25
- updatedAt: now,
26
- };
27
- await saveSession(session);
28
- return session;
29
- }
30
- export async function getSession(id) {
31
- const cached = sessionCache.get(id);
32
- if (cached)
33
- return cached;
34
- try {
35
- const data = await fs.readFile(sessionPath(id), "utf-8");
36
- const session = JSON.parse(data);
37
- sessionCache.set(id, session);
38
- return session;
4
+ export class SessionStore {
5
+ dir;
6
+ cache = new Map();
7
+ constructor(agentDir) {
8
+ this.dir = path.join(agentDir, "sessions");
39
9
  }
40
- catch {
41
- return undefined;
10
+ async init() {
11
+ this.cache.clear();
12
+ await fs.mkdir(this.dir, { recursive: true });
42
13
  }
43
- }
44
- export async function getOrCreateSession(id) {
45
- if (id) {
46
- const existing = await getSession(id);
47
- if (existing)
48
- return existing;
14
+ filePath(id) {
15
+ return path.join(this.dir, `${id}.json`);
49
16
  }
50
- return createSession();
51
- }
52
- export async function deleteSession(id) {
53
- sessionCache.delete(id);
54
- try {
55
- await fs.unlink(sessionPath(id));
56
- return true;
17
+ async save(session) {
18
+ session.updatedAt = Date.now();
19
+ this.cache.set(session.id, session);
20
+ await fs.writeFile(this.filePath(session.id), JSON.stringify(session, null, 2));
57
21
  }
58
- catch {
59
- return false;
22
+ async create() {
23
+ const now = Date.now();
24
+ const session = {
25
+ id: crypto.randomUUID(),
26
+ messages: [],
27
+ createdAt: now,
28
+ updatedAt: now,
29
+ };
30
+ await this.save(session);
31
+ return session;
60
32
  }
61
- }
62
- export async function listSessions() {
63
- try {
64
- const files = await fs.readdir(sessionsDir);
65
- const sessions = [];
66
- for (const file of files) {
67
- if (!file.endsWith(".json"))
68
- continue;
69
- try {
70
- const data = await fs.readFile(path.join(sessionsDir, file), "utf-8");
71
- sessions.push(JSON.parse(data));
72
- }
73
- catch {
74
- // skip corrupt files
75
- }
33
+ async get(id) {
34
+ const cached = this.cache.get(id);
35
+ if (cached)
36
+ return cached;
37
+ try {
38
+ const data = await fs.readFile(this.filePath(id), "utf-8");
39
+ const session = JSON.parse(data);
40
+ this.cache.set(id, session);
41
+ return session;
42
+ }
43
+ catch {
44
+ return undefined;
45
+ }
46
+ }
47
+ async getOrCreate(id) {
48
+ if (id) {
49
+ const existing = await this.get(id);
50
+ if (existing)
51
+ return existing;
52
+ }
53
+ return this.create();
54
+ }
55
+ async delete(id) {
56
+ this.cache.delete(id);
57
+ try {
58
+ await fs.unlink(this.filePath(id));
59
+ return true;
60
+ }
61
+ catch {
62
+ return false;
76
63
  }
77
- return sessions;
78
64
  }
79
- catch {
80
- return [];
65
+ async list() {
66
+ try {
67
+ const files = await fs.readdir(this.dir);
68
+ const sessions = [];
69
+ for (const file of files) {
70
+ if (!file.endsWith(".json"))
71
+ continue;
72
+ try {
73
+ const data = await fs.readFile(path.join(this.dir, file), "utf-8");
74
+ sessions.push(JSON.parse(data));
75
+ }
76
+ catch {
77
+ // skip corrupt files
78
+ }
79
+ }
80
+ return sessions;
81
+ }
82
+ catch {
83
+ return [];
84
+ }
81
85
  }
82
86
  }
@@ -0,0 +1,13 @@
1
+ # {{name}}
2
+
3
+ Built with [archon-claw](https://github.com/anthropics/archon-claw).
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ # Start dev server
9
+ npm run dev
10
+
11
+ # Start production server
12
+ npm run start
13
+ ```
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "archon-claw dev agents/my-agent",
8
+ "start": "archon-claw start agents/my-agent -p ${PORT:-5100}",
9
+ "create:agent": "archon-claw create-agent",
10
+ "update:skills": "archon-claw update-skills"
11
+ },
12
+ "keywords": [],
13
+ "license": "ISC",
14
+ "dependencies": {
15
+ "@archon-claw/cli": "^{{cliVersion}}"
16
+ }
17
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ValidatorPlugin, DirValidatorPlugin, ValidationResult } from "./plugin.js";
2
- export type { ValidatorPlugin, DirValidatorPlugin, ValidationResult } from "./plugin.js";
2
+ export type { ValidatorPlugin, DirValidatorPlugin, ValidationResult, ValidationError } from "./plugin.js";
3
3
  /** Get a content validator plugin by name */
4
4
  export declare function getPlugin(name: string): ValidatorPlugin | undefined;
5
5
  /** Get all registered content plugins */
@@ -8,6 +8,8 @@ export declare function getPlugins(): ValidatorPlugin[];
8
8
  export declare function getDirPlugin(name: string): DirValidatorPlugin | undefined;
9
9
  /** Register a custom content plugin */
10
10
  export declare function registerPlugin(plugin: ValidatorPlugin): void;
11
+ /** Unregister a content plugin by name */
12
+ export declare function unregisterPlugin(name: string): boolean;
11
13
  /** Register a custom directory plugin */
12
14
  export declare function registerDirPlugin(plugin: DirValidatorPlugin): void;
13
15
  /** Validate content using a specific plugin by name */
@@ -32,6 +32,14 @@ export function getDirPlugin(name) {
32
32
  export function registerPlugin(plugin) {
33
33
  contentPlugins.push(plugin);
34
34
  }
35
+ /** Unregister a content plugin by name */
36
+ export function unregisterPlugin(name) {
37
+ const idx = contentPlugins.findIndex((p) => p.name === name);
38
+ if (idx === -1)
39
+ return false;
40
+ contentPlugins.splice(idx, 1);
41
+ return true;
42
+ }
35
43
  /** Register a custom directory plugin */
36
44
  export function registerDirPlugin(plugin) {
37
45
  dirPlugins.push(plugin);
@@ -40,7 +48,7 @@ export function registerDirPlugin(plugin) {
40
48
  export function validate(pluginName, content, fileName) {
41
49
  const plugin = getPlugin(pluginName);
42
50
  if (!plugin) {
43
- return { valid: false, errors: [`Unknown validator plugin: ${pluginName}`] };
51
+ return { valid: false, errors: [{ message: `Unknown validator plugin: ${pluginName}`, severity: "error" }] };
44
52
  }
45
53
  return plugin.validate(content, fileName);
46
54
  }
@@ -48,7 +56,7 @@ export function validate(pluginName, content, fileName) {
48
56
  export async function validateDir(pluginName, dirPath) {
49
57
  const plugin = getDirPlugin(pluginName);
50
58
  if (!plugin) {
51
- return { valid: false, errors: [`Unknown dir validator plugin: ${pluginName}`] };
59
+ return { valid: false, errors: [{ message: `Unknown dir validator plugin: ${pluginName}`, severity: "error" }] };
52
60
  }
53
61
  return plugin.validate(dirPath);
54
62
  }
@@ -1,7 +1,17 @@
1
+ /** Structured validation error */
2
+ export interface ValidationError {
3
+ message: string;
4
+ severity: "error" | "warning";
5
+ file?: string;
6
+ path?: string;
7
+ expected?: string;
8
+ received?: string;
9
+ line?: number;
10
+ }
1
11
  /** Validation result */
2
12
  export interface ValidationResult {
3
13
  valid: boolean;
4
- errors: string[];
14
+ errors: ValidationError[];
5
15
  }
6
16
  /** Content validator plugin - validates file content */
7
17
  export interface ValidatorPlugin {
@@ -9,6 +19,8 @@ export interface ValidatorPlugin {
9
19
  name: string;
10
20
  /** File pattern this plugin handles, e.g. "*.json", "*.md" */
11
21
  pattern: string;
22
+ /** Whether this plugin's file(s) are required in an agent directory */
23
+ required?: boolean;
12
24
  /** Validate raw content, return result */
13
25
  validate(content: string, fileName?: string): ValidationResult;
14
26
  }
@@ -1,170 +1,154 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { modelPlugin } from "./model.js";
4
- import { systemPromptPlugin } from "./system-prompt.js";
5
- import { toolPlugin } from "./tool.js";
6
- import { agentSkillPlugin } from "./agent-skill.js";
7
- import { datasetPlugin } from "./dataset.js";
8
- import { mcpPlugin } from "./mcp.js";
9
- export const agentDirPlugin = {
10
- name: "agent-dir",
11
- async validate(dirPath) {
12
- const errors = [];
13
- // Check directory exists
3
+ import picomatch from "picomatch";
4
+ import { getPlugins } from "../index.js";
5
+ /**
6
+ * Given a pattern like "model.json" or "tools/*.json", find matching files
7
+ * in the agent directory. Returns relative paths (e.g. "tools/t.json").
8
+ */
9
+ async function matchFiles(dirPath, pattern) {
10
+ const parts = pattern.split("/");
11
+ if (parts.length === 1) {
12
+ // Flat file pattern like "model.json" or "custom-config.json"
13
+ const matcher = picomatch(pattern);
14
14
  try {
15
- const stat = await fs.stat(dirPath);
16
- if (!stat.isDirectory()) {
17
- return { valid: false, errors: [`${dirPath} is not a directory`] };
18
- }
15
+ const entries = await fs.readdir(dirPath);
16
+ return entries.filter((f) => matcher(f));
19
17
  }
20
18
  catch {
21
- return { valid: false, errors: [`${dirPath} does not exist`] };
19
+ return [];
22
20
  }
23
- // Check system-prompt.md
24
- const promptPath = path.join(dirPath, "system-prompt.md");
21
+ }
22
+ // Directory pattern like "tools/*.json" or "skills/*.md"
23
+ const subDir = parts.slice(0, -1).join("/");
24
+ const filePattern = parts[parts.length - 1];
25
+ const matcher = picomatch(filePattern);
26
+ const fullSubDir = path.join(dirPath, subDir);
27
+ try {
28
+ const stat = await fs.stat(fullSubDir);
29
+ if (!stat.isDirectory())
30
+ return [];
31
+ const entries = await fs.readdir(fullSubDir);
32
+ return entries.filter((f) => matcher(f)).map((f) => `${subDir}/${f}`);
33
+ }
34
+ catch {
35
+ return [];
36
+ }
37
+ }
38
+ /**
39
+ * Validate tools/ + tool-impls/ cross-matching.
40
+ * This is special logic that cannot be driven purely by pattern matching.
41
+ * Receives pre-read tool file contents to avoid reading/validating them again.
42
+ */
43
+ async function validateToolImpls(dirPath, toolContents, errors) {
44
+ const toolNames = [];
45
+ const nonServerToolNames = new Set();
46
+ for (const [relPath, content] of toolContents) {
47
+ const name = path.basename(relPath, ".json");
48
+ toolNames.push(name);
25
49
  try {
26
- const content = await fs.readFile(promptPath, "utf-8");
27
- const result = systemPromptPlugin.validate(content, "system-prompt.md");
28
- if (!result.valid) {
29
- errors.push(...result.errors.map((e) => `[system-prompt.md] ${e}`));
50
+ const parsed = JSON.parse(content);
51
+ if (parsed.execution_target === "client" || parsed.execution_target === "host") {
52
+ nonServerToolNames.add(name);
30
53
  }
31
54
  }
32
- catch {
33
- errors.push("Missing required file: system-prompt.md");
34
- }
35
- // Check model.json
36
- const modelPath = path.join(dirPath, "model.json");
37
- try {
38
- const content = await fs.readFile(modelPath, "utf-8");
39
- const result = modelPlugin.validate(content, "model.json");
40
- if (!result.valid) {
41
- errors.push(...result.errors.map((e) => `[model.json] ${e}`));
55
+ catch { /* already validated in main loop */ }
56
+ }
57
+ const serverToolNames = toolNames.filter((n) => !nonServerToolNames.has(n));
58
+ // Check tool-impls/
59
+ const implsDir = path.join(dirPath, "tool-impls");
60
+ try {
61
+ const implStat = await fs.stat(implsDir);
62
+ if (implStat.isDirectory()) {
63
+ const implFiles = await fs.readdir(implsDir);
64
+ const implNames = implFiles
65
+ .filter((f) => f.endsWith(".impl.js"))
66
+ .map((f) => f.replace(/\.impl\.js$/, ""));
67
+ for (const name of serverToolNames) {
68
+ if (!implNames.includes(name)) {
69
+ errors.push({ message: `Missing implementation for tool: ${name}`, severity: "error", file: "tool-impls/" });
70
+ }
71
+ }
72
+ for (const name of implNames) {
73
+ if (!toolNames.includes(name)) {
74
+ errors.push({ message: `No matching tool schema in tools/ for ${name}.impl.js`, severity: "warning", file: `tool-impls/${name}.impl.js` });
75
+ }
42
76
  }
43
77
  }
44
- catch {
45
- errors.push("Missing required file: model.json");
78
+ else {
79
+ errors.push({ message: "tool-impls is not a directory", severity: "error", file: "tool-impls" });
46
80
  }
47
- // Check tools/ directory
48
- const toolsDir = path.join(dirPath, "tools");
81
+ }
82
+ catch {
83
+ if (serverToolNames.length > 0) {
84
+ errors.push({ message: "Missing required directory: tool-impls/", severity: "error", file: "tool-impls/" });
85
+ }
86
+ }
87
+ }
88
+ export const agentDirPlugin = {
89
+ name: "agent-dir",
90
+ async validate(dirPath) {
91
+ const errors = [];
92
+ // Check directory exists
49
93
  try {
50
- const stat = await fs.stat(toolsDir);
94
+ const stat = await fs.stat(dirPath);
51
95
  if (!stat.isDirectory()) {
52
- errors.push("tools is not a directory");
96
+ return { valid: false, errors: [{ message: `${dirPath} is not a directory`, severity: "error", file: dirPath }] };
53
97
  }
54
- else {
55
- const files = await fs.readdir(toolsDir);
56
- const jsonFiles = files.filter((f) => f.endsWith(".json"));
57
- if (jsonFiles.length === 0) {
58
- errors.push("[tools/] No tool definitions found");
59
- }
60
- const toolNames = [];
61
- const nonServerToolNames = new Set();
62
- for (const file of jsonFiles) {
63
- const content = await fs.readFile(path.join(toolsDir, file), "utf-8");
64
- const result = toolPlugin.validate(content, file);
65
- if (!result.valid) {
66
- errors.push(...result.errors.map((e) => `[tools/${file}] ${e}`));
67
- }
68
- else {
69
- const name = file.replace(/\.json$/, "");
70
- toolNames.push(name);
71
- // Check if tool has execution_target that doesn't need server impl
72
- try {
73
- const parsed = JSON.parse(content);
74
- if (parsed.execution_target === "client" || parsed.execution_target === "host") {
75
- nonServerToolNames.add(name);
76
- }
77
- }
78
- catch { /* already validated above */ }
79
- }
98
+ }
99
+ catch {
100
+ return { valid: false, errors: [{ message: `${dirPath} does not exist`, severity: "error", file: dirPath }] };
101
+ }
102
+ const plugins = getPlugins();
103
+ // Track valid tool file contents for cross-matching with tool-impls
104
+ const validToolContents = new Map();
105
+ for (const plugin of plugins) {
106
+ const files = await matchFiles(dirPath, plugin.pattern);
107
+ if (files.length === 0) {
108
+ if (plugin.required) {
109
+ errors.push({
110
+ message: `Missing required file: ${plugin.pattern}`,
111
+ severity: "error",
112
+ file: plugin.pattern,
113
+ });
80
114
  }
81
- // Server tools that need impl files
82
- const serverToolNames = toolNames.filter((n) => !nonServerToolNames.has(n));
83
- // Check tool-impls/ matches server tools
84
- const implsDir = path.join(dirPath, "tool-impls");
85
- try {
86
- const implStat = await fs.stat(implsDir);
87
- if (implStat.isDirectory()) {
88
- const implFiles = await fs.readdir(implsDir);
89
- const implNames = implFiles
90
- .filter((f) => f.endsWith(".impl.js"))
91
- .map((f) => f.replace(/\.impl\.js$/, ""));
92
- // server tools without impl
93
- for (const name of serverToolNames) {
94
- if (!implNames.includes(name)) {
95
- errors.push(`[tool-impls/] Missing implementation for tool: ${name}`);
96
- }
115
+ // Special case: tool plugin needs at least 1 tool file
116
+ if (plugin.name === "tool") {
117
+ // Check if the tools/ directory exists at all
118
+ const toolsDir = path.join(dirPath, "tools");
119
+ try {
120
+ const stat = await fs.stat(toolsDir);
121
+ if (!stat.isDirectory()) {
122
+ errors.push({ message: "tools is not a directory", severity: "error", file: "tools" });
97
123
  }
98
- // impls without tool
99
- for (const name of implNames) {
100
- if (!toolNames.includes(name)) {
101
- errors.push(`[tool-impls/${name}.impl.js] No matching tool schema in tools/`);
102
- }
124
+ else {
125
+ errors.push({ message: "No tool definitions found", severity: "error", file: "tools/" });
103
126
  }
104
127
  }
105
- else {
106
- errors.push("tool-impls is not a directory");
107
- }
108
- }
109
- catch {
110
- if (serverToolNames.length > 0) {
111
- errors.push("Missing required directory: tool-impls/");
128
+ catch {
129
+ errors.push({ message: "Missing required directory: tools/", severity: "error", file: "tools/" });
112
130
  }
113
131
  }
132
+ continue;
114
133
  }
115
- }
116
- catch {
117
- errors.push("Missing required directory: tools/");
118
- }
119
- // Check datasets/ directory (optional)
120
- const datasetsDir = path.join(dirPath, "datasets");
121
- try {
122
- const stat = await fs.stat(datasetsDir);
123
- if (stat.isDirectory()) {
124
- const files = await fs.readdir(datasetsDir);
125
- const jsonFiles = files.filter((f) => f.endsWith(".json"));
126
- for (const file of jsonFiles) {
127
- const content = await fs.readFile(path.join(datasetsDir, file), "utf-8");
128
- const result = datasetPlugin.validate(content, file);
129
- if (!result.valid) {
130
- errors.push(...result.errors.map((e) => `[datasets/${file}] ${e}`));
131
- }
134
+ // Validate each matched file
135
+ for (const relPath of files) {
136
+ const absPath = path.join(dirPath, relPath);
137
+ const content = await fs.readFile(absPath, "utf-8");
138
+ const fileName = path.basename(relPath);
139
+ const result = plugin.validate(content, fileName);
140
+ if (!result.valid) {
141
+ errors.push(...result.errors.map((e) => ({ ...e, file: relPath })));
132
142
  }
133
- }
134
- }
135
- catch {
136
- // datasets/ is optional, skip if not exists
137
- }
138
- // Check skills/ directory (optional)
139
- const skillsDir = path.join(dirPath, "skills");
140
- try {
141
- const stat = await fs.stat(skillsDir);
142
- if (stat.isDirectory()) {
143
- const files = await fs.readdir(skillsDir);
144
- const mdFiles = files.filter((f) => f.endsWith(".md"));
145
- for (const file of mdFiles) {
146
- const content = await fs.readFile(path.join(skillsDir, file), "utf-8");
147
- const result = agentSkillPlugin.validate(content, file);
148
- if (!result.valid) {
149
- errors.push(...result.errors.map((e) => `[skills/${file}] ${e}`));
150
- }
143
+ else if (plugin.name === "tool") {
144
+ // Track valid tool contents for impl cross-matching
145
+ validToolContents.set(relPath, content);
151
146
  }
152
147
  }
153
148
  }
154
- catch {
155
- // skills/ is optional, skip if not exists
156
- }
157
- // Check mcp.json (optional)
158
- const mcpPath = path.join(dirPath, "mcp.json");
159
- try {
160
- const content = await fs.readFile(mcpPath, "utf-8");
161
- const result = mcpPlugin.validate(content, "mcp.json");
162
- if (!result.valid) {
163
- errors.push(...result.errors.map((e) => `[mcp.json] ${e}`));
164
- }
165
- }
166
- catch {
167
- // mcp.json is optional, skip if not exists
149
+ // tools/ + tool-impls/ cross-matching (special logic)
150
+ if (validToolContents.size > 0) {
151
+ await validateToolImpls(dirPath, validToolContents, errors);
168
152
  }
169
153
  return { valid: errors.length === 0, errors };
170
154
  },
@@ -11,20 +11,24 @@ export const agentSkillPlugin = {
11
11
  parsed = matter(content);
12
12
  }
13
13
  catch {
14
- return { valid: false, errors: ["Invalid frontmatter syntax"] };
14
+ return { valid: false, errors: [{ message: "Invalid frontmatter syntax", severity: "error" }] };
15
15
  }
16
16
  if (!parsed.data || Object.keys(parsed.data).length === 0) {
17
- errors.push("Missing frontmatter (---) block");
17
+ errors.push({ message: "Missing frontmatter (---) block", severity: "error" });
18
18
  }
19
19
  else {
20
20
  const result = agentSkillFrontmatterSchema.safeParse(parsed.data);
21
21
  if (!result.success) {
22
- const schemaErrors = formatZodErrors(result.error).map((e) => `frontmatter${e}`);
22
+ const schemaErrors = formatZodErrors(result.error, parsed.data).map((e) => ({
23
+ ...e,
24
+ message: `frontmatter${e.message}`,
25
+ path: e.path ? `frontmatter${e.path}` : e.path,
26
+ }));
23
27
  errors.push(...schemaErrors);
24
28
  }
25
29
  }
26
30
  if (!parsed.content.trim()) {
27
- errors.push("Skill body content is empty");
31
+ errors.push({ message: "Skill body content is empty", severity: "error" });
28
32
  }
29
33
  return { valid: errors.length === 0, errors };
30
34
  },
@@ -9,11 +9,11 @@ export const datasetPlugin = {
9
9
  data = JSON.parse(content);
10
10
  }
11
11
  catch {
12
- return { valid: false, errors: ["Invalid JSON"] };
12
+ return { valid: false, errors: [{ message: "Invalid JSON", severity: "error" }] };
13
13
  }
14
14
  const result = datasetSchema.safeParse(data);
15
15
  if (!result.success) {
16
- return { valid: false, errors: formatZodErrors(result.error) };
16
+ return { valid: false, errors: formatZodErrors(result.error, data, content) };
17
17
  }
18
18
  return { valid: true, errors: [] };
19
19
  },
@@ -9,11 +9,11 @@ export const mcpPlugin = {
9
9
  data = JSON.parse(content);
10
10
  }
11
11
  catch {
12
- return { valid: false, errors: ["Invalid JSON"] };
12
+ return { valid: false, errors: [{ message: "Invalid JSON", severity: "error" }] };
13
13
  }
14
14
  const result = mcpConfigSchema.safeParse(data);
15
15
  if (!result.success) {
16
- return { valid: false, errors: formatZodErrors(result.error) };
16
+ return { valid: false, errors: formatZodErrors(result.error, data, content) };
17
17
  }
18
18
  return { valid: true, errors: [] };
19
19
  },
@@ -3,17 +3,18 @@ import { formatZodErrors } from "../zod-utils.js";
3
3
  export const modelPlugin = {
4
4
  name: "model",
5
5
  pattern: "model.json",
6
+ required: true,
6
7
  validate(content, _fileName) {
7
8
  let data;
8
9
  try {
9
10
  data = JSON.parse(content);
10
11
  }
11
12
  catch {
12
- return { valid: false, errors: ["Invalid JSON"] };
13
+ return { valid: false, errors: [{ message: "Invalid JSON", severity: "error" }] };
13
14
  }
14
15
  const result = modelConfigSchema.safeParse(data);
15
16
  if (!result.success) {
16
- return { valid: false, errors: formatZodErrors(result.error) };
17
+ return { valid: false, errors: formatZodErrors(result.error, data, content) };
17
18
  }
18
19
  return { valid: true, errors: [] };
19
20
  },