@maccesar/titools 4.5.0 → 4.7.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/README.md +44 -8
- package/commands/ti-audit.md +1 -0
- package/commands/ti-check.md +3 -2
- package/lib/commands/doctor.js +1 -1
- package/lib/commands/list.js +151 -40
- package/lib/commands/skills.js +1 -4
- package/lib/commands/status.js +1 -2
- package/lib/commands/update.js +1 -1
- package/lib/config.js +1 -0
- package/lib/installer.js +0 -1
- package/lib/prompts/checkboxCancel.js +0 -6
- package/lib/symlink.js +1 -1
- package/package.json +4 -2
- package/skills/purgetss/SKILL.md +3 -2
- package/skills/purgetss/references/app-branding.md +406 -323
- package/skills/purgetss/references/cli-commands.md +76 -92
- package/skills/purgetss/references/customization-deep-dive.md +39 -39
- package/skills/purgetss/references/launch-background.md +102 -0
- package/skills/purgetss/references/migration-guide.md +92 -7
- package/skills/purgetss/references/version-history.md +29 -0
- package/skills/ti-game/SKILL.md +179 -0
- package/skills/ti-game/references/api.md +333 -0
- package/skills/ti-game/references/project-setup.md +280 -0
- package/skills/ti-game/references/recipes.md +1220 -0
- package/skills/ti-game/references/roadmap.md +55 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
|
-
TiTools is a Titanium SDK toolkit for AI coding assistants. It ships
|
|
9
|
+
TiTools is a Titanium SDK toolkit for AI coding assistants. It ships 9 skills — 4 opinionated (`ti-expert`, `purgetss`, `ti-ui`, `ti-game`) plus 5 documentation-mirror skills (`ti-api`, `ti-guides`, `ti-howtos`, `alloy-guides`, `alloy-howtos`) — a research agent, and reference files covering Titanium architecture, API reference, native how-tos, Alloy MVC, PurgeTSS styling, UI/UX patterns, and 2D game development with the `ti.game` module.
|
|
10
10
|
|
|
11
11
|
The reference files are maintained against official documentation whenever an official source exists, so the assistant can retrieve current framework behavior instead of guessing from generic training data.
|
|
12
12
|
|
|
@@ -36,9 +36,9 @@ The fastest way to get started with Claude Code. One command to add the marketpl
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
What you get:
|
|
39
|
-
- All
|
|
39
|
+
- All 9 TiTools skills (`ti-expert`, `purgetss`, `ti-ui`, `ti-game`, `ti-api`, `ti-guides`, `ti-howtos`, `alloy-guides`, `alloy-howtos`)
|
|
40
40
|
- ti-pro research agent
|
|
41
|
-
- Session hook that auto-detects Titanium projects (`tiapp.xml`, Alloy, PurgeTSS)
|
|
41
|
+
- Session hook that auto-detects Titanium projects (`tiapp.xml`, Alloy, PurgeTSS, `ti.game`)
|
|
42
42
|
- Slash commands: `/ti-check`, `/ti-new-screen`, `/ti-audit`
|
|
43
43
|
- Auto-updates via marketplace (opt-in — see note below)
|
|
44
44
|
|
|
@@ -70,7 +70,7 @@ claude # or gemini, or codex
|
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
What gets installed:
|
|
73
|
-
- The
|
|
73
|
+
- The 9 TiTools skills to `~/.agents/skills/`
|
|
74
74
|
- ti-pro agent for Claude Code
|
|
75
75
|
- Platform symlinks for Claude Code (Gemini CLI and Codex CLI auto-discover from `~/.agents/skills/` — no symlinks needed)
|
|
76
76
|
- Knowledge index in your project's `AGENTS.md`/`CLAUDE.md`/`GEMINI.md`
|
|
@@ -91,7 +91,7 @@ Why NPM?
|
|
|
91
91
|
| **Knowledge Index** | Not included | Included (`titools sync`) |
|
|
92
92
|
| **Auto-updates** | Marketplace (opt-in, enable in `/plugin`) | SessionStart hook (daily) |
|
|
93
93
|
| **Slash commands** | `/ti-check`, `/ti-new-screen`, `/ti-audit` | Same three (`~/.claude/commands/`) |
|
|
94
|
-
| **Session hook** | Auto-detects Titanium projects
|
|
94
|
+
| **Session hook** | Auto-detects Titanium projects (Alloy/Classic, PurgeTSS, `ti.game`) | Auto-update only |
|
|
95
95
|
|
|
96
96
|
Having both installed is fine: the CLI detects an enabled marketplace plugin and skips its own skill symlinks and command copies, so nothing shows up twice. `titools doctor` reports which channel is serving Claude Code.
|
|
97
97
|
|
|
@@ -177,6 +177,7 @@ The knowledge index is based on the latest Titanium SDK documentation. If your p
|
|
|
177
177
|
| ti-expert | Architecture and implementation | Starting point for most tasks |
|
|
178
178
|
| purgetss | Utility-first styling | UI styling and animations |
|
|
179
179
|
| ti-ui | UI/UX patterns | Complex layouts, ListViews, platform UI |
|
|
180
|
+
| ti-game | 2D games with the `ti.game` module | Sprites, physics, collisions, particles, camera |
|
|
180
181
|
| ti-api | Complete Titanium API reference | Looking up properties, methods, events |
|
|
181
182
|
| ti-guides | SDK fundamentals | tiapp.xml, Hyperloop, distribution, JDK/Xcode compatibility, release notes |
|
|
182
183
|
| ti-howtos | Native feature integration | Location, push, media, platform APIs |
|
|
@@ -184,7 +185,8 @@ The knowledge index is based on the latest Titanium SDK documentation. If your p
|
|
|
184
185
|
| alloy-howtos | Alloy CLI, configuration, debugging | alloy.jmk, config.json, custom XML tags |
|
|
185
186
|
|
|
186
187
|
Notes:
|
|
187
|
-
- The first
|
|
188
|
+
- The first four (`ti-expert`, `purgetss`, `ti-ui`, `ti-game`) are opinionated workflow skills reflecting TiTools' Titanium conventions.
|
|
189
|
+
- `ti-game` documents the `ti.game` native module (2D sprite engine on OpenGL ES 2.0, Android and iOS). Its API reference was verified against the module source; it applies to Alloy and Classic projects alike and does not depend on PurgeTSS.
|
|
188
190
|
- The latter five are documentation-mirror skills sourced from the official Titanium SDK docs (`tidev/titanium-docs`) and audited against them via the internal `titools-skill-auditor`.
|
|
189
191
|
- `purgetss` reference files are audited against the official PurgeTSS documentation, but its workflow conventions are opinionated.
|
|
190
192
|
|
|
@@ -254,9 +256,11 @@ These commands automatically invoke the relevant skills:
|
|
|
254
256
|
The plugin includes a session start hook that auto-detects Titanium projects. When you open Claude Code in a directory with `tiapp.xml`, it automatically reports:
|
|
255
257
|
|
|
256
258
|
```
|
|
257
|
-
Titanium Alloy + PurgeTSS project detected. TiTools skills are available and MUST be used...
|
|
259
|
+
Titanium Alloy + PurgeTSS + ti.game project detected. TiTools skills are available and MUST be used...
|
|
258
260
|
```
|
|
259
261
|
|
|
262
|
+
It reports Alloy vs Classic, whether PurgeTSS is configured, and whether the project declares the `ti.game` module — a game project also gets a line pointing at the `ti-game` skill.
|
|
263
|
+
|
|
260
264
|
This ensures Claude knows it's a Titanium project from the first prompt, preventing it from writing generic code.
|
|
261
265
|
|
|
262
266
|
---
|
|
@@ -285,6 +289,7 @@ All skills include automatic project detection to ensure compatibility:
|
|
|
285
289
|
| purgetss | PurgeTSS installation | Checks for `purgetss/` folder, `config.cjs`, `utilities.tss` |
|
|
286
290
|
| ti-expert | Alloy vs Classic | Checks for `app/` (Alloy) vs `Resources/` (Classic) structure |
|
|
287
291
|
| ti-ui | Titanium projects | Checks for `tiapp.xml` (both Alloy and Classic) |
|
|
292
|
+
| ti-game | ti.game module | Checks for `<module>ti.game</module>` in `tiapp.xml` or `require('ti.game')` |
|
|
288
293
|
|
|
289
294
|
Why this matters:
|
|
290
295
|
- PurgeTSS suggestions are only provided if PurgeTSS is installed
|
|
@@ -422,6 +427,37 @@ Key rules:
|
|
|
422
427
|
|
|
423
428
|
---
|
|
424
429
|
|
|
430
|
+
### ti-game
|
|
431
|
+
|
|
432
|
+
2D game development with the `ti.game` native module — a sprite engine rendered with OpenGL ES 2.0, same JS API on Android and iOS. Works in Alloy and Classic projects.
|
|
433
|
+
|
|
434
|
+
When it activates:
|
|
435
|
+
- `tiapp.xml` declares `ti.game`, or any file calls `require('ti.game')`
|
|
436
|
+
- Sprites, sprite sheets, TexturePacker atlases, frame animations, native tweens
|
|
437
|
+
- Physics: gravity and velocity, solid platforms, one-way floors, bouncing, the arcade car model, Newtonian thrust
|
|
438
|
+
- Collision groups, invisible trigger zones, hitbox tuning
|
|
439
|
+
- Particle emitters, Verlet ropes, camera follow/zoom/shake/effects, low-latency sound
|
|
440
|
+
- Drag & drop, pinch, rotate, multitouch controls
|
|
441
|
+
|
|
442
|
+
Example prompts:
|
|
443
|
+
```
|
|
444
|
+
"Build a flappy-bird style game with ti.game."
|
|
445
|
+
"My sprites won't collide — collisionGroup is set on both."
|
|
446
|
+
"Add a camera that follows the player with a dead zone."
|
|
447
|
+
"How do I make a top-down racer that drifts?"
|
|
448
|
+
"Why does my game stutter when I move sprites from setInterval?"
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Key rules:
|
|
452
|
+
- JS describes the scene and reacts to events; the engine runs every frame. Never move a sprite from a timer — use velocity, gravity, a tween, `carMode` or `thrust`
|
|
453
|
+
- Build the level inside a guarded `resize` handler, never from `Ti.Platform.displayCaps`
|
|
454
|
+
- `solidWith` blocks, `collidesWith` reports — they are independent
|
|
455
|
+
- A sprite with size but no sheet is an invisible trigger (scores, goals, walls)
|
|
456
|
+
- Add a whole level with one `gameView.add([...])` call
|
|
457
|
+
- There is no text in the engine: HUDs are `Ti.UI.Label` overlays
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
425
461
|
## Usage examples and best practices
|
|
426
462
|
|
|
427
463
|
### Example prompts
|
|
@@ -517,7 +553,7 @@ Behavior depends on where you run it:
|
|
|
517
553
|
| With `--local` flag | Installs skills locally to `./.agents/skills/` in the current project |
|
|
518
554
|
|
|
519
555
|
What it does:
|
|
520
|
-
- Installs the
|
|
556
|
+
- Installs the 9 TiTools skills (global or local depending on context)
|
|
521
557
|
- Installs ti-pro agent for Claude Code
|
|
522
558
|
- Installs the `/ti-check`, `/ti-new-screen` and `/ti-audit` slash commands into `~/.claude/commands/`
|
|
523
559
|
- Detects installed AI platforms and lets you choose which to link (only Claude Code needs platform symlinks; Gemini CLI and Codex CLI auto-discover from `~/.agents/skills/`)
|
package/commands/ti-audit.md
CHANGED
|
@@ -9,5 +9,6 @@ Audit the current Titanium project by invoking the relevant TiTools skills:
|
|
|
9
9
|
2. Invoke `ti-ui` — check for UI anti-patterns (Ti.UI.SIZE in ListViews, WebView inside TableView, missing accessibility)
|
|
10
10
|
3. If PurgeTSS project, invoke `purgetss` — check for custom TSS that should be utility classes, unsupported classes, padding on Views
|
|
11
11
|
4. Invoke `alloy-guides` — check for proper Alloy patterns (model usage, widget structure)
|
|
12
|
+
5. If the project declares `ti.game`, invoke `ti-game` — check for sprites moved from timers instead of native properties, levels sized from `Ti.Platform.displayCaps` instead of the `resize` event, sprites added one by one instead of in one array, and timers or sounds left running when the window closes
|
|
12
13
|
|
|
13
14
|
Report findings grouped by severity: critical, warning, info.
|
package/commands/ti-check.md
CHANGED
|
@@ -8,8 +8,9 @@ Check the current Titanium project and report:
|
|
|
8
8
|
1. Read `tiapp.xml` — report SDK version, app ID, deployment targets
|
|
9
9
|
2. Check if Alloy project (app/views + app/controllers exist)
|
|
10
10
|
3. Check if PurgeTSS is configured (purgetss/config.cjs exists)
|
|
11
|
-
4. Check
|
|
11
|
+
4. Check whether the `ti.game` module is declared in `tiapp.xml` — report its version and whether the module is actually present under `modules/<platform>/ti.game/<version>/` or installed globally
|
|
12
|
+
5. Check for common issues:
|
|
12
13
|
- Missing icon files (DefaultIcon.png)
|
|
13
14
|
- Empty modules section in tiapp.xml
|
|
14
15
|
- Outdated SDK version
|
|
15
|
-
|
|
16
|
+
6. Report a summary of findings
|
package/lib/commands/doctor.js
CHANGED
package/lib/commands/list.js
CHANGED
|
@@ -16,44 +16,144 @@ import {
|
|
|
16
16
|
const CHECK = chalk.green('✓');
|
|
17
17
|
const CROSS = chalk.red('✗');
|
|
18
18
|
|
|
19
|
+
// Two lines of description per skill: enough to tell what a skill is for,
|
|
20
|
+
// short enough that the list stays a list. Everything past that is the
|
|
21
|
+
// skill's trigger text, which is written for the agent, not for this screen.
|
|
22
|
+
const MAX_DESCRIPTION_LINES = 2;
|
|
23
|
+
|
|
24
|
+
// Descriptions still need room to say something when the window is narrow.
|
|
25
|
+
const MIN_DESCRIPTION_WIDTH = 24;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Unquote a YAML scalar. Most skills here are single-quoted because the
|
|
29
|
+
* descriptions contain colons and apostrophes; without this, the quote is
|
|
30
|
+
* printed as part of the text.
|
|
31
|
+
* @param {string} value - Raw scalar as it appears after `description:`
|
|
32
|
+
* @returns {string} The value with its wrapping quotes and escapes resolved
|
|
33
|
+
*/
|
|
34
|
+
function unquote(value) {
|
|
35
|
+
const text = value.trim();
|
|
36
|
+
const quote = text[0];
|
|
37
|
+
if ((quote !== "'" && quote !== '"') || !text.endsWith(quote) || text.length < 2) {
|
|
38
|
+
return text;
|
|
39
|
+
}
|
|
40
|
+
const inner = text.slice(1, -1);
|
|
41
|
+
// In single-quoted YAML an apostrophe is escaped by doubling it.
|
|
42
|
+
return quote === "'" ? inner.replace(/''/g, "'") : inner.replace(/\\"/g, '"');
|
|
43
|
+
}
|
|
44
|
+
|
|
19
45
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
46
|
+
* Wrap text into aligned lines, capped at maxLines with an ellipsis when it
|
|
47
|
+
* does not fit.
|
|
48
|
+
* @param {string} text - Text to wrap
|
|
49
|
+
* @param {number} width - Columns available for the text itself
|
|
50
|
+
* @param {number} maxLines - Hard cap on the number of lines
|
|
51
|
+
* @returns {string[]} Lines, none longer than width
|
|
23
52
|
*/
|
|
24
|
-
function
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
53
|
+
export function wrapDescription(text, width, maxLines = MAX_DESCRIPTION_LINES) {
|
|
54
|
+
const columns = Math.max(width, MIN_DESCRIPTION_WIDTH);
|
|
55
|
+
const lines = [];
|
|
56
|
+
let current = '';
|
|
57
|
+
|
|
58
|
+
for (let word of text.split(/\s+/).filter(Boolean)) {
|
|
59
|
+
// A single word wider than the column (a long URL) is broken by hand;
|
|
60
|
+
// otherwise the loop below could never place it and would spin.
|
|
61
|
+
while (word.length > columns) {
|
|
62
|
+
if (current) {
|
|
63
|
+
lines.push(current);
|
|
64
|
+
current = '';
|
|
65
|
+
}
|
|
66
|
+
lines.push(word.slice(0, columns));
|
|
67
|
+
word = word.slice(columns);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!current) {
|
|
71
|
+
current = word;
|
|
72
|
+
} else if (current.length + 1 + word.length <= columns) {
|
|
73
|
+
current += ` ${word}`;
|
|
74
|
+
} else {
|
|
75
|
+
lines.push(current);
|
|
76
|
+
current = word;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (current) lines.push(current);
|
|
80
|
+
|
|
81
|
+
if (lines.length <= maxLines) return lines;
|
|
82
|
+
|
|
83
|
+
const kept = lines.slice(0, maxLines);
|
|
84
|
+
let last = kept[maxLines - 1];
|
|
85
|
+
if (last.length + 1 > columns) {
|
|
86
|
+
last = last.slice(0, columns - 1).replace(/\s+\S*$/, '');
|
|
28
87
|
}
|
|
88
|
+
kept[maxLines - 1] = `${last.replace(/[,;:]$/, '')}…`;
|
|
89
|
+
return kept;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Extract the skill's short description from SKILL.md — the first sentence of
|
|
94
|
+
* the frontmatter description, unquoted and collapsed to a single line.
|
|
95
|
+
*
|
|
96
|
+
* The sentence ends at a period followed by whitespace, which avoids breaking
|
|
97
|
+
* on "SDK 13." and other version-number periods.
|
|
98
|
+
* @param {string} frontmatter - The YAML block between the --- fences
|
|
99
|
+
* @returns {string|null} The description, or null when there is none
|
|
100
|
+
*/
|
|
101
|
+
export function parseDescription(frontmatter) {
|
|
102
|
+
// The lookahead ends the value at the next top-level key, or at the end of
|
|
103
|
+
// the block. `(?![\s\S])` rather than `$`, which under the /m flag would
|
|
104
|
+
// match the first newline and cut a description written across two lines.
|
|
105
|
+
const descMatch = frontmatter.match(/^description:\s*([\s\S]+?)(?=\n[A-Za-z][\w-]*:|(?![\s\S]))/m);
|
|
106
|
+
if (!descMatch) return null;
|
|
107
|
+
|
|
108
|
+
const full = unquote(descMatch[1]).replace(/\s+/g, ' ').trim();
|
|
109
|
+
if (!full) return null;
|
|
110
|
+
|
|
111
|
+
const firstSentence = full.match(/^([^]*?\.)\s/);
|
|
112
|
+
if (firstSentence) return firstSentence[1].trim();
|
|
113
|
+
return full;
|
|
114
|
+
}
|
|
29
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Read a description out of one SKILL.md.
|
|
118
|
+
* @param {string} skillMd - Path to a SKILL.md
|
|
119
|
+
* @returns {string|null} The first sentence, or null when unreadable
|
|
120
|
+
*/
|
|
121
|
+
function readDescription(skillMd) {
|
|
30
122
|
try {
|
|
31
123
|
const content = readFileSync(skillMd, 'utf8');
|
|
32
124
|
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/);
|
|
33
|
-
if (!frontmatter) return
|
|
34
|
-
|
|
35
|
-
const descMatch = frontmatter[1].match(/description:\s*"([^"]+)"|description:\s*(.+)/);
|
|
36
|
-
if (!descMatch) return { description: null, installed: true };
|
|
37
|
-
|
|
38
|
-
const full = descMatch[1] || descMatch[2] || '';
|
|
39
|
-
// First sentence: period followed by whitespace (avoids breaking on "SDK 13."
|
|
40
|
-
// or other version-number periods). Falls back to a word-safe length cutoff.
|
|
41
|
-
let short;
|
|
42
|
-
const firstSentence = full.match(/^([^]*?\.)\s/);
|
|
43
|
-
if (firstSentence) {
|
|
44
|
-
short = firstSentence[1];
|
|
45
|
-
} else if (full.length > 80) {
|
|
46
|
-
const cut = full.slice(0, 80);
|
|
47
|
-
short = cut.slice(0, cut.lastIndexOf(' ')) + '…';
|
|
48
|
-
} else {
|
|
49
|
-
short = full;
|
|
50
|
-
}
|
|
51
|
-
return { description: short.trim(), installed: true };
|
|
125
|
+
if (!frontmatter) return null;
|
|
126
|
+
return parseDescription(frontmatter[1]);
|
|
52
127
|
} catch {
|
|
53
|
-
return
|
|
128
|
+
return null;
|
|
54
129
|
}
|
|
55
130
|
}
|
|
56
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Read a skill's description, preferring the installed copy and falling back
|
|
134
|
+
* to the one bundled in this package.
|
|
135
|
+
*
|
|
136
|
+
* The fallback is what lets the list describe a skill that is not installed
|
|
137
|
+
* yet — which is exactly when someone runs `titools list` and needs to know
|
|
138
|
+
* what they would be installing. `skills/` ships in the npm tarball, so the
|
|
139
|
+
* bundled copy is there for every install channel.
|
|
140
|
+
* @param {string} skillDir - Where the skill would be installed
|
|
141
|
+
* @param {string} name - Skill name, used to find the bundled copy
|
|
142
|
+
* @returns {{description: string|null, installed: boolean}}
|
|
143
|
+
*/
|
|
144
|
+
function readSkillMetadata(skillDir, name) {
|
|
145
|
+
const installedMd = join(skillDir, 'SKILL.md');
|
|
146
|
+
if (existsSync(installedMd)) {
|
|
147
|
+
return { description: readDescription(installedMd), installed: true };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const bundledMd = new URL(`../../skills/${name}/SKILL.md`, import.meta.url);
|
|
151
|
+
return {
|
|
152
|
+
description: existsSync(bundledMd) ? readDescription(bundledMd) : null,
|
|
153
|
+
installed: false,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
57
157
|
export async function listCommand() {
|
|
58
158
|
console.log('');
|
|
59
159
|
console.log(chalk.bold.blue(`Titanium skills (v${PACKAGE_VERSION})`));
|
|
@@ -61,21 +161,13 @@ export async function listCommand() {
|
|
|
61
161
|
|
|
62
162
|
const skillsDir = getAgentsSkillsDir();
|
|
63
163
|
|
|
64
|
-
if (!existsSync(skillsDir)) {
|
|
65
|
-
console.log(chalk.yellow('No skills installed yet.'));
|
|
66
|
-
console.log('Install with:');
|
|
67
|
-
console.log(chalk.cyan(' titools install'));
|
|
68
|
-
console.log('');
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
164
|
const rows = [];
|
|
73
165
|
let installedCount = 0;
|
|
74
166
|
let maxNameLen = 0;
|
|
75
167
|
|
|
76
168
|
for (const name of SKILLS) {
|
|
77
169
|
const skillDir = join(skillsDir, name);
|
|
78
|
-
const { description, installed } = readSkillMetadata(skillDir);
|
|
170
|
+
const { description, installed } = readSkillMetadata(skillDir, name);
|
|
79
171
|
|
|
80
172
|
if (installed) installedCount++;
|
|
81
173
|
if (name.length > maxNameLen) maxNameLen = name.length;
|
|
@@ -83,18 +175,37 @@ export async function listCommand() {
|
|
|
83
175
|
rows.push({ name, description, installed });
|
|
84
176
|
}
|
|
85
177
|
|
|
178
|
+
// Where the description column starts: ' ' + mark + ' ' + padded name + ' '.
|
|
179
|
+
// Measured on the plain text, since chalk's colour codes take no columns.
|
|
180
|
+
const descriptionColumn = 2 + 1 + 1 + (maxNameLen + 2) + 1;
|
|
181
|
+
const available = (process.stdout.columns || 80) - descriptionColumn;
|
|
182
|
+
|
|
86
183
|
// Print aligned rows
|
|
87
184
|
for (const row of rows) {
|
|
88
185
|
const mark = row.installed ? CHECK : CROSS;
|
|
89
186
|
const paddedName = row.name.padEnd(maxNameLen + 2);
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
: chalk.gray(row.installed ? '(no description)' : 'not installed');
|
|
187
|
+
const text = row.description || (row.installed ? '(no description)' : 'not installed');
|
|
188
|
+
const [first, ...rest] = wrapDescription(text, available);
|
|
93
189
|
|
|
94
|
-
console.log(` ${mark} ${chalk.cyan(paddedName)} ${
|
|
190
|
+
console.log(` ${mark} ${chalk.cyan(paddedName)} ${chalk.gray(first)}`);
|
|
191
|
+
for (const line of rest) {
|
|
192
|
+
console.log(`${' '.repeat(descriptionColumn)}${chalk.gray(line)}`);
|
|
193
|
+
}
|
|
95
194
|
}
|
|
96
195
|
|
|
97
196
|
console.log('');
|
|
197
|
+
|
|
198
|
+
if (installedCount === 0) {
|
|
199
|
+
// Naming the directory would point at somewhere that does not exist yet.
|
|
200
|
+
console.log(chalk.gray(`0/${SKILLS.length} installed`));
|
|
201
|
+
console.log('');
|
|
202
|
+
console.log(chalk.yellow('No skills installed yet.'));
|
|
203
|
+
console.log('Install with:');
|
|
204
|
+
console.log(chalk.cyan(' titools install'));
|
|
205
|
+
console.log('');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
98
209
|
console.log(chalk.gray(`${installedCount}/${SKILLS.length} installed at ${skillsDir}`));
|
|
99
210
|
console.log('');
|
|
100
211
|
console.log(chalk.gray('Run `titools status` for installation health.'));
|
package/lib/commands/skills.js
CHANGED
|
@@ -8,7 +8,6 @@ import ora from 'ora';
|
|
|
8
8
|
import {
|
|
9
9
|
SKILLS,
|
|
10
10
|
getPlatforms,
|
|
11
|
-
getAgentsSkillsDir,
|
|
12
11
|
} from '../config.js';
|
|
13
12
|
import select from '../prompts/selectCancel.js';
|
|
14
13
|
import checkbox, { Separator } from '../prompts/checkboxCancel.js';
|
|
@@ -38,7 +37,6 @@ import { downloadRepoArchive } from '../downloader.js';
|
|
|
38
37
|
import { createSkillSymlinks } from '../symlink.js';
|
|
39
38
|
import { installHook } from '../hooks.js';
|
|
40
39
|
import {
|
|
41
|
-
formatList,
|
|
42
40
|
isTitaniumProject,
|
|
43
41
|
readSkillDescription,
|
|
44
42
|
shortenSkillDescription,
|
|
@@ -305,7 +303,7 @@ export async function skillsCommand(options) {
|
|
|
305
303
|
if (!removeOnly) {
|
|
306
304
|
// Install skills
|
|
307
305
|
spinner.start('Installing skills...');
|
|
308
|
-
|
|
306
|
+
await installSkills(repoDir, baseDir, skillsToInstall);
|
|
309
307
|
spinner.succeed(`${skillsToInstall.length} skill${skillsToInstall.length !== 1 ? 's' : ''} installed`);
|
|
310
308
|
|
|
311
309
|
// If the user deselected some skills, sweep any previously-installed
|
|
@@ -415,7 +413,6 @@ export async function skillsCommand(options) {
|
|
|
415
413
|
removedCount: symlinkResult.removed.length,
|
|
416
414
|
};
|
|
417
415
|
});
|
|
418
|
-
const anyRemoved = platformResults.some((result) => result.removedCount > 0);
|
|
419
416
|
|
|
420
417
|
if (skillsResult.removed.length > 0) {
|
|
421
418
|
console.log(chalk.green('✓'), `${skillsResult.removed.length} skills removed`);
|
package/lib/commands/status.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
-
import { existsSync,
|
|
7
|
+
import { existsSync, lstatSync, readFileSync } from 'fs';
|
|
8
8
|
import { join } from 'path';
|
|
9
9
|
import os from 'os';
|
|
10
10
|
import {
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
getClaudeAgentsDir,
|
|
15
15
|
getConfigDir,
|
|
16
16
|
getPlatforms,
|
|
17
|
-
BLOCK_START,
|
|
18
17
|
} from '../config.js';
|
|
19
18
|
import { hasHook } from '../hooks.js';
|
|
20
19
|
import { readLastCheck } from '../cache.js';
|
package/lib/commands/update.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
fetchLatestNpmVersion,
|
|
28
28
|
} from '../downloader.js';
|
|
29
29
|
import { createSkillSymlinks } from '../symlink.js';
|
|
30
|
-
import {
|
|
30
|
+
import { isTitaniumProject } from '../utils.js';
|
|
31
31
|
import { getAgentsSkillsDir } from '../config.js';
|
|
32
32
|
import { existsSync } from 'fs';
|
|
33
33
|
import { join, resolve } from 'path';
|
package/lib/config.js
CHANGED
package/lib/installer.js
CHANGED
|
@@ -49,12 +49,6 @@ function toggle(item) {
|
|
|
49
49
|
return isSelectable(item) ? { ...item, checked: !item.checked } : item;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function check(checked) {
|
|
53
|
-
return function (item) {
|
|
54
|
-
return isSelectable(item) ? { ...item, checked } : item;
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
52
|
function normalizeChoices(choices) {
|
|
59
53
|
return choices.map((choice) => {
|
|
60
54
|
if (Separator.isSeparator(choice)) return choice;
|
package/lib/symlink.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { mkdirSync, existsSync } from 'fs';
|
|
7
|
-
import { symlink, readlink
|
|
7
|
+
import { symlink, readlink } from 'fs/promises';
|
|
8
8
|
import { join, dirname, relative } from 'path';
|
|
9
9
|
import { copy, remove } from 'fs-extra';
|
|
10
10
|
import { isWindows } from './platform.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maccesar/titools",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.0",
|
|
4
4
|
"description": "Titanium SDK skills and agents for AI coding assistants (Claude Code, Gemini CLI, Codex CLI)",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "node --test test/**/*.test.js",
|
|
12
|
-
"lint": "eslint lib
|
|
12
|
+
"lint": "eslint bin lib test",
|
|
13
13
|
"format": "prettier --write lib/**/*.js"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
@@ -49,7 +49,9 @@
|
|
|
49
49
|
"tar": "^7.5.7"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
+
"@eslint/js": "^9.39.5",
|
|
52
53
|
"eslint": "^9.39.2",
|
|
54
|
+
"globals": "^16.5.0",
|
|
53
55
|
"prettier": "^3.8.1"
|
|
54
56
|
},
|
|
55
57
|
"files": [
|
package/skills/purgetss/SKILL.md
CHANGED
|
@@ -427,7 +427,8 @@ Load these only when needed:
|
|
|
427
427
|
- [iOS Large Titles](references/ios-large-titles.md) - Best practice for iOS Large Title navigation
|
|
428
428
|
|
|
429
429
|
### App Assets
|
|
430
|
-
- [App Branding](references/app-branding.md) - `brand` command
|
|
430
|
+
- [App Branding](references/app-branding.md) - `brand` command: the 14 pieces it regenerates (launcher, adaptive, iOS 18+ Dark/Tinted, marketplace, both splash sets), the per-piece `brand:` config, `--only`, `--optimize` (v7.13.0)
|
|
431
|
+
- [Launch Background](references/launch-background.md) - Carrying `brand.background` into the iOS LaunchScreen and a launcher-only Android theme (`--notes` output)
|
|
431
432
|
- [Multi-Density Images](references/multi-density-images.md) - `images` command for Android res-*dpi + iPhone @1x/@2x/@3x UI images (v7.6.0)
|
|
432
433
|
- [SVG Pipeline](references/svg-pipeline.md) - SVG-aware compile-time image pipeline: `.svg` refs + numeric `w-*`/`h-*` classes compile to 8 density PNGs; `images.files`/`images.autoSync` (v7.11.0–v7.11.1)
|
|
433
434
|
|
|
@@ -444,7 +445,7 @@ Load these only when needed:
|
|
|
444
445
|
- [Animation Advanced](references/animation-advanced.md) - Property inheritance, utility classes, implementation rules, complex UI example
|
|
445
446
|
|
|
446
447
|
### Release Notes
|
|
447
|
-
- [Version History](references/version-history.md) - Release-by-release feature additions and behavior changes (v7.4.0 → v7.
|
|
448
|
+
- [Version History](references/version-history.md) - Release-by-release feature additions and behavior changes (v7.4.0 → v7.13.1)
|
|
448
449
|
|
|
449
450
|
> **💡 TEXT FONTS (Google Fonts, Roboto, etc.)**
|
|
450
451
|
> For text fonts, see [Custom Fonts](references/custom-fonts.md).
|