@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
|
@@ -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
|
+
};
|
package/lib/installer.js
ADDED
|
@@ -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
|
+
};
|