@maccesar/titools 2.0.7 → 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.
Files changed (132) hide show
  1. package/{AGENTS-TEMPLATE.md → AGENTS-VERCEL-RESEARCH.md} +16 -18
  2. package/README.md +250 -168
  3. package/agents/{ti-researcher.md → ti-pro.md} +10 -10
  4. package/bin/titools.js +16 -12
  5. package/lib/cleanup.js +95 -0
  6. package/lib/commands/agents.js +146 -70
  7. package/lib/commands/skills.js +349 -0
  8. package/lib/commands/uninstall.js +189 -154
  9. package/lib/commands/update.js +104 -21
  10. package/lib/config.js +26 -20
  11. package/lib/downloader.js +1 -1
  12. package/lib/installer.js +37 -97
  13. package/lib/platform.js +9 -5
  14. package/lib/prompts/checkboxCancel.js +264 -0
  15. package/lib/prompts/selectCancel.js +204 -0
  16. package/lib/symlink.js +19 -7
  17. package/lib/utils.js +17 -17
  18. package/package.json +10 -10
  19. package/skills/alloy-guides/SKILL.md +8 -30
  20. package/skills/alloy-guides/references/CLI_TASKS.md +14 -24
  21. package/skills/alloy-guides/references/CONCEPTS.md +5 -25
  22. package/skills/alloy-guides/references/CONTROLLERS.md +2 -19
  23. package/skills/alloy-guides/references/MODELS.md +40 -10
  24. package/skills/alloy-guides/references/PURGETSS.md +1 -11
  25. package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +8 -14
  26. package/skills/alloy-guides/references/VIEWS_STYLES.md +14 -25
  27. package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +2 -8
  28. package/skills/alloy-guides/references/VIEWS_XML.md +29 -40
  29. package/skills/alloy-guides/references/WIDGETS.md +1 -17
  30. package/skills/alloy-howtos/SKILL.md +1 -22
  31. package/skills/alloy-howtos/references/best_practices.md +0 -17
  32. package/skills/alloy-howtos/references/cli_reference.md +1 -23
  33. package/skills/alloy-howtos/references/config_files.md +86 -15
  34. package/skills/alloy-howtos/references/custom_tags.md +14 -13
  35. package/skills/alloy-howtos/references/debugging_troubleshooting.md +3 -22
  36. package/skills/alloy-howtos/references/samples.md +4 -14
  37. package/skills/purgetss/SKILL.md +11 -37
  38. package/skills/purgetss/references/EXAMPLES.md +106 -17
  39. package/skills/purgetss/references/animation-system.md +0 -67
  40. package/skills/purgetss/references/apply-directive.md +0 -18
  41. package/skills/purgetss/references/arbitrary-values.md +0 -25
  42. package/skills/purgetss/references/class-index.md +0 -46
  43. package/skills/purgetss/references/cli-commands.md +0 -41
  44. package/skills/purgetss/references/configurable-properties.md +0 -55
  45. package/skills/purgetss/references/custom-rules.md +0 -14
  46. package/skills/purgetss/references/customization-deep-dive.md +0 -49
  47. package/skills/purgetss/references/dynamic-component-creation.md +56 -37
  48. package/skills/purgetss/references/grid-layout.md +0 -32
  49. package/skills/purgetss/references/icon-fonts.md +0 -50
  50. package/skills/purgetss/references/installation-setup.md +61 -32
  51. package/skills/purgetss/references/migration-guide.md +127 -0
  52. package/skills/purgetss/references/opacity-modifier.md +0 -34
  53. package/skills/purgetss/references/performance-tips.md +261 -0
  54. package/skills/purgetss/references/platform-modifiers.md +0 -46
  55. package/skills/purgetss/references/smart-mappings.md +0 -12
  56. package/skills/purgetss/references/tikit-components.md +393 -0
  57. package/skills/purgetss/references/titanium-resets.md +1 -27
  58. package/skills/purgetss/references/ui-ux-design.md +467 -117
  59. package/skills/{alloy-expert → ti-expert}/SKILL.md +45 -54
  60. package/skills/ti-expert/references/alloy-builtins.md +387 -0
  61. package/skills/{alloy-expert → ti-expert}/references/alloy-structure.md +147 -40
  62. package/skills/ti-expert/references/anti-patterns.md +90 -0
  63. package/skills/ti-expert/references/cli-expert.md +109 -0
  64. package/skills/{alloy-expert → ti-expert}/references/code-conventions.md +182 -49
  65. package/skills/{alloy-expert → ti-expert}/references/controller-patterns.md +103 -93
  66. package/skills/{alloy-expert → ti-expert}/references/error-handling.md +12 -12
  67. package/skills/{alloy-expert → ti-expert}/references/examples.md +144 -85
  68. package/skills/{alloy-expert → ti-expert}/references/migration-patterns.md +38 -36
  69. package/skills/{alloy-expert → ti-expert}/references/patterns.md +8 -8
  70. package/skills/ti-expert/references/performance-listview.md +251 -0
  71. package/skills/{alloy-expert/references/performance-patterns.md → ti-expert/references/performance-optimization.md} +67 -283
  72. package/skills/{alloy-expert/references/security-patterns.md → ti-expert/references/security-device.md} +12 -295
  73. package/skills/ti-expert/references/security-fundamentals.md +284 -0
  74. package/skills/{alloy-expert → ti-expert}/references/state-management.md +10 -10
  75. package/skills/ti-expert/references/testing-e2e-ci.md +432 -0
  76. package/skills/ti-expert/references/testing-unit.md +433 -0
  77. package/skills/ti-expert/references/theming.md +394 -0
  78. package/skills/ti-guides/SKILL.md +1 -14
  79. package/skills/ti-guides/references/advanced-data-and-images.md +126 -10
  80. package/skills/ti-guides/references/app-distribution.md +48 -10
  81. package/skills/ti-guides/references/application-frameworks.md +9 -2
  82. package/skills/ti-guides/references/cli-reference.md +338 -82
  83. package/skills/ti-guides/references/coding-best-practices.md +14 -4
  84. package/skills/ti-guides/references/commonjs-advanced.md +103 -6
  85. package/skills/ti-guides/references/hello-world.md +5 -2
  86. package/skills/ti-guides/references/hyperloop-native-access.md +439 -46
  87. package/skills/ti-guides/references/javascript-primer.md +4 -3
  88. package/skills/ti-guides/references/resources.md +0 -1
  89. package/skills/ti-guides/references/style-and-conventions.md +57 -9
  90. package/skills/ti-guides/references/tiapp-config.md +48 -6
  91. package/skills/ti-howtos/SKILL.md +2 -27
  92. package/skills/ti-howtos/references/android-platform-deep-dives.md +41 -90
  93. package/skills/ti-howtos/references/automation-fastlane-appium.md +15 -14
  94. package/skills/ti-howtos/references/buffer-codec-streams.md +22 -22
  95. package/skills/ti-howtos/references/cross-platform-development.md +68 -37
  96. package/skills/ti-howtos/references/debugging-profiling.md +8 -53
  97. package/skills/ti-howtos/references/extending-titanium.md +12 -51
  98. package/skills/ti-howtos/references/google-maps-v2.md +30 -29
  99. package/skills/ti-howtos/references/ios-map-kit.md +19 -25
  100. package/skills/ti-howtos/references/ios-platform-deep-dives.md +95 -283
  101. package/skills/ti-howtos/references/local-data-sources.md +45 -37
  102. package/skills/ti-howtos/references/location-and-maps.md +47 -28
  103. package/skills/ti-howtos/references/media-apis.md +84 -27
  104. package/skills/ti-howtos/references/notification-services.md +40 -89
  105. package/skills/ti-howtos/references/remote-data-sources.md +21 -36
  106. package/skills/ti-howtos/references/tutorials.md +79 -39
  107. package/skills/ti-howtos/references/using-modules.md +4 -30
  108. package/skills/ti-howtos/references/web-content-integration.md +28 -45
  109. package/skills/ti-howtos/references/webpack-build-pipeline.md +54 -18
  110. package/skills/ti-ui/SKILL.md +1 -21
  111. package/skills/ti-ui/references/accessibility-deep-dive.md +71 -25
  112. package/skills/ti-ui/references/animation-and-matrices.md +0 -47
  113. package/skills/ti-ui/references/application-structures.md +0 -46
  114. package/skills/ti-ui/references/custom-fonts-styling.md +9 -57
  115. package/skills/ti-ui/references/event-handling.md +32 -40
  116. package/skills/ti-ui/references/gestures.md +0 -40
  117. package/skills/ti-ui/references/icons-and-splash-screens.md +21 -44
  118. package/skills/ti-ui/references/layouts-and-positioning.md +47 -39
  119. package/skills/ti-ui/references/listviews-and-performance.md +187 -73
  120. package/skills/ti-ui/references/orientation.md +57 -71
  121. package/skills/ti-ui/references/platform-ui-android.md +114 -48
  122. package/skills/ti-ui/references/platform-ui-ios.md +63 -38
  123. package/skills/ti-ui/references/scrolling-views.md +14 -23
  124. package/skills/ti-ui/references/tableviews.md +6 -56
  125. package/lib/commands/install.js +0 -188
  126. package/skills/alloy-expert/references/anti-patterns.md +0 -133
  127. package/skills/alloy-expert/references/testing.md +0 -872
  128. package/skills/ti-guides/references/alloy-cli-advanced.md +0 -84
  129. package/skills/ti-guides/references/alloy-data-mastery.md +0 -29
  130. package/skills/ti-guides/references/alloy-widgets-and-themes.md +0 -19
  131. /package/skills/{alloy-expert → ti-expert}/assets/ControllerAutoCleanup.js +0 -0
  132. /package/skills/{alloy-expert → ti-expert}/references/contracts.md +0 -0
@@ -3,116 +3,27 @@
3
3
  * Removes installed skills and agents
4
4
  */
5
5
 
6
- import inquirer from 'inquirer';
7
6
  import chalk from 'chalk';
8
7
  import ora from 'ora';
9
- import { SKILLS, AGENTS } from '../config.js';
8
+ import { SKILLS } from '../config.js';
10
9
  import {
11
10
  detectPlatforms,
12
11
  } from '../platform.js';
13
- import { remove } from 'fs-extra';
14
- import { existsSync, rmSync } from 'fs';
15
- import { join } from 'path';
16
12
  import {
17
- getAgentsSkillsDir,
18
- getClaudeAgentsDir,
19
- getClaudeSkillsDir,
20
- getGeminiSkillsDir,
21
- getCodexSkillsDir,
22
- } from '../config.js';
23
-
24
- /**
25
- * Remove skill symlinks from a platform directory
26
- * @param {string} platformSkillsDir - Platform skills directory
27
- * @returns {Object} Results object with success/failure counts
28
- */
29
- function removeSkillSymlinks(platformSkillsDir) {
30
- const results = {
31
- removed: [],
32
- failed: [],
33
- };
34
-
35
- if (!existsSync(platformSkillsDir)) {
36
- return results;
37
- }
38
-
39
- for (const skill of SKILLS) {
40
- const linkPath = join(platformSkillsDir, skill);
41
-
42
- if (existsSync(linkPath)) {
43
- try {
44
- rmSync(linkPath, { recursive: true, force: true });
45
- results.removed.push(skill);
46
- } catch (error) {
47
- results.failed.push(skill);
48
- }
49
- }
50
- }
51
-
52
- return results;
53
- }
54
-
55
- /**
56
- * Remove skills from central directory
57
- * @returns {Object} Results object
58
- */
59
- function removeSkills() {
60
- const skillsDir = getAgentsSkillsDir();
61
- const results = {
62
- removed: [],
63
- failed: [],
64
- };
65
-
66
- if (!existsSync(skillsDir)) {
67
- return results;
68
- }
69
-
70
- for (const skill of SKILLS) {
71
- const skillPath = join(skillsDir, skill);
72
-
73
- if (existsSync(skillPath)) {
74
- try {
75
- rmSync(skillPath, { recursive: true, force: true });
76
- results.removed.push(skill);
77
- } catch (error) {
78
- results.failed.push(skill);
79
- }
80
- }
81
- }
82
-
83
- return results;
84
- }
85
-
86
- /**
87
- * Remove agents from Claude Code
88
- * @returns {Object} Results object
89
- */
90
- function removeAgents() {
91
- const agentsDir = getClaudeAgentsDir();
92
- const results = {
93
- removed: [],
94
- failed: [],
95
- };
96
-
97
- if (!existsSync(agentsDir)) {
98
- return results;
99
- }
100
-
101
- for (const agent of AGENTS) {
102
- const agentPath = join(agentsDir, `${agent}.md`);
103
-
104
- if (existsSync(agentPath)) {
105
- try {
106
- rmSync(agentPath);
107
- results.removed.push(agent);
108
- } catch (error) {
109
- results.failed.push(agent);
110
- }
111
- }
112
- }
113
-
114
- return results;
115
- }
13
+ removeSkillSymlinks,
14
+ removeSkills,
15
+ removeAgents,
16
+ } from '../cleanup.js';
17
+ import checkbox, { Separator } from '../prompts/checkboxCancel.js';
18
+ import {
19
+ isTitaniumProject,
20
+ blockExists,
21
+ removeOldBlock,
22
+ } from '../utils.js';
23
+ import { existsSync } from 'fs';
24
+ import { join, resolve } from 'path';
25
+ import { getAgentsSkillsDir, getClaudeAgentsDir } from '../config.js';
26
+ import { getSkillList, getAgentList } from '../cleanup.js';
116
27
 
117
28
  /**
118
29
  * Uninstall command handler
@@ -123,93 +34,217 @@ export async function uninstallCommand(options) {
123
34
  console.log(chalk.bold.blue('Titanium SDK Skills Uninstaller'));
124
35
  console.log('');
125
36
 
126
- // Detect installed platforms
127
- const detectedPlatforms = detectPlatforms();
128
-
129
- if (detectedPlatforms.length === 0) {
130
- console.log(chalk.yellow('No AI coding assistants detected.'));
131
- console.log('However, you can still remove skills from the central directory.');
37
+ const projectDir = resolve(process.cwd());
38
+ const isProject = isTitaniumProject(projectDir);
39
+ const baseDir = options.local ? projectDir : undefined;
40
+ if (options.local) {
41
+ console.log(chalk.cyan('Mode: Local uninstallation (current project)'));
132
42
  console.log('');
133
43
  }
134
44
 
135
- // Show detected platforms
136
- for (const platform of detectedPlatforms) {
137
- console.log(chalk.green('✓'), `${platform.displayName} detected`);
45
+ // Detect installed platforms
46
+ const detectedPlatforms = detectPlatforms(baseDir);
47
+
48
+ const knowledgeFiles = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md'];
49
+ const existingKnowledgeFiles = knowledgeFiles
50
+ .map((file) => ({ file, path: join(projectDir, file) }))
51
+ .filter(({ path }) => existsSync(path));
52
+ const hasKnowledgeBlocks = isProject && existingKnowledgeFiles.some(({ path }) => blockExists(path));
53
+
54
+ const skillList = getSkillList();
55
+ const agentList = getAgentList().map((agent) => `${agent}.md`);
56
+ const homeSkillsDir = getAgentsSkillsDir();
57
+ const projectSkillsDir = getAgentsSkillsDir(projectDir);
58
+ const homeAgentsDir = getClaudeAgentsDir();
59
+ const projectAgentsDir = getClaudeAgentsDir(projectDir);
60
+
61
+ const hasAnyInDir = (dir, names) =>
62
+ !!dir && existsSync(dir) && names.some((name) => existsSync(join(dir, name)));
63
+
64
+ const hasHomeSkills = hasAnyInDir(homeSkillsDir, skillList);
65
+ const hasProjectSkills = isProject && hasAnyInDir(projectSkillsDir, skillList);
66
+ const hasHomeAgents = hasAnyInDir(homeAgentsDir, agentList);
67
+ const hasProjectAgents = isProject && hasAnyInDir(projectAgentsDir, agentList);
68
+
69
+ const hasHomeSymlinks = detectedPlatforms.some((platform) =>
70
+ hasAnyInDir(platform.skillsDir, skillList)
71
+ );
72
+ const hasProjectSymlinks = isProject && detectPlatforms(projectDir).some((platform) =>
73
+ hasAnyInDir(platform.skillsDir, skillList)
74
+ );
75
+
76
+ const choices = [];
77
+ if (hasHomeAgents || hasProjectAgents) {
78
+ choices.push({ name: '`ti-pro` for Claude Code', value: 'agents', checked: true });
79
+ }
80
+ if (hasKnowledgeBlocks) {
81
+ choices.push({ name: 'Knowledge index from context files', value: 'knowledge', checked: true });
82
+ }
83
+ if (hasHomeSkills) {
84
+ choices.push({ name: 'Skills from the `home` directory', value: 'skills-home', checked: false });
85
+ }
86
+ if (hasProjectSkills) {
87
+ choices.push({ name: 'Skills from the `project` directory', value: 'skills-project', checked: false });
88
+ }
89
+ if (hasHomeSymlinks) {
90
+ choices.push({ name: 'Skill symlinks from `home` directory', value: 'symlinks-home', checked: false });
91
+ }
92
+ if (hasProjectSymlinks) {
93
+ choices.push({ name: 'Skill symlinks from `project` directory', value: 'symlinks-project', checked: false });
138
94
  }
139
- console.log('');
140
95
 
141
- // Select what to uninstall
142
- const answers = await inquirer.prompt([
143
- {
144
- type: 'checkbox',
145
- name: 'targets',
146
- message: 'What do you want to uninstall?',
96
+ if (choices.length === 0) {
97
+ console.log(chalk.yellow('No skills, agents, symlinks, or knowledge index blocks found.'));
98
+ console.log('');
99
+ return;
100
+ }
101
+
102
+ // Modern functional checkbox selection
103
+ let targets = [];
104
+ try {
105
+ targets = await checkbox({
106
+ message: 'What do you want to uninstall:',
147
107
  choices: [
148
- { name: 'Skill symlinks from all platforms', value: 'symlinks', checked: true },
149
- { name: 'Skills from central directory (~/.agents/skills/)', value: 'skills', checked: false },
150
- { name: 'Agents from Claude Code', value: 'agents', checked: false },
108
+ ...choices,
109
+ new Separator(' '),
151
110
  ],
152
- validate: (answer) => answer.length > 0 || 'Please select at least one option',
153
- },
154
- ]);
111
+ shortcuts: { invert: null },
112
+ theme: {
113
+ style: {
114
+ renderSelectedChoices: () => '',
115
+ },
116
+ },
117
+ });
118
+ } catch (error) {
119
+ console.log('\nCancelled.');
120
+ process.exit(0);
121
+ }
155
122
 
156
- if (answers.targets.includes('symlinks') && detectedPlatforms.length === 0) {
157
- console.log(chalk.yellow('No platforms detected, skipping symlink removal.'));
158
- answers.targets = answers.targets.filter((t) => t !== 'symlinks');
123
+ // Handle cancellation (by 'q')
124
+ if (targets.includes('cancel')) {
125
+ console.log('Cancelled.');
126
+ return;
159
127
  }
160
128
 
161
- if (answers.targets.length === 0) {
162
- console.log('Nothing to uninstall.');
163
- process.exit(0);
129
+ if (targets.length === 0) {
130
+ console.log(chalk.yellow('Nothing to uninstall. Cancelled.'));
131
+ return;
164
132
  }
165
133
 
166
134
  const spinner = ora();
135
+ let actionTaken = false;
167
136
 
168
- // Remove symlinks from platforms
169
- if (answers.targets.includes('symlinks')) {
137
+ if (targets.includes('symlinks-home')) {
170
138
  for (const platform of detectedPlatforms) {
171
139
  spinner.start(`Removing ${platform.displayName} symlinks...`);
172
140
  const symlinkResult = removeSkillSymlinks(platform.skillsDir);
173
141
 
174
142
  if (symlinkResult.removed.length > 0) {
175
- spinner.succeed(`${platform.displayName}: ${symlinkResult.removed.length} removed`);
143
+ spinner.succeed(`${platform.displayName}: Skills unlinked`);
144
+ actionTaken = true;
145
+ } else {
146
+ spinner.info(`${platform.displayName}: No symlinks found`);
147
+ }
148
+ }
149
+ }
150
+
151
+ if (targets.includes('symlinks-project')) {
152
+ const projectPlatforms = detectPlatforms(projectDir);
153
+ for (const platform of projectPlatforms) {
154
+ spinner.start(`Removing ${platform.displayName} symlinks...`);
155
+ const symlinkResult = removeSkillSymlinks(platform.skillsDir);
156
+
157
+ if (symlinkResult.removed.length > 0) {
158
+ spinner.succeed(`${platform.displayName}: Skills unlinked`);
159
+ actionTaken = true;
176
160
  } else {
177
161
  spinner.info(`${platform.displayName}: No symlinks found`);
178
162
  }
179
163
  }
180
164
  }
181
165
 
182
- // Remove skills from central directory
183
- if (answers.targets.includes('skills')) {
184
- spinner.start('Removing skills from central directory...');
185
- const skillsResult = removeSkills();
166
+ if (targets.includes('skills-home')) {
167
+ spinner.start('Removing skills...');
168
+ const skillsResult = removeSkills(undefined);
169
+
170
+ if (skillsResult.removed.length > 0) {
171
+ spinner.succeed(`${SKILLS.length} skills removed`);
172
+ actionTaken = true;
173
+ } else {
174
+ spinner.info('No skills to remove');
175
+ }
176
+ }
177
+
178
+ if (targets.includes('skills-project')) {
179
+ spinner.start('Removing skills...');
180
+ const skillsResult = removeSkills(projectDir);
186
181
 
187
182
  if (skillsResult.removed.length > 0) {
188
- spinner.succeed(`Skills: ${skillsResult.removed.join(', ')}`);
183
+ spinner.succeed(`${SKILLS.length} skills removed`);
184
+ actionTaken = true;
189
185
  } else {
190
- spinner.info('No skills found in central directory');
186
+ spinner.info('No skills to remove');
191
187
  }
192
188
  }
193
189
 
194
- // Remove agents
195
- if (answers.targets.includes('agents')) {
190
+ if (targets.includes('agents')) {
196
191
  spinner.start('Removing agents...');
197
- const agentsResult = removeAgents();
192
+ const results = [];
193
+ results.push(removeAgents(undefined));
194
+ if (isProject) {
195
+ results.push(removeAgents(projectDir));
196
+ }
198
197
 
199
- if (agentsResult.removed.length > 0) {
200
- spinner.succeed(`Agents: ${agentsResult.removed.join(', ')}`);
198
+ const removed = results.flatMap((r) => r.removed);
199
+ if (removed.length > 0) {
200
+ spinner.succeed('Platform agents removed');
201
+ actionTaken = true;
201
202
  } else {
202
- spinner.info('No agents found');
203
+ spinner.info('No agents to remove');
203
204
  }
204
205
  }
205
206
 
206
- console.log('');
207
- console.log(chalk.green('✓ Uninstallation complete!'));
208
- console.log('');
209
- console.log(chalk.gray('Note: AGENTS.md/CLAUDE.md/GEMINI.md files in your projects were NOT removed.'));
210
- console.log(chalk.gray('To remove them manually:'));
211
- console.log(chalk.gray(' rm -f /path/to/your/project/AGENTS.md'));
212
- console.log('');
207
+ // Remove knowledge index blocks from project files
208
+ if (targets.includes('knowledge')) {
209
+ if (!isProject) {
210
+ spinner.info('Not a Titanium project, skipping knowledge cleanup');
211
+ } else {
212
+ spinner.start('Removing knowledge index...');
213
+ const cleaned = [];
214
+ for (const { file, path } of existingKnowledgeFiles) {
215
+ if (blockExists(path)) {
216
+ try {
217
+ removeOldBlock(path);
218
+ cleaned.push(file);
219
+ } catch {
220
+ // Ignore; we only report if no files were cleaned
221
+ }
222
+ }
223
+ }
224
+
225
+ if (cleaned.length > 0) {
226
+ spinner.succeed(`Knowledge index removed from: ${cleaned.join(', ')}`);
227
+ actionTaken = true;
228
+ } else {
229
+ spinner.info('No knowledge index blocks to remove');
230
+ }
231
+ }
232
+ }
233
+
234
+ if (actionTaken) {
235
+ console.log('');
236
+ console.log(chalk.green('✓ Uninstallation complete!'));
237
+ console.log('');
238
+ if (isProject && !targets.includes('knowledge')) {
239
+ console.log(chalk.gray('Note: Knowledge index blocks were not removed.'));
240
+ console.log('');
241
+ }
242
+ console.log('');
243
+ } else {
244
+ console.log('');
245
+ console.log(chalk.yellow('No changes were necessary.'));
246
+ console.log('');
247
+ }
213
248
  }
214
249
 
215
250
  export default uninstallCommand;
@@ -8,39 +8,104 @@ import ora from 'ora';
8
8
  import {
9
9
  PACKAGE_VERSION,
10
10
  REPO_URL,
11
- REPO_API_URL,
12
11
  SKILLS,
13
- AGENTS,
14
12
  } from '../config.js';
13
+ import select from '../prompts/selectCancel.js';
15
14
  import {
16
15
  detectPlatforms,
17
16
  } from '../platform.js';
17
+ import { cleanupLegacyArtifacts } from '../cleanup.js';
18
18
  import {
19
19
  installSkills,
20
20
  installAgents,
21
- installAgentsTemplate,
22
21
  getLocalRepoDir,
23
22
  } from '../installer.js';
23
+ import { agentsCommand } from './agents.js';
24
24
  import {
25
25
  downloadRepoArchive,
26
26
  checkForUpdate,
27
27
  } from '../downloader.js';
28
28
  import { createSkillSymlinks } from '../symlink.js';
29
- import { formatList } from '../utils.js';
29
+ import { formatList, isTitaniumProject } from '../utils.js';
30
+ import { getAgentsSkillsDir } from '../config.js';
30
31
  import { mkdtemp } from 'fs/promises';
31
- import { join } from 'path';
32
+ import { existsSync } from 'fs';
33
+ import { join, resolve } from 'path';
32
34
  import { tmpdir } from 'os';
33
35
 
34
36
  /**
35
37
  * Update command handler
38
+ * @param {Object} options - Command options
36
39
  */
37
- export async function updateCommand() {
40
+ export async function updateCommand(options) {
38
41
  console.log('');
39
42
  console.log(chalk.bold.blue('Titanium SDK Skills Updater'));
40
43
  console.log('');
41
44
 
42
45
  const spinner = ora();
43
46
 
47
+ // Determine base directory
48
+ let baseDir = options.local ? process.cwd() : undefined;
49
+ const hasSkillsAt = (dir) =>
50
+ SKILLS.some((skill) => existsSync(join(getAgentsSkillsDir(dir), skill)));
51
+
52
+ // If not explicitly local, check if local skills exist to ask user
53
+ if (!options.local) {
54
+ const projectDir = process.cwd();
55
+ const isProject = isTitaniumProject(projectDir);
56
+ const localPlatforms = detectPlatforms(projectDir);
57
+ const hasLocalSkills = isProject && hasSkillsAt(projectDir);
58
+ const hasGlobalSkills = hasSkillsAt(undefined);
59
+ if (hasLocalSkills && !hasGlobalSkills) {
60
+ baseDir = projectDir;
61
+ } else if (isProject && localPlatforms.length > 0) {
62
+ try {
63
+ const scope = await select({
64
+ message: 'Local installation detected. What do you want to update:',
65
+ choices: [
66
+ { name: 'Global skills (user home)', value: 'global' },
67
+ { name: 'Local skills (current project)', value: 'local' },
68
+ ],
69
+ theme: {
70
+ style: {
71
+ answer: () => '',
72
+ prefix: () => chalk.cyan('?'),
73
+ },
74
+ },
75
+ });
76
+ if (scope === 'cancel') {
77
+ console.log('Cancelled.');
78
+ process.exit(0);
79
+ }
80
+ if (scope === 'local') {
81
+ baseDir = process.cwd();
82
+ }
83
+ } catch (error) {
84
+ console.log('\nCancelled.');
85
+ process.exit(0);
86
+ }
87
+ }
88
+ }
89
+
90
+ if (baseDir) {
91
+ console.log(chalk.cyan('Mode: Local update (current project)'));
92
+ } else {
93
+ console.log(chalk.cyan('Mode: Global update (user home)'));
94
+ }
95
+ console.log('');
96
+
97
+ const skillsDir = getAgentsSkillsDir(baseDir);
98
+ const hasSkillsInstalled = skillsDir && SKILLS.some((skill) => existsSync(join(skillsDir, skill)));
99
+ if (!hasSkillsInstalled) {
100
+ console.log(chalk.yellow('No skills installed at this location.'));
101
+ console.log('Install them first with:');
102
+ console.log(' titools install');
103
+ console.log('');
104
+ console.log('Looked for skills in:');
105
+ console.log(` ${baseDir ? 'Local' : 'Global'}: ${skillsDir}`);
106
+ return;
107
+ }
108
+
44
109
  // Check for updates
45
110
  spinner.start('Checking for updates...');
46
111
 
@@ -49,8 +114,17 @@ export async function updateCommand() {
49
114
 
50
115
  if (!hasUpdate) {
51
116
  spinner.info(`Already up to date (v${PACKAGE_VERSION})`);
117
+ cleanupLegacyArtifacts(baseDir);
52
118
  console.log('');
53
119
  console.log(chalk.green('✓'), 'Skills and agents are already at the latest version');
120
+ const projectDir = resolve(process.cwd());
121
+ if (isTitaniumProject(projectDir)) {
122
+ const aiFiles = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md'];
123
+ const hasAnyAiFile = aiFiles.some((file) => existsSync(join(projectDir, file)));
124
+ if (hasAnyAiFile) {
125
+ await agentsCommand(projectDir, { onlyExisting: true, force: true });
126
+ }
127
+ }
54
128
  console.log('');
55
129
  return;
56
130
  }
@@ -61,12 +135,16 @@ export async function updateCommand() {
61
135
  console.log(chalk.gray(`Latest: (from GitHub)`));
62
136
  console.log('');
63
137
 
64
- // Detect installed platforms
65
- const detectedPlatforms = detectPlatforms();
138
+ // Detect installed platforms at target
139
+ const detectedPlatforms = detectPlatforms(baseDir);
66
140
 
67
141
  if (detectedPlatforms.length === 0) {
68
142
  console.log(chalk.yellow('No AI coding assistants detected.'));
69
- console.log('Update will install skills to ~/.agents/skills/');
143
+ if (baseDir) {
144
+ console.log('Update will install skills to ./.agents/skills/');
145
+ } else {
146
+ console.log('Update will install skills to ~/.agents/skills/');
147
+ }
70
148
  console.log('');
71
149
  } else {
72
150
  for (const platform of detectedPlatforms) {
@@ -91,14 +169,14 @@ export async function updateCommand() {
91
169
  try {
92
170
  // Install skills
93
171
  spinner.start('Updating skills...');
94
- const skillsResult = await installSkills(repoDir);
172
+ const skillsResult = await installSkills(repoDir, baseDir);
95
173
  spinner.succeed(
96
174
  `Skills: ${formatList(skillsResult.installed)}`
97
175
  );
98
176
 
99
177
  // Install agents
100
178
  spinner.start('Updating agents...');
101
- const agentsResult = await installAgents(repoDir);
179
+ const agentsResult = await installAgents(repoDir, baseDir);
102
180
  if (agentsResult.installed.length > 0) {
103
181
  spinner.succeed(
104
182
  `Agents: ${formatList(agentsResult.installed)}`
@@ -107,21 +185,15 @@ export async function updateCommand() {
107
185
  spinner.info('No agents to update');
108
186
  }
109
187
 
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
- }
188
+ cleanupLegacyArtifacts(baseDir);
118
189
 
119
190
  // Update symlinks for detected platforms
120
191
  for (const platform of detectedPlatforms) {
121
192
  spinner.start(`Updating ${platform.displayName} symlinks...`);
122
193
  const symlinkResult = await createSkillSymlinks(
123
194
  platform.skillsDir,
124
- SKILLS
195
+ SKILLS,
196
+ baseDir
125
197
  );
126
198
  if (symlinkResult.linked.length === SKILLS.length) {
127
199
  spinner.succeed(`${platform.displayName} linked`);
@@ -136,7 +208,18 @@ export async function updateCommand() {
136
208
  console.log('');
137
209
  console.log(chalk.green('✓ Update complete!'));
138
210
  console.log('');
139
- console.log(chalk.bold('▸'), 'Run in your Titanium project:', chalk.cyan('titools agents'));
211
+ const projectDir = resolve(process.cwd());
212
+ if (isTitaniumProject(projectDir)) {
213
+ const aiFiles = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md'];
214
+ const hasAnyAiFile = aiFiles.some((file) => existsSync(join(projectDir, file)));
215
+ if (hasAnyAiFile) {
216
+ await agentsCommand(projectDir, { onlyExisting: true, force: true });
217
+ } else {
218
+ console.log(chalk.bold('▸'), 'Run in the Titanium project:', chalk.cyan('titools sync'));
219
+ }
220
+ } else {
221
+ console.log(chalk.bold('▸'), 'Run in the Titanium project:', chalk.cyan('titools sync'));
222
+ }
140
223
  console.log('');
141
224
 
142
225
  } finally {