awesome-agents 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.
@@ -0,0 +1,315 @@
1
+ import { existsSync } from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { DEFAULT_AGENT, SUPPORTED_AGENTS } from "./constants.js";
5
+ import { contentHash, isGeneratedContent, normalizeAgentList, renderForAgent, resolveTargetPath } from "./renderers.js";
6
+ import { loadCatalog, materializeSource, splitSourceSpec } from "./source.js";
7
+ import { readRegistry, registryPath, removeInstall, upsertInstall, writeRegistry } from "./registry.js";
8
+
9
+ export async function listAvailable(sourceInput, options = {}) {
10
+ const { source } = splitSourceSpec(sourceInput);
11
+ const materialized = await materializeSource(source, options);
12
+ try {
13
+ const catalog = await loadCatalog(materialized.path);
14
+ return catalog.profiles.map((profile) => profileSummary(profile, materialized.source));
15
+ } finally {
16
+ await materialized.cleanup();
17
+ }
18
+ }
19
+
20
+ export async function installFromSource(sourceSpec, options = {}) {
21
+ const split = splitSourceSpec(sourceSpec);
22
+ const source = split.source;
23
+ const optionProfiles = normalizeProfileSelectors(options.profiles ?? options.profile ?? options.skills ?? options.skill);
24
+ const selectors = split.profile ? [...optionProfiles, split.profile] : optionProfiles;
25
+ const scope = resolveScope(options);
26
+ const harnesses = normalizeAgentList(options.agents ?? options.agent, {
27
+ all: options.all,
28
+ defaultAgent: DEFAULT_AGENT
29
+ });
30
+
31
+ const materialized = await materializeSource(source, options);
32
+ try {
33
+ const catalog = await loadCatalog(materialized.path);
34
+ const profiles = selectProfiles(catalog.profiles, selectors, options.all);
35
+ const registryOptions = { ...options, scope };
36
+ const registry = await readRegistry(registryOptions);
37
+ const operations = [];
38
+ const installedAt = new Date().toISOString();
39
+
40
+ for (const profile of profiles) {
41
+ for (const harness of harnesses) {
42
+ const content = renderForAgent(profile, harness, { source: materialized.source });
43
+ const target = resolveTargetPath(profile, harness, { ...options, scope });
44
+ await writeManagedFile(target, content, options);
45
+
46
+ const install = {
47
+ profile: profile.slug,
48
+ name: profile.name,
49
+ summary: profile.summary,
50
+ harness,
51
+ scope,
52
+ source: materialized.source,
53
+ sourceKind: materialized.kind,
54
+ target,
55
+ installedAt,
56
+ contentSha256: contentHash(content),
57
+ dryRun: Boolean(options.dryRun)
58
+ };
59
+
60
+ if (!options.dryRun) {
61
+ upsertInstall(registry, install);
62
+ }
63
+
64
+ operations.push({
65
+ action: options.dryRun ? "would-install" : "installed",
66
+ ...install
67
+ });
68
+ }
69
+ }
70
+
71
+ if (!options.dryRun) {
72
+ await writeRegistry(registry, registryOptions);
73
+ }
74
+
75
+ return { operations, registryPath: registryPath(registryOptions) };
76
+ } finally {
77
+ await materialized.cleanup();
78
+ }
79
+ }
80
+
81
+ export async function useFromSource(sourceSpec, options = {}) {
82
+ const split = splitSourceSpec(sourceSpec);
83
+ const profileSelector = options.profile ?? options.skill ?? split.profile;
84
+ if (!profileSelector) {
85
+ throw new Error("Specify a profile with source@profile or --profile <profile>.");
86
+ }
87
+
88
+ const harness = normalizeAgentList(options.agent ? [options.agent] : options.agents, {
89
+ defaultAgent: DEFAULT_AGENT
90
+ })[0];
91
+ const materialized = await materializeSource(split.source, options);
92
+ try {
93
+ const catalog = await loadCatalog(materialized.path);
94
+ const [profile] = selectProfiles(catalog.profiles, [profileSelector], false);
95
+ const content = renderForAgent(profile, harness, { source: materialized.source });
96
+ return {
97
+ profile: profile.slug,
98
+ harness,
99
+ source: materialized.source,
100
+ content
101
+ };
102
+ } finally {
103
+ await materialized.cleanup();
104
+ }
105
+ }
106
+
107
+ export async function listInstalled(options = {}) {
108
+ const scope = resolveScope(options);
109
+ const registry = await readRegistry({ ...options, scope });
110
+ const harnessInput = options.agents ?? options.agent;
111
+ const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
112
+ const installs = registry.installs
113
+ .filter((install) => !harnessFilter || harnessFilter.has(install.harness))
114
+ .map((install) => ({
115
+ ...install,
116
+ exists: existsSync(install.target)
117
+ }));
118
+
119
+ return {
120
+ scope,
121
+ registryPath: registryPath({ ...options, scope }),
122
+ installs
123
+ };
124
+ }
125
+
126
+ export async function removeInstalled(profileArgs = [], options = {}) {
127
+ const scope = resolveScope(options);
128
+ const registryOptions = { ...options, scope };
129
+ const registry = await readRegistry(registryOptions);
130
+ const selectors = normalizeProfileSelectors(profileArgs);
131
+ const removeAll = Boolean(options.all);
132
+ if (!removeAll && selectors.length === 0) {
133
+ throw new Error("Specify at least one profile to remove, or pass --all.");
134
+ }
135
+
136
+ const harnessInput = options.agents ?? options.agent;
137
+ const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
138
+ const operations = [];
139
+ const matched = registry.installs.filter((install) => {
140
+ const profileMatch = removeAll || selectors.includes(install.profile);
141
+ const harnessMatch = !harnessFilter || harnessFilter.has(install.harness);
142
+ return install.scope === scope && profileMatch && harnessMatch;
143
+ });
144
+
145
+ for (const install of matched) {
146
+ const exists = existsSync(install.target);
147
+ if (exists) {
148
+ const content = await fs.readFile(install.target, "utf8");
149
+ if (!isGeneratedContent(content) && !options.force) {
150
+ throw new Error(`Refusing to remove unmanaged file ${install.target}. Pass --force to override.`);
151
+ }
152
+ if (!options.dryRun) {
153
+ await fs.rm(install.target, { force: true });
154
+ }
155
+ }
156
+
157
+ if (!options.dryRun) {
158
+ removeInstall(registry, install);
159
+ }
160
+
161
+ operations.push({
162
+ action: options.dryRun ? "would-remove" : "removed",
163
+ ...install,
164
+ existed: exists
165
+ });
166
+ }
167
+
168
+ if (!options.dryRun) {
169
+ await writeRegistry(registry, registryOptions);
170
+ }
171
+
172
+ return { operations, registryPath: registryPath(registryOptions) };
173
+ }
174
+
175
+ export async function updateInstalled(profileArgs = [], options = {}) {
176
+ const scope = resolveScope(options);
177
+ const registryOptions = { ...options, scope };
178
+ const registry = await readRegistry(registryOptions);
179
+ const selectors = normalizeProfileSelectors(profileArgs);
180
+ const harnessInput = options.agents ?? options.agent;
181
+ const harnessFilter = harnessInput ? new Set(normalizeAgentList(harnessInput)) : undefined;
182
+ const candidates = registry.installs.filter((install) => {
183
+ const profileMatch = selectors.length === 0 || selectors.includes(install.profile);
184
+ const harnessMatch = !harnessFilter || harnessFilter.has(install.harness);
185
+ return install.scope === scope && profileMatch && harnessMatch;
186
+ });
187
+
188
+ const operations = [];
189
+ for (const install of candidates) {
190
+ const result = await installFromSource(install.source, {
191
+ ...options,
192
+ profiles: [install.profile],
193
+ agents: [install.harness],
194
+ global: scope === "global",
195
+ project: scope === "project",
196
+ force: true
197
+ });
198
+ operations.push(...result.operations.map((operation) => ({
199
+ ...operation,
200
+ action: options.dryRun ? "would-update" : "updated"
201
+ })));
202
+ }
203
+
204
+ return { operations, registryPath: registryPath(registryOptions) };
205
+ }
206
+
207
+ export async function initProfile(name, options = {}) {
208
+ const slug = slugify(name || "new-agent-profile");
209
+ const root = options.cwd ?? process.cwd();
210
+ const profilePath = path.join(root, "agents", "profiles", `${slug}.md`);
211
+ const adapterPath = path.join(root, "agents", "adapters", "codex", `${slug}.md`);
212
+
213
+ if (!options.force) {
214
+ for (const target of [profilePath, adapterPath]) {
215
+ if (existsSync(target)) {
216
+ throw new Error(`${target} already exists. Pass --force to overwrite.`);
217
+ }
218
+ }
219
+ }
220
+
221
+ const title = titleCase(slug);
222
+ 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`;
223
+ 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`;
224
+
225
+ if (!options.dryRun) {
226
+ await fs.mkdir(path.dirname(profilePath), { recursive: true });
227
+ await fs.mkdir(path.dirname(adapterPath), { recursive: true });
228
+ await fs.writeFile(profilePath, profileContent);
229
+ await fs.writeFile(adapterPath, adapterContent);
230
+ }
231
+
232
+ return {
233
+ action: options.dryRun ? "would-init" : "initialized",
234
+ files: [profilePath, adapterPath]
235
+ };
236
+ }
237
+
238
+ function selectProfiles(profiles, selectors, all = false) {
239
+ if (all || selectors.length === 0 || selectors.includes("*")) {
240
+ return profiles;
241
+ }
242
+
243
+ const selected = [];
244
+ for (const selector of selectors) {
245
+ const profile = profiles.find((candidate) => candidate.slug === selector || candidate.name === selector);
246
+ if (!profile) {
247
+ const available = profiles.map((candidate) => candidate.slug).join(", ");
248
+ throw new Error(`Profile "${selector}" was not found. Available profiles: ${available}`);
249
+ }
250
+ selected.push(profile);
251
+ }
252
+
253
+ return [...new Map(selected.map((profile) => [profile.slug, profile])).values()];
254
+ }
255
+
256
+ async function writeManagedFile(target, content, options = {}) {
257
+ if (options.dryRun) {
258
+ return;
259
+ }
260
+
261
+ if (existsSync(target) && !options.force) {
262
+ const existing = await fs.readFile(target, "utf8");
263
+ if (!isGeneratedContent(existing)) {
264
+ throw new Error(`Refusing to overwrite unmanaged file ${target}. Pass --force to override.`);
265
+ }
266
+ }
267
+
268
+ await fs.mkdir(path.dirname(target), { recursive: true });
269
+ await fs.writeFile(target, content);
270
+ }
271
+
272
+ function resolveScope(options = {}) {
273
+ if (options.project && options.global) {
274
+ throw new Error("Use either --global or --project, not both.");
275
+ }
276
+ return options.global ? "global" : "project";
277
+ }
278
+
279
+ function normalizeProfileSelectors(values) {
280
+ return [values]
281
+ .flat(2)
282
+ .filter(Boolean)
283
+ .flatMap((value) => String(value).split(","))
284
+ .map((value) => value.trim())
285
+ .filter(Boolean);
286
+ }
287
+
288
+ function profileSummary(profile, source) {
289
+ return {
290
+ slug: profile.slug,
291
+ name: profile.name,
292
+ summary: profile.summary,
293
+ kind: profile.attributes.kind,
294
+ source,
295
+ adapters: Object.keys(profile.adapters)
296
+ };
297
+ }
298
+
299
+ function slugify(value) {
300
+ return String(value)
301
+ .trim()
302
+ .toLowerCase()
303
+ .replace(/[^a-z0-9_-]+/g, "-")
304
+ .replace(/^-+|-+$/g, "") || "new-agent-profile";
305
+ }
306
+
307
+ function titleCase(slug) {
308
+ return slug
309
+ .split(/[-_]/)
310
+ .filter(Boolean)
311
+ .map((part) => `${part[0].toUpperCase()}${part.slice(1)}`)
312
+ .join(" ");
313
+ }
314
+
315
+ export { SUPPORTED_AGENTS };
@@ -0,0 +1,58 @@
1
+ import { existsSync } from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { REGISTRY_DIRNAME, REGISTRY_FILENAME } from "./constants.js";
6
+ import { expandHome } from "./source.js";
7
+
8
+ export function registryPath(options = {}) {
9
+ const scope = options.scope ?? "global";
10
+ const cwd = options.cwd ?? process.cwd();
11
+ const home = path.resolve(expandHome(options.home ?? os.homedir()));
12
+
13
+ if (scope === "project") {
14
+ return path.join(cwd, REGISTRY_DIRNAME, REGISTRY_FILENAME);
15
+ }
16
+
17
+ return path.join(home, REGISTRY_DIRNAME, REGISTRY_FILENAME);
18
+ }
19
+
20
+ export async function readRegistry(options = {}) {
21
+ const filePath = registryPath(options);
22
+ if (!existsSync(filePath)) {
23
+ return { version: 1, installs: [] };
24
+ }
25
+
26
+ const raw = await fs.readFile(filePath, "utf8");
27
+ const parsed = JSON.parse(raw);
28
+ return {
29
+ version: parsed.version ?? 1,
30
+ installs: Array.isArray(parsed.installs) ? parsed.installs : []
31
+ };
32
+ }
33
+
34
+ export async function writeRegistry(registry, options = {}) {
35
+ const filePath = registryPath(options);
36
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
37
+ await fs.writeFile(filePath, `${JSON.stringify(registry, null, 2)}\n`);
38
+ }
39
+
40
+ export function upsertInstall(registry, install) {
41
+ const key = installKey(install);
42
+ const index = registry.installs.findIndex((entry) => installKey(entry) === key);
43
+ if (index >= 0) {
44
+ registry.installs[index] = { ...registry.installs[index], ...install };
45
+ } else {
46
+ registry.installs.push(install);
47
+ }
48
+ registry.installs.sort((a, b) => installKey(a).localeCompare(installKey(b)));
49
+ }
50
+
51
+ export function removeInstall(registry, install) {
52
+ const key = installKey(install);
53
+ registry.installs = registry.installs.filter((entry) => installKey(entry) !== key);
54
+ }
55
+
56
+ export function installKey(install) {
57
+ return `${install.scope}:${install.harness}:${install.profile}`;
58
+ }
@@ -0,0 +1,256 @@
1
+ import crypto from "node:crypto";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { AGENT_ALIASES, GENERATED_MARKER, SUPPORTED_AGENTS } from "./constants.js";
5
+ import { stringifyFrontmatter } from "./frontmatter.js";
6
+ import { expandHome } from "./source.js";
7
+
8
+ export function normalizeAgent(agent) {
9
+ const normalized = AGENT_ALIASES.get(String(agent).toLowerCase());
10
+ if (!normalized) {
11
+ throw new Error(`Unsupported agent "${agent}". Supported agents: ${SUPPORTED_AGENTS.join(", ")}`);
12
+ }
13
+ return normalized;
14
+ }
15
+
16
+ export function normalizeAgentList(input, options = {}) {
17
+ if (options.all) {
18
+ return SUPPORTED_AGENTS;
19
+ }
20
+
21
+ const rawAgents = flattenValues(input);
22
+ if (rawAgents.length === 0) {
23
+ return [options.defaultAgent ?? "codex"];
24
+ }
25
+
26
+ if (rawAgents.includes("*")) {
27
+ return SUPPORTED_AGENTS;
28
+ }
29
+
30
+ return [...new Set(rawAgents.map(normalizeAgent))];
31
+ }
32
+
33
+ export function renderForAgent(profile, agent, context) {
34
+ const normalized = normalizeAgent(agent);
35
+ if (normalized === "codex") {
36
+ return renderCodex(profile, context);
37
+ }
38
+ if (normalized === "claude-code") {
39
+ return renderClaudeCode(profile, context);
40
+ }
41
+ if (normalized === "opencode") {
42
+ return renderOpenCode(profile, context);
43
+ }
44
+ throw new Error(`Unsupported agent "${agent}"`);
45
+ }
46
+
47
+ export function resolveTargetPath(profile, agent, options = {}) {
48
+ const normalized = normalizeAgent(agent);
49
+ const scope = options.scope ?? "global";
50
+ const cwd = options.cwd ?? process.cwd();
51
+ const home = path.resolve(expandHome(options.home ?? os.homedir()));
52
+
53
+ if (normalized === "codex") {
54
+ const codexHome = options.codexHome
55
+ ? path.resolve(expandHome(options.codexHome, home))
56
+ : process.env.CODEX_HOME
57
+ ? path.resolve(expandHome(process.env.CODEX_HOME, home))
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`);
65
+ }
66
+
67
+ if (normalized === "claude-code") {
68
+ if (scope === "project") {
69
+ return path.join(cwd, ".claude", "agents", `${profile.slug}.md`);
70
+ }
71
+
72
+ const claudeHome = options.claudeHome
73
+ ? path.resolve(expandHome(options.claudeHome, home))
74
+ : process.env.CLAUDE_HOME
75
+ ? path.resolve(expandHome(process.env.CLAUDE_HOME, home))
76
+ : path.join(home, ".claude");
77
+ return path.join(claudeHome, "agents", `${profile.slug}.md`);
78
+ }
79
+
80
+ if (normalized === "opencode") {
81
+ if (scope === "project") {
82
+ return path.join(cwd, ".opencode", "agents", `${profile.slug}.md`);
83
+ }
84
+
85
+ const opencodeHome = options.opencodeHome
86
+ ? path.resolve(expandHome(options.opencodeHome, home))
87
+ : process.env.OPENCODE_CONFIG_DIR
88
+ ? path.resolve(expandHome(process.env.OPENCODE_CONFIG_DIR, home))
89
+ : path.join(process.env.XDG_CONFIG_HOME ? path.resolve(expandHome(process.env.XDG_CONFIG_HOME, home)) : path.join(home, ".config"), "opencode");
90
+ return path.join(opencodeHome, "agents", `${profile.slug}.md`);
91
+ }
92
+
93
+ throw new Error(`Unsupported agent "${agent}"`);
94
+ }
95
+
96
+ export function contentHash(content) {
97
+ return crypto.createHash("sha256").update(content).digest("hex");
98
+ }
99
+
100
+ export function isGeneratedContent(content) {
101
+ return content.includes(GENERATED_MARKER);
102
+ }
103
+
104
+ function renderCodex(profile, context) {
105
+ const adapter = profile.adapters.codex;
106
+ const model = adapter?.attributes.model ?? profile.attributes.recommended_model ?? first(profile.attributes.recommended_models);
107
+ const effort = adapter?.attributes.reasoning_effort ?? profile.attributes.recommended_reasoning_effort;
108
+ const instructions = buildInstructionBody(profile, adapter, "codex");
109
+ const lines = [
110
+ `# ${GENERATED_MARKER}.`,
111
+ `# Source: ${context.source}`,
112
+ `# Profile: ${profile.slug}`,
113
+ "",
114
+ `name = ${tomlString(profile.slug)}`,
115
+ `description = ${tomlString(profile.summary || profile.name)}`
116
+ ];
117
+
118
+ if (model && model !== "inherit") {
119
+ lines.push(`model = ${tomlString(model)}`);
120
+ }
121
+ if (effort && effort !== "inherit") {
122
+ lines.push(`model_reasoning_effort = ${tomlString(effort)}`);
123
+ }
124
+ lines.push("");
125
+ lines.push(`developer_instructions = ${tomlMultiline(instructions)}`);
126
+ return `${lines.join("\n")}\n`;
127
+ }
128
+
129
+ function renderClaudeCode(profile, context) {
130
+ const attributes = {
131
+ name: profile.slug,
132
+ description: profile.summary || profile.name
133
+ };
134
+ const model = chooseClaudeModel(profile);
135
+ const effort = profile.attributes.recommended_reasoning_effort;
136
+
137
+ if (model && model !== "inherit") {
138
+ attributes.model = model;
139
+ }
140
+ if (effort && effort !== "inherit" && ["low", "medium", "high", "xhigh", "max"].includes(effort)) {
141
+ attributes.effort = effort;
142
+ }
143
+
144
+ attributes.tools = "Bash";
145
+
146
+ const marker = htmlMarker(profile, "claude-code", context);
147
+ return stringifyFrontmatter(attributes, `${marker}\n\n${buildInstructionBody(profile, undefined, "claude-code")}`);
148
+ }
149
+
150
+ function renderOpenCode(profile, context) {
151
+ const attributes = {
152
+ description: profile.summary || profile.name,
153
+ mode: "subagent",
154
+ permission: {
155
+ edit: "deny"
156
+ }
157
+ };
158
+ const model = chooseOpenCodeModel(profile);
159
+ if (model) {
160
+ attributes.model = model;
161
+ }
162
+
163
+ const marker = htmlMarker(profile, "opencode", context);
164
+ return stringifyFrontmatter(attributes, `${marker}\n\n${buildInstructionBody(profile, undefined, "opencode")}`);
165
+ }
166
+
167
+ function buildInstructionBody(profile, adapter, harness) {
168
+ const parts = [
169
+ profile.body.trimEnd(),
170
+ "",
171
+ "## Installed Profile Context",
172
+ "",
173
+ `- Canonical profile slug: \`${profile.slug}\``,
174
+ `- Installed for: \`${harness}\``,
175
+ "- This profile is a reusable operational agent profile, not local machine setup.",
176
+ "- The runtime agent manages any profile-specific home directory and notes at task time."
177
+ ];
178
+
179
+ if (adapter?.body) {
180
+ parts.push("", "## Harness Adapter", "", adapter.body.trimEnd());
181
+ }
182
+
183
+ return parts.join("\n");
184
+ }
185
+
186
+ function htmlMarker(profile, harness, context) {
187
+ const payload = JSON.stringify({
188
+ package: "awesome-agents",
189
+ profile: profile.slug,
190
+ harness,
191
+ source: context.source
192
+ });
193
+ return `<!-- ${GENERATED_MARKER}: ${payload} -->`;
194
+ }
195
+
196
+ function chooseClaudeModel(profile) {
197
+ const candidates = [
198
+ profile.attributes.recommended_model,
199
+ ...arrayify(profile.attributes.recommended_models)
200
+ ].filter(Boolean).map((value) => String(value).toLowerCase());
201
+
202
+ if (candidates.some((model) => model.includes("opus"))) {
203
+ return "opus";
204
+ }
205
+ if (candidates.some((model) => model.includes("sonnet"))) {
206
+ return "sonnet";
207
+ }
208
+ if (candidates.some((model) => model.includes("haiku"))) {
209
+ return "haiku";
210
+ }
211
+ if (candidates.some((model) => model.includes("fable"))) {
212
+ return "fable";
213
+ }
214
+ return "inherit";
215
+ }
216
+
217
+ function chooseOpenCodeModel(profile) {
218
+ const candidates = [
219
+ profile.attributes.recommended_model,
220
+ ...arrayify(profile.attributes.recommended_models)
221
+ ].filter(Boolean).map(String);
222
+ return candidates.find((model) => model.includes("/"));
223
+ }
224
+
225
+ function tomlString(value) {
226
+ return JSON.stringify(String(value));
227
+ }
228
+
229
+ function tomlMultiline(value) {
230
+ const text = String(value).trimEnd();
231
+ if (!text.includes("'''")) {
232
+ return `'''\n${text}\n'''`;
233
+ }
234
+ return `"""\n${text.replaceAll("\\", "\\\\").replaceAll('"""', '\\"\\"\\"')}\n"""`;
235
+ }
236
+
237
+ function first(value) {
238
+ return arrayify(value)[0];
239
+ }
240
+
241
+ function arrayify(value) {
242
+ if (Array.isArray(value)) {
243
+ return value;
244
+ }
245
+ if (value === undefined || value === null) {
246
+ return [];
247
+ }
248
+ return [value];
249
+ }
250
+
251
+ function flattenValues(values) {
252
+ return arrayify(values)
253
+ .flatMap((value) => String(value).split(","))
254
+ .map((value) => value.trim())
255
+ .filter(Boolean);
256
+ }