@matimo/core 0.1.2 → 0.1.3

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.
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Runtime-safe public surface for built-in tool files.
3
+ *
4
+ * Keep this entrypoint intentionally narrow to avoid coupling tools to the
5
+ * full package barrel and to reduce circular dependency risk.
6
+ */
7
+ export { MatimoError, ErrorCode } from '../errors/matimo-error.js';
8
+ export { getGlobalMatimoLogger } from '../logging/index.js';
9
+ export { getGlobalApprovalHandler } from '../approval/approval-handler.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Runtime-safe public surface for built-in tool files.
3
+ *
4
+ * Keep this entrypoint intentionally narrow to avoid coupling tools to the
5
+ * full package barrel and to reduce circular dependency risk.
6
+ */
7
+ export { MatimoError, ErrorCode } from '../errors/matimo-error.js';
8
+ export { getGlobalMatimoLogger } from '../logging/index.js';
9
+ export { getGlobalApprovalHandler } from '../approval/approval-handler.js';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matimo/core",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Core SDK for Matimo: Framework-agnostic YAML-driven tool ecosystem for AI agents.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,6 +11,10 @@
11
11
  "types": "./dist/index.d.ts",
12
12
  "default": "./dist/index.js"
13
13
  },
14
+ "./runtime": {
15
+ "types": "./dist/runtime/index.d.ts",
16
+ "default": "./dist/runtime/index.js"
17
+ },
14
18
  "./mcp": {
15
19
  "types": "./dist/mcp/index.d.ts",
16
20
  "default": "./dist/mcp/index.js"
@@ -3,8 +3,7 @@
3
3
  * Pattern: Function-based tool (same as execute)
4
4
  */
5
5
 
6
- import { MatimoError, ErrorCode } from '../../src/errors/matimo-error';
7
- import { getGlobalMatimoLogger } from '../../src/logging/logger';
6
+ import { MatimoError, ErrorCode, getGlobalMatimoLogger } from '@matimo/core/runtime';
8
7
 
9
8
  interface CalculatorParams {
10
9
  operation: string;
@@ -6,9 +6,12 @@
6
6
 
7
7
  import { exec } from 'child_process';
8
8
  import { promisify } from 'util';
9
- import { MatimoError, ErrorCode } from '../../src/errors/matimo-error';
10
- import { getGlobalMatimoLogger } from '../../src/logging/logger';
11
- import { getGlobalApprovalHandler } from '../../src/approval/approval-handler';
9
+ import {
10
+ MatimoError,
11
+ ErrorCode,
12
+ getGlobalMatimoLogger,
13
+ getGlobalApprovalHandler,
14
+ } from '@matimo/core/runtime';
12
15
 
13
16
  const execAsync = promisify(exec);
14
17
 
@@ -5,7 +5,7 @@ import {
5
5
  validateSkillName,
6
6
  parseSkillContent,
7
7
  validateFrontmatter,
8
- } from '../shared/skill-validation';
8
+ } from '../shared/skill-validation.js';
9
9
 
10
10
  interface CreateSkillParams {
11
11
  name: string;
@@ -1,7 +1,14 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { getGlobalMatimoLogger, getGlobalMatimoInstance, ToolLoader, SkillSummary } from '@matimo/core';
4
- import { parseSkillContent, listBundledResources, type BundledResources } from '../shared/skill-validation';
4
+ import { parseSkillContent, listBundledResources } from '../shared/skill-validation.js';
5
+
6
+ interface BundledResources {
7
+ scripts: string[];
8
+ references: string[];
9
+ assets: string[];
10
+ other: string[];
11
+ }
5
12
 
6
13
  interface GetSkillParams {
7
14
  name: string;
@@ -5,9 +5,20 @@ import {
5
5
  parseSkillContent,
6
6
  validateFrontmatter,
7
7
  listBundledResources,
8
- type ValidationIssue,
9
- type BundledResources,
10
- } from '../shared/skill-validation';
8
+ } from '../shared/skill-validation.js';
9
+
10
+ interface ValidationIssue {
11
+ field: string;
12
+ message: string;
13
+ severity: 'error' | 'warning';
14
+ }
15
+
16
+ interface BundledResources {
17
+ scripts: string[];
18
+ references: string[];
19
+ assets: string[];
20
+ other: string[];
21
+ }
11
22
 
12
23
  interface ValidateSkillParams {
13
24
  /** Name of the skill directory to validate. */
@@ -53,9 +53,10 @@ export default async function searchTool(params: SearchParams): Promise<SearchRe
53
53
  caseSensitive = false,
54
54
  maxResults = 50,
55
55
  contextLines = 2,
56
- excludePatterns = ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**'],
57
56
  } = params;
58
57
 
58
+ const excludePatterns = params.excludePatterns;
59
+
59
60
  const startTime = Date.now();
60
61
 
61
62
  logger.debug('Search tool invoked', {
@@ -116,11 +117,20 @@ export default async function searchTool(params: SearchParams): Promise<SearchRe
116
117
  let files: string[] = [];
117
118
 
118
119
  try {
119
- files = await glob(globPattern, {
120
+ const globOptions: {
121
+ nodir: true;
122
+ absolute: true;
123
+ ignore?: string[];
124
+ } = {
120
125
  nodir: true,
121
126
  absolute: true,
122
- ignore: excludePatterns,
123
- });
127
+ };
128
+
129
+ if (excludePatterns && excludePatterns.length > 0) {
130
+ globOptions.ignore = excludePatterns;
131
+ }
132
+
133
+ files = await glob(globPattern, globOptions);
124
134
  } catch {
125
135
  throw new MatimoError('Invalid glob pattern', ErrorCode.INVALID_PARAMETER, {
126
136
  pattern: filePattern,
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Shared validation utilities for Agent Skills — aligned with the official
3
+ * Agent Skills specification (https://agentskills.io/specification).
4
+ *
5
+ * Covers: name rules, description rules, frontmatter parsing/validation,
6
+ * and bundled resource discovery.
7
+ */
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+
11
+ // Name validation
12
+ const VALID_NAME_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
13
+ const CONSECUTIVE_HYPHENS = /--/;
14
+ const MAX_NAME_LENGTH = 64;
15
+ const MAX_DESCRIPTION_LENGTH = 1024;
16
+ const MAX_COMPATIBILITY_LENGTH = 500;
17
+ const UNSAFE_NAME_PATTERN = /[/\\]|\.\.|[\x00-\x1f]/;
18
+
19
+ export function validateSkillName(name) {
20
+ if (!name || name.trim().length === 0) {
21
+ return { valid: false, error: 'Skill name is required' };
22
+ }
23
+
24
+ if (UNSAFE_NAME_PATTERN.test(name)) {
25
+ return { valid: false, error: 'Skill name contains invalid characters' };
26
+ }
27
+
28
+ if (name.length > MAX_NAME_LENGTH) {
29
+ return {
30
+ valid: false,
31
+ error: `Skill name must be at most ${MAX_NAME_LENGTH} characters (got ${name.length})`,
32
+ };
33
+ }
34
+
35
+ if (!VALID_NAME_PATTERN.test(name)) {
36
+ return {
37
+ valid: false,
38
+ error:
39
+ 'Skill name must contain only lowercase letters, numbers, and hyphens, and must not start or end with a hyphen',
40
+ };
41
+ }
42
+
43
+ if (CONSECUTIVE_HYPHENS.test(name)) {
44
+ return { valid: false, error: 'Skill name must not contain consecutive hyphens (--)' };
45
+ }
46
+
47
+ return { valid: true };
48
+ }
49
+
50
+ export function parseSkillContent(content) {
51
+ if (!content || !content.startsWith('---')) {
52
+ return { success: false, error: 'Skill content must start with YAML frontmatter (---)' };
53
+ }
54
+
55
+ const endIndex = content.indexOf('---', 3);
56
+ if (endIndex === -1) {
57
+ return { success: false, error: 'Skill content must have closing YAML frontmatter (---)' };
58
+ }
59
+
60
+ const frontmatterBlock = content.substring(3, endIndex).trim();
61
+ const body = content.substring(endIndex + 3).trim();
62
+
63
+ const fields = {};
64
+ let currentMetadata = null;
65
+ let currentArray = null;
66
+ let currentArrayKey = null;
67
+
68
+ for (const line of frontmatterBlock.split('\n')) {
69
+ if (currentArray !== null && /^\s*- /.test(line)) {
70
+ const item = line.trim().substring(2).trim();
71
+ if (item) {
72
+ currentArray.push(item.replace(/^["']|["']$/g, ''));
73
+ }
74
+ continue;
75
+ }
76
+
77
+ if (currentMetadata !== null && /^\s+\S/.test(line)) {
78
+ const colonIndex = line.indexOf(':');
79
+ if (colonIndex !== -1) {
80
+ const key = line.substring(0, colonIndex).trim();
81
+ const value = line.substring(colonIndex + 1).trim();
82
+ if (key && value) {
83
+ currentMetadata[key] = value.replace(/^["']|["']$/g, '');
84
+ }
85
+ }
86
+ continue;
87
+ }
88
+
89
+ if (currentArray !== null && currentArrayKey) {
90
+ fields[currentArrayKey] = currentArray;
91
+ }
92
+ if (currentMetadata !== null) {
93
+ fields.metadata = currentMetadata;
94
+ }
95
+ currentMetadata = null;
96
+ currentArray = null;
97
+ currentArrayKey = null;
98
+ const colonIndex = line.indexOf(':');
99
+ if (colonIndex === -1) continue;
100
+
101
+ const key = line.substring(0, colonIndex).trim();
102
+ const value = line.substring(colonIndex + 1).trim();
103
+
104
+ if (key === 'metadata' && !value) {
105
+ currentMetadata = {};
106
+ fields.metadata = currentMetadata;
107
+ continue;
108
+ }
109
+
110
+ if (key === 'allowed-tools' && !value) {
111
+ currentArray = [];
112
+ currentArrayKey = 'allowed-tools';
113
+ continue;
114
+ }
115
+
116
+ if (key && value) {
117
+ fields[key] = value.replace(/^["']|["']$/g, '');
118
+ }
119
+ }
120
+
121
+ if (currentArray !== null && currentArrayKey) {
122
+ fields[currentArrayKey] = currentArray;
123
+ }
124
+
125
+ const frontmatter = {
126
+ name: fields.name || '',
127
+ description: fields.description || '',
128
+ };
129
+
130
+ if (fields.license) frontmatter.license = fields.license;
131
+ if (fields.compatibility) frontmatter.compatibility = fields.compatibility;
132
+ if (fields['allowed-tools']) {
133
+ frontmatter['allowed-tools'] = Array.isArray(fields['allowed-tools'])
134
+ ? fields['allowed-tools']
135
+ : fields['allowed-tools'];
136
+ }
137
+ if (currentMetadata && Object.keys(currentMetadata).length > 0) {
138
+ frontmatter.metadata = currentMetadata;
139
+ }
140
+
141
+ return {
142
+ success: true,
143
+ parsed: { frontmatter, body, raw: content },
144
+ };
145
+ }
146
+
147
+ export function validateFrontmatter(frontmatter, directoryName) {
148
+ const issues = [];
149
+
150
+ if (!frontmatter.name) {
151
+ issues.push({
152
+ field: 'name',
153
+ message: 'YAML frontmatter must include a "name" field',
154
+ severity: 'error',
155
+ });
156
+ } else {
157
+ const nameResult = validateSkillName(frontmatter.name);
158
+ if (!nameResult.valid) {
159
+ issues.push({ field: 'name', message: nameResult.error, severity: 'error' });
160
+ }
161
+ }
162
+
163
+ if (!frontmatter.description) {
164
+ issues.push({
165
+ field: 'description',
166
+ message: 'YAML frontmatter must include a "description" field',
167
+ severity: 'error',
168
+ });
169
+ } else if (frontmatter.description.length > MAX_DESCRIPTION_LENGTH) {
170
+ issues.push({
171
+ field: 'description',
172
+ message: `Description must be at most ${MAX_DESCRIPTION_LENGTH} characters (got ${frontmatter.description.length})`,
173
+ severity: 'error',
174
+ });
175
+ }
176
+
177
+ if (frontmatter.compatibility && frontmatter.compatibility.length > MAX_COMPATIBILITY_LENGTH) {
178
+ issues.push({
179
+ field: 'compatibility',
180
+ message: `Compatibility must be at most ${MAX_COMPATIBILITY_LENGTH} characters (got ${frontmatter.compatibility.length})`,
181
+ severity: 'error',
182
+ });
183
+ }
184
+
185
+ if (directoryName && frontmatter.name && frontmatter.name !== directoryName) {
186
+ issues.push({
187
+ field: 'name',
188
+ message: `Skill name "${frontmatter.name}" must match its directory name "${directoryName}"`,
189
+ severity: 'error',
190
+ });
191
+ }
192
+
193
+ return {
194
+ valid: issues.filter((i) => i.severity === 'error').length === 0,
195
+ issues,
196
+ };
197
+ }
198
+
199
+ export function listBundledResources(skillDir) {
200
+ const resources = {
201
+ scripts: [],
202
+ references: [],
203
+ assets: [],
204
+ other: [],
205
+ };
206
+
207
+ if (!fs.existsSync(skillDir)) return resources;
208
+
209
+ const KNOWN_DIRS = {
210
+ scripts: 'scripts',
211
+ references: 'references',
212
+ assets: 'assets',
213
+ };
214
+
215
+ const entries = fs.readdirSync(skillDir, { withFileTypes: true });
216
+ for (const entry of entries) {
217
+ if (entry.name === 'SKILL.md') continue;
218
+
219
+ if (entry.isDirectory()) {
220
+ const category = KNOWN_DIRS[entry.name];
221
+ if (category) {
222
+ const subDir = path.join(skillDir, entry.name);
223
+ const subEntries = fs.readdirSync(subDir);
224
+ for (const sub of subEntries) {
225
+ resources[category].push(`${entry.name}/${sub}`);
226
+ }
227
+ } else {
228
+ const subDir = path.join(skillDir, entry.name);
229
+ const subEntries = fs.readdirSync(subDir);
230
+ for (const sub of subEntries) {
231
+ resources.other.push(`${entry.name}/${sub}`);
232
+ }
233
+ }
234
+ } else {
235
+ resources.other.push(entry.name);
236
+ }
237
+ }
238
+
239
+ return resources;
240
+ }
package/tools/web/web.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import axios, { AxiosRequestConfig, AxiosError } from 'axios';
8
- import { MatimoError, ErrorCode } from '../../src/errors/matimo-error';
8
+ import { MatimoError, ErrorCode } from '@matimo/core/runtime';
9
9
 
10
10
  interface WebParams {
11
11
  url: string;
@@ -74,7 +74,7 @@ export default async function webTool(params: WebParams): Promise<WebResult> {
74
74
  headers: {
75
75
  'User-Agent': 'Matimo/1.0 (AI Agent Tool SDK)',
76
76
  Accept: 'application/json, text/plain, text/html, */*',
77
- ...headers,
77
+ ...(headers || {}),
78
78
  },
79
79
  validateStatus: () => true, // Don't throw on any status code
80
80
  };