@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.
- package/package.json +7 -6
- package/src/cli.ts +0 -33
- package/src/context.ts +3 -29
- package/src/fake-gateways.ts +108 -380
- package/src/gateways/errors.ts +2 -2
- package/src/gateways/fs-utils.ts +2 -2
- package/src/gateways/project-fs.ts +29 -41
- package/src/gateways/project-gateway.ts +98 -31
- package/src/gateways.ts +34 -150
- package/src/index.ts +2 -28
- package/src/operations/check.ts +33 -20
- package/src/operations/doctor-skills.ts +71 -7
- package/src/operations/file-state.ts +4 -4
- package/src/operations/manifest-source-findings.ts +45 -0
- package/src/operations/manifest-sources.ts +119 -0
- package/src/operations/pi-settings.ts +2 -5
- package/src/operations/project-inspection.ts +32 -28
- package/src/operations/project-mutations.ts +5 -21
- package/src/operations/project-resolution.ts +2 -2
- package/src/operations/skill-find.ts +23 -3
- package/src/operations/skill-kind-apply-plan.ts +2 -2
- package/src/operations/skill-kind-frontmatter.ts +1 -1
- package/src/operations/skill-kind-inference.ts +30 -14
- package/src/operations/skill-kind.ts +34 -12
- package/src/sort.ts +3 -3
- package/src/gateways/command-constants.ts +0 -1
- package/src/gateways/github-gateway.ts +0 -111
- package/src/gateways/host-gateway.ts +0 -35
- package/src/gateways/mutation-policy.ts +0 -94
- package/src/gateways/npx-skills-gateway.ts +0 -53
- package/src/gateways/prompt-gateway.ts +0 -21
- package/src/gateways/skillx-workspace-gateway.ts +0 -220
- package/src/operations/frontmatter.ts +0 -151
- package/src/operations/init.ts +0 -548
- package/src/operations/lockfile.ts +0 -107
- package/src/operations/managed-markdown-block.ts +0 -47
- package/src/operations/project-agents.ts +0 -115
- package/src/operations/skill-mirror-conventions.ts +0 -126
- package/src/operations/skillx.ts +0 -305
- package/src/operations/toml-section.ts +0 -53
- package/src/operations/update-skills.ts +0 -325
- package/src/real-gateways.ts +0 -6
- package/src/skill-lookup.ts +0 -254
|
@@ -11,6 +11,7 @@ import { renderTextTable, type TextTableColumn } from "@nseng-ai/foundation/text
|
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
|
|
13
13
|
import type { AregCliContext } from "../context.ts";
|
|
14
|
+
import { aregManifestSkillSourceViewSchema } from "./manifest-sources.ts";
|
|
14
15
|
import { isPathStateError } from "./file-state.ts";
|
|
15
16
|
import { inspectSkillKindProject } from "./project-inspection.ts";
|
|
16
17
|
import { inspectResolvedProjectGitRoot } from "./project-resolution.ts";
|
|
@@ -73,6 +74,8 @@ const skillKindReplacementSchema = z.object({
|
|
|
73
74
|
advice: z.string().optional(),
|
|
74
75
|
});
|
|
75
76
|
|
|
77
|
+
const skillKindManifestSourceSchema = aregManifestSkillSourceViewSchema;
|
|
78
|
+
|
|
76
79
|
const skillKindRecordSchema = z.object({
|
|
77
80
|
skill: z.string(),
|
|
78
81
|
kind: z.enum(INFERRED_SKILL_INVOCATION_KINDS),
|
|
@@ -81,6 +84,7 @@ const skillKindRecordSchema = z.object({
|
|
|
81
84
|
piExtension: z.enum(PI_EXTENSION_STATUSES),
|
|
82
85
|
artifacts: skillKindArtifactFactsSchema,
|
|
83
86
|
replacement: skillKindReplacementSchema,
|
|
87
|
+
manifestSources: z.array(skillKindManifestSourceSchema),
|
|
84
88
|
notes: z.array(z.string()),
|
|
85
89
|
});
|
|
86
90
|
|
|
@@ -127,7 +131,7 @@ export const skillKindApplyRequestSchema = z.object({
|
|
|
127
131
|
.default(".")
|
|
128
132
|
.describe("Project directory or subdirectory to mutate (default: current directory)."),
|
|
129
133
|
dryRun: z.boolean().default(false).describe("Show planned edits without writing files."),
|
|
130
|
-
yes: z.boolean().default(false).describe("Approve deletion prompts for
|
|
134
|
+
yes: z.boolean().default(false).describe("Approve deletion prompts for harness overlays."),
|
|
131
135
|
kind: z.enum(SKILL_INVOCATION_KINDS).describe("Desired skill invocation kind."),
|
|
132
136
|
skills: z.array(z.string()).min(1).describe("Installed skill names or path-like skill specs."),
|
|
133
137
|
});
|
|
@@ -193,7 +197,7 @@ export function buildSkillGroup(): ClinkrGroup<AregCliContext> {
|
|
|
193
197
|
skillGroup.command({
|
|
194
198
|
name: "apply",
|
|
195
199
|
description:
|
|
196
|
-
"Apply the
|
|
200
|
+
"Apply the harness overlays for a skill invocation kind. This reconciles harness overlays to the requested kind. The unlisted kind additionally deletes the .agents/skills and .claude/skills mirror symlinks so the skill disappears from every harness typeahead. It is not a historical undo system; use git to roll back exact previous file contents.",
|
|
197
201
|
schema: skillKindApplyRequestSchema,
|
|
198
202
|
positionals: { kind: { position: 0 }, skills: { position: 1 } },
|
|
199
203
|
options: { yes: { short: "-y" } },
|
|
@@ -314,7 +318,6 @@ export async function runSkillKindApply(
|
|
|
314
318
|
const applyResult = await applyProjectMutationPlan({
|
|
315
319
|
ctx,
|
|
316
320
|
projectDir,
|
|
317
|
-
policy: "skill-kind",
|
|
318
321
|
writes: plans.flatMap(plannedWrites),
|
|
319
322
|
deletes: plans.flatMap(plannedDeletes),
|
|
320
323
|
deleteSymlinks: plans.flatMap(plannedDeleteSymlinks),
|
|
@@ -363,7 +366,8 @@ export function renderSkillKindList(
|
|
|
363
366
|
caps: RenderCapabilities = { canEmitAnsi: false },
|
|
364
367
|
): string {
|
|
365
368
|
if (result.skills.length === 0) return "No managed skills found.";
|
|
366
|
-
const
|
|
369
|
+
const hasSources = result.skills.some((record) => record.manifestSources.length > 0);
|
|
370
|
+
const shouldIncludeNotes = result.skills.some((record) => record.notes.length > 0);
|
|
367
371
|
const columns: TextTableColumn[] = [
|
|
368
372
|
{ header: "SKILL", style: "bold-cyan" },
|
|
369
373
|
{ header: "KIND" },
|
|
@@ -371,7 +375,8 @@ export function renderSkillKindList(
|
|
|
371
375
|
{ header: "NATIVE" },
|
|
372
376
|
{ header: "PI" },
|
|
373
377
|
];
|
|
374
|
-
if (
|
|
378
|
+
if (hasSources) columns.push({ header: "SOURCES" });
|
|
379
|
+
if (shouldIncludeNotes) columns.push({ header: "NOTES", style: "dim" });
|
|
375
380
|
return renderTextTable({
|
|
376
381
|
columns,
|
|
377
382
|
rows: result.skills.map((record) => {
|
|
@@ -382,7 +387,8 @@ export function renderSkillKindList(
|
|
|
382
387
|
record.nativeDirect,
|
|
383
388
|
record.piExtension,
|
|
384
389
|
];
|
|
385
|
-
if (
|
|
390
|
+
if (hasSources) base.push(skillKindSourcesLabel(record));
|
|
391
|
+
if (shouldIncludeNotes) base.push(record.notes.join("; "));
|
|
386
392
|
return base;
|
|
387
393
|
}),
|
|
388
394
|
canEmitAnsi: caps.canEmitAnsi,
|
|
@@ -408,6 +414,14 @@ export function renderSkillKindShow(result: SkillKindShowResult): string {
|
|
|
408
414
|
`- .claude/skills mirror: ${presence(record.artifacts.claudeMirror)}`,
|
|
409
415
|
`- Pi replacement: ${record.replacement.label}`,
|
|
410
416
|
];
|
|
417
|
+
if (record.manifestSources.length > 0) {
|
|
418
|
+
lines.push("Manifest sources:");
|
|
419
|
+
for (const source of record.manifestSources) {
|
|
420
|
+
lines.push(
|
|
421
|
+
`- ${source.harness} ${source.manifestKey}: ${source.packageName}@${source.version} -> ${source.targetSkillRelativePath}`,
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
411
425
|
if (record.notes.length > 0) {
|
|
412
426
|
lines.push("Notes:");
|
|
413
427
|
for (const note of record.notes) lines.push(`- ${note}`);
|
|
@@ -472,26 +486,33 @@ function toSkillKindRecordResult(record: SkillKindRecord): SkillKindRecordResult
|
|
|
472
486
|
evidence: record.replacement.evidence,
|
|
473
487
|
advice: record.replacement.advice,
|
|
474
488
|
},
|
|
489
|
+
manifestSources: record.manifestSources.map((source) => ({ ...source })),
|
|
475
490
|
notes: [...record.notes],
|
|
476
491
|
};
|
|
477
492
|
}
|
|
478
493
|
|
|
494
|
+
function skillKindSourcesLabel(record: SkillKindRecordResult): string {
|
|
495
|
+
if (record.manifestSources.length === 0) return "";
|
|
496
|
+
const labels = new Set(record.manifestSources.map((source) => source.harness));
|
|
497
|
+
return ["manifest", ...labels].join(":");
|
|
498
|
+
}
|
|
499
|
+
|
|
479
500
|
function renderApplyOperation(
|
|
480
501
|
operation: SkillKindApplyResult["skills"][number]["operations"][number],
|
|
481
|
-
|
|
502
|
+
isDryRun: boolean,
|
|
482
503
|
): string | undefined {
|
|
483
504
|
if (!("isApplied" in operation)) return undefined;
|
|
484
505
|
switch (operation.type) {
|
|
485
506
|
case "write":
|
|
486
|
-
return `${
|
|
507
|
+
return `${isDryRun ? "Would write" : "Wrote"} ${operation.path}`;
|
|
487
508
|
case "skip":
|
|
488
|
-
return `${
|
|
509
|
+
return `${isDryRun ? "Would skip" : "Skipped"} ${operation.path}: ${operation.reason ?? "already current"}`;
|
|
489
510
|
case "delete":
|
|
490
|
-
return `${
|
|
511
|
+
return `${isDryRun ? "Would delete" : "Deleted"} ${operation.path}`;
|
|
491
512
|
case "delete-symlink":
|
|
492
|
-
return `${
|
|
513
|
+
return `${isDryRun ? "Would delete symlink" : "Deleted symlink"} ${operation.path}`;
|
|
493
514
|
case "remove-empty-dir":
|
|
494
|
-
if (
|
|
515
|
+
if (isDryRun) return `Would remove ${operation.path} if empty`;
|
|
495
516
|
return operation.isApplied ? `Removed ${operation.path}` : undefined;
|
|
496
517
|
}
|
|
497
518
|
}
|
|
@@ -519,6 +540,7 @@ function emptyShowResult(projectDir: string, skillName: string): SkillKindShowRe
|
|
|
519
540
|
claudeMirror: false,
|
|
520
541
|
},
|
|
521
542
|
replacement: { verified: false, label: "replacement-missing" },
|
|
543
|
+
manifestSources: [],
|
|
522
544
|
notes: [],
|
|
523
545
|
},
|
|
524
546
|
};
|
package/src/sort.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export function
|
|
2
|
-
return [...values].sort();
|
|
1
|
+
export function sortStringsLocaleAware(values: Iterable<string>): string[] {
|
|
2
|
+
return [...values].sort((left, right) => left.localeCompare(right));
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
export function uniqueSortedStrings(values: readonly string[]): string[] {
|
|
6
|
-
return
|
|
6
|
+
return sortStringsLocaleAware(new Set(values));
|
|
7
7
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const COMMAND_TIMEOUT_MS = 60_000;
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
formatCommand,
|
|
3
|
-
formatCommandResultFailure,
|
|
4
|
-
runCommand,
|
|
5
|
-
stripTerminalEscapes,
|
|
6
|
-
type CommandRunner,
|
|
7
|
-
} from "@nseng-ai/foundation/exec";
|
|
8
|
-
|
|
9
|
-
import type {
|
|
10
|
-
AregGithubGateway,
|
|
11
|
-
AregGithubSkillFileResult,
|
|
12
|
-
AregGithubSkillListResult,
|
|
13
|
-
} from "../gateways.ts";
|
|
14
|
-
import { COMMAND_TIMEOUT_MS } from "./command-constants.ts";
|
|
15
|
-
import { errorInfo } from "./errors.ts";
|
|
16
|
-
|
|
17
|
-
export class RealAregGithubGateway implements AregGithubGateway {
|
|
18
|
-
private readonly runner: CommandRunner;
|
|
19
|
-
|
|
20
|
-
constructor(options: { runner?: CommandRunner } = {}) {
|
|
21
|
-
this.runner = options.runner ?? runCommand;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async listSkillDirectoryNames(options: {
|
|
25
|
-
repo: string;
|
|
26
|
-
ref?: string;
|
|
27
|
-
env: NodeJS.ProcessEnv;
|
|
28
|
-
}): Promise<AregGithubSkillListResult> {
|
|
29
|
-
const resource = githubContentsResource(options.repo, "skills", options.ref);
|
|
30
|
-
const args = ["api", resource, "--jq", ".[].name"];
|
|
31
|
-
const displayCommand = formatCommand("gh", args);
|
|
32
|
-
const result = await this.runner("gh", args, { env: options.env, timeout: COMMAND_TIMEOUT_MS });
|
|
33
|
-
if (result.code === 0) {
|
|
34
|
-
return {
|
|
35
|
-
type: "ok",
|
|
36
|
-
skillNames: result.stdout
|
|
37
|
-
.split("\n")
|
|
38
|
-
.map((line) => line.trim())
|
|
39
|
-
.filter((line) => line.length > 0),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
return classifyGithubFailure({
|
|
43
|
-
result,
|
|
44
|
-
args,
|
|
45
|
-
displayCommand,
|
|
46
|
-
missingMessage: `No skills directory found in ${options.repo}`,
|
|
47
|
-
authMessage: `Authentication error accessing ${options.repo}`,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async checkSkillFile(options: {
|
|
52
|
-
repo: string;
|
|
53
|
-
path: string;
|
|
54
|
-
ref?: string;
|
|
55
|
-
env: NodeJS.ProcessEnv;
|
|
56
|
-
}): Promise<AregGithubSkillFileResult> {
|
|
57
|
-
const resource = githubContentsResource(options.repo, options.path, options.ref);
|
|
58
|
-
const args = ["api", resource, "--jq", ".type"];
|
|
59
|
-
const displayCommand = formatCommand("gh", args);
|
|
60
|
-
const result = await this.runner("gh", args, { env: options.env, timeout: COMMAND_TIMEOUT_MS });
|
|
61
|
-
if (result.code === 0) return { type: "found" };
|
|
62
|
-
return classifyGithubFailure({
|
|
63
|
-
result,
|
|
64
|
-
args,
|
|
65
|
-
displayCommand,
|
|
66
|
-
missingMessage: `Skill file not found in ${options.repo}: ${options.path}`,
|
|
67
|
-
authMessage: `Authentication error accessing ${options.repo}`,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
interface GithubFailureInput {
|
|
73
|
-
result: Awaited<ReturnType<CommandRunner>>;
|
|
74
|
-
args: readonly string[];
|
|
75
|
-
displayCommand: string;
|
|
76
|
-
missingMessage: string;
|
|
77
|
-
authMessage: string;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function githubContentsResource(repo: string, path: string, ref: string | undefined): string {
|
|
81
|
-
const normalizedPath = path
|
|
82
|
-
.split("/")
|
|
83
|
-
.filter((part) => part.length > 0)
|
|
84
|
-
.map(encodeURIComponent)
|
|
85
|
-
.join("/");
|
|
86
|
-
const resource = `repos/${repo}/contents/${normalizedPath}`;
|
|
87
|
-
if (ref === undefined) return resource;
|
|
88
|
-
return `${resource}?ref=${encodeURIComponent(ref)}`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function classifyGithubFailure(
|
|
92
|
-
input: GithubFailureInput,
|
|
93
|
-
):
|
|
94
|
-
| { type: "missing"; message: string }
|
|
95
|
-
| { type: "auth-error"; message: string }
|
|
96
|
-
| { type: "error"; error: ReturnType<typeof errorInfo> } {
|
|
97
|
-
const combined = stripTerminalEscapes(
|
|
98
|
-
`${input.result.stdout}\n${input.result.stderr}`,
|
|
99
|
-
).toLowerCase();
|
|
100
|
-
if (combined.includes("404")) return { type: "missing", message: input.missingMessage };
|
|
101
|
-
if (combined.includes("401") || combined.includes("403"))
|
|
102
|
-
return { type: "auth-error", message: input.authMessage };
|
|
103
|
-
return {
|
|
104
|
-
type: "error",
|
|
105
|
-
error: errorInfo(
|
|
106
|
-
input.result.startupError === undefined ? "gh-failed" : "gh-startup-failed",
|
|
107
|
-
formatCommandResultFailure("gh api failed", "gh", input.args, input.result),
|
|
108
|
-
input.displayCommand,
|
|
109
|
-
),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { constants } from "node:fs";
|
|
2
|
-
import { access } from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
import type { AregHostGateway, AregHostToolName, AregToolCheckResult } from "../gateways.ts";
|
|
6
|
-
|
|
7
|
-
export class RealAregHostGateway implements AregHostGateway {
|
|
8
|
-
async checkTool(options: {
|
|
9
|
-
tool: AregHostToolName;
|
|
10
|
-
cwd: string;
|
|
11
|
-
env: NodeJS.ProcessEnv;
|
|
12
|
-
}): Promise<AregToolCheckResult> {
|
|
13
|
-
const pathValue = options.env.PATH ?? "";
|
|
14
|
-
for (const directory of pathValue.split(path.delimiter)) {
|
|
15
|
-
if (directory.length === 0) continue;
|
|
16
|
-
const candidate = path.join(directory, options.tool);
|
|
17
|
-
if (await isExecutable(candidate))
|
|
18
|
-
return { type: "found", tool: options.tool, path: candidate };
|
|
19
|
-
}
|
|
20
|
-
return {
|
|
21
|
-
type: "missing",
|
|
22
|
-
tool: options.tool,
|
|
23
|
-
message: `Required host tool is missing: ${options.tool}`,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function isExecutable(candidate: string): Promise<boolean> {
|
|
29
|
-
try {
|
|
30
|
-
await access(candidate, constants.X_OK);
|
|
31
|
-
return true;
|
|
32
|
-
} catch {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import type { AregProjectMutationPolicy } from "../gateways.ts";
|
|
2
|
-
|
|
3
|
-
interface AregProjectMutationPolicyDescriptor {
|
|
4
|
-
policy: AregProjectMutationPolicy;
|
|
5
|
-
isAllowedRelativePath(relativePath: string): boolean;
|
|
6
|
-
refusedTargetCode: string;
|
|
7
|
-
shouldCheckUnsupportedFirst: boolean;
|
|
8
|
-
unsupportedMessage(relativePath: string, description: string): string;
|
|
9
|
-
unsafeMessage(relativePath: string, description: string): string;
|
|
10
|
-
outsideMessage(relativePath: string, description: string): string;
|
|
11
|
-
symlinkCode: string;
|
|
12
|
-
notFileCode: string;
|
|
13
|
-
parentSymlinkCode: string;
|
|
14
|
-
parentNotDirectoryCode: string;
|
|
15
|
-
parentMissingCode: string;
|
|
16
|
-
parentCreateFailedCode: string;
|
|
17
|
-
writeFailedCode: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const INIT_MUTATION_POLICY = {
|
|
21
|
-
policy: "init",
|
|
22
|
-
isAllowedRelativePath: isAllowedInitRelativePath,
|
|
23
|
-
refusedTargetCode: "init-write-target-refused",
|
|
24
|
-
shouldCheckUnsupportedFirst: true,
|
|
25
|
-
unsupportedMessage: (relativePath) =>
|
|
26
|
-
`Refusing to write unsupported init target: ${relativePath}`,
|
|
27
|
-
unsafeMessage: (relativePath) => `Refusing to write unsafe init target: ${relativePath}`,
|
|
28
|
-
outsideMessage: (relativePath) => `Refusing to write outside project root: ${relativePath}`,
|
|
29
|
-
symlinkCode: "init-symlink",
|
|
30
|
-
notFileCode: "init-not-file",
|
|
31
|
-
parentSymlinkCode: "init-parent-symlink",
|
|
32
|
-
parentNotDirectoryCode: "init-parent-not-directory",
|
|
33
|
-
parentMissingCode: "init-parent-missing",
|
|
34
|
-
parentCreateFailedCode: "init-parent-create-failed",
|
|
35
|
-
writeFailedCode: "init-write-failed",
|
|
36
|
-
} satisfies AregProjectMutationPolicyDescriptor;
|
|
37
|
-
|
|
38
|
-
const SKILL_KIND_MUTATION_POLICY = {
|
|
39
|
-
policy: "skill-kind",
|
|
40
|
-
isAllowedRelativePath: isAllowedSkillKindRelativePath,
|
|
41
|
-
refusedTargetCode: "skill-kind-target-refused",
|
|
42
|
-
shouldCheckUnsupportedFirst: false,
|
|
43
|
-
unsupportedMessage: (candidate, description) =>
|
|
44
|
-
`Refusing to manage unsupported ${description} target: ${candidate}`,
|
|
45
|
-
unsafeMessage: (candidate, description) =>
|
|
46
|
-
`Refusing to manage unsafe ${description} target: ${candidate}`,
|
|
47
|
-
outsideMessage: (candidate, description) =>
|
|
48
|
-
`Refusing to manage ${description} outside project root: ${candidate}`,
|
|
49
|
-
symlinkCode: "skill-kind-symlink",
|
|
50
|
-
notFileCode: "skill-kind-not-file",
|
|
51
|
-
parentSymlinkCode: "skill-kind-parent-symlink",
|
|
52
|
-
parentNotDirectoryCode: "skill-kind-parent-not-directory",
|
|
53
|
-
parentMissingCode: "skill-kind-parent-missing",
|
|
54
|
-
parentCreateFailedCode: "skill-kind-parent-create-failed",
|
|
55
|
-
writeFailedCode: "skill-kind-write-failed",
|
|
56
|
-
} satisfies AregProjectMutationPolicyDescriptor;
|
|
57
|
-
|
|
58
|
-
export function getAregProjectMutationPolicyDescriptor(
|
|
59
|
-
policy: AregProjectMutationPolicy,
|
|
60
|
-
): AregProjectMutationPolicyDescriptor {
|
|
61
|
-
switch (policy) {
|
|
62
|
-
case "init":
|
|
63
|
-
return INIT_MUTATION_POLICY;
|
|
64
|
-
case "skill-kind":
|
|
65
|
-
return SKILL_KIND_MUTATION_POLICY;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function isAllowedInitRelativePath(relativePath: string): boolean {
|
|
70
|
-
return ["ns.toml", "AGENTS.md", "CLAUDE.md", ".claude/settings.local.json"].includes(
|
|
71
|
-
relativePath,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function isAllowedSkillKindRelativePath(relativePath: string): boolean {
|
|
76
|
-
if (relativePath === ".pi/settings.json") return true;
|
|
77
|
-
const parts = relativePath.split("/");
|
|
78
|
-
return (
|
|
79
|
-
isAllowedSkillKindRelativePathParts(parts, 1) || isAllowedSkillKindRelativePathParts(parts, 2)
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function isAllowedSkillKindRelativePathParts(parts: readonly string[], rootLength: 1 | 2): boolean {
|
|
84
|
-
const isLocalRoot = rootLength === 1 && parts[0] === "skills";
|
|
85
|
-
const isVendoredRoot = rootLength === 2 && parts[0] === ".agents" && parts[1] === "skills";
|
|
86
|
-
if (!isLocalRoot && !isVendoredRoot) return false;
|
|
87
|
-
return (
|
|
88
|
-
(parts.length === rootLength + 2 && parts[rootLength + 1] === "SKILL.md") ||
|
|
89
|
-
(parts.length === rootLength + 3 &&
|
|
90
|
-
parts[rootLength + 1] === "agents" &&
|
|
91
|
-
parts[rootLength + 2] === "openai.yaml") ||
|
|
92
|
-
(parts.length === rootLength + 2 && parts[rootLength + 1] === "agents")
|
|
93
|
-
);
|
|
94
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
formatCommand,
|
|
3
|
-
formatCommandResultFailure,
|
|
4
|
-
runCommand,
|
|
5
|
-
type CommandRunner,
|
|
6
|
-
} from "@nseng-ai/foundation/exec";
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
AregNpxSkillsAddRequest,
|
|
10
|
-
AregNpxSkillsAddResult,
|
|
11
|
-
AregNpxSkillsGateway,
|
|
12
|
-
} from "../gateways.ts";
|
|
13
|
-
import { COMMAND_TIMEOUT_MS } from "./command-constants.ts";
|
|
14
|
-
import { errorInfo } from "./errors.ts";
|
|
15
|
-
|
|
16
|
-
export class RealAregNpxSkillsGateway implements AregNpxSkillsGateway {
|
|
17
|
-
private readonly runner: CommandRunner;
|
|
18
|
-
|
|
19
|
-
constructor(options: { runner?: CommandRunner } = {}) {
|
|
20
|
-
this.runner = options.runner ?? runCommand;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async addSkills(request: AregNpxSkillsAddRequest): Promise<AregNpxSkillsAddResult> {
|
|
24
|
-
const args = buildNpxSkillsAddArgs(request);
|
|
25
|
-
const displayCommand = formatCommand("npx", args);
|
|
26
|
-
const result = await this.runner("npx", args, {
|
|
27
|
-
cwd: request.cwd,
|
|
28
|
-
env: request.env,
|
|
29
|
-
timeout: COMMAND_TIMEOUT_MS,
|
|
30
|
-
});
|
|
31
|
-
if (result.code === 0) return { type: "ok" };
|
|
32
|
-
return {
|
|
33
|
-
type: "error",
|
|
34
|
-
error: errorInfo(
|
|
35
|
-
result.startupError === undefined ? "npx-failed" : "npx-startup-failed",
|
|
36
|
-
formatCommandResultFailure("npx skills add failed", "npx", args, result),
|
|
37
|
-
displayCommand,
|
|
38
|
-
),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function buildNpxSkillsAddArgs(request: AregNpxSkillsAddRequest): string[] {
|
|
44
|
-
const args = ["skills", "add", request.sourceRepo];
|
|
45
|
-
for (const skillName of request.skillNames) {
|
|
46
|
-
args.push("--skill", skillName);
|
|
47
|
-
}
|
|
48
|
-
for (const agent of request.targetAgents) {
|
|
49
|
-
args.push("--agent", agent);
|
|
50
|
-
}
|
|
51
|
-
args.push("-y");
|
|
52
|
-
return args;
|
|
53
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
}
|