@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.
Files changed (42) hide show
  1. package/.claude/settings.json +15 -0
  2. package/.github/workflows/ci.yml +28 -0
  3. package/.github/workflows/publish.yml +42 -0
  4. package/AGENTS.md +123 -0
  5. package/README.md +568 -0
  6. package/biome.jsonc +24 -0
  7. package/lefthook.yml +12 -0
  8. package/package.json +52 -0
  9. package/src/commands/collect.ts +172 -0
  10. package/src/commands/diff.ts +127 -0
  11. package/src/commands/init.ts +66 -0
  12. package/src/commands/list.ts +80 -0
  13. package/src/commands/providers.ts +46 -0
  14. package/src/commands/sync.ts +111 -0
  15. package/src/commands/validate.ts +17 -0
  16. package/src/config/env.ts +49 -0
  17. package/src/config/loader.ts +88 -0
  18. package/src/config/parser.ts +44 -0
  19. package/src/config/schema.ts +67 -0
  20. package/src/errors/index.ts +43 -0
  21. package/src/index.ts +301 -0
  22. package/src/providers/antigravity.ts +24 -0
  23. package/src/providers/base.ts +70 -0
  24. package/src/providers/claude-code.ts +12 -0
  25. package/src/providers/claude-desktop.ts +19 -0
  26. package/src/providers/codex.ts +61 -0
  27. package/src/providers/continue.ts +62 -0
  28. package/src/providers/cursor.ts +25 -0
  29. package/src/providers/index.ts +34 -0
  30. package/src/providers/opencode.ts +42 -0
  31. package/src/providers/registry.ts +74 -0
  32. package/src/providers/types.ts +99 -0
  33. package/src/providers/utils.ts +106 -0
  34. package/src/providers/windsurf.ts +50 -0
  35. package/src/providers/zed.ts +35 -0
  36. package/src/scripts/generate-schema.ts +17 -0
  37. package/src/skills/copy.ts +58 -0
  38. package/src/skills/discovery.ts +87 -0
  39. package/src/skills/validator.ts +95 -0
  40. package/src/utils/atomic-write.ts +35 -0
  41. package/src/utils/backup.ts +27 -0
  42. 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
+ }
@@ -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
+ }