@nseng-ai/areg 0.1.1
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/package.json +29 -0
- package/src/cli.ts +109 -0
- package/src/context.ts +59 -0
- package/src/fake-gateways.ts +1009 -0
- package/src/gateways/command-constants.ts +1 -0
- package/src/gateways/errors.ts +5 -0
- package/src/gateways/fs-utils.ts +26 -0
- package/src/gateways/github-gateway.ts +111 -0
- package/src/gateways/host-gateway.ts +35 -0
- package/src/gateways/mutation-policy.ts +94 -0
- package/src/gateways/npx-skills-gateway.ts +53 -0
- package/src/gateways/project-fs.ts +320 -0
- package/src/gateways/project-gateway.ts +801 -0
- package/src/gateways/prompt-gateway.ts +21 -0
- package/src/gateways/skill-kind-classification.ts +39 -0
- package/src/gateways/skillx-workspace-gateway.ts +220 -0
- package/src/gateways.ts +361 -0
- package/src/index.ts +48 -0
- package/src/operations/check.ts +559 -0
- package/src/operations/doctor-skills-report.ts +109 -0
- package/src/operations/doctor-skills-severity.ts +9 -0
- package/src/operations/doctor-skills.ts +471 -0
- package/src/operations/file-state.ts +77 -0
- package/src/operations/frontmatter.ts +151 -0
- package/src/operations/init.ts +548 -0
- package/src/operations/lockfile.ts +107 -0
- package/src/operations/managed-markdown-block.ts +47 -0
- package/src/operations/pi-replacement.ts +33 -0
- package/src/operations/pi-settings.ts +60 -0
- package/src/operations/project-agents.ts +115 -0
- package/src/operations/project-inspection.ts +165 -0
- package/src/operations/project-mutations.ts +383 -0
- package/src/operations/project-resolution.ts +53 -0
- package/src/operations/skill-find.ts +295 -0
- package/src/operations/skill-kind-apply-plan.ts +531 -0
- package/src/operations/skill-kind-frontmatter.ts +55 -0
- package/src/operations/skill-kind-inference.ts +391 -0
- package/src/operations/skill-kind.ts +525 -0
- package/src/operations/skill-mirror-conventions.ts +126 -0
- package/src/operations/skillx.ts +305 -0
- package/src/operations/toml-section.ts +53 -0
- package/src/operations/update-skills.ts +325 -0
- package/src/real-gateways.ts +6 -0
- package/src/skill-lookup.ts +254 -0
- package/src/sort.ts +7 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import readline from "node:readline/promises";
|
|
2
|
+
|
|
3
|
+
import type { AregPromptGateway } from "../gateways.ts";
|
|
4
|
+
|
|
5
|
+
export class RealAregPromptGateway implements AregPromptGateway {
|
|
6
|
+
async confirm(request: { message: string; defaultValue: boolean }): Promise<boolean> {
|
|
7
|
+
const suffix = request.defaultValue ? " [Y/n] " : " [y/N] ";
|
|
8
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
9
|
+
try {
|
|
10
|
+
while (true) {
|
|
11
|
+
const answer = (await rl.question(`${request.message}${suffix}`)).trim().toLowerCase();
|
|
12
|
+
if (answer.length === 0) return request.defaultValue;
|
|
13
|
+
if (answer === "y" || answer === "yes") return true;
|
|
14
|
+
if (answer === "n" || answer === "no") return false;
|
|
15
|
+
process.stdout.write("Please answer yes or no.\n");
|
|
16
|
+
}
|
|
17
|
+
} finally {
|
|
18
|
+
rl.close();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AregSkillKindResolveResult, AregSkillKindSkillInspection } from "../gateways.ts";
|
|
2
|
+
import { errorInfo } from "./errors.ts";
|
|
3
|
+
|
|
4
|
+
export function classifyResolvedSkillKindInspection(options: {
|
|
5
|
+
spec: string;
|
|
6
|
+
skillName: string;
|
|
7
|
+
inspection: AregSkillKindSkillInspection;
|
|
8
|
+
}): AregSkillKindResolveResult {
|
|
9
|
+
if (options.inspection.skillDir.type === "symlink")
|
|
10
|
+
return {
|
|
11
|
+
type: "error",
|
|
12
|
+
error: errorInfo(
|
|
13
|
+
"skill-kind-symlink-skill-dir",
|
|
14
|
+
`${options.inspection.baseRelativePath} is a symlink; refusing to manage invocation metadata`,
|
|
15
|
+
),
|
|
16
|
+
};
|
|
17
|
+
if (options.inspection.skillDir.type !== "directory")
|
|
18
|
+
return {
|
|
19
|
+
type: "error",
|
|
20
|
+
error: errorInfo("skill-kind-missing-skill", `Managed skill not found: ${options.spec}`),
|
|
21
|
+
};
|
|
22
|
+
if (options.inspection.skillMd.type === "symlink")
|
|
23
|
+
return {
|
|
24
|
+
type: "error",
|
|
25
|
+
error: errorInfo(
|
|
26
|
+
"skill-kind-symlink-skill-md",
|
|
27
|
+
`${options.inspection.baseRelativePath}/SKILL.md is a symlink; refusing to manage invocation metadata`,
|
|
28
|
+
),
|
|
29
|
+
};
|
|
30
|
+
if (options.inspection.skillMd.type !== "file")
|
|
31
|
+
return {
|
|
32
|
+
type: "error",
|
|
33
|
+
error: errorInfo(
|
|
34
|
+
"skill-kind-missing-skill-md",
|
|
35
|
+
`${options.inspection.baseRelativePath}/SKILL.md does not exist`,
|
|
36
|
+
),
|
|
37
|
+
};
|
|
38
|
+
return { type: "ok", skillName: options.skillName };
|
|
39
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { lstat, mkdtemp, readdir, realpath, rm } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { formatErrorMessage } from "@nseng-ai/foundation/primitives";
|
|
6
|
+
import { resultErr, resultOk } from "@nseng-ai/foundation/result";
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
AregErrorInfo,
|
|
10
|
+
AregNpxSkillsGateway,
|
|
11
|
+
AregOperationResult,
|
|
12
|
+
AregSkillxInstallRequest,
|
|
13
|
+
AregSkillxInstallResult,
|
|
14
|
+
AregSkillxInstalledSkill,
|
|
15
|
+
AregSkillxWorkspaceGateway,
|
|
16
|
+
} from "../gateways.ts";
|
|
17
|
+
import { sortStrings } from "../sort.ts";
|
|
18
|
+
import { errorInfo } from "./errors.ts";
|
|
19
|
+
import { inspectPath, isNodeErrorCode, isPathAtOrBelow } from "./fs-utils.ts";
|
|
20
|
+
|
|
21
|
+
export class RealAregSkillxWorkspaceGateway implements AregSkillxWorkspaceGateway {
|
|
22
|
+
private readonly npxSkills: AregNpxSkillsGateway;
|
|
23
|
+
|
|
24
|
+
constructor(options: { npxSkills: AregNpxSkillsGateway }) {
|
|
25
|
+
this.npxSkills = options.npxSkills;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async installIntoWorkspace(request: AregSkillxInstallRequest): Promise<AregSkillxInstallResult> {
|
|
29
|
+
const workspaceRoot = await mkdtemp(path.join(os.tmpdir(), "skillx."));
|
|
30
|
+
const install = await this.npxSkills.addSkills({
|
|
31
|
+
sourceRepo: request.sourceRepo,
|
|
32
|
+
skillNames: request.skillName === undefined ? [] : [request.skillName],
|
|
33
|
+
targetAgents: ["codex"],
|
|
34
|
+
cwd: workspaceRoot,
|
|
35
|
+
env: request.env,
|
|
36
|
+
});
|
|
37
|
+
if (install.type === "error") {
|
|
38
|
+
await removeWorkspaceQuietly(workspaceRoot);
|
|
39
|
+
return {
|
|
40
|
+
type: "error",
|
|
41
|
+
error: errorInfo(
|
|
42
|
+
"skillx-install-failed",
|
|
43
|
+
`npx skills add failed: ${install.error.message}`,
|
|
44
|
+
install.error.displayCommand,
|
|
45
|
+
),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const inspected = await inspectInstalledSkills(workspaceRoot, request.skillName);
|
|
49
|
+
if (inspected.type === "error") {
|
|
50
|
+
await removeWorkspaceQuietly(workspaceRoot);
|
|
51
|
+
return { type: "error", error: inspected.error };
|
|
52
|
+
}
|
|
53
|
+
return { type: "ok", workspace: { workspaceRoot, installedSkills: inspected.installedSkills } };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async cleanupWorkspace(request: { workspaceRoot: string }): Promise<AregOperationResult> {
|
|
57
|
+
return await cleanupSkillxWorkspace(request.workspaceRoot);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function inspectInstalledSkills(
|
|
62
|
+
workspaceRoot: string,
|
|
63
|
+
requestedSkillName: string | undefined,
|
|
64
|
+
): Promise<
|
|
65
|
+
| { type: "ok"; installedSkills: AregSkillxInstalledSkill[] }
|
|
66
|
+
| { type: "error"; error: AregErrorInfo }
|
|
67
|
+
> {
|
|
68
|
+
const skillsRoot = path.join(workspaceRoot, ".agents", "skills");
|
|
69
|
+
const skillsRootState = await inspectPath(skillsRoot);
|
|
70
|
+
if (skillsRootState.type !== "directory")
|
|
71
|
+
return { type: "error", error: errorInfo("skillx-no-skills", "No skills were installed") };
|
|
72
|
+
if (requestedSkillName !== undefined) {
|
|
73
|
+
const inspected = await inspectOneSkill(skillsRoot, requestedSkillName);
|
|
74
|
+
if (inspected.type === "error") return inspected;
|
|
75
|
+
return { type: "ok", installedSkills: [inspected.skill] };
|
|
76
|
+
}
|
|
77
|
+
const entries = await readdir(skillsRoot, { withFileTypes: true });
|
|
78
|
+
const skillNames = sortStrings(
|
|
79
|
+
entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name),
|
|
80
|
+
);
|
|
81
|
+
if (skillNames.length === 0)
|
|
82
|
+
return { type: "error", error: errorInfo("skillx-no-skills", "No skills were installed") };
|
|
83
|
+
const installedSkills: AregSkillxInstalledSkill[] = [];
|
|
84
|
+
for (const skillName of skillNames) {
|
|
85
|
+
const inspected = await inspectOneSkill(skillsRoot, skillName);
|
|
86
|
+
if (inspected.type === "error") return inspected;
|
|
87
|
+
installedSkills.push(inspected.skill);
|
|
88
|
+
}
|
|
89
|
+
return { type: "ok", installedSkills };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function inspectOneSkill(
|
|
93
|
+
skillsRoot: string,
|
|
94
|
+
skillName: string,
|
|
95
|
+
): Promise<
|
|
96
|
+
{ type: "ok"; skill: AregSkillxInstalledSkill } | { type: "error"; error: AregErrorInfo }
|
|
97
|
+
> {
|
|
98
|
+
const directory = path.join(skillsRoot, skillName);
|
|
99
|
+
const directoryKind = await inspectPath(directory);
|
|
100
|
+
if (directoryKind.type !== "directory")
|
|
101
|
+
return {
|
|
102
|
+
type: "error",
|
|
103
|
+
error: errorInfo(
|
|
104
|
+
"skillx-skill-missing",
|
|
105
|
+
`Skill '${skillName}' was not found in installed skills`,
|
|
106
|
+
),
|
|
107
|
+
};
|
|
108
|
+
const skillFile = path.join(directory, "SKILL.md");
|
|
109
|
+
const fileKind = await inspectPath(skillFile);
|
|
110
|
+
if (fileKind.type !== "file")
|
|
111
|
+
return {
|
|
112
|
+
type: "error",
|
|
113
|
+
error: errorInfo(
|
|
114
|
+
"skillx-skill-malformed",
|
|
115
|
+
`Installed skill '${skillName}' is missing SKILL.md`,
|
|
116
|
+
),
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
type: "ok",
|
|
120
|
+
skill: {
|
|
121
|
+
name: skillName,
|
|
122
|
+
directory,
|
|
123
|
+
skillFile,
|
|
124
|
+
relativeFiles: await listRelativeFiles(directory),
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function listRelativeFiles(root: string): Promise<string[]> {
|
|
130
|
+
const files: string[] = [];
|
|
131
|
+
async function visit(directory: string, prefix: string): Promise<void> {
|
|
132
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
133
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
const relativePath = prefix.length === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
136
|
+
const fullPath = path.join(directory, entry.name);
|
|
137
|
+
if (entry.isDirectory()) {
|
|
138
|
+
await visit(fullPath, relativePath);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (entry.isFile()) files.push(relativePath);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
await visit(root, "");
|
|
145
|
+
return sortStrings(files);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function cleanupSkillxWorkspace(workspaceRoot: string): Promise<AregOperationResult> {
|
|
149
|
+
if (!path.basename(workspaceRoot).startsWith("skillx.")) {
|
|
150
|
+
return resultErr(
|
|
151
|
+
errorInfo(
|
|
152
|
+
"skillx-cleanup-refused",
|
|
153
|
+
`Refusing to remove non-skillx workspace: ${workspaceRoot}`,
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
let info;
|
|
158
|
+
try {
|
|
159
|
+
info = await lstat(workspaceRoot);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (isNodeErrorCode(error, "ENOENT"))
|
|
162
|
+
return resultErr(
|
|
163
|
+
errorInfo("skillx-cleanup-missing", `Workspace does not exist: ${workspaceRoot}`),
|
|
164
|
+
);
|
|
165
|
+
return resultErr(
|
|
166
|
+
errorInfo(
|
|
167
|
+
"skillx-cleanup-stat-failed",
|
|
168
|
+
`Could not inspect workspace: ${formatErrorMessage(error)}`,
|
|
169
|
+
),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
if (info.isSymbolicLink())
|
|
173
|
+
return resultErr(
|
|
174
|
+
errorInfo("skillx-cleanup-symlink", `Refusing to remove symlink workspace: ${workspaceRoot}`),
|
|
175
|
+
);
|
|
176
|
+
if (!info.isDirectory())
|
|
177
|
+
return resultErr(
|
|
178
|
+
errorInfo("skillx-cleanup-not-directory", `Workspace is not a directory: ${workspaceRoot}`),
|
|
179
|
+
);
|
|
180
|
+
let resolvedWorkspace: string;
|
|
181
|
+
let resolvedTemp: string;
|
|
182
|
+
try {
|
|
183
|
+
resolvedWorkspace = await realpath(workspaceRoot);
|
|
184
|
+
resolvedTemp = await realpath(os.tmpdir());
|
|
185
|
+
} catch (error) {
|
|
186
|
+
return resultErr(
|
|
187
|
+
errorInfo(
|
|
188
|
+
"skillx-cleanup-realpath-failed",
|
|
189
|
+
`Could not resolve workspace path: ${formatErrorMessage(error)}`,
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (!isPathAtOrBelow(resolvedWorkspace, resolvedTemp)) {
|
|
194
|
+
return resultErr(
|
|
195
|
+
errorInfo(
|
|
196
|
+
"skillx-cleanup-outside-temp",
|
|
197
|
+
`Refusing to remove workspace outside temp directory: ${workspaceRoot}`,
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
await rm(resolvedWorkspace, { recursive: true });
|
|
203
|
+
return resultOk(undefined);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
return resultErr(
|
|
206
|
+
errorInfo(
|
|
207
|
+
"skillx-cleanup-remove-failed",
|
|
208
|
+
`Could not remove workspace: ${formatErrorMessage(error)}`,
|
|
209
|
+
),
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function removeWorkspaceQuietly(workspaceRoot: string): Promise<void> {
|
|
215
|
+
try {
|
|
216
|
+
await rm(workspaceRoot, { recursive: true, force: true });
|
|
217
|
+
} catch {
|
|
218
|
+
// Best-effort cleanup of a directory this gateway just created; the command result carries the original failure.
|
|
219
|
+
}
|
|
220
|
+
}
|
package/src/gateways.ts
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SKILL_LOOKUP_ROOT_DESCRIPTORS,
|
|
3
|
+
skillLookupDescriptorForSourceType,
|
|
4
|
+
} from "./skill-lookup.ts";
|
|
5
|
+
import type {
|
|
6
|
+
SkillLookupRoot,
|
|
7
|
+
SkillLookupRootDescriptor,
|
|
8
|
+
SkillLookupSourceType,
|
|
9
|
+
} from "./skill-lookup.ts";
|
|
10
|
+
import type { ErrorInfo, Result } from "@nseng-ai/foundation/result";
|
|
11
|
+
|
|
12
|
+
export const AREG_HOST_TOOL_NAMES = ["gh", "npx"] as const;
|
|
13
|
+
export type AregHostToolName = (typeof AREG_HOST_TOOL_NAMES)[number];
|
|
14
|
+
|
|
15
|
+
export interface AregErrorInfo extends ErrorInfo {
|
|
16
|
+
displayCommand?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type AregOperationResult = Result<undefined, AregErrorInfo>;
|
|
20
|
+
|
|
21
|
+
export type AregToolCheckResult =
|
|
22
|
+
| { type: "found"; tool: AregHostToolName; path: string }
|
|
23
|
+
| { type: "missing"; tool: AregHostToolName; message: string };
|
|
24
|
+
|
|
25
|
+
export interface AregHostGateway {
|
|
26
|
+
checkTool(options: {
|
|
27
|
+
tool: AregHostToolName;
|
|
28
|
+
cwd: string;
|
|
29
|
+
env: NodeJS.ProcessEnv;
|
|
30
|
+
}): Promise<AregToolCheckResult>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type AregGithubSkillListResult =
|
|
34
|
+
| { type: "ok"; skillNames: readonly string[] }
|
|
35
|
+
| { type: "missing"; message: string }
|
|
36
|
+
| { type: "auth-error"; message: string }
|
|
37
|
+
| { type: "error"; error: AregErrorInfo };
|
|
38
|
+
|
|
39
|
+
export type AregGithubSkillFileResult =
|
|
40
|
+
| { type: "found" }
|
|
41
|
+
| { type: "missing"; message: string }
|
|
42
|
+
| { type: "auth-error"; message: string }
|
|
43
|
+
| { type: "error"; error: AregErrorInfo };
|
|
44
|
+
|
|
45
|
+
export interface AregGithubGateway {
|
|
46
|
+
listSkillDirectoryNames(options: {
|
|
47
|
+
repo: string;
|
|
48
|
+
ref?: string;
|
|
49
|
+
env: NodeJS.ProcessEnv;
|
|
50
|
+
}): Promise<AregGithubSkillListResult>;
|
|
51
|
+
checkSkillFile(options: {
|
|
52
|
+
repo: string;
|
|
53
|
+
path: string;
|
|
54
|
+
ref?: string;
|
|
55
|
+
env: NodeJS.ProcessEnv;
|
|
56
|
+
}): Promise<AregGithubSkillFileResult>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface AregNpxSkillsAddRequest {
|
|
60
|
+
sourceRepo: string;
|
|
61
|
+
/** Empty means install all skills from the source repository. */
|
|
62
|
+
skillNames: readonly string[];
|
|
63
|
+
targetAgents: readonly string[];
|
|
64
|
+
cwd: string;
|
|
65
|
+
env: NodeJS.ProcessEnv;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type AregNpxSkillsAddResult = { type: "ok" } | { type: "error"; error: AregErrorInfo };
|
|
69
|
+
|
|
70
|
+
export interface AregNpxSkillsGateway {
|
|
71
|
+
addSkills(request: AregNpxSkillsAddRequest): Promise<AregNpxSkillsAddResult>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface AregPromptGateway {
|
|
75
|
+
confirm(request: { message: string; defaultValue: boolean }): Promise<boolean>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface AregSkillxInstalledSkill {
|
|
79
|
+
name: string;
|
|
80
|
+
directory: string;
|
|
81
|
+
skillFile: string;
|
|
82
|
+
relativeFiles: readonly string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface AregSkillxWorkspaceInstall {
|
|
86
|
+
workspaceRoot: string;
|
|
87
|
+
installedSkills: readonly AregSkillxInstalledSkill[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface AregSkillxInstallRequest {
|
|
91
|
+
sourceRepo: string;
|
|
92
|
+
skillName?: string;
|
|
93
|
+
cwd: string;
|
|
94
|
+
env: NodeJS.ProcessEnv;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface AregSkillxWorkspaceCleanupRequest {
|
|
98
|
+
workspaceRoot: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type AregSkillxInstallResult =
|
|
102
|
+
| { type: "ok"; workspace: AregSkillxWorkspaceInstall }
|
|
103
|
+
| { type: "error"; error: AregErrorInfo };
|
|
104
|
+
|
|
105
|
+
export interface AregSkillxWorkspaceGateway {
|
|
106
|
+
installIntoWorkspace(request: AregSkillxInstallRequest): Promise<AregSkillxInstallResult>;
|
|
107
|
+
cleanupWorkspace(request: AregSkillxWorkspaceCleanupRequest): Promise<AregOperationResult>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type AregPathState =
|
|
111
|
+
| { type: "missing" }
|
|
112
|
+
| { type: "file" }
|
|
113
|
+
| { type: "directory" }
|
|
114
|
+
| { type: "symlink"; target: string }
|
|
115
|
+
| { type: "other" };
|
|
116
|
+
|
|
117
|
+
export type AregTextFileState =
|
|
118
|
+
| { type: "missing" }
|
|
119
|
+
| { type: "file"; text: string }
|
|
120
|
+
| { type: "directory" }
|
|
121
|
+
| { type: "symlink"; target: string }
|
|
122
|
+
| { type: "other" }
|
|
123
|
+
| { type: "unreadable"; message: string };
|
|
124
|
+
|
|
125
|
+
export interface AregCheckSkillInspection {
|
|
126
|
+
name: string;
|
|
127
|
+
skillsPath: AregPathState;
|
|
128
|
+
agentsPath: AregPathState;
|
|
129
|
+
claudePath: AregPathState;
|
|
130
|
+
localSkillMd: AregTextFileState;
|
|
131
|
+
remoteSkillMd: AregTextFileState;
|
|
132
|
+
openaiPolicy: AregTextFileState;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function missingCheckSkillInspection(name: string): AregCheckSkillInspection {
|
|
136
|
+
const missing = { type: "missing" as const };
|
|
137
|
+
return {
|
|
138
|
+
name,
|
|
139
|
+
skillsPath: missing,
|
|
140
|
+
agentsPath: missing,
|
|
141
|
+
claudePath: missing,
|
|
142
|
+
localSkillMd: missing,
|
|
143
|
+
remoteSkillMd: missing,
|
|
144
|
+
openaiPolicy: missing,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface AregCheckPairingDirectory {
|
|
149
|
+
relativeDir: string;
|
|
150
|
+
hasAgents: boolean;
|
|
151
|
+
hasClaude: boolean;
|
|
152
|
+
claudeText?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type AregSkillKindSourceType = Extract<SkillLookupSourceType, "repo" | "vendored">;
|
|
156
|
+
export type AregSkillKindRootDescriptor = Extract<
|
|
157
|
+
SkillLookupRootDescriptor,
|
|
158
|
+
{ sourceType: AregSkillKindSourceType }
|
|
159
|
+
>;
|
|
160
|
+
|
|
161
|
+
export const AREG_SKILL_KIND_ROOT_DESCRIPTORS = SKILL_LOOKUP_ROOT_DESCRIPTORS.filter(
|
|
162
|
+
(descriptor): descriptor is AregSkillKindRootDescriptor =>
|
|
163
|
+
descriptor.sourceType === "repo" || descriptor.sourceType === "vendored",
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
export function skillKindDescriptorForSourceType(
|
|
167
|
+
sourceType: AregSkillKindSourceType,
|
|
168
|
+
): AregSkillKindRootDescriptor {
|
|
169
|
+
const descriptor = skillLookupDescriptorForSourceType(sourceType);
|
|
170
|
+
if (descriptor.sourceType !== "repo" && descriptor.sourceType !== "vendored") {
|
|
171
|
+
throw new Error(`Unknown skill-kind source type: ${sourceType}`);
|
|
172
|
+
}
|
|
173
|
+
return descriptor;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface AregSkillFindSkillInspection {
|
|
177
|
+
name: string;
|
|
178
|
+
root: SkillLookupRoot;
|
|
179
|
+
sourceType: SkillLookupSourceType;
|
|
180
|
+
baseRelativePath: string;
|
|
181
|
+
skillDir: AregPathState;
|
|
182
|
+
skillMd: AregTextFileState;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface AregSkillFindRootsInspection {
|
|
186
|
+
skills: readonly AregSkillFindSkillInspection[];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface AregSkillKindSkillInspection {
|
|
190
|
+
name: string;
|
|
191
|
+
sourceType: AregSkillKindSourceType;
|
|
192
|
+
baseRelativePath: string;
|
|
193
|
+
skillDir: AregPathState;
|
|
194
|
+
skillMd: AregTextFileState;
|
|
195
|
+
openaiPolicy: AregTextFileState;
|
|
196
|
+
agentsPath: AregPathState;
|
|
197
|
+
claudePath: AregPathState;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface AregReplacementInspection {
|
|
201
|
+
verifiedSurfaces: readonly string[];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface AregProjectInspectionRequest {
|
|
205
|
+
cwd: string;
|
|
206
|
+
projectPath: string;
|
|
207
|
+
env: NodeJS.ProcessEnv;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface AregProjectDirRequest {
|
|
211
|
+
projectDir: string;
|
|
212
|
+
env: NodeJS.ProcessEnv;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface AregSkillInspectionRequest {
|
|
216
|
+
projectDir: string;
|
|
217
|
+
skillName: string;
|
|
218
|
+
env: NodeJS.ProcessEnv;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface AregProjectBaseInspection {
|
|
222
|
+
projectDir: string;
|
|
223
|
+
projectPathState: AregPathState;
|
|
224
|
+
lockfile: AregTextFileState;
|
|
225
|
+
nsToml: AregTextFileState;
|
|
226
|
+
aregJson: AregTextFileState;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface AregInstructionFilesInspection {
|
|
230
|
+
agentsMd: AregTextFileState;
|
|
231
|
+
claudeMd: AregTextFileState;
|
|
232
|
+
claudeDir: AregPathState;
|
|
233
|
+
claudeSettings: AregTextFileState;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export interface AregPiArtifactsInspection {
|
|
237
|
+
piDir: AregPathState;
|
|
238
|
+
piSettings: AregTextFileState;
|
|
239
|
+
replacement: AregReplacementInspection;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface AregPiSkillInventoryInspection {
|
|
243
|
+
skillNames: readonly string[];
|
|
244
|
+
isApproximation: boolean;
|
|
245
|
+
source: string;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export interface AregSkillNameInventory {
|
|
249
|
+
skillsDirectoryNames: readonly string[];
|
|
250
|
+
agentsSkillNames: readonly string[];
|
|
251
|
+
claudeSkillNames: readonly string[];
|
|
252
|
+
skillKindNames: readonly string[];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface AregSkillKindResolveRequest {
|
|
256
|
+
projectDir: string;
|
|
257
|
+
spec: string;
|
|
258
|
+
cwd: string;
|
|
259
|
+
env: NodeJS.ProcessEnv;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export type AregSkillKindResolveResult =
|
|
263
|
+
| { type: "ok"; skillName: string }
|
|
264
|
+
| { type: "error"; error: AregErrorInfo };
|
|
265
|
+
|
|
266
|
+
export type AregProjectMutationPolicy = "init" | "skill-kind";
|
|
267
|
+
|
|
268
|
+
export interface AregProjectTextWriteRequest {
|
|
269
|
+
projectDir: string;
|
|
270
|
+
relativePath: string;
|
|
271
|
+
content: string;
|
|
272
|
+
description: string;
|
|
273
|
+
createParent: boolean;
|
|
274
|
+
policy: AregProjectMutationPolicy;
|
|
275
|
+
env: NodeJS.ProcessEnv;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export interface AregProjectManagedTargetRequest {
|
|
279
|
+
projectDir: string;
|
|
280
|
+
relativePath: string;
|
|
281
|
+
description: string;
|
|
282
|
+
policy: "skill-kind";
|
|
283
|
+
env: NodeJS.ProcessEnv;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export type AregProjectFileDeleteRequest = AregProjectManagedTargetRequest;
|
|
287
|
+
export type AregProjectRemoveEmptyDirRequest = AregProjectManagedTargetRequest;
|
|
288
|
+
export type AregProjectSymlinkDeleteRequest = AregProjectManagedTargetRequest;
|
|
289
|
+
|
|
290
|
+
export type AregProjectMutationResult = { ok: true } | { ok: false; error: AregErrorInfo };
|
|
291
|
+
export type AregProjectRemoveEmptyDirResult =
|
|
292
|
+
| { ok: true; removed: boolean }
|
|
293
|
+
| { ok: false; error: AregErrorInfo };
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Areg's project-resource gateway.
|
|
297
|
+
*
|
|
298
|
+
* This is intentionally domain-oriented: it exposes named areg project facts and
|
|
299
|
+
* project-scoped safe mutation primitives, not a generic filesystem API. Add new
|
|
300
|
+
* reads here only when they represent stable areg project concepts, and route new
|
|
301
|
+
* writes/deletes through project-scoped primitives with an explicit policy. Do not
|
|
302
|
+
* add an unrestricted filesystem gateway to areg as a convenience for operation code.
|
|
303
|
+
*/
|
|
304
|
+
export interface AregProjectGateway {
|
|
305
|
+
inspectProjectBase(request: AregProjectInspectionRequest): Promise<AregProjectBaseInspection>;
|
|
306
|
+
inspectInstructionFiles(request: AregProjectDirRequest): Promise<AregInstructionFilesInspection>;
|
|
307
|
+
inspectPiArtifacts(request: AregProjectDirRequest): Promise<AregPiArtifactsInspection>;
|
|
308
|
+
inspectPiSkillInventory(request: AregProjectDirRequest): Promise<AregPiSkillInventoryInspection>;
|
|
309
|
+
inspectSkillNameInventory(request: AregProjectDirRequest): Promise<AregSkillNameInventory>;
|
|
310
|
+
inspectSkillFindRoots(request: AregProjectDirRequest): Promise<AregSkillFindRootsInspection>;
|
|
311
|
+
inspectCheckSkill(request: AregSkillInspectionRequest): Promise<AregCheckSkillInspection>;
|
|
312
|
+
inspectSkillKindSkill(request: AregSkillInspectionRequest): Promise<AregSkillKindSkillInspection>;
|
|
313
|
+
inspectPairingDirectories(
|
|
314
|
+
request: AregProjectDirRequest,
|
|
315
|
+
): Promise<readonly AregCheckPairingDirectory[]>;
|
|
316
|
+
readLocallyExcludedSkillNames(request: AregProjectDirRequest): Promise<readonly string[]>;
|
|
317
|
+
resolveSkillKindSpec(request: AregSkillKindResolveRequest): Promise<AregSkillKindResolveResult>;
|
|
318
|
+
preflightWriteTextFile(request: AregProjectTextWriteRequest): Promise<AregProjectMutationResult>;
|
|
319
|
+
preflightDeleteFile(request: AregProjectFileDeleteRequest): Promise<AregProjectMutationResult>;
|
|
320
|
+
preflightDeleteSymlink(
|
|
321
|
+
request: AregProjectSymlinkDeleteRequest,
|
|
322
|
+
): Promise<AregProjectMutationResult>;
|
|
323
|
+
preflightRemoveEmptyDir(
|
|
324
|
+
request: AregProjectRemoveEmptyDirRequest,
|
|
325
|
+
): Promise<AregProjectMutationResult>;
|
|
326
|
+
writeTextFile(request: AregProjectTextWriteRequest): Promise<AregProjectMutationResult>;
|
|
327
|
+
deleteFile(request: AregProjectFileDeleteRequest): Promise<AregProjectMutationResult>;
|
|
328
|
+
deleteSymlink(request: AregProjectSymlinkDeleteRequest): Promise<AregProjectMutationResult>;
|
|
329
|
+
removeEmptyDir(
|
|
330
|
+
request: AregProjectRemoveEmptyDirRequest,
|
|
331
|
+
): Promise<AregProjectRemoveEmptyDirResult>;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export interface AregInitTextWritePlan {
|
|
335
|
+
relativePath: "ns.toml" | "AGENTS.md" | "CLAUDE.md" | ".claude/settings.local.json";
|
|
336
|
+
content: string;
|
|
337
|
+
description: string;
|
|
338
|
+
createParent: boolean;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export interface AregSkillKindTextWritePlan {
|
|
342
|
+
relativePath: string;
|
|
343
|
+
content: string;
|
|
344
|
+
description: string;
|
|
345
|
+
createParent: boolean;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export interface AregSkillKindDeletePlan {
|
|
349
|
+
relativePath: string;
|
|
350
|
+
description: string;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export interface AregSkillKindDeleteSymlinkPlan {
|
|
354
|
+
relativePath: string;
|
|
355
|
+
description: string;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export interface AregSkillKindRemoveEmptyDirPlan {
|
|
359
|
+
relativePath: string;
|
|
360
|
+
description: string;
|
|
361
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export { buildCli, runCli, VERSION, type CliDeps } from "./cli.ts";
|
|
2
|
+
export { createRealAregContext, type AregCliContext } from "./context.ts";
|
|
3
|
+
export type {
|
|
4
|
+
AregCheckPairingDirectory,
|
|
5
|
+
AregCheckSkillInspection,
|
|
6
|
+
AregErrorInfo,
|
|
7
|
+
AregReplacementInspection,
|
|
8
|
+
AregGithubGateway,
|
|
9
|
+
AregGithubSkillListResult,
|
|
10
|
+
AregHostGateway,
|
|
11
|
+
AregHostToolName,
|
|
12
|
+
AregNpxSkillsAddRequest,
|
|
13
|
+
AregNpxSkillsAddResult,
|
|
14
|
+
AregNpxSkillsGateway,
|
|
15
|
+
AregOperationResult,
|
|
16
|
+
AregPathState,
|
|
17
|
+
AregProjectBaseInspection,
|
|
18
|
+
AregProjectDirRequest,
|
|
19
|
+
AregProjectGateway,
|
|
20
|
+
AregProjectInspectionRequest,
|
|
21
|
+
AregProjectMutationPolicy,
|
|
22
|
+
AregProjectMutationResult,
|
|
23
|
+
AregProjectRemoveEmptyDirResult,
|
|
24
|
+
AregProjectTextWriteRequest,
|
|
25
|
+
AregPromptGateway,
|
|
26
|
+
AregSkillInspectionRequest,
|
|
27
|
+
AregSkillKindResolveRequest,
|
|
28
|
+
AregSkillKindResolveResult,
|
|
29
|
+
AregSkillKindSkillInspection,
|
|
30
|
+
AregSkillxInstallRequest,
|
|
31
|
+
AregSkillxInstallResult,
|
|
32
|
+
AregSkillxInstalledSkill,
|
|
33
|
+
AregSkillxWorkspaceCleanupRequest,
|
|
34
|
+
AregSkillxWorkspaceGateway,
|
|
35
|
+
AregSkillxWorkspaceInstall,
|
|
36
|
+
AregTextFileState,
|
|
37
|
+
AregToolCheckResult,
|
|
38
|
+
} from "./gateways.ts";
|
|
39
|
+
export { parseLockfileData, parseSkillFrontmatterText } from "./operations/check.ts";
|
|
40
|
+
export { parseSkillInput } from "./operations/skillx.ts";
|
|
41
|
+
export {
|
|
42
|
+
buildNpxSkillsAddArgs,
|
|
43
|
+
RealAregGithubGateway,
|
|
44
|
+
RealAregHostGateway,
|
|
45
|
+
RealAregNpxSkillsGateway,
|
|
46
|
+
RealAregProjectGateway,
|
|
47
|
+
RealAregSkillxWorkspaceGateway,
|
|
48
|
+
} from "./real-gateways.ts";
|