tab-agent 0.3.3 → 0.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +183 -31
  3. package/bin/tab-agent.js +23 -8
  4. package/cli/command.js +113 -9
  5. package/cli/detect-extension.js +96 -14
  6. package/cli/launch-chrome.js +150 -0
  7. package/cli/setup.js +57 -22
  8. package/cli/start.js +65 -13
  9. package/cli/status.js +41 -7
  10. package/extension/content-script.js +218 -17
  11. package/extension/manifest.json +4 -3
  12. package/extension/manifest.safari.json +45 -0
  13. package/extension/popup/popup.html +58 -1
  14. package/extension/popup/popup.js +18 -0
  15. package/extension/service-worker.js +106 -13
  16. package/package.json +14 -3
  17. package/relay/install-native-host.sh +2 -2
  18. package/relay/native-host-wrapper.sh +1 -1
  19. package/relay/native-host.js +3 -1
  20. package/relay/server.js +124 -17
  21. package/skills/claude-code/tab-agent/SKILL.md +92 -0
  22. package/skills/codex/tab-agent/SKILL.md +92 -0
  23. package/relay/node_modules/.package-lock.json +0 -29
  24. package/relay/node_modules/ws/LICENSE +0 -20
  25. package/relay/node_modules/ws/README.md +0 -548
  26. package/relay/node_modules/ws/browser.js +0 -8
  27. package/relay/node_modules/ws/index.js +0 -13
  28. package/relay/node_modules/ws/lib/buffer-util.js +0 -131
  29. package/relay/node_modules/ws/lib/constants.js +0 -19
  30. package/relay/node_modules/ws/lib/event-target.js +0 -292
  31. package/relay/node_modules/ws/lib/extension.js +0 -203
  32. package/relay/node_modules/ws/lib/limiter.js +0 -55
  33. package/relay/node_modules/ws/lib/permessage-deflate.js +0 -528
  34. package/relay/node_modules/ws/lib/receiver.js +0 -706
  35. package/relay/node_modules/ws/lib/sender.js +0 -602
  36. package/relay/node_modules/ws/lib/stream.js +0 -161
  37. package/relay/node_modules/ws/lib/subprotocol.js +0 -62
  38. package/relay/node_modules/ws/lib/validation.js +0 -152
  39. package/relay/node_modules/ws/lib/websocket-server.js +0 -554
  40. package/relay/node_modules/ws/lib/websocket.js +0 -1393
  41. package/relay/node_modules/ws/package.json +0 -69
  42. package/relay/node_modules/ws/wrapper.mjs +0 -8
  43. package/relay/package-lock.json +0 -36
  44. package/relay/package.json +0 -12
  45. package/skills/claude-code/tab-agent.md +0 -57
  46. package/skills/codex/tab-agent.md +0 -38
@@ -3,8 +3,10 @@ const fs = require('fs');
3
3
  const path = require('path');
4
4
  const os = require('os');
5
5
 
6
+ const PREF_FILES = ['Secure Preferences', 'Preferences'];
7
+
6
8
  // Support multiple browsers: Chrome, Brave, Edge, Chromium
7
- function getAllBrowserExtensionPaths() {
9
+ function getBrowserRoots() {
8
10
  const platform = os.platform();
9
11
  const home = os.homedir();
10
12
  const paths = [];
@@ -35,24 +37,99 @@ function getAllBrowserExtensionPaths() {
35
37
  );
36
38
  }
37
39
 
38
- // Expand to include Default and Profile N directories
39
- const expandedPaths = [];
40
- for (const browserPath of paths) {
40
+ return paths;
41
+ }
42
+
43
+ function getAllBrowserProfileDirs() {
44
+ const profileDirs = [];
45
+
46
+ for (const browserPath of getBrowserRoots()) {
41
47
  if (!fs.existsSync(browserPath)) continue;
42
48
 
43
49
  const profiles = ['Default', ...fs.readdirSync(browserPath).filter(f => f.startsWith('Profile '))];
44
50
  for (const profile of profiles) {
45
- const extPath = path.join(browserPath, profile, 'Extensions');
46
- if (fs.existsSync(extPath)) {
47
- expandedPaths.push(extPath);
51
+ const profileDir = path.join(browserPath, profile);
52
+ if (fs.existsSync(profileDir)) {
53
+ profileDirs.push({ browserPath, profileDir, profile });
48
54
  }
49
55
  }
50
56
  }
51
57
 
58
+ return profileDirs;
59
+ }
60
+
61
+ function getAllBrowserExtensionPaths() {
62
+ const expandedPaths = [];
63
+ for (const { profileDir } of getAllBrowserProfileDirs()) {
64
+ const extPath = path.join(profileDir, 'Extensions');
65
+ if (fs.existsSync(extPath)) {
66
+ expandedPaths.push(extPath);
67
+ }
68
+ }
69
+
52
70
  return expandedPaths;
53
71
  }
54
72
 
73
+ function readJson(filePath) {
74
+ try {
75
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
76
+ } catch (e) {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ function readExtensionManifest(extensionPath) {
82
+ if (!extensionPath) return null;
83
+
84
+ for (const filename of ['manifest.json', 'manifest.chrome.json']) {
85
+ const manifestPath = path.join(extensionPath, filename);
86
+ if (fs.existsSync(manifestPath)) {
87
+ const manifest = readJson(manifestPath);
88
+ if (manifest) return manifest;
89
+ }
90
+ }
91
+
92
+ return null;
93
+ }
94
+
95
+ function isTabAgentManifest(manifest) {
96
+ return manifest?.name === 'Tab Agent';
97
+ }
98
+
99
+ function findTabAgentFromPreferences() {
100
+ for (const { browserPath, profileDir, profile } of getAllBrowserProfileDirs()) {
101
+ for (const prefFile of PREF_FILES) {
102
+ const prefs = readJson(path.join(profileDir, prefFile));
103
+ const settings = prefs?.extensions?.settings;
104
+ if (!settings) continue;
105
+
106
+ for (const [extId, info] of Object.entries(settings)) {
107
+ const manifest = isTabAgentManifest(info.manifest)
108
+ ? info.manifest
109
+ : readExtensionManifest(info.path);
110
+
111
+ if (!isTabAgentManifest(manifest)) continue;
112
+
113
+ return {
114
+ extId,
115
+ browser: path.join(profileDir, 'Extensions'),
116
+ browserPath,
117
+ profile,
118
+ path: info.path || null,
119
+ source: 'preferences',
120
+ version: manifest.version || info.service_worker_registration_info?.version || null
121
+ };
122
+ }
123
+ }
124
+ }
125
+
126
+ return null;
127
+ }
128
+
55
129
  function findTabAgentExtension() {
130
+ const unpacked = findTabAgentFromPreferences();
131
+ if (unpacked) return unpacked;
132
+
56
133
  const extensionPaths = getAllBrowserExtensionPaths();
57
134
 
58
135
  for (const extPath of extensionPaths) {
@@ -68,12 +145,16 @@ function findTabAgentExtension() {
68
145
  for (const version of versions) {
69
146
  const manifestPath = path.join(extDir, version, 'manifest.json');
70
147
  if (fs.existsSync(manifestPath)) {
71
- try {
72
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
73
- if (manifest.name === 'Tab Agent') {
74
- return { extId, browser: extPath };
75
- }
76
- } catch (e) {}
148
+ const manifest = readJson(manifestPath);
149
+ if (isTabAgentManifest(manifest)) {
150
+ return {
151
+ extId,
152
+ browser: extPath,
153
+ path: path.join(extDir, version),
154
+ source: 'extensions-dir',
155
+ version: manifest.version || version
156
+ };
157
+ }
77
158
  }
78
159
  }
79
160
  }
@@ -127,5 +208,6 @@ module.exports = {
127
208
  findTabAgentExtension,
128
209
  checkExistingManifest,
129
210
  promptForExtensionId,
130
- getAllBrowserExtensionPaths
211
+ getAllBrowserExtensionPaths,
212
+ getAllBrowserProfileDirs
131
213
  };
@@ -0,0 +1,150 @@
1
+ // cli/launch-chrome.js
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const readline = require('readline');
6
+ const { execSync, spawn } = require('child_process');
7
+
8
+ function getChromeUserDataDir() {
9
+ const home = os.homedir();
10
+ const platform = os.platform();
11
+
12
+ if (platform === 'darwin') {
13
+ return path.join(home, 'Library/Application Support/Google/Chrome');
14
+ } else if (platform === 'linux') {
15
+ return path.join(home, '.config/google-chrome');
16
+ } else if (platform === 'win32') {
17
+ return path.join(process.env.LOCALAPPDATA, 'Google/Chrome/User Data');
18
+ }
19
+ return null;
20
+ }
21
+
22
+ function detectProfiles() {
23
+ const userDataDir = getChromeUserDataDir();
24
+ if (!userDataDir || !fs.existsSync(userDataDir)) return [];
25
+
26
+ const candidates = ['Default'];
27
+ try {
28
+ const entries = fs.readdirSync(userDataDir);
29
+ for (const entry of entries) {
30
+ if (entry.startsWith('Profile ')) candidates.push(entry);
31
+ }
32
+ } catch (e) {}
33
+
34
+ const profiles = [];
35
+ for (const dir of candidates) {
36
+ const prefsPath = path.join(userDataDir, dir, 'Preferences');
37
+ if (!fs.existsSync(prefsPath)) continue;
38
+ try {
39
+ const prefs = JSON.parse(fs.readFileSync(prefsPath, 'utf8'));
40
+ const name = prefs.profile?.name || dir;
41
+ profiles.push({ dir, name });
42
+ } catch (e) {
43
+ profiles.push({ dir, name: dir });
44
+ }
45
+ }
46
+ return profiles;
47
+ }
48
+
49
+ async function promptForProfile(profiles) {
50
+ if (profiles.length === 0) {
51
+ console.log('No Chrome profiles found.');
52
+ return null;
53
+ }
54
+
55
+ if (profiles.length === 1) {
56
+ console.log(`Using profile: ${profiles[0].name}`);
57
+ return profiles[0];
58
+ }
59
+
60
+ console.log('\nChrome Profiles:');
61
+ profiles.forEach((p, i) => {
62
+ console.log(` ${i + 1}. ${p.name} (${p.dir})`);
63
+ });
64
+
65
+ const rl = readline.createInterface({
66
+ input: process.stdin,
67
+ output: process.stdout
68
+ });
69
+
70
+ return new Promise((resolve) => {
71
+ rl.question(`\nSelect profile [1-${profiles.length}]: `, (answer) => {
72
+ rl.close();
73
+ const idx = parseInt(answer, 10) - 1;
74
+ if (idx >= 0 && idx < profiles.length) {
75
+ resolve(profiles[idx]);
76
+ } else {
77
+ console.log('Invalid selection, using first profile.');
78
+ resolve(profiles[0]);
79
+ }
80
+ });
81
+ });
82
+ }
83
+
84
+ function isChromeRunning() {
85
+ const platform = os.platform();
86
+ try {
87
+ if (platform === 'darwin') {
88
+ execSync('pgrep -x "Google Chrome"', { stdio: 'ignore' });
89
+ return true;
90
+ } else if (platform === 'linux') {
91
+ execSync('pgrep -x chrome', { stdio: 'ignore' });
92
+ return true;
93
+ } else if (platform === 'win32') {
94
+ const result = execSync('tasklist /FI "IMAGENAME eq chrome.exe" /NH', { encoding: 'utf8' });
95
+ return result.includes('chrome.exe');
96
+ }
97
+ } catch (e) {}
98
+ return false;
99
+ }
100
+
101
+ function launchChrome(profileDir) {
102
+ const platform = os.platform();
103
+ const args = profileDir ? [`--profile-directory=${profileDir}`] : [];
104
+
105
+ if (platform === 'darwin') {
106
+ spawn('open', ['-a', 'Google Chrome', '--args', ...args], {
107
+ detached: true,
108
+ stdio: 'ignore'
109
+ }).unref();
110
+ } else if (platform === 'linux') {
111
+ spawn('google-chrome', args, {
112
+ detached: true,
113
+ stdio: 'ignore'
114
+ }).unref();
115
+ } else if (platform === 'win32') {
116
+ const chromePath = path.join(
117
+ process.env.PROGRAMFILES || 'C:\\Program Files',
118
+ 'Google\\Chrome\\Application\\chrome.exe'
119
+ );
120
+ spawn(chromePath, args, {
121
+ detached: true,
122
+ stdio: 'ignore'
123
+ }).unref();
124
+ }
125
+ }
126
+
127
+ function getConfigPath() {
128
+ return path.join(os.homedir(), '.tab-agent.json');
129
+ }
130
+
131
+ function loadConfig() {
132
+ try {
133
+ return JSON.parse(fs.readFileSync(getConfigPath(), 'utf8'));
134
+ } catch (e) {
135
+ return {};
136
+ }
137
+ }
138
+
139
+ function saveConfig(config) {
140
+ const existing = loadConfig();
141
+ const merged = { ...existing, ...config };
142
+ fs.writeFileSync(getConfigPath(), JSON.stringify(merged, null, 2));
143
+ }
144
+
145
+ function getSavedProfile() {
146
+ const config = loadConfig();
147
+ return config.profile || null;
148
+ }
149
+
150
+ module.exports = { detectProfiles, getChromeUserDataDir, promptForProfile, isChromeRunning, launchChrome, loadConfig, saveConfig, getSavedProfile };
package/cli/setup.js CHANGED
@@ -3,6 +3,7 @@ const fs = require('fs');
3
3
  const path = require('path');
4
4
  const os = require('os');
5
5
  const { findTabAgentExtension, checkExistingManifest, promptForExtensionId } = require('./detect-extension');
6
+ const { detectProfiles, promptForProfile, saveConfig } = require('./launch-chrome');
6
7
 
7
8
  async function setup() {
8
9
  console.log('Tab Agent Setup\n');
@@ -15,6 +16,12 @@ async function setup() {
15
16
  if (found) {
16
17
  extensionId = found.extId;
17
18
  console.log(`✓ Found extension: ${extensionId}`);
19
+ if (found.profile) {
20
+ console.log(` Profile: ${found.profile}`);
21
+ }
22
+ if (found.path) {
23
+ console.log(` Path: ${found.path}`);
24
+ }
18
25
  } else {
19
26
  extensionId = checkExistingManifest();
20
27
  if (extensionId) {
@@ -36,15 +43,28 @@ async function setup() {
36
43
  installNativeHost(extensionId);
37
44
  console.log('✓ Native messaging host installed');
38
45
 
39
- // 3. Install skills
46
+ // 3. Select Chrome profile
47
+ console.log('\nDetecting Chrome profiles...');
48
+ const profiles = detectProfiles();
49
+ if (profiles.length > 0) {
50
+ const selected = await promptForProfile(profiles);
51
+ if (selected) {
52
+ saveConfig({ profile: selected.dir });
53
+ console.log(`✓ Saved default profile: ${selected.name} (${selected.dir})`);
54
+ }
55
+ } else {
56
+ console.log(' No Chrome profiles found, skipping.');
57
+ }
58
+
59
+ // 4. Install skills
40
60
  console.log('\nInstalling skills...');
41
61
  installSkills();
42
62
 
43
63
  console.log('\n✓ Setup complete!\n');
44
64
  console.log('Usage:');
45
- console.log(' 1. Click Tab Agent icon on any tab (turns green)');
46
- console.log(' 2. Ask Claude/Codex: "Use tab-agent to search Google"');
47
- console.log('\nThe relay server starts automatically when needed.');
65
+ console.log(' 1. Start relay server: npx tab-agent@latest start');
66
+ console.log(' 2. Click Tab Agent icon on any tab (turns green)');
67
+ console.log(' 3. Ask Claude/Codex: "Use tab-agent to search Google"');
48
68
  }
49
69
 
50
70
  function installNativeHost(extensionId) {
@@ -99,35 +119,50 @@ function installSkills() {
99
119
  const skillSource = path.join(packageDir, 'skills');
100
120
 
101
121
  // Claude Code - always install
102
- const claudeSkillDir = path.join(home, '.claude', 'skills');
103
- const claudeSkillPath = path.join(claudeSkillDir, 'tab-agent.md');
122
+ // Expected structure: ~/.claude/skills/tab-agent/SKILL.md
123
+ const claudeSkillDir = path.join(home, '.claude', 'skills', 'tab-agent');
124
+ const claudeSkillPath = path.join(claudeSkillDir, 'SKILL.md');
125
+
126
+ // Remove old flat file if it exists (from previous versions)
127
+ const oldClaudeSkillPath = path.join(home, '.claude', 'skills', 'tab-agent.md');
128
+ if (fs.existsSync(oldClaudeSkillPath)) {
129
+ fs.unlinkSync(oldClaudeSkillPath);
130
+ console.log(' Removed old skill file format');
131
+ }
132
+
104
133
  fs.mkdirSync(claudeSkillDir, { recursive: true });
105
134
 
106
135
  if (fs.existsSync(claudeSkillPath)) {
107
- console.log(` Updating existing skill at ${claudeSkillPath}`);
136
+ console.log(` Updating existing skill at ${claudeSkillDir}`);
108
137
  }
109
138
  fs.copyFileSync(
110
- path.join(skillSource, 'claude-code', 'tab-agent.md'),
139
+ path.join(skillSource, 'claude-code', 'tab-agent', 'SKILL.md'),
111
140
  claudeSkillPath
112
141
  );
113
142
  console.log('✓ Installed Claude Code skill');
114
143
 
115
- // Codex (only if .codex exists)
116
- const codexDir = path.join(home, '.codex');
117
- if (fs.existsSync(codexDir)) {
118
- const codexSkillDir = path.join(codexDir, 'skills');
119
- const codexSkillPath = path.join(codexSkillDir, 'tab-agent.md');
120
- fs.mkdirSync(codexSkillDir, { recursive: true });
144
+ // Codex - always install (create .codex if needed)
145
+ // Expected structure: ~/.codex/skills/tab-agent/SKILL.md
146
+ const codexSkillDir = path.join(home, '.codex', 'skills', 'tab-agent');
147
+ const codexSkillPath = path.join(codexSkillDir, 'SKILL.md');
121
148
 
122
- if (fs.existsSync(codexSkillPath)) {
123
- console.log(` Updating existing skill at ${codexSkillPath}`);
124
- }
125
- fs.copyFileSync(
126
- path.join(skillSource, 'codex', 'tab-agent.md'),
127
- codexSkillPath
128
- );
129
- console.log('✓ Installed Codex skill');
149
+ // Remove old flat file if it exists (from previous versions)
150
+ const oldCodexSkillPath = path.join(home, '.codex', 'skills', 'tab-agent.md');
151
+ if (fs.existsSync(oldCodexSkillPath)) {
152
+ fs.unlinkSync(oldCodexSkillPath);
153
+ console.log(' Removed old Codex skill file format');
130
154
  }
155
+
156
+ fs.mkdirSync(codexSkillDir, { recursive: true });
157
+
158
+ if (fs.existsSync(codexSkillPath)) {
159
+ console.log(` Updating existing skill at ${codexSkillDir}`);
160
+ }
161
+ fs.copyFileSync(
162
+ path.join(skillSource, 'codex', 'tab-agent', 'SKILL.md'),
163
+ codexSkillPath
164
+ );
165
+ console.log('✓ Installed Codex skill');
131
166
  }
132
167
 
133
168
  setup().catch(console.error);
package/cli/start.js CHANGED
@@ -1,19 +1,71 @@
1
1
  // cli/start.js
2
2
  const path = require('path');
3
3
  const { spawn } = require('child_process');
4
+ const { detectProfiles, promptForProfile, isChromeRunning, launchChrome, getSavedProfile } = require('./launch-chrome');
4
5
 
5
- const serverPath = path.join(__dirname, '..', 'relay', 'server.js');
6
- const server = spawn('node', [serverPath], {
7
- stdio: 'inherit',
8
- detached: false
9
- });
6
+ async function start() {
7
+ // Parse --profile flag
8
+ const profileFlag = process.argv.find(a => a.startsWith('--profile'));
9
+ let requestedProfile = null;
10
+ if (profileFlag) {
11
+ requestedProfile = profileFlag.includes('=')
12
+ ? profileFlag.split('=')[1]
13
+ : process.argv[process.argv.indexOf(profileFlag) + 1];
14
+ }
10
15
 
11
- server.on('error', (err) => {
12
- console.error('Failed to start server:', err.message);
13
- process.exit(1);
14
- });
16
+ // Launch Chrome if not running
17
+ if (!isChromeRunning()) {
18
+ console.log('Chrome is not running.\n');
19
+ const profiles = detectProfiles();
15
20
 
16
- process.on('SIGINT', () => {
17
- server.kill();
18
- process.exit(0);
19
- });
21
+ if (profiles.length > 0) {
22
+ let selected;
23
+ // Priority: --profile flag > saved config > interactive prompt
24
+ const profileToFind = requestedProfile || getSavedProfile();
25
+ if (profileToFind) {
26
+ selected = profiles.find(p =>
27
+ p.name.toLowerCase() === profileToFind.toLowerCase() ||
28
+ p.dir.toLowerCase() === profileToFind.toLowerCase()
29
+ );
30
+ if (!selected) {
31
+ console.log(`Profile "${profileToFind}" not found. Available profiles:`);
32
+ profiles.forEach(p => console.log(` - ${p.name} (${p.dir})`));
33
+ process.exit(1);
34
+ }
35
+ } else {
36
+ selected = await promptForProfile(profiles);
37
+ }
38
+ if (selected) {
39
+ console.log(`\nLaunching Chrome with profile "${selected.name}"...`);
40
+ launchChrome(selected.dir);
41
+ // Give Chrome time to start and load extension
42
+ await new Promise(r => setTimeout(r, 3000));
43
+ }
44
+ } else {
45
+ console.log('Launching Chrome...');
46
+ launchChrome(null);
47
+ await new Promise(r => setTimeout(r, 3000));
48
+ }
49
+ } else {
50
+ console.log('Chrome is already running.');
51
+ }
52
+
53
+ // Start relay server
54
+ const serverPath = path.join(__dirname, '..', 'relay', 'server.js');
55
+ const server = spawn('node', [serverPath], {
56
+ stdio: 'inherit',
57
+ detached: false
58
+ });
59
+
60
+ server.on('error', (err) => {
61
+ console.error('Failed to start server:', err.message);
62
+ process.exit(1);
63
+ });
64
+
65
+ process.on('SIGINT', () => {
66
+ server.kill();
67
+ process.exit(0);
68
+ });
69
+ }
70
+
71
+ start().catch(console.error);
package/cli/status.js CHANGED
@@ -3,6 +3,9 @@ const fs = require('fs');
3
3
  const path = require('path');
4
4
  const os = require('os');
5
5
  const http = require('http');
6
+ const { findTabAgentExtension } = require('./detect-extension');
7
+
8
+ const PACKAGE_VERSION = require('../package.json').version;
6
9
 
7
10
  function checkRelayServer() {
8
11
  return new Promise((resolve) => {
@@ -27,7 +30,7 @@ function checkRelayServer() {
27
30
  }
28
31
 
29
32
  async function status() {
30
- console.log('Tab Agent Status\n');
33
+ console.log(`Tab Agent Status v${PACKAGE_VERSION}\n`);
31
34
 
32
35
  // Check native host
33
36
  const home = os.homedir();
@@ -46,16 +49,47 @@ async function status() {
46
49
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
47
50
  console.log('Native Host: Installed');
48
51
  console.log(` Extension: ${manifest.allowed_origins[0]}`);
52
+ console.log(` Path: ${manifest.path}`);
49
53
  } else {
50
- console.log('Native Host: Not installed (run: npx tab-agent setup)');
54
+ console.log('Native Host: Not installed (run: npx tab-agent@latest setup)');
55
+ }
56
+
57
+ const extension = findTabAgentExtension();
58
+ if (extension) {
59
+ console.log('\nExtension: Detected');
60
+ console.log(` ID: ${extension.extId}`);
61
+ if (extension.version) {
62
+ console.log(` Version: ${extension.version}`);
63
+ }
64
+ if (extension.profile) {
65
+ console.log(` Profile: ${extension.profile}`);
66
+ }
67
+ if (extension.path) {
68
+ console.log(` Path: ${extension.path}`);
69
+ }
70
+ } else {
71
+ console.log('\nExtension: Not detected');
51
72
  }
52
73
 
53
74
  // Check skills
54
- const claudeSkill = path.join(home, '.claude', 'skills', 'tab-agent.md');
55
- const codexSkill = path.join(home, '.codex', 'skills', 'tab-agent.md');
75
+ const claudeSkill = path.join(home, '.claude', 'skills', 'tab-agent', 'SKILL.md');
76
+ const codexSkill = path.join(home, '.codex', 'skills', 'tab-agent', 'SKILL.md');
77
+ const legacyClaudeSkill = path.join(home, '.claude', 'skills', 'tab-agent.md');
78
+ const legacyCodexSkill = path.join(home, '.codex', 'skills', 'tab-agent.md');
79
+
80
+ const claudeSkillStatus = fs.existsSync(claudeSkill)
81
+ ? 'Installed'
82
+ : fs.existsSync(legacyClaudeSkill)
83
+ ? 'Installed (legacy path)'
84
+ : 'Not installed';
85
+ const codexSkillStatus = fs.existsSync(codexSkill)
86
+ ? 'Installed'
87
+ : fs.existsSync(legacyCodexSkill)
88
+ ? 'Installed (legacy path)'
89
+ : 'Not installed (optional)';
56
90
 
57
- console.log(`\nClaude Skill: ${fs.existsSync(claudeSkill) ? 'Installed' : 'Not installed'}`);
58
- console.log(`Codex Skill: ${fs.existsSync(codexSkill) ? 'Installed' : 'Not installed (optional)'}`);
91
+ console.log(`\nClaude Skill: ${claudeSkillStatus}`);
92
+ console.log(`Codex Skill: ${codexSkillStatus}`);
59
93
 
60
94
  // Check relay server
61
95
  console.log('\nRelay Server:');
@@ -63,7 +97,7 @@ async function status() {
63
97
  if (relayStatus.running) {
64
98
  console.log(` Status: Running (${relayStatus.clients} client${relayStatus.clients !== 1 ? 's' : ''})`);
65
99
  } else {
66
- console.log(' Status: Not running (starts automatically when needed)');
100
+ console.log(' Status: Not running (start with: npx tab-agent@latest start)');
67
101
  }
68
102
  }
69
103