awesome-agents 0.1.1 → 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.
package/src/installer.js CHANGED
@@ -21,11 +21,11 @@ export async function installFromSource(sourceSpec, options = {}) {
21
21
  const split = splitSourceSpec(sourceSpec);
22
22
  const source = split.source;
23
23
  const { selectors, harnessInput } = resolveInstallSelection(split, options);
24
- const scope = resolveScope(options);
25
24
  const harnesses = normalizeAgentList(harnessInput, {
26
25
  all: options.all,
27
26
  defaultAgent: DEFAULT_AGENT
28
27
  });
28
+ const scope = resolveScope(options, harnesses);
29
29
 
30
30
  const materialized = await materializeSource(source, options);
31
31
  try {
@@ -71,7 +71,11 @@ export async function installFromSource(sourceSpec, options = {}) {
71
71
  await writeRegistry(registry, registryOptions);
72
72
  }
73
73
 
74
- return { operations, registryPath: registryPath(registryOptions) };
74
+ return {
75
+ operations,
76
+ registryPath: registryPath(registryOptions),
77
+ runInstructions: buildRunInstructions(operations)
78
+ };
75
79
  } finally {
76
80
  await materialized.cleanup();
77
81
  }
@@ -104,10 +108,11 @@ export async function useFromSource(sourceSpec, options = {}) {
104
108
  }
105
109
 
106
110
  export async function listInstalled(options = {}) {
107
- const scope = resolveScope(options);
108
- const registry = await readRegistry({ ...options, scope });
109
111
  const harnessInput = options.agents ?? options.agent;
110
- const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
112
+ const harnesses = harnessInput ? normalizeAgentList(harnessInput) : [];
113
+ const scope = resolveScope(options, harnesses);
114
+ const registry = await readRegistry({ ...options, scope });
115
+ const harnessFilter = harnesses.length > 0 ? new Set(harnesses) : undefined;
111
116
  const installs = registry.installs
112
117
  .filter((install) => !harnessFilter || harnessFilter.has(install.harness))
113
118
  .map((install) => ({
@@ -123,7 +128,9 @@ export async function listInstalled(options = {}) {
123
128
  }
124
129
 
125
130
  export async function removeInstalled(profileArgs = [], options = {}) {
126
- const scope = resolveScope(options);
131
+ const harnessInput = options.agents ?? options.agent;
132
+ const harnesses = harnessInput ? normalizeAgentList(harnessInput) : [];
133
+ const scope = resolveScope(options, harnesses);
127
134
  const registryOptions = { ...options, scope };
128
135
  const registry = await readRegistry(registryOptions);
129
136
  const selectors = normalizeProfileSelectors(profileArgs);
@@ -132,8 +139,7 @@ export async function removeInstalled(profileArgs = [], options = {}) {
132
139
  throw new Error("Specify at least one profile to remove, or pass --all.");
133
140
  }
134
141
 
135
- const harnessInput = options.agents ?? options.agent;
136
- const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
142
+ const harnessFilter = harnesses.length > 0 ? new Set(harnesses) : undefined;
137
143
  const operations = [];
138
144
  const matched = registry.installs.filter((install) => {
139
145
  const profileMatch = removeAll || selectors.includes(install.profile);
@@ -172,12 +178,13 @@ export async function removeInstalled(profileArgs = [], options = {}) {
172
178
  }
173
179
 
174
180
  export async function updateInstalled(profileArgs = [], options = {}) {
175
- const scope = resolveScope(options);
181
+ const harnessInput = options.agents ?? options.agent;
182
+ const harnesses = harnessInput ? normalizeAgentList(harnessInput) : [];
183
+ const scope = resolveScope(options, harnesses);
176
184
  const registryOptions = { ...options, scope };
177
185
  const registry = await readRegistry(registryOptions);
178
186
  const selectors = normalizeProfileSelectors(profileArgs);
179
- const harnessInput = options.agents ?? options.agent;
180
- const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
187
+ const harnessFilter = harnesses.length > 0 ? new Set(harnesses) : undefined;
181
188
  const candidates = registry.installs.filter((install) => {
182
189
  const profileMatch = selectors.length === 0 || selectors.includes(install.profile);
183
190
  const harnessMatch = !harnessFilter || harnessFilter.has(install.harness);
@@ -185,6 +192,7 @@ export async function updateInstalled(profileArgs = [], options = {}) {
185
192
  });
186
193
 
187
194
  const operations = [];
195
+ const runInstructions = [];
188
196
  for (const install of candidates) {
189
197
  const result = await installFromSource(install.source, {
190
198
  ...options,
@@ -198,42 +206,126 @@ export async function updateInstalled(profileArgs = [], options = {}) {
198
206
  ...operation,
199
207
  action: options.dryRun ? "would-update" : "updated"
200
208
  })));
209
+ runInstructions.push(...result.runInstructions);
201
210
  }
202
211
 
203
- return { operations, registryPath: registryPath(registryOptions) };
212
+ return { operations, registryPath: registryPath(registryOptions), runInstructions };
204
213
  }
205
214
 
206
215
  export async function initProfile(name, options = {}) {
207
216
  const slug = slugify(name || "new-agent-profile");
208
217
  const root = options.cwd ?? process.cwd();
209
- const profilePath = path.join(root, "agents", "profiles", `${slug}.md`);
210
- const adapterPath = path.join(root, "agents", "adapters", "codex", `${slug}.md`);
218
+ const profilePath = path.join(root, "agents", "profiles", `${slug}.agent.yaml`);
211
219
 
212
220
  if (!options.force) {
213
- for (const target of [profilePath, adapterPath]) {
214
- if (existsSync(target)) {
215
- throw new Error(`${target} already exists. Pass --force to overwrite.`);
216
- }
221
+ if (existsSync(profilePath)) {
222
+ throw new Error(`${profilePath} already exists. Pass --force to overwrite.`);
217
223
  }
218
224
  }
219
225
 
220
226
  const title = titleCase(slug);
221
- const profileContent = `---\nslug: ${slug}\nname: ${title}\nkind: operational-agent-profile\nsummary: Describe when to use this agent profile.\nrecommended_model: inherit\nrecommended_reasoning_effort: medium\nhome_notes_template: "~/.agents/homes/${slug}/<project>/notes"\n---\n\n# ${title}\n\nDescribe the agent's mission, boundaries, tools, coordination model, notes, and reporting style.\n`;
222
- const adapterContent = `---\nslug: ${slug}\nprofile: ../../profiles/${slug}.md\nharness: codex\nmodel: inherit\nreasoning_effort: medium\nrequired_skills: []\n---\n\n# Codex Adapter: ${title}\n\nLoad the canonical profile at \`agents/profiles/${slug}.md\`.\n`;
227
+ const profileContent = `schema: awesome-agents/v1\nid: ${slug}\nname: ${title}\ndescription: Describe when to use this agent profile.\nmodel: inherit\nreasoning_effort: medium\nhome_notes_template: "~/.agents/homes/${slug}/<project>/notes"\ninstructions: |\n Describe the agent's mission, boundaries, tools, coordination model, notes,\n and reporting style.\n`;
223
228
 
224
229
  if (!options.dryRun) {
225
230
  await fs.mkdir(path.dirname(profilePath), { recursive: true });
226
- await fs.mkdir(path.dirname(adapterPath), { recursive: true });
227
231
  await fs.writeFile(profilePath, profileContent);
228
- await fs.writeFile(adapterPath, adapterContent);
229
232
  }
230
233
 
231
234
  return {
232
235
  action: options.dryRun ? "would-init" : "initialized",
233
- files: [profilePath, adapterPath]
236
+ files: [profilePath]
234
237
  };
235
238
  }
236
239
 
240
+ function buildRunInstructions(operations, env = process.env) {
241
+ const instructions = [];
242
+ const seen = new Set();
243
+
244
+ for (const operation of operations) {
245
+ if (String(operation.action).startsWith("would-")) {
246
+ continue;
247
+ }
248
+
249
+ const instruction = runInstructionForOperation(operation, env);
250
+ if (!instruction) {
251
+ continue;
252
+ }
253
+
254
+ const key = `${instruction.harness}:${instruction.profile}:${instruction.command}`;
255
+ if (seen.has(key)) {
256
+ continue;
257
+ }
258
+ seen.add(key);
259
+ instructions.push(instruction);
260
+ }
261
+
262
+ return instructions;
263
+ }
264
+
265
+ function runInstructionForOperation(operation, env) {
266
+ if (operation.harness === "codex") {
267
+ if (!commandExists("codex", env)) {
268
+ return undefined;
269
+ }
270
+ return {
271
+ profile: operation.profile,
272
+ harness: operation.harness,
273
+ command: `codex --profile ${shellWord(operation.profile)}`,
274
+ note: "Starts Codex with this installed profile."
275
+ };
276
+ }
277
+
278
+ if (operation.harness === "claude-code") {
279
+ if (!commandExists("claude", env)) {
280
+ return undefined;
281
+ }
282
+ return {
283
+ profile: operation.profile,
284
+ harness: operation.harness,
285
+ command: `claude --agent ${shellWord(operation.profile)}`,
286
+ note: "Starts Claude Code with this installed agent profile."
287
+ };
288
+ }
289
+
290
+ if (operation.harness === "opencode") {
291
+ if (!commandExists("opencode", env)) {
292
+ return undefined;
293
+ }
294
+ return {
295
+ profile: operation.profile,
296
+ harness: operation.harness,
297
+ command: "opencode",
298
+ note: `Start OpenCode, then invoke @${operation.profile} in the session.`
299
+ };
300
+ }
301
+
302
+ return undefined;
303
+ }
304
+
305
+ function commandExists(command, env) {
306
+ const pathValue = env.PATH ?? "";
307
+ if (!pathValue) {
308
+ return false;
309
+ }
310
+
311
+ const extensions = process.platform === "win32"
312
+ ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
313
+ : [""];
314
+
315
+ return pathValue
316
+ .split(path.delimiter)
317
+ .filter(Boolean)
318
+ .some((directory) => extensions.some((extension) => existsSync(path.join(directory, `${command}${extension}`))));
319
+ }
320
+
321
+ function shellWord(value) {
322
+ const text = String(value);
323
+ if (/^[A-Za-z0-9._/@:-]+$/.test(text)) {
324
+ return text;
325
+ }
326
+ return `'${text.replaceAll("'", "'\\''")}'`;
327
+ }
328
+
237
329
  function selectProfiles(profiles, selectors, all = false) {
238
330
  if (all || selectors.length === 0 || selectors.includes("*")) {
239
331
  return profiles;
@@ -268,11 +360,23 @@ async function writeManagedFile(target, content, options = {}) {
268
360
  await fs.writeFile(target, content);
269
361
  }
270
362
 
271
- function resolveScope(options = {}) {
363
+ function resolveScope(options = {}, harnesses = []) {
272
364
  if (options.project && options.global) {
273
365
  throw new Error("Use either --global or --project, not both.");
274
366
  }
275
- return options.global ? "global" : "project";
367
+ if (options.project && harnesses.includes("codex")) {
368
+ throw new Error("Codex profiles are loaded from $CODEX_HOME/<name>.config.toml and cannot be installed project-locally. Use --global or choose a different harness.");
369
+ }
370
+ if (options.global) {
371
+ return "global";
372
+ }
373
+ if (options.project) {
374
+ return "project";
375
+ }
376
+ if (harnesses.includes("codex")) {
377
+ return "global";
378
+ }
379
+ return "project";
276
380
  }
277
381
 
278
382
  function normalizeProfileSelectors(values) {
package/src/renderers.js CHANGED
@@ -56,12 +56,7 @@ export function resolveTargetPath(profile, agent, options = {}) {
56
56
  : process.env.CODEX_HOME
57
57
  ? path.resolve(expandHome(process.env.CODEX_HOME, home))
58
58
  : path.join(home, ".codex");
59
-
60
- if (scope === "project") {
61
- return path.join(cwd, ".codex", "agents", `${profile.slug}.toml`);
62
- }
63
-
64
- return path.join(codexHome, "agents", `${profile.slug}.toml`);
59
+ return path.join(codexHome, `${profile.slug}.config.toml`);
65
60
  }
66
61
 
67
62
  if (normalized === "claude-code") {
@@ -170,9 +165,11 @@ function buildInstructionBody(profile, adapter, harness) {
170
165
  "",
171
166
  "## Installed Profile Context",
172
167
  "",
173
- `- Canonical profile slug: \`${profile.slug}\``,
168
+ `- Installed identity: \`${profile.slug}\``,
169
+ `- Role/name: \`${profile.name}\``,
174
170
  `- Installed for: \`${harness}\``,
175
- "- This profile is a reusable operational agent profile, not local machine setup.",
171
+ "- When asked who you are, what agent is running, or what role you are acting as, answer with this identity and role.",
172
+ "- This profile is a reusable operational agent profile, not a skill or local machine setup.",
176
173
  "- The runtime agent manages any profile-specific home directory and notes at task time."
177
174
  ];
178
175
 
package/src/source.js CHANGED
@@ -4,14 +4,24 @@ import fs from "node:fs/promises";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
- import { DEFAULT_SOURCE } from "./constants.js";
7
+ import YAML from "yaml";
8
8
  import { parseFrontmatter } from "./frontmatter.js";
9
9
 
10
10
  const GITHUB_SHORTHAND = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
11
+ const PROFILE_SUFFIXES = [
12
+ ".agent.yaml",
13
+ ".agent.yml",
14
+ ".agf.yaml",
15
+ ".agf.yml",
16
+ ".yaml",
17
+ ".yml",
18
+ ".agent.md",
19
+ ".md"
20
+ ];
11
21
 
12
22
  export function splitSourceSpec(spec) {
13
23
  if (!spec) {
14
- return { source: DEFAULT_SOURCE, profile: undefined };
24
+ return { source: undefined, profile: undefined };
15
25
  }
16
26
 
17
27
  const atIndex = spec.lastIndexOf("@");
@@ -57,13 +67,17 @@ export function normalizeGithubUrl(source) {
57
67
  return undefined;
58
68
  }
59
69
 
60
- export async function materializeSource(sourceInput = DEFAULT_SOURCE, options = {}) {
70
+ export async function materializeSource(sourceInput, options = {}) {
61
71
  const home = options.home ?? os.homedir();
62
- const source = sourceInput || DEFAULT_SOURCE;
72
+ const source = sourceInput;
73
+
74
+ if (!source) {
75
+ throw new Error("Specify a source. Use a local path, owner/repo, or GitHub URL.");
76
+ }
63
77
 
64
78
  if (isLocalSource(source, home)) {
65
79
  const sourcePath = path.resolve(expandHome(source, home));
66
- await assertTouchGrassLayout(sourcePath);
80
+ await assertAgentProfileLayout(sourcePath);
67
81
  return {
68
82
  kind: "local",
69
83
  source: sourcePath,
@@ -89,7 +103,7 @@ export async function materializeSource(sourceInput = DEFAULT_SOURCE, options =
89
103
  throw new Error(`Could not clone ${source}: ${detail}`);
90
104
  }
91
105
 
92
- await assertTouchGrassLayout(cloneDir);
106
+ await assertAgentProfileLayout(cloneDir);
93
107
 
94
108
  return {
95
109
  kind: "git",
@@ -102,7 +116,7 @@ export async function materializeSource(sourceInput = DEFAULT_SOURCE, options =
102
116
  };
103
117
  }
104
118
 
105
- async function assertTouchGrassLayout(sourcePath) {
119
+ async function assertAgentProfileLayout(sourcePath) {
106
120
  const profilesDir = path.join(sourcePath, "agents", "profiles");
107
121
  if (!existsSync(profilesDir)) {
108
122
  throw new Error(`No agents/profiles directory found in ${sourcePath}`);
@@ -116,25 +130,15 @@ export async function loadCatalog(sourcePath) {
116
130
  const profiles = [];
117
131
 
118
132
  for (const file of files) {
119
- if (!file.isFile() || !file.name.endsWith(".md")) {
133
+ if (!file.isFile() || !isProfileFile(file.name)) {
120
134
  continue;
121
135
  }
122
136
 
123
137
  const filePath = path.join(profilesDir, file.name);
124
- const raw = await fs.readFile(filePath, "utf8");
125
- const parsed = parseFrontmatter(raw, filePath);
126
- const slug = parsed.attributes.slug ?? path.basename(file.name, ".md");
127
-
138
+ const profile = await loadProfileFile(filePath, sourcePath);
128
139
  profiles.push({
129
- slug,
130
- name: parsed.attributes.name ?? slug,
131
- summary: parsed.attributes.summary ?? "",
132
- attributes: parsed.attributes,
133
- body: parsed.body,
134
- raw,
135
- filePath,
136
- relativePath: path.relative(sourcePath, filePath),
137
- adapters: await loadAdapters(adapterRoot, slug, sourcePath)
140
+ ...profile,
141
+ adapters: await loadAdapters(adapterRoot, profile.slug, sourcePath)
138
142
  });
139
143
  }
140
144
 
@@ -154,13 +158,13 @@ async function loadAdapters(adapterRoot, slug, sourcePath) {
154
158
  continue;
155
159
  }
156
160
 
157
- const adapterPath = path.join(adapterRoot, harnessDir.name, `${slug}.md`);
161
+ const adapterPath = await findProfileLikeFile(path.join(adapterRoot, harnessDir.name), slug);
158
162
  if (!existsSync(adapterPath)) {
159
163
  continue;
160
164
  }
161
165
 
162
166
  const raw = await fs.readFile(adapterPath, "utf8");
163
- const parsed = parseFrontmatter(raw, adapterPath);
167
+ const parsed = await loadDefinitionFile(adapterPath, raw);
164
168
  adapters[harnessDir.name] = {
165
169
  harness: harnessDir.name,
166
170
  attributes: parsed.attributes,
@@ -174,6 +178,179 @@ async function loadAdapters(adapterRoot, slug, sourcePath) {
174
178
  return adapters;
175
179
  }
176
180
 
181
+ async function loadProfileFile(filePath, sourcePath) {
182
+ const raw = await fs.readFile(filePath, "utf8");
183
+ const parsed = await loadDefinitionFile(filePath, raw);
184
+ const fallbackSlug = profileSlugFromFilename(path.basename(filePath));
185
+ const slug = parsed.attributes.slug ?? parsed.attributes.id ?? fallbackSlug;
186
+ const name = parsed.attributes.name ?? titleize(slug);
187
+ const summary = parsed.attributes.summary ?? parsed.attributes.description ?? "";
188
+
189
+ return {
190
+ slug,
191
+ name,
192
+ summary,
193
+ attributes: {
194
+ ...parsed.attributes,
195
+ slug,
196
+ name,
197
+ summary
198
+ },
199
+ body: parsed.body,
200
+ raw,
201
+ filePath,
202
+ relativePath: path.relative(sourcePath, filePath),
203
+ format: parsed.format
204
+ };
205
+ }
206
+
207
+ async function loadDefinitionFile(filePath, rawInput) {
208
+ const raw = rawInput ?? await fs.readFile(filePath, "utf8");
209
+ if (isMarkdownProfile(filePath)) {
210
+ const parsed = parseFrontmatter(raw, filePath);
211
+ return {
212
+ attributes: normalizeFlatAttributes(parsed.attributes),
213
+ body: parsed.body,
214
+ format: "markdown-frontmatter"
215
+ };
216
+ }
217
+
218
+ let document;
219
+ try {
220
+ document = YAML.parse(raw) ?? {};
221
+ } catch (error) {
222
+ throw new Error(`Could not parse YAML profile in ${filePath}: ${error.message}`);
223
+ }
224
+ if (!isObject(document)) {
225
+ throw new Error(`YAML profile in ${filePath} must be a mapping/object.`);
226
+ }
227
+
228
+ const normalized = normalizeYamlProfile(document, filePath);
229
+ return {
230
+ attributes: normalized.attributes,
231
+ body: normalized.body,
232
+ format: normalized.format
233
+ };
234
+ }
235
+
236
+ function normalizeYamlProfile(document, filePath) {
237
+ const metadata = isObject(document.metadata) ? document.metadata : {};
238
+ const executionPolicy = isObject(document.execution_policy) ? document.execution_policy : {};
239
+ const executionConfig = isObject(executionPolicy.config) ? executionPolicy.config : {};
240
+
241
+ const id = firstString(
242
+ document.slug,
243
+ document.id,
244
+ metadata.id,
245
+ profileSlugFromFilename(path.basename(filePath))
246
+ );
247
+ const name = firstString(document.name, metadata.name, titleize(id));
248
+ const description = firstString(document.summary, document.description, metadata.description, "");
249
+ const instructions = firstString(
250
+ document.instructions,
251
+ document.instruction,
252
+ document.prompt,
253
+ document.system_prompt,
254
+ executionConfig.instructions,
255
+ ""
256
+ );
257
+ const model = firstString(document.recommended_model, document.model, executionConfig.model, "");
258
+ const reasoningEffort = firstString(
259
+ document.recommended_reasoning_effort,
260
+ document.reasoning_effort,
261
+ document.model_reasoning_effort,
262
+ executionConfig.reasoning_effort,
263
+ ""
264
+ );
265
+ const recommendedModels = document.recommended_models ?? document.models ?? undefined;
266
+
267
+ const attributes = {
268
+ ...document,
269
+ slug: id,
270
+ id,
271
+ name,
272
+ summary: description,
273
+ description,
274
+ kind: document.kind ?? document.type ?? "operational-agent-profile"
275
+ };
276
+
277
+ if (model && !attributes.recommended_model) {
278
+ attributes.recommended_model = model;
279
+ }
280
+ if (reasoningEffort && !attributes.recommended_reasoning_effort) {
281
+ attributes.recommended_reasoning_effort = reasoningEffort;
282
+ }
283
+ if (recommendedModels && !attributes.recommended_models) {
284
+ attributes.recommended_models = recommendedModels;
285
+ }
286
+
287
+ return {
288
+ attributes,
289
+ body: renderYamlProfileBody(name, description, instructions),
290
+ format: isAgentFormat(document) ? "agent-format-yaml" : "yaml"
291
+ };
292
+ }
293
+
294
+ function normalizeFlatAttributes(attributes) {
295
+ return {
296
+ ...attributes,
297
+ summary: attributes.summary ?? attributes.description ?? ""
298
+ };
299
+ }
300
+
301
+ function renderYamlProfileBody(name, description, instructions) {
302
+ const body = instructions || description || "No instructions provided.";
303
+ return `# ${name}\n\n${body}`.trimEnd();
304
+ }
305
+
306
+ async function findProfileLikeFile(directory, slug) {
307
+ for (const suffix of PROFILE_SUFFIXES) {
308
+ const candidate = path.join(directory, `${slug}${suffix}`);
309
+ if (existsSync(candidate)) {
310
+ return candidate;
311
+ }
312
+ }
313
+ return path.join(directory, `${slug}.md`);
314
+ }
315
+
316
+ function isProfileFile(filename) {
317
+ return PROFILE_SUFFIXES.some((suffix) => filename.endsWith(suffix));
318
+ }
319
+
320
+ function isMarkdownProfile(filePath) {
321
+ return filePath.endsWith(".md");
322
+ }
323
+
324
+ function profileSlugFromFilename(filename) {
325
+ const suffix = PROFILE_SUFFIXES.find((value) => filename.endsWith(value));
326
+ return suffix ? filename.slice(0, -suffix.length) : path.basename(filename, path.extname(filename));
327
+ }
328
+
329
+ function isAgentFormat(document) {
330
+ return isObject(document.metadata) && isObject(document.execution_policy);
331
+ }
332
+
333
+ function isObject(value) {
334
+ return value !== null && typeof value === "object" && !Array.isArray(value);
335
+ }
336
+
337
+ function firstString(...values) {
338
+ for (const value of values) {
339
+ if (typeof value === "string" && value.trim()) {
340
+ return value.trim();
341
+ }
342
+ }
343
+ return "";
344
+ }
345
+
346
+ function titleize(slug) {
347
+ return String(slug)
348
+ .split(/[-_]+/)
349
+ .filter(Boolean)
350
+ .map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
351
+ .join(" ");
352
+ }
353
+
177
354
  export function resolvePackageRoot() {
178
355
  return path.dirname(path.dirname(fileURLToPath(import.meta.url)));
179
356
  }