@maccesar/titools 2.0.6 → 2.2.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 → AGENTS-VERCEL-RESEARCH.md} +16 -18
- package/README.md +250 -168
- package/agents/{ti-researcher.md → ti-pro.md} +10 -10
- package/bin/titools.js +16 -12
- package/lib/cleanup.js +95 -0
- package/lib/commands/agents.js +146 -70
- package/lib/commands/skills.js +349 -0
- package/lib/commands/uninstall.js +189 -154
- package/lib/commands/update.js +104 -21
- package/lib/config.js +26 -20
- package/lib/downloader.js +1 -1
- package/lib/installer.js +37 -97
- package/lib/platform.js +9 -5
- package/lib/prompts/checkboxCancel.js +264 -0
- package/lib/prompts/selectCancel.js +204 -0
- package/lib/symlink.js +19 -7
- package/lib/utils.js +21 -31
- package/package.json +10 -10
- package/skills/alloy-guides/SKILL.md +8 -30
- package/skills/alloy-guides/references/CLI_TASKS.md +14 -24
- package/skills/alloy-guides/references/CONCEPTS.md +5 -25
- package/skills/alloy-guides/references/CONTROLLERS.md +2 -19
- package/skills/alloy-guides/references/MODELS.md +40 -10
- package/skills/alloy-guides/references/PURGETSS.md +1 -11
- package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +8 -14
- package/skills/alloy-guides/references/VIEWS_STYLES.md +14 -25
- package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +2 -8
- package/skills/alloy-guides/references/VIEWS_XML.md +29 -40
- package/skills/alloy-guides/references/WIDGETS.md +1 -17
- package/skills/alloy-howtos/SKILL.md +1 -22
- package/skills/alloy-howtos/references/best_practices.md +0 -17
- package/skills/alloy-howtos/references/cli_reference.md +1 -23
- package/skills/alloy-howtos/references/config_files.md +86 -15
- package/skills/alloy-howtos/references/custom_tags.md +14 -13
- package/skills/alloy-howtos/references/debugging_troubleshooting.md +3 -22
- package/skills/alloy-howtos/references/samples.md +4 -14
- package/skills/purgetss/SKILL.md +11 -37
- package/skills/purgetss/references/EXAMPLES.md +106 -17
- package/skills/purgetss/references/animation-system.md +0 -67
- package/skills/purgetss/references/apply-directive.md +0 -18
- package/skills/purgetss/references/arbitrary-values.md +0 -25
- package/skills/purgetss/references/class-index.md +0 -46
- package/skills/purgetss/references/cli-commands.md +0 -41
- package/skills/purgetss/references/configurable-properties.md +0 -55
- package/skills/purgetss/references/custom-rules.md +0 -14
- package/skills/purgetss/references/customization-deep-dive.md +0 -49
- package/skills/purgetss/references/dynamic-component-creation.md +56 -37
- package/skills/purgetss/references/grid-layout.md +0 -32
- package/skills/purgetss/references/icon-fonts.md +0 -50
- package/skills/purgetss/references/installation-setup.md +61 -32
- package/skills/purgetss/references/migration-guide.md +127 -0
- package/skills/purgetss/references/opacity-modifier.md +0 -34
- package/skills/purgetss/references/performance-tips.md +261 -0
- package/skills/purgetss/references/platform-modifiers.md +0 -46
- package/skills/purgetss/references/smart-mappings.md +0 -12
- package/skills/purgetss/references/tikit-components.md +393 -0
- package/skills/purgetss/references/titanium-resets.md +1 -27
- package/skills/purgetss/references/ui-ux-design.md +467 -117
- package/skills/{alloy-expert → ti-expert}/SKILL.md +45 -54
- package/skills/ti-expert/references/alloy-builtins.md +387 -0
- package/skills/{alloy-expert → ti-expert}/references/alloy-structure.md +147 -40
- package/skills/ti-expert/references/anti-patterns.md +90 -0
- package/skills/ti-expert/references/cli-expert.md +109 -0
- package/skills/{alloy-expert → ti-expert}/references/code-conventions.md +182 -49
- package/skills/{alloy-expert → ti-expert}/references/controller-patterns.md +103 -93
- package/skills/{alloy-expert → ti-expert}/references/error-handling.md +12 -12
- package/skills/{alloy-expert → ti-expert}/references/examples.md +144 -85
- package/skills/{alloy-expert → ti-expert}/references/migration-patterns.md +38 -36
- package/skills/{alloy-expert → ti-expert}/references/patterns.md +8 -8
- package/skills/ti-expert/references/performance-listview.md +251 -0
- package/skills/{alloy-expert/references/performance-patterns.md → ti-expert/references/performance-optimization.md} +67 -283
- package/skills/{alloy-expert/references/security-patterns.md → ti-expert/references/security-device.md} +12 -295
- package/skills/ti-expert/references/security-fundamentals.md +284 -0
- package/skills/{alloy-expert → ti-expert}/references/state-management.md +10 -10
- package/skills/ti-expert/references/testing-e2e-ci.md +432 -0
- package/skills/ti-expert/references/testing-unit.md +433 -0
- package/skills/ti-expert/references/theming.md +394 -0
- package/skills/ti-guides/SKILL.md +1 -14
- package/skills/ti-guides/references/advanced-data-and-images.md +126 -10
- package/skills/ti-guides/references/app-distribution.md +48 -10
- package/skills/ti-guides/references/application-frameworks.md +9 -2
- package/skills/ti-guides/references/cli-reference.md +338 -82
- package/skills/ti-guides/references/coding-best-practices.md +14 -4
- package/skills/ti-guides/references/commonjs-advanced.md +103 -6
- package/skills/ti-guides/references/hello-world.md +5 -2
- package/skills/ti-guides/references/hyperloop-native-access.md +439 -46
- package/skills/ti-guides/references/javascript-primer.md +4 -3
- package/skills/ti-guides/references/resources.md +0 -1
- package/skills/ti-guides/references/style-and-conventions.md +57 -9
- package/skills/ti-guides/references/tiapp-config.md +48 -6
- package/skills/ti-howtos/SKILL.md +2 -27
- package/skills/ti-howtos/references/android-platform-deep-dives.md +41 -90
- package/skills/ti-howtos/references/automation-fastlane-appium.md +15 -14
- package/skills/ti-howtos/references/buffer-codec-streams.md +22 -22
- package/skills/ti-howtos/references/cross-platform-development.md +68 -37
- package/skills/ti-howtos/references/debugging-profiling.md +8 -53
- package/skills/ti-howtos/references/extending-titanium.md +12 -51
- package/skills/ti-howtos/references/google-maps-v2.md +30 -29
- package/skills/ti-howtos/references/ios-map-kit.md +19 -25
- package/skills/ti-howtos/references/ios-platform-deep-dives.md +95 -283
- package/skills/ti-howtos/references/local-data-sources.md +45 -37
- package/skills/ti-howtos/references/location-and-maps.md +47 -28
- package/skills/ti-howtos/references/media-apis.md +84 -27
- package/skills/ti-howtos/references/notification-services.md +40 -89
- package/skills/ti-howtos/references/remote-data-sources.md +21 -36
- package/skills/ti-howtos/references/tutorials.md +79 -39
- package/skills/ti-howtos/references/using-modules.md +4 -30
- package/skills/ti-howtos/references/web-content-integration.md +28 -45
- package/skills/ti-howtos/references/webpack-build-pipeline.md +54 -18
- package/skills/ti-ui/SKILL.md +1 -21
- package/skills/ti-ui/references/accessibility-deep-dive.md +71 -25
- package/skills/ti-ui/references/animation-and-matrices.md +0 -47
- package/skills/ti-ui/references/application-structures.md +0 -46
- package/skills/ti-ui/references/custom-fonts-styling.md +9 -57
- package/skills/ti-ui/references/event-handling.md +32 -40
- package/skills/ti-ui/references/gestures.md +0 -40
- package/skills/ti-ui/references/icons-and-splash-screens.md +21 -44
- package/skills/ti-ui/references/layouts-and-positioning.md +47 -39
- package/skills/ti-ui/references/listviews-and-performance.md +187 -73
- package/skills/ti-ui/references/orientation.md +57 -71
- package/skills/ti-ui/references/platform-ui-android.md +114 -48
- package/skills/ti-ui/references/platform-ui-ios.md +63 -38
- package/skills/ti-ui/references/scrolling-views.md +14 -23
- package/skills/ti-ui/references/tableviews.md +6 -56
- package/lib/commands/install.js +0 -188
- package/skills/alloy-expert/references/anti-patterns.md +0 -133
- package/skills/alloy-expert/references/testing.md +0 -872
- package/skills/ti-guides/references/alloy-cli-advanced.md +0 -84
- package/skills/ti-guides/references/alloy-data-mastery.md +0 -29
- package/skills/ti-guides/references/alloy-widgets-and-themes.md +0 -19
- /package/skills/{alloy-expert → ti-expert}/assets/ControllerAutoCleanup.js +0 -0
- /package/skills/{alloy-expert → ti-expert}/references/contracts.md +0 -0
package/lib/config.js
CHANGED
|
@@ -8,7 +8,7 @@ import os from 'os';
|
|
|
8
8
|
import { readFileSync } from 'fs';
|
|
9
9
|
|
|
10
10
|
// Read package.json version dynamically
|
|
11
|
-
let packageVersion = '1.
|
|
11
|
+
let packageVersion = '2.1.0';
|
|
12
12
|
try {
|
|
13
13
|
const packagePath = new URL('../package.json', import.meta.url);
|
|
14
14
|
const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
@@ -30,7 +30,7 @@ export const REPO_API_URL = 'https://api.github.com/repos/macCesar/titools';
|
|
|
30
30
|
|
|
31
31
|
// Skills to install
|
|
32
32
|
export const SKILLS = [
|
|
33
|
-
'
|
|
33
|
+
'ti-expert',
|
|
34
34
|
'purgetss',
|
|
35
35
|
'ti-ui',
|
|
36
36
|
'ti-howtos',
|
|
@@ -38,45 +38,51 @@ export const SKILLS = [
|
|
|
38
38
|
'alloy-guides',
|
|
39
39
|
'alloy-howtos',
|
|
40
40
|
];
|
|
41
|
+
// Legacy skills to remove during updates/uninstall
|
|
42
|
+
export const LEGACY_SKILLS = [
|
|
43
|
+
'alloy-expert',
|
|
44
|
+
];
|
|
41
45
|
|
|
42
46
|
// Agents to install
|
|
43
47
|
export const AGENTS = [
|
|
48
|
+
'ti-pro',
|
|
49
|
+
];
|
|
50
|
+
// Legacy agents to remove during updates/uninstall
|
|
51
|
+
export const LEGACY_AGENTS = [
|
|
44
52
|
'ti-researcher',
|
|
45
53
|
];
|
|
46
54
|
|
|
47
55
|
// Directory paths
|
|
48
|
-
export const getAgentsDir = () => path.join(
|
|
49
|
-
export const getAgentsSkillsDir = () => path.join(getAgentsDir(), 'skills');
|
|
50
|
-
export const getClaudeAgentsDir = () => path.join(
|
|
51
|
-
export const getClaudeSkillsDir = () => path.join(
|
|
52
|
-
export const getGeminiSkillsDir = () => path.join(
|
|
53
|
-
export const getCodexSkillsDir = () => path.join(
|
|
56
|
+
export const getAgentsDir = (baseDir = os.homedir()) => path.join(baseDir, '.agents');
|
|
57
|
+
export const getAgentsSkillsDir = (baseDir = os.homedir()) => path.join(getAgentsDir(baseDir), 'skills');
|
|
58
|
+
export const getClaudeAgentsDir = (baseDir = os.homedir()) => path.join(baseDir, '.claude', 'agents');
|
|
59
|
+
export const getClaudeSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.claude', 'skills');
|
|
60
|
+
export const getGeminiSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.gemini', 'skills');
|
|
61
|
+
export const getCodexSkillsDir = (baseDir = os.homedir()) => path.join(baseDir, '.codex', 'skills');
|
|
54
62
|
|
|
55
63
|
// AI platform detection
|
|
56
|
-
export const
|
|
64
|
+
export const getPlatforms = (baseDir = os.homedir()) => [
|
|
57
65
|
{
|
|
58
66
|
name: 'claude',
|
|
59
67
|
displayName: 'Claude Code',
|
|
60
|
-
skillsDir: getClaudeSkillsDir(),
|
|
61
|
-
configDir: path.join(
|
|
68
|
+
skillsDir: getClaudeSkillsDir(baseDir),
|
|
69
|
+
configDir: path.join(baseDir, '.claude'),
|
|
62
70
|
},
|
|
63
71
|
{
|
|
64
72
|
name: 'gemini',
|
|
65
73
|
displayName: 'Gemini CLI',
|
|
66
|
-
skillsDir: getGeminiSkillsDir(),
|
|
67
|
-
configDir: path.join(
|
|
74
|
+
skillsDir: getGeminiSkillsDir(baseDir),
|
|
75
|
+
configDir: path.join(baseDir, '.gemini'),
|
|
68
76
|
},
|
|
69
77
|
{
|
|
70
78
|
name: 'codex',
|
|
71
79
|
displayName: 'Codex CLI',
|
|
72
|
-
skillsDir: getCodexSkillsDir(),
|
|
73
|
-
configDir: path.join(
|
|
80
|
+
skillsDir: getCodexSkillsDir(baseDir),
|
|
81
|
+
configDir: path.join(baseDir, '.codex'),
|
|
74
82
|
},
|
|
75
83
|
];
|
|
76
84
|
|
|
77
85
|
// Files to install
|
|
78
|
-
export const AGENTS_TEMPLATE_FILE = 'AGENTS-TEMPLATE.md';
|
|
79
|
-
export const TI_DOCS_INDEX_SCRIPT = 'ti-docs-index';
|
|
80
86
|
|
|
81
87
|
// AI file priorities (higher = more priority)
|
|
82
88
|
export const AI_FILE_PRIORITIES = {
|
|
@@ -103,16 +109,16 @@ export default {
|
|
|
103
109
|
REPO_RAW_URL,
|
|
104
110
|
REPO_API_URL,
|
|
105
111
|
SKILLS,
|
|
112
|
+
LEGACY_SKILLS,
|
|
106
113
|
AGENTS,
|
|
114
|
+
LEGACY_AGENTS,
|
|
107
115
|
getAgentsDir,
|
|
108
116
|
getAgentsSkillsDir,
|
|
109
117
|
getClaudeAgentsDir,
|
|
110
118
|
getClaudeSkillsDir,
|
|
111
119
|
getGeminiSkillsDir,
|
|
112
120
|
getCodexSkillsDir,
|
|
113
|
-
|
|
114
|
-
AGENTS_TEMPLATE_FILE,
|
|
115
|
-
TI_DOCS_INDEX_SCRIPT,
|
|
121
|
+
getPlatforms,
|
|
116
122
|
AI_FILE_PRIORITIES,
|
|
117
123
|
TITANIUM_PROJECT_FILE,
|
|
118
124
|
GITHUB_API_HEADERS,
|
package/lib/downloader.js
CHANGED
|
@@ -94,7 +94,7 @@ export async function downloadRepoArchive(destDir, ref = 'main') {
|
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
96
|
* Download a single file from GitHub raw content
|
|
97
|
-
* @param {string} filePath - Path in repository (e.g., 'AGENTS-
|
|
97
|
+
* @param {string} filePath - Path in repository (e.g., 'AGENTS-VERCEL-RESEARCH.md')
|
|
98
98
|
* @param {string} destPath - Local destination path
|
|
99
99
|
* @param {string} ref - Git ref (branch, tag, commit)
|
|
100
100
|
* @returns {Promise<void>}
|
package/lib/installer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* File installation utilities
|
|
3
|
-
* Installs skills
|
|
3
|
+
* Installs skills and agents to their respective directories
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
@@ -11,14 +11,14 @@ import {
|
|
|
11
11
|
} from 'fs';
|
|
12
12
|
import { join } from 'path';
|
|
13
13
|
import { remove, copy } from 'fs-extra';
|
|
14
|
+
import os from 'os';
|
|
14
15
|
import {
|
|
15
16
|
SKILLS,
|
|
16
17
|
AGENTS,
|
|
17
18
|
getAgentsSkillsDir,
|
|
18
19
|
getClaudeAgentsDir,
|
|
19
|
-
getAgentsDir,
|
|
20
|
-
AGENTS_TEMPLATE_FILE,
|
|
21
20
|
} from './config.js';
|
|
21
|
+
import { removeSkills, removeAgents } from './cleanup.js';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Recursively copy a directory
|
|
@@ -36,25 +36,15 @@ export async function copyDirectory(src, dest) {
|
|
|
36
36
|
await copy(src, dest, { overwrite: true });
|
|
37
37
|
}
|
|
38
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
39
|
/**
|
|
51
40
|
* Install a single skill to the agents skills directory
|
|
52
41
|
* @param {string} repoDir - Repository directory
|
|
53
42
|
* @param {string} skillName - Name of the skill
|
|
43
|
+
* @param {string} baseDir - Base directory for installation
|
|
54
44
|
* @returns {Promise<boolean>} True if installed successfully
|
|
55
45
|
*/
|
|
56
|
-
export async function installSkill(repoDir, skillName) {
|
|
57
|
-
const skillsDir = getAgentsSkillsDir();
|
|
46
|
+
export async function installSkill(repoDir, skillName, baseDir = os.homedir()) {
|
|
47
|
+
const skillsDir = getAgentsSkillsDir(baseDir);
|
|
58
48
|
const skillSrc = join(repoDir, 'skills', skillName);
|
|
59
49
|
const skillDest = join(skillsDir, skillName);
|
|
60
50
|
|
|
@@ -81,16 +71,28 @@ export async function installSkill(repoDir, skillName) {
|
|
|
81
71
|
/**
|
|
82
72
|
* Install all skills to the agents skills directory
|
|
83
73
|
* @param {string} repoDir - Repository directory
|
|
74
|
+
* @param {string} baseDir - Base directory for installation
|
|
84
75
|
* @returns {Promise<Object>} Results object with success/failure counts
|
|
85
76
|
*/
|
|
86
|
-
export async function installSkills(repoDir) {
|
|
77
|
+
export async function installSkills(repoDir, baseDir = os.homedir()) {
|
|
87
78
|
const results = {
|
|
88
79
|
installed: [],
|
|
89
80
|
failed: [],
|
|
81
|
+
removed: [],
|
|
90
82
|
};
|
|
91
83
|
|
|
84
|
+
const legacyLocal = removeSkills(baseDir, { legacyOnly: true });
|
|
85
|
+
results.removed.push(...legacyLocal.removed);
|
|
86
|
+
results.failed.push(...legacyLocal.failed);
|
|
87
|
+
|
|
88
|
+
if (baseDir && baseDir !== os.homedir()) {
|
|
89
|
+
const legacyGlobal = removeSkills(undefined, { legacyOnly: true });
|
|
90
|
+
results.removed.push(...legacyGlobal.removed);
|
|
91
|
+
results.failed.push(...legacyGlobal.failed);
|
|
92
|
+
}
|
|
93
|
+
|
|
92
94
|
for (const skill of SKILLS) {
|
|
93
|
-
if (await installSkill(repoDir, skill)) {
|
|
95
|
+
if (await installSkill(repoDir, skill, baseDir)) {
|
|
94
96
|
results.installed.push(skill);
|
|
95
97
|
} else {
|
|
96
98
|
results.failed.push(skill);
|
|
@@ -104,10 +106,11 @@ export async function installSkills(repoDir) {
|
|
|
104
106
|
* Install a single agent to Claude agents directory
|
|
105
107
|
* @param {string} repoDir - Repository directory
|
|
106
108
|
* @param {string} agentName - Name of the agent (without .md)
|
|
109
|
+
* @param {string} baseDir - Base directory for installation
|
|
107
110
|
* @returns {Promise<boolean>} True if installed successfully
|
|
108
111
|
*/
|
|
109
|
-
export async function installAgent(repoDir, agentName) {
|
|
110
|
-
const agentsDir = getClaudeAgentsDir();
|
|
112
|
+
export async function installAgent(repoDir, agentName, baseDir = os.homedir()) {
|
|
113
|
+
const agentsDir = getClaudeAgentsDir(baseDir);
|
|
111
114
|
const agentSrc = join(repoDir, 'agents', `${agentName}.md`);
|
|
112
115
|
const agentDest = join(agentsDir, `${agentName}.md`);
|
|
113
116
|
|
|
@@ -134,16 +137,28 @@ export async function installAgent(repoDir, agentName) {
|
|
|
134
137
|
/**
|
|
135
138
|
* Install all agents to Claude agents directory
|
|
136
139
|
* @param {string} repoDir - Repository directory
|
|
140
|
+
* @param {string} baseDir - Base directory for installation
|
|
137
141
|
* @returns {Promise<Object>} Results object with success/failure counts
|
|
138
142
|
*/
|
|
139
|
-
export async function installAgents(repoDir) {
|
|
143
|
+
export async function installAgents(repoDir, baseDir = os.homedir()) {
|
|
140
144
|
const results = {
|
|
141
145
|
installed: [],
|
|
142
146
|
failed: [],
|
|
147
|
+
removed: [],
|
|
143
148
|
};
|
|
144
149
|
|
|
150
|
+
const legacyLocal = removeAgents(baseDir, { legacyOnly: true });
|
|
151
|
+
results.removed.push(...legacyLocal.removed);
|
|
152
|
+
results.failed.push(...legacyLocal.failed);
|
|
153
|
+
|
|
154
|
+
if (baseDir && baseDir !== os.homedir()) {
|
|
155
|
+
const legacyGlobal = removeAgents(undefined, { legacyOnly: true });
|
|
156
|
+
results.removed.push(...legacyGlobal.removed);
|
|
157
|
+
results.failed.push(...legacyGlobal.failed);
|
|
158
|
+
}
|
|
159
|
+
|
|
145
160
|
for (const agent of AGENTS) {
|
|
146
|
-
if (await installAgent(repoDir, agent)) {
|
|
161
|
+
if (await installAgent(repoDir, agent, baseDir)) {
|
|
147
162
|
results.installed.push(agent);
|
|
148
163
|
} else {
|
|
149
164
|
results.failed.push(agent);
|
|
@@ -153,78 +168,6 @@ export async function installAgents(repoDir) {
|
|
|
153
168
|
return results;
|
|
154
169
|
}
|
|
155
170
|
|
|
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
171
|
/**
|
|
229
172
|
* Get the local repository directory if running from source
|
|
230
173
|
* @returns {string|null} Local repo directory or null
|
|
@@ -242,12 +185,9 @@ export function getLocalRepoDir() {
|
|
|
242
185
|
|
|
243
186
|
export default {
|
|
244
187
|
copyDirectory,
|
|
245
|
-
removeExisting,
|
|
246
188
|
installSkill,
|
|
247
189
|
installSkills,
|
|
248
190
|
installAgent,
|
|
249
191
|
installAgents,
|
|
250
|
-
installAgentsTemplate,
|
|
251
|
-
installTiDocsIndexScript,
|
|
252
192
|
getLocalRepoDir,
|
|
253
193
|
};
|
package/lib/platform.js
CHANGED
|
@@ -4,16 +4,18 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { existsSync } from 'fs';
|
|
7
|
-
import {
|
|
7
|
+
import { getPlatforms } from './config.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Detect installed AI coding assistant platforms
|
|
11
|
+
* @param {string} baseDir - Optional base directory to check
|
|
11
12
|
* @returns {Array} Detected platforms
|
|
12
13
|
*/
|
|
13
|
-
export function detectPlatforms() {
|
|
14
|
+
export function detectPlatforms(baseDir) {
|
|
14
15
|
const detected = [];
|
|
16
|
+
const platforms = getPlatforms(baseDir);
|
|
15
17
|
|
|
16
|
-
for (const platform of
|
|
18
|
+
for (const platform of platforms) {
|
|
17
19
|
if (existsSync(platform.configDir)) {
|
|
18
20
|
detected.push(platform);
|
|
19
21
|
}
|
|
@@ -25,10 +27,12 @@ export function detectPlatforms() {
|
|
|
25
27
|
/**
|
|
26
28
|
* Get platform by name
|
|
27
29
|
* @param {string} name - Platform name (claude, gemini, codex)
|
|
30
|
+
* @param {string} baseDir - Optional base directory
|
|
28
31
|
* @returns {Object|null} Platform object or null
|
|
29
32
|
*/
|
|
30
|
-
export function getPlatformByName(name) {
|
|
31
|
-
|
|
33
|
+
export function getPlatformByName(name, baseDir) {
|
|
34
|
+
const platforms = getPlatforms(baseDir);
|
|
35
|
+
return platforms.find((p) => p.name === name) || null;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
/**
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Separator,
|
|
3
|
+
ValidationError,
|
|
4
|
+
createPrompt,
|
|
5
|
+
isDownKey,
|
|
6
|
+
isEnterKey,
|
|
7
|
+
isNumberKey,
|
|
8
|
+
isSpaceKey,
|
|
9
|
+
isUpKey,
|
|
10
|
+
makeTheme,
|
|
11
|
+
useKeypress,
|
|
12
|
+
useMemo,
|
|
13
|
+
usePagination,
|
|
14
|
+
usePrefix,
|
|
15
|
+
useState,
|
|
16
|
+
} from '@inquirer/core';
|
|
17
|
+
import { cursorHide } from '@inquirer/ansi';
|
|
18
|
+
import { styleText } from 'node:util';
|
|
19
|
+
import figures from '@inquirer/figures';
|
|
20
|
+
|
|
21
|
+
const checkboxTheme = {
|
|
22
|
+
icon: {
|
|
23
|
+
checked: styleText('green', figures.circleFilled),
|
|
24
|
+
unchecked: figures.circle,
|
|
25
|
+
cursor: figures.pointer,
|
|
26
|
+
},
|
|
27
|
+
style: {
|
|
28
|
+
disabledChoice: (text) => styleText('dim', `- ${text}`),
|
|
29
|
+
renderSelectedChoices: (selectedChoices) =>
|
|
30
|
+
selectedChoices.map((choice) => choice.short).join(', '),
|
|
31
|
+
description: (text) => styleText('cyan', text),
|
|
32
|
+
keysHelpTip: (keys) =>
|
|
33
|
+
keys
|
|
34
|
+
.map(([key, action]) => `${styleText('bold', key)} ${styleText('dim', action)}`)
|
|
35
|
+
.join(styleText('dim', ' • ')),
|
|
36
|
+
},
|
|
37
|
+
keybindings: [],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function isSelectable(item) {
|
|
41
|
+
return !Separator.isSeparator(item) && !item.disabled;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isChecked(item) {
|
|
45
|
+
return isSelectable(item) && item.checked;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toggle(item) {
|
|
49
|
+
return isSelectable(item) ? { ...item, checked: !item.checked } : item;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function check(checked) {
|
|
53
|
+
return function (item) {
|
|
54
|
+
return isSelectable(item) ? { ...item, checked } : item;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeChoices(choices) {
|
|
59
|
+
return choices.map((choice) => {
|
|
60
|
+
if (Separator.isSeparator(choice)) return choice;
|
|
61
|
+
if (typeof choice === 'string') {
|
|
62
|
+
return {
|
|
63
|
+
value: choice,
|
|
64
|
+
name: choice,
|
|
65
|
+
short: choice,
|
|
66
|
+
checkedName: choice,
|
|
67
|
+
disabled: false,
|
|
68
|
+
checked: false,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const name = choice.name ?? String(choice.value);
|
|
72
|
+
const normalizedChoice = {
|
|
73
|
+
value: choice.value,
|
|
74
|
+
name,
|
|
75
|
+
short: choice.short ?? name,
|
|
76
|
+
checkedName: choice.checkedName ?? name,
|
|
77
|
+
disabled: choice.disabled ?? false,
|
|
78
|
+
checked: choice.checked ?? false,
|
|
79
|
+
};
|
|
80
|
+
if (choice.description) {
|
|
81
|
+
normalizedChoice.description = choice.description;
|
|
82
|
+
}
|
|
83
|
+
return normalizedChoice;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const checkboxCancel = createPrompt((config, done) => {
|
|
88
|
+
const {
|
|
89
|
+
pageSize = 7,
|
|
90
|
+
loop = true,
|
|
91
|
+
required,
|
|
92
|
+
validate = () => true,
|
|
93
|
+
cancelValue = 'cancel',
|
|
94
|
+
} = config;
|
|
95
|
+
|
|
96
|
+
// Shortcuts handling - allow disabling by setting to false or null
|
|
97
|
+
const shortcuts = {
|
|
98
|
+
all: config.shortcuts?.all === undefined ? 'a' : config.shortcuts.all,
|
|
99
|
+
invert: config.shortcuts?.invert === undefined ? 'i' : config.shortcuts.invert
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const theme = makeTheme(checkboxTheme, config.theme);
|
|
103
|
+
const { keybindings } = theme;
|
|
104
|
+
const [status, setStatus] = useState('idle');
|
|
105
|
+
const prefix = usePrefix({ status, theme });
|
|
106
|
+
const [items, setItems] = useState(normalizeChoices(config.choices));
|
|
107
|
+
const bounds = useMemo(() => {
|
|
108
|
+
const first = items.findIndex(isSelectable);
|
|
109
|
+
const last = items.findLastIndex(isSelectable);
|
|
110
|
+
if (first === -1) {
|
|
111
|
+
throw new ValidationError('[checkbox prompt] No selectable choices. All choices are disabled.');
|
|
112
|
+
}
|
|
113
|
+
return { first, last };
|
|
114
|
+
}, [items]);
|
|
115
|
+
const [active, setActive] = useState(bounds.first);
|
|
116
|
+
const [errorMsg, setError] = useState();
|
|
117
|
+
const [cancelled, setCancelled] = useState(false);
|
|
118
|
+
|
|
119
|
+
useKeypress(async (key) => {
|
|
120
|
+
if (key.name?.toLowerCase() === 'q') {
|
|
121
|
+
setCancelled(true);
|
|
122
|
+
setStatus('done');
|
|
123
|
+
done([cancelValue]);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (isEnterKey(key)) {
|
|
127
|
+
const activeItem = items[active];
|
|
128
|
+
|
|
129
|
+
// If the active item IS the cancel option, cancel immediately
|
|
130
|
+
if (isSelectable(activeItem) && activeItem.value === cancelValue) {
|
|
131
|
+
setCancelled(true);
|
|
132
|
+
setStatus('done');
|
|
133
|
+
done([cancelValue]);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// If ANY checked item is the cancel option, cancel immediately
|
|
138
|
+
const anyCancelChecked = items.some(
|
|
139
|
+
(item) => isSelectable(item) && item.value === cancelValue && item.checked
|
|
140
|
+
);
|
|
141
|
+
if (anyCancelChecked) {
|
|
142
|
+
setCancelled(true);
|
|
143
|
+
setStatus('done');
|
|
144
|
+
done([cancelValue]);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const selection = items.filter(isChecked);
|
|
149
|
+
const isValid = await validate([...selection]);
|
|
150
|
+
if (required && selection.length === 0) {
|
|
151
|
+
setError('At least one choice must be selected');
|
|
152
|
+
} else if (isValid === true) {
|
|
153
|
+
setStatus('done');
|
|
154
|
+
done(selection.map((choice) => choice.value));
|
|
155
|
+
} else {
|
|
156
|
+
setError(isValid || 'You must select a valid value');
|
|
157
|
+
}
|
|
158
|
+
} else if (isUpKey(key, keybindings) || isDownKey(key, keybindings)) {
|
|
159
|
+
if (
|
|
160
|
+
loop ||
|
|
161
|
+
(isUpKey(key, keybindings) && active !== bounds.first) ||
|
|
162
|
+
(isDownKey(key, keybindings) && active !== bounds.last)
|
|
163
|
+
) {
|
|
164
|
+
const offset = isUpKey(key, keybindings) ? -1 : 1;
|
|
165
|
+
let next = active;
|
|
166
|
+
do {
|
|
167
|
+
next = (next + offset + items.length) % items.length;
|
|
168
|
+
} while (!isSelectable(items[next]));
|
|
169
|
+
setActive(next);
|
|
170
|
+
}
|
|
171
|
+
} else if (isSpaceKey(key)) {
|
|
172
|
+
setError(undefined);
|
|
173
|
+
setItems(items.map((choice, i) => (i === active ? toggle(choice) : choice)));
|
|
174
|
+
} else if (shortcuts.all && key.name === shortcuts.all) {
|
|
175
|
+
const selectAll = items.some((choice) => isSelectable(choice) && !choice.checked && choice.value !== cancelValue);
|
|
176
|
+
setItems(items.map((item) => {
|
|
177
|
+
if (isSelectable(item) && item.value !== cancelValue) {
|
|
178
|
+
return { ...item, checked: selectAll };
|
|
179
|
+
}
|
|
180
|
+
return item;
|
|
181
|
+
}));
|
|
182
|
+
} else if (shortcuts.invert && key.name === shortcuts.invert) {
|
|
183
|
+
setItems(items.map((item) => {
|
|
184
|
+
if (isSelectable(item) && item.value !== cancelValue) {
|
|
185
|
+
return toggle(item);
|
|
186
|
+
}
|
|
187
|
+
return item;
|
|
188
|
+
}));
|
|
189
|
+
} else if (isNumberKey(key)) {
|
|
190
|
+
const selectedIndex = Number(key.name) - 1;
|
|
191
|
+
let selectableIndex = -1;
|
|
192
|
+
const position = items.findIndex((item) => {
|
|
193
|
+
if (Separator.isSeparator(item)) return false;
|
|
194
|
+
selectableIndex++;
|
|
195
|
+
return selectableIndex === selectedIndex;
|
|
196
|
+
});
|
|
197
|
+
const selectedItem = items[position];
|
|
198
|
+
if (selectedItem && isSelectable(selectedItem)) {
|
|
199
|
+
setActive(position);
|
|
200
|
+
setItems(items.map((choice, i) => (i === position ? toggle(choice) : choice)));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const message = theme.style.message(config.message, status);
|
|
206
|
+
let description;
|
|
207
|
+
const page = usePagination({
|
|
208
|
+
items,
|
|
209
|
+
active,
|
|
210
|
+
renderItem({ item, isActive }) {
|
|
211
|
+
if (Separator.isSeparator(item)) {
|
|
212
|
+
return ` ${item.separator}`;
|
|
213
|
+
}
|
|
214
|
+
if (item.disabled) {
|
|
215
|
+
const disabledLabel = typeof item.disabled === 'string' ? item.disabled : '(disabled)';
|
|
216
|
+
return theme.style.disabledChoice(`${item.name} ${disabledLabel}`);
|
|
217
|
+
}
|
|
218
|
+
if (isActive) {
|
|
219
|
+
description = item.description;
|
|
220
|
+
}
|
|
221
|
+
const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
|
|
222
|
+
const name = item.checked ? item.checkedName : item.name;
|
|
223
|
+
const color = isActive ? theme.style.highlight : (x) => x;
|
|
224
|
+
const cursor = isActive ? theme.icon.cursor : ' ';
|
|
225
|
+
return color(`${cursor}${checkbox} ${name}`);
|
|
226
|
+
},
|
|
227
|
+
pageSize,
|
|
228
|
+
loop,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (status === 'done') {
|
|
232
|
+
if (cancelled) {
|
|
233
|
+
return [prefix, message].filter(Boolean).join(' ');
|
|
234
|
+
}
|
|
235
|
+
const selection = items.filter(item => isChecked(item) && item.value !== cancelValue);
|
|
236
|
+
const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
|
|
237
|
+
return [prefix, message, answer].filter(Boolean).join(' ');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const keys = [
|
|
241
|
+
['↑↓', 'navigate'],
|
|
242
|
+
['space', 'select'],
|
|
243
|
+
];
|
|
244
|
+
if (shortcuts.all) keys.push([shortcuts.all, 'all']);
|
|
245
|
+
if (shortcuts.invert) keys.push([shortcuts.invert, 'invert']);
|
|
246
|
+
keys.push(['⏎', 'submit']);
|
|
247
|
+
keys.push(['q', 'quit']);
|
|
248
|
+
const helpLine = theme.style.keysHelpTip(keys);
|
|
249
|
+
const lines = [
|
|
250
|
+
[prefix, message].filter(Boolean).join(' '),
|
|
251
|
+
page,
|
|
252
|
+
' ',
|
|
253
|
+
description ? theme.style.description(description) : '',
|
|
254
|
+
errorMsg ? theme.style.error(errorMsg) : '',
|
|
255
|
+
helpLine,
|
|
256
|
+
]
|
|
257
|
+
.filter(Boolean)
|
|
258
|
+
.join('\n')
|
|
259
|
+
.trimEnd();
|
|
260
|
+
return `${lines}${cursorHide}`;
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
export { Separator };
|
|
264
|
+
export default checkboxCancel;
|