@nseng-ai/areg 0.1.1 → 0.1.2

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.
Files changed (43) hide show
  1. package/package.json +7 -6
  2. package/src/cli.ts +0 -33
  3. package/src/context.ts +3 -29
  4. package/src/fake-gateways.ts +108 -380
  5. package/src/gateways/errors.ts +2 -2
  6. package/src/gateways/fs-utils.ts +2 -2
  7. package/src/gateways/project-fs.ts +29 -41
  8. package/src/gateways/project-gateway.ts +98 -31
  9. package/src/gateways.ts +34 -150
  10. package/src/index.ts +2 -28
  11. package/src/operations/check.ts +33 -20
  12. package/src/operations/doctor-skills.ts +71 -7
  13. package/src/operations/file-state.ts +4 -4
  14. package/src/operations/manifest-source-findings.ts +45 -0
  15. package/src/operations/manifest-sources.ts +119 -0
  16. package/src/operations/pi-settings.ts +2 -5
  17. package/src/operations/project-inspection.ts +32 -28
  18. package/src/operations/project-mutations.ts +5 -21
  19. package/src/operations/project-resolution.ts +2 -2
  20. package/src/operations/skill-find.ts +23 -3
  21. package/src/operations/skill-kind-apply-plan.ts +2 -2
  22. package/src/operations/skill-kind-frontmatter.ts +1 -1
  23. package/src/operations/skill-kind-inference.ts +30 -14
  24. package/src/operations/skill-kind.ts +34 -12
  25. package/src/sort.ts +3 -3
  26. package/src/gateways/command-constants.ts +0 -1
  27. package/src/gateways/github-gateway.ts +0 -111
  28. package/src/gateways/host-gateway.ts +0 -35
  29. package/src/gateways/mutation-policy.ts +0 -94
  30. package/src/gateways/npx-skills-gateway.ts +0 -53
  31. package/src/gateways/prompt-gateway.ts +0 -21
  32. package/src/gateways/skillx-workspace-gateway.ts +0 -220
  33. package/src/operations/frontmatter.ts +0 -151
  34. package/src/operations/init.ts +0 -548
  35. package/src/operations/lockfile.ts +0 -107
  36. package/src/operations/managed-markdown-block.ts +0 -47
  37. package/src/operations/project-agents.ts +0 -115
  38. package/src/operations/skill-mirror-conventions.ts +0 -126
  39. package/src/operations/skillx.ts +0 -305
  40. package/src/operations/toml-section.ts +0 -53
  41. package/src/operations/update-skills.ts +0 -325
  42. package/src/real-gateways.ts +0 -6
  43. package/src/skill-lookup.ts +0 -254
@@ -1,115 +0,0 @@
1
- import { formatErrorMessage, isRecord } from "@nseng-ai/foundation/primitives";
2
- import { err, type Result } from "@nseng-ai/foundation/result";
3
- import { parse } from "smol-toml";
4
-
5
- import type { AregTextFileState } from "../gateways.ts";
6
- import { rejectTextState } from "./file-state.ts";
7
-
8
- export const DEFAULT_AGENTS = ["codex", "claude-code"] as const;
9
-
10
- export function resolveProjectAgents(input: {
11
- explicitAgents: readonly string[];
12
- nsToml: AregTextFileState;
13
- aregJson: AregTextFileState;
14
- }): Result<string[]> {
15
- if (input.explicitAgents.length > 0) return { ok: true, value: [...input.explicitAgents] };
16
- const nsAgents = parseNsAregAgentsFromState(input.nsToml);
17
- if (!nsAgents.ok) return nsAgents;
18
- if (nsAgents.value.length > 0) return nsAgents;
19
- const legacyAgents = parseLegacyAregJsonAgentsFromState(input.aregJson);
20
- if (!legacyAgents.ok) return legacyAgents;
21
- if (legacyAgents.value.length > 0) return legacyAgents;
22
- return { ok: true, value: [...DEFAULT_AGENTS] };
23
- }
24
-
25
- export function parseNsAregAgents(text: string, pathLabel = "ns.toml"): Result<string[]> {
26
- let data: unknown;
27
- try {
28
- data = parse(text);
29
- } catch (error) {
30
- return err({
31
- code: "ns_toml_invalid",
32
- message: `Invalid TOML in ${pathLabel}: ${formatErrorMessage(error)}`,
33
- });
34
- }
35
- if (!isRecord(data)) return { ok: true, value: [] };
36
- const areg = data.areg;
37
- if (areg === undefined) return { ok: true, value: [] };
38
- if (!isRecord(areg))
39
- return err({
40
- code: "ns_toml_invalid",
41
- message: `[areg] in ${pathLabel} must be a TOML table.`,
42
- });
43
- const agents = areg.agents;
44
- if (agents === undefined) return { ok: true, value: [] };
45
- if (!Array.isArray(agents))
46
- return err({
47
- code: "ns_toml_invalid",
48
- message: `${pathLabel} [areg].agents must be a string array.`,
49
- });
50
- if (agents.length === 0) return { ok: true, value: [] };
51
- return validateNonEmptyStringList(agents, {
52
- code: "ns_toml_invalid",
53
- message: `${pathLabel} [areg].agents must be a non-empty string list.`,
54
- });
55
- }
56
-
57
- export function parseLegacyAregJsonAgents(text: string): Result<string[]> {
58
- let data: unknown;
59
- try {
60
- data = JSON.parse(text);
61
- } catch (error) {
62
- return err({
63
- code: "areg_json_invalid",
64
- message: `Invalid JSON in areg.json: ${formatErrorMessage(error)}`,
65
- });
66
- }
67
- if (!isRecord(data))
68
- return err({ code: "areg_json_invalid", message: "areg.json must contain a JSON object." });
69
- const agents = data.agents;
70
- if (!Array.isArray(agents) || agents.length === 0)
71
- return err({
72
- code: "areg_agents_invalid",
73
- message: "areg.json field `agents` must be a non-empty string list.",
74
- });
75
- return validateNonEmptyStringList(agents, {
76
- code: "areg_agents_invalid",
77
- message: "areg.json field `agents` must be a non-empty string list.",
78
- });
79
- }
80
-
81
- function validateNonEmptyStringList(
82
- agents: readonly unknown[],
83
- invalid: { code: string; message: string },
84
- ): Result<string[]> {
85
- const result: string[] = [];
86
- for (const agent of agents) {
87
- if (typeof agent !== "string" || agent.trim().length === 0) return err(invalid);
88
- result.push(agent);
89
- }
90
- return { ok: true, value: result };
91
- }
92
-
93
- function parseNsAregAgentsFromState(state: AregTextFileState): Result<string[]> {
94
- if (state.type === "missing") return { ok: true, value: [] };
95
- if (state.type !== "file")
96
- return rejectTextState({
97
- pathLabel: "ns.toml",
98
- state,
99
- description: "ns.toml",
100
- action: "manage it",
101
- });
102
- return parseNsAregAgents(state.text, "ns.toml");
103
- }
104
-
105
- function parseLegacyAregJsonAgentsFromState(state: AregTextFileState): Result<string[]> {
106
- if (state.type === "missing") return { ok: true, value: [] };
107
- if (state.type !== "file")
108
- return rejectTextState({
109
- pathLabel: "areg.json",
110
- state,
111
- description: "areg.json",
112
- action: "manage it",
113
- });
114
- return parseLegacyAregJsonAgents(state.text);
115
- }
@@ -1,126 +0,0 @@
1
- import type { AregErrorInfo, AregPathState } from "../gateways.ts";
2
-
3
- export function expectedAgentsSkillSymlinkTarget(skillName: string): string {
4
- return `../../skills/${skillName}`;
5
- }
6
-
7
- export function expectedClaudeSkillSymlinkTarget(skillName: string): string {
8
- return `../../.agents/skills/${skillName}`;
9
- }
10
-
11
- export function isAgentsSkillMirror(pathState: AregPathState, skillName: string): boolean {
12
- return (
13
- pathState.type === "symlink" && pathState.target === expectedAgentsSkillSymlinkTarget(skillName)
14
- );
15
- }
16
-
17
- export function isClaudeSkillMirror(pathState: AregPathState, skillName: string): boolean {
18
- return (
19
- pathState.type === "symlink" && pathState.target === expectedClaudeSkillSymlinkTarget(skillName)
20
- );
21
- }
22
-
23
- export function agentsSkillMirrorRelativePath(skillName: string): string {
24
- return `.agents/skills/${skillName}`;
25
- }
26
-
27
- export function claudeSkillMirrorRelativePath(skillName: string): string {
28
- return `.claude/skills/${skillName}`;
29
- }
30
-
31
- export type SkillMirrorKind = "agents" | "claude";
32
-
33
- export interface SkillMirrorRelativePathInfo {
34
- mirrorKind: SkillMirrorKind;
35
- skillName: string;
36
- expectedTarget: string;
37
- }
38
-
39
- /**
40
- * Returns parsed mirror information for an exact areg-managed skill mirror
41
- * location, or undefined when the path is not a valid mirror path.
42
- */
43
- export function parseSkillMirrorRelativePath(
44
- relativePath: string,
45
- ): SkillMirrorRelativePathInfo | undefined {
46
- const parts = relativePath.split("/");
47
- if (parts.length !== 3 || parts[1] !== "skills") return undefined;
48
- const skillName = parts[2];
49
- if (skillName === undefined || !isPlainSkillNameSegment(skillName)) return undefined;
50
- if (parts[0] === ".agents")
51
- return {
52
- mirrorKind: "agents",
53
- skillName,
54
- expectedTarget: expectedAgentsSkillSymlinkTarget(skillName),
55
- };
56
- if (parts[0] === ".claude")
57
- return {
58
- mirrorKind: "claude",
59
- skillName,
60
- expectedTarget: expectedClaudeSkillSymlinkTarget(skillName),
61
- };
62
- return undefined;
63
- }
64
-
65
- /**
66
- * Returns true when the relative path is exactly an areg-managed skill mirror
67
- * location: `.agents/skills/<name>` or `.claude/skills/<name>` with a plain
68
- * single-segment skill name.
69
- */
70
- export function isSkillMirrorRelativePath(relativePath: string): boolean {
71
- return expectedMirrorTarget(relativePath) !== undefined;
72
- }
73
-
74
- /**
75
- * Returns the convention symlink target for a skill mirror relative path, or
76
- * undefined when the path is not a valid skill mirror location.
77
- */
78
- export function expectedMirrorTarget(relativePath: string): string | undefined {
79
- return parseSkillMirrorRelativePath(relativePath)?.expectedTarget;
80
- }
81
-
82
- /**
83
- * Classifies a skill-mirror symlink deletion target against the delete contract,
84
- * returning the matching error or undefined when the target is a deletable
85
- * convention symlink. An undefined `state` is treated as missing so fixture-backed
86
- * callers share the same cascade as filesystem inspection.
87
- */
88
- export function classifySkillMirrorSymlinkState(
89
- relativePath: string,
90
- state: AregPathState | undefined,
91
- description: string,
92
- target: string,
93
- ): AregErrorInfo | undefined {
94
- const expectedTarget = expectedMirrorTarget(relativePath);
95
- if (expectedTarget === undefined)
96
- return {
97
- code: "skill-kind-delete-symlink-refused",
98
- message: `Refusing to delete ${description}: ${relativePath} is not a managed skill mirror path.`,
99
- };
100
- if (state === undefined || state.type === "missing")
101
- return {
102
- code: "skill-kind-delete-symlink-missing",
103
- message: `${description} at ${target} does not exist.`,
104
- };
105
- if (state.type !== "symlink")
106
- return {
107
- code: "skill-kind-delete-symlink-not-symlink",
108
- message: `${description} at ${target} is not a symlink; refusing to delete it.`,
109
- };
110
- if (state.target !== expectedTarget)
111
- return {
112
- code: "skill-kind-delete-symlink-wrong-target",
113
- message: `${description} at ${target} points to ${state.target}, expected ${expectedTarget}; refusing to delete it.`,
114
- };
115
- return undefined;
116
- }
117
-
118
- function isPlainSkillNameSegment(segment: string): boolean {
119
- return (
120
- segment.length > 0 &&
121
- segment !== "." &&
122
- segment !== ".." &&
123
- !segment.includes("/") &&
124
- !segment.includes("\\")
125
- );
126
- }
@@ -1,305 +0,0 @@
1
- import { failure, negative, ok, type ClinkrExit, ClinkrGroup } from "@nseng-ai/clinkr";
2
- import { optionalEntry } from "@nseng-ai/foundation/primitives";
3
- import { z } from "zod";
4
-
5
- import type { AregCliContext } from "../context.ts";
6
- import type { AregSkillxInstalledSkill } from "../gateways.ts";
7
- import { sortStrings } from "../sort.ts";
8
-
9
- const SKILLX_FORMAT_VALUES = ["url", "skill-flag", "plain", "repo-only"] as const;
10
- const REPO_PATTERN = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
11
-
12
- export const skillxParseRequestSchema = z.object({
13
- inputText: z.string().describe("GitHub URL, owner/repo, or owner/repo plus skill selector."),
14
- });
15
-
16
- export const skillxListRequestSchema = z.object({
17
- repo: z.string().describe("GitHub repository in owner/repo form."),
18
- });
19
-
20
- export const skillxFetchRequestSchema = z.object({
21
- repo: z.string().describe("GitHub repository in owner/repo form."),
22
- skill: z.string().optional().describe("Specific skill name to select after installation."),
23
- });
24
-
25
- export const skillxCleanupRequestSchema = z.object({
26
- dir: z.string().describe("Transient skillx workspace directory to remove."),
27
- });
28
-
29
- const skillxFormatSchema = z.enum(SKILLX_FORMAT_VALUES);
30
-
31
- const parseSuccessSchema = z.object({
32
- success: z.literal(true),
33
- repo: z.string(),
34
- skill: z.string().nullable(),
35
- format: skillxFormatSchema,
36
- });
37
-
38
- const parseFailureSchema = z.object({
39
- success: z.literal(false),
40
- error: z.string(),
41
- });
42
-
43
- export const skillxParseResultSchema = z.union([parseSuccessSchema, parseFailureSchema]);
44
-
45
- const listSuccessSchema = z.object({
46
- success: z.literal(true),
47
- repo: z.string(),
48
- skills: z.array(z.string()),
49
- });
50
-
51
- const listFailureSchema = z.object({
52
- success: z.literal(false),
53
- error: z.string(),
54
- hint: z.string().optional(),
55
- });
56
-
57
- export const skillxListResultSchema = z.union([listSuccessSchema, listFailureSchema]);
58
-
59
- const fetchSelectedSuccessSchema = z.object({
60
- success: z.literal(true),
61
- repo: z.string(),
62
- skill: z.string(),
63
- tmpDir: z.string(),
64
- skillDir: z.string(),
65
- skillMd: z.string(),
66
- files: z.array(z.string()),
67
- needsSelection: z.literal(false).optional(),
68
- });
69
-
70
- const fetchSelectionSuccessSchema = z.object({
71
- success: z.literal(true),
72
- repo: z.string(),
73
- skill: z.null(),
74
- tmpDir: z.string(),
75
- skillDir: z.null(),
76
- skillMd: z.null(),
77
- files: z.null(),
78
- needsSelection: z.literal(true),
79
- availableSkills: z.array(z.string()),
80
- });
81
-
82
- const fetchFailureSchema = z.object({
83
- success: z.literal(false),
84
- error: z.string(),
85
- tmpDir: z.null(),
86
- });
87
-
88
- export const skillxFetchResultSchema = z.union([
89
- fetchSelectedSuccessSchema,
90
- fetchSelectionSuccessSchema,
91
- fetchFailureSchema,
92
- ]);
93
-
94
- const cleanupSuccessSchema = z.object({
95
- success: z.literal(true),
96
- removed: z.string(),
97
- });
98
-
99
- const cleanupFailureSchema = z.object({
100
- success: z.literal(false),
101
- error: z.string(),
102
- });
103
-
104
- export const skillxCleanupResultSchema = z.union([cleanupSuccessSchema, cleanupFailureSchema]);
105
-
106
- export type SkillxParseRequest = z.infer<typeof skillxParseRequestSchema>;
107
- export type SkillxListRequest = z.infer<typeof skillxListRequestSchema>;
108
- export type SkillxFetchRequest = z.infer<typeof skillxFetchRequestSchema>;
109
- export type SkillxCleanupRequest = z.infer<typeof skillxCleanupRequestSchema>;
110
- export type SkillxParseResult = z.infer<typeof skillxParseResultSchema>;
111
- export type SkillxListResult = z.infer<typeof skillxListResultSchema>;
112
- export type SkillxFetchResult = z.infer<typeof skillxFetchResultSchema>;
113
- export type SkillxCleanupResult = z.infer<typeof skillxCleanupResultSchema>;
114
-
115
- export function buildSkillxGroup(): ClinkrGroup<AregCliContext> {
116
- const group = new ClinkrGroup<AregCliContext>({
117
- name: "skillx",
118
- description: "Skillx helper operations.",
119
- });
120
- group.command({
121
- name: "parse",
122
- description: "Parse a skill source selector.",
123
- schema: skillxParseRequestSchema,
124
- positionals: { inputText: { position: 0 } },
125
- resultSchema: skillxParseResultSchema,
126
- handler: runSkillxParse,
127
- });
128
- group.command({
129
- name: "list",
130
- description: "List skills in a GitHub repository skills/ directory.",
131
- schema: skillxListRequestSchema,
132
- resultSchema: skillxListResultSchema,
133
- handler: runSkillxList,
134
- });
135
- group.command({
136
- name: "fetch",
137
- description: "Fetch skills into a transient workspace for agent reading.",
138
- schema: skillxFetchRequestSchema,
139
- resultSchema: skillxFetchResultSchema,
140
- handler: runSkillxFetch,
141
- });
142
- group.command({
143
- name: "cleanup",
144
- description: "Remove a transient skillx workspace.",
145
- schema: skillxCleanupRequestSchema,
146
- resultSchema: skillxCleanupResultSchema,
147
- handler: runSkillxCleanup,
148
- });
149
- return group;
150
- }
151
-
152
- export function parseSkillInput(raw: string): SkillxParseResult {
153
- const input = raw.trim();
154
- if (input.length === 0) return parseError("Empty input");
155
- const urlResult = parseGithubUrl(input);
156
- if (urlResult !== undefined) return urlResult;
157
- const parts = input.split(/\s+/u);
158
- const [repo, second, third, ...rest] = parts;
159
- if (repo === undefined || !isRepo(repo))
160
- return parseError(`Could not extract owner/repo from input: ${JSON.stringify(input)}`);
161
- if ((second === "--skill" || second === "-s") && third !== undefined && rest.length === 0) {
162
- return { success: true, repo, skill: third, format: "skill-flag" };
163
- }
164
- if (second === undefined) return { success: true, repo, skill: null, format: "repo-only" };
165
- if (third === undefined && isSkillName(second))
166
- return { success: true, repo, skill: second, format: "plain" };
167
- return parseError(`Could not extract owner/repo from input: ${JSON.stringify(input)}`);
168
- }
169
-
170
- export async function runSkillxParse(
171
- _ctx: AregCliContext,
172
- request: SkillxParseRequest,
173
- ): Promise<ClinkrExit<SkillxParseResult>> {
174
- const result = parseSkillInput(request.inputText);
175
- if (result.success) return ok(result);
176
- return negative(result.error, { data: result });
177
- }
178
-
179
- export async function runSkillxList(
180
- ctx: AregCliContext,
181
- request: SkillxListRequest,
182
- ): Promise<ClinkrExit<SkillxListResult>> {
183
- const tool = await ctx.host.checkTool({ tool: "gh", cwd: ctx.cwd, env: ctx.env });
184
- if (tool.type === "missing") return failure("missing-tool", tool.message);
185
- const result = await ctx.github.listSkillDirectoryNames({ repo: request.repo, env: ctx.env });
186
- if (result.type === "ok") {
187
- return ok({ success: true, repo: request.repo, skills: sortStrings(result.skillNames) });
188
- }
189
- if (result.type === "missing") {
190
- const error = `No skills directory found in ${request.repo}`;
191
- return negative(error, {
192
- data: {
193
- success: false,
194
- error,
195
- hint: "Check that the repo exists and has a skills/ directory",
196
- },
197
- });
198
- }
199
- if (result.type === "auth-error") {
200
- return failure("github-auth-failed", `Authentication error accessing ${request.repo}`);
201
- }
202
- return failure("github-gateway-failed", result.error.message);
203
- }
204
-
205
- export async function runSkillxFetch(
206
- ctx: AregCliContext,
207
- request: SkillxFetchRequest,
208
- ): Promise<ClinkrExit<SkillxFetchResult>> {
209
- const tool = await ctx.host.checkTool({ tool: "npx", cwd: ctx.cwd, env: ctx.env });
210
- if (tool.type === "missing") return failure("missing-tool", tool.message);
211
- const install = await ctx.skillxWorkspace.installIntoWorkspace({
212
- sourceRepo: request.repo,
213
- ...optionalEntry("skillName", request.skill),
214
- cwd: ctx.cwd,
215
- env: ctx.env,
216
- });
217
- if (install.type === "error") return failure("skill-install-failed", install.error.message);
218
- const workspaceRoot = install.workspace.workspaceRoot;
219
- const installedSkills = sortedInstalledSkills(install.workspace.installedSkills);
220
- if (installedSkills.length === 0) return fetchNegative("No skills were installed");
221
- if (request.skill === undefined && installedSkills.length > 1) {
222
- return ok({
223
- success: true,
224
- repo: request.repo,
225
- skill: null,
226
- tmpDir: workspaceRoot,
227
- skillDir: null,
228
- skillMd: null,
229
- files: null,
230
- needsSelection: true,
231
- availableSkills: installedSkills.map((skill) => skill.name),
232
- });
233
- }
234
- const selected =
235
- request.skill === undefined
236
- ? installedSkills[0]
237
- : installedSkills.find((skill) => skill.name === request.skill);
238
- if (selected === undefined) {
239
- await ctx.skillxWorkspace.cleanupWorkspace({ workspaceRoot });
240
- const base = `Skill '${request.skill}' was not found in installed skills`;
241
- return fetchNegative(base);
242
- }
243
- return ok({
244
- success: true,
245
- repo: request.repo,
246
- skill: selected.name,
247
- tmpDir: workspaceRoot,
248
- skillDir: selected.directory,
249
- skillMd: selected.skillFile,
250
- files: sortStrings(selected.relativeFiles),
251
- needsSelection: false,
252
- });
253
- }
254
-
255
- export async function runSkillxCleanup(
256
- ctx: AregCliContext,
257
- request: SkillxCleanupRequest,
258
- ): Promise<ClinkrExit<SkillxCleanupResult>> {
259
- const cleanup = await ctx.skillxWorkspace.cleanupWorkspace({ workspaceRoot: request.dir });
260
- if (cleanup.ok) return ok({ success: true, removed: request.dir });
261
- return failure("cleanup-failed", cleanup.error.message);
262
- }
263
-
264
- function parseGithubUrl(input: string): z.infer<typeof parseSuccessSchema> | undefined {
265
- let url: URL;
266
- try {
267
- url = new URL(input);
268
- } catch {
269
- return undefined;
270
- }
271
- if (url.protocol !== "https:" && url.protocol !== "http:") return undefined;
272
- if (url.hostname !== "github.com" && url.hostname !== "www.github.com") return undefined;
273
- const segments = url.pathname.split("/").filter((part) => part.length > 0);
274
- const owner = segments[0];
275
- const repoName = segments[1];
276
- if (owner === undefined || repoName === undefined || !isRepo(`${owner}/${repoName}`))
277
- return undefined;
278
- const skillsIndex = segments.indexOf("skills");
279
- const skill = skillsIndex === -1 ? null : (segments[skillsIndex + 1] ?? null);
280
- return { success: true, repo: `${owner}/${repoName}`, skill, format: "url" };
281
- }
282
-
283
- function isRepo(value: string): boolean {
284
- return REPO_PATTERN.test(value);
285
- }
286
-
287
- function isSkillName(value: string): boolean {
288
- return value.length > 0 && !value.includes("/") && !value.includes("@") && !value.startsWith("-");
289
- }
290
-
291
- function parseError(error: string): SkillxParseResult {
292
- return { success: false, error };
293
- }
294
-
295
- function fetchNegative(error: string): ClinkrExit<SkillxFetchResult> {
296
- return negative(error, { data: { success: false, error, tmpDir: null } });
297
- }
298
-
299
- function sortedInstalledSkills(
300
- skills: readonly AregSkillxInstalledSkill[],
301
- ): AregSkillxInstalledSkill[] {
302
- return skills
303
- .map((skill) => ({ ...skill, relativeFiles: sortStrings(skill.relativeFiles) }))
304
- .sort((left, right) => left.name.localeCompare(right.name));
305
- }
@@ -1,53 +0,0 @@
1
- export function renderAregSection(agents: readonly string[]): string {
2
- return `[areg]\nagents = ${JSON.stringify([...agents])}\n`;
3
- }
4
-
5
- export function replaceOrAppendAregSection(content: string, agents: readonly string[]): string {
6
- const lines = content.split(/(?<=\n)/u);
7
- if (lines.length === 1 && lines[0] === "") lines.pop();
8
- const start = aregSectionStart(lines);
9
- if (start === undefined) return appendTomlSection(content, renderAregSection(agents));
10
- const end = tomlSectionEnd(lines, start);
11
- let replacement = renderAregSection(agents);
12
- if (end < lines.length) replacement += "\n";
13
- lines.splice(
14
- start,
15
- end - start,
16
- ...(replacement.match(/.*(?:\n|$)/gu)?.filter((line) => line.length > 0) ?? []),
17
- );
18
- return lines.join("");
19
- }
20
-
21
- function appendTomlSection(content: string, section: string): string {
22
- if (content.length === 0) return section;
23
- if (content.endsWith("\n\n")) return `${content}${section}`;
24
- if (content.endsWith("\n")) return `${content}\n${section}`;
25
- return `${content}\n\n${section}`;
26
- }
27
-
28
- function aregSectionStart(lines: readonly string[]): number | undefined {
29
- for (let index = 0; index < lines.length; index += 1) {
30
- if (tomlTableName(lines[index] ?? "") === "areg") return index;
31
- }
32
- return undefined;
33
- }
34
-
35
- function tomlSectionEnd(lines: readonly string[], start: number): number {
36
- for (let index = start + 1; index < lines.length; index += 1) {
37
- if (tomlTableName(lines[index] ?? "") !== null) return index;
38
- }
39
- return lines.length;
40
- }
41
-
42
- function tomlTableName(line: string): string | null {
43
- const stripped = line.trim();
44
- if (stripped.startsWith("[[")) {
45
- const closingIndex = stripped.indexOf("]]", 2);
46
- if (closingIndex < 0) return null;
47
- return stripped.slice(2, closingIndex).trim();
48
- }
49
- if (!stripped.startsWith("[")) return null;
50
- const closingIndex = stripped.indexOf("]");
51
- if (closingIndex < 0) return null;
52
- return stripped.slice(1, closingIndex).trim();
53
- }