@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,108 @@
1
+ /**
2
+ * Platform detection utilities
3
+ * Detects AI coding assistants and operating system
4
+ */
5
+
6
+ import { existsSync } from 'fs';
7
+ import { PLATFORMS } from './config.js';
8
+
9
+ /**
10
+ * Detect installed AI coding assistant platforms
11
+ * @returns {Array} Detected platforms
12
+ */
13
+ export function detectPlatforms() {
14
+ const detected = [];
15
+
16
+ for (const platform of PLATFORMS) {
17
+ if (existsSync(platform.configDir)) {
18
+ detected.push(platform);
19
+ }
20
+ }
21
+
22
+ return detected;
23
+ }
24
+
25
+ /**
26
+ * Get platform by name
27
+ * @param {string} name - Platform name (claude, gemini, codex)
28
+ * @returns {Object|null} Platform object or null
29
+ */
30
+ export function getPlatformByName(name) {
31
+ return PLATFORMS.find((p) => p.name === name) || null;
32
+ }
33
+
34
+ /**
35
+ * Detect operating system
36
+ * @returns {string} OS type: 'macos', 'linux', 'windows'
37
+ */
38
+ export function detectOS() {
39
+ const platform = process.platform;
40
+
41
+ if (platform === 'darwin') {
42
+ return 'macos';
43
+ }
44
+ if (platform === 'win32') {
45
+ return 'windows';
46
+ }
47
+ return 'linux';
48
+ }
49
+
50
+ /**
51
+ * Check if running on Windows
52
+ * @returns {boolean}
53
+ */
54
+ export function isWindows() {
55
+ return process.platform === 'win32';
56
+ }
57
+
58
+ /**
59
+ * Check if running on macOS
60
+ * @returns {boolean}
61
+ */
62
+ export function isMacOS() {
63
+ return process.platform === 'darwin';
64
+ }
65
+
66
+ /**
67
+ * Check if running on Linux
68
+ * @returns {boolean}
69
+ */
70
+ export function isLinux() {
71
+ return process.platform === 'linux';
72
+ }
73
+
74
+ /**
75
+ * Get bin directory path for the current platform
76
+ * @returns {string} Path to bin directory
77
+ */
78
+ export function getBinDir() {
79
+ if (isWindows()) {
80
+ return `${process.env.HOME || process.env.USERPROFILE}\\bin`;
81
+ }
82
+ return '/usr/local/bin';
83
+ }
84
+
85
+ /**
86
+ * Check if we have sudo/admin permissions
87
+ * @returns {boolean}
88
+ */
89
+ export function hasSudo() {
90
+ if (isWindows()) {
91
+ // Windows: check if running as administrator
92
+ return process.env.USERDOMAIN === process.env.COMPUTERNAME;
93
+ }
94
+ // Unix-like: check if we can write to /usr/local/bin
95
+ const binDir = getBinDir();
96
+ return existsSync(binDir) && process.getuid && process.getuid() === 0;
97
+ }
98
+
99
+ export default {
100
+ detectPlatforms,
101
+ getPlatformByName,
102
+ detectOS,
103
+ isWindows,
104
+ isMacOS,
105
+ isLinux,
106
+ getBinDir,
107
+ hasSudo,
108
+ };
package/lib/symlink.js ADDED
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Cross-platform symlink utilities
3
+ * Creates symlinks with fallback to copy on Windows
4
+ */
5
+
6
+ import { mkdirSync, existsSync } from 'fs';
7
+ import { symlink, readlink, unlink } from 'fs/promises';
8
+ import { join, dirname } from 'path';
9
+ import { copy, remove } from 'fs-extra';
10
+ import { isWindows } from './platform.js';
11
+
12
+ /**
13
+ * Create a symlink or copy as fallback
14
+ * @param {string} target - Target path (what the symlink points to)
15
+ * @param {string} path - Symlink path (where to create it)
16
+ * @returns {Promise<boolean>} True if successful
17
+ */
18
+ export async function createSymlinkOrCopy(target, path) {
19
+ // Ensure parent directory exists
20
+ const dir = dirname(path);
21
+ if (!existsSync(dir)) {
22
+ mkdirSync(dir, { recursive: true });
23
+ }
24
+
25
+ // Remove existing file/directory/symlink
26
+ if (existsSync(path)) {
27
+ await removePath(path);
28
+ }
29
+
30
+ // Try creating symlink first
31
+ try {
32
+ await symlink(target, path, 'dir');
33
+ return true;
34
+ } catch (error) {
35
+ // On Windows or if symlink fails, copy the directory
36
+ if (isWindows() || error.code === 'EPERM' || error.code === 'EXDEV') {
37
+ try {
38
+ await copy(target, path, { overwrite: true });
39
+ return true;
40
+ } catch (copyError) {
41
+ console.error(`Failed to copy: ${copyError.message}`);
42
+ return false;
43
+ }
44
+ }
45
+ console.error(`Failed to create symlink: ${error.message}`);
46
+ return false;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Remove a file, directory, or symlink
52
+ * @param {string} path - Path to remove
53
+ * @returns {Promise<void>}
54
+ */
55
+ async function removePath(path) {
56
+ try {
57
+ await remove(path);
58
+ } catch {
59
+ // Ignore errors
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Create symlinks for all skills to a platform directory
65
+ * @param {string} platformSkillsDir - Platform skills directory
66
+ * @param {Array} skills - List of skill names
67
+ * @returns {Promise<Object>} Results object with success/failure counts
68
+ */
69
+ export async function createSkillSymlinks(platformSkillsDir, skills) {
70
+ const { getAgentsSkillsDir } = await import('./config.js');
71
+ const agentsSkillsDir = getAgentsSkillsDir();
72
+
73
+ const results = {
74
+ linked: [],
75
+ failed: [],
76
+ };
77
+
78
+ // Ensure platform directory exists
79
+ if (!existsSync(platformSkillsDir)) {
80
+ mkdirSync(platformSkillsDir, { recursive: true });
81
+ }
82
+
83
+ for (const skill of skills) {
84
+ const target = join(agentsSkillsDir, skill);
85
+ const linkPath = join(platformSkillsDir, skill);
86
+
87
+ if (await createSymlinkOrCopy(target, linkPath)) {
88
+ results.linked.push(skill);
89
+ } else {
90
+ results.failed.push(skill);
91
+ }
92
+ }
93
+
94
+ return results;
95
+ }
96
+
97
+ /**
98
+ * Check if a path is a symlink
99
+ * @param {string} path - Path to check
100
+ * @returns {Promise<boolean>} True if symlink
101
+ */
102
+ export async function isSymlink(path) {
103
+ try {
104
+ const { lstat } = await import('fs/promises');
105
+ const stats = await lstat(path);
106
+ return stats.isSymbolicLink();
107
+ } catch {
108
+ return false;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Resolve symlink target
114
+ * @param {string} path - Symlink path
115
+ * @returns {Promise<string|null>} Target path or null
116
+ */
117
+ export async function resolveSymlink(path) {
118
+ try {
119
+ return await readlink(path);
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Remove a symlink and recreate it (update)
127
+ * @param {string} target - New target path
128
+ * @param {string} path - Symlink path
129
+ * @returns {Promise<boolean>} True if successful
130
+ */
131
+ export async function updateSymlink(target, path) {
132
+ await removePath(path);
133
+ return createSymlinkOrCopy(target, path);
134
+ }
135
+
136
+ export default {
137
+ createSymlinkOrCopy,
138
+ createSkillSymlinks,
139
+ isSymlink,
140
+ resolveSymlink,
141
+ updateSymlink,
142
+ };
package/lib/utils.js ADDED
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Utility functions
3
+ * Block management, color output, and helper functions
4
+ */
5
+
6
+ import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
7
+ import { resolve, join, dirname } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { BLOCK_START, BLOCK_END, TITANIUM_KNOWLEDGE_VERSION } from './config.js';
10
+
11
+ // Get package root directory (works for both npm install and npm link)
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ const packageRoot = resolve(__dirname, '..');
15
+ const packageSkillsDir = join(packageRoot, 'skills');
16
+
17
+ /**
18
+ * Check if Titanium knowledge block exists in a file
19
+ * @param {string} filePath - Path to the file
20
+ * @returns {boolean} True if block exists
21
+ */
22
+ export function blockExists(filePath) {
23
+ if (!existsSync(filePath)) {
24
+ return false;
25
+ }
26
+
27
+ const content = readFileSync(filePath, 'utf8');
28
+ return content.includes(BLOCK_START);
29
+ }
30
+
31
+ /**
32
+ * Remove old Titanium knowledge block from a file
33
+ * @param {string} filePath - Path to the file
34
+ * @returns {boolean} True if block was removed
35
+ */
36
+ export function removeOldBlock(filePath) {
37
+ if (!existsSync(filePath)) {
38
+ return false;
39
+ }
40
+
41
+ const content = readFileSync(filePath, 'utf8');
42
+ const regex = new RegExp(
43
+ `[\\s\\S]*?${BLOCK_START}[\\s\\S]*?${BLOCK_END}[\\s\\S]*?\\n*`,
44
+ 'g'
45
+ );
46
+
47
+ const newContent = content.replace(regex, '\n\n');
48
+
49
+ writeFileSync(filePath, newContent, 'utf8');
50
+ return true;
51
+ }
52
+
53
+ /**
54
+ * Build the compressed documentation index dynamically by scanning skills
55
+ * @returns {string} The compressed index section
56
+ */
57
+ export function buildKnowledgeIndex() {
58
+ let skills = [];
59
+
60
+ try {
61
+ skills = readdirSync(packageSkillsDir, { withFileTypes: true })
62
+ .filter(dirent => dirent.isDirectory())
63
+ .map(dirent => dirent.name);
64
+ } catch (error) {
65
+ throw new Error(`Cannot read skills directory from package: ${packageSkillsDir}`);
66
+ }
67
+
68
+ const indexEntries = [];
69
+
70
+ for (const skill of skills.sort()) {
71
+ const refsDir = join(packageSkillsDir, skill, 'references');
72
+
73
+ if (existsSync(refsDir)) {
74
+ const files = readdirSync(refsDir)
75
+ .filter(f => f.endsWith('.md'))
76
+ .sort();
77
+
78
+ if (files.length > 0) {
79
+ indexEntries.push(`${skill}/references:{${files.join(',')}}`);
80
+ }
81
+ }
82
+ }
83
+
84
+ // Build the compressed index format
85
+ // Note: Using \u0060 to avoid backtick parsing issues in template literals
86
+ return '### Compressed Documentation Index\n\n' +
87
+ '\u0060\u0060\u0060\n' +
88
+ '[Titanium SDK Docs Index]|root: ~/.agents/skills\n' +
89
+ '|IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning when working with Titanium SDK. Always consult the documentation files below rather than relying on training data, which may be outdated.\n' +
90
+ '|' + indexEntries.join('\n|') + '\n' +
91
+ '\u0060\u0060\u0060';
92
+ }
93
+
94
+ /**
95
+ * Create the Titanium knowledge block content dynamically
96
+ * @returns {string} The knowledge block content
97
+ */
98
+ export function createKnowledgeBlock() {
99
+ // Build the compressed index dynamically
100
+ const compressedIndex = buildKnowledgeIndex();
101
+
102
+ // Build the knowledge block using template literals
103
+ const blockStart = `<!-- TITANIUM-KNOWLEDGE-${TITANIUM_KNOWLEDGE_VERSION} -->`;
104
+ const commentContent = `## Titanium SDK Knowledge Index
105
+
106
+ Generated by \`titools\` based on Vercel's research on AGENTS.md effectiveness.
107
+
108
+ **IMPORTANT**: Prefer retrieval-led reasoning over pre-training-led reasoning when working with Titanium SDK.
109
+ Always consult the documentation files below rather than relying on training data, which may be outdated.
110
+
111
+ This knowledge index is based on the latest Titanium SDK documentation.`;
112
+
113
+ return `\n\n${blockStart}\n${commentContent}\n\n${compressedIndex}\n<!-- END-TITANIUM-KNOWLEDGE -->\n`;
114
+ }
115
+
116
+ /**
117
+ * Add or update Titanium knowledge block in a file
118
+ * @param {string} filePath - Path to the file
119
+ * @returns {boolean} True if file was updated
120
+ */
121
+ export function addOrUpdateBlock(filePath) {
122
+ let content = '';
123
+
124
+ if (existsSync(filePath)) {
125
+ content = readFileSync(filePath, 'utf8');
126
+ }
127
+
128
+ const block = createKnowledgeBlock();
129
+
130
+ // Remove existing block if present (only the block, not the content before/after)
131
+ if (content.includes(BLOCK_START)) {
132
+ const startIndex = content.indexOf(BLOCK_START);
133
+ if (startIndex !== -1) {
134
+ const endIndex = content.indexOf(BLOCK_END, startIndex);
135
+ if (endIndex !== -1) {
136
+ // Remove from start to end of block (including END marker and trailing newlines)
137
+ const afterBlock = content.substring(endIndex + BLOCK_END.length);
138
+ // Trim leading newlines from afterBlock
139
+ const trimmedAfter = afterBlock.replace(/^\n+/, '');
140
+ content = content.substring(0, startIndex) + '\n\n' + trimmedAfter;
141
+ }
142
+ }
143
+ }
144
+
145
+ // Add new block at the end (preserve existing content)
146
+ content = content.trimEnd() + '\n\n' + block + '\n';
147
+
148
+ writeFileSync(filePath, content, 'utf8');
149
+ return true;
150
+ }
151
+
152
+ /**
153
+ * Detect Titanium SDK version from tiapp.xml
154
+ * @param {string} projectDir - Path to project directory
155
+ * @returns {string} SDK version or 'unknown'
156
+ */
157
+ export function detectTitaniumVersion(projectDir) {
158
+ const tiappPath = join(projectDir, 'tiapp.xml');
159
+
160
+ if (!existsSync(tiappPath)) {
161
+ return 'unknown';
162
+ }
163
+
164
+ const content = readFileSync(tiappPath, 'utf8');
165
+ const match = content.match(/<sdk-version>([^<]+)<\/sdk-version>/);
166
+
167
+ return match ? match[1].trim() : 'unknown';
168
+ }
169
+
170
+ /**
171
+ * Check if directory is a Titanium project
172
+ * @param {string} dir - Path to check
173
+ * @returns {boolean}
174
+ */
175
+ export function isTitaniumProject(dir) {
176
+ return existsSync(join(dir, 'tiapp.xml'));
177
+ }
178
+
179
+ /**
180
+ * Get AI configuration files that exist in a directory
181
+ * @param {string} dir - Path to check
182
+ * @returns {Object} Object with boolean flags for each file type
183
+ */
184
+ export function getAIFiles(dir) {
185
+ return {
186
+ claude: existsSync(join(dir, 'CLAUDE.md')),
187
+ gemini: existsSync(join(dir, 'GEMINI.md')),
188
+ agents: existsSync(join(dir, 'AGENTS.md')),
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Determine which AI files to update based on priority
194
+ * @param {Object} aiFiles - Object with boolean flags for each file type
195
+ * @returns {Array} Array of file names to update (priority order)
196
+ */
197
+ export function determineFilesToUpdate(aiFiles) {
198
+ const files = [];
199
+
200
+ // Priority: CLAUDE.md > GEMINI.md > AGENTS.md
201
+ if (aiFiles.claude) {
202
+ files.push('CLAUDE.md');
203
+ if (aiFiles.gemini) files.push('GEMINI.md');
204
+ if (aiFiles.agents) files.push('AGENTS.md');
205
+ } else if (aiFiles.gemini) {
206
+ files.push('GEMINI.md');
207
+ if (aiFiles.agents) files.push('AGENTS.md');
208
+ } else if (aiFiles.agents) {
209
+ files.push('AGENTS.md');
210
+ }
211
+
212
+ return files;
213
+ }
214
+
215
+ /**
216
+ * Format a list of items for display
217
+ * @param {Array} items - Array of strings
218
+ * @returns {string} Comma-separated list
219
+ */
220
+ export function formatList(items) {
221
+ return items.join(', ');
222
+ }
223
+
224
+ /**
225
+ * Parse SDK version string to compare
226
+ * @param {string} version - Version string (e.g., "13.1.0.GA")
227
+ * @returns {Array} Array of version parts
228
+ */
229
+ export function parseVersion(version) {
230
+ return version
231
+ .replace('.GA', '')
232
+ .replace('.RC', '-')
233
+ .split(/[.-]/)
234
+ .map((v) => parseInt(v, 10) || 0);
235
+ }
236
+
237
+ /**
238
+ * Compare two version strings
239
+ * @param {string} v1 - First version
240
+ * @param {string} v2 - Second version
241
+ * @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2
242
+ */
243
+ export function compareVersions(v1, v2) {
244
+ const parts1 = parseVersion(v1);
245
+ const parts2 = parseVersion(v2);
246
+
247
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
248
+ const p1 = parts1[i] || 0;
249
+ const p2 = parts2[i] || 0;
250
+
251
+ if (p1 < p2) return -1;
252
+ if (p1 > p2) return 1;
253
+ }
254
+
255
+ return 0;
256
+ }
257
+
258
+ export default {
259
+ blockExists,
260
+ removeOldBlock,
261
+ createKnowledgeBlock,
262
+ addOrUpdateBlock,
263
+ detectTitaniumVersion,
264
+ isTitaniumProject,
265
+ getAIFiles,
266
+ determineFilesToUpdate,
267
+ formatList,
268
+ parseVersion,
269
+ compareVersions,
270
+ };
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@maccesar/titools",
3
+ "version": "2.0.0",
4
+ "description": "Titanium SDK skills and agents for AI coding assistants (Claude Code, Gemini CLI, Codex CLI)",
5
+ "main": "lib/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "titools": "./bin/titools.js"
9
+ },
10
+ "scripts": {
11
+ "test": "node --test test/**/*.test.js",
12
+ "lint": "eslint lib/**/*.js",
13
+ "format": "prettier --write lib/**/*.js"
14
+ },
15
+ "keywords": [
16
+ "titanium",
17
+ "titanium-sdk",
18
+ "alloy",
19
+ "purgetss",
20
+ "ai",
21
+ "claude",
22
+ "gemini",
23
+ "codex",
24
+ "skills",
25
+ "agents",
26
+ "mobile",
27
+ "ios",
28
+ "android"
29
+ ],
30
+ "author": "César Estrada <maccesar@gmail.com> (https://github.com/macCesar)",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/macCesar/titools.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/macCesar/titools/issues"
38
+ },
39
+ "homepage": "https://github.com/macCesar/titools#readme",
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "dependencies": {
44
+ "chalk": "^5.3.0",
45
+ "commander": "^12.0.0",
46
+ "fs-extra": "^11.2.0",
47
+ "inquirer": "^9.2.0",
48
+ "node-fetch": "^3.3.0",
49
+ "ora": "^8.0.0",
50
+ "tar": "^6.2.0"
51
+ },
52
+ "devDependencies": {
53
+ "eslint": "^8.57.0",
54
+ "prettier": "^3.2.0"
55
+ },
56
+ "files": [
57
+ "bin/",
58
+ "lib/",
59
+ "skills/",
60
+ "agents/",
61
+ "AGENTS-TEMPLATE.md",
62
+ "scripts/titools-docs"
63
+ ],
64
+ "publishConfig": {
65
+ "access": "public"
66
+ }
67
+ }