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.
- package/CHANGELOG.md +29 -0
- package/README.md +183 -31
- package/bin/tab-agent.js +23 -8
- package/cli/command.js +113 -9
- package/cli/detect-extension.js +96 -14
- package/cli/launch-chrome.js +150 -0
- package/cli/setup.js +57 -22
- package/cli/start.js +65 -13
- package/cli/status.js +41 -7
- package/extension/content-script.js +218 -17
- package/extension/manifest.json +4 -3
- package/extension/manifest.safari.json +45 -0
- package/extension/popup/popup.html +58 -1
- package/extension/popup/popup.js +18 -0
- package/extension/service-worker.js +106 -13
- package/package.json +14 -3
- package/relay/install-native-host.sh +2 -2
- package/relay/native-host-wrapper.sh +1 -1
- package/relay/native-host.js +3 -1
- package/relay/server.js +124 -17
- package/skills/claude-code/tab-agent/SKILL.md +92 -0
- package/skills/codex/tab-agent/SKILL.md +92 -0
- package/relay/node_modules/.package-lock.json +0 -29
- package/relay/node_modules/ws/LICENSE +0 -20
- package/relay/node_modules/ws/README.md +0 -548
- package/relay/node_modules/ws/browser.js +0 -8
- package/relay/node_modules/ws/index.js +0 -13
- package/relay/node_modules/ws/lib/buffer-util.js +0 -131
- package/relay/node_modules/ws/lib/constants.js +0 -19
- package/relay/node_modules/ws/lib/event-target.js +0 -292
- package/relay/node_modules/ws/lib/extension.js +0 -203
- package/relay/node_modules/ws/lib/limiter.js +0 -55
- package/relay/node_modules/ws/lib/permessage-deflate.js +0 -528
- package/relay/node_modules/ws/lib/receiver.js +0 -706
- package/relay/node_modules/ws/lib/sender.js +0 -602
- package/relay/node_modules/ws/lib/stream.js +0 -161
- package/relay/node_modules/ws/lib/subprotocol.js +0 -62
- package/relay/node_modules/ws/lib/validation.js +0 -152
- package/relay/node_modules/ws/lib/websocket-server.js +0 -554
- package/relay/node_modules/ws/lib/websocket.js +0 -1393
- package/relay/node_modules/ws/package.json +0 -69
- package/relay/node_modules/ws/wrapper.mjs +0 -8
- package/relay/package-lock.json +0 -36
- package/relay/package.json +0 -12
- package/skills/claude-code/tab-agent.md +0 -57
- package/skills/codex/tab-agent.md +0 -38
package/cli/detect-extension.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
46
|
-
if (fs.existsSync(
|
|
47
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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.
|
|
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.
|
|
46
|
-
console.log(' 2.
|
|
47
|
-
console.log('
|
|
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
|
-
|
|
103
|
-
const
|
|
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 ${
|
|
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 (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
fs.
|
|
126
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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(
|
|
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: ${
|
|
58
|
-
console.log(`Codex Skill: ${
|
|
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 (
|
|
100
|
+
console.log(' Status: Not running (start with: npx tab-agent@latest start)');
|
|
67
101
|
}
|
|
68
102
|
}
|
|
69
103
|
|