@dikshanty94/webnav 0.2.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 (145) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +283 -0
  3. package/bin/webnav +18 -0
  4. package/dist/agent/loop.d.ts +59 -0
  5. package/dist/agent/loop.js +267 -0
  6. package/dist/agent/server.d.ts +60 -0
  7. package/dist/agent/server.js +254 -0
  8. package/dist/cli-help.d.ts +3 -0
  9. package/dist/cli-help.js +111 -0
  10. package/dist/cli-spec.d.ts +24 -0
  11. package/dist/cli-spec.js +460 -0
  12. package/dist/cli.d.ts +208 -0
  13. package/dist/cli.js +2558 -0
  14. package/dist/contract.d.ts +20 -0
  15. package/dist/contract.js +15 -0
  16. package/dist/creds.d.ts +46 -0
  17. package/dist/creds.js +126 -0
  18. package/dist/dashboard/server.d.ts +137 -0
  19. package/dist/dashboard/server.js +395 -0
  20. package/dist/dashboard/shell.d.ts +1 -0
  21. package/dist/dashboard/shell.js +1343 -0
  22. package/dist/explorer/analyse.d.ts +25 -0
  23. package/dist/explorer/analyse.js +39 -0
  24. package/dist/explorer/diff.d.ts +11 -0
  25. package/dist/explorer/diff.js +22 -0
  26. package/dist/explorer/draft.d.ts +81 -0
  27. package/dist/explorer/draft.js +1658 -0
  28. package/dist/explorer/fingerprint-page.d.ts +11 -0
  29. package/dist/explorer/fingerprint-page.js +19 -0
  30. package/dist/explorer/fingerprint.d.ts +14 -0
  31. package/dist/explorer/fingerprint.js +18 -0
  32. package/dist/explorer/infer.d.ts +85 -0
  33. package/dist/explorer/infer.js +370 -0
  34. package/dist/explorer/pattern-propose.d.ts +27 -0
  35. package/dist/explorer/pattern-propose.js +81 -0
  36. package/dist/explorer/patterns.d.ts +49 -0
  37. package/dist/explorer/patterns.js +290 -0
  38. package/dist/explorer/shadow.d.ts +3 -0
  39. package/dist/explorer/shadow.js +95 -0
  40. package/dist/graph/coverage.d.ts +33 -0
  41. package/dist/graph/coverage.js +155 -0
  42. package/dist/graph/edit.d.ts +47 -0
  43. package/dist/graph/edit.js +211 -0
  44. package/dist/graph/frontier.d.ts +24 -0
  45. package/dist/graph/frontier.js +95 -0
  46. package/dist/graph/seed.d.ts +23 -0
  47. package/dist/graph/seed.js +29 -0
  48. package/dist/graph/show.d.ts +8 -0
  49. package/dist/graph/show.js +6 -0
  50. package/dist/graph/teach.d.ts +8 -0
  51. package/dist/graph/teach.js +10 -0
  52. package/dist/hosted.d.ts +36 -0
  53. package/dist/hosted.js +69 -0
  54. package/dist/index.d.ts +2 -0
  55. package/dist/index.js +4 -0
  56. package/dist/mapstore/record.d.ts +128 -0
  57. package/dist/mapstore/record.js +224 -0
  58. package/dist/mapstore/schema.sql +47 -0
  59. package/dist/mapstore/store.d.ts +108 -0
  60. package/dist/mapstore/store.js +399 -0
  61. package/dist/mapstore/types.d.ts +87 -0
  62. package/dist/mapstore/types.js +29 -0
  63. package/dist/mcp/server.d.ts +46 -0
  64. package/dist/mcp/server.js +163 -0
  65. package/dist/paths.d.ts +10 -0
  66. package/dist/paths.js +33 -0
  67. package/dist/playwright/adapter.d.ts +62 -0
  68. package/dist/playwright/adapter.js +145 -0
  69. package/dist/playwright/ax-adapter.d.ts +33 -0
  70. package/dist/playwright/ax-adapter.js +76 -0
  71. package/dist/playwright/capture.d.ts +2 -0
  72. package/dist/playwright/capture.js +10 -0
  73. package/dist/playwright/fingerprint.d.ts +40 -0
  74. package/dist/playwright/fingerprint.js +139 -0
  75. package/dist/playwright/profile-lock.d.ts +15 -0
  76. package/dist/playwright/profile-lock.js +51 -0
  77. package/dist/playwright/sessions.d.ts +72 -0
  78. package/dist/playwright/sessions.js +229 -0
  79. package/dist/playwright/snapshot.d.ts +10 -0
  80. package/dist/playwright/snapshot.js +52 -0
  81. package/dist/playwright/throttle.d.ts +14 -0
  82. package/dist/playwright/throttle.js +76 -0
  83. package/dist/playwright/video.d.ts +13 -0
  84. package/dist/playwright/video.js +32 -0
  85. package/dist/protocol.d.ts +49 -0
  86. package/dist/protocol.js +3 -0
  87. package/dist/recorder/agent-session.d.ts +59 -0
  88. package/dist/recorder/agent-session.js +230 -0
  89. package/dist/recorder/capture-loop.d.ts +19 -0
  90. package/dist/recorder/capture-loop.js +40 -0
  91. package/dist/recorder/coverage.d.ts +22 -0
  92. package/dist/recorder/coverage.js +49 -0
  93. package/dist/recorder/hover-probe.d.ts +35 -0
  94. package/dist/recorder/hover-probe.js +100 -0
  95. package/dist/recorder/ingest.d.ts +39 -0
  96. package/dist/recorder/ingest.js +138 -0
  97. package/dist/recorder/live-record.d.ts +41 -0
  98. package/dist/recorder/live-record.js +255 -0
  99. package/dist/recorder/live.d.ts +53 -0
  100. package/dist/recorder/live.js +400 -0
  101. package/dist/recorder/probe.d.ts +14 -0
  102. package/dist/recorder/probe.js +33 -0
  103. package/dist/recorder/replay.d.ts +60 -0
  104. package/dist/recorder/replay.js +401 -0
  105. package/dist/recorder/review.d.ts +73 -0
  106. package/dist/recorder/review.js +171 -0
  107. package/dist/recorder/snapshot-dom.d.ts +7 -0
  108. package/dist/recorder/snapshot-dom.js +25 -0
  109. package/dist/router/auth-status.d.ts +20 -0
  110. package/dist/router/auth-status.js +55 -0
  111. package/dist/router/browse.d.ts +108 -0
  112. package/dist/router/browse.js +214 -0
  113. package/dist/router/catalog.d.ts +18 -0
  114. package/dist/router/catalog.js +6 -0
  115. package/dist/router/extract-content.d.ts +13 -0
  116. package/dist/router/extract-content.js +87 -0
  117. package/dist/router/live-extension-browser.d.ts +35 -0
  118. package/dist/router/live-extension-browser.js +175 -0
  119. package/dist/router/path.d.ts +7 -0
  120. package/dist/router/path.js +55 -0
  121. package/dist/router/read.d.ts +26 -0
  122. package/dist/router/read.js +30 -0
  123. package/dist/router/readiness.d.ts +9 -0
  124. package/dist/router/readiness.js +55 -0
  125. package/dist/router/replay.d.ts +14 -0
  126. package/dist/router/replay.js +23 -0
  127. package/dist/router/resolve.d.ts +21 -0
  128. package/dist/router/resolve.js +33 -0
  129. package/dist/router/search-live.d.ts +21 -0
  130. package/dist/router/search-live.js +98 -0
  131. package/dist/router/search-providers.d.ts +5 -0
  132. package/dist/router/search-providers.js +12 -0
  133. package/dist/router/search.d.ts +6 -0
  134. package/dist/router/search.js +62 -0
  135. package/dist/router/suite.d.ts +69 -0
  136. package/dist/router/suite.js +226 -0
  137. package/dist/router/tokens.d.ts +13 -0
  138. package/dist/router/tokens.js +26 -0
  139. package/dist/router/walk-live.d.ts +57 -0
  140. package/dist/router/walk-live.js +308 -0
  141. package/dist/router/walk-session.d.ts +49 -0
  142. package/dist/router/walk-session.js +90 -0
  143. package/dist/router/walk.d.ts +48 -0
  144. package/dist/router/walk.js +384 -0
  145. package/package.json +59 -0
@@ -0,0 +1,111 @@
1
+ // Pure string builders for help text, driven entirely by the COMMANDS registry
2
+ // so help and parsing never drift. No I/O here — main() does the printing.
3
+ import { COMMANDS, CONSUMER_COMMANDS, DEV_COMMANDS, VERSION } from './cli-spec.js';
4
+ const GLOBAL_FLAGS = [
5
+ { name: '--help, -h', description: 'Show help (this menu, or per-command help).' },
6
+ { name: '--version, -V', description: 'Print the webnav version and exit.' },
7
+ { name: '--json', description: 'Emit machine-readable JSON to stdout only (suppress human prose).' },
8
+ ];
9
+ function pad(s, width) {
10
+ return s.length >= width ? s : s + ' '.repeat(width - s.length);
11
+ }
12
+ export function topLevelHelp() {
13
+ const lines = [];
14
+ lines.push('webnav — a navigation memory for AI agents: record a site once, then walk routes to your goals deterministically (zero-LLM). Read pages and search the web too.');
15
+ lines.push('');
16
+ lines.push('Usage: webnav <command> [args...] [flags]');
17
+ lines.push(`Version: ${VERSION}`);
18
+ lines.push('');
19
+ lines.push('use — drive the browser + query the map:');
20
+ lines.push('');
21
+ const GROUPS = [
22
+ { key: 'find', header: 'Find: (where is it)' },
23
+ { key: 'read', header: 'Read: (get content / evidence)' },
24
+ { key: 'navigate', header: 'Navigate: (drive a page)' },
25
+ ];
26
+ const nameWidth = Math.max(...CONSUMER_COMMANDS.map((c) => c.name.length));
27
+ for (const g of GROUPS) {
28
+ const cmds = CONSUMER_COMMANDS.filter((c) => c.group === g.key);
29
+ if (cmds.length === 0)
30
+ continue;
31
+ lines.push(g.header);
32
+ for (const c of cmds) {
33
+ lines.push(` ${pad(c.name, nameWidth)} ${c.summary}`);
34
+ }
35
+ lines.push('');
36
+ }
37
+ // dev — the authoring/inspection verbs. Shown here too (not just under
38
+ // `webnav dev --help`) so a single `webnav --help` is a complete tool list;
39
+ // they stay a separate CATEGORY (invoked as `webnav dev <cmd>`) because they
40
+ // build/inspect the map rather than drive it at runtime.
41
+ lines.push('dev — teach & inspect the map (run as `webnav dev <command>`):');
42
+ lines.push('');
43
+ const devWidth = Math.max(...DEV_COMMANDS.map((c) => c.name.length));
44
+ for (const c of DEV_COMMANDS) {
45
+ lines.push(` ${pad(c.name, devWidth)} ${c.summary}`);
46
+ }
47
+ lines.push('');
48
+ lines.push('Global flags:');
49
+ const flagWidth = Math.max(...GLOBAL_FLAGS.map((f) => f.name.length));
50
+ for (const f of GLOBAL_FLAGS) {
51
+ lines.push(` ${pad(f.name, flagWidth)} ${f.description}`);
52
+ }
53
+ lines.push('');
54
+ lines.push('Run `webnav <command> --help` (or `webnav dev <command> --help`) for details.');
55
+ return lines.join('\n');
56
+ }
57
+ export function devHelp() {
58
+ const lines = [];
59
+ lines.push('webnav dev — teaching & inspection tools (not needed for normal use).');
60
+ lines.push('');
61
+ lines.push('Usage: webnav dev <command> [args...]');
62
+ lines.push('');
63
+ lines.push('Commands:');
64
+ const nameWidth = Math.max(...DEV_COMMANDS.map((c) => c.name.length));
65
+ for (const c of DEV_COMMANDS) {
66
+ lines.push(` ${pad(c.name, nameWidth)} ${c.summary}`);
67
+ }
68
+ lines.push('');
69
+ lines.push('Run `webnav dev <command> --help` for details.');
70
+ return lines.join('\n');
71
+ }
72
+ function usageLine(c) {
73
+ const parts = ['Usage: webnav', c.name];
74
+ for (const a of c.args) {
75
+ parts.push(a.required ? `<${a.name}>` : `[${a.name}]`);
76
+ }
77
+ if (c.flags.length > 0)
78
+ parts.push('[flags]');
79
+ return parts.join(' ');
80
+ }
81
+ export function commandHelp(name) {
82
+ const c = COMMANDS.find((cmd) => cmd.name === name);
83
+ if (!c) {
84
+ return `Unknown command: ${name}\nRun \`webnav --help\` to see available commands.`;
85
+ }
86
+ const lines = [];
87
+ lines.push(usageLine(c));
88
+ lines.push('');
89
+ lines.push(c.summary);
90
+ if (c.args.length > 0) {
91
+ lines.push('');
92
+ lines.push('Arguments:');
93
+ const w = Math.max(...c.args.map((a) => a.name.length));
94
+ for (const a of c.args) {
95
+ const req = a.required ? 'required' : 'optional';
96
+ lines.push(` ${pad(a.name, w)} (${req}) ${a.description}`);
97
+ }
98
+ }
99
+ if (c.flags.length > 0) {
100
+ lines.push('');
101
+ lines.push('Flags:');
102
+ for (const f of c.flags) {
103
+ const sig = f.takesValue ? `${f.name} <value>` : f.name;
104
+ const def = f.default !== undefined ? ` (default: ${f.default})` : '';
105
+ lines.push(` ${sig}${def} — ${f.description}`);
106
+ }
107
+ }
108
+ lines.push('');
109
+ lines.push(`Example: ${c.example}`);
110
+ return lines.join('\n');
111
+ }
@@ -0,0 +1,24 @@
1
+ export interface ArgSpec {
2
+ name: string;
3
+ required: boolean;
4
+ description: string;
5
+ }
6
+ export interface FlagSpec {
7
+ name: string;
8
+ takesValue: boolean;
9
+ default?: string;
10
+ description: string;
11
+ }
12
+ export interface CommandSpec {
13
+ name: string;
14
+ group?: 'find' | 'read' | 'navigate';
15
+ summary: string;
16
+ args: ArgSpec[];
17
+ flags: FlagSpec[];
18
+ example: string;
19
+ }
20
+ export declare const VERSION: string;
21
+ export declare const BROWSER_FLAGS: FlagSpec[];
22
+ export declare const CONSUMER_COMMANDS: CommandSpec[];
23
+ export declare const DEV_COMMANDS: CommandSpec[];
24
+ export declare const COMMANDS: CommandSpec[];
@@ -0,0 +1,460 @@
1
+ // Command registry: the single source of truth that drives BOTH help rendering
2
+ // and (the intent of) parsing, so the two can never drift. clig.dev: a
3
+ // self-describing CLI whose --help is the agent's tool menu.
4
+ // Single source of truth: read the version from package.json at runtime (no
5
+ // resolveJsonModule needed, and no hand-maintained constant to drift). Works from
6
+ // both src/ (tsx) and dist/ — ../package.json resolves to the repo root either way.
7
+ import { readFileSync as _readVersionFile } from 'node:fs';
8
+ import { fileURLToPath as _versionUrl } from 'node:url';
9
+ export const VERSION = (() => {
10
+ try {
11
+ return JSON.parse(_readVersionFile(_versionUrl(new URL('../package.json', import.meta.url)), 'utf8')).version ?? '0.0.0';
12
+ }
13
+ catch {
14
+ return '0.0.0';
15
+ }
16
+ })();
17
+ // Browser-launch flags shared by every verb that opens a browser (read / navigate
18
+ // / walk). Default is HEADED (a visible window); --headless opts out.
19
+ export const BROWSER_FLAGS = [
20
+ { name: '--headless', takesValue: false, description: 'Run without a visible browser window (CI / servers). Default is headed — a real visible window.' },
21
+ { name: '--headed', takesValue: false, description: 'Show a real browser window (the default; kept as an explicit no-op for back-compat).' },
22
+ { name: '--persistent', takesValue: false, description: 'Reuse a persistent browser profile so a logged-in session survives across runs.' },
23
+ { name: '--profile', takesValue: true, description: 'Persistent profile directory (implies --persistent).' },
24
+ { name: '--browser', takesValue: true, description: 'Engine/channel: chrome | firefox | webkit | msedge.' },
25
+ ];
26
+ export const CONSUMER_COMMANDS = [
27
+ {
28
+ name: 'read',
29
+ group: 'read',
30
+ summary: 'Open a URL and return its distilled content (use --raw for the full page snapshot).',
31
+ args: [{ name: 'url', required: true, description: 'A URL to open and read.' }],
32
+ flags: [
33
+ { name: '--raw', takesValue: false, description: 'Return the full page snapshot instead of distilled content.' },
34
+ ...BROWSER_FLAGS,
35
+ ],
36
+ example: 'webnav read https://github.com/psf/requests',
37
+ },
38
+ {
39
+ name: 'search',
40
+ group: 'read',
41
+ summary: 'Search the open web for a query: visit top-N results and return extracted answer-evidence.',
42
+ args: [
43
+ { name: 'query', required: true, description: 'The query to search the open web for.' },
44
+ ],
45
+ flags: [
46
+ {
47
+ name: '--top',
48
+ takesValue: true,
49
+ default: '3',
50
+ description: 'Number of top results to visit and extract evidence from.',
51
+ },
52
+ ],
53
+ example: 'webnav search "who won the 2018 world cup" --top 3',
54
+ },
55
+ {
56
+ name: 'eval',
57
+ group: 'navigate',
58
+ summary: 'Open a URL and run a JS expression in the page — returns just the value (cheap, targeted extraction).',
59
+ args: [
60
+ { name: 'url', required: true, description: 'A URL to open.' },
61
+ { name: 'js', required: true, description: 'A () => <value> JS expression evaluated in the page; its return value is returned.' },
62
+ ],
63
+ flags: [],
64
+ example: 'webnav eval https://github.com/psf/requests "() => document.title"',
65
+ },
66
+ {
67
+ name: 'network',
68
+ group: 'navigate',
69
+ summary: 'Open a URL and return the network/API calls the page made (often the JSON behind the rendered DOM).',
70
+ args: [{ name: 'url', required: true, description: 'A URL to open.' }],
71
+ flags: [],
72
+ example: 'webnav network https://api-backed-site.example',
73
+ },
74
+ {
75
+ name: 'go-back',
76
+ group: 'navigate',
77
+ summary: 'Step back in a browser session (pass --session to target one you are driving).',
78
+ args: [],
79
+ flags: [{ name: '--session', takesValue: true, description: 'Browser session name to act on (default: webnav-nav).' }],
80
+ example: 'webnav go-back --session mysession',
81
+ },
82
+ {
83
+ name: 'reload',
84
+ group: 'navigate',
85
+ summary: 'Reload the page in a browser session (pass --session to target one you are driving).',
86
+ args: [],
87
+ flags: [{ name: '--session', takesValue: true, description: 'Browser session name to act on (default: webnav-nav).' }],
88
+ example: 'webnav reload --session mysession',
89
+ },
90
+ {
91
+ name: 'close',
92
+ group: 'navigate',
93
+ summary: 'Close a browser session and free its window (the `use` verbs keep a session alive between calls; call this when done).',
94
+ args: [{ name: 'session', required: false, description: 'Session to close (or use --session).' }],
95
+ flags: [{ name: '--session', takesValue: true, description: 'Browser session name to close.' }],
96
+ example: 'webnav use close --session mysession',
97
+ },
98
+ {
99
+ name: 'session',
100
+ group: 'navigate',
101
+ summary: 'Interactive long-lived agent session: opens ONE browser (+ video + recording), then reads JSON-line commands on stdin and writes JSON results on stdout until quit/EOF. The shape that gives agent recordings video with no leaked windows.',
102
+ args: [],
103
+ flags: [
104
+ { name: '--session', takesValue: true, description: 'Session name (required).' },
105
+ { name: '--url', takesValue: true, description: 'Start URL (default about:blank).' },
106
+ { name: '--profile', takesValue: true, description: 'Named profile to run under (shared login); e.g. default.' },
107
+ { name: '--headless', takesValue: false, description: 'Run headless (recommended for agents/tests).' },
108
+ ],
109
+ example: 'echo \'{"cmd":"snapshot"}\' | webnav use session --session s1 --url https://example.com --headless',
110
+ },
111
+ {
112
+ name: 'walk', group: 'navigate',
113
+ summary: 'Walk a multi-step route to a non-URL state (pathfinds over the graph; pauses at forks for the agent). Response protocol: {status:"done",evidence} reached the goal (evidence carries `snapshot`+`repertoire` too when the GOAL state is dynamic — see --observe-dynamic) · {status:"needs-navigation",...} real drift, agent supplies a ref · {status:"needs-classification",...} a possibly-destructive action, agent classifies safe|commit · {status:"needs-auth",profile,site,loginUrl,at} a settled landing classified as an SSO/login wall THAT SURVIVED a fresh-session retry under the same --profile (a stale-login pattern, not evasion) — this is a FAIL, not a resumable pause: log in by hand in that profile, then re-run `walk` · {status:"checkpoint",at,state,snapshot,repertoire} a CONFIRMED arrival at a state you asked to observe — the live page + that state\'s stored affordance repertoire, so you don\'t have to snapshot manually; resume with `walk-resume <session> --continue` (fires at most once per state per walk; you may also fire `use` actions on the session before continuing — that\'s the designed pattern) · {status:"failed",reason}.',
114
+ args: [],
115
+ flags: [
116
+ { name: '--start', takesValue: true, description: 'Start state id (from `dev graph-show`).' },
117
+ { name: '--goal', takesValue: true, description: 'Goal state id to reach.' },
118
+ { name: '--input', takesValue: true, description: 'Runtime input slot=value (repeatable; never stored). Stored creds are used if set.' },
119
+ { name: '--hosted', takesValue: false, description: 'Use the HOSTED shared map: fetch this site\'s map live from the webnav service (needs `webnav login <key>`) instead of the local map. Credentials still stay local.' },
120
+ { name: '--observe', takesValue: true, description: 'Pause with a checkpoint on ARRIVAL at this state (id or semanticName; repeatable). Opt-in — walk stays a zero-token autopilot unless you ask to observe something.' },
121
+ { name: '--observe-dynamic', takesValue: false, description: 'Pause with a checkpoint on arrival at ANY state the map marks dynamic (provisional, or carrying a row/widget-scoped affordance) — for content that changes between visits.' },
122
+ ...BROWSER_FLAGS,
123
+ ],
124
+ example: 'webnav walk --start www.saucedemo.com:login --goal www.saucedemo.com:checkout-overview --headed --observe-dynamic',
125
+ },
126
+ {
127
+ name: 'login', group: 'navigate',
128
+ summary: 'Save your hosted-route API key (from the webnav site) to ~/.webnav/config.json so `walk --hosted` can fetch shared maps. The key is NOT a credential and is stored separately from site logins.',
129
+ args: [{ name: 'key', required: true, description: 'The API key issued on the webnav website.' }],
130
+ flags: [],
131
+ example: 'webnav login wn_live_xxx',
132
+ },
133
+ {
134
+ name: 'walk-resume', group: 'navigate',
135
+ summary: 'Continue a paused walk: answer the fork it stopped on.',
136
+ args: [{ name: 'session', required: true, description: 'Walk session id from a paused `walk`.' }],
137
+ flags: [
138
+ { name: '--ref', takesValue: true, description: 'Element ref (answers needs-navigation; from the snapshot).' },
139
+ { name: '--classify', takesValue: true, description: 'safe | commit (answers needs-classification; commit halts).' },
140
+ { name: '--continue', takesValue: false, description: 'Answers a checkpoint pause (--observe/--observe-dynamic) — proceed with no action. You may fire `use` actions on the session\'s browser first (the designed pattern); errors with a hint if the session actually paused on a different kind (needs-navigation/needs-classification instead).' },
141
+ { name: '--input', takesValue: true, description: 'Re-supply a one-off slot=value from the original `walk` (repeatable; runtime-only, never stored — stored creds are rebuilt automatically).' },
142
+ ],
143
+ example: 'webnav walk-resume walk-w-123 --ref e42',
144
+ },
145
+ {
146
+ name: 'creds', group: 'navigate',
147
+ summary: 'Store login/form credentials LOCALLY per site (~/.webnav/credentials.json, chmod 600; never in the map, never transmitted). A walk auto-fills its input slots from these. Subcommands: set <site> key=value… | list (key names only) | rm <site> [key].',
148
+ args: [{ name: 'sub', required: true, description: 'set | list | rm' }],
149
+ flags: [],
150
+ example: 'webnav creds set www.saucedemo.com username=standard_user password=secret_sauce',
151
+ },
152
+ {
153
+ name: 'test', group: 'navigate',
154
+ summary: 'Run a RELEASE SUITE against a site: "is everything still good?" as one command. A suite (*.suite.json) is a list of declarative walk cases; each case walks a route on AUTOPILOT and asserts checkpoints. The zero-answer philosophy: the runner answers NOTHING — a case that escalates (needs-navigation on drift, or needs-classification at a commit point) FAILS, because a release check has no judge and must never place an order or guess a fork. So the assertions are structural, not behavioral. Suite format: {site, profile?, cases:[{name, start, goal, observe?:[state], expect?:{status?, maxInteractions?, checkpoint?:{<state>:{repertoireContains?:[label], kinds?:{label:kind}, snapshotContains?:[substr]}}}}]}. `start`/`goal`/`observe` are state ids or bare semanticNames (from `dev graph-show --node <site>`). Execution: auth pre-flight ONCE (profile-status against the site\'s map — needs-login fails the WHOLE run fast with the login hint), then each case SERIALLY in a FRESH headless browser (reaped between). maxInteractions defaults 0 (pure autopilot); ANY needs-* beyond it fails the case with the pause payload as diagnostics. Output: {status:"ok"|"failed", passed, failed, cases:[{name, verdict, elapsedMs, failure?:{at, payload}}]}. Exit 0 all-pass · 3 any-fail · 2 bad suite/config. Progress → stderr. A worked example suite ships under packs/suites/.',
155
+ args: [],
156
+ flags: [
157
+ { name: '--suite', takesValue: true, description: 'Path to a *.suite.json file (the release cases).' },
158
+ { name: '--headless', takesValue: false, description: 'Run without a visible window (recommended — a suite opens a fresh browser per case).' },
159
+ ],
160
+ example: 'webnav test --suite packs/suites/mysite.suite.json --headless',
161
+ },
162
+ {
163
+ name: 'navigate', group: 'navigate',
164
+ summary: 'Open a URL in a session browser; records a landing observation if the session is recording. If the settled landing classifies as an SSO/login wall (foreign-host bounce, interstitial, or a password field — checked against the target site\'s own map), the JSON output adds `authWall: true, loginUrl`. NEVER auto-retried here (a recording captures what actually happened, judgment-free) — that\'s `walk`\'s job.',
165
+ args: [{ name: 'url', required: true, description: 'URL to open.' }],
166
+ flags: [
167
+ { name: '--session', takesValue: true, description: 'Session id (browser + record buffer; from `dev record-start`).' },
168
+ ...BROWSER_FLAGS,
169
+ ],
170
+ example: 'webnav use navigate https://www.saucedemo.com --session sd1 --headed',
171
+ },
172
+ {
173
+ name: 'snapshot', group: 'navigate',
174
+ summary: 'Return the current page snapshot as JSON {snapshot: "<yaml>"} (read refs from .snapshot to act on). Never records.',
175
+ args: [],
176
+ flags: [{ name: '--session', takesValue: true, description: 'Session id whose live browser to snapshot.' }],
177
+ example: 'webnav use snapshot --session sd1',
178
+ },
179
+ {
180
+ name: 'click', group: 'navigate',
181
+ summary: 'Click an element by ref (from `use snapshot`); records the before/after action-effect if recording.',
182
+ args: [{ name: 'ref', required: true, description: 'Element ref from a prior `use snapshot`.' }],
183
+ flags: [{ name: '--session', takesValue: true, description: 'Session id (browser + record buffer).' }],
184
+ example: 'webnav use click e42 --session sd1',
185
+ },
186
+ {
187
+ name: 'type', group: 'navigate',
188
+ summary: 'Type text into a field by ref (from `use snapshot`); records the action-effect if recording.',
189
+ args: [
190
+ { name: 'ref', required: true, description: 'Field ref from a prior `use snapshot`.' },
191
+ { name: 'text', required: true, description: 'Text to type into the field.' },
192
+ ],
193
+ flags: [{ name: '--session', takesValue: true, description: 'Session id (browser + record buffer).' }],
194
+ example: 'webnav use type e1 standard_user --session sd1',
195
+ },
196
+ ];
197
+ export const DEV_COMMANDS = [
198
+ {
199
+ name: 'list',
200
+ summary: 'List the sites webnav has a map for, with each site\'s state count. The index of your maps; inspect one with `dev graph-show` / `dev outline`.',
201
+ args: [],
202
+ flags: [],
203
+ example: 'webnav dev list',
204
+ },
205
+ {
206
+ name: 'node-add',
207
+ summary: 'Teach webnav a new site: its id and home url.',
208
+ args: [
209
+ { name: 'id', required: true, description: 'Node id (e.g. npmjs.com) — also the skeleton namespace.' },
210
+ ],
211
+ flags: [
212
+ { name: '--url', takesValue: true, description: 'Entry/home URL for the site.' },
213
+ ],
214
+ example: 'webnav dev node-add npmjs.com --url https://www.npmjs.com',
215
+ },
216
+ {
217
+ name: 'capture',
218
+ summary: 'Dev helper: open a URL and save its snapshot YAML to a file (for test fixtures).',
219
+ args: [
220
+ { name: 'url', required: true, description: 'URL to open and snapshot.' },
221
+ { name: 'out', required: true, description: 'Output file path for the snapshot YAML.' },
222
+ ],
223
+ flags: [],
224
+ example: 'webnav capture https://github.com out.yml',
225
+ },
226
+ {
227
+ name: 'record-live',
228
+ summary: 'Record a site by BROWSING it yourself: opens a headed browser you click through; every action is captured (real-a11y snapshots) into the record buffer. Stop with `dev record-stop --session S` (or Ctrl-C), then `dev graph-analyse --session <S> --draft`. Secret rule: typed values are never recorded.',
229
+ args: [],
230
+ flags: [
231
+ { name: '--session', takesValue: true, description: 'Record session id (also the browser session).' },
232
+ { name: '--url', takesValue: true, description: 'Where the recording starts (the site to map).' },
233
+ { name: '--interval', takesValue: true, description: 'Poll interval ms (default 500).' },
234
+ ],
235
+ example: 'webnav dev record-live --session map-1 --url https://www.saucedemo.com',
236
+ },
237
+ {
238
+ name: 'record-start',
239
+ summary: 'Begin a record session: pages you browse via webnav are captured for mapping.',
240
+ args: [],
241
+ flags: [{ name: '--session', takesValue: true, description: 'Record session id (defaults to a generated one).' }],
242
+ example: 'webnav dev record-start --session map-1',
243
+ },
244
+ {
245
+ name: 'record-stop',
246
+ summary: 'End a record session (stop capturing pages).',
247
+ args: [],
248
+ flags: [{ name: '--session', takesValue: true, description: 'Record session id from `dev record-start`.' }],
249
+ example: 'webnav dev record-stop --session map-1',
250
+ },
251
+ {
252
+ name: 'graph-analyse',
253
+ summary: 'Mechanically derive a per-site navigation structure from one OR MORE record sessions. Default: raw observations (data only). With --draft: a ready, SELF-VERIFIED {node,states,edges} graph-edit spec (absolute URLs, uniqueness fingerprints, resolvable edges, login wired) — one logical page = one state (in-page tabs/search/sort do NOT split it), and multiple sessions of the same site MERGE into one map. A state seen only once is marked `provisional` (identity unconfirmed) and its `receipt.requests` lists what to record again to confirm it — check requests, drive those states once more, re-draft. A synthetic `_shell` state (role shell) carries the site-wide chrome (nav/header/footer) as from-anywhere edges, not a page. Repeated widget/card/row subtrees (e.g. dashboard chart widgets, table rows) fold into single `scope:\'widget\'|\'row\'` template affordances, and personalized pages built from the same widget templates merge on that template identity rather than staying separate per instance. APPROVAL GATE: only sessions whose capture-review PASSED (`dev review`) are built from; a failed or never-reviewed session is excluded (reported as excludedUnverified) so the map is never trained on an untrusted capture. Drive a site, review each session until it passes, then `--draft` and pipe to graph-edit. THE EXTENSION LOOP: --draft also reports `unknowns` — everything the core\'s structural inference honestly could not resolve (an undetected overlay, a degenerate/ambiguous landing, an affordance self-verify flagged, or a pattern pack disabled for over-excising a page — each with capped evidence + context + the extensionPoint it plugs into). Feed an unknown to `dev pattern-propose --from-unknown <this-output>#<index> --name <slug>` to scaffold a declarative pattern-pack entry in `packs/patterns/proposed/`; once it lints clean it applies locally on the next --draft, and a PR upstream shares it (see docs/EXTENDING.md). Packs are DATA — never hand-patch this engine\'s code for a site-specific gap.',
254
+ args: [],
255
+ flags: [
256
+ { name: '--session', takesValue: true, description: 'Record session id. REPEATABLE — pass several to merge multiple drives of one site into one draft.' },
257
+ { name: '--host', takesValue: true, description: 'Auto-include EVERY recorded session for this host (e.g. www.saucedemo.com) — drafts the whole site from all its drives at once.' },
258
+ { name: '--draft', takesValue: false, description: 'Emit a ready-to-edit graph spec (states+affordances) instead of raw observations; `_warning` flags anything the self-verify found shaky.' },
259
+ { name: '--skip-review-gate', takesValue: false, description: 'Build from ALL given sessions even if unreviewed/failed (bypasses the approval gate — for a deliberate raw build).' },
260
+ ],
261
+ example: 'webnav dev graph-analyse --host www.saucedemo.com --draft # or: --session a --session b',
262
+ },
263
+ {
264
+ name: 'pattern-propose',
265
+ summary: 'THE EXTENSION LOOP, step 2: turn ONE entry from a `graph-analyse --draft` JSON\'s `unknowns[]` array into a scaffolded declarative pattern-pack proposal at `packs/patterns/proposed/<name>.json` — never a hand-patch to core code (see docs/EXTENDING.md for the full process). Reads the unknown\'s evidence as the embedded fixture + a TODO trigger (empty `contains`, context inferred from the unknown\'s extensionPoint: overlay-open -> diff.added, value-domain -> overlay), writes the file, and prints a checklist (fill trigger -> `--lint` -> re-run graph-analyse and confirm the unknown is GONE -> add a grammar fixture test -> the `gh pr create` command to upstream it, generated with a body citing the evidence — printed only, NEVER auto-run). An unknown whose extensionPoint is `core-design` (no pack TYPE can express the fix) is DECLINED with the reason instead of scaffolded — file a core-design issue with its evidence, never invent a third pack type. Exits 0 on a successful scaffold even though the TODO trigger fails lint by design (that failure is reported IN the JSON, not as a process error).',
266
+ args: [],
267
+ flags: [
268
+ { name: '--from-unknown', takesValue: true, description: '"<path to a graph-analyse --draft JSON file>#<index into its unknowns[] array>", e.g. draft.json#3.' },
269
+ { name: '--name', takesValue: true, description: 'Kebab-case slug for the new pack (becomes packs/patterns/proposed/<name>.json and the pack\'s "name" field).' },
270
+ { name: '--lint', takesValue: true, description: 'Re-check path (no standalone pattern-lint verb): re-run lint on an existing pack FILE (after you\'ve filled in the trigger) instead of scaffolding a new one.' },
271
+ ],
272
+ example: 'webnav dev pattern-propose --from-unknown draft.json#3 --name date-picker-divsoup # then: webnav dev pattern-propose --lint packs/patterns/proposed/date-picker-divsoup.json',
273
+ },
274
+ {
275
+ name: 'graph-edit',
276
+ summary: 'Upsert a validated navigation graph into a site-node interior (creates the node if new). Accepts graph-analyse --draft output as-is, incl. a state\'s `provisional` (tri-state: omit key = keep prior, null = confirmed/clear, a string = still-provisional with that note) and an affordance\'s `scope: \'row\'` (a folded per-row repeat, e.g. one "edit" button standing for all table rows).',
277
+ args: [],
278
+ flags: [
279
+ { name: '--node', takesValue: true, description: 'Site-node id (host), e.g. github.com.' },
280
+ { name: '--graph', takesValue: true, description: 'JSON {states:[{label,urlPattern?,fingerprint?}], edges:[{from,to,via,needsInput?,why?}]}.' },
281
+ ],
282
+ example: 'webnav dev graph-edit --node github.com --graph \'{"states":[],"edges":[]}\'',
283
+ },
284
+ {
285
+ name: 'graph-show',
286
+ summary: 'Read a site-node\'s persisted navigation skeleton (states + edges).',
287
+ args: [],
288
+ flags: [{ name: '--node', takesValue: true, description: 'Site-node id (host), e.g. github.com.' }],
289
+ example: 'webnav dev graph-show --node github.com',
290
+ },
291
+ {
292
+ name: 'node-clear',
293
+ summary: 'Wipe a site-node\'s INTERIOR (its states + edges) so the site can be RE-LEARNED from scratch through webnav. The node row itself stays. Use before re-recording a site whose map is stale/wrong — never hand-edit the DB.',
294
+ args: [],
295
+ flags: [{ name: '--node', takesValue: true, description: 'Site-node id (host) to clear, e.g. github.com.' }],
296
+ example: 'webnav dev node-clear --node www.saucedemo.com',
297
+ },
298
+ {
299
+ name: 'node-rm',
300
+ summary: 'Fully DELETE a site-node — its states, edges, node-edges, AND the node row itself — so a stale or empty site disappears entirely (e.g. from the dashboard). Stronger than node-clear (which keeps the row). Never hand-edit the DB.',
301
+ args: [],
302
+ flags: [{ name: '--node', takesValue: true, description: 'Site-node id (host) to delete, e.g. pypi.org.' }],
303
+ example: 'webnav dev node-rm --node pypi.org',
304
+ },
305
+ {
306
+ name: 'export-map',
307
+ summary: 'Emit a site\'s full map pack {node, states} as JSON (skeleton only — never credentials). The unit a hosted service publishes; pipe it wherever maps are shared.',
308
+ args: [{ name: 'site', required: true, description: 'Site-node id (host), e.g. www.saucedemo.com. Also accepts --node.' }],
309
+ flags: [{ name: '--node', takesValue: true, description: 'Site-node id (alternative to the positional).' }],
310
+ example: 'webnav dev export-map www.saucedemo.com > saucedemo-map.json',
311
+ },
312
+ {
313
+ name: 'import-map',
314
+ summary: 'Load a map pack (the JSON `export-map` emits) into your local map, so you can `walk` a site someone else mapped WITHOUT re-learning it. Pure skeleton — set the login creds separately with `creds set`.',
315
+ args: [{ name: 'file', required: true, description: 'Path to a map-pack JSON file. Also accepts --file.' }],
316
+ flags: [{ name: '--file', takesValue: true, description: 'Map-pack path (alternative to the positional).' }],
317
+ example: 'webnav dev import-map mappacks/saucedemo.mappack.json',
318
+ },
319
+ {
320
+ name: 'outline',
321
+ summary: 'Human-scannable text outline of a site\'s interior (states + typed affordances), with completeness cues: unexplored exits, dead-ends, orphans, per-kind counts. Answers "did we capture everything?" without the UI.',
322
+ args: [{ name: 'site', required: true, description: 'Site-node id (host), e.g. www.saucedemo.com. Also accepts --node.' }],
323
+ flags: [{ name: '--node', takesValue: true, description: 'Site-node id (alternative to the positional).' }],
324
+ example: 'webnav dev outline www.saucedemo.com',
325
+ },
326
+ {
327
+ name: 'mermaid',
328
+ summary: 'Emit a Mermaid stateDiagram-v2 of a site\'s interior — paste into GitHub/markdown/mermaid.live to render a diagram (no React Flow). Unexplored exits → an "unexplored" sink; dead-ends marked terminal.',
329
+ args: [{ name: 'site', required: true, description: 'Site-node id (host), e.g. www.saucedemo.com. Also accepts --node.' }],
330
+ flags: [{ name: '--node', takesValue: true, description: 'Site-node id (alternative to the positional).' }],
331
+ example: 'webnav dev mermaid www.saucedemo.com',
332
+ },
333
+ {
334
+ name: 'frontier',
335
+ summary: 'Report a map\'s UNEXPLORED FRONTIER — the declared affordances the map recorded an opener for but NEVER followed to a resolved state. This is how you know a walkthrough is COMPLETE: drive the frontier to empty (minus your hard exclusions) and the site is mapped; don\'t guess by eyeballing. Three item kinds, all from stored data (zero LLM): `dangling-target` (a navigate/reveal with no toState — opener seen, destination never captured), `unopened-panel` (a reveal whose children were never recorded), `ambiguous-action` (a mutate/input whose label does NOT match a known in-place shape — sort/refresh/pagination/toggle/close/search/… — so it MIGHT open a new surface an explorer should verify, e.g. an account/model switcher). NOT flagged: resolved navigates, reveals with children, clearly in-place mutates, and per-row/widget scope templates (already generalized). Output: {status, node, total, frontier:[{state,kind,label,reason,hint}], excluded:[…], byState:{stateId:count}}. Since the map is judgment-free, webnav does NOT know your hard "never click" list — pass it yourself with repeatable `--exclude <label>` (case-insensitive substring); matched items move to `excluded[]` (visible, off the worklist) rather than being dropped. Exit 0 = frontier empty (fully explored) · 3 = frontier non-empty (ran fine, work remains).',
336
+ args: [{ name: 'site', required: false, description: 'Site-node id (host), e.g. www.saucedemo.com. Also accepts --node.' }],
337
+ flags: [
338
+ { name: '--node', takesValue: true, description: 'Site-node id (alternative to the positional).' },
339
+ { name: '--exclude', takesValue: true, description: 'A label to treat as a hard exclusion (repeatable). Case-insensitive substring; matched frontier items go to `excluded[]` instead of the worklist. Supply your own per-site "never click" list (e.g. Admin, a destructive toggle).' },
340
+ ],
341
+ example: 'webnav dev frontier www.saucedemo.com --exclude Admin --exclude "Reset App State"',
342
+ },
343
+ {
344
+ name: 'capture-loop',
345
+ summary: 'Self-improving capture loop: run --explore-cmd (drives one agent exploration of --objective, recording $WEBNAV_LOOP_SESSION), then a structured review audits video-vs-steps for capture gaps; repeats until a clean audit or exits 3 with the gaps to fix.',
346
+ args: [],
347
+ flags: [
348
+ { name: '--objective', takesValue: true, description: 'What the agent should explore (required).' },
349
+ { name: '--explore-cmd', takesValue: true, description: 'Command that drives ONE round via `use session` on $WEBNAV_LOOP_SESSION (required).' },
350
+ { name: '--max-rounds', takesValue: true, description: 'Max rounds before giving up (default 5).' },
351
+ { name: '--model', takesValue: true, description: 'Review model (default sonnet).' },
352
+ ],
353
+ example: 'webnav dev capture-loop --objective "explore reports" --explore-cmd "./explore.sh" --max-rounds 3',
354
+ },
355
+ {
356
+ name: 'effects',
357
+ summary: 'Dump a record session\'s RAW action-effects (full before/after snapshots + diff + navigated) as JSON — the unabridged data `graph-analyse` only summarizes.',
358
+ args: [],
359
+ flags: [{ name: '--session', takesValue: true, description: 'Record session id from `dev record-start`.' }],
360
+ example: 'webnav dev effects --session map-1',
361
+ },
362
+ {
363
+ name: 'record-rename',
364
+ summary: 'Rename a recording\'s id (across the session row + its observations) and move its on-disk video/review dirs — so a session reads as what it captures (e.g. reports-list) instead of an ad-hoc id (s1final). Refuses if the target id already exists.',
365
+ args: [],
366
+ flags: [
367
+ { name: '--from', takesValue: true, description: 'Current session id.' },
368
+ { name: '--to', takesValue: true, description: 'New session id (must not already exist).' },
369
+ ],
370
+ example: 'webnav dev record-rename --from s1final --to reports-list',
371
+ },
372
+ {
373
+ name: 'review',
374
+ summary: 'Audit a recorded session\'s VIDEO against its captured STEPS (Sonnet over ffmpeg frames) → capture gaps: on-screen changes with no recorded step. Writes an APPROVED verdict tag on the session when zero gaps (→ graph-ready) or needs-fix with the gap list (exit 3). The per-session gate: a session should be APPROVED before you build a graph from it.',
375
+ args: [{ name: 'session', required: false, description: 'Record session id (or pass --session).' }],
376
+ flags: [
377
+ { name: '--session', takesValue: true, description: 'Record session id to review.' },
378
+ { name: '--model', takesValue: true, description: 'Review model (default sonnet).' },
379
+ { name: '--instructions', takesValue: true, description: 'Override the audit task prose (advanced).' },
380
+ ],
381
+ example: 'webnav dev review --session s1v3',
382
+ },
383
+ {
384
+ name: 'verify',
385
+ summary: 'Check that the affordance element fingerprints of the state the --session browser is currently on resolve UNIQUELY against the live page. The live-page check graph-edit (offline) cannot do: matchState identifies the state, then each navigate/input affordance\'s elementFp is resolved. status done = all unique · non-unique = some collide (exit 3) · no-match = not on a known state.',
386
+ args: [],
387
+ flags: [
388
+ { name: '--node', takesValue: true, description: 'Site-node id whose states to match against (e.g. www.saucedemo.com).' },
389
+ { name: '--session', takesValue: true, description: 'A live browser session already ON the page to verify.' },
390
+ ],
391
+ example: 'webnav dev verify --node www.saucedemo.com --session sd1',
392
+ },
393
+ {
394
+ name: 'hover-probe',
395
+ summary: 'Reveal the HOVER / RIGHT-CLICK repertoire of the page the --session browser is currently on — the mega-menus and context menus that live in NO settled snapshot and so are otherwise omitted from the map (gap X2). An OPT-IN pass over a LIVE recording session: it snapshots the page, picks STRUCTURAL candidates (nodes with aria-haspopup, menuitems, and named interactive nodes inside a banner/navigation landmark — judgment-free, capped at --limit), hovers each, diffs what appears, and appends a reveal ActionEffect (marked hover) to the recording for any candidate that exposed new nodes. REVEAL ONLY — it never clicks anything inside a revealed menu (commit rule). --right-click switches to right-click (context menus, marked rightClick). The session MUST be recording (start it and drive it to the page first) — a non-recording session is refused, never a silent no-op. status done = something revealed · empty = nothing revealed (exit 3). Run it, then re-draft (dev graph-analyse --draft) to pick up the new reveal affordances.',
396
+ args: [],
397
+ flags: [
398
+ { name: '--session', takesValue: true, description: 'A live recording session already ON the page to probe (from dev record-start, driven to the page).' },
399
+ { name: '--limit', takesValue: true, default: '12', description: 'Max candidates to probe (default 12).' },
400
+ { name: '--right-click', takesValue: false, description: 'Right-click (context menus) instead of hover (mega-menus). Marks the effect rightClick.' },
401
+ ],
402
+ example: 'webnav dev hover-probe --session sd1 --limit 8',
403
+ },
404
+ {
405
+ name: 'profile-status',
406
+ summary: 'Evidence-based "is this profile still logged in for this site?" check — call BEFORE walking/recording an authed site instead of guessing. Opens ONE headless session under --profile, loads the site\'s map homeUrl (or --url), settles the landing, and classifies it against the site\'s own map fingerprints (the oracle: matchState) — valid (landed + matched a known state) | needs-login (foreign-host wall, interstitial/bot-wall, or a declared password field — includes loginUrl) | unknown (no map yet / ambiguous landing). The session is always reaped after. Exit 0 in all three cases — needs-login is a normal, useful answer, not a failure.',
407
+ args: [],
408
+ flags: [
409
+ { name: '--profile', takesValue: true, description: 'Named profile to check (shared login dir under ~/.webnav/profiles, or an absolute path).' },
410
+ { name: '--site', takesValue: true, description: 'Site-node id (host), e.g. www.saucedemo.com — its map supplies homeUrl + the fingerprints matchState checks against.' },
411
+ { name: '--url', takesValue: true, description: 'Override the entry url instead of the map\'s homeUrl (required if the site has no map yet).' },
412
+ ],
413
+ example: 'webnav dev profile-status --profile work --site www.saucedemo.com',
414
+ },
415
+ {
416
+ name: 'sessions',
417
+ summary: 'List or reap playwright-cli browser sessions. They are DAEMONIZED (survive the CLI exiting) so `use`/`walk-resume` can reattach — but a paused-and-abandoned walk or a stopped `use` exploration leaks a Chrome forever. `list` shows each session + age + whether its browser is still live; `reap` closes them.',
418
+ args: [{ name: 'sub', required: false, description: 'list (default) | reap' }],
419
+ flags: [
420
+ { name: '--all', takesValue: false, description: 'reap: close EVERY session (default closes only orphans whose browser already died).' },
421
+ { name: '--max-age-hours', takesValue: true, description: 'reap: also close LIVE sessions older than N hours (a TTL sweep).' },
422
+ ],
423
+ example: 'webnav dev sessions reap # close dead-browser orphans\nwebnav dev sessions reap --all # close everything\nwebnav dev sessions reap --max-age-hours 6 # also close live sessions older than 6h\nwebnav dev sessions list',
424
+ },
425
+ {
426
+ name: 'mcp',
427
+ summary: 'Serve every webnav verb as MCP tools over stdio (Model Context Protocol) — point an MCP client\'s command at `webnav mcp`. Tools are generated from this spec; each call runs the real CLI.',
428
+ args: [],
429
+ flags: [],
430
+ example: 'webnav mcp',
431
+ },
432
+ {
433
+ name: 'dashboard',
434
+ summary: 'Open a LOCAL operator UI (localhost only) to inspect which sites webnav has data for, view a site\'s JSON map, and manage stored credentials (grouped by category, masked with reveal + inline edit). Reads ./webnav.db + ~/.webnav/credentials.json; the only writes are credential set/remove/recategorize (chmod 600).',
435
+ args: [],
436
+ flags: [
437
+ { name: '--port', takesValue: true, default: '7777', description: 'Port to bind on 127.0.0.1 (or set WEBNAV_PORT). The dashboard runs until Ctrl-C.' },
438
+ { name: '--open', takesValue: false, description: 'Open the dashboard URL in a browser tab on start (macOS). Off by default — the URL is printed to click.' },
439
+ ],
440
+ example: 'webnav dev dashboard --port 7777',
441
+ },
442
+ {
443
+ name: 'ingest',
444
+ summary: 'Run a localhost receiver that turns human-recorded browser sessions into map data. Starts an HTTP server on --port (default 7778); the webnav-extension Chrome extension POSTs recorded steps to POST /ingest — they land in webnav.db as ActionEffects, identical to agent-recorded ones. Then use `dev graph-analyse --session <id> --draft`. Runs until Ctrl-C.',
445
+ args: [],
446
+ flags: [{ name: '--port', takesValue: true, default: '7778', description: 'Localhost port to listen on.' }],
447
+ example: 'webnav dev ingest --port 7778',
448
+ },
449
+ {
450
+ name: 'agent-serve',
451
+ summary: 'Run the local agent server the webnav-extension sidePanel talks to. Starts an HTTP server on --port (default 7779): GET /api/agent/events streams SSE {type:turn|action|done|error|plan} events (last connection wins — opening a second panel evicts the first); POST /api/agent/goal {goal,sessionId,mode} starts a run; POST /api/agent/command-result {id,result} resolves a pending action command the server emitted; POST /api/agent/stop aborts the current run; POST /ingest-ax lands a live run as ActionEffects (same path as `dev ingest`). Runs until Ctrl-C.',
452
+ args: [],
453
+ flags: [
454
+ { name: '--port', takesValue: true, default: '7779', description: 'Localhost port to listen on.' },
455
+ { name: '--token', takesValue: true, description: 'Pin the auth token to a fixed hex string, stable across restarts (paste into the panel once). Omit to get a fresh random token every run (the old behavior).' },
456
+ ],
457
+ example: 'webnav dev agent-serve --port 7779 --token deadbeef1234',
458
+ },
459
+ ];
460
+ export const COMMANDS = [...CONSUMER_COMMANDS, ...DEV_COMMANDS];