@agentrhq/webcmd 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/README.md +45 -272
- package/cli-manifest.json +184 -311
- package/clis/_shared/site-auth.js +37 -40
- package/clis/_shared/site-auth.test.js +109 -22
- package/clis/amazon/auth.js +0 -6
- package/clis/band/auth.js +0 -6
- package/clis/blinkit/auth.js +13 -40
- package/clis/blinkit/blinkit.test.js +39 -2
- package/clis/chatgpt/auth.js +0 -6
- package/clis/claude/auth.js +0 -6
- package/clis/coupang/auth.js +0 -6
- package/clis/district/auth.js +0 -2
- package/clis/facebook/auth.js +0 -6
- package/clis/gemini/auth.js +0 -6
- package/clis/github/auth.js +0 -6
- package/clis/grok/auth.js +0 -6
- package/clis/hackernews/hackernews.test.js +20 -1
- package/clis/hackernews/jobs.js +1 -1
- package/clis/hf/auth.js +2 -7
- package/clis/instagram/auth.js +0 -6
- package/clis/linkedin/auth.js +0 -6
- package/clis/linkedin-learning/auth.js +0 -6
- package/clis/manus/auth.js +0 -6
- package/clis/notebooklm/auth.js +0 -6
- package/clis/pixiv/auth.js +1 -7
- package/clis/practo/login.js +12 -38
- package/clis/practo/practo.test.js +13 -9
- package/clis/reddit/auth.js +0 -6
- package/clis/reuters/auth.js +0 -14
- package/clis/suno/auth.js +0 -6
- package/clis/tiktok/auth.js +0 -6
- package/clis/twitter/auth.js +0 -6
- package/clis/upwork/auth.js +0 -6
- package/clis/youtube/auth.js +0 -6
- package/clis/zepto/auth.js +16 -29
- package/clis/zepto/zepto.test.js +45 -2
- package/dist/src/browser/ax-snapshot.js +15 -5
- package/dist/src/browser/ax-snapshot.test.js +49 -0
- package/dist/src/browser/cdp.js +3 -1
- package/dist/src/browser/cdp.test.js +12 -0
- package/dist/src/browser/page.js +6 -2
- package/dist/src/browser/page.test.js +1 -0
- package/dist/src/browser/protocol.d.ts +2 -0
- package/dist/src/browser/runtime/local-cloak/actions.js +42 -21
- package/dist/src/browser/runtime/local-cloak/provider.test.js +33 -5
- package/dist/src/build-manifest.js +3 -3
- package/dist/src/build-manifest.test.js +14 -0
- package/dist/src/docs-sync-review.js +2 -2
- package/dist/src/docs-sync-review.test.js +2 -2
- package/dist/src/hosted/main-lifecycle.test.js +2 -1
- package/dist/src/hosted/manifest.test.js +2 -1
- package/dist/src/hosted/output-parity.test.js +2 -1
- package/dist/src/hosted/root-command-surface.test.js +1 -1
- package/dist/src/hosted/runner.test.js +8 -4
- package/dist/src/skills.test.js +54 -0
- package/hosted-contract.json +191 -366
- package/package.json +5 -4
- package/skills/smart-search/SKILL.md +20 -6
- package/skills/webcmd-adapter-author/SKILL.md +2 -0
- package/skills/webcmd-autofix/SKILL.md +5 -3
- package/skills/webcmd-browser/SKILL.md +28 -13
- package/skills/webcmd-usage/SKILL.md +29 -6
- package/clis/practo/whoami.js +0 -28
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { LocalCloakRuntimeProvider } from './provider.js';
|
|
3
|
-
function fakePage(url) {
|
|
3
|
+
function fakePage(url, initialViewport = { width: 1280, height: 720 }) {
|
|
4
4
|
let closed = false;
|
|
5
|
-
let viewportSize =
|
|
5
|
+
let viewportSize = initialViewport;
|
|
6
6
|
return {
|
|
7
7
|
isClosed: vi.fn(() => closed),
|
|
8
8
|
goto: vi.fn(async (nextUrl) => {
|
|
@@ -24,8 +24,9 @@ function fakePage(url) {
|
|
|
24
24
|
}),
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
-
function makeProviderWithFakePage() {
|
|
28
|
-
const pages = [fakePage('https://example.com/')];
|
|
27
|
+
function makeProviderWithFakePage(initialViewport = { width: 1280, height: 720 }) {
|
|
28
|
+
const pages = [fakePage('https://example.com/', initialViewport)];
|
|
29
|
+
const cdpSession = { send: vi.fn().mockResolvedValue(undefined), detach: vi.fn().mockResolvedValue(undefined) };
|
|
29
30
|
const context = {
|
|
30
31
|
on: vi.fn(),
|
|
31
32
|
pages: vi.fn(() => pages.filter((page) => !page.isClosed())),
|
|
@@ -34,6 +35,7 @@ function makeProviderWithFakePage() {
|
|
|
34
35
|
pages.push(page);
|
|
35
36
|
return page;
|
|
36
37
|
}),
|
|
38
|
+
newCDPSession: vi.fn().mockResolvedValue(cdpSession),
|
|
37
39
|
cookies: vi.fn().mockResolvedValue([{ name: 'sid', value: '1', domain: 'example.com', path: '/' }]),
|
|
38
40
|
close: vi.fn().mockResolvedValue(undefined),
|
|
39
41
|
};
|
|
@@ -41,7 +43,7 @@ function makeProviderWithFakePage() {
|
|
|
41
43
|
baseDir: '/tmp/webcmd-test',
|
|
42
44
|
launchPersistentContext: vi.fn().mockResolvedValue(context),
|
|
43
45
|
});
|
|
44
|
-
return { provider, page: pages[0], pages, context };
|
|
46
|
+
return { provider, page: pages[0], pages, context, cdpSession };
|
|
45
47
|
}
|
|
46
48
|
describe('LocalCloakRuntimeProvider', () => {
|
|
47
49
|
it('reports a runtime-named connected status before any profile launches', async () => {
|
|
@@ -66,6 +68,20 @@ describe('LocalCloakRuntimeProvider', () => {
|
|
|
66
68
|
expect(result).toMatchObject({ id: 'cmd-1', ok: true, page: expect.any(String) });
|
|
67
69
|
expect(page.goto).toHaveBeenCalledWith('https://example.com/', expect.objectContaining({ waitUntil: 'load' }));
|
|
68
70
|
});
|
|
71
|
+
it("maps waitUntil 'none' to a commit-only navigation wait", async () => {
|
|
72
|
+
const { provider, page } = makeProviderWithFakePage();
|
|
73
|
+
const result = await provider.dispatch({
|
|
74
|
+
id: 'cmd-1',
|
|
75
|
+
action: 'navigate',
|
|
76
|
+
session: 'work',
|
|
77
|
+
surface: 'browser',
|
|
78
|
+
url: 'https://example.com/',
|
|
79
|
+
waitUntil: 'none',
|
|
80
|
+
profileId: 'default',
|
|
81
|
+
});
|
|
82
|
+
expect(result).toMatchObject({ id: 'cmd-1', ok: true, page: expect.any(String) });
|
|
83
|
+
expect(page.goto).toHaveBeenCalledWith('https://example.com/', expect.objectContaining({ waitUntil: 'commit' }));
|
|
84
|
+
});
|
|
69
85
|
it('evaluates JavaScript in the resolved page', async () => {
|
|
70
86
|
const { provider } = makeProviderWithFakePage();
|
|
71
87
|
const nav = await provider.dispatch({ id: 'nav', action: 'navigate', session: 'work', surface: 'browser', url: 'https://example.com/', profileId: 'default' });
|
|
@@ -146,6 +162,18 @@ describe('LocalCloakRuntimeProvider', () => {
|
|
|
146
162
|
expect(page.setViewportSize).toHaveBeenNthCalledWith(2, { width: 1280, height: 720 });
|
|
147
163
|
expect(page.screenshot).toHaveBeenCalledTimes(1);
|
|
148
164
|
});
|
|
165
|
+
it('reversibly overrides via CDP and never pins the viewport when the context has no fixed viewport', async () => {
|
|
166
|
+
const { provider, page, cdpSession } = makeProviderWithFakePage(null);
|
|
167
|
+
const nav = await provider.dispatch({ id: 'nav', action: 'navigate', session: 'work', surface: 'browser', url: 'https://example.com/', profileId: 'default' });
|
|
168
|
+
await provider.dispatch({ id: 'shot', action: 'screenshot', session: 'work', surface: 'browser', page: nav.page, format: 'png', width: 375, height: 812, profileId: 'default' });
|
|
169
|
+
// The override must not permanently pin the real window via setViewportSize (#120).
|
|
170
|
+
expect(page.setViewportSize).not.toHaveBeenCalled();
|
|
171
|
+
expect(cdpSession.send).toHaveBeenCalledWith('Emulation.setDeviceMetricsOverride', expect.objectContaining({ width: 375, height: 812 }));
|
|
172
|
+
// ...and it must be cleared afterward so the override is per-shot only.
|
|
173
|
+
expect(cdpSession.send).toHaveBeenCalledWith('Emulation.clearDeviceMetricsOverride');
|
|
174
|
+
expect(cdpSession.detach).toHaveBeenCalledTimes(1);
|
|
175
|
+
expect(page.screenshot).toHaveBeenCalledTimes(1);
|
|
176
|
+
});
|
|
149
177
|
it('ignores screenshot height overrides for full-page captures while applying width', async () => {
|
|
150
178
|
const { provider, page } = makeProviderWithFakePage();
|
|
151
179
|
const nav = await provider.dispatch({ id: 'nav', action: 'navigate', session: 'work', surface: 'browser', url: 'https://example.com/', profileId: 'default' });
|
|
@@ -348,8 +348,8 @@ async function main() {
|
|
|
348
348
|
fs.mkdirSync(path.dirname(OUTPUT), { recursive: true });
|
|
349
349
|
fs.writeFileSync(OUTPUT, artifacts.manifestJson);
|
|
350
350
|
fs.writeFileSync(HOSTED_CONTRACT_OUTPUT, artifacts.hostedContractJson);
|
|
351
|
-
console.
|
|
352
|
-
console.
|
|
351
|
+
console.error(`✅ Manifest compiled: ${entries.length} entries → ${OUTPUT}`);
|
|
352
|
+
console.error(`✅ Hosted contract compiled: ${packageMetadata.name}@${packageMetadata.version} → ${HOSTED_CONTRACT_OUTPUT}`);
|
|
353
353
|
// Restore executable permissions on bin entries.
|
|
354
354
|
// tsc does not preserve the +x bit, so after a clean rebuild the CLI
|
|
355
355
|
// entry-point loses its executable permission, causing "Permission denied".
|
|
@@ -366,7 +366,7 @@ async function main() {
|
|
|
366
366
|
const abs = path.resolve(projectRoot, binPath);
|
|
367
367
|
if (fs.existsSync(abs)) {
|
|
368
368
|
fs.chmodSync(abs, 0o755);
|
|
369
|
-
console.
|
|
369
|
+
console.error(`✅ Restored executable permission: ${binPath}`);
|
|
370
370
|
}
|
|
371
371
|
}
|
|
372
372
|
}
|
|
@@ -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/
|
|
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/
|
|
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/
|
|
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/
|
|
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:
|
|
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:
|
|
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:
|
|
30
|
+
webcmdPackageVersion: PKG_VERSION,
|
|
30
31
|
generatedAt: '2026-07-14T00:00:00.000Z',
|
|
31
32
|
},
|
|
32
33
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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' }),
|
package/dist/src/skills.test.js
CHANGED
|
@@ -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');
|