@maccesar/titools 4.1.0 → 4.3.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 (97) hide show
  1. package/README.md +22 -5
  2. package/bin/titools.js +4 -4
  3. package/commands/ti-audit.md +13 -0
  4. package/commands/ti-check.md +15 -0
  5. package/commands/ti-new-screen.md +18 -0
  6. package/lib/claude-plugin.js +105 -0
  7. package/lib/cleanup.js +18 -0
  8. package/lib/commands/doctor.js +75 -3
  9. package/lib/commands/skills.js +66 -6
  10. package/lib/commands/uninstall.js +32 -2
  11. package/lib/commands/update.js +15 -0
  12. package/lib/config.js +37 -0
  13. package/lib/installer.js +85 -1
  14. package/lib/symlink.js +40 -3
  15. package/package.json +2 -1
  16. package/skills/alloy-guides/references/MODELS.md +23 -0
  17. package/skills/alloy-guides/references/MODELS_ADVANCED.md +19 -0
  18. package/skills/alloy-guides/references/VIEWS_STYLES.md +12 -0
  19. package/skills/alloy-guides/references/VIEWS_XML.md +18 -0
  20. package/skills/purgetss/references/EXAMPLES.md +24 -0
  21. package/skills/purgetss/references/animation-advanced.md +12 -0
  22. package/skills/purgetss/references/animation-system.md +23 -0
  23. package/skills/purgetss/references/app-branding.md +23 -0
  24. package/skills/purgetss/references/apply-directive.md +17 -0
  25. package/skills/purgetss/references/arbitrary-values.md +13 -0
  26. package/skills/purgetss/references/class-index-properties.md +11 -0
  27. package/skills/purgetss/references/cli-commands.md +27 -0
  28. package/skills/purgetss/references/customization-deep-dive.md +15 -0
  29. package/skills/purgetss/references/dynamic-component-creation.md +16 -0
  30. package/skills/purgetss/references/migration-guide.md +20 -0
  31. package/skills/purgetss/references/multi-density-images.md +27 -0
  32. package/skills/purgetss/references/semantic-colors.md +15 -0
  33. package/skills/purgetss/references/tikit-components.md +17 -0
  34. package/skills/purgetss/references/titanium-resets.md +16 -0
  35. package/skills/purgetss/references/ui-ux-design.md +20 -0
  36. package/skills/ti-api/references/api-android.md +23 -0
  37. package/skills/ti-api/references/api-app-platform.md +24 -0
  38. package/skills/ti-api/references/api-core.md +24 -0
  39. package/skills/ti-api/references/api-data-network.md +21 -0
  40. package/skills/ti-api/references/api-media.md +15 -1
  41. package/skills/ti-api/references/api-modules-ble-bluetooth.md +26 -0
  42. package/skills/ti-api/references/api-modules-coremotion-urlsession.md +18 -0
  43. package/skills/ti-api/references/api-modules-map.md +21 -2
  44. package/skills/ti-api/references/api-modules-nfc.md +34 -0
  45. package/skills/ti-api/references/api-modules-social-misc.md +22 -0
  46. package/skills/ti-api/references/api-services.md +21 -1
  47. package/skills/ti-api/references/api-ui-android.md +18 -0
  48. package/skills/ti-api/references/api-ui-extras.md +19 -1
  49. package/skills/ti-api/references/api-ui-ios-animator.md +14 -0
  50. package/skills/ti-api/references/api-ui-ios.md +42 -0
  51. package/skills/ti-api/references/api-ui-lists.md +14 -2
  52. package/skills/ti-api/references/api-ui-text-input.md +15 -0
  53. package/skills/ti-api/references/api-ui-views.md +19 -0
  54. package/skills/ti-api/references/api-ui-windows-navigation.md +13 -0
  55. package/skills/ti-api/references/api-xml-global.md +31 -0
  56. package/skills/ti-expert/references/adaptive-layouts.md +14 -0
  57. package/skills/ti-expert/references/alloy-builtins.md +15 -0
  58. package/skills/ti-expert/references/alloy-structure.md +15 -0
  59. package/skills/ti-expert/references/code-conventions.md +19 -0
  60. package/skills/ti-expert/references/controller-patterns.md +18 -0
  61. package/skills/ti-expert/references/error-handling.md +16 -0
  62. package/skills/ti-expert/references/examples.md +17 -0
  63. package/skills/ti-expert/references/migration-patterns.md +15 -0
  64. package/skills/ti-expert/references/patterns.md +16 -0
  65. package/skills/ti-expert/references/performance-optimization.md +17 -0
  66. package/skills/ti-expert/references/security-device.md +13 -0
  67. package/skills/ti-expert/references/state-management.md +16 -0
  68. package/skills/ti-expert/references/testing-e2e-ci.md +17 -0
  69. package/skills/ti-expert/references/testing-unit.md +20 -0
  70. package/skills/ti-expert/references/theming.md +20 -0
  71. package/skills/ti-guides/references/app-distribution.md +34 -0
  72. package/skills/ti-guides/references/application-frameworks.md +13 -0
  73. package/skills/ti-guides/references/cli-reference.md +16 -0
  74. package/skills/ti-guides/references/hyperloop-native-access.md +21 -0
  75. package/skills/ti-guides/references/javascript-primer.md +12 -0
  76. package/skills/ti-guides/references/tiapp-config.md +15 -0
  77. package/skills/ti-howtos/references/android-platform-deep-dives.md +15 -0
  78. package/skills/ti-howtos/references/cross-platform-development.md +16 -0
  79. package/skills/ti-howtos/references/debugging-profiling.md +15 -0
  80. package/skills/ti-howtos/references/extending-titanium.md +14 -0
  81. package/skills/ti-howtos/references/ios-platform-deep-dives.md +16 -0
  82. package/skills/ti-howtos/references/local-data-sources.md +32 -0
  83. package/skills/ti-howtos/references/notification-services.md +12 -0
  84. package/skills/ti-howtos/references/remote-data-sources.md +15 -0
  85. package/skills/ti-howtos/references/tutorials.md +13 -0
  86. package/skills/ti-ui/references/animation-and-matrices.md +17 -0
  87. package/skills/ti-ui/references/application-structures.md +19 -0
  88. package/skills/ti-ui/references/custom-fonts-styling.md +18 -0
  89. package/skills/ti-ui/references/event-handling.md +16 -0
  90. package/skills/ti-ui/references/gestures.md +17 -0
  91. package/skills/ti-ui/references/icons-and-splash-screens.md +17 -0
  92. package/skills/ti-ui/references/layouts-and-positioning.md +16 -0
  93. package/skills/ti-ui/references/listviews-and-performance.md +21 -0
  94. package/skills/ti-ui/references/orientation.md +20 -0
  95. package/skills/ti-ui/references/platform-ui-android.md +20 -0
  96. package/skills/ti-ui/references/platform-ui-ios.md +17 -0
  97. 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` | Not available |
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 (Plugin only)
239
+ ## Slash Commands (Claude Code)
236
240
 
237
- When installed as a plugin, TiTools provides slash commands for common tasks:
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
  | ----------------------- | ----------------------------------------------------------------------------------- |
@@ -485,7 +492,7 @@ More examples: See [Example Prompts](EXAMPLE-PROMPTS.md) for detailed prompts th
485
492
 
486
493
  ### titools install
487
494
 
488
- Installs Titanium skills, agents, and platform symlinks.
495
+ Installs Titanium skills, the ti-pro agent, slash commands, and platform symlinks.
489
496
 
490
497
  ```bash
491
498
  titools install [options]
@@ -509,8 +516,10 @@ Behavior depends on where you run it:
509
516
  What it does:
510
517
  - Installs the 8 TiTools skills (global or local depending on context)
511
518
  - Installs ti-pro agent for Claude Code
519
+ - Installs the `/ti-check`, `/ti-new-screen` and `/ti-audit` slash commands into `~/.claude/commands/`
512
520
  - 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
521
  - Creates symlinks from `~/.claude/skills/` to the central `~/.agents/skills/`
522
+ - Skips any skill or command already served by an enabled marketplace plugin, and removes a duplicate left by an earlier install
514
523
  - Cleans up legacy artifacts (`alloy-expert` skill, `ti-researcher` agent, redundant Gemini/Codex symlinks)
515
524
  - If run inside a Titanium project, prompts to run `titools sync` afterward
516
525
  - Installs Claude Code SessionStart hook for auto-updates
@@ -557,7 +566,15 @@ Diagnoses installation health.
557
566
  titools doctor
558
567
  ```
559
568
 
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.
569
+ 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.
570
+
571
+ It also reports the marketplace plugin's state, which changes what "healthy" means:
572
+
573
+ | Plugin state | What doctor reports |
574
+ | ---------------------------------- | ----------------------------------------------------------------------- |
575
+ | Enabled | Claude Code is served by the plugin — missing mirrors are correct |
576
+ | Not installed | Skills reach Claude Code through npm mirrors — mirrors must be present |
577
+ | Uninstalled, cache directory left | Warns and prints the `rm -rf` that clears the leftover |
561
578
 
562
579
  ### titools sync
563
580
 
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 knowledge packages and platform links')
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 knowledge packages and agent')
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 knowledge packages and agent')
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
  };
@@ -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 linkedCount = SKILLS.length - missing.length - broken.length;
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
- console.log(` ${CHECK} ${platform.displayName}: ${SKILLS.length}/${SKILLS.length} skills linked`);
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(` ${CROSS} ${platform.displayName}: ${linkedCount}/${SKILLS.length} skills linked (${problems.join('; ')})`);
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('');
@@ -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.dim('Read directly by Gemini, Codex, Cursor, Cline, Amp, GitHub Copilot, +more.')
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('Below: extra symlink mirrors for Claude Code.')
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(chalk.green('✓'), `${platform.displayName} detected`);
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
- if (symlinkResult.linked.length === skillsToInstall.length) {
341
- spinner.succeed(`${platform.displayName}: Skills linked`);
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}/${skillsToInstall.length} skills linked`
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('');