@maccesar/titools 2.0.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.
Files changed (120) hide show
  1. package/AGENTS-TEMPLATE.md +173 -0
  2. package/README.md +867 -0
  3. package/agents/ti-researcher.md +108 -0
  4. package/bin/titools.js +53 -0
  5. package/lib/commands/agents.js +126 -0
  6. package/lib/commands/install.js +188 -0
  7. package/lib/commands/uninstall.js +215 -0
  8. package/lib/commands/update.js +159 -0
  9. package/lib/config.js +119 -0
  10. package/lib/downloader.js +153 -0
  11. package/lib/installer.js +253 -0
  12. package/lib/platform.js +108 -0
  13. package/lib/symlink.js +142 -0
  14. package/lib/utils.js +270 -0
  15. package/package.json +67 -0
  16. package/skills/alloy-expert/SKILL.md +247 -0
  17. package/skills/alloy-expert/assets/ControllerAutoCleanup.js +182 -0
  18. package/skills/alloy-expert/references/alloy-structure.md +381 -0
  19. package/skills/alloy-expert/references/anti-patterns.md +133 -0
  20. package/skills/alloy-expert/references/code-conventions.md +469 -0
  21. package/skills/alloy-expert/references/contracts.md +280 -0
  22. package/skills/alloy-expert/references/controller-patterns.md +520 -0
  23. package/skills/alloy-expert/references/error-handling.md +484 -0
  24. package/skills/alloy-expert/references/examples.md +735 -0
  25. package/skills/alloy-expert/references/migration-patterns.md +298 -0
  26. package/skills/alloy-expert/references/patterns.md +448 -0
  27. package/skills/alloy-expert/references/performance-patterns.md +855 -0
  28. package/skills/alloy-expert/references/security-patterns.md +847 -0
  29. package/skills/alloy-expert/references/state-management.md +779 -0
  30. package/skills/alloy-expert/references/testing.md +872 -0
  31. package/skills/alloy-guides/SKILL.md +214 -0
  32. package/skills/alloy-guides/references/CLI_TASKS.md +243 -0
  33. package/skills/alloy-guides/references/CONCEPTS.md +191 -0
  34. package/skills/alloy-guides/references/CONTROLLERS.md +298 -0
  35. package/skills/alloy-guides/references/MODELS.md +1028 -0
  36. package/skills/alloy-guides/references/PURGETSS.md +56 -0
  37. package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +242 -0
  38. package/skills/alloy-guides/references/VIEWS_STYLES.md +388 -0
  39. package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +109 -0
  40. package/skills/alloy-guides/references/VIEWS_XML.md +558 -0
  41. package/skills/alloy-guides/references/WIDGETS.md +176 -0
  42. package/skills/alloy-howtos/SKILL.md +203 -0
  43. package/skills/alloy-howtos/references/best_practices.md +138 -0
  44. package/skills/alloy-howtos/references/cli_reference.md +253 -0
  45. package/skills/alloy-howtos/references/config_files.md +87 -0
  46. package/skills/alloy-howtos/references/custom_tags.md +147 -0
  47. package/skills/alloy-howtos/references/debugging_troubleshooting.md +101 -0
  48. package/skills/alloy-howtos/references/samples.md +167 -0
  49. package/skills/purgetss/SKILL.md +442 -0
  50. package/skills/purgetss/assets/purgetss.config.cjs +17 -0
  51. package/skills/purgetss/references/EXAMPLES.md +247 -0
  52. package/skills/purgetss/references/animation-system.md +1294 -0
  53. package/skills/purgetss/references/apply-directive.md +375 -0
  54. package/skills/purgetss/references/arbitrary-values.md +612 -0
  55. package/skills/purgetss/references/class-index.md +1350 -0
  56. package/skills/purgetss/references/cli-commands.md +948 -0
  57. package/skills/purgetss/references/configurable-properties.md +654 -0
  58. package/skills/purgetss/references/custom-rules.md +161 -0
  59. package/skills/purgetss/references/customization-deep-dive.md +722 -0
  60. package/skills/purgetss/references/dynamic-component-creation.md +489 -0
  61. package/skills/purgetss/references/grid-layout.md +455 -0
  62. package/skills/purgetss/references/icon-fonts.md +609 -0
  63. package/skills/purgetss/references/installation-setup.md +366 -0
  64. package/skills/purgetss/references/opacity-modifier.md +291 -0
  65. package/skills/purgetss/references/platform-modifiers.md +479 -0
  66. package/skills/purgetss/references/smart-mappings.md +42 -0
  67. package/skills/purgetss/references/titanium-resets.md +359 -0
  68. package/skills/purgetss/references/ui-ux-design.md +1526 -0
  69. package/skills/ti-guides/SKILL.md +94 -0
  70. package/skills/ti-guides/references/advanced-data-and-images.md +19 -0
  71. package/skills/ti-guides/references/alloy-cli-advanced.md +84 -0
  72. package/skills/ti-guides/references/alloy-data-mastery.md +29 -0
  73. package/skills/ti-guides/references/alloy-widgets-and-themes.md +19 -0
  74. package/skills/ti-guides/references/android-manifest.md +97 -0
  75. package/skills/ti-guides/references/app-distribution.md +258 -0
  76. package/skills/ti-guides/references/application-frameworks.md +377 -0
  77. package/skills/ti-guides/references/cli-reference.md +402 -0
  78. package/skills/ti-guides/references/coding-best-practices.md +102 -0
  79. package/skills/ti-guides/references/commonjs-advanced.md +134 -0
  80. package/skills/ti-guides/references/hello-world.md +100 -0
  81. package/skills/ti-guides/references/hyperloop-native-access.md +62 -0
  82. package/skills/ti-guides/references/javascript-primer.md +411 -0
  83. package/skills/ti-guides/references/reserved-words.md +36 -0
  84. package/skills/ti-guides/references/resources.md +183 -0
  85. package/skills/ti-guides/references/style-and-conventions.md +48 -0
  86. package/skills/ti-guides/references/tiapp-config.md +609 -0
  87. package/skills/ti-howtos/SKILL.md +174 -0
  88. package/skills/ti-howtos/references/android-platform-deep-dives.md +658 -0
  89. package/skills/ti-howtos/references/automation-fastlane-appium.md +95 -0
  90. package/skills/ti-howtos/references/buffer-codec-streams.md +140 -0
  91. package/skills/ti-howtos/references/cross-platform-development.md +348 -0
  92. package/skills/ti-howtos/references/debugging-profiling.md +543 -0
  93. package/skills/ti-howtos/references/extending-titanium.md +723 -0
  94. package/skills/ti-howtos/references/google-maps-v2.md +169 -0
  95. package/skills/ti-howtos/references/ios-map-kit.md +143 -0
  96. package/skills/ti-howtos/references/ios-platform-deep-dives.md +783 -0
  97. package/skills/ti-howtos/references/local-data-sources.md +301 -0
  98. package/skills/ti-howtos/references/location-and-maps.md +252 -0
  99. package/skills/ti-howtos/references/media-apis.md +210 -0
  100. package/skills/ti-howtos/references/notification-services.md +599 -0
  101. package/skills/ti-howtos/references/remote-data-sources.md +349 -0
  102. package/skills/ti-howtos/references/tutorials.md +502 -0
  103. package/skills/ti-howtos/references/using-modules.md +237 -0
  104. package/skills/ti-howtos/references/web-content-integration.md +307 -0
  105. package/skills/ti-howtos/references/webpack-build-pipeline.md +78 -0
  106. package/skills/ti-ui/SKILL.md +179 -0
  107. package/skills/ti-ui/references/accessibility-deep-dive.md +242 -0
  108. package/skills/ti-ui/references/animation-and-matrices.md +599 -0
  109. package/skills/ti-ui/references/application-structures.md +655 -0
  110. package/skills/ti-ui/references/custom-fonts-styling.md +579 -0
  111. package/skills/ti-ui/references/event-handling.md +393 -0
  112. package/skills/ti-ui/references/gestures.md +473 -0
  113. package/skills/ti-ui/references/icons-and-splash-screens.md +409 -0
  114. package/skills/ti-ui/references/layouts-and-positioning.md +462 -0
  115. package/skills/ti-ui/references/listviews-and-performance.md +619 -0
  116. package/skills/ti-ui/references/orientation.md +362 -0
  117. package/skills/ti-ui/references/platform-ui-android.md +635 -0
  118. package/skills/ti-ui/references/platform-ui-ios.md +469 -0
  119. package/skills/ti-ui/references/scrolling-views.md +252 -0
  120. package/skills/ti-ui/references/tableviews.md +568 -0
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Update command
3
+ * Updates skills and docs to the latest version
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
8
+ import {
9
+ PACKAGE_VERSION,
10
+ REPO_URL,
11
+ REPO_API_URL,
12
+ SKILLS,
13
+ AGENTS,
14
+ } from '../config.js';
15
+ import {
16
+ detectPlatforms,
17
+ } from '../platform.js';
18
+ import {
19
+ installSkills,
20
+ installAgents,
21
+ installAgentsTemplate,
22
+ getLocalRepoDir,
23
+ } from '../installer.js';
24
+ import {
25
+ downloadRepoArchive,
26
+ checkForUpdate,
27
+ } from '../downloader.js';
28
+ import { createSkillSymlinks } from '../symlink.js';
29
+ import { formatList } from '../utils.js';
30
+ import { mkdtemp } from 'fs/promises';
31
+ import { join } from 'path';
32
+ import { tmpdir } from 'os';
33
+
34
+ /**
35
+ * Update command handler
36
+ */
37
+ export async function updateCommand() {
38
+ console.log('');
39
+ console.log(chalk.bold.blue('Titanium SDK Skills Updater'));
40
+ console.log('');
41
+
42
+ const spinner = ora();
43
+
44
+ // Check for updates
45
+ spinner.start('Checking for updates...');
46
+
47
+ try {
48
+ const hasUpdate = await checkForUpdate(PACKAGE_VERSION);
49
+
50
+ if (!hasUpdate) {
51
+ spinner.info(`Already up to date (v${PACKAGE_VERSION})`);
52
+ console.log('');
53
+ console.log(chalk.green('✓'), 'Skills and agents are already at the latest version');
54
+ console.log('');
55
+ return;
56
+ }
57
+
58
+ spinner.succeed(`Update available!`);
59
+ console.log('');
60
+ console.log(chalk.gray(`Current: ${PACKAGE_VERSION}`));
61
+ console.log(chalk.gray(`Latest: (from GitHub)`));
62
+ console.log('');
63
+
64
+ // Detect installed platforms
65
+ const detectedPlatforms = detectPlatforms();
66
+
67
+ if (detectedPlatforms.length === 0) {
68
+ console.log(chalk.yellow('No AI coding assistants detected.'));
69
+ console.log('Update will install skills to ~/.agents/skills/');
70
+ console.log('');
71
+ } else {
72
+ for (const platform of detectedPlatforms) {
73
+ console.log(chalk.green('✓'), platform.displayName);
74
+ }
75
+ console.log('');
76
+ }
77
+
78
+ // Download latest from GitHub
79
+ spinner.start('Downloading latest from GitHub...');
80
+
81
+ let repoDir = getLocalRepoDir();
82
+ let tempDir = null;
83
+
84
+ if (!repoDir) {
85
+ tempDir = await mkdtemp(join(tmpdir(), 'titanium-skills-'));
86
+ repoDir = await downloadRepoArchive(tempDir);
87
+ }
88
+
89
+ spinner.succeed('Downloaded from GitHub');
90
+
91
+ try {
92
+ // Install skills
93
+ spinner.start('Updating skills...');
94
+ const skillsResult = await installSkills(repoDir);
95
+ spinner.succeed(
96
+ `Skills: ${formatList(skillsResult.installed)}`
97
+ );
98
+
99
+ // Install agents
100
+ spinner.start('Updating agents...');
101
+ const agentsResult = await installAgents(repoDir);
102
+ if (agentsResult.installed.length > 0) {
103
+ spinner.succeed(
104
+ `Agents: ${formatList(agentsResult.installed)}`
105
+ );
106
+ } else {
107
+ spinner.info('No agents to update');
108
+ }
109
+
110
+ // Install AGENTS-TEMPLATE.md
111
+ spinner.start('Updating AGENTS-TEMPLATE.md...');
112
+ const templateInstalled = await installAgentsTemplate(repoDir);
113
+ if (templateInstalled) {
114
+ spinner.succeed('AGENTS-TEMPLATE.md updated');
115
+ } else {
116
+ spinner.warn('AGENTS-TEMPLATE.md not found');
117
+ }
118
+
119
+ // Update symlinks for detected platforms
120
+ for (const platform of detectedPlatforms) {
121
+ spinner.start(`Updating ${platform.displayName} symlinks...`);
122
+ const symlinkResult = await createSkillSymlinks(
123
+ platform.skillsDir,
124
+ SKILLS
125
+ );
126
+ if (symlinkResult.linked.length === SKILLS.length) {
127
+ spinner.succeed(`${platform.displayName} linked`);
128
+ } else {
129
+ spinner.warn(
130
+ `${platform.displayName}: ${symlinkResult.linked.length}/${SKILLS.length} linked`
131
+ );
132
+ }
133
+ }
134
+
135
+ // Summary
136
+ console.log('');
137
+ console.log(chalk.green('✓ Update complete!'));
138
+ console.log('');
139
+ console.log(chalk.bold('▸'), 'Run in your Titanium project:', chalk.cyan('titools agents'));
140
+ console.log('');
141
+
142
+ } finally {
143
+ // Clean up temp directory
144
+ if (tempDir) {
145
+ await import('fs-extra').then(({ remove }) => remove(tempDir));
146
+ }
147
+ }
148
+
149
+ } catch (error) {
150
+ spinner.fail('Update failed');
151
+ console.error(chalk.red(error.message));
152
+ console.log('');
153
+ console.log('You can try manually installing from:');
154
+ console.log(chalk.cyan(REPO_URL));
155
+ process.exit(1);
156
+ }
157
+ }
158
+
159
+ export default updateCommand;
package/lib/config.js ADDED
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Configuration constants for Titanium SDK Skills
3
+ * Single source of truth for version management
4
+ */
5
+
6
+ import path from 'path';
7
+ import os from 'os';
8
+ import { readFileSync } from 'fs';
9
+
10
+ // Read package.json version dynamically
11
+ let packageVersion = '1.7.0';
12
+ try {
13
+ const packagePath = new URL('../package.json', import.meta.url);
14
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
15
+ packageVersion = pkg.version;
16
+ } catch {
17
+ // Use default version if package.json not found
18
+ }
19
+
20
+ // Version management
21
+ export const PACKAGE_VERSION = packageVersion;
22
+ export const TITANIUM_KNOWLEDGE_VERSION = `v${PACKAGE_VERSION}`;
23
+ export const BLOCK_START = `<!-- TITANIUM-KNOWLEDGE-${TITANIUM_KNOWLEDGE_VERSION} -->`;
24
+ export const BLOCK_END = '<!-- END-TITANIUM-KNOWLEDGE -->';
25
+
26
+ // Repository configuration
27
+ export const REPO_URL = 'https://github.com/macCesar/titools';
28
+ export const REPO_RAW_URL = 'https://raw.githubusercontent.com/macCesar/titools/main';
29
+ export const REPO_API_URL = 'https://api.github.com/repos/macCesar/titools';
30
+
31
+ // Skills to install
32
+ export const SKILLS = [
33
+ 'alloy-expert',
34
+ 'purgetss',
35
+ 'ti-ui',
36
+ 'ti-howtos',
37
+ 'ti-guides',
38
+ 'alloy-guides',
39
+ 'alloy-howtos',
40
+ ];
41
+
42
+ // Agents to install
43
+ export const AGENTS = [
44
+ 'ti-researcher',
45
+ ];
46
+
47
+ // Directory paths
48
+ export const getAgentsDir = () => path.join(os.homedir(), '.agents');
49
+ export const getAgentsSkillsDir = () => path.join(getAgentsDir(), 'skills');
50
+ export const getClaudeAgentsDir = () => path.join(os.homedir(), '.claude', 'agents');
51
+ export const getClaudeSkillsDir = () => path.join(os.homedir(), '.claude', 'skills');
52
+ export const getGeminiSkillsDir = () => path.join(os.homedir(), '.gemini', 'skills');
53
+ export const getCodexSkillsDir = () => path.join(os.homedir(), '.codex', 'skills');
54
+
55
+ // AI platform detection
56
+ export const PLATFORMS = [
57
+ {
58
+ name: 'claude',
59
+ displayName: 'Claude Code',
60
+ skillsDir: getClaudeSkillsDir(),
61
+ configDir: path.join(os.homedir(), '.claude'),
62
+ },
63
+ {
64
+ name: 'gemini',
65
+ displayName: 'Gemini CLI',
66
+ skillsDir: getGeminiSkillsDir(),
67
+ configDir: path.join(os.homedir(), '.gemini'),
68
+ },
69
+ {
70
+ name: 'codex',
71
+ displayName: 'Codex CLI',
72
+ skillsDir: getCodexSkillsDir(),
73
+ configDir: path.join(os.homedir(), '.codex'),
74
+ },
75
+ ];
76
+
77
+ // Files to install
78
+ export const AGENTS_TEMPLATE_FILE = 'AGENTS-TEMPLATE.md';
79
+ export const TI_DOCS_INDEX_SCRIPT = 'ti-docs-index';
80
+
81
+ // AI file priorities (higher = more priority)
82
+ export const AI_FILE_PRIORITIES = {
83
+ 'CLAUDE.md': 3,
84
+ 'GEMINI.md': 2,
85
+ 'AGENTS.md': 1,
86
+ };
87
+
88
+ // Titanium project detection
89
+ export const TITANIUM_PROJECT_FILE = 'tiapp.xml';
90
+
91
+ // API configuration
92
+ export const GITHUB_API_HEADERS = {
93
+ Accept: 'application/vnd.github.v3+json',
94
+ 'User-Agent': '@maccesar/titanium-skills',
95
+ };
96
+
97
+ export default {
98
+ PACKAGE_VERSION,
99
+ TITANIUM_KNOWLEDGE_VERSION,
100
+ BLOCK_START,
101
+ BLOCK_END,
102
+ REPO_URL,
103
+ REPO_RAW_URL,
104
+ REPO_API_URL,
105
+ SKILLS,
106
+ AGENTS,
107
+ getAgentsDir,
108
+ getAgentsSkillsDir,
109
+ getClaudeAgentsDir,
110
+ getClaudeSkillsDir,
111
+ getGeminiSkillsDir,
112
+ getCodexSkillsDir,
113
+ PLATFORMS,
114
+ AGENTS_TEMPLATE_FILE,
115
+ TI_DOCS_INDEX_SCRIPT,
116
+ AI_FILE_PRIORITIES,
117
+ TITANIUM_PROJECT_FILE,
118
+ GITHUB_API_HEADERS,
119
+ };
@@ -0,0 +1,153 @@
1
+ /**
2
+ * GitHub download utilities
3
+ * Fetches releases and archives from GitHub
4
+ */
5
+
6
+ import { createWriteStream, existsSync, mkdirSync } from 'fs';
7
+ import { tmpdir } from 'os';
8
+ import { join, dirname } from 'path';
9
+ import { pipeline } from 'stream/promises';
10
+ import { unlink } from 'fs/promises';
11
+ import tar from 'tar';
12
+ import fetch from 'node-fetch';
13
+ import { REPO_API_URL, REPO_RAW_URL, GITHUB_API_HEADERS } from './config.js';
14
+
15
+ /**
16
+ * Fetch latest release info from GitHub API
17
+ * @returns {Promise<Object>} Release information
18
+ */
19
+ export async function fetchLatestRelease() {
20
+ const response = await fetch(
21
+ `${REPO_API_URL}/releases/latest`,
22
+ {
23
+ headers: GITHUB_API_HEADERS,
24
+ }
25
+ );
26
+
27
+ if (!response.ok) {
28
+ throw new Error(`Failed to fetch release info: ${response.statusText}`);
29
+ }
30
+
31
+ return response.json();
32
+ }
33
+
34
+ /**
35
+ * Fetch latest version from GitHub
36
+ * @returns {Promise<string>} Latest version tag
37
+ */
38
+ export async function fetchLatestVersion() {
39
+ const release = await fetchLatestRelease();
40
+ return release.tag_name;
41
+ }
42
+
43
+ /**
44
+ * Download a file from URL to local path
45
+ * @param {string} url - URL to download
46
+ * @param {string} destPath - Destination path
47
+ * @returns {Promise<void>}
48
+ */
49
+ export async function downloadFile(url, destPath) {
50
+ const response = await fetch(url);
51
+
52
+ if (!response.ok) {
53
+ throw new Error(`Failed to download: ${response.statusText}`);
54
+ }
55
+
56
+ // Ensure directory exists
57
+ const dir = dirname(destPath);
58
+ if (!existsSync(dir)) {
59
+ mkdirSync(dir, { recursive: true });
60
+ }
61
+
62
+ // Download with progress indication
63
+ await pipeline(response.body, createWriteStream(destPath));
64
+ }
65
+
66
+ /**
67
+ * Download and extract GitHub repository archive
68
+ * @param {string} destDir - Destination directory
69
+ * @param {string} ref - Git ref (branch, tag, commit)
70
+ * @returns {Promise<string>} Path to extracted directory
71
+ */
72
+ export async function downloadRepoArchive(destDir, ref = 'main') {
73
+ const archiveUrl = `${REPO_API_URL}/tarball/${ref}`;
74
+ const tempFile = join(tmpdir(), `titanium-skills-${Date.now()}.tar.gz`);
75
+
76
+ try {
77
+ // Download archive
78
+ await downloadFile(archiveUrl, tempFile);
79
+
80
+ // Extract archive
81
+ await tar.extract({
82
+ file: tempFile,
83
+ cwd: destDir,
84
+ strip: 1, // Remove top-level directory
85
+ });
86
+
87
+ return destDir;
88
+ } finally {
89
+ // Clean up temp file
90
+ if (existsSync(tempFile)) {
91
+ await unlink(tempFile);
92
+ }
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Download a single file from GitHub raw content
98
+ * @param {string} filePath - Path in repository (e.g., 'AGENTS-TEMPLATE.md')
99
+ * @param {string} destPath - Local destination path
100
+ * @param {string} ref - Git ref (branch, tag, commit)
101
+ * @returns {Promise<void>}
102
+ */
103
+ export async function downloadRawFile(filePath, destPath, ref = 'main') {
104
+ const url = `${REPO_RAW_URL}/${ref}/${filePath}`;
105
+
106
+ // Ensure directory exists
107
+ const dir = dirname(destPath);
108
+ if (!existsSync(dir)) {
109
+ mkdirSync(dir, { recursive: true });
110
+ }
111
+
112
+ await downloadFile(url, destPath);
113
+ }
114
+
115
+ /**
116
+ * Check if an update is available
117
+ * @param {string} currentVersion - Current version
118
+ * @returns {Promise<boolean>} True if update available
119
+ */
120
+ export async function checkForUpdate(currentVersion) {
121
+ try {
122
+ const latestVersion = await fetchLatestVersion();
123
+ // Remove 'v' prefix if present
124
+ const latest = latestVersion.replace(/^v/, '');
125
+ const current = currentVersion.replace(/^v/, '');
126
+
127
+ // Simple version comparison
128
+ const latestParts = latest.split('.').map((v) => parseInt(v, 10));
129
+ const currentParts = current.split('.').map((v) => parseInt(v, 10));
130
+
131
+ for (let i = 0; i < Math.max(latestParts.length, currentParts.length); i++) {
132
+ const l = latestParts[i] || 0;
133
+ const c = currentParts[i] || 0;
134
+
135
+ if (l > c) return true;
136
+ if (l < c) return false;
137
+ }
138
+
139
+ return false;
140
+ } catch {
141
+ // If check fails, assume no update
142
+ return false;
143
+ }
144
+ }
145
+
146
+ export default {
147
+ fetchLatestRelease,
148
+ fetchLatestVersion,
149
+ downloadFile,
150
+ downloadRepoArchive,
151
+ downloadRawFile,
152
+ checkForUpdate,
153
+ };
@@ -0,0 +1,253 @@
1
+ /**
2
+ * File installation utilities
3
+ * Installs skills, agents, and templates to their respective directories
4
+ */
5
+
6
+ import {
7
+ copyFileSync,
8
+ existsSync,
9
+ mkdirSync,
10
+ readdirSync,
11
+ } from 'fs';
12
+ import { join } from 'path';
13
+ import { remove, copy } from 'fs-extra';
14
+ import {
15
+ SKILLS,
16
+ AGENTS,
17
+ getAgentsSkillsDir,
18
+ getClaudeAgentsDir,
19
+ getAgentsDir,
20
+ AGENTS_TEMPLATE_FILE,
21
+ } from './config.js';
22
+
23
+ /**
24
+ * Recursively copy a directory
25
+ * @param {string} src - Source directory
26
+ * @param {string} dest - Destination directory
27
+ * @returns {Promise<void>}
28
+ */
29
+ export async function copyDirectory(src, dest) {
30
+ // Create destination if it doesn't exist
31
+ if (!existsSync(dest)) {
32
+ mkdirSync(dest, { recursive: true });
33
+ }
34
+
35
+ // Use fs-extra copy for recursive directory copy
36
+ await copy(src, dest, { overwrite: true });
37
+ }
38
+
39
+ /**
40
+ * Remove existing file or directory
41
+ * @param {string} target - Path to remove
42
+ * @returns {Promise<void>}
43
+ */
44
+ export async function removeExisting(target) {
45
+ if (existsSync(target)) {
46
+ await remove(target);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Install a single skill to the agents skills directory
52
+ * @param {string} repoDir - Repository directory
53
+ * @param {string} skillName - Name of the skill
54
+ * @returns {Promise<boolean>} True if installed successfully
55
+ */
56
+ export async function installSkill(repoDir, skillName) {
57
+ const skillsDir = getAgentsSkillsDir();
58
+ const skillSrc = join(repoDir, 'skills', skillName);
59
+ const skillDest = join(skillsDir, skillName);
60
+
61
+ // Create skills directory if needed
62
+ if (!existsSync(skillsDir)) {
63
+ mkdirSync(skillsDir, { recursive: true });
64
+ }
65
+
66
+ // Check if source exists
67
+ if (!existsSync(skillSrc)) {
68
+ return false;
69
+ }
70
+
71
+ // Remove existing if present
72
+ if (existsSync(skillDest)) {
73
+ await remove(skillDest);
74
+ }
75
+
76
+ // Copy skill directory
77
+ await copyDirectory(skillSrc, skillDest);
78
+ return true;
79
+ }
80
+
81
+ /**
82
+ * Install all skills to the agents skills directory
83
+ * @param {string} repoDir - Repository directory
84
+ * @returns {Promise<Object>} Results object with success/failure counts
85
+ */
86
+ export async function installSkills(repoDir) {
87
+ const results = {
88
+ installed: [],
89
+ failed: [],
90
+ };
91
+
92
+ for (const skill of SKILLS) {
93
+ if (await installSkill(repoDir, skill)) {
94
+ results.installed.push(skill);
95
+ } else {
96
+ results.failed.push(skill);
97
+ }
98
+ }
99
+
100
+ return results;
101
+ }
102
+
103
+ /**
104
+ * Install a single agent to Claude agents directory
105
+ * @param {string} repoDir - Repository directory
106
+ * @param {string} agentName - Name of the agent (without .md)
107
+ * @returns {Promise<boolean>} True if installed successfully
108
+ */
109
+ export async function installAgent(repoDir, agentName) {
110
+ const agentsDir = getClaudeAgentsDir();
111
+ const agentSrc = join(repoDir, 'agents', `${agentName}.md`);
112
+ const agentDest = join(agentsDir, `${agentName}.md`);
113
+
114
+ // Create agents directory if needed
115
+ if (!existsSync(agentsDir)) {
116
+ mkdirSync(agentsDir, { recursive: true });
117
+ }
118
+
119
+ // Check if source exists
120
+ if (!existsSync(agentSrc)) {
121
+ return false;
122
+ }
123
+
124
+ // Remove existing if present
125
+ if (existsSync(agentDest)) {
126
+ await remove(agentDest);
127
+ }
128
+
129
+ // Copy agent file
130
+ copyFileSync(agentSrc, agentDest);
131
+ return true;
132
+ }
133
+
134
+ /**
135
+ * Install all agents to Claude agents directory
136
+ * @param {string} repoDir - Repository directory
137
+ * @returns {Promise<Object>} Results object with success/failure counts
138
+ */
139
+ export async function installAgents(repoDir) {
140
+ const results = {
141
+ installed: [],
142
+ failed: [],
143
+ };
144
+
145
+ for (const agent of AGENTS) {
146
+ if (await installAgent(repoDir, agent)) {
147
+ results.installed.push(agent);
148
+ } else {
149
+ results.failed.push(agent);
150
+ }
151
+ }
152
+
153
+ return results;
154
+ }
155
+
156
+ /**
157
+ * Install AGENTS-TEMPLATE.md to the agents directory
158
+ * @param {string} repoDir - Repository directory
159
+ * @returns {Promise<boolean>} True if installed successfully
160
+ */
161
+ export async function installAgentsTemplate(repoDir) {
162
+ const agentsDir = getAgentsDir();
163
+ const templateSrc = join(repoDir, AGENTS_TEMPLATE_FILE);
164
+ const templateDest = join(agentsDir, AGENTS_TEMPLATE_FILE);
165
+
166
+ // Create agents directory if needed
167
+ if (!existsSync(agentsDir)) {
168
+ mkdirSync(agentsDir, { recursive: true });
169
+ }
170
+
171
+ // Check if source exists
172
+ if (!existsSync(templateSrc)) {
173
+ return false;
174
+ }
175
+
176
+ // Copy template file
177
+ copyFileSync(templateSrc, templateDest);
178
+ return true;
179
+ }
180
+
181
+ /**
182
+ * Install the ti-docs-index helper script
183
+ * @param {string} repoDir - Repository directory
184
+ * @param {string} binDir - Bin directory path
185
+ * @returns {Promise<boolean>} True if installed successfully
186
+ */
187
+ export async function installTiDocsIndexScript(repoDir, binDir) {
188
+ const scriptSrc = join(repoDir, 'scripts', 'ti-docs-index');
189
+ const scriptDest = join(binDir, 'ti-docs-index');
190
+
191
+ // Check if source exists
192
+ if (!existsSync(scriptSrc)) {
193
+ return false;
194
+ }
195
+
196
+ // Create bin directory if needed
197
+ if (!existsSync(binDir)) {
198
+ try {
199
+ mkdirSync(binDir, { recursive: true });
200
+ } catch {
201
+ // If we can't create the bin dir, skip script installation
202
+ return false;
203
+ }
204
+ }
205
+
206
+ // Try to copy script file
207
+ try {
208
+ copyFileSync(scriptSrc, scriptDest);
209
+ } catch (error) {
210
+ // If we can't write to binDir (permission denied), skip script installation
211
+ if (error.code === 'EACCES' || error.code === 'EPERM') {
212
+ return false;
213
+ }
214
+ throw error;
215
+ }
216
+
217
+ // Make executable (chmod +x)
218
+ try {
219
+ const { chmod } = await import('fs/promises');
220
+ await chmod(scriptDest, 0o755);
221
+ } catch {
222
+ // Ignore permission errors
223
+ }
224
+
225
+ return true;
226
+ }
227
+
228
+ /**
229
+ * Get the local repository directory if running from source
230
+ * @returns {string|null} Local repo directory or null
231
+ */
232
+ export function getLocalRepoDir() {
233
+ const scriptDir = new URL('..', import.meta.url).pathname;
234
+ const skillsDir = join(scriptDir, 'skills');
235
+
236
+ if (existsSync(skillsDir)) {
237
+ return scriptDir;
238
+ }
239
+
240
+ return null;
241
+ }
242
+
243
+ export default {
244
+ copyDirectory,
245
+ removeExisting,
246
+ installSkill,
247
+ installSkills,
248
+ installAgent,
249
+ installAgents,
250
+ installAgentsTemplate,
251
+ installTiDocsIndexScript,
252
+ getLocalRepoDir,
253
+ };