@maccesar/titools 2.4.2 → 2.5.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/bin/titools.js +14 -0
- package/lib/commands/auto-update.js +5 -3
- package/lib/commands/doctor.js +194 -0
- package/lib/commands/status.js +136 -0
- package/package.json +1 -1
package/bin/titools.js
CHANGED
|
@@ -12,6 +12,8 @@ import { agentsCommand } from '../lib/commands/agents.js';
|
|
|
12
12
|
import { updateCommand } from '../lib/commands/update.js';
|
|
13
13
|
import { uninstallCommand } from '../lib/commands/uninstall.js';
|
|
14
14
|
import { autoUpdateCommand } from '../lib/commands/auto-update.js';
|
|
15
|
+
import { statusCommand } from '../lib/commands/status.js';
|
|
16
|
+
import { doctorCommand } from '../lib/commands/doctor.js';
|
|
15
17
|
|
|
16
18
|
const program = new Command();
|
|
17
19
|
|
|
@@ -59,6 +61,18 @@ program
|
|
|
59
61
|
.option('-s, --silent', 'Suppress all output except errors')
|
|
60
62
|
.action(autoUpdateCommand);
|
|
61
63
|
|
|
64
|
+
// Status command
|
|
65
|
+
program
|
|
66
|
+
.command('status')
|
|
67
|
+
.description('Show installation status and linked platforms')
|
|
68
|
+
.action(statusCommand);
|
|
69
|
+
|
|
70
|
+
// Doctor command
|
|
71
|
+
program
|
|
72
|
+
.command('doctor')
|
|
73
|
+
.description('Check installation health and diagnose issues')
|
|
74
|
+
.action(doctorCommand);
|
|
75
|
+
|
|
62
76
|
// Parse arguments
|
|
63
77
|
program.parse();
|
|
64
78
|
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import ora from 'ora';
|
|
9
|
-
import {
|
|
9
|
+
import { execFile as execFileCb } from 'child_process';
|
|
10
|
+
import { promisify } from 'util';
|
|
11
|
+
|
|
12
|
+
const execFile = promisify(execFileCb);
|
|
10
13
|
import { existsSync } from 'fs';
|
|
11
14
|
import { join, resolve } from 'path';
|
|
12
15
|
import {
|
|
@@ -89,8 +92,7 @@ export async function autoUpdateCommand(options) {
|
|
|
89
92
|
} else {
|
|
90
93
|
spinner.start(`Downloading and installing v${latestVersion}...`);
|
|
91
94
|
try {
|
|
92
|
-
|
|
93
|
-
stdio: 'pipe',
|
|
95
|
+
await execFile('npm', ['update', '-g', '@maccesar/titools'], {
|
|
94
96
|
timeout: 60000,
|
|
95
97
|
});
|
|
96
98
|
spinner.succeed(`Updated to v${latestVersion}`);
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor command
|
|
3
|
+
* Diagnoses installation health (read-only)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { existsSync, lstatSync, readFileSync, realpathSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import {
|
|
11
|
+
SKILLS,
|
|
12
|
+
PACKAGE_VERSION,
|
|
13
|
+
TITANIUM_KNOWLEDGE_VERSION,
|
|
14
|
+
getAgentsSkillsDir,
|
|
15
|
+
getClaudeAgentsDir,
|
|
16
|
+
getConfigDir,
|
|
17
|
+
getPlatforms,
|
|
18
|
+
} from '../config.js';
|
|
19
|
+
import { hasHook } from '../hooks.js';
|
|
20
|
+
import { readLastCheck } from '../cache.js';
|
|
21
|
+
import { isTitaniumProject, blockExists } from '../utils.js';
|
|
22
|
+
|
|
23
|
+
const CHECK = chalk.green('✓');
|
|
24
|
+
const CROSS = chalk.red('✗');
|
|
25
|
+
const WARN = chalk.yellow('⚠');
|
|
26
|
+
|
|
27
|
+
function extractIndexVersion(filePath) {
|
|
28
|
+
if (!existsSync(filePath)) return null;
|
|
29
|
+
try {
|
|
30
|
+
const content = readFileSync(filePath, 'utf8');
|
|
31
|
+
const match = content.match(/<!-- Version: (v[\d.]+) -->/);
|
|
32
|
+
return match ? match[1] : null;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function doctorCommand() {
|
|
39
|
+
const homeDir = os.homedir();
|
|
40
|
+
const skillsDir = getAgentsSkillsDir(homeDir);
|
|
41
|
+
const agentsDir = getClaudeAgentsDir(homeDir);
|
|
42
|
+
const claudeDir = join(homeDir, '.claude');
|
|
43
|
+
const cacheDir = getConfigDir();
|
|
44
|
+
const cwd = process.cwd();
|
|
45
|
+
|
|
46
|
+
let issues = 0;
|
|
47
|
+
const symlinkIssues = [];
|
|
48
|
+
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(chalk.bold('Titanium SDK Skills Doctor'));
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log(' Checking installation health...');
|
|
53
|
+
console.log('');
|
|
54
|
+
|
|
55
|
+
// CLI version
|
|
56
|
+
console.log(` ${CHECK} CLI version: v${PACKAGE_VERSION}`);
|
|
57
|
+
|
|
58
|
+
// Skills check
|
|
59
|
+
const missingSkills = [];
|
|
60
|
+
for (const skill of SKILLS) {
|
|
61
|
+
if (!existsSync(join(skillsDir, skill))) {
|
|
62
|
+
missingSkills.push(skill);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const installedCount = SKILLS.length - missingSkills.length;
|
|
66
|
+
if (missingSkills.length === 0) {
|
|
67
|
+
console.log(` ${CHECK} Skills: ${installedCount}/${SKILLS.length} installed in ~/.agents/skills/`);
|
|
68
|
+
} else {
|
|
69
|
+
console.log(` ${CROSS} Skills: ${installedCount}/${SKILLS.length} installed in ~/.agents/skills/ (missing: ${missingSkills.join(', ')})`);
|
|
70
|
+
issues += missingSkills.length;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Agent check
|
|
74
|
+
const agentPath = join(agentsDir, 'ti-pro.md');
|
|
75
|
+
if (existsSync(agentPath)) {
|
|
76
|
+
console.log(` ${CHECK} Agent: ti-pro installed`);
|
|
77
|
+
} else {
|
|
78
|
+
console.log(` ${CROSS} Agent: ti-pro not found`);
|
|
79
|
+
issues++;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Hook check
|
|
83
|
+
if (hasHook(claudeDir)) {
|
|
84
|
+
console.log(` ${CHECK} Hook: SessionStart configured`);
|
|
85
|
+
} else {
|
|
86
|
+
console.log(` ${CROSS} Hook: SessionStart not configured`);
|
|
87
|
+
issues++;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Cache check
|
|
91
|
+
const lastCheck = readLastCheck(cacheDir);
|
|
92
|
+
if (lastCheck) {
|
|
93
|
+
const hoursAgo = Math.round((Date.now() - lastCheck.lastCheck) / (1000 * 60 * 60));
|
|
94
|
+
const timeLabel = hoursAgo < 1 ? 'less than an hour ago' : `${hoursAgo} hour${hoursAgo === 1 ? '' : 's'} ago`;
|
|
95
|
+
console.log(` ${CHECK} Cache: last check ${timeLabel}`);
|
|
96
|
+
} else {
|
|
97
|
+
console.log(` ${WARN} Cache: no check recorded`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Platforms
|
|
101
|
+
console.log('');
|
|
102
|
+
console.log(' Platforms:');
|
|
103
|
+
const platforms = getPlatforms(homeDir);
|
|
104
|
+
for (const platform of platforms) {
|
|
105
|
+
const missing = [];
|
|
106
|
+
const broken = [];
|
|
107
|
+
|
|
108
|
+
for (const skill of SKILLS) {
|
|
109
|
+
const linkPath = join(platform.skillsDir, skill);
|
|
110
|
+
try {
|
|
111
|
+
const stat = lstatSync(linkPath);
|
|
112
|
+
if (stat.isSymbolicLink()) {
|
|
113
|
+
// Check if target exists
|
|
114
|
+
if (!existsSync(linkPath)) {
|
|
115
|
+
broken.push(skill);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// exists (symlink or directory), count as linked
|
|
119
|
+
} catch {
|
|
120
|
+
missing.push(skill);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const linkedCount = SKILLS.length - missing.length - broken.length;
|
|
125
|
+
|
|
126
|
+
if (missing.length === 0 && broken.length === 0) {
|
|
127
|
+
console.log(` ${CHECK} ${platform.displayName}: ${SKILLS.length}/${SKILLS.length} skills linked`);
|
|
128
|
+
} else {
|
|
129
|
+
const problems = [];
|
|
130
|
+
if (missing.length > 0) problems.push(`missing: ${missing.join(', ')}`);
|
|
131
|
+
if (broken.length > 0) problems.push(`broken: ${broken.join(', ')}`);
|
|
132
|
+
console.log(` ${CROSS} ${platform.displayName}: ${linkedCount}/${SKILLS.length} skills linked (${problems.join('; ')})`);
|
|
133
|
+
issues += missing.length + broken.length;
|
|
134
|
+
|
|
135
|
+
// Collect symlink issues for detailed report
|
|
136
|
+
for (const skill of broken) {
|
|
137
|
+
symlinkIssues.push(`${CROSS} ~/${platform.name === 'claude' ? '.claude' : `.${platform.name}`}/skills/${skill} → broken symlink (target missing)`);
|
|
138
|
+
}
|
|
139
|
+
for (const skill of missing) {
|
|
140
|
+
symlinkIssues.push(`${CROSS} ~/${platform.name === 'claude' ? '.claude' : `.${platform.name}`}/skills/${skill} → not found`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Symlink issues detail
|
|
146
|
+
if (symlinkIssues.length > 0) {
|
|
147
|
+
console.log('');
|
|
148
|
+
console.log(` ${WARN} Symlink issues:`);
|
|
149
|
+
for (const issue of symlinkIssues) {
|
|
150
|
+
console.log(` ${issue}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Project check
|
|
155
|
+
if (isTitaniumProject(cwd)) {
|
|
156
|
+
console.log('');
|
|
157
|
+
console.log(' Project:');
|
|
158
|
+
|
|
159
|
+
const mdFiles = ['CLAUDE.md', 'GEMINI.md', 'AGENTS.md'];
|
|
160
|
+
for (const file of mdFiles) {
|
|
161
|
+
const filePath = join(cwd, file);
|
|
162
|
+
if (!existsSync(filePath)) {
|
|
163
|
+
// Not an issue if file doesn't exist — it's optional
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (blockExists(filePath)) {
|
|
168
|
+
const version = extractIndexVersion(filePath);
|
|
169
|
+
if (version === TITANIUM_KNOWLEDGE_VERSION) {
|
|
170
|
+
console.log(` ${CHECK} ${file}: Knowledge Index present (${version})`);
|
|
171
|
+
} else if (version) {
|
|
172
|
+
console.log(` ${CROSS} ${file}: Knowledge Index outdated (${version})`);
|
|
173
|
+
issues++;
|
|
174
|
+
} else {
|
|
175
|
+
console.log(` ${CHECK} ${file}: Knowledge Index present`);
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
console.log(` ${CROSS} ${file}: no Knowledge Index`);
|
|
179
|
+
issues++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Summary
|
|
185
|
+
console.log('');
|
|
186
|
+
if (issues === 0) {
|
|
187
|
+
console.log(chalk.green(` No issues found.`));
|
|
188
|
+
} else {
|
|
189
|
+
console.log(chalk.yellow(` ${issues} issue${issues === 1 ? '' : 's'} found. Run 'titools install' to fix.`));
|
|
190
|
+
}
|
|
191
|
+
console.log('');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export default { doctorCommand };
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status command
|
|
3
|
+
* Shows a quick overview of what's installed (read-only)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { existsSync, readdirSync, lstatSync, readFileSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import {
|
|
11
|
+
SKILLS,
|
|
12
|
+
PACKAGE_VERSION,
|
|
13
|
+
getAgentsSkillsDir,
|
|
14
|
+
getClaudeAgentsDir,
|
|
15
|
+
getConfigDir,
|
|
16
|
+
getPlatforms,
|
|
17
|
+
BLOCK_START,
|
|
18
|
+
} from '../config.js';
|
|
19
|
+
import { hasHook } from '../hooks.js';
|
|
20
|
+
import { readLastCheck } from '../cache.js';
|
|
21
|
+
import { isTitaniumProject, detectTitaniumVersion, blockExists } from '../utils.js';
|
|
22
|
+
|
|
23
|
+
const CHECK = chalk.green('✓');
|
|
24
|
+
const CROSS = chalk.red('✗');
|
|
25
|
+
|
|
26
|
+
function countInstalledSkills(skillsDir) {
|
|
27
|
+
let count = 0;
|
|
28
|
+
for (const skill of SKILLS) {
|
|
29
|
+
if (existsSync(join(skillsDir, skill))) {
|
|
30
|
+
count++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return count;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function countLinkedSkills(platformSkillsDir) {
|
|
37
|
+
let count = 0;
|
|
38
|
+
for (const skill of SKILLS) {
|
|
39
|
+
const linkPath = join(platformSkillsDir, skill);
|
|
40
|
+
try {
|
|
41
|
+
lstatSync(linkPath);
|
|
42
|
+
count++;
|
|
43
|
+
} catch {
|
|
44
|
+
// not found
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return count;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatLastCheck(data) {
|
|
51
|
+
if (!data) return chalk.gray('never');
|
|
52
|
+
const date = new Date(data.lastCheck);
|
|
53
|
+
const formatted = date.toISOString().replace('T', ' ').slice(0, 16);
|
|
54
|
+
return `${formatted} (v${data.latestVersion})`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractIndexVersion(filePath) {
|
|
58
|
+
if (!existsSync(filePath)) return null;
|
|
59
|
+
try {
|
|
60
|
+
const content = readFileSync(filePath, 'utf8');
|
|
61
|
+
const match = content.match(/<!-- Version: (v[\d.]+) -->/);
|
|
62
|
+
return match ? match[1] : null;
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function statusCommand() {
|
|
69
|
+
const homeDir = os.homedir();
|
|
70
|
+
const skillsDir = getAgentsSkillsDir(homeDir);
|
|
71
|
+
const agentsDir = getClaudeAgentsDir(homeDir);
|
|
72
|
+
const claudeDir = join(homeDir, '.claude');
|
|
73
|
+
const cacheDir = getConfigDir();
|
|
74
|
+
const cwd = process.cwd();
|
|
75
|
+
|
|
76
|
+
// Skills count
|
|
77
|
+
const installedCount = countInstalledSkills(skillsDir);
|
|
78
|
+
const totalCount = SKILLS.length;
|
|
79
|
+
|
|
80
|
+
// Agent check
|
|
81
|
+
const agentExists = existsSync(join(agentsDir, 'ti-pro.md'));
|
|
82
|
+
|
|
83
|
+
// Hook check
|
|
84
|
+
const hookExists = hasHook(claudeDir);
|
|
85
|
+
|
|
86
|
+
// Cache
|
|
87
|
+
const lastCheck = readLastCheck(cacheDir);
|
|
88
|
+
|
|
89
|
+
console.log('');
|
|
90
|
+
console.log(chalk.bold('Titanium SDK Skills Status'));
|
|
91
|
+
console.log('');
|
|
92
|
+
console.log(` Version: v${PACKAGE_VERSION}`);
|
|
93
|
+
console.log(` Skills: ${installedCount}/${totalCount} installed`);
|
|
94
|
+
console.log(` Agent: ti-pro ${agentExists ? CHECK : CROSS}`);
|
|
95
|
+
console.log(` Hook: Claude Code SessionStart ${hookExists ? CHECK : CROSS}`);
|
|
96
|
+
console.log(` Last check: ${formatLastCheck(lastCheck)}`);
|
|
97
|
+
|
|
98
|
+
// Platforms
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log(' Platforms:');
|
|
101
|
+
const platforms = getPlatforms(homeDir);
|
|
102
|
+
for (const platform of platforms) {
|
|
103
|
+
const linked = countLinkedSkills(platform.skillsDir);
|
|
104
|
+
if (linked > 0) {
|
|
105
|
+
console.log(` ${platform.displayName.padEnd(13)} ${CHECK} ${linked} skills linked`);
|
|
106
|
+
} else {
|
|
107
|
+
console.log(` ${platform.displayName.padEnd(13)} ${CROSS} ${chalk.gray('not linked')}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Project
|
|
112
|
+
console.log('');
|
|
113
|
+
if (isTitaniumProject(cwd)) {
|
|
114
|
+
const sdkVersion = detectTitaniumVersion(cwd);
|
|
115
|
+
console.log(` Project: ${chalk.cyan('Titanium')} (SDK ${sdkVersion})`);
|
|
116
|
+
|
|
117
|
+
const mdFiles = ['CLAUDE.md', 'GEMINI.md', 'AGENTS.md'];
|
|
118
|
+
for (const file of mdFiles) {
|
|
119
|
+
const filePath = join(cwd, file);
|
|
120
|
+
if (!existsSync(filePath)) {
|
|
121
|
+
console.log(` ${file.padEnd(11)} ${CROSS} ${chalk.gray('not found')}`);
|
|
122
|
+
} else if (blockExists(filePath)) {
|
|
123
|
+
const version = extractIndexVersion(filePath);
|
|
124
|
+
console.log(` ${file.padEnd(11)} ${CHECK} Knowledge Index${version ? ` (${version})` : ''}`);
|
|
125
|
+
} else {
|
|
126
|
+
console.log(` ${file.padEnd(11)} ${CROSS} ${chalk.gray('no index')}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
console.log(` Project: ${chalk.gray('(not a Titanium project)')}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log('');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default { statusCommand };
|