@agentrhq/webcmd 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -24
- package/cli-manifest.json +504 -0
- package/clis/_shared/site-auth.js +6 -1
- package/clis/district/_lib.js +566 -0
- package/clis/district/auth.js +49 -0
- package/clis/district/checkout.js +278 -0
- package/clis/district/listings.js +158 -0
- package/clis/district/locations.js +211 -0
- package/clis/district/search.js +218 -0
- package/clis/district/seats.js +233 -0
- package/clis/district/set-location.js +82 -0
- package/clis/district/showtimes.js +433 -0
- package/clis/reddit/popular.js +12 -1
- package/clis/reddit/popular.test.js +12 -3
- package/dist/src/browser/bridge.d.ts +1 -0
- package/dist/src/browser/bridge.js +1 -1
- package/dist/src/browser/page.d.ts +4 -1
- package/dist/src/browser/page.js +12 -1
- package/dist/src/browser/protocol.d.ts +2 -0
- package/dist/src/browser/runtime/local-cloak/actions.js +1 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +2 -0
- package/dist/src/browser/runtime/local-cloak/session-manager.js +12 -2
- package/dist/src/browser/runtime/local-cloak/session-manager.test.js +58 -0
- package/dist/src/build-manifest.js +1 -0
- package/dist/src/build-manifest.test.js +34 -0
- package/dist/src/cli.js +111 -28
- package/dist/src/discovery.js +1 -0
- package/dist/src/engine.test.js +62 -0
- package/dist/src/execution.js +1 -1
- package/dist/src/manifest-types.d.ts +2 -0
- package/dist/src/registry.d.ts +10 -0
- package/dist/src/registry.js +5 -3
- package/dist/src/registry.test.js +25 -0
- package/dist/src/runtime.d.ts +2 -0
- package/dist/src/runtime.js +1 -0
- package/dist/src/skills.d.ts +23 -5
- package/dist/src/skills.js +87 -45
- package/dist/src/skills.test.js +80 -23
- package/package.json +2 -2
- package/skills/smart-search/SKILL.md +156 -0
- package/skills/smart-search/references/sources-ai.md +74 -0
- package/skills/smart-search/references/sources-info.md +43 -0
- package/skills/smart-search/references/sources-media.md +40 -0
- package/skills/smart-search/references/sources-other.md +32 -0
- package/skills/smart-search/references/sources-shopping.md +21 -0
- package/skills/smart-search/references/sources-social.md +36 -0
- package/skills/smart-search/references/sources-tech.md +38 -0
- package/skills/smart-search/references/sources-travel.md +26 -0
- package/skills/webcmd-adapter-author/SKILL.md +1 -0
- package/skills/webcmd-adapter-author/references/adapter-template.md +2 -0
- package/skills/webcmd-autofix/SKILL.md +8 -0
- package/skills/webcmd-sitemap-author/SKILL.md +1 -1
package/dist/src/skills.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
2
3
|
import * as path from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import yaml from 'js-yaml';
|
|
@@ -13,35 +14,100 @@ export function listWebcmdSkills(packageRoot) {
|
|
|
13
14
|
if (!fs.existsSync(skillsRoot))
|
|
14
15
|
return [];
|
|
15
16
|
return fs.readdirSync(skillsRoot, { withFileTypes: true })
|
|
16
|
-
.filter((entry) => entry.isDirectory()
|
|
17
|
+
.filter((entry) => entry.isDirectory())
|
|
17
18
|
.map((entry) => readSkillInfo(skillsRoot, entry.name))
|
|
18
19
|
.filter((entry) => entry !== null)
|
|
19
20
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
20
21
|
}
|
|
21
|
-
export function
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
export function installWebcmdSkill(options = {}) {
|
|
23
|
+
const provider = options.customPath === undefined ? normalizeProvider(options.provider) : undefined;
|
|
24
|
+
const scope = normalizeScope(options.scope);
|
|
25
|
+
const skills = updateStableSkillLinks(options)
|
|
26
|
+
.map((skill) => {
|
|
27
|
+
const destination = destinationFor(skill.name, provider, scope, options);
|
|
28
|
+
replaceDirectorySymlink(skill.stableLink, destination);
|
|
29
|
+
return { ...skill, destination };
|
|
30
|
+
});
|
|
31
|
+
return { provider, scope, skills };
|
|
32
|
+
}
|
|
33
|
+
export function updateWebcmdSkill(options = {}) {
|
|
34
|
+
const skills = updateStableSkillLinks(options);
|
|
35
|
+
if (options.provider === undefined && options.scope === undefined && options.customPath === undefined)
|
|
36
|
+
return { skills };
|
|
37
|
+
const provider = options.customPath === undefined ? normalizeProvider(options.provider) : undefined;
|
|
38
|
+
const scope = normalizeScope(options.scope);
|
|
39
|
+
return {
|
|
40
|
+
provider,
|
|
41
|
+
scope,
|
|
42
|
+
skills: skills.map((skill) => {
|
|
43
|
+
const destination = destinationFor(skill.name, provider, scope, options);
|
|
44
|
+
replaceDirectorySymlink(skill.stableLink, destination);
|
|
45
|
+
return { ...skill, destination };
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function updateStableSkillLinks(options) {
|
|
50
|
+
const skillsRoot = getSkillsRoot(options.packageRoot);
|
|
51
|
+
const skills = listWebcmdSkills(options.packageRoot);
|
|
52
|
+
if (skills.length === 0) {
|
|
53
|
+
throw new ArgumentError(`No Webcmd skills found: ${skillsRoot}`, 'Install a package that includes skills/*/SKILL.md.');
|
|
25
54
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
55
|
+
return skills.map((skill) => {
|
|
56
|
+
const source = path.join(skillsRoot, skill.name);
|
|
57
|
+
const stableLink = path.join(options.homeDir ?? os.homedir(), '.webcmd', 'skills', skill.name);
|
|
58
|
+
replaceDirectorySymlink(source, stableLink);
|
|
59
|
+
return { name: skill.name, source, stableLink };
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function destinationFor(name, provider, scope, options) {
|
|
63
|
+
if (options.customPath !== undefined)
|
|
64
|
+
return path.join(expandHomePath(options.customPath), name);
|
|
65
|
+
const base = scope === 'project' ? options.cwd ?? process.cwd() : options.homeDir ?? os.homedir();
|
|
66
|
+
const agentDir = provider === 'claude' ? '.claude' : provider === 'codex' ? '.codex' : '.agents';
|
|
67
|
+
return path.join(base, agentDir, 'skills', name);
|
|
68
|
+
}
|
|
69
|
+
function expandHomePath(raw) {
|
|
70
|
+
const value = raw.trim();
|
|
71
|
+
if (!value)
|
|
72
|
+
throw new ArgumentError('Custom skills path must be non-empty.');
|
|
73
|
+
return path.resolve(value === '~' ? os.homedir() : value.startsWith('~/') ? path.join(os.homedir(), value.slice(2)) : value);
|
|
74
|
+
}
|
|
75
|
+
function normalizeProvider(raw = 'agents') {
|
|
76
|
+
const value = raw.trim().toLowerCase();
|
|
77
|
+
if (value === 'agents' || value === 'codex')
|
|
78
|
+
return value;
|
|
79
|
+
if (value === 'claude' || value === 'claude-code' || value === 'claude_code')
|
|
80
|
+
return 'claude';
|
|
81
|
+
throw new ArgumentError(`Unsupported skill provider: ${raw}`, 'Use one of: agents, codex, claude.');
|
|
82
|
+
}
|
|
83
|
+
function normalizeScope(raw = 'user') {
|
|
84
|
+
const value = raw.trim().toLowerCase();
|
|
85
|
+
if (value === 'user' || value === 'global')
|
|
86
|
+
return 'user';
|
|
87
|
+
if (value === 'project' || value === 'local')
|
|
88
|
+
return 'project';
|
|
89
|
+
throw new ArgumentError(`Unsupported skill scope: ${raw}`, 'Use one of: user, global, project, local.');
|
|
90
|
+
}
|
|
91
|
+
function replaceDirectorySymlink(target, linkPath) {
|
|
92
|
+
const current = safeLstat(linkPath);
|
|
93
|
+
if (current) {
|
|
94
|
+
if (!current.isSymbolicLink()) {
|
|
95
|
+
throw new ArgumentError(`Refusing to replace non-symlink path: ${linkPath}`, 'Remove it manually or choose a different scope/provider.');
|
|
96
|
+
}
|
|
97
|
+
fs.unlinkSync(linkPath);
|
|
30
98
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
99
|
+
fs.mkdirSync(path.dirname(linkPath), { recursive: true });
|
|
100
|
+
fs.symlinkSync(target, linkPath, process.platform === 'win32' ? 'junction' : 'dir');
|
|
101
|
+
}
|
|
102
|
+
function safeLstat(filePath) {
|
|
103
|
+
try {
|
|
104
|
+
return fs.lstatSync(filePath);
|
|
36
105
|
}
|
|
37
|
-
|
|
38
|
-
|
|
106
|
+
catch (err) {
|
|
107
|
+
if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT')
|
|
108
|
+
return null;
|
|
109
|
+
throw err;
|
|
39
110
|
}
|
|
40
|
-
return {
|
|
41
|
-
skill: name,
|
|
42
|
-
path: relativePath,
|
|
43
|
-
content: fs.readFileSync(absolutePath, 'utf8'),
|
|
44
|
-
};
|
|
45
111
|
}
|
|
46
112
|
function readSkillInfo(skillsRoot, name) {
|
|
47
113
|
const skillMdPath = path.join(skillsRoot, name, 'SKILL.md');
|
|
@@ -56,30 +122,6 @@ function readSkillInfo(skillsRoot, name) {
|
|
|
56
122
|
path: `${name}/SKILL.md`,
|
|
57
123
|
};
|
|
58
124
|
}
|
|
59
|
-
function parseSkillTarget(target, relpath) {
|
|
60
|
-
const normalizedTarget = normalizeSkillPath(target);
|
|
61
|
-
if (relpath) {
|
|
62
|
-
return { name: normalizedTarget, pathInSkill: relpath };
|
|
63
|
-
}
|
|
64
|
-
const slash = normalizedTarget.indexOf('/');
|
|
65
|
-
if (slash === -1) {
|
|
66
|
-
return { name: normalizedTarget, pathInSkill: '' };
|
|
67
|
-
}
|
|
68
|
-
return {
|
|
69
|
-
name: normalizedTarget.slice(0, slash),
|
|
70
|
-
pathInSkill: normalizedTarget.slice(slash + 1),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function normalizeSkillPath(raw) {
|
|
74
|
-
const normalized = raw.trim().replace(/\\/g, '/');
|
|
75
|
-
if (!normalized || normalized.includes('\0')) {
|
|
76
|
-
throw new ArgumentError('Skill path must be non-empty.');
|
|
77
|
-
}
|
|
78
|
-
if (normalized.startsWith('/') || normalized.split('/').some((part) => part === '..')) {
|
|
79
|
-
throw new ArgumentError(`Invalid skill path: ${raw}`, 'Use a path relative to a Webcmd skill directory.');
|
|
80
|
-
}
|
|
81
|
-
return path.posix.normalize(normalized);
|
|
82
|
-
}
|
|
83
125
|
function parseFrontmatter(content) {
|
|
84
126
|
if (!content.startsWith('---\n'))
|
|
85
127
|
return {};
|
package/dist/src/skills.test.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as os from 'node:os';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
+
import yaml from 'js-yaml';
|
|
4
5
|
import { describe, expect, it } from 'vitest';
|
|
5
6
|
import { ArgumentError } from './errors.js';
|
|
6
|
-
import { listWebcmdSkills,
|
|
7
|
-
function makePackageRoot() {
|
|
8
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(),
|
|
9
|
-
fs.mkdirSync(path.join(root, 'skills', 'webcmd-browser'
|
|
7
|
+
import { installWebcmdSkill, listWebcmdSkills, updateWebcmdSkill } from './skills.js';
|
|
8
|
+
function makePackageRoot(label = 'current') {
|
|
9
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), `webcmd-skills-${label}-`));
|
|
10
|
+
fs.mkdirSync(path.join(root, 'skills', 'webcmd-browser'), { recursive: true });
|
|
10
11
|
fs.mkdirSync(path.join(root, 'skills', 'webcmd-autofix'), { recursive: true });
|
|
11
12
|
fs.mkdirSync(path.join(root, 'skills', 'smart-search'), { recursive: true });
|
|
12
13
|
fs.writeFileSync(path.join(root, 'package.json'), '{"name":"@agentrhq/webcmd"}\n');
|
|
13
14
|
fs.writeFileSync(path.join(root, 'skills', 'webcmd-browser', 'SKILL.md'), [
|
|
14
15
|
'---',
|
|
15
16
|
'name: webcmd-browser',
|
|
16
|
-
|
|
17
|
+
`description: Browser control skill ${label}`,
|
|
17
18
|
'version: 1.2.3',
|
|
18
19
|
'---',
|
|
19
20
|
'',
|
|
@@ -22,7 +23,6 @@ function makePackageRoot() {
|
|
|
22
23
|
'Body.',
|
|
23
24
|
'',
|
|
24
25
|
].join('\n'));
|
|
25
|
-
fs.writeFileSync(path.join(root, 'skills', 'webcmd-browser', 'references', 'targets.md'), '# Targets\n');
|
|
26
26
|
fs.writeFileSync(path.join(root, 'skills', 'webcmd-autofix', 'SKILL.md'), [
|
|
27
27
|
'---',
|
|
28
28
|
'name: webcmd-autofix',
|
|
@@ -39,33 +39,90 @@ function makePackageRoot() {
|
|
|
39
39
|
].join('\n'));
|
|
40
40
|
return root;
|
|
41
41
|
}
|
|
42
|
+
function real(filePath) {
|
|
43
|
+
return fs.realpathSync(filePath);
|
|
44
|
+
}
|
|
42
45
|
describe('webcmd skills content', () => {
|
|
43
|
-
it('
|
|
46
|
+
it('keeps bundled skill frontmatter valid yaml', () => {
|
|
47
|
+
const skillsRoot = path.join(process.cwd(), 'skills');
|
|
48
|
+
const skillNames = fs.readdirSync(skillsRoot, { withFileTypes: true })
|
|
49
|
+
.filter((entry) => entry.isDirectory())
|
|
50
|
+
.map((entry) => entry.name);
|
|
51
|
+
for (const name of skillNames) {
|
|
52
|
+
const content = fs.readFileSync(path.join(skillsRoot, name, 'SKILL.md'), 'utf8');
|
|
53
|
+
const end = content.indexOf('\n---', 4);
|
|
54
|
+
expect(end, name).toBeGreaterThan(0);
|
|
55
|
+
expect(() => yaml.load(content.slice(4, end)), name).not.toThrow();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
it('lists bundled skills', () => {
|
|
44
59
|
const root = makePackageRoot();
|
|
45
60
|
expect(listWebcmdSkills(root).map((skill) => skill.name)).toEqual([
|
|
61
|
+
'smart-search',
|
|
46
62
|
'webcmd-autofix',
|
|
47
63
|
'webcmd-browser',
|
|
48
64
|
]);
|
|
49
65
|
expect(listWebcmdSkills(root).find((skill) => skill.name === 'webcmd-autofix')?.description)
|
|
50
66
|
.toBe('Fix adapters: keep scope narrow');
|
|
51
67
|
});
|
|
52
|
-
it('
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
it('keeps the expected installable skill set', () => {
|
|
69
|
+
const skills = fs.readdirSync(path.join(process.cwd(), 'skills'), { withFileTypes: true })
|
|
70
|
+
.filter((entry) => entry.isDirectory())
|
|
71
|
+
.map((entry) => entry.name)
|
|
72
|
+
.sort();
|
|
73
|
+
expect(skills).toEqual([
|
|
74
|
+
'smart-search',
|
|
75
|
+
'webcmd-adapter-author',
|
|
76
|
+
'webcmd-autofix',
|
|
77
|
+
'webcmd-browser',
|
|
78
|
+
'webcmd-browser-sitemap',
|
|
79
|
+
'webcmd-sitemap-author',
|
|
80
|
+
'webcmd-usage',
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
it('installs bundled skills once and refreshes them after package updates', () => {
|
|
84
|
+
const firstRoot = makePackageRoot('first');
|
|
85
|
+
const secondRoot = makePackageRoot('second');
|
|
86
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'webcmd-home-'));
|
|
87
|
+
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'webcmd-project-'));
|
|
88
|
+
const installed = installWebcmdSkill({ packageRoot: firstRoot, homeDir, cwd, provider: 'codex', scope: 'project' });
|
|
89
|
+
expect(installed).toMatchObject({
|
|
90
|
+
provider: 'codex',
|
|
91
|
+
scope: 'project',
|
|
62
92
|
});
|
|
63
|
-
expect(
|
|
93
|
+
expect(installed.skills.map((skill) => skill.name)).toEqual(['smart-search', 'webcmd-autofix', 'webcmd-browser']);
|
|
94
|
+
for (const skill of installed.skills) {
|
|
95
|
+
expect(skill.source).toBe(path.join(firstRoot, 'skills', skill.name));
|
|
96
|
+
expect(skill.stableLink).toBe(path.join(homeDir, '.webcmd', 'skills', skill.name));
|
|
97
|
+
expect(skill.destination).toBe(path.join(cwd, '.codex', 'skills', skill.name));
|
|
98
|
+
expect(real(skill.destination)).toBe(real(skill.source));
|
|
99
|
+
}
|
|
100
|
+
const updated = updateWebcmdSkill({ packageRoot: secondRoot, homeDir });
|
|
101
|
+
expect(updated.skills.every((skill) => skill.destination === undefined)).toBe(true);
|
|
102
|
+
for (const skill of installed.skills) {
|
|
103
|
+
expect(real(skill.destination)).toBe(real(path.join(secondRoot, 'skills', skill.name)));
|
|
104
|
+
}
|
|
64
105
|
});
|
|
65
|
-
it('
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
106
|
+
it('installs bundled skills into a custom skills directory', () => {
|
|
107
|
+
const packageRoot = makePackageRoot();
|
|
108
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'webcmd-home-'));
|
|
109
|
+
const customPath = fs.mkdtempSync(path.join(os.tmpdir(), 'webcmd-custom-skills-'));
|
|
110
|
+
const installed = installWebcmdSkill({ packageRoot, homeDir, customPath });
|
|
111
|
+
expect(installed.provider).toBeUndefined();
|
|
112
|
+
expect(installed.skills.map((skill) => skill.destination)).toEqual([
|
|
113
|
+
path.join(customPath, 'smart-search'),
|
|
114
|
+
path.join(customPath, 'webcmd-autofix'),
|
|
115
|
+
path.join(customPath, 'webcmd-browser'),
|
|
116
|
+
]);
|
|
117
|
+
for (const skill of installed.skills) {
|
|
118
|
+
expect(real(skill.destination)).toBe(real(skill.source));
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
it('refuses to replace real files or directories', () => {
|
|
122
|
+
const packageRoot = makePackageRoot();
|
|
123
|
+
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'webcmd-home-'));
|
|
124
|
+
const stablePath = path.join(homeDir, '.webcmd', 'skills', 'smart-search');
|
|
125
|
+
fs.mkdirSync(stablePath, { recursive: true });
|
|
126
|
+
expect(() => updateWebcmdSkill({ packageRoot, homeDir })).toThrow(ArgumentError);
|
|
70
127
|
});
|
|
71
128
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentrhq/webcmd",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Turn websites, browser sessions, desktop apps, and local tools into deterministic CLI surfaces for humans and AI agents.",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"files": [
|
|
31
31
|
"dist/src/",
|
|
32
32
|
"clis/",
|
|
33
|
-
"skills
|
|
33
|
+
"skills/**",
|
|
34
34
|
"cli-manifest.json",
|
|
35
35
|
"scripts/",
|
|
36
36
|
"README.md",
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: smart-search
|
|
3
|
+
description: Intelligent search router based on webcmd commands. Use this skill when the user wants to search, query, find, or research information through Webcmd, CLI, or API sources, especially for named websites, social media, technical material, news, shopping, travel, jobs, finance, or multilingual content
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Smart Search Router
|
|
7
|
+
|
|
8
|
+
Route a query to the best webcmd search source based on the topic and context. The goal of this skill is not to memorize command signatures. First choose the data source, then have the agent read live help through `webcmd` so stale documentation does not leak into the answer.
|
|
9
|
+
|
|
10
|
+
## Mandatory Preflight
|
|
11
|
+
|
|
12
|
+
Before every use, do both of these steps:
|
|
13
|
+
|
|
14
|
+
- Run `webcmd list -f yaml`
|
|
15
|
+
- Use the live registry to confirm that candidate sites exist, and inspect `strategy`, `browser`, and `domain`
|
|
16
|
+
|
|
17
|
+
After choosing a site, do both of these steps:
|
|
18
|
+
|
|
19
|
+
- Run `webcmd <site> -h` to see the site's subcommands
|
|
20
|
+
- If a subcommand is already selected, run `webcmd <site> <command> -h` to inspect parameters, output columns, and strategy
|
|
21
|
+
|
|
22
|
+
Do not hard-code parameters or assume command signatures from skill docs. Trust the live output of `webcmd ... -h`.
|
|
23
|
+
|
|
24
|
+
## Main Routing Rule
|
|
25
|
+
|
|
26
|
+
Use this single rule instead of maintaining multiple priority lists:
|
|
27
|
+
|
|
28
|
+
1. If the user explicitly names a website, platform, or data source, use that site directly.
|
|
29
|
+
2. If the user does not name a site, prefer exactly one AI source: choose one of `grok`, `chatgpt`, or `gemini`.
|
|
30
|
+
3. If the AI answer is thin, lacks raw data, needs authoritative corroboration, or needs vertical results, add 1-2 specialized sources.
|
|
31
|
+
|
|
32
|
+
## Per-Question Budget And Rate Limits
|
|
33
|
+
|
|
34
|
+
Treat a "single user question" as one problem-solving chain for the same intent. Follow-ups, clarifications, and added constraints in the same thread still count as the same question when the core problem has not changed.
|
|
35
|
+
|
|
36
|
+
First create a site-call ledger. After each real search command, update it immediately:
|
|
37
|
+
|
|
38
|
+
- `site`
|
|
39
|
+
- `query`
|
|
40
|
+
- `count`
|
|
41
|
+
- `status`
|
|
42
|
+
|
|
43
|
+
Counting rules:
|
|
44
|
+
|
|
45
|
+
- `webcmd list -f yaml`, `webcmd <site> -h`, and `webcmd <site> <command> -h` are preflight/help commands and do not count as searches.
|
|
46
|
+
- One real `webcmd <site> ...` search/query execution counts as 1 call for that site.
|
|
47
|
+
- A failed call caused by an error, timeout, CAPTCHA, anti-bot check, or broken login state still counts as 1 call for that site. Do not retry indefinitely.
|
|
48
|
+
|
|
49
|
+
Rate limits:
|
|
50
|
+
|
|
51
|
+
- Hard AI-source limit: for the same question, call each AI source at most once.
|
|
52
|
+
- The default strategy is still to choose only 1 AI source. Do not chain multiple AI sources as a routine workflow.
|
|
53
|
+
- Call additional AI sources only when the user explicitly asks to compare multiple AI sources. Even then, each named AI source may be called at most once.
|
|
54
|
+
- Non-AI sites default to at most 2 calls.
|
|
55
|
+
- The second call to a non-AI site must have a clear reason, such as narrowing by time, region, category, sorting, or keywords after an overly broad first result.
|
|
56
|
+
- Do not make a third call to a non-AI site. If information is still insufficient, stop expanding and state the gap clearly.
|
|
57
|
+
|
|
58
|
+
When a rate limit is reached:
|
|
59
|
+
|
|
60
|
+
- Record: `Skipped: <site> reached the rate limit`
|
|
61
|
+
- Prefer another site of the same type
|
|
62
|
+
- If no suitable alternative source exists, answer from the collected information and explain coverage and gaps
|
|
63
|
+
|
|
64
|
+
## End-Of-Query Report
|
|
65
|
+
|
|
66
|
+
At the end of every query, append a short "Search Summary" with at least these three items:
|
|
67
|
+
|
|
68
|
+
- Which sites were searched
|
|
69
|
+
- What query terms were used for each site
|
|
70
|
+
- How many times each site was searched
|
|
71
|
+
|
|
72
|
+
If any site was skipped because of a rate limit, say so explicitly.
|
|
73
|
+
|
|
74
|
+
Use this fixed format when possible:
|
|
75
|
+
|
|
76
|
+
```md
|
|
77
|
+
Search Summary
|
|
78
|
+
- Site: <site1> | Query: <term1> | Calls: <n>
|
|
79
|
+
- Site: <site2> | Query: <term2>; <term3> | Calls: <n>
|
|
80
|
+
- Skipped: <site3> | Reason: reached the rate limit
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## AI Source Selection
|
|
84
|
+
|
|
85
|
+
- `grok`
|
|
86
|
+
Best for real-time discussion, English-language internet sentiment, Twitter/X context, and trending topics.
|
|
87
|
+
- `chatgpt`
|
|
88
|
+
Best for broad Q&A, synthesis, planning, coding help, and general-purpose English-language research.
|
|
89
|
+
- `gemini`
|
|
90
|
+
Best for global web coverage, English-language sources, general information retrieval, and background summaries.
|
|
91
|
+
|
|
92
|
+
If the user did not name a site, first judge language and context, then choose exactly one of these three.
|
|
93
|
+
|
|
94
|
+
After an AI site has run one real query for the same question, do not call that same AI site again with rewritten keywords. If the answer is insufficient, prefer specialized sources instead of repeatedly hitting the same AI site.
|
|
95
|
+
|
|
96
|
+
## AI Query Guidance
|
|
97
|
+
|
|
98
|
+
When using an AI source, do not send a very short keyword by itself. Prefer a query shaped as "topic + goal + constraints."
|
|
99
|
+
|
|
100
|
+
- Topic
|
|
101
|
+
The object, event, product, person, company, or technical term the user really wants to investigate.
|
|
102
|
+
- Goal
|
|
103
|
+
The desired result, such as summary, comparison, cause, trend, recommendation, or raw leads.
|
|
104
|
+
- Constraints
|
|
105
|
+
Language, region, time range, platform scope, audience, price band, job location, or whether raw sources are required.
|
|
106
|
+
|
|
107
|
+
Prefer these shapes:
|
|
108
|
+
|
|
109
|
+
- `<topic> + <question to answer>`
|
|
110
|
+
- `<topic> + <time range/region/language>`
|
|
111
|
+
- `<topic> + <platform or source scope>`
|
|
112
|
+
- `<topic> + <output requirement>`
|
|
113
|
+
|
|
114
|
+
Avoid sending only:
|
|
115
|
+
|
|
116
|
+
- A single noun
|
|
117
|
+
- A trending question with no time range
|
|
118
|
+
- A shopping, jobs, or travel question with no region
|
|
119
|
+
- A social-media question with no platform scope
|
|
120
|
+
|
|
121
|
+
## When To Add Specialized Sources
|
|
122
|
+
|
|
123
|
+
Add specialized sources when any of these conditions apply:
|
|
124
|
+
|
|
125
|
+
- The AI provides a summary, but raw posts, raw videos, raw products, or raw job results are needed
|
|
126
|
+
- The AI coverage is thin or misses vertical-site information
|
|
127
|
+
- Higher authority or stronger domain relevance is needed
|
|
128
|
+
- The user explicitly asks to search on a specific platform
|
|
129
|
+
|
|
130
|
+
Keep a typical query to 1 AI source plus 1-2 specialized sources to avoid result overload.
|
|
131
|
+
|
|
132
|
+
## Handling Unavailable Sources
|
|
133
|
+
|
|
134
|
+
When a site is unavailable:
|
|
135
|
+
|
|
136
|
+
- Do not stop the whole search because one source failed
|
|
137
|
+
- Record: `Skipped: <site> unavailable`
|
|
138
|
+
- Fall back to another site of the same type, or to one AI source
|
|
139
|
+
- Always trust the actual output of `webcmd list -f yaml` and `webcmd <site> -h`
|
|
140
|
+
|
|
141
|
+
Do not assume any site is always available. Even for public sites, trust live help and execution results in the current environment.
|
|
142
|
+
|
|
143
|
+
## Reference Files
|
|
144
|
+
|
|
145
|
+
Read only the files relevant to the current query:
|
|
146
|
+
|
|
147
|
+
- **`references/sources-ai.md`** - default AI sources
|
|
148
|
+
- **`references/sources-tech.md`** - technology and research
|
|
149
|
+
- **`references/sources-social.md`** - social media
|
|
150
|
+
- **`references/sources-media.md`** - media and entertainment
|
|
151
|
+
- **`references/sources-info.md`** - news and knowledge
|
|
152
|
+
- **`references/sources-shopping.md`** - shopping
|
|
153
|
+
- **`references/sources-travel.md`** - travel
|
|
154
|
+
- **`references/sources-other.md`** - other vertical sources
|
|
155
|
+
|
|
156
|
+
Do not load every reference file unless the query actually needs them.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Default AI Sources
|
|
2
|
+
|
|
3
|
+
When the user does not explicitly name a site, choose one of `grok`, `chatgpt`, or `gemini` first. Do not start by running multiple AI sources in parallel.
|
|
4
|
+
|
|
5
|
+
## Usage Rules
|
|
6
|
+
|
|
7
|
+
1. Run `webcmd list -f yaml`
|
|
8
|
+
2. Confirm which of `grok`, `chatgpt`, and `gemini` are available in the current registry
|
|
9
|
+
3. Run `webcmd <site> -h`
|
|
10
|
+
4. After selecting a subcommand, run `webcmd <site> <command> -h`
|
|
11
|
+
|
|
12
|
+
## Routing Guidance
|
|
13
|
+
|
|
14
|
+
### grok
|
|
15
|
+
|
|
16
|
+
- Use for: real-time trends, Twitter/X context, English-language internet discussion, sentiment, and trends
|
|
17
|
+
- Common follow-up sources: `twitter`, `reddit`, `reuters`, `google`
|
|
18
|
+
- Query suggestions:
|
|
19
|
+
- Add a time range, such as "today" or "this week"
|
|
20
|
+
- Add a platform scope, such as "on X" or "from social posts"
|
|
21
|
+
- Add a goal, such as "latest reactions", "main viewpoints", or "key claims"
|
|
22
|
+
- Examples:
|
|
23
|
+
- `OpenAI latest reactions on X this week`
|
|
24
|
+
- `TSLA earnings main viewpoints on social media April 2026`
|
|
25
|
+
- `Nintendo Switch 2 rumors latest discussion on X`
|
|
26
|
+
|
|
27
|
+
### chatgpt
|
|
28
|
+
|
|
29
|
+
- Use for: broad Q&A, synthesis, planning, coding help, and general-purpose English-language research
|
|
30
|
+
- Common follow-up sources: `reddit`, `youtube`, `google`, `reuters`
|
|
31
|
+
- Query suggestions:
|
|
32
|
+
- Add a language, market, or audience scope, such as "English-language discussion" or "US users"
|
|
33
|
+
- Add the requested goal, such as "summarize", "compare", or "extract recommendation reasons"
|
|
34
|
+
- Add audience or use-case constraints, such as "for beginners", "under $500", or "remote jobs"
|
|
35
|
+
- Examples:
|
|
36
|
+
- `Is the 2026 MacBook Air worth buying? Main views in English-language discussions`
|
|
37
|
+
- `Remote AI product manager hiring trends over the last month`
|
|
38
|
+
- `Sunscreen recommendations for sensitive skin, common pros and cons from US users`
|
|
39
|
+
|
|
40
|
+
### gemini
|
|
41
|
+
|
|
42
|
+
- Use for: global web coverage, English-language sources, background summaries, and general retrieval
|
|
43
|
+
- Common follow-up sources: `google`, `wikipedia`, `arxiv`, `stackoverflow`
|
|
44
|
+
- Query suggestions:
|
|
45
|
+
- Add a topic type, such as "overview", "comparison", "background", or "best sources"
|
|
46
|
+
- Add scope constraints, such as region, time, language, or industry
|
|
47
|
+
- Add an output shape, such as "with sources", "compare pros and cons", or "official guidance"
|
|
48
|
+
- Examples:
|
|
49
|
+
- `MCP overview and official guidance with sources`
|
|
50
|
+
- `best budget travel destinations in Japan April 2026 compare pros and cons`
|
|
51
|
+
- `TypeScript decorators current status official sources`
|
|
52
|
+
|
|
53
|
+
## Follow-Up Principles
|
|
54
|
+
|
|
55
|
+
- Use one AI source first to get an initial answer
|
|
56
|
+
- If the answer lacks raw data, vertical results, or authoritative sources, add 1-2 specialized sources
|
|
57
|
+
- Do not treat default AI sources as ground truth for command signatures; command details always come from `webcmd ... -h`
|
|
58
|
+
|
|
59
|
+
## General Query Templates
|
|
60
|
+
|
|
61
|
+
You can build AI queries from these templates:
|
|
62
|
+
|
|
63
|
+
- Trending topic/news:
|
|
64
|
+
`<event> + latest developments + <time range> + <region/platform>`
|
|
65
|
+
- Comparison/recommendation:
|
|
66
|
+
`<item A> vs <item B> + <evaluation dimensions> + <audience/budget/use case>`
|
|
67
|
+
- Regional or language community:
|
|
68
|
+
`<topic> + main viewpoints in <language/market> discussions + <time range>`
|
|
69
|
+
- Global sources:
|
|
70
|
+
`<topic> + overview/background + with sources`
|
|
71
|
+
- Jobs:
|
|
72
|
+
`<role> + <city/country> + market trends/hiring + <time range>`
|
|
73
|
+
- Shopping:
|
|
74
|
+
`<product> + reviews/price/value + <region/budget>`
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# News And Knowledge
|
|
2
|
+
|
|
3
|
+
Use for news, encyclopedic background, general knowledge, and technology/business reporting.
|
|
4
|
+
|
|
5
|
+
## Sites
|
|
6
|
+
|
|
7
|
+
### google
|
|
8
|
+
|
|
9
|
+
- Use for: general web search and cross-site fallback
|
|
10
|
+
- Before use, run: `webcmd google -h`
|
|
11
|
+
|
|
12
|
+
### wikipedia
|
|
13
|
+
|
|
14
|
+
- Use for: definitions, background knowledge, and historical facts
|
|
15
|
+
- Before use, run: `webcmd wikipedia -h`
|
|
16
|
+
|
|
17
|
+
### reuters
|
|
18
|
+
|
|
19
|
+
- Use for: international news and factual reporting
|
|
20
|
+
- Before use, run: `webcmd reuters -h`
|
|
21
|
+
|
|
22
|
+
### hackernews
|
|
23
|
+
|
|
24
|
+
- Use for: technology news discussion, startup links, and developer reactions
|
|
25
|
+
- Before use, run: `webcmd hackernews -h`
|
|
26
|
+
|
|
27
|
+
### github
|
|
28
|
+
|
|
29
|
+
- Use for: repository, issue, pull request, release, and open-source project signals
|
|
30
|
+
- Before use, run: `webcmd github -h`
|
|
31
|
+
|
|
32
|
+
### substack
|
|
33
|
+
|
|
34
|
+
- Use for: newsletters, creator subscription content, and long-form posts
|
|
35
|
+
- Before use, run: `webcmd substack -h`
|
|
36
|
+
|
|
37
|
+
## Routing Hints
|
|
38
|
+
|
|
39
|
+
- For background knowledge and definitions, prefer `wikipedia`
|
|
40
|
+
- For general web and cross-site information, prefer `google`
|
|
41
|
+
- For international news, prefer `reuters`
|
|
42
|
+
- For technology-community signals, prefer `hackernews` or `github`
|
|
43
|
+
- When an AI answer is too generic, use these sources to add facts and links
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Media And Entertainment
|
|
2
|
+
|
|
3
|
+
Use for video, film, TV, music, podcast, and content-platform search.
|
|
4
|
+
|
|
5
|
+
## Sites
|
|
6
|
+
|
|
7
|
+
### youtube
|
|
8
|
+
|
|
9
|
+
- Use for: videos, tutorials, reviews, interviews, creator channels, and explainers
|
|
10
|
+
- Before use, run: `webcmd youtube -h`
|
|
11
|
+
|
|
12
|
+
### tiktok
|
|
13
|
+
|
|
14
|
+
- Use for: short video, trends, entertainment clips, and creator discovery
|
|
15
|
+
- Before use, run: `webcmd tiktok -h`
|
|
16
|
+
|
|
17
|
+
### imdb
|
|
18
|
+
|
|
19
|
+
- Use for: movies, TV shows, actors, credits, and ratings
|
|
20
|
+
- Before use, run: `webcmd imdb -h`
|
|
21
|
+
|
|
22
|
+
### spotify
|
|
23
|
+
|
|
24
|
+
- Use for: songs, artists, albums, playlists, and music discovery
|
|
25
|
+
- Before use, run: `webcmd spotify -h`
|
|
26
|
+
|
|
27
|
+
### apple-podcasts
|
|
28
|
+
|
|
29
|
+
- Use for: podcast shows and podcast search
|
|
30
|
+
- Before use, run: `webcmd apple-podcasts -h`
|
|
31
|
+
|
|
32
|
+
### medium
|
|
33
|
+
|
|
34
|
+
- Use for: long-form articles, technical blogs, design writing, and product posts
|
|
35
|
+
- Before use, run: `webcmd medium -h`
|
|
36
|
+
|
|
37
|
+
## Routing Hints
|
|
38
|
+
|
|
39
|
+
- If the user only asks for videos, reviews, podcasts, or articles, choose the site by content type first
|
|
40
|
+
- If the user did not name a site, start with `grok` or `gemini`, then add a media site for raw results
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Other Vertical Sources
|
|
2
|
+
|
|
3
|
+
Use for jobs, finance, books, dictionaries, and other cases that do not fit the earlier categories.
|
|
4
|
+
|
|
5
|
+
## Sites
|
|
6
|
+
|
|
7
|
+
### linkedin
|
|
8
|
+
|
|
9
|
+
- Use for: global jobs, English-language job listings, and multinational-company hiring
|
|
10
|
+
- Before use, run: `webcmd linkedin -h`
|
|
11
|
+
|
|
12
|
+
### yahoo
|
|
13
|
+
|
|
14
|
+
- Use for: finance, market data, stock quotes, and company finance pages
|
|
15
|
+
- Before use, run: `webcmd yahoo -h`
|
|
16
|
+
|
|
17
|
+
### github
|
|
18
|
+
|
|
19
|
+
- Use for: open-source project discovery, repository signals, issues, pull requests, and releases
|
|
20
|
+
- Before use, run: `webcmd github -h`
|
|
21
|
+
|
|
22
|
+
### dictionary
|
|
23
|
+
|
|
24
|
+
- Use for: English word meanings and basic dictionary lookup
|
|
25
|
+
- Before use, run: `webcmd dictionary -h`
|
|
26
|
+
|
|
27
|
+
## Routing Hints
|
|
28
|
+
|
|
29
|
+
- For jobs, prefer `linkedin`
|
|
30
|
+
- For finance and market data, prefer `yahoo`
|
|
31
|
+
- For open-source project signals, prefer `github`
|
|
32
|
+
- For English word definitions, prefer `dictionary`
|