@audiodn/agent-kit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +74 -0
- package/assets/content/instructions.md +48 -0
- package/assets/content/partials/auth.md +17 -0
- package/assets/content/partials/compatibility.md +10 -0
- package/assets/content/partials/playback.md +14 -0
- package/assets/content/partials/processing.md +17 -0
- package/assets/content/partials/security.md +14 -0
- package/assets/content/partials/upload.md +17 -0
- package/assets/content/partials/webhooks.md +13 -0
- package/assets/skill/SKILL.md +52 -0
- package/assets/skill/references/authentication.md +27 -0
- package/assets/skill/references/playback.md +29 -0
- package/assets/skill/references/processing.md +23 -0
- package/assets/skill/references/security.md +30 -0
- package/assets/skill/references/upload-flow.md +29 -0
- package/assets/skill/references/webhooks.md +19 -0
- package/assets/skill/scripts/known-endpoints.json +28 -0
- package/assets/skill/scripts/validate.mjs +287 -0
- package/assets/skill/templates/cloudflare-worker.md +39 -0
- package/assets/skill/templates/nextjs.md +47 -0
- package/assets/skill/templates/node-server.md +44 -0
- package/assets/skill/templates/vue-nuxt.md +49 -0
- package/assets/snapshots/llms-full.txt +347 -0
- package/assets/snapshots/openapi.json +1416 -0
- package/assets/snapshots/sources.json +19 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +101 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.js +35 -0
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +28 -0
- package/dist/commands/uninstall.d.ts +6 -0
- package/dist/commands/uninstall.js +20 -0
- package/dist/commands/validate.d.ts +5 -0
- package/dist/commands/validate.js +18 -0
- package/dist/core/formats.d.ts +23 -0
- package/dist/core/formats.js +43 -0
- package/dist/core/fsutil.d.ts +5 -0
- package/dist/core/fsutil.js +34 -0
- package/dist/core/install.d.ts +16 -0
- package/dist/core/install.js +76 -0
- package/dist/core/markers.d.ts +5 -0
- package/dist/core/markers.js +13 -0
- package/dist/core/merge.d.ts +21 -0
- package/dist/core/merge.js +38 -0
- package/dist/core/prompt.d.ts +2 -0
- package/dist/core/prompt.js +23 -0
- package/dist/core/render.d.ts +5 -0
- package/dist/core/render.js +21 -0
- package/dist/core/report.d.ts +10 -0
- package/dist/core/report.js +35 -0
- package/dist/core/skill.d.ts +13 -0
- package/dist/core/skill.js +117 -0
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +10 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +12 -0
- package/package.json +61 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapiVersion": "1.0.0",
|
|
3
|
+
"generatedBy": "scripts/sync-canonical.mjs",
|
|
4
|
+
"note": "Seeded from the canonical monorepo openapi.json (ahead of the current public deploy). scripts/sync-canonical.mjs refreshes from https://audiodeliverynetwork.com once redeployed.",
|
|
5
|
+
"sources": [
|
|
6
|
+
{
|
|
7
|
+
"name": "openapi.json",
|
|
8
|
+
"url": "https://audiodeliverynetwork.com/openapi.json",
|
|
9
|
+
"fetchedAt": "2026-07-13",
|
|
10
|
+
"sha256": "e1554673a4d7287a15460bdc2761e0bbc38286b4e101df26b00fc5e9205e2deb"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "llms-full.txt",
|
|
14
|
+
"url": "https://audiodeliverynetwork.com/llms-full.txt",
|
|
15
|
+
"fetchedAt": "2026-07-13",
|
|
16
|
+
"sha256": "41980ad7654d997132683399b6a4ab38fce87944ebb9c5cf1779daea996d18af"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runInit } from './commands/init.js';
|
|
3
|
+
import { runList } from './commands/list.js';
|
|
4
|
+
import { runUninstall } from './commands/uninstall.js';
|
|
5
|
+
import { runValidate } from './commands/validate.js';
|
|
6
|
+
import { getVersion } from './version.js';
|
|
7
|
+
const VALUE_FLAGS = new Set(['formats', 'cwd', 'severity', 'path']);
|
|
8
|
+
function parseArgs(argv) {
|
|
9
|
+
const _ = [];
|
|
10
|
+
const flags = {};
|
|
11
|
+
for (let i = 0; i < argv.length; i++) {
|
|
12
|
+
const arg = argv[i];
|
|
13
|
+
if (arg.startsWith('--')) {
|
|
14
|
+
const eq = arg.indexOf('=');
|
|
15
|
+
if (eq !== -1) {
|
|
16
|
+
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const key = arg.slice(2);
|
|
20
|
+
if (VALUE_FLAGS.has(key) && i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
|
|
21
|
+
flags[key] = argv[++i];
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
flags[key] = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else if (arg.startsWith('-') && arg.length > 1) {
|
|
29
|
+
for (const ch of arg.slice(1))
|
|
30
|
+
flags[ch] = true;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
_.push(arg);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { _, flags };
|
|
37
|
+
}
|
|
38
|
+
const HELP = `@audiodn/agent-kit — AudioDN guidance for AI coding agents
|
|
39
|
+
|
|
40
|
+
Usage:
|
|
41
|
+
npx @audiodn/agent-kit <command> [options]
|
|
42
|
+
|
|
43
|
+
Commands:
|
|
44
|
+
init Install AudioDN guidance (merge-safe, idempotent)
|
|
45
|
+
validate [path] Scan a project for common AudioDN mistakes
|
|
46
|
+
uninstall Remove managed blocks / kit-owned files only
|
|
47
|
+
list Show formats and bundled documentation versions
|
|
48
|
+
|
|
49
|
+
Options:
|
|
50
|
+
-y, --yes Non-interactive; install all formats unless --formats given
|
|
51
|
+
--formats <ids> Comma-separated: agents,claude,copilot,cursor,skill (or "all")
|
|
52
|
+
--dry-run Report actions without writing
|
|
53
|
+
--force Overwrite unmanaged conflicts
|
|
54
|
+
--cwd <dir> Target directory (default: current directory)
|
|
55
|
+
--json (validate) Output findings as JSON
|
|
56
|
+
-v, --version Print version
|
|
57
|
+
-h, --help Show this help
|
|
58
|
+
`;
|
|
59
|
+
async function main() {
|
|
60
|
+
const { _, flags } = parseArgs(process.argv.slice(2));
|
|
61
|
+
const cmd = _[0];
|
|
62
|
+
const cwd = flags.cwd || process.cwd();
|
|
63
|
+
if (flags.version || flags.v) {
|
|
64
|
+
console.log(getVersion());
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
if (!cmd || flags.help || flags.h || cmd === 'help') {
|
|
68
|
+
console.log(HELP);
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
switch (cmd) {
|
|
72
|
+
case 'init':
|
|
73
|
+
return runInit({
|
|
74
|
+
cwd,
|
|
75
|
+
yes: Boolean(flags.yes || flags.y),
|
|
76
|
+
dryRun: Boolean(flags['dry-run']),
|
|
77
|
+
force: Boolean(flags.force),
|
|
78
|
+
formats: typeof flags.formats === 'string' ? flags.formats : undefined,
|
|
79
|
+
});
|
|
80
|
+
case 'validate':
|
|
81
|
+
return runValidate({ path: _[1] || cwd, json: Boolean(flags.json) });
|
|
82
|
+
case 'uninstall':
|
|
83
|
+
return runUninstall({
|
|
84
|
+
cwd,
|
|
85
|
+
dryRun: Boolean(flags['dry-run']),
|
|
86
|
+
formats: typeof flags.formats === 'string' ? flags.formats : undefined,
|
|
87
|
+
});
|
|
88
|
+
case 'list':
|
|
89
|
+
return runList();
|
|
90
|
+
default:
|
|
91
|
+
console.error(`Unknown command: ${cmd}\n`);
|
|
92
|
+
console.log(HELP);
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
main()
|
|
97
|
+
.then((code) => process.exit(code))
|
|
98
|
+
.catch((err) => {
|
|
99
|
+
console.error(err instanceof Error ? err.message : err);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ALL_FORMAT_IDS, parseFormatList } from '../core/formats.js';
|
|
2
|
+
import { installFormats } from '../core/install.js';
|
|
3
|
+
import { promptFormats } from '../core/prompt.js';
|
|
4
|
+
import { hasConflicts, printReport } from '../core/report.js';
|
|
5
|
+
import { getVersion } from '../version.js';
|
|
6
|
+
export async function runInit(args) {
|
|
7
|
+
let ids;
|
|
8
|
+
if (args.formats) {
|
|
9
|
+
const { ids: parsed, unknown } = parseFormatList(args.formats);
|
|
10
|
+
if (unknown.length) {
|
|
11
|
+
console.error(`Unknown format(s): ${unknown.join(', ')}. Valid: ${ALL_FORMAT_IDS.join(', ')}, all`);
|
|
12
|
+
return 1;
|
|
13
|
+
}
|
|
14
|
+
ids = parsed.length ? parsed : [...ALL_FORMAT_IDS];
|
|
15
|
+
}
|
|
16
|
+
else if (args.yes) {
|
|
17
|
+
ids = [...ALL_FORMAT_IDS];
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
ids = await promptFormats();
|
|
21
|
+
}
|
|
22
|
+
if (ids.length === 0) {
|
|
23
|
+
console.log('No formats selected. Nothing to do.');
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
const entries = installFormats({
|
|
27
|
+
cwd: args.cwd,
|
|
28
|
+
version: getVersion(),
|
|
29
|
+
formats: ids,
|
|
30
|
+
dryRun: args.dryRun,
|
|
31
|
+
force: args.force,
|
|
32
|
+
});
|
|
33
|
+
printReport(entries, { dryRun: args.dryRun });
|
|
34
|
+
return hasConflicts(entries) && !args.force ? 2 : 0;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runList(): number;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { FORMATS } from '../core/formats.js';
|
|
3
|
+
import { readIfExists } from '../core/fsutil.js';
|
|
4
|
+
import { SNAPSHOTS_DIR } from '../paths.js';
|
|
5
|
+
import { getVersion } from '../version.js';
|
|
6
|
+
export function runList() {
|
|
7
|
+
console.log(`@audiodn/agent-kit v${getVersion()}`);
|
|
8
|
+
console.log('\nFormats:');
|
|
9
|
+
for (const f of FORMATS) {
|
|
10
|
+
const target = f.kind === 'skill' ? `${f.target}/` : f.target;
|
|
11
|
+
console.log(` ${f.id.padEnd(8)} -> ${target} (${f.label})`);
|
|
12
|
+
}
|
|
13
|
+
const raw = readIfExists(join(SNAPSHOTS_DIR, 'sources.json'));
|
|
14
|
+
if (raw) {
|
|
15
|
+
try {
|
|
16
|
+
const s = JSON.parse(raw);
|
|
17
|
+
console.log('\nBundled canonical snapshots:');
|
|
18
|
+
console.log(` OpenAPI version: ${s.openapiVersion ?? 'unknown'}`);
|
|
19
|
+
for (const src of s.sources ?? []) {
|
|
20
|
+
console.log(` - ${src.name} (${src.url}) fetched ${src.fetchedAt ?? 'unknown'}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
/* ignore malformed snapshot manifest */
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ALL_FORMAT_IDS, parseFormatList } from '../core/formats.js';
|
|
2
|
+
import { uninstallFormats } from '../core/install.js';
|
|
3
|
+
import { printReport } from '../core/report.js';
|
|
4
|
+
export function runUninstall(args) {
|
|
5
|
+
let ids;
|
|
6
|
+
if (args.formats) {
|
|
7
|
+
const { ids: parsed, unknown } = parseFormatList(args.formats);
|
|
8
|
+
if (unknown.length) {
|
|
9
|
+
console.error(`Unknown format(s): ${unknown.join(', ')}.`);
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
ids = parsed.length ? parsed : [...ALL_FORMAT_IDS];
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
ids = [...ALL_FORMAT_IDS];
|
|
16
|
+
}
|
|
17
|
+
const entries = uninstallFormats({ cwd: args.cwd, formats: ids, dryRun: args.dryRun });
|
|
18
|
+
printReport(entries, { dryRun: args.dryRun });
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { join, resolve } from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { SKILL_DIR } from '../paths.js';
|
|
4
|
+
// The validator implementation lives in the portable Skill payload so it can be
|
|
5
|
+
// run standalone (without the kit) once installed. We import it here to avoid
|
|
6
|
+
// duplicating the rule logic.
|
|
7
|
+
export async function runValidate(args) {
|
|
8
|
+
const validatorUrl = pathToFileURL(join(SKILL_DIR, 'scripts', 'validate.mjs')).href;
|
|
9
|
+
const mod = await import(validatorUrl);
|
|
10
|
+
const result = await mod.runValidation(resolve(args.path), {});
|
|
11
|
+
if (args.json) {
|
|
12
|
+
console.log(JSON.stringify(result, null, 2));
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
mod.printFindings(result);
|
|
16
|
+
}
|
|
17
|
+
return result.errorCount > 0 ? 1 : 0;
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type FormatId = 'agents' | 'claude' | 'copilot' | 'cursor' | 'skill';
|
|
2
|
+
export interface BlockFormat {
|
|
3
|
+
id: Exclude<FormatId, 'skill'>;
|
|
4
|
+
label: string;
|
|
5
|
+
kind: 'block';
|
|
6
|
+
target: string;
|
|
7
|
+
appendIfUnmanaged: boolean;
|
|
8
|
+
preambleOnCreate?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SkillFormat {
|
|
11
|
+
id: 'skill';
|
|
12
|
+
label: string;
|
|
13
|
+
kind: 'skill';
|
|
14
|
+
target: string;
|
|
15
|
+
}
|
|
16
|
+
export type Format = BlockFormat | SkillFormat;
|
|
17
|
+
export declare const FORMATS: Format[];
|
|
18
|
+
export declare const ALL_FORMAT_IDS: FormatId[];
|
|
19
|
+
export declare function resolveFormats(ids: FormatId[]): Format[];
|
|
20
|
+
export declare function parseFormatList(value: string): {
|
|
21
|
+
ids: FormatId[];
|
|
22
|
+
unknown: string[];
|
|
23
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const CURSOR_FRONTMATTER = `---
|
|
2
|
+
description: AudioDN integration guidance for AI coding agents (auth, upload flow, processing readiness, playback, security).
|
|
3
|
+
globs:
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---`;
|
|
6
|
+
export const FORMATS = [
|
|
7
|
+
{ id: 'agents', label: 'AGENTS.md', kind: 'block', target: 'AGENTS.md', appendIfUnmanaged: true },
|
|
8
|
+
{ id: 'claude', label: 'CLAUDE.md', kind: 'block', target: 'CLAUDE.md', appendIfUnmanaged: true },
|
|
9
|
+
{
|
|
10
|
+
id: 'copilot',
|
|
11
|
+
label: 'GitHub Copilot',
|
|
12
|
+
kind: 'block',
|
|
13
|
+
target: '.github/copilot-instructions.md',
|
|
14
|
+
appendIfUnmanaged: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'cursor',
|
|
18
|
+
label: 'Cursor rule',
|
|
19
|
+
kind: 'block',
|
|
20
|
+
target: '.cursor/rules/audiodn.mdc',
|
|
21
|
+
appendIfUnmanaged: false,
|
|
22
|
+
preambleOnCreate: CURSOR_FRONTMATTER,
|
|
23
|
+
},
|
|
24
|
+
{ id: 'skill', label: 'AudioDN Skill', kind: 'skill', target: '.agents/skills/audiodn' },
|
|
25
|
+
];
|
|
26
|
+
export const ALL_FORMAT_IDS = FORMATS.map((f) => f.id);
|
|
27
|
+
export function resolveFormats(ids) {
|
|
28
|
+
return FORMATS.filter((f) => ids.includes(f.id));
|
|
29
|
+
}
|
|
30
|
+
export function parseFormatList(value) {
|
|
31
|
+
const ids = [];
|
|
32
|
+
const unknown = [];
|
|
33
|
+
for (const raw of value.split(',').map((s) => s.trim()).filter(Boolean)) {
|
|
34
|
+
if (raw === 'all') {
|
|
35
|
+
return { ids: [...ALL_FORMAT_IDS], unknown: [] };
|
|
36
|
+
}
|
|
37
|
+
if (ALL_FORMAT_IDS.includes(raw))
|
|
38
|
+
ids.push(raw);
|
|
39
|
+
else
|
|
40
|
+
unknown.push(raw);
|
|
41
|
+
}
|
|
42
|
+
return { ids, unknown };
|
|
43
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function sha256(input: string | Buffer): string;
|
|
2
|
+
export declare function readIfExists(path: string): string | null;
|
|
3
|
+
export declare function writeFileEnsuringDir(path: string, content: string): void;
|
|
4
|
+
export declare function listFilesRecursive(root: string): string[];
|
|
5
|
+
export declare function relativeFiles(root: string): string[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, relative } from 'node:path';
|
|
4
|
+
export function sha256(input) {
|
|
5
|
+
return createHash('sha256').update(input).digest('hex');
|
|
6
|
+
}
|
|
7
|
+
export function readIfExists(path) {
|
|
8
|
+
try {
|
|
9
|
+
return readFileSync(path, 'utf8');
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function writeFileEnsuringDir(path, content) {
|
|
16
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
17
|
+
writeFileSync(path, content, 'utf8');
|
|
18
|
+
}
|
|
19
|
+
export function listFilesRecursive(root) {
|
|
20
|
+
const out = [];
|
|
21
|
+
if (!existsSync(root))
|
|
22
|
+
return out;
|
|
23
|
+
for (const name of readdirSync(root)) {
|
|
24
|
+
const full = join(root, name);
|
|
25
|
+
if (statSync(full).isDirectory())
|
|
26
|
+
out.push(...listFilesRecursive(full));
|
|
27
|
+
else
|
|
28
|
+
out.push(full);
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
export function relativeFiles(root) {
|
|
33
|
+
return listFilesRecursive(root).map((f) => relative(root, f).split('\\').join('/'));
|
|
34
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FormatId } from './formats.js';
|
|
2
|
+
import type { ReportEntry } from './report.js';
|
|
3
|
+
export interface InstallOptions {
|
|
4
|
+
cwd: string;
|
|
5
|
+
version: string;
|
|
6
|
+
formats: FormatId[];
|
|
7
|
+
dryRun?: boolean;
|
|
8
|
+
force?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function installFormats(opts: InstallOptions): ReportEntry[];
|
|
11
|
+
export interface UninstallOptions {
|
|
12
|
+
cwd: string;
|
|
13
|
+
formats: FormatId[];
|
|
14
|
+
dryRun?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function uninstallFormats(opts: UninstallOptions): ReportEntry[];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { rmSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { FORMATS } from './formats.js';
|
|
4
|
+
import { readIfExists, writeFileEnsuringDir } from './fsutil.js';
|
|
5
|
+
import { mergeBlock, removeBlock } from './merge.js';
|
|
6
|
+
import { buildGuidanceBody } from './render.js';
|
|
7
|
+
import { installSkill, uninstallSkill } from './skill.js';
|
|
8
|
+
export function installFormats(opts) {
|
|
9
|
+
const body = buildGuidanceBody();
|
|
10
|
+
const entries = [];
|
|
11
|
+
const selected = FORMATS.filter((f) => opts.formats.includes(f.id));
|
|
12
|
+
for (const fmt of selected) {
|
|
13
|
+
if (fmt.kind === 'skill') {
|
|
14
|
+
entries.push(...installSkill(opts.cwd, opts.version, { dryRun: opts.dryRun, force: opts.force }));
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const target = join(opts.cwd, fmt.target);
|
|
18
|
+
const existing = readIfExists(target);
|
|
19
|
+
const result = mergeBlock(existing, opts.version, body, {
|
|
20
|
+
preambleOnCreate: fmt.preambleOnCreate,
|
|
21
|
+
appendIfUnmanaged: fmt.appendIfUnmanaged,
|
|
22
|
+
});
|
|
23
|
+
if (result.action === 'conflict') {
|
|
24
|
+
const fresh = mergeBlock(null, opts.version, body, {
|
|
25
|
+
preambleOnCreate: fmt.preambleOnCreate,
|
|
26
|
+
appendIfUnmanaged: fmt.appendIfUnmanaged,
|
|
27
|
+
}).content;
|
|
28
|
+
if (opts.force) {
|
|
29
|
+
if (!opts.dryRun)
|
|
30
|
+
writeFileEnsuringDir(target, fresh);
|
|
31
|
+
entries.push({ path: fmt.target, action: 'update', note: 'forced overwrite' });
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
if (!opts.dryRun)
|
|
35
|
+
writeFileEnsuringDir(`${target}.audiodn.new`, fresh);
|
|
36
|
+
entries.push({ path: fmt.target, action: 'conflict', note: 'wrote .audiodn.new' });
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (result.content !== null && result.action !== 'unchanged' && !opts.dryRun) {
|
|
41
|
+
writeFileEnsuringDir(target, result.content);
|
|
42
|
+
}
|
|
43
|
+
entries.push({ path: fmt.target, action: result.action });
|
|
44
|
+
}
|
|
45
|
+
return entries;
|
|
46
|
+
}
|
|
47
|
+
export function uninstallFormats(opts) {
|
|
48
|
+
const entries = [];
|
|
49
|
+
const selected = FORMATS.filter((f) => opts.formats.includes(f.id));
|
|
50
|
+
for (const fmt of selected) {
|
|
51
|
+
if (fmt.kind === 'skill') {
|
|
52
|
+
entries.push(...uninstallSkill(opts.cwd, { dryRun: opts.dryRun }));
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const target = join(opts.cwd, fmt.target);
|
|
56
|
+
const existing = readIfExists(target);
|
|
57
|
+
if (existing === null)
|
|
58
|
+
continue;
|
|
59
|
+
const { content, changed } = removeBlock(existing);
|
|
60
|
+
if (!changed) {
|
|
61
|
+
entries.push({ path: fmt.target, action: 'skipped', note: 'no managed block' });
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (content.trim() === '') {
|
|
65
|
+
if (!opts.dryRun)
|
|
66
|
+
rmSync(target);
|
|
67
|
+
entries.push({ path: fmt.target, action: 'removed' });
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
if (!opts.dryRun)
|
|
71
|
+
writeFileEnsuringDir(target, content);
|
|
72
|
+
entries.push({ path: fmt.target, action: 'removed', note: 'kept your content' });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return entries;
|
|
76
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const END_MARKER = "<!-- AUDIODN:END -->";
|
|
2
|
+
export declare const BLOCK_RE: RegExp;
|
|
3
|
+
export declare function beginMarker(version: string): string;
|
|
4
|
+
export declare function buildBlock(version: string, body: string): string;
|
|
5
|
+
export declare function hasBlock(content: string): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const END_MARKER = '<!-- AUDIODN:END -->';
|
|
2
|
+
// Matches a managed block regardless of the version recorded in the BEGIN marker.
|
|
3
|
+
export const BLOCK_RE = /<!-- AUDIODN:BEGIN[\s\S]*?<!-- AUDIODN:END -->/;
|
|
4
|
+
export function beginMarker(version) {
|
|
5
|
+
return `<!-- AUDIODN:BEGIN v${version} (managed by @audiodn/agent-kit — do not edit inside) -->`;
|
|
6
|
+
}
|
|
7
|
+
export function buildBlock(version, body) {
|
|
8
|
+
return `${beginMarker(version)}\n${body.trim()}\n${END_MARKER}`;
|
|
9
|
+
}
|
|
10
|
+
export function hasBlock(content) {
|
|
11
|
+
BLOCK_RE.lastIndex = 0;
|
|
12
|
+
return BLOCK_RE.test(content);
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type MergeAction = 'create' | 'update' | 'unchanged' | 'append' | 'conflict';
|
|
2
|
+
export interface MergeResult {
|
|
3
|
+
content: string | null;
|
|
4
|
+
action: MergeAction;
|
|
5
|
+
}
|
|
6
|
+
export interface MergeOptions {
|
|
7
|
+
preambleOnCreate?: string;
|
|
8
|
+
appendIfUnmanaged: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Merge a managed block into (possibly existing) file content without ever
|
|
12
|
+
* clobbering user content outside the block.
|
|
13
|
+
*/
|
|
14
|
+
export declare function mergeBlock(existing: string | null, version: string, body: string, opts: MergeOptions): MergeResult;
|
|
15
|
+
/**
|
|
16
|
+
* Remove the managed block. Returns changed=false if there was no block.
|
|
17
|
+
*/
|
|
18
|
+
export declare function removeBlock(existing: string): {
|
|
19
|
+
content: string;
|
|
20
|
+
changed: boolean;
|
|
21
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { BLOCK_RE, buildBlock, hasBlock } from './markers.js';
|
|
2
|
+
/**
|
|
3
|
+
* Merge a managed block into (possibly existing) file content without ever
|
|
4
|
+
* clobbering user content outside the block.
|
|
5
|
+
*/
|
|
6
|
+
export function mergeBlock(existing, version, body, opts) {
|
|
7
|
+
const block = buildBlock(version, body);
|
|
8
|
+
if (existing === null) {
|
|
9
|
+
const preamble = opts.preambleOnCreate ? `${opts.preambleOnCreate.replace(/\n*$/, '')}\n\n` : '';
|
|
10
|
+
return { content: `${preamble}${block}\n`, action: 'create' };
|
|
11
|
+
}
|
|
12
|
+
if (hasBlock(existing)) {
|
|
13
|
+
const replaced = existing.replace(BLOCK_RE, block);
|
|
14
|
+
if (replaced === existing)
|
|
15
|
+
return { content: existing, action: 'unchanged' };
|
|
16
|
+
return { content: replaced, action: 'update' };
|
|
17
|
+
}
|
|
18
|
+
if (opts.appendIfUnmanaged) {
|
|
19
|
+
const sep = existing.endsWith('\n') ? '\n' : '\n\n';
|
|
20
|
+
return { content: `${existing}${sep}${block}\n`, action: 'append' };
|
|
21
|
+
}
|
|
22
|
+
return { content: null, action: 'conflict' };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Remove the managed block. Returns changed=false if there was no block.
|
|
26
|
+
*/
|
|
27
|
+
export function removeBlock(existing) {
|
|
28
|
+
if (!hasBlock(existing))
|
|
29
|
+
return { content: existing, changed: false };
|
|
30
|
+
let out = existing.replace(BLOCK_RE, '');
|
|
31
|
+
// Tidy the whitespace left behind.
|
|
32
|
+
out = out.replace(/\n{3,}/g, '\n\n').replace(/^\s+/, '');
|
|
33
|
+
if (out.trim().length > 0)
|
|
34
|
+
out = out.replace(/\s+$/, '') + '\n';
|
|
35
|
+
else
|
|
36
|
+
out = '';
|
|
37
|
+
return { content: out, changed: true };
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { ALL_FORMAT_IDS, FORMATS } from './formats.js';
|
|
3
|
+
export async function promptFormats() {
|
|
4
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
5
|
+
const question = (q) => new Promise((res) => rl.question(q, res));
|
|
6
|
+
console.log('AudioDN Agent Kit — select formats to install:');
|
|
7
|
+
FORMATS.forEach((f, i) => console.log(` ${i + 1}. ${f.id.padEnd(8)} ${f.label}`));
|
|
8
|
+
const ans = (await question('Enter comma-separated ids or numbers [all]: ')).trim();
|
|
9
|
+
rl.close();
|
|
10
|
+
if (!ans || ans.toLowerCase() === 'all')
|
|
11
|
+
return [...ALL_FORMAT_IDS];
|
|
12
|
+
const ids = [];
|
|
13
|
+
for (const token of ans.split(',').map((s) => s.trim()).filter(Boolean)) {
|
|
14
|
+
const num = Number(token);
|
|
15
|
+
if (Number.isInteger(num) && num >= 1 && num <= FORMATS.length) {
|
|
16
|
+
ids.push(FORMATS[num - 1].id);
|
|
17
|
+
}
|
|
18
|
+
else if (ALL_FORMAT_IDS.includes(token)) {
|
|
19
|
+
ids.push(token);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return [...new Set(ids)];
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { CONTENT_DIR } from '../paths.js';
|
|
4
|
+
const PARTIAL_ORDER = [
|
|
5
|
+
'auth',
|
|
6
|
+
'upload',
|
|
7
|
+
'processing',
|
|
8
|
+
'playback',
|
|
9
|
+
'webhooks',
|
|
10
|
+
'security',
|
|
11
|
+
'compatibility',
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Build the canonical guidance body shared by all block-format outputs. Single
|
|
15
|
+
* source of truth: assets/content/instructions.md with {{PARTIALS}} expanded.
|
|
16
|
+
*/
|
|
17
|
+
export function buildGuidanceBody() {
|
|
18
|
+
const instructions = readFileSync(join(CONTENT_DIR, 'instructions.md'), 'utf8');
|
|
19
|
+
const partials = PARTIAL_ORDER.map((name) => readFileSync(join(CONTENT_DIR, 'partials', `${name}.md`), 'utf8').trim()).join('\n\n');
|
|
20
|
+
return instructions.replace('{{PARTIALS}}', partials).trim();
|
|
21
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Action = 'create' | 'update' | 'unchanged' | 'append' | 'conflict' | 'removed' | 'skipped';
|
|
2
|
+
export interface ReportEntry {
|
|
3
|
+
path: string;
|
|
4
|
+
action: Action;
|
|
5
|
+
note?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function printReport(entries: ReportEntry[], opts?: {
|
|
8
|
+
dryRun?: boolean;
|
|
9
|
+
}): void;
|
|
10
|
+
export declare function hasConflicts(entries: ReportEntry[]): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const SYMBOL = {
|
|
2
|
+
create: '+ created ',
|
|
3
|
+
update: '~ updated ',
|
|
4
|
+
append: '~ appended',
|
|
5
|
+
unchanged: '= unchanged',
|
|
6
|
+
conflict: '! conflict',
|
|
7
|
+
removed: '- removed ',
|
|
8
|
+
skipped: '. skipped ',
|
|
9
|
+
};
|
|
10
|
+
export function printReport(entries, opts = {}) {
|
|
11
|
+
const prefix = opts.dryRun ? '[dry-run] ' : '';
|
|
12
|
+
if (entries.length === 0) {
|
|
13
|
+
console.log(`${prefix}Nothing to do.`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
for (const e of entries) {
|
|
17
|
+
const label = SYMBOL[e.action] ?? e.action;
|
|
18
|
+
const note = e.note ? ` (${e.note})` : '';
|
|
19
|
+
console.log(`${prefix}${label} ${e.path}${note}`);
|
|
20
|
+
}
|
|
21
|
+
const counts = entries.reduce((acc, e) => {
|
|
22
|
+
acc[e.action] = (acc[e.action] ?? 0) + 1;
|
|
23
|
+
return acc;
|
|
24
|
+
}, {});
|
|
25
|
+
const summary = Object.entries(counts)
|
|
26
|
+
.map(([k, v]) => `${v} ${k}`)
|
|
27
|
+
.join(', ');
|
|
28
|
+
console.log(`${prefix}Summary: ${summary}`);
|
|
29
|
+
if (counts.conflict) {
|
|
30
|
+
console.log(`${prefix}Conflicts were written as *.audiodn.new — review and merge, or re-run with --force.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function hasConflicts(entries) {
|
|
34
|
+
return entries.some((e) => e.action === 'conflict');
|
|
35
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReportEntry } from './report.js';
|
|
2
|
+
export declare const SKILL_TARGET_DIR = ".agents/skills/audiodn";
|
|
3
|
+
interface SkillOptions {
|
|
4
|
+
dryRun?: boolean;
|
|
5
|
+
force?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Install the Skill payload directory. Kit-owned files are tracked in a
|
|
9
|
+
* manifest so re-runs are idempotent and user edits are never clobbered.
|
|
10
|
+
*/
|
|
11
|
+
export declare function installSkill(cwd: string, version: string, opts?: SkillOptions): ReportEntry[];
|
|
12
|
+
export declare function uninstallSkill(cwd: string, opts?: SkillOptions): ReportEntry[];
|
|
13
|
+
export {};
|