@anvia/cli 1.1.1 → 1.3.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.
- package/README.md +48 -0
- package/dist/chunk-ERCP4EIZ.js +607 -0
- package/dist/chunk-ERCP4EIZ.js.map +1 -0
- package/dist/cli.js +287 -33
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +81 -1
- package/dist/index.js +27 -3
- package/dist/skills/anvia-agent/SKILL.md +59 -0
- package/dist/skills/anvia-agent/references/agent-options.md +83 -0
- package/dist/skills/anvia-agent/references/providers.md +22 -0
- package/dist/skills/anvia-agent/references/teams.md +84 -0
- package/dist/skills/anvia-agent/references/tools.md +71 -0
- package/dist/skills/anvia-agent/scripts/check-agent.sh +99 -0
- package/dist/skills/anvia-channels/SKILL.md +59 -0
- package/dist/skills/anvia-channels/references/adapters.md +53 -0
- package/dist/skills/anvia-channels/references/channel-agent.md +52 -0
- package/dist/skills/anvia-channels/references/delivery.md +53 -0
- package/dist/skills/anvia-channels/scripts/check-channels.sh +79 -0
- package/dist/skills/anvia-chat/SKILL.md +61 -0
- package/dist/skills/anvia-chat/references/react-ui.md +104 -0
- package/dist/skills/anvia-chat/references/server-protocol.md +53 -0
- package/dist/skills/anvia-chat/references/transports-state.md +71 -0
- package/dist/skills/anvia-chat/scripts/check-chat-boundary.sh +70 -0
- package/dist/skills/anvia-evals/SKILL.md +51 -0
- package/dist/skills/anvia-evals/references/judges.md +61 -0
- package/dist/skills/anvia-evals/references/metrics.md +45 -0
- package/dist/skills/anvia-evals/references/running.md +62 -0
- package/dist/skills/anvia-evals/scripts/check-evals.sh +73 -0
- package/dist/skills/anvia-mcp/SKILL.md +49 -0
- package/dist/skills/anvia-mcp/references/clients.md +73 -0
- package/dist/skills/anvia-mcp/references/safety.md +25 -0
- package/dist/skills/anvia-mcp/scripts/check-mcp.sh +73 -0
- package/dist/skills/anvia-pipeline/SKILL.md +52 -0
- package/dist/skills/anvia-pipeline/references/agents-extract.md +62 -0
- package/dist/skills/anvia-pipeline/references/steps-compose.md +63 -0
- package/dist/skills/anvia-pipeline/scripts/check-pipeline.sh +69 -0
- package/dist/skills/anvia-rag/SKILL.md +50 -0
- package/dist/skills/anvia-rag/references/graph-rag.md +115 -0
- package/dist/skills/anvia-rag/references/pipeline.md +81 -0
- package/dist/skills/anvia-rag/references/rag-tool.md +43 -0
- package/dist/skills/anvia-rag/references/stores.md +68 -0
- package/dist/skills/anvia-rag/scripts/check-rag.sh +75 -0
- package/dist/skills/anvia-studio/SKILL.md +45 -0
- package/dist/skills/anvia-studio/references/inspect.md +33 -0
- package/dist/skills/anvia-studio/references/observe.md +34 -0
- package/dist/skills/anvia-studio/references/serve.md +65 -0
- package/dist/skills/anvia-studio/scripts/check-studio.sh +59 -0
- package/dist/skills/release-notes/SKILL.md +18 -0
- package/dist/skills/release-notes/references/style.md +6 -0
- package/dist/skills/release-notes/scripts/draft.sh +22 -0
- package/package.json +3 -3
- package/dist/chunk-TE2ODJOV.js +0 -135
- package/dist/chunk-TE2ODJOV.js.map +0 -1
package/README.md
CHANGED
|
@@ -13,3 +13,51 @@ pnpm dlx @anvia/cli add chat
|
|
|
13
13
|
|
|
14
14
|
Available items: `chat`, `thread`, `message`, `composer`, `attachment`, `markdown`, and
|
|
15
15
|
`tool-fallback`.
|
|
16
|
+
|
|
17
|
+
## Updating installed components
|
|
18
|
+
|
|
19
|
+
`update` compares the Anvia components in your project against the current registry:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pnpm dlx @anvia/cli update # check every item (preview only, writes nothing)
|
|
23
|
+
pnpm dlx @anvia/cli update composer # check a single item
|
|
24
|
+
pnpm dlx @anvia/cli update --overwrite
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Without `--overwrite`, `update` is a preview: it reports each file as `up-to-date`,
|
|
28
|
+
`modified` (the installed copy differs from the registry), or `missing`. Pass `--overwrite`
|
|
29
|
+
to write the registry content over out-of-date and missing files of installed components.
|
|
30
|
+
`update` never installs new items — use `add` for that. Locally edited copies are
|
|
31
|
+
overwritten, so commit or stash your changes first.
|
|
32
|
+
|
|
33
|
+
## Agent Skills
|
|
34
|
+
|
|
35
|
+
Scaffold the Anvia Agent Skills — curated, API-verified knowledge for building with
|
|
36
|
+
Anvia (agents, chat, RAG, MCP, pipelines, Studio, evals, channels) — so your coding
|
|
37
|
+
agent writes correct Anvia code:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
pnpm dlx @anvia/cli skills list # show the available skills
|
|
41
|
+
pnpm dlx @anvia/cli skills init # copy every skill into ./skills
|
|
42
|
+
pnpm dlx @anvia/cli skills init --claude --agents # also wire Claude Code and AGENTS.md
|
|
43
|
+
pnpm dlx @anvia/cli skills init --codex --cursor # --codex is an AGENTS.md alias
|
|
44
|
+
pnpm dlx @anvia/cli skills update # compare installed skills (preview only)
|
|
45
|
+
pnpm dlx @anvia/cli skills update --force
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`skills init` always writes the canonical skills into `./skills` (override with
|
|
49
|
+
`--dir <path>`) — one folder per skill (`SKILL.md` + `references/` + `scripts/`), with
|
|
50
|
+
the executable bit preserved on skill scripts — and prints the wiring snippet. Generated
|
|
51
|
+
adapters and the snippet always reference the effective directory. The
|
|
52
|
+
target flags add adapters on top:
|
|
53
|
+
|
|
54
|
+
- `--claude` copies the skills into `.claude/skills/`, where Claude Code loads them natively.
|
|
55
|
+
- `--cursor` writes one on-demand Cursor rule per skill into `.cursor/rules/` that points
|
|
56
|
+
at the matching `skills/<name>/SKILL.md`.
|
|
57
|
+
- `--agents` (and its alias `--codex`) maintains an `AGENTS.md` section, between
|
|
58
|
+
`anvia-skills` markers, listing every skill with its description. Existing
|
|
59
|
+
`AGENTS.md` content outside the markers is never touched.
|
|
60
|
+
|
|
61
|
+
Skills you have locally edited are left alone unless you pass `--force`. `skills update`
|
|
62
|
+
refreshes installed skills and adapters in place and never reinstalls skills that were
|
|
63
|
+
removed from the project. Both commands accept `--cwd <path>`.
|
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
// src/registry.ts
|
|
2
|
+
import { spawnSync } from "child_process";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
import { basename, dirname, join } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
var registryItemNames = [
|
|
9
|
+
"chat",
|
|
10
|
+
"thread",
|
|
11
|
+
"message",
|
|
12
|
+
"composer",
|
|
13
|
+
"attachment",
|
|
14
|
+
"markdown",
|
|
15
|
+
"tool-fallback"
|
|
16
|
+
];
|
|
17
|
+
var itemFiles = {
|
|
18
|
+
attachment: ["attachment.tsx"],
|
|
19
|
+
chat: [
|
|
20
|
+
"attachment.tsx",
|
|
21
|
+
"markdown.tsx",
|
|
22
|
+
"tool-fallback.tsx",
|
|
23
|
+
"message.tsx",
|
|
24
|
+
"composer.tsx",
|
|
25
|
+
"thread.tsx",
|
|
26
|
+
"chat.tsx"
|
|
27
|
+
],
|
|
28
|
+
composer: ["attachment.tsx", "composer.tsx"],
|
|
29
|
+
markdown: ["markdown.tsx"],
|
|
30
|
+
message: ["attachment.tsx", "markdown.tsx", "tool-fallback.tsx", "message.tsx"],
|
|
31
|
+
thread: ["attachment.tsx", "markdown.tsx", "tool-fallback.tsx", "message.tsx", "thread.tsx"],
|
|
32
|
+
"tool-fallback": ["tool-fallback.tsx"]
|
|
33
|
+
};
|
|
34
|
+
var revealCss = {
|
|
35
|
+
"@keyframes anvia-stream-gradient-settle": {
|
|
36
|
+
to: {
|
|
37
|
+
opacity: "1"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"@layer components": {
|
|
41
|
+
'.anvia-markdown [data-state="revealing"]': {
|
|
42
|
+
animation: "anvia-stream-gradient-settle var(--anvia-stream-reveal-duration, 180ms) linear both",
|
|
43
|
+
opacity: "var(--anvia-stream-reveal-opacity, 1)"
|
|
44
|
+
},
|
|
45
|
+
"@media (prefers-reduced-motion: reduce)": {
|
|
46
|
+
'.anvia-markdown [data-state="revealing"]': {
|
|
47
|
+
animation: "none",
|
|
48
|
+
opacity: "1"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function createRegistryItem(name, options = {}) {
|
|
54
|
+
const packageVersion = options.packageVersion ?? currentPackageVersion();
|
|
55
|
+
const registryDirectory = options.registryDirectory ?? bundledRegistryDirectory();
|
|
56
|
+
const files = itemFiles[name].map((filename) => ({
|
|
57
|
+
content: readFileSync(join(registryDirectory, filename), "utf8"),
|
|
58
|
+
path: `registry/anvia/${filename}`,
|
|
59
|
+
target: `@components/anvia/${filename}`,
|
|
60
|
+
type: "registry:component"
|
|
61
|
+
}));
|
|
62
|
+
const item = {
|
|
63
|
+
$schema: "https://ui.shadcn.com/schema/registry-item.json",
|
|
64
|
+
dependencies: [`@anvia/react-ui@${packageVersion}`],
|
|
65
|
+
description: registryItemDescription(name),
|
|
66
|
+
files,
|
|
67
|
+
name,
|
|
68
|
+
title: `Anvia ${name}`,
|
|
69
|
+
type: files.length === 1 ? "registry:component" : "registry:block"
|
|
70
|
+
};
|
|
71
|
+
if (name === "chat" || name === "markdown" || name === "message" || name === "thread") {
|
|
72
|
+
item.css = revealCss;
|
|
73
|
+
}
|
|
74
|
+
return item;
|
|
75
|
+
}
|
|
76
|
+
function initializeProject(options = {}) {
|
|
77
|
+
const cwd = options.cwd ?? process.cwd();
|
|
78
|
+
const args = ["init", "--cwd", cwd, "--yes", "--no-monorepo", "--base", "radix"];
|
|
79
|
+
if (options.template !== void 0) {
|
|
80
|
+
args.push("--template", options.template);
|
|
81
|
+
}
|
|
82
|
+
if (options.force === true) {
|
|
83
|
+
args.push("--force");
|
|
84
|
+
}
|
|
85
|
+
runShadcn(args);
|
|
86
|
+
}
|
|
87
|
+
function addRegistryItem(name, options = {}) {
|
|
88
|
+
const cwd = options.cwd ?? process.cwd();
|
|
89
|
+
const temporaryDirectory = mkdtempSync(join(tmpdir(), "anvia-registry-"));
|
|
90
|
+
const itemPath = join(temporaryDirectory, `${name}.json`);
|
|
91
|
+
try {
|
|
92
|
+
writeFileSync(itemPath, `${JSON.stringify(createRegistryItem(name), null, 2)}
|
|
93
|
+
`);
|
|
94
|
+
const args = ["add", itemPath, "--cwd", cwd, "--yes"];
|
|
95
|
+
if (options.overwrite === true) {
|
|
96
|
+
args.push("--overwrite");
|
|
97
|
+
}
|
|
98
|
+
runShadcn(args);
|
|
99
|
+
} finally {
|
|
100
|
+
rmSync(temporaryDirectory, { force: true, recursive: true });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function isRegistryItemName(value) {
|
|
104
|
+
return registryItemNames.includes(value);
|
|
105
|
+
}
|
|
106
|
+
function inspectInstalledItems(options = {}) {
|
|
107
|
+
const names = options.items ?? registryItemNames;
|
|
108
|
+
const directory = componentsDirectory(options);
|
|
109
|
+
const registry = options.registryDirectory ?? bundledRegistryDirectory();
|
|
110
|
+
return names.map((name) => {
|
|
111
|
+
const files = itemFiles[name].map((filename) => {
|
|
112
|
+
const path = join(directory, "anvia", filename);
|
|
113
|
+
let status = "missing";
|
|
114
|
+
if (existsSync(path)) {
|
|
115
|
+
const installed = readFileSync(path, "utf8");
|
|
116
|
+
status = installed === registryFileContent(registry, filename) ? "up-to-date" : "modified";
|
|
117
|
+
}
|
|
118
|
+
return { filename, path, status };
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
name,
|
|
122
|
+
installed: files.some((file) => file.status !== "missing"),
|
|
123
|
+
complete: files.every((file) => file.status !== "missing"),
|
|
124
|
+
files
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function updateInstalledItems(options = {}) {
|
|
129
|
+
const registry = options.registryDirectory ?? bundledRegistryDirectory();
|
|
130
|
+
const report = inspectInstalledItems(options);
|
|
131
|
+
const updated = [];
|
|
132
|
+
if (options.overwrite === true) {
|
|
133
|
+
const actionable = /* @__PURE__ */ new Map();
|
|
134
|
+
for (const item of report) {
|
|
135
|
+
if (!item.installed) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
for (const file of item.files) {
|
|
139
|
+
if (file.status === "up-to-date" || actionable.has(file.path)) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
actionable.set(file.path, registryFileContent(registry, file.filename));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const [path, content] of actionable) {
|
|
146
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
147
|
+
writeFileSync(path, content);
|
|
148
|
+
updated.push(path);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return { report, updated };
|
|
152
|
+
}
|
|
153
|
+
function closestRegistryItemName(value) {
|
|
154
|
+
let best;
|
|
155
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
156
|
+
for (const name of registryItemNames) {
|
|
157
|
+
const distance = levenshteinDistance(value, name);
|
|
158
|
+
if (distance < bestDistance) {
|
|
159
|
+
bestDistance = distance;
|
|
160
|
+
best = name;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return bestDistance <= 2 ? best : void 0;
|
|
164
|
+
}
|
|
165
|
+
function componentsDirectory(options = {}) {
|
|
166
|
+
const cwd = options.cwd ?? process.cwd();
|
|
167
|
+
const manifestPath = join(cwd, "components.json");
|
|
168
|
+
let manifest;
|
|
169
|
+
try {
|
|
170
|
+
manifest = JSON.parse(stripJsonComments(readFileSync(manifestPath, "utf8")));
|
|
171
|
+
} catch {
|
|
172
|
+
throw new Error(`Missing or unreadable components.json in ${cwd}. Run \`anvia init\` first.`);
|
|
173
|
+
}
|
|
174
|
+
const alias = manifest.aliases?.components ?? "@/components";
|
|
175
|
+
return resolveAliasDirectory(cwd, alias);
|
|
176
|
+
}
|
|
177
|
+
function resolveAliasDirectory(cwd, alias) {
|
|
178
|
+
for (const configName of ["tsconfig.json", "jsconfig.json"]) {
|
|
179
|
+
let config;
|
|
180
|
+
try {
|
|
181
|
+
config = JSON.parse(stripJsonComments(readFileSync(join(cwd, configName), "utf8")));
|
|
182
|
+
} catch {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const paths = config.compilerOptions?.paths;
|
|
186
|
+
if (paths === void 0) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const target = resolveAliasTarget(alias, paths);
|
|
190
|
+
if (target === void 0) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
return join(cwd, config.compilerOptions?.baseUrl ?? ".", target);
|
|
194
|
+
}
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Cannot resolve the components alias "${alias}" from tsconfig.json or jsconfig.json in ${cwd}.`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
function resolveAliasTarget(alias, paths) {
|
|
200
|
+
for (const [key, targets] of Object.entries(paths)) {
|
|
201
|
+
const target = targets[0];
|
|
202
|
+
if (target === void 0) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (key.endsWith("/*")) {
|
|
206
|
+
const prefix = key.slice(0, -2);
|
|
207
|
+
if (alias === prefix) {
|
|
208
|
+
return target.replace(/\/\*$/, "");
|
|
209
|
+
}
|
|
210
|
+
if (alias.startsWith(`${prefix}/`)) {
|
|
211
|
+
return join(target.replace(/\/\*$/, ""), alias.slice(prefix.length + 1));
|
|
212
|
+
}
|
|
213
|
+
} else if (key === alias) {
|
|
214
|
+
return target;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return void 0;
|
|
218
|
+
}
|
|
219
|
+
function stripJsonComments(source) {
|
|
220
|
+
let result = "";
|
|
221
|
+
let inString = false;
|
|
222
|
+
let escaped = false;
|
|
223
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
224
|
+
const character = source[index];
|
|
225
|
+
if (inString) {
|
|
226
|
+
result += character;
|
|
227
|
+
if (escaped) {
|
|
228
|
+
escaped = false;
|
|
229
|
+
} else if (character === "\\") {
|
|
230
|
+
escaped = true;
|
|
231
|
+
} else if (character === '"') {
|
|
232
|
+
inString = false;
|
|
233
|
+
}
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (character === '"') {
|
|
237
|
+
inString = true;
|
|
238
|
+
result += character;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (character === "/" && source[index + 1] === "/") {
|
|
242
|
+
while (index < source.length && source[index] !== "\n") {
|
|
243
|
+
index += 1;
|
|
244
|
+
}
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
result += character;
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
function registryFileContent(registryDirectory, filename) {
|
|
252
|
+
return readFileSync(join(registryDirectory, filename), "utf8");
|
|
253
|
+
}
|
|
254
|
+
function levenshteinDistance(left, right) {
|
|
255
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
256
|
+
for (let i = 1; i <= left.length; i += 1) {
|
|
257
|
+
let diagonal = previous[0];
|
|
258
|
+
previous[0] = i;
|
|
259
|
+
for (let j = 1; j <= right.length; j += 1) {
|
|
260
|
+
const above = previous[j];
|
|
261
|
+
previous[j] = Math.min(
|
|
262
|
+
above + 1,
|
|
263
|
+
previous[j - 1] + 1,
|
|
264
|
+
diagonal + (left[i - 1] === right[j - 1] ? 0 : 1)
|
|
265
|
+
);
|
|
266
|
+
diagonal = above;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return previous[right.length];
|
|
270
|
+
}
|
|
271
|
+
function runShadcn(args) {
|
|
272
|
+
const require2 = createRequire(import.meta.url);
|
|
273
|
+
const shadcnEntry = require2.resolve("shadcn");
|
|
274
|
+
const result = spawnSync(process.execPath, [shadcnEntry, ...args], {
|
|
275
|
+
encoding: "utf8",
|
|
276
|
+
stdio: "inherit"
|
|
277
|
+
});
|
|
278
|
+
if (result.error !== void 0) {
|
|
279
|
+
throw result.error;
|
|
280
|
+
}
|
|
281
|
+
if (result.status !== 0) {
|
|
282
|
+
throw new Error(`shadcn ${args[0] ?? "command"} failed with exit code ${result.status}.`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function bundledRegistryDirectory() {
|
|
286
|
+
return fileURLToPath(new URL("./registry/", import.meta.url));
|
|
287
|
+
}
|
|
288
|
+
function currentPackageVersion() {
|
|
289
|
+
const manifestPath = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
290
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
291
|
+
if (typeof manifest.version !== "string") {
|
|
292
|
+
throw new Error(`Missing package version in ${basename(manifestPath)}.`);
|
|
293
|
+
}
|
|
294
|
+
return manifest.version;
|
|
295
|
+
}
|
|
296
|
+
function registryItemDescription(name) {
|
|
297
|
+
if (name === "chat") {
|
|
298
|
+
return "A complete editable Anvia chat interface.";
|
|
299
|
+
}
|
|
300
|
+
return `Editable Anvia ${name} UI.`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/skills/discovery.ts
|
|
304
|
+
import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
305
|
+
import { join as join2 } from "path";
|
|
306
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
307
|
+
function bundledSkillsDirectory() {
|
|
308
|
+
return fileURLToPath2(new URL("./skills/", import.meta.url));
|
|
309
|
+
}
|
|
310
|
+
function skillNames(options = {}) {
|
|
311
|
+
const directory = options.skillsDirectory ?? bundledSkillsDirectory();
|
|
312
|
+
return readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
313
|
+
}
|
|
314
|
+
function skillsTargetDirectory(options = {}) {
|
|
315
|
+
const cwd = options.cwd ?? process.cwd();
|
|
316
|
+
return join2(cwd, options.dir ?? "skills");
|
|
317
|
+
}
|
|
318
|
+
function collectSkillFiles(root) {
|
|
319
|
+
const files = [];
|
|
320
|
+
const visit = (directory, prefix) => {
|
|
321
|
+
const entries = readdirSync(directory, { withFileTypes: true }).sort(
|
|
322
|
+
(left, right) => left.name.localeCompare(right.name)
|
|
323
|
+
);
|
|
324
|
+
for (const entry of entries) {
|
|
325
|
+
if (entry.isDirectory()) {
|
|
326
|
+
visit(join2(directory, entry.name), `${prefix}${entry.name}/`);
|
|
327
|
+
} else if (entry.isFile()) {
|
|
328
|
+
files.push(`${prefix}${entry.name}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
visit(root, "");
|
|
333
|
+
return files;
|
|
334
|
+
}
|
|
335
|
+
function skillDescription(skillsDirectory, name) {
|
|
336
|
+
const source = readFileSync2(join2(skillsDirectory, name, "SKILL.md"), "utf8");
|
|
337
|
+
return (/^description:\s*(.+)$/m.exec(source)?.[1] ?? "").trim();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/skills/write.ts
|
|
341
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
342
|
+
import { join as join6 } from "path";
|
|
343
|
+
|
|
344
|
+
// src/skills/adapters/agents-doc.ts
|
|
345
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
346
|
+
import { dirname as dirname2 } from "path";
|
|
347
|
+
var agentsSectionStart = "<!-- anvia-skills:start -->";
|
|
348
|
+
var agentsSectionEnd = "<!-- anvia-skills:end -->";
|
|
349
|
+
function syncAgentsDoc(options) {
|
|
350
|
+
const section = agentsSkillsSection(options.skillsDirectory, options.canonicalDir);
|
|
351
|
+
if (!existsSync2(options.agentsPath)) {
|
|
352
|
+
mkdirSync2(dirname2(options.agentsPath), { recursive: true });
|
|
353
|
+
writeFileSync2(options.agentsPath, `# AGENTS.md
|
|
354
|
+
|
|
355
|
+
${section}
|
|
356
|
+
`);
|
|
357
|
+
return { created: [options.agentsPath], updated: [], skipped: 0 };
|
|
358
|
+
}
|
|
359
|
+
const existing = readFileSync3(options.agentsPath, "utf8");
|
|
360
|
+
const startIndex = existing.indexOf(agentsSectionStart);
|
|
361
|
+
const endIndex = existing.indexOf(agentsSectionEnd);
|
|
362
|
+
let next;
|
|
363
|
+
if (startIndex === -1 || endIndex === -1) {
|
|
364
|
+
next = `${existing.replace(/\n+$/, "")}
|
|
365
|
+
|
|
366
|
+
${section}
|
|
367
|
+
`;
|
|
368
|
+
} else {
|
|
369
|
+
next = existing.slice(0, startIndex) + section + existing.slice(endIndex + agentsSectionEnd.length);
|
|
370
|
+
}
|
|
371
|
+
if (next === existing) {
|
|
372
|
+
return { created: [], updated: [], skipped: 0 };
|
|
373
|
+
}
|
|
374
|
+
writeFileSync2(options.agentsPath, next);
|
|
375
|
+
return { created: [], updated: [options.agentsPath], skipped: 0 };
|
|
376
|
+
}
|
|
377
|
+
function agentsSkillsSection(skillsDirectory, canonicalDir) {
|
|
378
|
+
const lines = skillNames({ skillsDirectory }).map(
|
|
379
|
+
(name) => `- \`${canonicalDir}/${name}/SKILL.md\` \u2014 ${skillDescription(skillsDirectory, name)}`
|
|
380
|
+
);
|
|
381
|
+
return [
|
|
382
|
+
agentsSectionStart,
|
|
383
|
+
"## Anvia Agent Skills",
|
|
384
|
+
"",
|
|
385
|
+
`This project keeps Anvia Agent Skills in \`${canonicalDir}/\`. When a task matches a skill, read`,
|
|
386
|
+
"its `SKILL.md` first and follow it, including the `references/` files and `scripts/`",
|
|
387
|
+
"it points to. Run skill scripts with `sh` when they help verify the work.",
|
|
388
|
+
"",
|
|
389
|
+
...lines,
|
|
390
|
+
agentsSectionEnd
|
|
391
|
+
].join("\n");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/skills/adapters/claude.ts
|
|
395
|
+
import { join as join4 } from "path";
|
|
396
|
+
|
|
397
|
+
// src/skills/adapters/skill-tree.ts
|
|
398
|
+
import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
399
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
400
|
+
function syncSkillTree(options) {
|
|
401
|
+
const created = [];
|
|
402
|
+
const updated = [];
|
|
403
|
+
let skipped = 0;
|
|
404
|
+
for (const name of skillNames({ skillsDirectory: options.skillsDirectory })) {
|
|
405
|
+
const sourceRoot = join3(options.skillsDirectory, name);
|
|
406
|
+
const targetRoot = join3(options.targetRoot, name);
|
|
407
|
+
const sourceFiles = collectSkillFiles(sourceRoot);
|
|
408
|
+
if (options.mode === "update" && !isSkillInstalled(targetRoot, sourceFiles)) {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
for (const relativePath of sourceFiles) {
|
|
412
|
+
const outcome = syncOneFile(sourceRoot, targetRoot, relativePath, options);
|
|
413
|
+
if (outcome === "created") {
|
|
414
|
+
created.push(join3(targetRoot, relativePath));
|
|
415
|
+
} else if (outcome === "updated") {
|
|
416
|
+
updated.push(join3(targetRoot, relativePath));
|
|
417
|
+
} else if (outcome === "skipped") {
|
|
418
|
+
skipped += 1;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return { created, updated, skipped };
|
|
423
|
+
}
|
|
424
|
+
function isSkillInstalled(targetRoot, sourceFiles) {
|
|
425
|
+
return sourceFiles.some((file) => existsSync3(join3(targetRoot, file)));
|
|
426
|
+
}
|
|
427
|
+
function syncOneFile(sourceRoot, targetRoot, relativePath, options) {
|
|
428
|
+
const sourcePath = join3(sourceRoot, relativePath);
|
|
429
|
+
const targetPath = join3(targetRoot, relativePath);
|
|
430
|
+
const content = readFileSync4(sourcePath, "utf8");
|
|
431
|
+
if (!existsSync3(targetPath)) {
|
|
432
|
+
if (options.mode === "update" && !options.force) {
|
|
433
|
+
return "skipped";
|
|
434
|
+
}
|
|
435
|
+
mkdirSync3(dirname3(targetPath), { recursive: true });
|
|
436
|
+
writeFileSync3(targetPath, content);
|
|
437
|
+
chmodSync(targetPath, statSync(sourcePath).mode & 511);
|
|
438
|
+
return "created";
|
|
439
|
+
}
|
|
440
|
+
if (readFileSync4(targetPath, "utf8") === content) {
|
|
441
|
+
return "unchanged";
|
|
442
|
+
}
|
|
443
|
+
if (!options.force) {
|
|
444
|
+
return "skipped";
|
|
445
|
+
}
|
|
446
|
+
writeFileSync3(targetPath, content);
|
|
447
|
+
chmodSync(targetPath, statSync(sourcePath).mode & 511);
|
|
448
|
+
return "updated";
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// src/skills/adapters/claude.ts
|
|
452
|
+
function syncClaudeSkills(options) {
|
|
453
|
+
return syncSkillTree({
|
|
454
|
+
skillsDirectory: options.skillsDirectory,
|
|
455
|
+
targetRoot: join4(options.cwd, ".claude", "skills"),
|
|
456
|
+
force: options.force,
|
|
457
|
+
mode: options.mode
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/skills/adapters/cursor-rules.ts
|
|
462
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
463
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
464
|
+
function syncCursorRules(options) {
|
|
465
|
+
const created = [];
|
|
466
|
+
const updated = [];
|
|
467
|
+
let skipped = 0;
|
|
468
|
+
for (const name of skillNames({ skillsDirectory: options.skillsDirectory })) {
|
|
469
|
+
const targetPath = join5(options.rulesDirectory, `${name}.mdc`);
|
|
470
|
+
const content = cursorRuleContent(options.skillsDirectory, name, options.canonicalDir);
|
|
471
|
+
if (!existsSync4(targetPath)) {
|
|
472
|
+
if (options.mode === "update" && !options.force) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
mkdirSync4(dirname4(targetPath), { recursive: true });
|
|
476
|
+
writeFileSync4(targetPath, content);
|
|
477
|
+
created.push(targetPath);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (readFileSync5(targetPath, "utf8") === content) {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
if (!options.force) {
|
|
484
|
+
skipped += 1;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
writeFileSync4(targetPath, content);
|
|
488
|
+
updated.push(targetPath);
|
|
489
|
+
}
|
|
490
|
+
return { created, updated, skipped };
|
|
491
|
+
}
|
|
492
|
+
function cursorRuleContent(skillsDirectory, name, canonicalDir) {
|
|
493
|
+
const description = skillDescription(skillsDirectory, name).replace(/"/g, '\\"');
|
|
494
|
+
return [
|
|
495
|
+
"---",
|
|
496
|
+
`description: "${description}"`,
|
|
497
|
+
"alwaysApply: false",
|
|
498
|
+
"---",
|
|
499
|
+
"",
|
|
500
|
+
`When a task matches this skill, read \`${canonicalDir}/${name}/SKILL.md\` at the project root and`,
|
|
501
|
+
"follow it, including the `references/` files and `scripts/` it points to. Run skill",
|
|
502
|
+
"scripts with `sh` when they help verify the work.",
|
|
503
|
+
""
|
|
504
|
+
].join("\n");
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/skills/write.ts
|
|
508
|
+
function inspectInstalledSkills(options = {}) {
|
|
509
|
+
const skillsDirectory = options.skillsDirectory ?? bundledSkillsDirectory();
|
|
510
|
+
const target = skillsTargetDirectory(options);
|
|
511
|
+
return skillNames({ skillsDirectory }).map((name) => {
|
|
512
|
+
const files = collectSkillFiles(join6(skillsDirectory, name)).map((relativePath) => {
|
|
513
|
+
const path = join6(target, name, relativePath);
|
|
514
|
+
let status = "missing";
|
|
515
|
+
if (existsSync5(path)) {
|
|
516
|
+
const installed = readFileSync6(path, "utf8");
|
|
517
|
+
const source = readFileSync6(join6(skillsDirectory, name, relativePath), "utf8");
|
|
518
|
+
status = installed === source ? "up-to-date" : "modified";
|
|
519
|
+
}
|
|
520
|
+
return { relativePath, path, status };
|
|
521
|
+
});
|
|
522
|
+
return {
|
|
523
|
+
name,
|
|
524
|
+
installed: files.some((file) => file.status !== "missing"),
|
|
525
|
+
complete: files.every((file) => file.status !== "missing"),
|
|
526
|
+
files
|
|
527
|
+
};
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
function initSkills(options = {}) {
|
|
531
|
+
return writeSkills({ ...options, mode: "init" });
|
|
532
|
+
}
|
|
533
|
+
function updateSkills(options = {}) {
|
|
534
|
+
return writeSkills({ ...options, mode: "update" });
|
|
535
|
+
}
|
|
536
|
+
function writeSkills(options) {
|
|
537
|
+
const skillsDirectory = options.skillsDirectory ?? bundledSkillsDirectory();
|
|
538
|
+
const cwd = options.cwd ?? process.cwd();
|
|
539
|
+
const force = options.force === true;
|
|
540
|
+
const canonicalDir = options.dir ?? "skills";
|
|
541
|
+
const targets = new Set(options.targets ?? ["anvia"]);
|
|
542
|
+
targets.add("anvia");
|
|
543
|
+
const results = [];
|
|
544
|
+
const anvia = syncSkillTree({
|
|
545
|
+
skillsDirectory,
|
|
546
|
+
targetRoot: skillsTargetDirectory(options),
|
|
547
|
+
force,
|
|
548
|
+
mode: options.mode
|
|
549
|
+
});
|
|
550
|
+
results.push({ target: "anvia", ...anvia });
|
|
551
|
+
if (targets.has("claude")) {
|
|
552
|
+
const claude = syncClaudeSkills({ cwd, skillsDirectory, force, mode: options.mode });
|
|
553
|
+
results.push({ target: "claude", ...claude });
|
|
554
|
+
}
|
|
555
|
+
if (targets.has("cursor")) {
|
|
556
|
+
const cursor = syncCursorRules({
|
|
557
|
+
skillsDirectory,
|
|
558
|
+
rulesDirectory: join6(cwd, ".cursor", "rules"),
|
|
559
|
+
canonicalDir,
|
|
560
|
+
force,
|
|
561
|
+
mode: options.mode
|
|
562
|
+
});
|
|
563
|
+
results.push({ target: "cursor", ...cursor });
|
|
564
|
+
}
|
|
565
|
+
if (targets.has("agents") || targets.has("codex")) {
|
|
566
|
+
const doc = syncAgentsDoc({
|
|
567
|
+
agentsPath: join6(cwd, "AGENTS.md"),
|
|
568
|
+
skillsDirectory,
|
|
569
|
+
canonicalDir
|
|
570
|
+
});
|
|
571
|
+
const target = targets.has("agents") ? "agents" : "codex";
|
|
572
|
+
results.push({ target, ...doc });
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
report: inspectInstalledSkills(options),
|
|
576
|
+
created: anvia.created,
|
|
577
|
+
updated: anvia.updated,
|
|
578
|
+
targets: results
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// src/skills/types.ts
|
|
583
|
+
var skillsTargetNames = ["anvia", "claude", "codex", "cursor", "agents"];
|
|
584
|
+
function isSkillsTarget(value) {
|
|
585
|
+
return skillsTargetNames.includes(value);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export {
|
|
589
|
+
registryItemNames,
|
|
590
|
+
createRegistryItem,
|
|
591
|
+
initializeProject,
|
|
592
|
+
addRegistryItem,
|
|
593
|
+
isRegistryItemName,
|
|
594
|
+
inspectInstalledItems,
|
|
595
|
+
updateInstalledItems,
|
|
596
|
+
closestRegistryItemName,
|
|
597
|
+
bundledSkillsDirectory,
|
|
598
|
+
skillNames,
|
|
599
|
+
skillsTargetDirectory,
|
|
600
|
+
collectSkillFiles,
|
|
601
|
+
inspectInstalledSkills,
|
|
602
|
+
initSkills,
|
|
603
|
+
updateSkills,
|
|
604
|
+
skillsTargetNames,
|
|
605
|
+
isSkillsTarget
|
|
606
|
+
};
|
|
607
|
+
//# sourceMappingURL=chunk-ERCP4EIZ.js.map
|