@maccesar/titools 4.1.0 → 4.4.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 +30 -7
- package/bin/titools.js +4 -4
- package/commands/ti-audit.md +13 -0
- package/commands/ti-check.md +15 -0
- package/commands/ti-new-screen.md +18 -0
- package/lib/claude-plugin.js +105 -0
- package/lib/cleanup.js +18 -0
- package/lib/commands/doctor.js +75 -3
- package/lib/commands/skills.js +66 -6
- package/lib/commands/uninstall.js +32 -2
- package/lib/commands/update.js +15 -0
- package/lib/config.js +37 -0
- package/lib/installer.js +85 -1
- package/lib/symlink.js +40 -3
- package/package.json +2 -1
- package/skills/alloy-guides/references/MODELS.md +23 -0
- package/skills/alloy-guides/references/MODELS_ADVANCED.md +19 -0
- package/skills/alloy-guides/references/VIEWS_STYLES.md +12 -0
- package/skills/alloy-guides/references/VIEWS_XML.md +18 -0
- package/skills/purgetss/references/EXAMPLES.md +24 -0
- package/skills/purgetss/references/animation-advanced.md +12 -0
- package/skills/purgetss/references/animation-system.md +23 -0
- package/skills/purgetss/references/app-branding.md +23 -0
- package/skills/purgetss/references/apply-directive.md +17 -0
- package/skills/purgetss/references/arbitrary-values.md +13 -0
- package/skills/purgetss/references/class-index-properties.md +11 -0
- package/skills/purgetss/references/cli-commands.md +27 -0
- package/skills/purgetss/references/customization-deep-dive.md +15 -0
- package/skills/purgetss/references/dynamic-component-creation.md +16 -0
- package/skills/purgetss/references/migration-guide.md +20 -0
- package/skills/purgetss/references/multi-density-images.md +27 -0
- package/skills/purgetss/references/semantic-colors.md +15 -0
- package/skills/purgetss/references/tikit-components.md +17 -0
- package/skills/purgetss/references/titanium-resets.md +16 -0
- package/skills/purgetss/references/ui-ux-design.md +20 -0
- package/skills/ti-api/references/api-android.md +23 -0
- package/skills/ti-api/references/api-app-platform.md +24 -0
- package/skills/ti-api/references/api-core.md +24 -0
- package/skills/ti-api/references/api-data-network.md +21 -0
- package/skills/ti-api/references/api-media.md +15 -1
- package/skills/ti-api/references/api-modules-ble-bluetooth.md +26 -0
- package/skills/ti-api/references/api-modules-coremotion-urlsession.md +18 -0
- package/skills/ti-api/references/api-modules-map.md +21 -2
- package/skills/ti-api/references/api-modules-nfc.md +34 -0
- package/skills/ti-api/references/api-modules-social-misc.md +22 -0
- package/skills/ti-api/references/api-services.md +21 -1
- package/skills/ti-api/references/api-ui-android.md +18 -0
- package/skills/ti-api/references/api-ui-extras.md +19 -1
- package/skills/ti-api/references/api-ui-ios-animator.md +14 -0
- package/skills/ti-api/references/api-ui-ios.md +42 -0
- package/skills/ti-api/references/api-ui-lists.md +14 -2
- package/skills/ti-api/references/api-ui-text-input.md +15 -0
- package/skills/ti-api/references/api-ui-views.md +19 -0
- package/skills/ti-api/references/api-ui-windows-navigation.md +13 -0
- package/skills/ti-api/references/api-xml-global.md +31 -0
- package/skills/ti-expert/SKILL.md +38 -5
- package/skills/ti-expert/references/adaptive-layouts.md +14 -0
- package/skills/ti-expert/references/alloy-builtins.md +26 -22
- package/skills/ti-expert/references/alloy-structure.md +57 -18
- package/skills/ti-expert/references/anti-patterns.md +13 -5
- package/skills/ti-expert/references/code-conventions.md +28 -3
- package/skills/ti-expert/references/controller-patterns.md +51 -26
- package/skills/ti-expert/references/error-handling.md +16 -0
- package/skills/ti-expert/references/examples.md +65 -21
- package/skills/ti-expert/references/feedback-migration.md +165 -0
- package/skills/ti-expert/references/feedback-surfaces.md +140 -0
- package/skills/ti-expert/references/feedback-widget-contracts.md +213 -0
- package/skills/ti-expert/references/migration-patterns.md +61 -24
- package/skills/ti-expert/references/patterns.md +16 -0
- package/skills/ti-expert/references/performance-optimization.md +17 -0
- package/skills/ti-expert/references/security-device.md +71 -56
- package/skills/ti-expert/references/state-management.md +21 -5
- package/skills/ti-expert/references/testing-e2e-ci.md +17 -0
- package/skills/ti-expert/references/testing-unit.md +20 -0
- package/skills/ti-expert/references/theming.md +135 -373
- package/skills/ti-guides/references/app-distribution.md +34 -0
- package/skills/ti-guides/references/application-frameworks.md +13 -0
- package/skills/ti-guides/references/cli-reference.md +16 -0
- package/skills/ti-guides/references/hyperloop-native-access.md +21 -0
- package/skills/ti-guides/references/javascript-primer.md +12 -0
- package/skills/ti-guides/references/tiapp-config.md +15 -0
- package/skills/ti-howtos/references/android-platform-deep-dives.md +15 -0
- package/skills/ti-howtos/references/cross-platform-development.md +16 -0
- package/skills/ti-howtos/references/debugging-profiling.md +15 -0
- package/skills/ti-howtos/references/extending-titanium.md +14 -0
- package/skills/ti-howtos/references/ios-platform-deep-dives.md +16 -0
- package/skills/ti-howtos/references/local-data-sources.md +32 -0
- package/skills/ti-howtos/references/notification-services.md +12 -0
- package/skills/ti-howtos/references/remote-data-sources.md +15 -0
- package/skills/ti-howtos/references/tutorials.md +13 -0
- package/skills/ti-ui/references/animation-and-matrices.md +17 -0
- package/skills/ti-ui/references/application-structures.md +19 -0
- package/skills/ti-ui/references/custom-fonts-styling.md +18 -0
- package/skills/ti-ui/references/event-handling.md +16 -0
- package/skills/ti-ui/references/gestures.md +17 -0
- package/skills/ti-ui/references/icons-and-splash-screens.md +17 -0
- package/skills/ti-ui/references/layouts-and-positioning.md +16 -0
- package/skills/ti-ui/references/listviews-and-performance.md +21 -0
- package/skills/ti-ui/references/orientation.md +20 -0
- package/skills/ti-ui/references/platform-ui-android.md +20 -0
- package/skills/ti-ui/references/platform-ui-ios.md +17 -0
- package/skills/ti-ui/references/tableviews.md +24 -0
package/README.md
CHANGED
|
@@ -92,9 +92,13 @@ Why NPM?
|
|
|
92
92
|
| **Codex CLI** | Not available | Supported |
|
|
93
93
|
| **Knowledge Index** | Not included | Included (`titools sync`) |
|
|
94
94
|
| **Auto-updates** | Marketplace (opt-in, enable in `/plugin`) | SessionStart hook (daily) |
|
|
95
|
-
| **Slash commands** | `/ti-check`, `/ti-new-screen`, `/ti-audit` |
|
|
95
|
+
| **Slash commands** | `/ti-check`, `/ti-new-screen`, `/ti-audit` | Same three (`~/.claude/commands/`) |
|
|
96
96
|
| **Session hook** | Auto-detects Titanium projects | Auto-update only |
|
|
97
97
|
|
|
98
|
+
Having both installed is fine: the CLI detects an enabled marketplace plugin and skips
|
|
99
|
+
its own skill symlinks and command copies, so nothing shows up twice. `titools doctor`
|
|
100
|
+
reports which channel is serving Claude Code.
|
|
101
|
+
|
|
98
102
|
Use **Option A** if you only use Claude Code. Use **Option B** if you use multiple AI assistants or want the Knowledge Index feature.
|
|
99
103
|
|
|
100
104
|
---
|
|
@@ -232,9 +236,12 @@ Key difference:
|
|
|
232
236
|
|
|
233
237
|
---
|
|
234
238
|
|
|
235
|
-
## Slash Commands (
|
|
239
|
+
## Slash Commands (Claude Code)
|
|
236
240
|
|
|
237
|
-
|
|
241
|
+
TiTools provides slash commands for common tasks. Both channels ship them: the
|
|
242
|
+
plugin serves them from its own cache, and `titools install` copies them into
|
|
243
|
+
`~/.claude/commands/`. If you have both, the CLI detects the plugin and skips its
|
|
244
|
+
own copy so the command does not appear twice in the autocomplete.
|
|
238
245
|
|
|
239
246
|
| Command | Description |
|
|
240
247
|
| ----------------------- | ----------------------------------------------------------------------------------- |
|
|
@@ -326,6 +333,10 @@ When it activates:
|
|
|
326
333
|
- Choosing data strategies (Models vs Collections)
|
|
327
334
|
- Writing clean ES6+ code
|
|
328
335
|
- Memory management and cleanup patterns
|
|
336
|
+
- Choosing a feedback surface (Snackbar, Dialog, Bottom Sheet) and deciding what stays native
|
|
337
|
+
- Designing reusable Alloy Widgets and their lifecycle contracts
|
|
338
|
+
- Adaptive layouts for tablets, foldables, and large screens
|
|
339
|
+
- Theming and dark mode
|
|
329
340
|
- Performance optimization
|
|
330
341
|
- Security patterns
|
|
331
342
|
- Migrating legacy apps
|
|
@@ -336,13 +347,15 @@ Example prompts:
|
|
|
336
347
|
"Create a user service that fetches data from an API."
|
|
337
348
|
"What's the best pattern for navigation between screens?"
|
|
338
349
|
"Prevent memory leaks in my controllers."
|
|
350
|
+
"Every confirmation in my app is an alertDialog — what should each one actually be?"
|
|
351
|
+
"Should this be a Widget or just a <Require>?"
|
|
339
352
|
"Migrate classic Titanium code to modern Alloy patterns."
|
|
340
353
|
"What's a clean way to organize services in an Alloy app?"
|
|
341
354
|
```
|
|
342
355
|
|
|
343
356
|
Key features:
|
|
344
357
|
- Quick decision matrix for common questions
|
|
345
|
-
-
|
|
358
|
+
- 24 reference guides for deep dives
|
|
346
359
|
- Delegates to specialized skills when needed
|
|
347
360
|
|
|
348
361
|
---
|
|
@@ -485,7 +498,7 @@ More examples: See [Example Prompts](EXAMPLE-PROMPTS.md) for detailed prompts th
|
|
|
485
498
|
|
|
486
499
|
### titools install
|
|
487
500
|
|
|
488
|
-
Installs Titanium skills,
|
|
501
|
+
Installs Titanium skills, the ti-pro agent, slash commands, and platform symlinks.
|
|
489
502
|
|
|
490
503
|
```bash
|
|
491
504
|
titools install [options]
|
|
@@ -509,8 +522,10 @@ Behavior depends on where you run it:
|
|
|
509
522
|
What it does:
|
|
510
523
|
- Installs the 8 TiTools skills (global or local depending on context)
|
|
511
524
|
- Installs ti-pro agent for Claude Code
|
|
525
|
+
- Installs the `/ti-check`, `/ti-new-screen` and `/ti-audit` slash commands into `~/.claude/commands/`
|
|
512
526
|
- 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/`)
|
|
513
527
|
- Creates symlinks from `~/.claude/skills/` to the central `~/.agents/skills/`
|
|
528
|
+
- Skips any skill or command already served by an enabled marketplace plugin, and removes a duplicate left by an earlier install
|
|
514
529
|
- Cleans up legacy artifacts (`alloy-expert` skill, `ti-researcher` agent, redundant Gemini/Codex symlinks)
|
|
515
530
|
- If run inside a Titanium project, prompts to run `titools sync` afterward
|
|
516
531
|
- Installs Claude Code SessionStart hook for auto-updates
|
|
@@ -557,7 +572,15 @@ Diagnoses installation health.
|
|
|
557
572
|
titools doctor
|
|
558
573
|
```
|
|
559
574
|
|
|
560
|
-
Checks: skill directories exist, symlinks are valid (not broken), agent is installed, hook is configured, cache is readable, Knowledge Index version matches CLI version. Reports issues with fix suggestions.
|
|
575
|
+
Checks: skill directories exist, symlinks are valid (not broken), agent is installed, slash commands are installed, hook is configured, cache is readable, Knowledge Index version matches CLI version. Reports issues with fix suggestions.
|
|
576
|
+
|
|
577
|
+
It also reports the marketplace plugin's state, which changes what "healthy" means:
|
|
578
|
+
|
|
579
|
+
| Plugin state | What doctor reports |
|
|
580
|
+
| ---------------------------------- | ----------------------------------------------------------------------- |
|
|
581
|
+
| Enabled | Claude Code is served by the plugin — missing mirrors are correct |
|
|
582
|
+
| Not installed | Skills reach Claude Code through npm mirrors — mirrors must be present |
|
|
583
|
+
| Uninstalled, cache directory left | Warns and prints the `rm -rf` that clears the leftover |
|
|
561
584
|
|
|
562
585
|
### titools sync
|
|
563
586
|
|
|
@@ -701,7 +724,7 @@ This pattern is documented across three TiTools skills: `ti-ui`, `ti-expert`, an
|
|
|
701
724
|
|
|
702
725
|
| Skill | SKILL.md | References |
|
|
703
726
|
| --------- | ----------------------------- | ----------------------------------------------------- |
|
|
704
|
-
| ti-expert | Architecture + Implementation |
|
|
727
|
+
| ti-expert | Architecture + Implementation | 24 files (patterns, feedback surfaces, testing, security, etc.) |
|
|
705
728
|
| purgetss | Setup + Critical Rules | 33 files (grid, animations, icons, class-index, SVG pipeline, etc.) |
|
|
706
729
|
| ti-ui | UI Rules + Platform Diffs | 14 files (layouts, lists, gestures, etc.) |
|
|
707
730
|
|
package/bin/titools.js
CHANGED
|
@@ -26,7 +26,7 @@ program
|
|
|
26
26
|
// Install command
|
|
27
27
|
program
|
|
28
28
|
.command('install')
|
|
29
|
-
.description('Install Titanium
|
|
29
|
+
.description('Install Titanium skills, the ti-pro agent, slash commands and platform links')
|
|
30
30
|
.option('-l, --local', 'Install skills locally in the current project')
|
|
31
31
|
.option('-a, --all', 'Install all detected platforms without prompting')
|
|
32
32
|
.option('--path <path>', 'Install to a custom path (skips symlink setup)')
|
|
@@ -44,14 +44,14 @@ program
|
|
|
44
44
|
// Update command
|
|
45
45
|
program
|
|
46
46
|
.command('update')
|
|
47
|
-
.description('Check for newer CLI versions, then sync installed
|
|
47
|
+
.description('Check for newer CLI versions, then sync installed skills, agent and slash commands')
|
|
48
48
|
.option('-l, --local', 'Update local skills in the current project')
|
|
49
49
|
.action(updateCommand);
|
|
50
50
|
|
|
51
51
|
// Remove command
|
|
52
52
|
program
|
|
53
53
|
.command('remove')
|
|
54
|
-
.description('Remove Titanium
|
|
54
|
+
.description('Remove Titanium skills, the ti-pro agent and slash commands')
|
|
55
55
|
.option('-l, --local', 'Remove local skills from the current project')
|
|
56
56
|
.action(uninstallCommand);
|
|
57
57
|
|
|
@@ -71,7 +71,7 @@ program
|
|
|
71
71
|
// Doctor command
|
|
72
72
|
program
|
|
73
73
|
.command('doctor')
|
|
74
|
-
.description('Check installation health and diagnose issues')
|
|
74
|
+
.description('Check installation health, including marketplace-plugin overlap, and diagnose issues')
|
|
75
75
|
.action(doctorCommand);
|
|
76
76
|
|
|
77
77
|
// List command
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ti-audit
|
|
3
|
+
description: Audit Titanium project for anti-patterns, memory leaks, and best practice violations
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Audit the current Titanium project by invoking the relevant TiTools skills:
|
|
7
|
+
|
|
8
|
+
1. Invoke `ti-expert` — check for architecture anti-patterns, memory leaks (missing event listener cleanup), and code conventions
|
|
9
|
+
2. Invoke `ti-ui` — check for UI anti-patterns (Ti.UI.SIZE in ListViews, WebView inside TableView, missing accessibility)
|
|
10
|
+
3. If PurgeTSS project, invoke `purgetss` — check for custom TSS that should be utility classes, unsupported classes, padding on Views
|
|
11
|
+
4. Invoke `alloy-guides` — check for proper Alloy patterns (model usage, widget structure)
|
|
12
|
+
|
|
13
|
+
Report findings grouped by severity: critical, warning, info.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ti-check
|
|
3
|
+
description: Verify Titanium project setup and report issues
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Check the current Titanium project and report:
|
|
7
|
+
|
|
8
|
+
1. Read `tiapp.xml` — report SDK version, app ID, deployment targets
|
|
9
|
+
2. Check if Alloy project (app/views + app/controllers exist)
|
|
10
|
+
3. Check if PurgeTSS is configured (purgetss/config.cjs exists)
|
|
11
|
+
4. Check for common issues:
|
|
12
|
+
- Missing icon files (DefaultIcon.png)
|
|
13
|
+
- Empty modules section in tiapp.xml
|
|
14
|
+
- Outdated SDK version
|
|
15
|
+
5. Report a summary of findings
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ti-new-screen
|
|
3
|
+
description: Create a new Alloy screen (controller + view + style) following project conventions
|
|
4
|
+
argument-hint: "<screenName>"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Create a new Alloy screen with the given name. Before creating files:
|
|
8
|
+
|
|
9
|
+
1. Invoke the `alloy-guides` skill to follow Alloy MVC patterns
|
|
10
|
+
2. If PurgeTSS is detected (purgetss/ folder exists), invoke the `purgetss` skill — all styling MUST use utility classes in the XML, NOT custom TSS
|
|
11
|
+
3. If PurgeTSS is detected, invoke the `ti-ui` skill for layout patterns
|
|
12
|
+
|
|
13
|
+
Create these files:
|
|
14
|
+
- `app/views/$ARGUMENTS.xml` — Alloy XML view with PurgeTSS utility classes
|
|
15
|
+
- `app/controllers/$ARGUMENTS.js` — Controller with proper event cleanup
|
|
16
|
+
- `app/styles/$ARGUMENTS.tss` — Empty or minimal (PurgeTSS handles styling)
|
|
17
|
+
|
|
18
|
+
Follow the existing screens in the project as examples for conventions.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection of the maccesar-titools Claude Code marketplace plugin.
|
|
3
|
+
*
|
|
4
|
+
* When the plugin is installed, Claude Code already lists our skills and slash
|
|
5
|
+
* commands from its plugin cache, so the CLI must not also install its own copy
|
|
6
|
+
* into ~/.claude/ — that produces duplicate entries in the autocomplete.
|
|
7
|
+
*
|
|
8
|
+
* The subtlety, and the reason this lives in its own module: **the cache on disk
|
|
9
|
+
* does not mean the plugin is installed.** Uninstalling a plugin removes it from
|
|
10
|
+
* `enabledPlugins` in settings.json but leaves the cache directory behind.
|
|
11
|
+
* Treating that leftover directory as proof of installation makes the CLI skip
|
|
12
|
+
* work it should do, which leaves Claude Code with no skills at all and no way
|
|
13
|
+
* for the user to repair it by re-running install. So the question we answer here
|
|
14
|
+
* is "is the plugin enabled AND does it carry this file", never just the latter.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
import {
|
|
20
|
+
CLAUDE_PLUGIN_KEY,
|
|
21
|
+
getClaudePluginSkillsPath,
|
|
22
|
+
getClaudeSettingsPaths,
|
|
23
|
+
} from './config.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether Claude Code currently has the titools plugin enabled.
|
|
27
|
+
* @param {string} baseDir - Optional base directory (defaults to homedir via config)
|
|
28
|
+
* @returns {boolean} True only when a settings file explicitly enables the plugin
|
|
29
|
+
*/
|
|
30
|
+
export function isClaudePluginEnabled(baseDir) {
|
|
31
|
+
for (const settingsPath of getClaudeSettingsPaths(baseDir)) {
|
|
32
|
+
try {
|
|
33
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
34
|
+
if (settings?.enabledPlugins?.[CLAUDE_PLUGIN_KEY] === true) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// Missing or malformed settings: nothing here says the plugin is enabled.
|
|
39
|
+
// Falling through to `false` is the safe direction — the cost of a wrong
|
|
40
|
+
// `false` is a duplicate entry, the cost of a wrong `true` is a user with
|
|
41
|
+
// no skills installed.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether a plugin cache directory exists at all, regardless of whether the
|
|
49
|
+
* plugin is enabled. An enabled plugin implies a cache; a cache implies nothing,
|
|
50
|
+
* because uninstalling leaves it behind. Diagnostics use this to tell "never
|
|
51
|
+
* installed" apart from "uninstalled, leftovers on disk".
|
|
52
|
+
* @param {string} baseDir - Optional base directory
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
*/
|
|
55
|
+
export function hasClaudePluginCache(baseDir) {
|
|
56
|
+
return existsSync(getClaudePluginSkillsPath(baseDir));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Whether any cached version of the plugin carries the given file.
|
|
61
|
+
* @param {string} kind - Subdirectory inside the plugin ('skills' or 'commands')
|
|
62
|
+
* @param {string} entry - Entry to look for (skill directory or command file)
|
|
63
|
+
* @param {string} baseDir - Optional base directory
|
|
64
|
+
* @returns {boolean} True if a cached version contains it
|
|
65
|
+
*/
|
|
66
|
+
function pluginCacheContains(kind, entry, baseDir) {
|
|
67
|
+
const pluginBase = getClaudePluginSkillsPath(baseDir);
|
|
68
|
+
if (!existsSync(pluginBase)) return false;
|
|
69
|
+
try {
|
|
70
|
+
return readdirSync(pluginBase).some((version) =>
|
|
71
|
+
existsSync(join(pluginBase, version, kind, entry))
|
|
72
|
+
);
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Whether the installed plugin already provides this skill to Claude Code.
|
|
80
|
+
* @param {string} skillName - Skill name (e.g. 'ti-expert')
|
|
81
|
+
* @param {string} baseDir - Optional base directory
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
export function pluginProvidesSkill(skillName, baseDir) {
|
|
85
|
+
return isClaudePluginEnabled(baseDir) && pluginCacheContains('skills', skillName, baseDir);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Whether the installed plugin already provides this slash command.
|
|
90
|
+
* @param {string} commandName - Command name without the .md extension
|
|
91
|
+
* @param {string} baseDir - Optional base directory
|
|
92
|
+
* @returns {boolean}
|
|
93
|
+
*/
|
|
94
|
+
export function pluginProvidesCommand(commandName, baseDir) {
|
|
95
|
+
return (
|
|
96
|
+
isClaudePluginEnabled(baseDir) && pluginCacheContains('commands', `${commandName}.md`, baseDir)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default {
|
|
101
|
+
isClaudePluginEnabled,
|
|
102
|
+
hasClaudePluginCache,
|
|
103
|
+
pluginProvidesSkill,
|
|
104
|
+
pluginProvidesCommand,
|
|
105
|
+
};
|
package/lib/cleanup.js
CHANGED
|
@@ -7,8 +7,11 @@ import {
|
|
|
7
7
|
LEGACY_SKILLS,
|
|
8
8
|
AGENTS,
|
|
9
9
|
LEGACY_AGENTS,
|
|
10
|
+
COMMANDS,
|
|
11
|
+
LEGACY_COMMANDS,
|
|
10
12
|
getAgentsSkillsDir,
|
|
11
13
|
getClaudeAgentsDir,
|
|
14
|
+
getClaudeCommandsDir,
|
|
12
15
|
getCodexSkillsDir,
|
|
13
16
|
getGeminiSkillsDir,
|
|
14
17
|
} from './config.js';
|
|
@@ -26,6 +29,11 @@ export function getAgentList({ includeLegacy = true, legacyOnly = false } = {})
|
|
|
26
29
|
return includeLegacy ? [...AGENTS, ...LEGACY_AGENTS] : [...AGENTS];
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
export function getCommandList({ includeLegacy = true, legacyOnly = false } = {}) {
|
|
33
|
+
if (legacyOnly) return [...LEGACY_COMMANDS];
|
|
34
|
+
return includeLegacy ? [...COMMANDS, ...LEGACY_COMMANDS] : [...COMMANDS];
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
function removeEntriesAtDir(dir, names, { suffix = '', recursive = true } = {}) {
|
|
30
38
|
const results = { removed: [], failed: [] };
|
|
31
39
|
if (!dir || !existsSync(dir)) return results;
|
|
@@ -98,9 +106,16 @@ export function removeAgents(baseDir, options = {}) {
|
|
|
98
106
|
return removeEntriesAtDir(agentsDir, agentList, { suffix: '.md', recursive: false });
|
|
99
107
|
}
|
|
100
108
|
|
|
109
|
+
export function removeCommands(baseDir, options = {}) {
|
|
110
|
+
const commandsDir = getClaudeCommandsDir(baseDir);
|
|
111
|
+
const commandList = getCommandList(options);
|
|
112
|
+
return removeEntriesAtDir(commandsDir, commandList, { suffix: '.md', recursive: false });
|
|
113
|
+
}
|
|
114
|
+
|
|
101
115
|
export function cleanupLegacyArtifacts(baseDir) {
|
|
102
116
|
removeSkills(baseDir, { legacyOnly: true });
|
|
103
117
|
removeAgents(baseDir, { legacyOnly: true });
|
|
118
|
+
removeCommands(baseDir, { legacyOnly: true });
|
|
104
119
|
|
|
105
120
|
const platforms = detectPlatforms(baseDir);
|
|
106
121
|
for (const platform of platforms) {
|
|
@@ -113,6 +128,7 @@ export function cleanupLegacyArtifacts(baseDir) {
|
|
|
113
128
|
if (baseDir) {
|
|
114
129
|
removeSkills(undefined, { legacyOnly: true });
|
|
115
130
|
removeAgents(undefined, { legacyOnly: true });
|
|
131
|
+
removeCommands(undefined, { legacyOnly: true });
|
|
116
132
|
const globalPlatforms = detectPlatforms();
|
|
117
133
|
for (const platform of globalPlatforms) {
|
|
118
134
|
removeLegacySkillSymlinks(platform.skillsDir);
|
|
@@ -125,6 +141,7 @@ export function cleanupLegacyArtifacts(baseDir) {
|
|
|
125
141
|
export default {
|
|
126
142
|
getSkillList,
|
|
127
143
|
getAgentList,
|
|
144
|
+
getCommandList,
|
|
128
145
|
removeSkillSymlinks,
|
|
129
146
|
removeLegacySkillSymlinks,
|
|
130
147
|
removeCodexRedundantSymlinks,
|
|
@@ -133,5 +150,6 @@ export default {
|
|
|
133
150
|
removeUnselectedSymlinks,
|
|
134
151
|
removeSkills,
|
|
135
152
|
removeAgents,
|
|
153
|
+
removeCommands,
|
|
136
154
|
cleanupLegacyArtifacts,
|
|
137
155
|
};
|
package/lib/commands/doctor.js
CHANGED
|
@@ -9,16 +9,24 @@ import { join } from 'path';
|
|
|
9
9
|
import os from 'os';
|
|
10
10
|
import {
|
|
11
11
|
SKILLS,
|
|
12
|
+
COMMANDS,
|
|
12
13
|
PACKAGE_VERSION,
|
|
13
14
|
TITANIUM_KNOWLEDGE_VERSION,
|
|
14
15
|
getAgentsSkillsDir,
|
|
15
16
|
getClaudeAgentsDir,
|
|
17
|
+
getClaudeCommandsDir,
|
|
16
18
|
getConfigDir,
|
|
17
19
|
getPlatforms,
|
|
18
20
|
} from '../config.js';
|
|
19
21
|
import { hasHook } from '../hooks.js';
|
|
20
22
|
import { readLastCheck } from '../cache.js';
|
|
21
23
|
import { isTitaniumProject, blockExists } from '../utils.js';
|
|
24
|
+
import {
|
|
25
|
+
isClaudePluginEnabled,
|
|
26
|
+
hasClaudePluginCache,
|
|
27
|
+
pluginProvidesSkill,
|
|
28
|
+
pluginProvidesCommand,
|
|
29
|
+
} from '../claude-plugin.js';
|
|
22
30
|
|
|
23
31
|
const CHECK = chalk.green('✓');
|
|
24
32
|
const CROSS = chalk.red('✗');
|
|
@@ -39,6 +47,7 @@ export async function doctorCommand() {
|
|
|
39
47
|
const homeDir = os.homedir();
|
|
40
48
|
const skillsDir = getAgentsSkillsDir(homeDir);
|
|
41
49
|
const agentsDir = getClaudeAgentsDir(homeDir);
|
|
50
|
+
const commandsDir = getClaudeCommandsDir(homeDir);
|
|
42
51
|
const claudeDir = join(homeDir, '.claude');
|
|
43
52
|
const cacheDir = getConfigDir();
|
|
44
53
|
const cwd = process.cwd();
|
|
@@ -79,6 +88,27 @@ export async function doctorCommand() {
|
|
|
79
88
|
issues++;
|
|
80
89
|
}
|
|
81
90
|
|
|
91
|
+
// Slash commands check. Like the skills above, commands the marketplace
|
|
92
|
+
// plugin serves are supposed to be absent from ~/.claude/commands/.
|
|
93
|
+
const missingCommands = [];
|
|
94
|
+
const pluginCommands = [];
|
|
95
|
+
for (const command of COMMANDS) {
|
|
96
|
+
if (pluginProvidesCommand(command, homeDir)) {
|
|
97
|
+
pluginCommands.push(command);
|
|
98
|
+
} else if (!existsSync(join(commandsDir, `${command}.md`))) {
|
|
99
|
+
missingCommands.push(command);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const expectedCommands = COMMANDS.length - pluginCommands.length;
|
|
103
|
+
if (pluginCommands.length === COMMANDS.length) {
|
|
104
|
+
console.log(` ${CHECK} Slash commands: all ${COMMANDS.length} served by the marketplace plugin`);
|
|
105
|
+
} else if (missingCommands.length === 0) {
|
|
106
|
+
console.log(` ${CHECK} Slash commands: ${expectedCommands}/${expectedCommands} installed in ~/.claude/commands/`);
|
|
107
|
+
} else {
|
|
108
|
+
console.log(` ${CROSS} Slash commands: ${expectedCommands - missingCommands.length}/${expectedCommands} installed (missing: ${missingCommands.join(', ')})`);
|
|
109
|
+
issues += missingCommands.length;
|
|
110
|
+
}
|
|
111
|
+
|
|
82
112
|
// Hook check
|
|
83
113
|
if (hasHook(claudeDir)) {
|
|
84
114
|
console.log(` ${CHECK} Hook: SessionStart configured`);
|
|
@@ -104,8 +134,18 @@ export async function doctorCommand() {
|
|
|
104
134
|
for (const platform of platforms) {
|
|
105
135
|
const missing = [];
|
|
106
136
|
const broken = [];
|
|
137
|
+
const servedByPlugin = [];
|
|
107
138
|
|
|
108
139
|
for (const skill of SKILLS) {
|
|
140
|
+
// A skill the marketplace plugin provides is *supposed* to have no mirror
|
|
141
|
+
// here — the CLI removes it on purpose to avoid a duplicate entry. Counting
|
|
142
|
+
// it as missing turns a healthy marketplace install into a wall of errors
|
|
143
|
+
// telling the user to run a command that will correctly do nothing.
|
|
144
|
+
if (platform.name === 'claude' && pluginProvidesSkill(skill, homeDir)) {
|
|
145
|
+
servedByPlugin.push(skill);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
109
149
|
const linkPath = join(platform.skillsDir, skill);
|
|
110
150
|
try {
|
|
111
151
|
const stat = lstatSync(linkPath);
|
|
@@ -121,15 +161,26 @@ export async function doctorCommand() {
|
|
|
121
161
|
}
|
|
122
162
|
}
|
|
123
163
|
|
|
124
|
-
const
|
|
164
|
+
const expected = SKILLS.length - servedByPlugin.length;
|
|
165
|
+
const linkedCount = expected - missing.length - broken.length;
|
|
166
|
+
const pluginNote =
|
|
167
|
+
servedByPlugin.length > 0
|
|
168
|
+
? chalk.dim(` (+${servedByPlugin.length} served by the marketplace plugin)`)
|
|
169
|
+
: '';
|
|
125
170
|
|
|
126
171
|
if (missing.length === 0 && broken.length === 0) {
|
|
127
|
-
|
|
172
|
+
const summary =
|
|
173
|
+
expected === 0
|
|
174
|
+
? `all ${servedByPlugin.length} skills served by the marketplace plugin`
|
|
175
|
+
: `${linkedCount}/${expected} skills linked${pluginNote}`;
|
|
176
|
+
console.log(` ${CHECK} ${platform.displayName}: ${summary}`);
|
|
128
177
|
} else {
|
|
129
178
|
const problems = [];
|
|
130
179
|
if (missing.length > 0) problems.push(`missing: ${missing.join(', ')}`);
|
|
131
180
|
if (broken.length > 0) problems.push(`broken: ${broken.join(', ')}`);
|
|
132
|
-
console.log(
|
|
181
|
+
console.log(
|
|
182
|
+
` ${CROSS} ${platform.displayName}: ${linkedCount}/${expected} skills linked${pluginNote} (${problems.join('; ')})`
|
|
183
|
+
);
|
|
133
184
|
issues += missing.length + broken.length;
|
|
134
185
|
|
|
135
186
|
// Collect symlink issues for detailed report
|
|
@@ -142,6 +193,27 @@ export async function doctorCommand() {
|
|
|
142
193
|
}
|
|
143
194
|
}
|
|
144
195
|
|
|
196
|
+
// Marketplace plugin
|
|
197
|
+
//
|
|
198
|
+
// Worth its own section because the two channels look identical from the
|
|
199
|
+
// outside and produce opposite expectations: with the plugin enabled, absent
|
|
200
|
+
// mirrors are correct; without it, absent mirrors mean no skills at all.
|
|
201
|
+
console.log('');
|
|
202
|
+
console.log(' Marketplace plugin:');
|
|
203
|
+
const pluginEnabled = isClaudePluginEnabled(homeDir);
|
|
204
|
+
const pluginCached = hasClaudePluginCache(homeDir);
|
|
205
|
+
|
|
206
|
+
if (pluginEnabled) {
|
|
207
|
+
console.log(` ${CHECK} Enabled — Claude Code is served by the plugin, mirrors intentionally absent`);
|
|
208
|
+
} else if (pluginCached) {
|
|
209
|
+
console.log(` ${WARN} Not enabled, but a cache directory remains from a previous install`);
|
|
210
|
+
console.log(` ${chalk.dim('Harmless on this version, which requires the plugin to be enabled.')}`);
|
|
211
|
+
console.log(` ${chalk.dim('Remove it with:')}`);
|
|
212
|
+
console.log(` ${chalk.cyan('rm -rf ~/.claude/plugins/cache/maccesar-titools')}`);
|
|
213
|
+
} else {
|
|
214
|
+
console.log(` ${CHECK} Not installed — skills reach Claude Code through npm mirrors`);
|
|
215
|
+
}
|
|
216
|
+
|
|
145
217
|
// Symlink issues detail
|
|
146
218
|
if (symlinkIssues.length > 0) {
|
|
147
219
|
console.log('');
|
package/lib/commands/skills.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
removeLegacySkillSymlinks,
|
|
23
23
|
removeSkills,
|
|
24
24
|
removeAgents,
|
|
25
|
+
removeCommands,
|
|
25
26
|
removeUnselectedSkills,
|
|
26
27
|
removeUnselectedSymlinks,
|
|
27
28
|
cleanupLegacyArtifacts,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
import {
|
|
30
31
|
installSkills,
|
|
31
32
|
installAgents,
|
|
33
|
+
installCommands,
|
|
32
34
|
getLocalRepoDir,
|
|
33
35
|
} from '../installer.js';
|
|
34
36
|
import { agentsCommand } from './agents.js';
|
|
@@ -117,10 +119,15 @@ export async function skillsCommand(options) {
|
|
|
117
119
|
chalk.dim('(agentskills.io standard)')
|
|
118
120
|
);
|
|
119
121
|
console.log(
|
|
120
|
-
' ' + chalk.
|
|
122
|
+
' ' + chalk.green('✓'),
|
|
123
|
+
chalk.dim('Gemini, Codex, Cursor, Cline, Amp, GitHub Copilot +more read it directly —')
|
|
121
124
|
);
|
|
125
|
+
console.log(' ' + chalk.dim('nothing else to configure for them.'));
|
|
122
126
|
console.log(
|
|
123
|
-
' ' + chalk.dim('
|
|
127
|
+
' ' + chalk.dim('Claude Code needs symlink mirrors, plus the ti-pro agent and the')
|
|
128
|
+
);
|
|
129
|
+
console.log(
|
|
130
|
+
' ' + chalk.dim('/ti-check, /ti-new-screen and /ti-audit slash commands — created below.')
|
|
124
131
|
);
|
|
125
132
|
console.log('');
|
|
126
133
|
|
|
@@ -151,9 +158,17 @@ export async function skillsCommand(options) {
|
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
// Show detected platforms
|
|
161
|
+
// Only assistants that need TiTools-managed mirrors appear here, so a bare
|
|
162
|
+
// "Claude Code detected" reads as if the others were looked for and not found.
|
|
163
|
+
// They were never looked for: Gemini, Codex and the rest read
|
|
164
|
+
// ~/.agents/skills/ directly and are already served by the install itself.
|
|
154
165
|
if (detectedPlatforms.length > 0) {
|
|
155
166
|
for (const platform of detectedPlatforms) {
|
|
156
|
-
console.log(
|
|
167
|
+
console.log(
|
|
168
|
+
chalk.green('✓'),
|
|
169
|
+
`${platform.displayName} detected`,
|
|
170
|
+
chalk.dim('— needs mirrors, linked below')
|
|
171
|
+
);
|
|
157
172
|
}
|
|
158
173
|
console.log('');
|
|
159
174
|
} else if (isLocal) {
|
|
@@ -315,6 +330,22 @@ export async function skillsCommand(options) {
|
|
|
315
330
|
} else {
|
|
316
331
|
spinner.info('No agents to install (Claude Code not detected)');
|
|
317
332
|
}
|
|
333
|
+
// Install slash commands (/ti-check, /ti-new-screen, /ti-audit).
|
|
334
|
+
// Claude Code only — they live in ~/.claude/commands/ and are skipped
|
|
335
|
+
// when the marketplace plugin already serves them.
|
|
336
|
+
spinner.start('Installing slash commands...');
|
|
337
|
+
const commandsResult = await installCommands(repoDir, baseDir);
|
|
338
|
+
if (commandsResult.installed.length > 0) {
|
|
339
|
+
spinner.succeed(
|
|
340
|
+
`${commandsResult.installed.length} slash command${commandsResult.installed.length !== 1 ? 's' : ''} installed`
|
|
341
|
+
);
|
|
342
|
+
} else if (commandsResult.skipped.length > 0) {
|
|
343
|
+
spinner.info(
|
|
344
|
+
`${commandsResult.skipped.length} slash command${commandsResult.skipped.length !== 1 ? 's' : ''} already provided by the marketplace plugin`
|
|
345
|
+
);
|
|
346
|
+
} else {
|
|
347
|
+
spinner.info('No slash commands to install');
|
|
348
|
+
}
|
|
318
349
|
} else {
|
|
319
350
|
spinner.start('Removing agents...');
|
|
320
351
|
const agentsResult = removeAgents(baseDir);
|
|
@@ -323,6 +354,13 @@ export async function skillsCommand(options) {
|
|
|
323
354
|
} else {
|
|
324
355
|
spinner.info('No agents to remove');
|
|
325
356
|
}
|
|
357
|
+
|
|
358
|
+
// Slash commands are Claude-only, so they follow the agents: if Claude
|
|
359
|
+
// is not among the selected platforms, nothing should be left behind.
|
|
360
|
+
const commandsResult = removeCommands(baseDir);
|
|
361
|
+
if (commandsResult.removed.length > 0) {
|
|
362
|
+
spinner.succeed('Slash commands removed');
|
|
363
|
+
}
|
|
326
364
|
}
|
|
327
365
|
|
|
328
366
|
// Create symlinks for selected platforms
|
|
@@ -337,11 +375,22 @@ export async function skillsCommand(options) {
|
|
|
337
375
|
skillsToInstall,
|
|
338
376
|
baseDir
|
|
339
377
|
);
|
|
340
|
-
|
|
341
|
-
|
|
378
|
+
// Skills the marketplace plugin already serves are skipped on purpose,
|
|
379
|
+
// so they must not count against the expected total — otherwise a
|
|
380
|
+
// healthy install with the plugin enabled reports a warning.
|
|
381
|
+
const expected = skillsToInstall.length - symlinkResult.skipped.length;
|
|
382
|
+
const pluginNote = symlinkResult.skipped.length > 0
|
|
383
|
+
? ` (${symlinkResult.skipped.length} served by the marketplace plugin)`
|
|
384
|
+
: '';
|
|
385
|
+
if (expected === 0) {
|
|
386
|
+
spinner.info(
|
|
387
|
+
`${platform.displayName}: all skills served by the marketplace plugin`
|
|
388
|
+
);
|
|
389
|
+
} else if (symlinkResult.linked.length === expected) {
|
|
390
|
+
spinner.succeed(`${platform.displayName}: Skills linked${pluginNote}`);
|
|
342
391
|
} else {
|
|
343
392
|
spinner.warn(
|
|
344
|
-
`${platform.displayName}: ${symlinkResult.linked.length}/${
|
|
393
|
+
`${platform.displayName}: ${symlinkResult.linked.length}/${expected} skills linked${pluginNote}`
|
|
345
394
|
);
|
|
346
395
|
}
|
|
347
396
|
}
|
|
@@ -357,6 +406,7 @@ export async function skillsCommand(options) {
|
|
|
357
406
|
}
|
|
358
407
|
} else {
|
|
359
408
|
const agentsResult = removeAgents(baseDir);
|
|
409
|
+
const commandsResult = removeCommands(baseDir);
|
|
360
410
|
const skillsResult = removeSkills(baseDir);
|
|
361
411
|
const platformResults = detectedPlatforms.map((platform) => {
|
|
362
412
|
const symlinkResult = removeSkillSymlinks(platform.skillsDir);
|
|
@@ -379,6 +429,12 @@ export async function skillsCommand(options) {
|
|
|
379
429
|
console.log(chalk.gray('ℹ'), 'No agents to remove');
|
|
380
430
|
}
|
|
381
431
|
|
|
432
|
+
if (commandsResult.removed.length > 0) {
|
|
433
|
+
console.log(chalk.green('✓'), `${commandsResult.removed.length} slash commands removed`);
|
|
434
|
+
} else {
|
|
435
|
+
console.log(chalk.gray('ℹ'), 'No slash commands to remove');
|
|
436
|
+
}
|
|
437
|
+
|
|
382
438
|
for (const result of platformResults) {
|
|
383
439
|
if (result.removedCount > 0) {
|
|
384
440
|
console.log(chalk.green('✓'), `${result.displayName}: Skills unlinked`);
|
|
@@ -423,6 +479,10 @@ export async function skillsCommand(options) {
|
|
|
423
479
|
' ' + chalk.dim('• symlinked: ') +
|
|
424
480
|
'Claude Code ' + chalk.dim('(~/.claude/skills/)')
|
|
425
481
|
);
|
|
482
|
+
console.log(
|
|
483
|
+
' ' + chalk.dim('• commands: ') +
|
|
484
|
+
'/ti-check, /ti-new-screen, /ti-audit ' + chalk.dim('(~/.claude/commands/)')
|
|
485
|
+
);
|
|
426
486
|
}
|
|
427
487
|
}
|
|
428
488
|
console.log('');
|