@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.
- package/AGENTS-TEMPLATE.md +173 -0
- package/README.md +867 -0
- package/agents/ti-researcher.md +108 -0
- package/bin/titools.js +53 -0
- package/lib/commands/agents.js +126 -0
- package/lib/commands/install.js +188 -0
- package/lib/commands/uninstall.js +215 -0
- package/lib/commands/update.js +159 -0
- package/lib/config.js +119 -0
- package/lib/downloader.js +153 -0
- package/lib/installer.js +253 -0
- package/lib/platform.js +108 -0
- package/lib/symlink.js +142 -0
- package/lib/utils.js +270 -0
- package/package.json +67 -0
- package/skills/alloy-expert/SKILL.md +247 -0
- package/skills/alloy-expert/assets/ControllerAutoCleanup.js +182 -0
- package/skills/alloy-expert/references/alloy-structure.md +381 -0
- package/skills/alloy-expert/references/anti-patterns.md +133 -0
- package/skills/alloy-expert/references/code-conventions.md +469 -0
- package/skills/alloy-expert/references/contracts.md +280 -0
- package/skills/alloy-expert/references/controller-patterns.md +520 -0
- package/skills/alloy-expert/references/error-handling.md +484 -0
- package/skills/alloy-expert/references/examples.md +735 -0
- package/skills/alloy-expert/references/migration-patterns.md +298 -0
- package/skills/alloy-expert/references/patterns.md +448 -0
- package/skills/alloy-expert/references/performance-patterns.md +855 -0
- package/skills/alloy-expert/references/security-patterns.md +847 -0
- package/skills/alloy-expert/references/state-management.md +779 -0
- package/skills/alloy-expert/references/testing.md +872 -0
- package/skills/alloy-guides/SKILL.md +214 -0
- package/skills/alloy-guides/references/CLI_TASKS.md +243 -0
- package/skills/alloy-guides/references/CONCEPTS.md +191 -0
- package/skills/alloy-guides/references/CONTROLLERS.md +298 -0
- package/skills/alloy-guides/references/MODELS.md +1028 -0
- package/skills/alloy-guides/references/PURGETSS.md +56 -0
- package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +242 -0
- package/skills/alloy-guides/references/VIEWS_STYLES.md +388 -0
- package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +109 -0
- package/skills/alloy-guides/references/VIEWS_XML.md +558 -0
- package/skills/alloy-guides/references/WIDGETS.md +176 -0
- package/skills/alloy-howtos/SKILL.md +203 -0
- package/skills/alloy-howtos/references/best_practices.md +138 -0
- package/skills/alloy-howtos/references/cli_reference.md +253 -0
- package/skills/alloy-howtos/references/config_files.md +87 -0
- package/skills/alloy-howtos/references/custom_tags.md +147 -0
- package/skills/alloy-howtos/references/debugging_troubleshooting.md +101 -0
- package/skills/alloy-howtos/references/samples.md +167 -0
- package/skills/purgetss/SKILL.md +442 -0
- package/skills/purgetss/assets/purgetss.config.cjs +17 -0
- package/skills/purgetss/references/EXAMPLES.md +247 -0
- package/skills/purgetss/references/animation-system.md +1294 -0
- package/skills/purgetss/references/apply-directive.md +375 -0
- package/skills/purgetss/references/arbitrary-values.md +612 -0
- package/skills/purgetss/references/class-index.md +1350 -0
- package/skills/purgetss/references/cli-commands.md +948 -0
- package/skills/purgetss/references/configurable-properties.md +654 -0
- package/skills/purgetss/references/custom-rules.md +161 -0
- package/skills/purgetss/references/customization-deep-dive.md +722 -0
- package/skills/purgetss/references/dynamic-component-creation.md +489 -0
- package/skills/purgetss/references/grid-layout.md +455 -0
- package/skills/purgetss/references/icon-fonts.md +609 -0
- package/skills/purgetss/references/installation-setup.md +366 -0
- package/skills/purgetss/references/opacity-modifier.md +291 -0
- package/skills/purgetss/references/platform-modifiers.md +479 -0
- package/skills/purgetss/references/smart-mappings.md +42 -0
- package/skills/purgetss/references/titanium-resets.md +359 -0
- package/skills/purgetss/references/ui-ux-design.md +1526 -0
- package/skills/ti-guides/SKILL.md +94 -0
- package/skills/ti-guides/references/advanced-data-and-images.md +19 -0
- package/skills/ti-guides/references/alloy-cli-advanced.md +84 -0
- package/skills/ti-guides/references/alloy-data-mastery.md +29 -0
- package/skills/ti-guides/references/alloy-widgets-and-themes.md +19 -0
- package/skills/ti-guides/references/android-manifest.md +97 -0
- package/skills/ti-guides/references/app-distribution.md +258 -0
- package/skills/ti-guides/references/application-frameworks.md +377 -0
- package/skills/ti-guides/references/cli-reference.md +402 -0
- package/skills/ti-guides/references/coding-best-practices.md +102 -0
- package/skills/ti-guides/references/commonjs-advanced.md +134 -0
- package/skills/ti-guides/references/hello-world.md +100 -0
- package/skills/ti-guides/references/hyperloop-native-access.md +62 -0
- package/skills/ti-guides/references/javascript-primer.md +411 -0
- package/skills/ti-guides/references/reserved-words.md +36 -0
- package/skills/ti-guides/references/resources.md +183 -0
- package/skills/ti-guides/references/style-and-conventions.md +48 -0
- package/skills/ti-guides/references/tiapp-config.md +609 -0
- package/skills/ti-howtos/SKILL.md +174 -0
- package/skills/ti-howtos/references/android-platform-deep-dives.md +658 -0
- package/skills/ti-howtos/references/automation-fastlane-appium.md +95 -0
- package/skills/ti-howtos/references/buffer-codec-streams.md +140 -0
- package/skills/ti-howtos/references/cross-platform-development.md +348 -0
- package/skills/ti-howtos/references/debugging-profiling.md +543 -0
- package/skills/ti-howtos/references/extending-titanium.md +723 -0
- package/skills/ti-howtos/references/google-maps-v2.md +169 -0
- package/skills/ti-howtos/references/ios-map-kit.md +143 -0
- package/skills/ti-howtos/references/ios-platform-deep-dives.md +783 -0
- package/skills/ti-howtos/references/local-data-sources.md +301 -0
- package/skills/ti-howtos/references/location-and-maps.md +252 -0
- package/skills/ti-howtos/references/media-apis.md +210 -0
- package/skills/ti-howtos/references/notification-services.md +599 -0
- package/skills/ti-howtos/references/remote-data-sources.md +349 -0
- package/skills/ti-howtos/references/tutorials.md +502 -0
- package/skills/ti-howtos/references/using-modules.md +237 -0
- package/skills/ti-howtos/references/web-content-integration.md +307 -0
- package/skills/ti-howtos/references/webpack-build-pipeline.md +78 -0
- package/skills/ti-ui/SKILL.md +179 -0
- package/skills/ti-ui/references/accessibility-deep-dive.md +242 -0
- package/skills/ti-ui/references/animation-and-matrices.md +599 -0
- package/skills/ti-ui/references/application-structures.md +655 -0
- package/skills/ti-ui/references/custom-fonts-styling.md +579 -0
- package/skills/ti-ui/references/event-handling.md +393 -0
- package/skills/ti-ui/references/gestures.md +473 -0
- package/skills/ti-ui/references/icons-and-splash-screens.md +409 -0
- package/skills/ti-ui/references/layouts-and-positioning.md +462 -0
- package/skills/ti-ui/references/listviews-and-performance.md +619 -0
- package/skills/ti-ui/references/orientation.md +362 -0
- package/skills/ti-ui/references/platform-ui-android.md +635 -0
- package/skills/ti-ui/references/platform-ui-ios.md +469 -0
- package/skills/ti-ui/references/scrolling-views.md +252 -0
- package/skills/ti-ui/references/tableviews.md +568 -0
package/lib/platform.js
ADDED
|
@@ -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
|
+
}
|