@agentrhq/webcmd 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +43 -268
  2. package/cli-manifest.json +184 -311
  3. package/clis/_shared/site-auth.js +37 -40
  4. package/clis/_shared/site-auth.test.js +109 -22
  5. package/clis/amazon/auth.js +0 -6
  6. package/clis/band/auth.js +0 -6
  7. package/clis/blinkit/auth.js +13 -40
  8. package/clis/blinkit/blinkit.test.js +39 -2
  9. package/clis/chatgpt/auth.js +0 -6
  10. package/clis/claude/auth.js +0 -6
  11. package/clis/coupang/auth.js +0 -6
  12. package/clis/district/auth.js +0 -2
  13. package/clis/facebook/auth.js +0 -6
  14. package/clis/gemini/auth.js +0 -6
  15. package/clis/github/auth.js +0 -6
  16. package/clis/grok/auth.js +0 -6
  17. package/clis/hackernews/hackernews.test.js +20 -1
  18. package/clis/hackernews/jobs.js +1 -1
  19. package/clis/hf/auth.js +2 -7
  20. package/clis/instagram/auth.js +0 -6
  21. package/clis/linkedin/auth.js +0 -6
  22. package/clis/linkedin-learning/auth.js +0 -6
  23. package/clis/manus/auth.js +0 -6
  24. package/clis/notebooklm/auth.js +0 -6
  25. package/clis/pixiv/auth.js +1 -7
  26. package/clis/practo/login.js +12 -38
  27. package/clis/practo/practo.test.js +13 -9
  28. package/clis/reddit/auth.js +0 -6
  29. package/clis/reuters/auth.js +0 -14
  30. package/clis/suno/auth.js +0 -6
  31. package/clis/tiktok/auth.js +0 -6
  32. package/clis/twitter/auth.js +0 -6
  33. package/clis/upwork/auth.js +0 -6
  34. package/clis/youtube/auth.js +0 -6
  35. package/clis/zepto/auth.js +16 -29
  36. package/clis/zepto/zepto.test.js +45 -2
  37. package/dist/src/browser/cdp.js +3 -1
  38. package/dist/src/browser/cdp.test.js +12 -0
  39. package/dist/src/browser/page.js +6 -2
  40. package/dist/src/browser/page.test.js +1 -0
  41. package/dist/src/browser/protocol.d.ts +2 -0
  42. package/dist/src/browser/runtime/local-cloak/actions.js +42 -21
  43. package/dist/src/browser/runtime/local-cloak/provider.test.js +33 -5
  44. package/dist/src/build-manifest.js +3 -3
  45. package/dist/src/build-manifest.test.js +14 -0
  46. package/dist/src/docs-sync-review.js +2 -2
  47. package/dist/src/docs-sync-review.test.js +2 -2
  48. package/dist/src/hosted/main-lifecycle.test.js +2 -1
  49. package/dist/src/hosted/manifest.test.js +2 -1
  50. package/dist/src/hosted/output-parity.test.js +2 -1
  51. package/dist/src/hosted/root-command-surface.test.js +1 -1
  52. package/dist/src/hosted/runner.test.js +8 -4
  53. package/dist/src/skills.test.js +54 -0
  54. package/hosted-contract.json +191 -366
  55. package/package.json +3 -3
  56. package/skills/webcmd-adapter-author/SKILL.md +2 -0
  57. package/skills/webcmd-autofix/SKILL.md +5 -3
  58. package/skills/webcmd-browser/SKILL.md +12 -11
  59. package/skills/webcmd-usage/SKILL.md +8 -0
  60. package/clis/practo/whoami.js +0 -28
@@ -428,4 +428,18 @@ describe('manifest helper rules', () => {
428
428
  // Unknown flags are ignored.
429
429
  expect(parseBuildManifestArgs(['--something-else']).allowRemovals).toBe(0);
430
430
  });
431
+ it('keeps every browser login on the local handoff contract', () => {
432
+ const manifest = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'cli-manifest.json'), 'utf8'));
433
+ const logins = manifest.filter((entry) => entry.browser === true && entry.name === 'login');
434
+ const keys = new Set(manifest.map((entry) => `${entry.site}/${entry.name}`));
435
+ expect(logins.length).toBeGreaterThan(0);
436
+ for (const login of logins) {
437
+ expect(login.args.some((arg) => arg.name === 'timeout'), `${login.site}/login`).toBe(false);
438
+ expect(login.description, `${login.site}/login`).not.toMatch(/wait until/i);
439
+ expect(login.columns, `${login.site}/login`).toEqual(expect.arrayContaining([
440
+ 'status', 'logged_in', 'site', 'action', 'verify_command',
441
+ ]));
442
+ expect(keys.has(`${login.site}/whoami`), `${login.site}/whoami`).toBe(true);
443
+ }
444
+ });
431
445
  });
@@ -98,7 +98,7 @@ const GENERAL_DOCUMENTATION = [
98
98
  const BROWSER_DOCUMENTATION = [
99
99
  'README.md',
100
100
  'docs/agent-prompts.mdx',
101
- 'docs/agent-runtime.mdx',
101
+ 'docs/browser-and-sitemap-memory.mdx',
102
102
  'docs/cli-reference.mdx',
103
103
  'docs/concepts.mdx',
104
104
  'skills/webcmd-browser-sitemap/SKILL.md',
@@ -109,7 +109,7 @@ const ADAPTER_DOCUMENTATION = [
109
109
  'README.md',
110
110
  'docs/authoring.mdx',
111
111
  'docs/cli-reference.mdx',
112
- 'docs/plugins-and-skills.mdx',
112
+ 'docs/skills.mdx',
113
113
  'skills/webcmd-adapter-author/SKILL.md',
114
114
  'skills/webcmd-usage/SKILL.md',
115
115
  ];
@@ -87,7 +87,7 @@ describe('review context', () => {
87
87
  ])).toEqual([
88
88
  'README.md',
89
89
  'docs/agent-prompts.mdx',
90
- 'docs/agent-runtime.mdx',
90
+ 'docs/browser-and-sitemap-memory.mdx',
91
91
  'docs/cli-reference.mdx',
92
92
  'docs/concepts.mdx',
93
93
  'skills/webcmd-browser-sitemap/SKILL.md',
@@ -100,7 +100,7 @@ describe('review context', () => {
100
100
  'README.md',
101
101
  'docs/authoring.mdx',
102
102
  'docs/cli-reference.mdx',
103
- 'docs/plugins-and-skills.mdx',
103
+ 'docs/skills.mdx',
104
104
  'skills/webcmd-adapter-author/SKILL.md',
105
105
  'skills/webcmd-usage/SKILL.md',
106
106
  ]);
@@ -6,6 +6,7 @@ import { tmpdir } from 'node:os';
6
6
  import path from 'node:path';
7
7
  import { fileURLToPath, pathToFileURL } from 'node:url';
8
8
  import { afterEach, describe, expect, it } from 'vitest';
9
+ import { PKG_VERSION } from '../version.js';
9
10
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
10
11
  const entrypoint = path.join(packageRoot, 'src/main.ts');
11
12
  const largeOutput = 'hosted-lifecycle-chunk\n'.repeat(64 * 1024);
@@ -152,7 +153,7 @@ async function createHostedFixture(outcome) {
152
153
  userId: 'user_lifecycle',
153
154
  metadata: {
154
155
  contractSchemaVersion: 1,
155
- webcmdPackageVersion: '0.3.0',
156
+ webcmdPackageVersion: PKG_VERSION,
156
157
  generatedAt: '2026-07-14T00:00:00.000Z',
157
158
  },
158
159
  commands: [command, authCommand],
@@ -8,11 +8,12 @@ import { makeHostedConfig } from './config.js';
8
8
  import { runHostedCli } from './runner.js';
9
9
  import { formatRootHelp } from '../command-presentation.js';
10
10
  import { HOSTED_ROOT_HELP } from '../completion-shared.js';
11
+ import { PKG_VERSION } from '../version.js';
11
12
  const manifest = {
12
13
  userId: 'user_demo',
13
14
  metadata: {
14
15
  contractSchemaVersion: 1,
15
- webcmdPackageVersion: '0.3.0',
16
+ webcmdPackageVersion: PKG_VERSION,
16
17
  generatedAt: '2026-07-08T00:00:00.000Z',
17
18
  },
18
19
  commands: [
@@ -3,6 +3,7 @@ import { Command } from 'commander';
3
3
  import { beforeEach, describe, expect, it, vi } from 'vitest';
4
4
  import { Strategy } from '../registry.js';
5
5
  import { registerCommandToProgram } from '../commanderAdapter.js';
6
+ import { PKG_VERSION } from '../version.js';
6
7
  import { makeHostedConfig } from './config.js';
7
8
  import { runHostedCli } from './runner.js';
8
9
  const { mockExecuteCommand } = vi.hoisted(() => ({
@@ -26,7 +27,7 @@ const manifest = {
26
27
  userId: 'user_demo',
27
28
  metadata: {
28
29
  contractSchemaVersion: 1,
29
- webcmdPackageVersion: '0.3.0',
30
+ webcmdPackageVersion: PKG_VERSION,
30
31
  generatedAt: '2026-07-14T00:00:00.000Z',
31
32
  },
32
33
  commands: [{
@@ -26,7 +26,7 @@ const manifest = {
26
26
  userId: 'user_demo',
27
27
  metadata: {
28
28
  contractSchemaVersion: 1,
29
- webcmdPackageVersion: '0.3.0',
29
+ webcmdPackageVersion: PKG_VERSION,
30
30
  generatedAt: '2026-07-08T00:00:00.000Z',
31
31
  },
32
32
  commands: [{
@@ -9,13 +9,17 @@ import { rewriteBrowserArgv } from '../cli-argv-preprocess.js';
9
9
  import { createProgram } from '../cli.js';
10
10
  import { formatRootHelp } from '../command-presentation.js';
11
11
  import { HOSTED_ROOT_HELP } from '../completion-shared.js';
12
+ import { PKG_VERSION } from '../version.js';
12
13
  import { makeHostedConfig } from './config.js';
13
14
  import { runHostedCli } from './runner.js';
15
+ const [packageMajor, packageMinor] = PKG_VERSION.split('.');
16
+ const compatiblePatchVersion = `${packageMajor}.${packageMinor}.99`;
17
+ const incompatibleMinorVersion = `${packageMajor}.${Number(packageMinor) + 1}.0`;
14
18
  const manifest = {
15
19
  userId: 'user_demo',
16
20
  metadata: {
17
21
  contractSchemaVersion: 1,
18
- webcmdPackageVersion: '0.3.0',
22
+ webcmdPackageVersion: PKG_VERSION,
19
23
  generatedAt: '2026-07-08T00:00:00.000Z',
20
24
  },
21
25
  commands: [
@@ -1084,7 +1088,7 @@ describe('runHostedCli', () => {
1084
1088
  const stdout = sink();
1085
1089
  const patchBumped = {
1086
1090
  ...manifest,
1087
- metadata: { ...manifest.metadata, webcmdPackageVersion: '0.3.99' },
1091
+ metadata: { ...manifest.metadata, webcmdPackageVersion: compatiblePatchVersion },
1088
1092
  };
1089
1093
  const result = await runHostedCli(['github', 'whoami', '-f', 'json'], {
1090
1094
  config: makeHostedConfig({ apiBaseUrl: 'https://api.example.com', apiKey: 'key' }),
@@ -1108,7 +1112,7 @@ describe('runHostedCli', () => {
1108
1112
  const stderr = sink();
1109
1113
  const mismatched = {
1110
1114
  ...manifest,
1111
- metadata: { ...manifest.metadata, webcmdPackageVersion: '0.4.0' },
1115
+ metadata: { ...manifest.metadata, webcmdPackageVersion: incompatibleMinorVersion },
1112
1116
  };
1113
1117
  const result = await runHostedCli(['github', 'whoami'], {
1114
1118
  config: makeHostedConfig({ apiBaseUrl: 'https://api.example.com', apiKey: 'key' }),
@@ -1527,7 +1531,7 @@ describe('runHostedCli', () => {
1527
1531
  const stderr = sink();
1528
1532
  const mismatched = {
1529
1533
  ...manifest,
1530
- metadata: { ...manifest.metadata, webcmdPackageVersion: '0.4.0' },
1534
+ metadata: { ...manifest.metadata, webcmdPackageVersion: incompatibleMinorVersion },
1531
1535
  };
1532
1536
  const result = await runHostedCli(['browser', 'work', 'state'], {
1533
1537
  config: makeHostedConfig({ apiBaseUrl: 'https://api.example.com', apiKey: 'key' }),
@@ -42,6 +42,9 @@ function makePackageRoot(label = 'current') {
42
42
  function real(filePath) {
43
43
  return fs.realpathSync(filePath);
44
44
  }
45
+ function bundledSkill(name) {
46
+ return fs.readFileSync(path.join(process.cwd(), 'skills', name, 'SKILL.md'), 'utf8');
47
+ }
45
48
  describe('webcmd skills content', () => {
46
49
  it('keeps bundled skill frontmatter valid yaml', () => {
47
50
  const skillsRoot = path.join(process.cwd(), 'skills');
@@ -80,6 +83,57 @@ describe('webcmd skills content', () => {
80
83
  'webcmd-usage',
81
84
  ]);
82
85
  });
86
+ it('enforces mode-neutral authentication and human handoff policy', () => {
87
+ const browser = bundledSkill('webcmd-browser');
88
+ const usage = bundledSkill('webcmd-usage');
89
+ const autofix = bundledSkill('webcmd-autofix');
90
+ const author = bundledSkill('webcmd-adapter-author');
91
+ const skills = [browser, usage, autofix, author];
92
+ const handoffSkills = [browser, usage, autofix];
93
+ const autofixAuthRequired = autofix.match(/^- \*\*`AUTH_REQUIRED`\*\*[\s\S]*?(?=\n- \*\*)/m)?.[0] ?? '';
94
+ const autofixAuthRequiredRow = autofix.split('\n')
95
+ .find((line) => line.startsWith('| AUTH_REQUIRED |')) ?? '';
96
+ expect(browser).toContain('webcmd <site> login');
97
+ expect(browser).toContain('webcmd <site> whoami');
98
+ expect(browser).toContain('CAPTCHA');
99
+ expect(browser).toContain('fresh browser state');
100
+ expect(browser).not.toContain('hunter2');
101
+ expect(browser).not.toMatch(/browser login type/i);
102
+ expect(usage).toContain('AUTH_REQUIRED');
103
+ expect(usage).toContain('action_required');
104
+ expect(autofix).toContain('webcmd <site> login');
105
+ expect(author).toContain('registerSiteAuthCommands');
106
+ for (const skill of handoffSkills) {
107
+ expect(skill).toContain('handoff.status');
108
+ expect(skill).toContain('handoff.viewUrl');
109
+ expect(skill).toContain('handoff.verifyCommand');
110
+ expect(skill).toContain('Webcmd browser:');
111
+ expect(skill).not.toMatch(/\bhosted\b|\bKernel\b|\blocal mode\b|\blocally\b/i);
112
+ }
113
+ for (const skill of [browser, usage]) {
114
+ expect(skill).toContain('already_logged_in');
115
+ expect(skill).toContain('in_progress');
116
+ expect(skill).toContain('action_url');
117
+ expect(skill).toContain('view_url');
118
+ expect(skill).toMatch(/in_progress[^\n]*(?:do not ask the user|do not wait for user confirmation)/i);
119
+ expect(skill).toMatch(/(?:action_url|view_url|handoff\.viewUrl|Webcmd browser:)[\s\S]{0,300}user/i);
120
+ }
121
+ for (const skill of skills) {
122
+ expect(skill).toContain('action_required');
123
+ expect(skill).toContain('verify_command');
124
+ expect(skill).toMatch(/verify_command[\s\S]{0,250}verification must succeed[\s\S]{0,250}retry/i);
125
+ expect(skill).toMatch(/verify_command[\s\S]{0,250}user[\s\S]{0,250}(?:done|complet)/i);
126
+ expect(skill).toMatch(/CAPTCHA[\s\S]{0,250}(?:human handoff|stop(?:s)? automation)/i);
127
+ expect(skill).toMatch(/(?:must not|never).*?(?:password|secret|credential)/i);
128
+ }
129
+ for (const skill of [browser, usage, autofix]) {
130
+ expect(skill).toMatch(/(?:no (?:site )?login command|without a verifier)[\s\S]{0,500}fresh browser state[\s\S]{0,500}(?:identity check|post-action state)[\s\S]{0,250}before (?:any )?retry/i);
131
+ }
132
+ expect(autofixAuthRequired).toMatch(/if (?:a|the) site login command exists[\s\S]*webcmd <site> login[\s\S]*returned `verify_command`[\s\S]*verification must succeed[\s\S]*retry/i);
133
+ expect(autofixAuthRequired).toMatch(/no site login command[\s\S]*stop (?:browser )?writes[\s\S]*visible browser[\s\S]*fresh browser state[\s\S]*(?:identity check|post-action state)[\s\S]*before retry[\s\S]*report alone is not verification/i);
134
+ expect(autofixAuthRequiredRow).toMatch(/conditional[^|]*Safety Boundaries|no site login command/i);
135
+ expect(autofix).toMatch(/CAPTCHA[\s\S]{0,250}stop automation[\s\S]{0,250}verification must succeed/i);
136
+ });
83
137
  it('adds bundled skills once and refreshes them after package updates', () => {
84
138
  const firstRoot = makePackageRoot('first');
85
139
  const secondRoot = makePackageRoot('second');