@plico-ai/core 0.0.0-reserved.0 → 0.1.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/NOTICE ADDED
@@ -0,0 +1,4 @@
1
+ Plico
2
+ Copyright 2026 Plico contributors
3
+
4
+ This product includes software developed by the Plico contributors.
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # @plico-ai/core
2
2
 
3
- Project loading and validation for Plico.
3
+ Core project-loading and validation helpers for Plico.
4
4
 
5
- This is a pre-release placeholder. The first developer preview is in progress at
6
- https://github.com/ITZSHOAIB/plico. Docs: https://plico.sohab.dev
5
+ ## API
6
+
7
+ - `loadProject(root)` loads `plico.config.ts` and returns the normalized project config
8
+ - `validateProject(root)` validates the project shape, required files and directories, and duplicate tool names
9
+
10
+ This package stays local-first and file-first. It does not know about runtime execution or provider-specific agent abstractions.
@@ -0,0 +1,8 @@
1
+ import { type Frontmatter } from "./frontmatter.js";
2
+ import type { ValidationIssue } from "./types.js";
3
+ /** Frontmatter keys `agent.md` understands. Later features add keys here without breaking projects. */
4
+ export declare const AGENT_FRONTMATTER_KEYS: readonly string[];
5
+ /** Splits `agent.md` into its frontmatter and the instructions the model sees. */
6
+ export declare function parseAgentFile(text: string): Frontmatter;
7
+ /** One warning per frontmatter key that `agent.md` does not understand. */
8
+ export declare function unknownAgentKeyWarnings(path: string, data: Frontmatter["data"]): ValidationIssue[];
@@ -0,0 +1,17 @@
1
+ import { parseFrontmatter } from "./frontmatter.js";
2
+ /** Frontmatter keys `agent.md` understands. Later features add keys here without breaking projects. */
3
+ export const AGENT_FRONTMATTER_KEYS = [];
4
+ /** Splits `agent.md` into its frontmatter and the instructions the model sees. */
5
+ export function parseAgentFile(text) {
6
+ return parseFrontmatter(text);
7
+ }
8
+ /** One warning per frontmatter key that `agent.md` does not understand. */
9
+ export function unknownAgentKeyWarnings(path, data) {
10
+ return Object.keys(data)
11
+ .filter((key) => !AGENT_FRONTMATTER_KEYS.includes(key))
12
+ .map((key) => ({
13
+ path,
14
+ message: `Unknown frontmatter key "${key}" in ${path}. It is ignored and never sent to the model.`,
15
+ severity: "warning",
16
+ }));
17
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Project files import `@plico-ai/sdk` for `defineConfig` and `defineTool`. Those helpers return
3
+ * their argument unchanged, so any copy works. A project run by a global `plico` CLI may have no
4
+ * node_modules of its own; resolution falls back to the copy that ships with this install.
5
+ */
6
+ export declare const SPECIFIER = "@plico-ai/sdk";
7
+ /** The file URL the `@plico-ai/sdk` specifier should resolve to for a file in `fromFile`'s project. */
8
+ export declare function resolveAuthoringModule(fromFile: string): string;
9
+ /** Point `import "@plico-ai/sdk"` in a transpiled project file at an absolute file URL. */
10
+ export declare function rewriteAuthoringImports(code: string, fromFile: string): string;
11
+ /** Make `import "@plico-ai/sdk"` and `require("@plico-ai/sdk")` fall back to this install when a tool file cannot resolve it. */
12
+ export declare function installAuthoringFallback(): void;
@@ -0,0 +1,64 @@
1
+ import Module, { createRequire, register } from "node:module";
2
+ import { dirname } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ /**
5
+ * Project files import `@plico-ai/sdk` for `defineConfig` and `defineTool`. Those helpers return
6
+ * their argument unchanged, so any copy works. A project run by a global `plico` CLI may have no
7
+ * node_modules of its own; resolution falls back to the copy that ships with this install.
8
+ */
9
+ export const SPECIFIER = "@plico-ai/sdk";
10
+ function ownEntry() {
11
+ return createRequire(import.meta.url).resolve(SPECIFIER);
12
+ }
13
+ /** The file URL the `@plico-ai/sdk` specifier should resolve to for a file in `fromFile`'s project. */
14
+ export function resolveAuthoringModule(fromFile) {
15
+ try {
16
+ return pathToFileURL(createRequire(fromFile).resolve(SPECIFIER)).href;
17
+ }
18
+ catch {
19
+ return pathToFileURL(ownEntry()).href;
20
+ }
21
+ }
22
+ /** Point `import "@plico-ai/sdk"` in a transpiled project file at an absolute file URL. */
23
+ export function rewriteAuthoringImports(code, fromFile) {
24
+ return code.replace(/(\bfrom\s*|\bimport\s*)(["'])@plico-ai\/sdk\2/g, (_match, keyword) => `${keyword}${JSON.stringify(resolveAuthoringModule(fromFile))}`);
25
+ }
26
+ const ESM_HOOKS = `
27
+ let fallback;
28
+ export function initialize(data) { fallback = data.url; }
29
+ export async function resolve(specifier, context, nextResolve) {
30
+ try {
31
+ return await nextResolve(specifier, context);
32
+ } catch (error) {
33
+ if (specifier === "${SPECIFIER}" && error?.code === "ERR_MODULE_NOT_FOUND") {
34
+ return { url: fallback, shortCircuit: true };
35
+ }
36
+ throw error;
37
+ }
38
+ }
39
+ `;
40
+ let installed = false;
41
+ /** Make `import "@plico-ai/sdk"` and `require("@plico-ai/sdk")` fall back to this install when a tool file cannot resolve it. */
42
+ export function installAuthoringFallback() {
43
+ if (installed)
44
+ return;
45
+ installed = true;
46
+ const entry = ownEntry();
47
+ register(`data:text/javascript,${encodeURIComponent(ESM_HOOKS)}`, {
48
+ parentURL: pathToFileURL(`${dirname(entry)}/`).href,
49
+ data: { url: pathToFileURL(entry).href },
50
+ });
51
+ const internals = Module;
52
+ const original = internals._resolveFilename;
53
+ internals._resolveFilename = function resolveWithFallback(request, ...rest) {
54
+ try {
55
+ return original.call(this, request, ...rest);
56
+ }
57
+ catch (error) {
58
+ const code = error.code;
59
+ if (request === SPECIFIER && code === "MODULE_NOT_FOUND")
60
+ return entry;
61
+ throw error;
62
+ }
63
+ };
64
+ }
@@ -0,0 +1,2 @@
1
+ import type { LoadedProject } from "./types.js";
2
+ export declare function loadProject(root: string): Promise<LoadedProject>;
package/dist/config.js ADDED
@@ -0,0 +1,307 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import { transform } from "esbuild";
3
+ import { rewriteAuthoringImports } from "./authoring.js";
4
+ import { configLoadMessage, missingFileMessage, ProjectConfigError } from "./errors.js";
5
+ import { getProjectPaths, getProjectRelativePaths } from "./layout.js";
6
+ export async function loadProject(root) {
7
+ const configPath = getProjectPaths(root).configFile;
8
+ const config = await readProjectConfig(configPath);
9
+ return {
10
+ root,
11
+ configPath,
12
+ config,
13
+ };
14
+ }
15
+ async function readProjectConfig(configPath) {
16
+ const relativePaths = getProjectRelativePaths();
17
+ try {
18
+ await stat(configPath);
19
+ }
20
+ catch (error) {
21
+ throw new ProjectConfigError(configPath, [
22
+ {
23
+ path: relativePaths.configFile,
24
+ message: missingFileMessage(error),
25
+ severity: "error",
26
+ },
27
+ ]);
28
+ }
29
+ let configModule;
30
+ try {
31
+ configModule = await importTranspiledProjectConfig(configPath);
32
+ }
33
+ catch (error) {
34
+ throw new ProjectConfigError(configPath, [
35
+ {
36
+ path: relativePaths.configFile,
37
+ message: configLoadMessage(error),
38
+ severity: "error",
39
+ },
40
+ ]);
41
+ }
42
+ const exportedConfig = readDefaultExport(configModule, configPath);
43
+ return normalizeProjectConfig(exportedConfig, configPath);
44
+ }
45
+ async function importTranspiledProjectConfig(configPath) {
46
+ const source = await readFile(configPath, "utf8");
47
+ const transpiled = await transform(source, {
48
+ loader: "ts",
49
+ format: "esm",
50
+ target: "es2022",
51
+ sourcefile: configPath,
52
+ });
53
+ const code = rewriteAuthoringImports(transpiled.code, configPath);
54
+ const encoded = Buffer.from(code, "utf8").toString("base64");
55
+ const moduleUrl = `data:text/javascript;base64,${encoded}`;
56
+ return import(moduleUrl);
57
+ }
58
+ function readDefaultExport(configModule, configPath) {
59
+ const relativePaths = getProjectRelativePaths();
60
+ if (!isRecord(configModule) || !Object.hasOwn(configModule, "default")) {
61
+ throw new ProjectConfigError(configPath, [
62
+ {
63
+ path: relativePaths.configFile,
64
+ message: `Missing default export in ${relativePaths.configFile}`,
65
+ severity: "error",
66
+ },
67
+ ]);
68
+ }
69
+ return unwrapDefaultExport(configModule.default);
70
+ }
71
+ function normalizeProjectConfig(value, configPath) {
72
+ const issues = validateProjectConfigShape(value);
73
+ if (issues.length > 0) {
74
+ throw new ProjectConfigError(configPath, issues);
75
+ }
76
+ const config = value;
77
+ const normalized = {
78
+ schemaVersion: config.schemaVersion,
79
+ name: config.name,
80
+ artifacts: { access: config.artifacts?.access ?? "read-write" },
81
+ };
82
+ if (typeof config.description === "string") {
83
+ normalized.description = config.description;
84
+ }
85
+ if (typeof config.template === "string") {
86
+ normalized.template = config.template;
87
+ }
88
+ if (config.model !== undefined) {
89
+ normalized.model = { provider: config.model.provider, name: config.model.name };
90
+ if (config.model.baseURL !== undefined)
91
+ normalized.model.baseURL = config.model.baseURL;
92
+ if (config.model.apiKeyEnv !== undefined)
93
+ normalized.model.apiKeyEnv = config.model.apiKeyEnv;
94
+ if (config.model.maxSteps !== undefined)
95
+ normalized.model.maxSteps = config.model.maxSteps;
96
+ if (config.model.retries !== undefined)
97
+ normalized.model.retries = config.model.retries;
98
+ if (config.model.retryDelayMs !== undefined)
99
+ normalized.model.retryDelayMs = config.model.retryDelayMs;
100
+ }
101
+ if (config.execution !== undefined) {
102
+ normalized.execution = {};
103
+ if (config.execution.leaseMs !== undefined)
104
+ normalized.execution.leaseMs = config.execution.leaseMs;
105
+ }
106
+ if (config.threads !== undefined) {
107
+ normalized.threads = {};
108
+ if (config.threads.title !== undefined)
109
+ normalized.threads.title = config.threads.title;
110
+ }
111
+ if (config.approvals !== undefined) {
112
+ normalized.approvals = {};
113
+ if (config.approvals.default !== undefined)
114
+ normalized.approvals.default = config.approvals.default;
115
+ if (config.approvals.timeoutMs !== undefined)
116
+ normalized.approvals.timeoutMs = config.approvals.timeoutMs;
117
+ if (config.approvals.tools !== undefined)
118
+ normalized.approvals.tools = { ...config.approvals.tools };
119
+ }
120
+ return normalized;
121
+ }
122
+ function validateProjectConfigShape(value) {
123
+ const relativePaths = getProjectRelativePaths();
124
+ const issues = [];
125
+ if (!isRecord(value) || Array.isArray(value)) {
126
+ issues.push({
127
+ path: relativePaths.configFile,
128
+ message: "Expected default export to be an object",
129
+ severity: "error",
130
+ });
131
+ return issues;
132
+ }
133
+ if (!Object.hasOwn(value, "schemaVersion")) {
134
+ issues.push({
135
+ path: relativePaths.configFile,
136
+ message: "Missing required field: schemaVersion",
137
+ severity: "error",
138
+ });
139
+ }
140
+ else if (!Number.isInteger(value.schemaVersion)) {
141
+ issues.push({
142
+ path: relativePaths.configFile,
143
+ message: "Expected schemaVersion to be an integer",
144
+ severity: "error",
145
+ });
146
+ }
147
+ if (!Object.hasOwn(value, "name")) {
148
+ issues.push({
149
+ path: relativePaths.configFile,
150
+ message: "Missing required field: name",
151
+ severity: "error",
152
+ });
153
+ }
154
+ else if (typeof value.name !== "string") {
155
+ issues.push({
156
+ path: relativePaths.configFile,
157
+ message: "Expected name to be a string",
158
+ severity: "error",
159
+ });
160
+ }
161
+ if (Object.hasOwn(value, "description") && typeof value.description !== "string") {
162
+ issues.push({
163
+ path: relativePaths.configFile,
164
+ message: "Expected description to be a string",
165
+ severity: "error",
166
+ });
167
+ }
168
+ if (Object.hasOwn(value, "template") && typeof value.template !== "string") {
169
+ issues.push({
170
+ path: relativePaths.configFile,
171
+ message: "Expected template to be a string",
172
+ severity: "error",
173
+ });
174
+ }
175
+ if (Object.hasOwn(value, "artifacts") &&
176
+ (!isRecord(value.artifacts) ||
177
+ Array.isArray(value.artifacts) ||
178
+ (value.artifacts.access !== "read-write" &&
179
+ value.artifacts.access !== "read-only" &&
180
+ value.artifacts.access !== "off"))) {
181
+ issues.push({
182
+ path: relativePaths.configFile,
183
+ message: 'Expected artifacts.access to be "read-write", "read-only", or "off"',
184
+ severity: "error",
185
+ });
186
+ }
187
+ if (Object.hasOwn(value, "model")) {
188
+ const model = value.model;
189
+ if (!isRecord(model) ||
190
+ Array.isArray(model) ||
191
+ typeof model.provider !== "string" ||
192
+ !model.provider.trim() ||
193
+ typeof model.name !== "string" ||
194
+ !model.name.trim() ||
195
+ (Object.hasOwn(model, "baseURL") &&
196
+ (typeof model.baseURL !== "string" || !model.baseURL.trim())) ||
197
+ (Object.hasOwn(model, "apiKeyEnv") &&
198
+ (typeof model.apiKeyEnv !== "string" || !/^[A-Za-z_]\w*$/.test(model.apiKeyEnv))) ||
199
+ (Object.hasOwn(model, "maxSteps") &&
200
+ (!Number.isSafeInteger(model.maxSteps) || Number(model.maxSteps) <= 0)) ||
201
+ (Object.hasOwn(model, "retries") &&
202
+ (!Number.isSafeInteger(model.retries) ||
203
+ Number(model.retries) < 0 ||
204
+ Number(model.retries) > 10)) ||
205
+ (Object.hasOwn(model, "retryDelayMs") &&
206
+ (!Number.isSafeInteger(model.retryDelayMs) ||
207
+ Number(model.retryDelayMs) < 0 ||
208
+ Number(model.retryDelayMs) > 60000))) {
209
+ issues.push({
210
+ path: relativePaths.configFile,
211
+ message: "Expected non-empty model.provider and model.name, string model.baseURL and model.apiKeyEnv (a variable name), positive model.maxSteps, retries between 0 and 10, and retryDelayMs between 0 and 60000",
212
+ severity: "error",
213
+ });
214
+ }
215
+ }
216
+ if (Object.hasOwn(value, "execution")) {
217
+ const execution = value.execution;
218
+ if (!isRecord(execution) ||
219
+ Array.isArray(execution) ||
220
+ (Object.hasOwn(execution, "leaseMs") &&
221
+ (!Number.isSafeInteger(execution.leaseMs) || Number(execution.leaseMs) <= 0))) {
222
+ issues.push({
223
+ path: relativePaths.configFile,
224
+ message: "Expected execution.leaseMs to be a positive integer",
225
+ severity: "error",
226
+ });
227
+ }
228
+ }
229
+ if (Object.hasOwn(value, "threads")) {
230
+ const threads = value.threads;
231
+ if (!isRecord(threads) ||
232
+ Array.isArray(threads) ||
233
+ (Object.hasOwn(threads, "title") && threads.title !== "keywords" && threads.title !== "model")) {
234
+ issues.push({
235
+ path: relativePaths.configFile,
236
+ message: 'Expected threads.title to be "keywords" or "model"',
237
+ severity: "error",
238
+ });
239
+ }
240
+ }
241
+ if (Object.hasOwn(value, "approvals")) {
242
+ const approvals = value.approvals;
243
+ if (!isRecord(approvals) || Array.isArray(approvals)) {
244
+ issues.push({
245
+ path: relativePaths.configFile,
246
+ message: "Expected approvals to be an object",
247
+ severity: "error",
248
+ });
249
+ }
250
+ else {
251
+ if (Object.hasOwn(approvals, "default") &&
252
+ approvals.default !== "none" &&
253
+ approvals.default !== "required") {
254
+ issues.push({
255
+ path: relativePaths.configFile,
256
+ message: 'Expected approvals.default to be "none" or "required"',
257
+ severity: "error",
258
+ });
259
+ }
260
+ if (Object.hasOwn(approvals, "timeoutMs") &&
261
+ (!Number.isSafeInteger(approvals.timeoutMs) || Number(approvals.timeoutMs) <= 0)) {
262
+ issues.push({
263
+ path: relativePaths.configFile,
264
+ message: "Expected approvals.timeoutMs to be a positive integer",
265
+ severity: "error",
266
+ });
267
+ }
268
+ if (Object.hasOwn(approvals, "tools")) {
269
+ const tools = approvals.tools;
270
+ if (!isRecord(tools) || Array.isArray(tools)) {
271
+ issues.push({
272
+ path: relativePaths.configFile,
273
+ message: "Expected approvals.tools to be an object",
274
+ severity: "error",
275
+ });
276
+ }
277
+ else {
278
+ for (const [name, value2] of Object.entries(tools)) {
279
+ if (value2 !== "none" && value2 !== "required") {
280
+ issues.push({
281
+ path: relativePaths.configFile,
282
+ message: `Expected approvals.tools.${name} to be "none" or "required"`,
283
+ severity: "error",
284
+ });
285
+ }
286
+ }
287
+ }
288
+ }
289
+ }
290
+ }
291
+ return issues;
292
+ }
293
+ function isRecord(value) {
294
+ return typeof value === "object" && value !== null;
295
+ }
296
+ function unwrapDefaultExport(value) {
297
+ let current = value;
298
+ while (isRecord(current) && Object.hasOwn(current, "default")) {
299
+ const keys = Object.keys(current);
300
+ const looksLikeLoaderWrapper = keys.length === 1 || (keys.length === 2 && keys.includes("module.exports"));
301
+ if (!looksLikeLoaderWrapper) {
302
+ break;
303
+ }
304
+ current = current.default;
305
+ }
306
+ return current;
307
+ }
@@ -0,0 +1,9 @@
1
+ import type { ValidationIssue } from "./types.js";
2
+ export declare class ProjectConfigError extends Error {
3
+ readonly configPath: string;
4
+ readonly issues: ValidationIssue[];
5
+ constructor(configPath: string, issues: ValidationIssue[]);
6
+ }
7
+ export declare function missingFileMessage(error: unknown): string;
8
+ export declare function missingFileMessageFor(path: string, error: unknown): string;
9
+ export declare function configLoadMessage(error: unknown): string;
package/dist/errors.js ADDED
@@ -0,0 +1,31 @@
1
+ import { getProjectRelativePaths } from "./layout.js";
2
+ export class ProjectConfigError extends Error {
3
+ configPath;
4
+ issues;
5
+ constructor(configPath, issues) {
6
+ super(`Invalid config at ${configPath}`);
7
+ this.configPath = configPath;
8
+ this.issues = issues;
9
+ }
10
+ }
11
+ export function missingFileMessage(error) {
12
+ const relativePaths = getProjectRelativePaths();
13
+ if (error instanceof Error && error.message.includes("ENOENT")) {
14
+ return `Missing required file: ${relativePaths.configFile}`;
15
+ }
16
+ return `Unable to read ${relativePaths.configFile}`;
17
+ }
18
+ export function missingFileMessageFor(path, error) {
19
+ if (error instanceof Error && error.message.includes("ENOENT")) {
20
+ return `Missing required file: ${path}`;
21
+ }
22
+ return `Unable to read ${path}`;
23
+ }
24
+ export function configLoadMessage(error) {
25
+ const relativePaths = getProjectRelativePaths();
26
+ if (error instanceof Error && error.message) {
27
+ const detail = error.message.trim();
28
+ return `Failed to load ${relativePaths.configFile}: ${detail}`;
29
+ }
30
+ return `Failed to load ${relativePaths.configFile}`;
31
+ }
@@ -0,0 +1,11 @@
1
+ export interface Frontmatter {
2
+ /** Top-level scalar keys. Nested values are not supported. */
3
+ data: Record<string, string>;
4
+ /** The text after the closing `---` line, or the whole text when there is no frontmatter. */
5
+ body: string;
6
+ }
7
+ /**
8
+ * Splits a leading `---` block of `key: value` lines from the rest of a Markdown file. Values may
9
+ * be plain, single- or double-quoted, or a `>` (folded) or `|` (literal) block.
10
+ */
11
+ export declare function parseFrontmatter(text: string): Frontmatter;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Splits a leading `---` block of `key: value` lines from the rest of a Markdown file. Values may
3
+ * be plain, single- or double-quoted, or a `>` (folded) or `|` (literal) block.
4
+ */
5
+ export function parseFrontmatter(text) {
6
+ const lines = text.split("\n");
7
+ if (lines[0]?.trimEnd() !== "---")
8
+ return { data: {}, body: text };
9
+ const end = lines.findIndex((line, index) => index > 0 && line.trimEnd() === "---");
10
+ if (end === -1)
11
+ return { data: {}, body: text };
12
+ const data = {};
13
+ const block = lines.slice(1, end).map((line) => line.trimEnd());
14
+ for (let index = 0; index < block.length; index++) {
15
+ const match = /^([A-Za-z_][\w-]*):\s*(.*)$/.exec(block[index] ?? "");
16
+ if (!match)
17
+ continue;
18
+ const [, key = "", rawValue = ""] = match;
19
+ const style = /^([>|])[+-]?$/.exec(rawValue)?.[1];
20
+ if (!style) {
21
+ data[key] = unquote(rawValue.trim());
22
+ continue;
23
+ }
24
+ const continuation = [];
25
+ while (index + 1 < block.length && /^(\s|$)/.test(block[index + 1] ?? "")) {
26
+ continuation.push((block[++index] ?? "").trim());
27
+ }
28
+ data[key] = (style === ">" ? continuation.filter(Boolean).join(" ") : continuation.join("\n")).trim();
29
+ }
30
+ return { data, body: lines.slice(end + 1).join("\n") };
31
+ }
32
+ function unquote(value) {
33
+ const quote = value[0];
34
+ if ((quote === '"' || quote === "'") && value.length >= 2 && value.endsWith(quote))
35
+ return value.slice(1, -1);
36
+ return value;
37
+ }
@@ -0,0 +1,10 @@
1
+ export { AGENT_FRONTMATTER_KEYS, parseAgentFile } from "./agent-file.js";
2
+ export { installAuthoringFallback } from "./authoring.js";
3
+ export { loadProject } from "./config.js";
4
+ export { type Frontmatter, parseFrontmatter } from "./frontmatter.js";
5
+ export { getProjectPaths, getProjectRelativePaths, PROJECT_LAYOUT, REQUIRED_PROJECT_DIRECTORIES, } from "./layout.js";
6
+ export { findProjectProviderFile, findProjectThreadTitleFile, NATIVE_MODEL_PROVIDERS, } from "./model-provider.js";
7
+ export { readSkills, type Skill, type SkillsResult } from "./skills.js";
8
+ export { ARTIFACT_TOOL_NAMES, SKILL_TOOL_NAME } from "./tool-name-validation.js";
9
+ export type { LoadedProject, ProjectConfig, ProjectDirectoryRole, ProjectLayout, ProjectLayoutDirectories, ProjectPaths, ProjectRelativePaths, ValidationIssue, ValidationResult, ValidationSeverity, } from "./types.js";
10
+ export { validateProject } from "./validation.js";
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { AGENT_FRONTMATTER_KEYS, parseAgentFile } from "./agent-file.js";
2
+ export { installAuthoringFallback } from "./authoring.js";
3
+ export { loadProject } from "./config.js";
4
+ export { parseFrontmatter } from "./frontmatter.js";
5
+ export { getProjectPaths, getProjectRelativePaths, PROJECT_LAYOUT, REQUIRED_PROJECT_DIRECTORIES, } from "./layout.js";
6
+ export { findProjectProviderFile, findProjectThreadTitleFile, NATIVE_MODEL_PROVIDERS, } from "./model-provider.js";
7
+ export { readSkills } from "./skills.js";
8
+ export { ARTIFACT_TOOL_NAMES, SKILL_TOOL_NAME } from "./tool-name-validation.js";
9
+ export { validateProject } from "./validation.js";
@@ -0,0 +1,5 @@
1
+ import type { ProjectDirectoryRole, ProjectLayout, ProjectPaths, ProjectRelativePaths } from "./types.js";
2
+ export declare const PROJECT_LAYOUT: ProjectLayout;
3
+ export declare const REQUIRED_PROJECT_DIRECTORIES: readonly ProjectDirectoryRole[];
4
+ export declare function getProjectRelativePaths(): ProjectRelativePaths;
5
+ export declare function getProjectPaths(root: string): ProjectPaths;
package/dist/layout.js ADDED
@@ -0,0 +1,40 @@
1
+ import { join } from "node:path";
2
+ export const PROJECT_LAYOUT = {
3
+ configFile: "plico.config.ts",
4
+ agentFile: "agent.md",
5
+ directories: {
6
+ skills: "skills",
7
+ tools: "tools",
8
+ evals: "evals",
9
+ artifacts: "artifacts",
10
+ memory: "memory",
11
+ },
12
+ };
13
+ export const REQUIRED_PROJECT_DIRECTORIES = [
14
+ "skills",
15
+ "tools",
16
+ "evals",
17
+ "artifacts",
18
+ "memory",
19
+ ];
20
+ export function getProjectRelativePaths() {
21
+ return {
22
+ configFile: PROJECT_LAYOUT.configFile,
23
+ agentFile: PROJECT_LAYOUT.agentFile,
24
+ directories: { ...PROJECT_LAYOUT.directories },
25
+ };
26
+ }
27
+ export function getProjectPaths(root) {
28
+ const relative = getProjectRelativePaths();
29
+ return {
30
+ configFile: join(root, relative.configFile),
31
+ agentFile: join(root, relative.agentFile),
32
+ directories: {
33
+ skills: join(root, relative.directories.skills),
34
+ tools: join(root, relative.directories.tools),
35
+ evals: join(root, relative.directories.evals),
36
+ artifacts: join(root, relative.directories.artifacts),
37
+ memory: join(root, relative.directories.memory),
38
+ },
39
+ };
40
+ }
@@ -0,0 +1,9 @@
1
+ import type { ProjectConfig, ValidationIssue } from "./types.js";
2
+ /** Providers Plico ships adapters for. Anything else comes from `providers/<name>.ts`. */
3
+ export declare const NATIVE_MODEL_PROVIDERS: readonly string[];
4
+ /** The file in `providers/` that supplies `provider`, or `undefined` when there is none. */
5
+ export declare function findProjectProviderFile(root: string, provider: string): Promise<string | undefined>;
6
+ /** The `threads/title.ts` module that names threads, or `undefined` when the project has none. */
7
+ export declare function findProjectThreadTitleFile(root: string): Promise<string | undefined>;
8
+ /** `plico validate` fails on a provider that is neither native nor supplied by the project. */
9
+ export declare function validateModelProvider(root: string, model: ProjectConfig["model"], configPath: string): Promise<ValidationIssue[]>;
@@ -0,0 +1,50 @@
1
+ import { access } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ /** Providers Plico ships adapters for. Anything else comes from `providers/<name>.ts`. */
4
+ export const NATIVE_MODEL_PROVIDERS = [
5
+ "anthropic",
6
+ "google",
7
+ "openai",
8
+ "openai-compatible",
9
+ ];
10
+ const PROVIDER_EXTENSIONS = [".ts", ".mts", ".js", ".mjs"];
11
+ /** The file in `providers/` that supplies `provider`, or `undefined` when there is none. */
12
+ export async function findProjectProviderFile(root, provider) {
13
+ if (!/^[\w-]+$/.test(provider))
14
+ return undefined;
15
+ for (const extension of PROVIDER_EXTENSIONS) {
16
+ const file = join(root, "providers", `${provider}${extension}`);
17
+ try {
18
+ await access(file);
19
+ return file;
20
+ }
21
+ catch { }
22
+ }
23
+ return undefined;
24
+ }
25
+ /** The `threads/title.ts` module that names threads, or `undefined` when the project has none. */
26
+ export async function findProjectThreadTitleFile(root) {
27
+ for (const extension of PROVIDER_EXTENSIONS) {
28
+ const file = join(root, "threads", `title${extension}`);
29
+ try {
30
+ await access(file);
31
+ return file;
32
+ }
33
+ catch { }
34
+ }
35
+ return undefined;
36
+ }
37
+ /** `plico validate` fails on a provider that is neither native nor supplied by the project. */
38
+ export async function validateModelProvider(root, model, configPath) {
39
+ if (!model || NATIVE_MODEL_PROVIDERS.includes(model.provider))
40
+ return [];
41
+ if (await findProjectProviderFile(root, model.provider))
42
+ return [];
43
+ return [
44
+ {
45
+ path: configPath,
46
+ message: `Unknown model provider "${model.provider}". Use ${NATIVE_MODEL_PROVIDERS.join(", ")}, or add providers/${model.provider}.ts.`,
47
+ severity: "error",
48
+ },
49
+ ];
50
+ }
@@ -0,0 +1,19 @@
1
+ import type { ValidationIssue } from "./types.js";
2
+ export interface Skill {
3
+ /** The folder name. The agent loads the skill by this name. */
4
+ name: string;
5
+ description: string;
6
+ /** Project-relative path, such as `skills/triage/SKILL.md`. */
7
+ source: string;
8
+ /** The skill text without its frontmatter. */
9
+ body: string;
10
+ }
11
+ export interface SkillsResult {
12
+ /** Skills that have a description, sorted by folder name. */
13
+ skills: Skill[];
14
+ issues: ValidationIssue[];
15
+ }
16
+ /** Reads every `skills/<name>/SKILL.md` file. A file that cannot be read throws. */
17
+ export declare function readSkills(root: string): Promise<SkillsResult>;
18
+ /** Skill problems for `plico validate`. Unreadable files are left for the run to report. */
19
+ export declare function validateSkills(root: string): Promise<ValidationIssue[]>;
package/dist/skills.js ADDED
@@ -0,0 +1,58 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { parseFrontmatter } from "./frontmatter.js";
4
+ import { getProjectPaths, getProjectRelativePaths } from "./layout.js";
5
+ const SKILL_FILE = "SKILL.md";
6
+ /** Reads every `skills/<name>/SKILL.md` file. A file that cannot be read throws. */
7
+ export async function readSkills(root) {
8
+ const directory = getProjectPaths(root).directories.skills;
9
+ const relative = getProjectRelativePaths().directories.skills;
10
+ const entries = (await readdir(directory, { withFileTypes: true })).sort((left, right) => left.name.localeCompare(right.name));
11
+ const skills = [];
12
+ const issues = [];
13
+ for (const entry of entries) {
14
+ if (entry.isFile() && entry.name.endsWith(".md")) {
15
+ const name = entry.name.slice(0, -".md".length);
16
+ issues.push({
17
+ path: `${relative}/${entry.name}`,
18
+ message: `Skills are folders. Move ${relative}/${entry.name} to ${relative}/${name}/${SKILL_FILE}.`,
19
+ severity: "error",
20
+ });
21
+ continue;
22
+ }
23
+ if (!entry.isDirectory() || entry.name.startsWith("."))
24
+ continue;
25
+ const folder = `${relative}/${entry.name}`;
26
+ const source = `${folder}/${SKILL_FILE}`;
27
+ if (!(await readdir(join(directory, entry.name))).includes(SKILL_FILE)) {
28
+ issues.push({
29
+ path: folder,
30
+ message: `Skill folder ${folder} needs a ${SKILL_FILE} file.`,
31
+ severity: "error",
32
+ });
33
+ continue;
34
+ }
35
+ const text = await readFile(join(directory, entry.name, SKILL_FILE), "utf8");
36
+ const { data, body } = parseFrontmatter(text);
37
+ const description = data.description?.trim();
38
+ if (!description) {
39
+ issues.push({
40
+ path: source,
41
+ message: `Skill ${source} needs a description. Add "description: <when to use this skill>" in a frontmatter block at the top of the file.`,
42
+ severity: "error",
43
+ });
44
+ continue;
45
+ }
46
+ skills.push({ name: entry.name, description, source, body });
47
+ }
48
+ return { skills, issues };
49
+ }
50
+ /** Skill problems for `plico validate`. Unreadable files are left for the run to report. */
51
+ export async function validateSkills(root) {
52
+ try {
53
+ return (await readSkills(root)).issues;
54
+ }
55
+ catch {
56
+ return [];
57
+ }
58
+ }
@@ -0,0 +1,5 @@
1
+ import type { ValidationIssue } from "./types.js";
2
+ export declare const ARTIFACT_TOOL_NAMES: readonly ["list_files", "read_file", "write_file", "edit_file"];
3
+ /** The built-in tool the agent calls to read a skill's full text. */
4
+ export declare const SKILL_TOOL_NAME = "load_skill";
5
+ export declare function validateToolNames(root: string): Promise<ValidationIssue[]>;
@@ -0,0 +1,219 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { parse, } from "acorn";
4
+ import { transform } from "esbuild";
5
+ import { SPECIFIER } from "./authoring.js";
6
+ import { getProjectPaths, getProjectRelativePaths } from "./layout.js";
7
+ export const ARTIFACT_TOOL_NAMES = ["list_files", "read_file", "write_file", "edit_file"];
8
+ /** The built-in tool the agent calls to read a skill's full text. */
9
+ export const SKILL_TOOL_NAME = "load_skill";
10
+ export async function validateToolNames(root) {
11
+ const paths = getProjectPaths(root);
12
+ const relativePaths = getProjectRelativePaths();
13
+ let entries;
14
+ try {
15
+ entries = (await readdir(paths.directories.tools, { withFileTypes: true }))
16
+ .filter((entry) => !entry.isDirectory() && entry.name.endsWith(".tool.ts"))
17
+ .map((entry) => entry.name)
18
+ .sort((left, right) => left.localeCompare(right));
19
+ }
20
+ catch (error) {
21
+ if (isMissingDirectory(error))
22
+ return [];
23
+ throw error;
24
+ }
25
+ const names = new Map();
26
+ const issues = [];
27
+ for (const entry of entries) {
28
+ const source = `${relativePaths.directories.tools}/${entry}`;
29
+ const text = await readFile(join(paths.directories.tools, entry), "utf8");
30
+ const name = await readToolName(text, source);
31
+ if (name === undefined)
32
+ continue;
33
+ if (ARTIFACT_TOOL_NAMES.includes(name)) {
34
+ issues.push({
35
+ path: source,
36
+ message: `Tool name "${name}" is reserved by Plico's artifact tools`,
37
+ severity: "error",
38
+ });
39
+ continue;
40
+ }
41
+ if (name === SKILL_TOOL_NAME) {
42
+ issues.push({
43
+ path: source,
44
+ message: `Tool name "${name}" is reserved by Plico's skill loader`,
45
+ severity: "error",
46
+ });
47
+ continue;
48
+ }
49
+ const first = names.get(name);
50
+ if (first) {
51
+ issues.push({
52
+ path: source,
53
+ message: `Duplicate tool name "${name}" declared in ${first} and ${source}`,
54
+ severity: "error",
55
+ });
56
+ }
57
+ else {
58
+ names.set(name, source);
59
+ }
60
+ }
61
+ return issues;
62
+ }
63
+ /**
64
+ * Types are stripped first, so `as`, `satisfies` and annotations are gone before the tree is read.
65
+ * A file that does not parse has no readable name; the project still validates and the load step
66
+ * reports the syntax error.
67
+ */
68
+ async function parseToolFile(text, fileName) {
69
+ try {
70
+ const { code } = await transform(text, {
71
+ loader: "ts",
72
+ format: "esm",
73
+ target: "esnext",
74
+ sourcefile: fileName,
75
+ });
76
+ return parse(code, { ecmaVersion: "latest", sourceType: "module" });
77
+ }
78
+ catch {
79
+ return undefined;
80
+ }
81
+ }
82
+ async function readToolName(text, fileName) {
83
+ const program = await parseToolFile(text, fileName);
84
+ if (!program)
85
+ return undefined;
86
+ const scope = { declarations: new Map(), defineTools: new Set(), namespaces: new Set() };
87
+ let defaultExpression;
88
+ for (const statement of program.body) {
89
+ if (statement.type === "VariableDeclaration") {
90
+ for (const declaration of statement.declarations) {
91
+ if (declaration.id.type === "Identifier" && declaration.init) {
92
+ scope.declarations.set(declaration.id.name, declaration.init);
93
+ }
94
+ }
95
+ }
96
+ else if (statement.type === "ExportDefaultDeclaration") {
97
+ const { declaration } = statement;
98
+ if (declaration.type !== "FunctionDeclaration" && declaration.type !== "ClassDeclaration") {
99
+ defaultExpression = declaration;
100
+ }
101
+ }
102
+ else if (statement.type === "ExportNamedDeclaration") {
103
+ // esbuild may write `export default tool` as `export { tool as default }`.
104
+ for (const specifier of statement.specifiers) {
105
+ if (moduleName(specifier.exported) === "default")
106
+ defaultExpression = specifier.local;
107
+ }
108
+ }
109
+ else if (statement.type === "ImportDeclaration") {
110
+ readAuthoringImport(statement, scope);
111
+ }
112
+ }
113
+ const object = resolveObject(defaultExpression, scope);
114
+ if (!object)
115
+ return undefined;
116
+ const result = resolveObjectName(object, scope);
117
+ return result.kind === "known" ? result.name : undefined;
118
+ }
119
+ function moduleName(node) {
120
+ if (node.type === "Identifier")
121
+ return node.name;
122
+ return typeof node.value === "string" ? node.value : undefined;
123
+ }
124
+ function readAuthoringImport(statement, scope) {
125
+ if (statement.source.value !== SPECIFIER)
126
+ return;
127
+ for (const specifier of statement.specifiers) {
128
+ if (specifier.type === "ImportNamespaceSpecifier") {
129
+ scope.namespaces.add(specifier.local.name);
130
+ }
131
+ else if (specifier.type === "ImportSpecifier" &&
132
+ moduleName(specifier.imported) === "defineTool") {
133
+ scope.defineTools.add(specifier.local.name);
134
+ }
135
+ }
136
+ }
137
+ function resolveObjectName(object, scope) {
138
+ for (const property of [...object.properties].reverse()) {
139
+ if (property.type === "Property" && property.kind === "init" && !property.method) {
140
+ const propertyName = property.computed ? undefined : moduleName(property.key);
141
+ if (propertyName === "name") {
142
+ const name = resolveString(property.value, scope);
143
+ return name === undefined ? { kind: "unknown" } : { kind: "known", name };
144
+ }
145
+ }
146
+ if (property.type === "SpreadElement") {
147
+ const spreadObject = resolveObject(property.argument, scope);
148
+ if (!spreadObject)
149
+ return { kind: "unknown" };
150
+ const spreadName = resolveObjectName(spreadObject, scope);
151
+ if (spreadName.kind !== "absent")
152
+ return spreadName;
153
+ }
154
+ }
155
+ return { kind: "absent" };
156
+ }
157
+ function resolveString(expression, scope) {
158
+ let current = unwrap(expression, scope);
159
+ const visited = new Set();
160
+ while (current.type === "Identifier") {
161
+ if (visited.has(current.name))
162
+ return undefined;
163
+ visited.add(current.name);
164
+ const declaration = scope.declarations.get(current.name);
165
+ if (!declaration)
166
+ return undefined;
167
+ current = unwrap(declaration, scope);
168
+ }
169
+ return current.type === "Literal" && typeof current.value === "string"
170
+ ? current.value
171
+ : undefined;
172
+ }
173
+ function resolveObject(expression, scope) {
174
+ let current = expression;
175
+ const visited = new Set();
176
+ while (current) {
177
+ current = unwrap(current, scope);
178
+ if (current.type === "ObjectExpression")
179
+ return current;
180
+ if (current.type !== "Identifier")
181
+ return undefined;
182
+ if (visited.has(current.name))
183
+ return undefined;
184
+ visited.add(current.name);
185
+ current = scope.declarations.get(current.name);
186
+ }
187
+ return undefined;
188
+ }
189
+ function unwrap(expression, scope) {
190
+ let current = expression;
191
+ while (isDefineToolCall(current, scope))
192
+ current = current.arguments[0];
193
+ return current;
194
+ }
195
+ /**
196
+ * `defineTool(tool)` returns `tool` unchanged, so its name is the argument's name. Only the
197
+ * `defineTool` imported from "@plico-ai/sdk" counts, under any local name or through a namespace import.
198
+ */
199
+ function isDefineToolCall(expression, scope) {
200
+ if (expression.type !== "CallExpression" || expression.arguments.length !== 1)
201
+ return false;
202
+ if (expression.arguments[0]?.type === "SpreadElement")
203
+ return false;
204
+ const { callee } = expression;
205
+ if (callee.type === "Identifier")
206
+ return scope.defineTools.has(callee.name);
207
+ return (callee.type === "MemberExpression" &&
208
+ !callee.computed &&
209
+ callee.object.type === "Identifier" &&
210
+ scope.namespaces.has(callee.object.name) &&
211
+ callee.property.type === "Identifier" &&
212
+ callee.property.name === "defineTool");
213
+ }
214
+ function isMissingDirectory(error) {
215
+ return (typeof error === "object" &&
216
+ error !== null &&
217
+ "code" in error &&
218
+ (error.code === "ENOENT" || error.code === "ENOTDIR"));
219
+ }
@@ -0,0 +1,24 @@
1
+ export type { LoadedProject, ProjectConfig, ValidationIssue, ValidationResult, ValidationSeverity, } from "@plico-ai/contracts";
2
+ export type ProjectDirectoryRole = keyof ProjectLayoutDirectories;
3
+ export interface ProjectLayoutDirectories {
4
+ skills: "skills";
5
+ tools: "tools";
6
+ evals: "evals";
7
+ artifacts: "artifacts";
8
+ memory: "memory";
9
+ }
10
+ export interface ProjectLayout {
11
+ configFile: "plico.config.ts";
12
+ agentFile: "agent.md";
13
+ directories: ProjectLayoutDirectories;
14
+ }
15
+ export interface ProjectRelativePaths {
16
+ configFile: string;
17
+ agentFile: string;
18
+ directories: Record<ProjectDirectoryRole, string>;
19
+ }
20
+ export interface ProjectPaths {
21
+ configFile: string;
22
+ agentFile: string;
23
+ directories: Record<ProjectDirectoryRole, string>;
24
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { ValidationResult } from "./types.js";
2
+ export declare function validateProject(root: string): Promise<ValidationResult>;
@@ -0,0 +1,123 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import { parseAgentFile, unknownAgentKeyWarnings } from "./agent-file.js";
3
+ import { loadProject } from "./config.js";
4
+ import { missingFileMessage, missingFileMessageFor, ProjectConfigError } from "./errors.js";
5
+ import { getProjectPaths, getProjectRelativePaths, REQUIRED_PROJECT_DIRECTORIES, } from "./layout.js";
6
+ import { validateModelProvider } from "./model-provider.js";
7
+ import { validateSkills } from "./skills.js";
8
+ import { validateToolNames } from "./tool-name-validation.js";
9
+ export async function validateProject(root) {
10
+ const errors = [];
11
+ const warnings = [];
12
+ const relativePaths = getProjectRelativePaths();
13
+ const projectPaths = getProjectPaths(root);
14
+ let project;
15
+ try {
16
+ project = await loadProject(root);
17
+ }
18
+ catch (error) {
19
+ if (error instanceof ProjectConfigError) {
20
+ errors.push(...error.issues);
21
+ }
22
+ else {
23
+ errors.push({
24
+ path: relativePaths.configFile,
25
+ message: missingFileMessage(error),
26
+ severity: "error",
27
+ });
28
+ }
29
+ return {
30
+ ok: false,
31
+ issues: [...errors, ...warnings],
32
+ errors,
33
+ warnings,
34
+ };
35
+ }
36
+ let agentText;
37
+ try {
38
+ agentText = await readFile(projectPaths.agentFile, "utf8");
39
+ }
40
+ catch (error) {
41
+ errors.push({
42
+ path: relativePaths.agentFile,
43
+ message: missingFileMessageFor(relativePaths.agentFile, error),
44
+ severity: "error",
45
+ });
46
+ }
47
+ if (typeof agentText === "string") {
48
+ const agent = parseAgentFile(agentText);
49
+ warnings.push(...unknownAgentKeyWarnings(relativePaths.agentFile, agent.data));
50
+ agentText = agent.body;
51
+ if (agentText.trim().length === 0) {
52
+ errors.push({
53
+ path: relativePaths.agentFile,
54
+ message: `${relativePaths.agentFile} must not be empty`,
55
+ severity: "error",
56
+ });
57
+ }
58
+ else {
59
+ const warning = detectWeakAgentContent(agentText);
60
+ if (warning) {
61
+ warnings.push({
62
+ path: relativePaths.agentFile,
63
+ message: warning,
64
+ severity: "warning",
65
+ });
66
+ }
67
+ }
68
+ }
69
+ for (const directoryRole of REQUIRED_PROJECT_DIRECTORIES) {
70
+ const directory = relativePaths.directories[directoryRole];
71
+ const directoryPath = projectPaths.directories[directoryRole];
72
+ try {
73
+ const directoryStat = await stat(directoryPath);
74
+ if (!directoryStat.isDirectory()) {
75
+ errors.push({
76
+ path: directory,
77
+ message: `Expected ${directory} to be a directory`,
78
+ severity: "error",
79
+ });
80
+ }
81
+ }
82
+ catch {
83
+ errors.push({
84
+ path: directory,
85
+ message: `Missing required directory: ${directory}`,
86
+ severity: "error",
87
+ });
88
+ }
89
+ }
90
+ if (project.config.schemaVersion !== 1) {
91
+ errors.push({
92
+ path: relativePaths.configFile,
93
+ message: "Unsupported schemaVersion. Expected 1.",
94
+ severity: "error",
95
+ });
96
+ }
97
+ if (!project.config.name.trim()) {
98
+ errors.push({
99
+ path: relativePaths.configFile,
100
+ message: "Project name is required.",
101
+ severity: "error",
102
+ });
103
+ }
104
+ errors.push(...(await validateToolNames(root)));
105
+ errors.push(...(await validateSkills(root)));
106
+ errors.push(...(await validateModelProvider(root, project.config.model, relativePaths.configFile)));
107
+ return {
108
+ ok: errors.length === 0,
109
+ issues: [...errors, ...warnings],
110
+ errors,
111
+ warnings,
112
+ project,
113
+ };
114
+ }
115
+ function detectWeakAgentContent(agentText) {
116
+ const normalized = agentText.trim().toLowerCase();
117
+ if (normalized.includes("follow the project instructions") ||
118
+ normalized === "# agent" ||
119
+ normalized.includes("placeholder agent instructions")) {
120
+ return "agent.md looks like placeholder content";
121
+ }
122
+ return undefined;
123
+ }
package/package.json CHANGED
@@ -1,21 +1,47 @@
1
1
  {
2
2
  "name": "@plico-ai/core",
3
- "version": "0.0.0-reserved.0",
4
- "description": "Project loading and validation for Plico. Not released yet.",
3
+ "version": "0.1.0",
4
+ "description": "Plico project model: load and validate agent projects from files",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://plico.sohab.dev",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/ITZSHOAIB/plico.git"
9
+ "url": "git+https://github.com/ITZSHOAIB/plico.git",
10
+ "directory": "packages/core"
10
11
  },
11
12
  "keywords": [
12
13
  "plico",
13
- "agent"
14
- ],
15
- "files": [
16
- "README.md"
14
+ "agent",
15
+ "validation"
17
16
  ],
18
17
  "publishConfig": {
19
18
  "access": "public"
19
+ },
20
+ "type": "module",
21
+ "main": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "NOTICE",
31
+ "dist"
32
+ ],
33
+ "dependencies": {
34
+ "acorn": "^8.19.0",
35
+ "esbuild": "~0.28.2",
36
+ "@plico-ai/contracts": "0.1.0",
37
+ "@plico-ai/sdk": "0.1.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=22.18.0"
41
+ },
42
+ "scripts": {
43
+ "typecheck": "tsc -p tsconfig.json --noEmit",
44
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
45
+ "test": "vitest run"
20
46
  }
21
- }
47
+ }