@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.
Files changed (52) hide show
  1. package/README.md +39 -24
  2. package/cli-manifest.json +504 -0
  3. package/clis/_shared/site-auth.js +6 -1
  4. package/clis/district/_lib.js +566 -0
  5. package/clis/district/auth.js +49 -0
  6. package/clis/district/checkout.js +278 -0
  7. package/clis/district/listings.js +158 -0
  8. package/clis/district/locations.js +211 -0
  9. package/clis/district/search.js +218 -0
  10. package/clis/district/seats.js +233 -0
  11. package/clis/district/set-location.js +82 -0
  12. package/clis/district/showtimes.js +433 -0
  13. package/clis/reddit/popular.js +12 -1
  14. package/clis/reddit/popular.test.js +12 -3
  15. package/dist/src/browser/bridge.d.ts +1 -0
  16. package/dist/src/browser/bridge.js +1 -1
  17. package/dist/src/browser/page.d.ts +4 -1
  18. package/dist/src/browser/page.js +12 -1
  19. package/dist/src/browser/protocol.d.ts +2 -0
  20. package/dist/src/browser/runtime/local-cloak/actions.js +1 -0
  21. package/dist/src/browser/runtime/local-cloak/session-manager.d.ts +2 -0
  22. package/dist/src/browser/runtime/local-cloak/session-manager.js +12 -2
  23. package/dist/src/browser/runtime/local-cloak/session-manager.test.js +58 -0
  24. package/dist/src/build-manifest.js +1 -0
  25. package/dist/src/build-manifest.test.js +34 -0
  26. package/dist/src/cli.js +111 -28
  27. package/dist/src/discovery.js +1 -0
  28. package/dist/src/engine.test.js +62 -0
  29. package/dist/src/execution.js +1 -1
  30. package/dist/src/manifest-types.d.ts +2 -0
  31. package/dist/src/registry.d.ts +10 -0
  32. package/dist/src/registry.js +5 -3
  33. package/dist/src/registry.test.js +25 -0
  34. package/dist/src/runtime.d.ts +2 -0
  35. package/dist/src/runtime.js +1 -0
  36. package/dist/src/skills.d.ts +23 -5
  37. package/dist/src/skills.js +87 -45
  38. package/dist/src/skills.test.js +80 -23
  39. package/package.json +2 -2
  40. package/skills/smart-search/SKILL.md +156 -0
  41. package/skills/smart-search/references/sources-ai.md +74 -0
  42. package/skills/smart-search/references/sources-info.md +43 -0
  43. package/skills/smart-search/references/sources-media.md +40 -0
  44. package/skills/smart-search/references/sources-other.md +32 -0
  45. package/skills/smart-search/references/sources-shopping.md +21 -0
  46. package/skills/smart-search/references/sources-social.md +36 -0
  47. package/skills/smart-search/references/sources-tech.md +38 -0
  48. package/skills/smart-search/references/sources-travel.md +26 -0
  49. package/skills/webcmd-adapter-author/SKILL.md +1 -0
  50. package/skills/webcmd-adapter-author/references/adapter-template.md +2 -0
  51. package/skills/webcmd-autofix/SKILL.md +8 -0
  52. package/skills/webcmd-sitemap-author/SKILL.md +1 -1
@@ -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() && entry.name.startsWith('webcmd-'))
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 readWebcmdSkill(target, relpath = '', packageRoot) {
22
- const { name, pathInSkill } = parseSkillTarget(target, relpath);
23
- if (!name.startsWith('webcmd-')) {
24
- throw new ArgumentError(`Unknown Webcmd skill: ${name}`, 'Run "webcmd skills list" to see available Webcmd skills.');
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
- const skillsRoot = getSkillsRoot(packageRoot);
27
- const skillRoot = path.join(skillsRoot, name);
28
- if (!isDirectory(skillRoot) || !fs.existsSync(path.join(skillRoot, 'SKILL.md'))) {
29
- throw new ArgumentError(`Unknown Webcmd skill: ${name}`, 'Run "webcmd skills list" to see available Webcmd skills.');
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
- const relativePath = normalizeSkillPath(pathInSkill || 'SKILL.md');
32
- const absolutePath = path.resolve(skillRoot, relativePath);
33
- const relativeToRoot = path.relative(skillRoot, absolutePath);
34
- if (relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot)) {
35
- throw new ArgumentError(`Invalid skill path: ${relativePath}`, 'Skill paths must stay inside the selected Webcmd skill.');
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
- if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isFile()) {
38
- throw new ArgumentError(`Skill file not found: ${name}/${relativePath}`, 'Run "webcmd skills list <skill>" is not supported yet; read SKILL.md or a known references/... file.');
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 {};
@@ -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, readWebcmdSkill } from './skills.js';
7
- function makePackageRoot() {
8
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'webcmd-skills-'));
9
- fs.mkdirSync(path.join(root, 'skills', 'webcmd-browser', 'references'), { recursive: true });
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
- 'description: Browser control skill',
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('lists only webcmd-prefixed skills', () => {
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('reads a skill SKILL.md and reference file', () => {
53
- const root = makePackageRoot();
54
- expect(readWebcmdSkill('webcmd-browser', '', root)).toMatchObject({
55
- skill: 'webcmd-browser',
56
- path: 'SKILL.md',
57
- });
58
- expect(readWebcmdSkill('webcmd-browser/references/targets.md', '', root)).toMatchObject({
59
- skill: 'webcmd-browser',
60
- path: 'references/targets.md',
61
- content: '# Targets\n',
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(readWebcmdSkill('webcmd-browser', 'references/targets.md', root).content).toBe('# Targets\n');
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('rejects non-webcmd skills and path traversal', () => {
66
- const root = makePackageRoot();
67
- expect(() => readWebcmdSkill('smart-search', '', root)).toThrow(ArgumentError);
68
- expect(() => readWebcmdSkill('webcmd-browser/../smart-search/SKILL.md', '', root)).toThrow(ArgumentError);
69
- expect(() => readWebcmdSkill('webcmd-browser', '../../package.json', root)).toThrow(ArgumentError);
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.1",
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/webcmd-*/**",
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`