@anaclumos/taal 1.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/.claude/settings.json +15 -0
- package/.github/workflows/ci.yml +28 -0
- package/.github/workflows/publish.yml +42 -0
- package/AGENTS.md +123 -0
- package/README.md +568 -0
- package/biome.jsonc +24 -0
- package/lefthook.yml +12 -0
- package/package.json +52 -0
- package/src/commands/collect.ts +172 -0
- package/src/commands/diff.ts +127 -0
- package/src/commands/init.ts +66 -0
- package/src/commands/list.ts +80 -0
- package/src/commands/providers.ts +46 -0
- package/src/commands/sync.ts +111 -0
- package/src/commands/validate.ts +17 -0
- package/src/config/env.ts +49 -0
- package/src/config/loader.ts +88 -0
- package/src/config/parser.ts +44 -0
- package/src/config/schema.ts +67 -0
- package/src/errors/index.ts +43 -0
- package/src/index.ts +301 -0
- package/src/providers/antigravity.ts +24 -0
- package/src/providers/base.ts +70 -0
- package/src/providers/claude-code.ts +12 -0
- package/src/providers/claude-desktop.ts +19 -0
- package/src/providers/codex.ts +61 -0
- package/src/providers/continue.ts +62 -0
- package/src/providers/cursor.ts +25 -0
- package/src/providers/index.ts +34 -0
- package/src/providers/opencode.ts +42 -0
- package/src/providers/registry.ts +74 -0
- package/src/providers/types.ts +99 -0
- package/src/providers/utils.ts +106 -0
- package/src/providers/windsurf.ts +50 -0
- package/src/providers/zed.ts +35 -0
- package/src/scripts/generate-schema.ts +17 -0
- package/src/skills/copy.ts +58 -0
- package/src/skills/discovery.ts +87 -0
- package/src/skills/validator.ts +95 -0
- package/src/utils/atomic-write.ts +35 -0
- package/src/utils/backup.ts +27 -0
- package/taal.schema.json +91 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { validateSkill } from "./validator.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents a discovered skill
|
|
8
|
+
*/
|
|
9
|
+
export interface Skill {
|
|
10
|
+
name: string;
|
|
11
|
+
path: string;
|
|
12
|
+
skillMdPath: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Expand ~ to home directory in a path
|
|
17
|
+
*/
|
|
18
|
+
function expandPath(path: string, home: string): string {
|
|
19
|
+
if (path.startsWith("~/")) {
|
|
20
|
+
return join(home, path.slice(2));
|
|
21
|
+
}
|
|
22
|
+
if (path === "~") {
|
|
23
|
+
return home;
|
|
24
|
+
}
|
|
25
|
+
return path;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Discover all valid skills from given paths
|
|
30
|
+
*
|
|
31
|
+
* @param paths - Array of paths to search for skills
|
|
32
|
+
* @param baseDir - Optional base directory for ~ expansion (defaults to homedir())
|
|
33
|
+
* @returns Array of discovered skills
|
|
34
|
+
*/
|
|
35
|
+
export function discoverSkills(paths: string[], baseDir?: string): Skill[] {
|
|
36
|
+
const skills: Skill[] = [];
|
|
37
|
+
const home = baseDir || homedir();
|
|
38
|
+
|
|
39
|
+
for (const rawPath of paths) {
|
|
40
|
+
const basePath = expandPath(rawPath, home);
|
|
41
|
+
if (!existsSync(basePath)) {
|
|
42
|
+
console.warn(`Skills path does not exist: ${basePath}`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const entries = readdirSync(basePath).sort();
|
|
48
|
+
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
const skillPath = join(basePath, entry);
|
|
51
|
+
|
|
52
|
+
// Skip if not a directory
|
|
53
|
+
try {
|
|
54
|
+
const stat = statSync(skillPath);
|
|
55
|
+
if (!stat.isDirectory()) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check if SKILL.md exists
|
|
63
|
+
const skillMdPath = join(skillPath, "SKILL.md");
|
|
64
|
+
if (!existsSync(skillMdPath)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Validate skill
|
|
69
|
+
const validation = validateSkill(skillPath);
|
|
70
|
+
if (!validation.valid) {
|
|
71
|
+
console.warn(`Invalid skill at ${skillPath}: ${validation.error}`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
skills.push({
|
|
76
|
+
name: validation.name!,
|
|
77
|
+
path: skillPath,
|
|
78
|
+
skillMdPath,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.warn(`Failed to read skills directory ${basePath}: ${error}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return skills;
|
|
87
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
|
|
5
|
+
interface SkillFrontmatter {
|
|
6
|
+
name?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
version?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ValidationResult {
|
|
12
|
+
valid: boolean;
|
|
13
|
+
error?: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validate a skill directory
|
|
19
|
+
* Checks that SKILL.md exists and has valid YAML frontmatter with 'name' field
|
|
20
|
+
*
|
|
21
|
+
* @param skillPath - Path to the skill directory
|
|
22
|
+
* @returns Validation result
|
|
23
|
+
*/
|
|
24
|
+
export function validateSkill(skillPath: string): ValidationResult {
|
|
25
|
+
const skillMdPath = join(skillPath, "SKILL.md");
|
|
26
|
+
|
|
27
|
+
// Check if SKILL.md exists
|
|
28
|
+
if (!existsSync(skillMdPath)) {
|
|
29
|
+
return {
|
|
30
|
+
valid: false,
|
|
31
|
+
error: "SKILL.md not found",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Read file content
|
|
36
|
+
let content: string;
|
|
37
|
+
try {
|
|
38
|
+
content = readFileSync(skillMdPath, "utf-8");
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return {
|
|
41
|
+
valid: false,
|
|
42
|
+
error: `Failed to read SKILL.md: ${error}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check for YAML frontmatter
|
|
47
|
+
if (!content.startsWith("---\n")) {
|
|
48
|
+
return {
|
|
49
|
+
valid: false,
|
|
50
|
+
error: "SKILL.md must start with YAML frontmatter (---)",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Extract frontmatter
|
|
55
|
+
const frontmatterEnd = content.indexOf("\n---\n", 4);
|
|
56
|
+
if (frontmatterEnd === -1) {
|
|
57
|
+
return {
|
|
58
|
+
valid: false,
|
|
59
|
+
error: "SKILL.md frontmatter not properly closed (missing closing ---)",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const frontmatterContent = content.substring(4, frontmatterEnd);
|
|
64
|
+
|
|
65
|
+
let parsed: unknown;
|
|
66
|
+
try {
|
|
67
|
+
parsed = parseYaml(frontmatterContent);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
valid: false,
|
|
71
|
+
error: `Invalid YAML frontmatter: ${error}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!parsed || typeof parsed !== "object") {
|
|
76
|
+
return {
|
|
77
|
+
valid: false,
|
|
78
|
+
error: "Frontmatter must be a YAML object",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const frontmatter = parsed as SkillFrontmatter;
|
|
83
|
+
|
|
84
|
+
if (!frontmatter.name || typeof frontmatter.name !== "string") {
|
|
85
|
+
return {
|
|
86
|
+
valid: false,
|
|
87
|
+
error: 'Frontmatter must contain a "name" field (string)',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
valid: true,
|
|
93
|
+
name: frontmatter.name,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Atomically writes content to a file using temp file + rename strategy
|
|
6
|
+
* This ensures the file is never in a partially written state
|
|
7
|
+
*
|
|
8
|
+
* @param filePath - Target file path
|
|
9
|
+
* @param content - Content to write (string or Buffer)
|
|
10
|
+
* @throws Error if write or rename fails
|
|
11
|
+
*/
|
|
12
|
+
export function atomicWrite(filePath: string, content: string | Buffer): void {
|
|
13
|
+
// Ensure parent directory exists
|
|
14
|
+
const dir = dirname(filePath);
|
|
15
|
+
mkdirSync(dir, { recursive: true });
|
|
16
|
+
|
|
17
|
+
// Create temp file in same directory (ensures same filesystem for atomic rename)
|
|
18
|
+
const tempPath = join(dir, `.${Date.now()}.tmp`);
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Write to temp file
|
|
22
|
+
writeFileSync(tempPath, content, "utf-8");
|
|
23
|
+
|
|
24
|
+
// Atomic rename (overwrites target if exists)
|
|
25
|
+
renameSync(tempPath, filePath);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
// Clean up temp file if it exists
|
|
28
|
+
try {
|
|
29
|
+
unlinkSync(tempPath);
|
|
30
|
+
} catch {
|
|
31
|
+
// Ignore cleanup errors
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`Atomic write failed for ${filePath}: ${error}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export function backupConfig(
|
|
6
|
+
filePath: string,
|
|
7
|
+
baseDir?: string
|
|
8
|
+
): string | null {
|
|
9
|
+
if (!existsSync(filePath)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const home = baseDir || homedir();
|
|
14
|
+
const backupDir = join(home, ".taal", "backups");
|
|
15
|
+
mkdirSync(backupDir, { recursive: true });
|
|
16
|
+
|
|
17
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
18
|
+
const filename = basename(filePath);
|
|
19
|
+
const backupPath = join(backupDir, `${filename}.${timestamp}.backup`);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
copyFileSync(filePath, backupPath);
|
|
23
|
+
return backupPath;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new Error(`Failed to backup ${filePath}: ${error}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
package/taal.schema.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$ref": "#/definitions/TaalConfig",
|
|
3
|
+
"definitions": {
|
|
4
|
+
"TaalConfig": {
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"version": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"const": "1"
|
|
10
|
+
},
|
|
11
|
+
"mcp": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"properties": {
|
|
16
|
+
"command": {
|
|
17
|
+
"type": "string"
|
|
18
|
+
},
|
|
19
|
+
"args": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"items": {
|
|
22
|
+
"type": "string"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"env": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"additionalProperties": {
|
|
28
|
+
"type": "string"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"url": {
|
|
32
|
+
"type": "string"
|
|
33
|
+
},
|
|
34
|
+
"headers": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"additionalProperties": {
|
|
37
|
+
"type": "string"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"overrides": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"additionalProperties": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"enabled_tools": {
|
|
46
|
+
"type": "array",
|
|
47
|
+
"items": {
|
|
48
|
+
"type": "string"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"additionalProperties": false
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"additionalProperties": false
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"skills": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"properties": {
|
|
62
|
+
"paths": {
|
|
63
|
+
"type": "array",
|
|
64
|
+
"items": {
|
|
65
|
+
"type": "string"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"required": ["paths"],
|
|
70
|
+
"additionalProperties": false
|
|
71
|
+
},
|
|
72
|
+
"providers": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"enabled": {
|
|
76
|
+
"type": "array",
|
|
77
|
+
"items": {
|
|
78
|
+
"type": "string"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"required": ["enabled"],
|
|
83
|
+
"additionalProperties": false
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"required": ["version"],
|
|
87
|
+
"additionalProperties": false
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
91
|
+
}
|