@maccesar/titools 2.2.2 → 2.2.4
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/README.md +8 -7
- package/lib/commands/update.js +86 -99
- package/package.json +1 -1
- package/skills/ti-expert/references/theming.md +51 -9
package/README.md
CHANGED
|
@@ -619,13 +619,14 @@ Behavior depends on where you run it:
|
|
|
619
619
|
| Inside a Titanium project (with existing knowledge index files) | Also refreshes `AGENTS.md`/`CLAUDE.md`/`GEMINI.md` |
|
|
620
620
|
|
|
621
621
|
What it does:
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
622
|
+
1. Checks GitHub for the latest CLI version
|
|
623
|
+
2. If a newer version exists → prompts you to update the CLI first with `npm update -g @maccesar/titools`
|
|
624
|
+
3. If CLI is current → syncs skills and agents from the installed package (no download needed)
|
|
625
|
+
4. Updates platform symlinks only for platforms that already have them
|
|
626
|
+
5. Cleans up legacy artifacts (`alloy-expert` skill, `ti-researcher` agent)
|
|
627
|
+
6. Auto-syncs knowledge index files if they exist in the current project
|
|
628
|
+
|
|
629
|
+
Note: This command syncs knowledge packages and agents from your installed CLI. To get new features, first update the CLI with `npm update -g @maccesar/titools`, then run `titools update`.
|
|
629
630
|
|
|
630
631
|
### titools remove
|
|
631
632
|
|
package/lib/commands/update.js
CHANGED
|
@@ -14,7 +14,7 @@ import select from '../prompts/selectCancel.js';
|
|
|
14
14
|
import {
|
|
15
15
|
detectPlatforms,
|
|
16
16
|
} from '../platform.js';
|
|
17
|
-
import { cleanupLegacyArtifacts } from '../cleanup.js';
|
|
17
|
+
import { cleanupLegacyArtifacts, getSkillList } from '../cleanup.js';
|
|
18
18
|
import {
|
|
19
19
|
installSkills,
|
|
20
20
|
installAgents,
|
|
@@ -22,16 +22,25 @@ import {
|
|
|
22
22
|
} from '../installer.js';
|
|
23
23
|
import { agentsCommand } from './agents.js';
|
|
24
24
|
import {
|
|
25
|
-
downloadRepoArchive,
|
|
26
25
|
checkForUpdate,
|
|
26
|
+
fetchLatestVersion,
|
|
27
27
|
} from '../downloader.js';
|
|
28
28
|
import { createSkillSymlinks } from '../symlink.js';
|
|
29
29
|
import { formatList, isTitaniumProject } from '../utils.js';
|
|
30
30
|
import { getAgentsSkillsDir } from '../config.js';
|
|
31
|
-
import { mkdtemp } from 'fs/promises';
|
|
32
31
|
import { existsSync } from 'fs';
|
|
33
32
|
import { join, resolve } from 'path';
|
|
34
|
-
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if a platform has any skill symlinks installed
|
|
36
|
+
* @param {string} platformSkillsDir - Platform skills directory
|
|
37
|
+
* @returns {boolean} True if any skill symlink exists
|
|
38
|
+
*/
|
|
39
|
+
function hasAnySkillSymlink(platformSkillsDir) {
|
|
40
|
+
if (!platformSkillsDir || !existsSync(platformSkillsDir)) return false;
|
|
41
|
+
const skillList = getSkillList();
|
|
42
|
+
return skillList.some((skill) => existsSync(join(platformSkillsDir, skill)));
|
|
43
|
+
}
|
|
35
44
|
|
|
36
45
|
/**
|
|
37
46
|
* Update command handler
|
|
@@ -112,122 +121,100 @@ export async function updateCommand(options) {
|
|
|
112
121
|
try {
|
|
113
122
|
const hasUpdate = await checkForUpdate(PACKAGE_VERSION);
|
|
114
123
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
}
|
|
124
|
+
// If there's a newer version on GitHub, prompt user to update CLI first
|
|
125
|
+
if (hasUpdate) {
|
|
126
|
+
let latestVersion = '(newer)';
|
|
127
|
+
try {
|
|
128
|
+
latestVersion = await fetchLatestVersion();
|
|
129
|
+
} catch {
|
|
130
|
+
// Ignore error, we already know there's an update
|
|
127
131
|
}
|
|
128
|
-
console.log('');
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
spinner.succeed(`Update available!`);
|
|
133
|
-
console.log('');
|
|
134
|
-
console.log(chalk.gray(`Current: ${PACKAGE_VERSION}`));
|
|
135
|
-
console.log(chalk.gray(`Latest: (from GitHub)`));
|
|
136
|
-
console.log('');
|
|
137
|
-
|
|
138
|
-
// Detect installed platforms at target
|
|
139
|
-
const detectedPlatforms = detectPlatforms(baseDir);
|
|
140
132
|
|
|
141
|
-
|
|
142
|
-
console.log(chalk.yellow('No AI coding assistants detected.'));
|
|
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
|
-
}
|
|
133
|
+
spinner.warn('New version available');
|
|
148
134
|
console.log('');
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
135
|
+
console.log(chalk.yellow('A newer version is available on npm:'));
|
|
136
|
+
console.log(` Current: ${chalk.gray('v' + PACKAGE_VERSION)}`);
|
|
137
|
+
console.log(` Latest: ${chalk.green(latestVersion)}`);
|
|
138
|
+
console.log('');
|
|
139
|
+
console.log('To update, run:');
|
|
140
|
+
console.log(` ${chalk.cyan('npm update -g @maccesar/titools')}`);
|
|
141
|
+
console.log('');
|
|
142
|
+
console.log('Then run this command again:');
|
|
143
|
+
console.log(` ${chalk.cyan('titools update')}`);
|
|
153
144
|
console.log('');
|
|
145
|
+
return;
|
|
154
146
|
}
|
|
155
147
|
|
|
156
|
-
//
|
|
157
|
-
spinner.
|
|
158
|
-
|
|
159
|
-
let repoDir = getLocalRepoDir();
|
|
160
|
-
let tempDir = null;
|
|
148
|
+
// CLI is up to date, now sync skills from the installed package
|
|
149
|
+
spinner.succeed(`CLI is up to date (v${PACKAGE_VERSION})`);
|
|
161
150
|
|
|
151
|
+
// Get repository directory from the installed package
|
|
152
|
+
const repoDir = getLocalRepoDir();
|
|
162
153
|
if (!repoDir) {
|
|
163
|
-
|
|
164
|
-
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log(chalk.red('Error: Could not locate skills source directory.'));
|
|
156
|
+
console.log('Try reinstalling with:');
|
|
157
|
+
console.log(` ${chalk.cyan('npm install -g titools')}`);
|
|
158
|
+
return;
|
|
165
159
|
}
|
|
166
160
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
161
|
+
// Detect platforms with existing symlinks (only update those)
|
|
162
|
+
const detectedPlatforms = detectPlatforms(baseDir);
|
|
163
|
+
const platformsWithSymlinks = detectedPlatforms.filter((p) =>
|
|
164
|
+
hasAnySkillSymlink(p.skillsDir)
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Install skills
|
|
168
|
+
spinner.start('Syncing skills...');
|
|
169
|
+
const skillsResult = await installSkills(repoDir, baseDir);
|
|
170
|
+
spinner.succeed(`Skills: ${formatList(skillsResult.installed)}`);
|
|
171
|
+
|
|
172
|
+
// Install agents (only if Claude Code has symlinks)
|
|
173
|
+
const claudePlatform = platformsWithSymlinks.find((p) => p.name === 'claude');
|
|
174
|
+
if (claudePlatform) {
|
|
175
|
+
spinner.start('Syncing agents...');
|
|
179
176
|
const agentsResult = await installAgents(repoDir, baseDir);
|
|
180
177
|
if (agentsResult.installed.length > 0) {
|
|
181
|
-
spinner.succeed(
|
|
182
|
-
`Agents: ${formatList(agentsResult.installed)}`
|
|
183
|
-
);
|
|
178
|
+
spinner.succeed(`Agents: ${formatList(agentsResult.installed)}`);
|
|
184
179
|
} else {
|
|
185
|
-
spinner.info('No agents to
|
|
180
|
+
spinner.info('No agents to sync');
|
|
186
181
|
}
|
|
182
|
+
}
|
|
187
183
|
|
|
188
|
-
|
|
184
|
+
cleanupLegacyArtifacts(baseDir);
|
|
189
185
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
} else {
|
|
201
|
-
spinner.warn(
|
|
202
|
-
`${platform.displayName}: ${symlinkResult.linked.length}/${SKILLS.length} linked`
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Summary
|
|
208
|
-
console.log('');
|
|
209
|
-
console.log(chalk.green('✓ Update complete!'));
|
|
210
|
-
console.log('');
|
|
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
|
-
}
|
|
186
|
+
// Update symlinks only for platforms that already had them
|
|
187
|
+
for (const platform of platformsWithSymlinks) {
|
|
188
|
+
spinner.start(`Updating ${platform.displayName} symlinks...`);
|
|
189
|
+
const symlinkResult = await createSkillSymlinks(
|
|
190
|
+
platform.skillsDir,
|
|
191
|
+
SKILLS,
|
|
192
|
+
baseDir
|
|
193
|
+
);
|
|
194
|
+
if (symlinkResult.linked.length === SKILLS.length) {
|
|
195
|
+
spinner.succeed(`${platform.displayName} linked`);
|
|
220
196
|
} else {
|
|
221
|
-
|
|
197
|
+
spinner.warn(
|
|
198
|
+
`${platform.displayName}: ${symlinkResult.linked.length}/${SKILLS.length} linked`
|
|
199
|
+
);
|
|
222
200
|
}
|
|
223
|
-
|
|
201
|
+
}
|
|
224
202
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
203
|
+
// Summary
|
|
204
|
+
console.log('');
|
|
205
|
+
console.log(chalk.green('✓ Update complete!'));
|
|
206
|
+
|
|
207
|
+
// Update knowledge index in MD files if in a Titanium project
|
|
208
|
+
const projectDir = resolve(process.cwd());
|
|
209
|
+
if (isTitaniumProject(projectDir)) {
|
|
210
|
+
const aiFiles = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md'];
|
|
211
|
+
const hasAnyAiFile = aiFiles.some((file) => existsSync(join(projectDir, file)));
|
|
212
|
+
if (hasAnyAiFile) {
|
|
213
|
+
console.log('');
|
|
214
|
+
await agentsCommand(projectDir, { onlyExisting: true, force: true });
|
|
229
215
|
}
|
|
230
216
|
}
|
|
217
|
+
console.log('');
|
|
231
218
|
|
|
232
219
|
} catch (error) {
|
|
233
220
|
spinner.fail('Update failed');
|
package/package.json
CHANGED
|
@@ -263,26 +263,68 @@ exports.ThemeService = {
|
|
|
263
263
|
}
|
|
264
264
|
```
|
|
265
265
|
|
|
266
|
-
### Semantic Colors (
|
|
266
|
+
### Semantic Colors (Cross-Platform)
|
|
267
267
|
|
|
268
|
-
Titanium
|
|
268
|
+
Titanium provides a cross-platform semantic colors API for Dark Mode support. Define colors in a `semantic.colors.json` file:
|
|
269
|
+
|
|
270
|
+
**File location:**
|
|
271
|
+
- **Classic apps**: `Resources/semantic.colors.json`
|
|
272
|
+
- **Alloy apps**: `app/assets/semantic.colors.json`
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"textColor": {
|
|
277
|
+
"dark": {
|
|
278
|
+
"color": "#ff85e2",
|
|
279
|
+
"alpha": "50"
|
|
280
|
+
},
|
|
281
|
+
"light": "#ff1f1f"
|
|
282
|
+
},
|
|
283
|
+
"backgroundColor": {
|
|
284
|
+
"dark": "#1a1a2e",
|
|
285
|
+
"light": "#ffffff"
|
|
286
|
+
},
|
|
287
|
+
"primaryColor": {
|
|
288
|
+
"dark": {
|
|
289
|
+
"color": "#4ade80",
|
|
290
|
+
"alpha": "100"
|
|
291
|
+
},
|
|
292
|
+
"light": {
|
|
293
|
+
"color": "#22c55e",
|
|
294
|
+
"alpha": "100"
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Using semantic colors:**
|
|
269
301
|
|
|
270
302
|
```tss
|
|
271
|
-
/*
|
|
303
|
+
/* Reference via Ti.UI.fetchSemanticColor() */
|
|
272
304
|
"#label": {
|
|
273
|
-
color: Ti.UI.fetchSemanticColor('
|
|
305
|
+
color: Ti.UI.fetchSemanticColor('textColor')
|
|
274
306
|
}
|
|
275
307
|
|
|
276
|
-
"#
|
|
277
|
-
backgroundColor: Ti.UI.fetchSemanticColor('
|
|
308
|
+
"#container": {
|
|
309
|
+
backgroundColor: Ti.UI.fetchSemanticColor('backgroundColor')
|
|
278
310
|
}
|
|
279
311
|
```
|
|
280
312
|
|
|
281
|
-
|
|
313
|
+
```javascript
|
|
314
|
+
// Or use color names directly as property values
|
|
315
|
+
$.label.color = 'textColor';
|
|
316
|
+
$.container.backgroundColor = 'backgroundColor';
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**How it works:**
|
|
320
|
+
- On iOS 13+ it uses the native system method that checks the user's system-wide Dark Mode setting
|
|
321
|
+
- On all other platforms it checks `Ti.UI.semanticColorType` and returns the correct color variant
|
|
322
|
+
- Alpha can be set from 0.0-100.0 (integer or float)
|
|
323
|
+
- Light values can use hex with alpha via ARGB/AARRGGBB format
|
|
282
324
|
|
|
283
325
|
:::tip When to use Semantic Colors vs Alloy.Globals
|
|
284
|
-
- **Semantic Colors**:
|
|
285
|
-
- **Alloy.Globals palette**:
|
|
326
|
+
- **Semantic Colors**: Built-in Dark Mode support, automatic switching, simpler setup. Works cross-platform.
|
|
327
|
+
- **Alloy.Globals palette**: More control over when/how themes switch, easier to add custom themes beyond light/dark, runtime theme changes with UI rebuild.
|
|
286
328
|
:::
|
|
287
329
|
|
|
288
330
|
---
|