@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,20 @@
1
+ import type { SiteNode, State } from './mapstore/types.js';
2
+ export type { SiteNode, State, Affordance, AffordanceKind, StateRole, EdgeKind, DeclaredShadow, } from './mapstore/types.js';
3
+ /** One site's full skeleton. This is what `webnav dev export-map` emits, what
4
+ * the site's `publish-map` uploads, and what `GET /api/maps/:site` returns. */
5
+ export interface MapPack {
6
+ node: SiteNode;
7
+ states: State[];
8
+ }
9
+ /** Request header carrying the API key on hosted-route calls. */
10
+ export declare const WEBNAV_KEY_HEADER = "X-Webnav-Key";
11
+ /** Path of the metered map-fetch endpoint for a site id (the SiteNode id,
12
+ * e.g. 'www.saucedemo.com'). */
13
+ export declare function mapFetchPath(site: string): string;
14
+ /** Non-200 statuses the map-fetch endpoint returns:
15
+ * 401 missing/invalid/revoked/expired key · 404 no shared map · 429 quota. */
16
+ export type MapFetchErrorStatus = 401 | 404 | 429;
17
+ /** Body of every non-200 map-fetch response. */
18
+ export interface MapFetchError {
19
+ error: string;
20
+ }
@@ -0,0 +1,15 @@
1
+ // The CLI <-> hosted-service CONTRACT — single source of truth for every shape
2
+ // that crosses the wire between the open-source CLI (this repo) and the hosted
3
+ // shared-knowledge service (webnav-site). The site imports this module
4
+ // types-only from `@dikshanty94/webnav/contract`; nothing here may import
5
+ // runtime code, so consuming it never pulls in better-sqlite3 etc.
6
+ //
7
+ // HARD INVARIANT (CLAUDE.md): the wire carries the MAP SKELETON ONLY —
8
+ // site navigation structure. It never carries credentials.
9
+ /** Request header carrying the API key on hosted-route calls. */
10
+ export const WEBNAV_KEY_HEADER = 'X-Webnav-Key';
11
+ /** Path of the metered map-fetch endpoint for a site id (the SiteNode id,
12
+ * e.g. 'www.saucedemo.com'). */
13
+ export function mapFetchPath(site) {
14
+ return `/api/maps/${encodeURIComponent(site)}`;
15
+ }
@@ -0,0 +1,46 @@
1
+ export type CredCategory = 'login' | 'personal' | 'other';
2
+ export interface CredEntry {
3
+ value: string;
4
+ category: CredCategory;
5
+ }
6
+ export type SiteCreds = Record<string, string>;
7
+ export type SiteCredsDetailed = Record<string, CredEntry>;
8
+ export declare function credsPath(): string;
9
+ export declare function categorize(key: string): CredCategory;
10
+ export declare class CredStore {
11
+ private path;
12
+ constructor(path?: string);
13
+ private read;
14
+ private write;
15
+ /** All creds for a site as a flat slot→value map (the walk fills inputs from
16
+ * this — the contract the walk depends on, unchanged). Legacy + new shapes
17
+ * both flatten to value strings here. */
18
+ get(site: string): SiteCreds;
19
+ /** All creds for a site WITH categories (for the dashboard). */
20
+ getDetailed(site: string): SiteCredsDetailed;
21
+ /** Merge key/values into a site's creds (existing keys overwritten). Category
22
+ * is inferred per key unless `category` is supplied (applies to all keys set
23
+ * in this call). Returns the site's key names after the merge. */
24
+ set(site: string, values: SiteCreds, category?: CredCategory): string[];
25
+ /** Change just the CATEGORY of an existing key (value untouched). Returns true
26
+ * if the key existed. */
27
+ setCategory(site: string, key: string, category: CredCategory): boolean;
28
+ /** Remove a whole site, or just one key when `key` is given. Returns true if
29
+ * anything was removed. */
30
+ remove(site: string, key?: string): boolean;
31
+ /** Sites + their KEY names only — never the values (for `creds list`). Shape
32
+ * unchanged so the CLI JSON contract stays stable. */
33
+ list(): {
34
+ site: string;
35
+ keys: string[];
36
+ }[];
37
+ /** Like list() but with each key's CATEGORY (still never the values). Lets the
38
+ * dashboard group by category without revealing anything. */
39
+ listDetailed(): {
40
+ site: string;
41
+ keys: {
42
+ name: string;
43
+ category: CredCategory;
44
+ }[];
45
+ }[];
46
+ }
package/dist/creds.js ADDED
@@ -0,0 +1,126 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join, dirname } from 'node:path';
4
+ export function credsPath() {
5
+ return process.env.WEBNAV_CREDS ?? join(homedir(), '.webnav', 'credentials.json');
6
+ }
7
+ // Infer a credential's category from its key name. Conservative: anything not
8
+ // recognized as a login secret or a piece of personal info falls back to 'other'.
9
+ const LOGIN_KEYS = /^(user(name)?|login|email|e-?mail|pass(word|wd)?|pin|otp|token|secret|api[-_]?key)$/i;
10
+ const PERSONAL_KEYS = /^(first[-_]?name|last[-_]?name|full[-_]?name|name|phone|mobile|tel|address|street|city|state|country|zip|postal[-_]?code|postcode|dob|birth|gender|company)$/i;
11
+ export function categorize(key) {
12
+ if (LOGIN_KEYS.test(key))
13
+ return 'login';
14
+ if (PERSONAL_KEYS.test(key))
15
+ return 'personal';
16
+ return 'other';
17
+ }
18
+ // Normalize a stored entry (legacy string OR object) to a full CredEntry.
19
+ function normalize(key, e) {
20
+ if (typeof e === 'string')
21
+ return { value: e, category: categorize(key) };
22
+ return { value: e.value, category: e.category ?? categorize(key) };
23
+ }
24
+ export class CredStore {
25
+ path;
26
+ constructor(path = credsPath()) { this.path = path; }
27
+ read() {
28
+ if (!existsSync(this.path))
29
+ return {};
30
+ try {
31
+ return JSON.parse(readFileSync(this.path, 'utf8'));
32
+ }
33
+ catch {
34
+ return {};
35
+ }
36
+ }
37
+ write(data) {
38
+ const dir = dirname(this.path);
39
+ if (!existsSync(dir))
40
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
41
+ writeFileSync(this.path, JSON.stringify(data, null, 2), { mode: 0o600 });
42
+ // Re-assert 0600 in case the file already existed with looser perms.
43
+ try {
44
+ chmodSync(this.path, 0o600);
45
+ }
46
+ catch { /* best-effort */ }
47
+ }
48
+ /** All creds for a site as a flat slot→value map (the walk fills inputs from
49
+ * this — the contract the walk depends on, unchanged). Legacy + new shapes
50
+ * both flatten to value strings here. */
51
+ get(site) {
52
+ const stored = this.read()[site] ?? {};
53
+ const out = {};
54
+ for (const [k, e] of Object.entries(stored))
55
+ out[k] = normalize(k, e).value;
56
+ return out;
57
+ }
58
+ /** All creds for a site WITH categories (for the dashboard). */
59
+ getDetailed(site) {
60
+ const stored = this.read()[site] ?? {};
61
+ const out = {};
62
+ for (const [k, e] of Object.entries(stored))
63
+ out[k] = normalize(k, e);
64
+ return out;
65
+ }
66
+ /** Merge key/values into a site's creds (existing keys overwritten). Category
67
+ * is inferred per key unless `category` is supplied (applies to all keys set
68
+ * in this call). Returns the site's key names after the merge. */
69
+ set(site, values, category) {
70
+ const data = this.read();
71
+ const site_creds = { ...(data[site] ?? {}) };
72
+ for (const [k, v] of Object.entries(values)) {
73
+ // Preserve an existing key's category on a value-only update unless told otherwise.
74
+ const existing = site_creds[k] ? normalize(k, site_creds[k]).category : undefined;
75
+ site_creds[k] = { value: v, category: category ?? existing ?? categorize(k) };
76
+ }
77
+ data[site] = site_creds;
78
+ this.write(data);
79
+ return Object.keys(data[site]);
80
+ }
81
+ /** Change just the CATEGORY of an existing key (value untouched). Returns true
82
+ * if the key existed. */
83
+ setCategory(site, key, category) {
84
+ const data = this.read();
85
+ if (!data[site] || !(key in data[site]))
86
+ return false;
87
+ const { value } = normalize(key, data[site][key]);
88
+ data[site][key] = { value, category };
89
+ this.write(data);
90
+ return true;
91
+ }
92
+ /** Remove a whole site, or just one key when `key` is given. Returns true if
93
+ * anything was removed. */
94
+ remove(site, key) {
95
+ const data = this.read();
96
+ if (!data[site])
97
+ return false;
98
+ if (key) {
99
+ if (!(key in data[site]))
100
+ return false;
101
+ delete data[site][key];
102
+ if (Object.keys(data[site]).length === 0)
103
+ delete data[site];
104
+ }
105
+ else {
106
+ delete data[site];
107
+ }
108
+ this.write(data);
109
+ return true;
110
+ }
111
+ /** Sites + their KEY names only — never the values (for `creds list`). Shape
112
+ * unchanged so the CLI JSON contract stays stable. */
113
+ list() {
114
+ const data = this.read();
115
+ return Object.keys(data).sort().map((site) => ({ site, keys: Object.keys(data[site]).sort() }));
116
+ }
117
+ /** Like list() but with each key's CATEGORY (still never the values). Lets the
118
+ * dashboard group by category without revealing anything. */
119
+ listDetailed() {
120
+ const data = this.read();
121
+ return Object.keys(data).sort().map((site) => ({
122
+ site,
123
+ keys: Object.keys(data[site]).sort().map((name) => ({ name, category: normalize(name, data[site][name]).category })),
124
+ }));
125
+ }
126
+ }
@@ -0,0 +1,137 @@
1
+ import { type Server } from 'node:http';
2
+ import type { IMapStore } from '../mapstore/store.js';
3
+ import type { CredStore } from '../creds.js';
4
+ import type { RecordSessionInfo } from '../mapstore/record.js';
5
+ import type { ReplayState } from '../recorder/replay.js';
6
+ /**
7
+ * webnav's local OPERATOR dashboard — a single long-lived process that lets a
8
+ * human inspect what webnav knows (sites + their JSON map) and manage the login
9
+ * credentials webnav uses. Bind 127.0.0.1 ONLY (single user, no auth/CORS): this
10
+ * is a local tool and credential plaintext must never leave the machine. The
11
+ * server reads SQLite (via MapStore) and the creds file (via CredStore); the only
12
+ * writes are credential set/remove/recategorize, which go through CredStore (chmod 600).
13
+ */
14
+ export interface DashboardOpts {
15
+ port?: number;
16
+ }
17
+ /**
18
+ * Human-session recording + replay, injected so the dashboard doesn't hard-depend
19
+ * on the recorder/playwright stack. Absent (undefined) → recordings routes 503.
20
+ */
21
+ export interface RecordingsDeps {
22
+ list(): RecordSessionInfo[] | Promise<RecordSessionInfo[]>;
23
+ steps(id: string): {
24
+ seq: number;
25
+ label: string;
26
+ kind: string;
27
+ fromUrl: string;
28
+ toUrl: string;
29
+ value?: string;
30
+ capturedAt: number;
31
+ }[];
32
+ del(id: string): void;
33
+ draft(id: string): unknown;
34
+ open(url: string, session: string, persistent: boolean, armedOnly?: boolean, profile?: string): Promise<{
35
+ ok: true;
36
+ } | {
37
+ ok: false;
38
+ error: string;
39
+ }>;
40
+ record(id: string): boolean;
41
+ stop(id: string): boolean;
42
+ replay(id: string, mode?: 'steps' | 'ledger'): Promise<{
43
+ ok: true;
44
+ } | {
45
+ ok: false;
46
+ error: string;
47
+ }>;
48
+ events(id: string): unknown;
49
+ replayState(): ReplayState | null;
50
+ replayControl(action: string, payload: {
51
+ value?: string;
52
+ save?: boolean;
53
+ fire?: boolean;
54
+ }): boolean;
55
+ shotPath(session: string, file: string): string | null;
56
+ toggle(id: string, desired?: boolean): {
57
+ recording: boolean;
58
+ };
59
+ videos(id: string): string[];
60
+ videoPath(session: string, file: string): string | null;
61
+ subscribe(cb: (type: string) => void): () => void;
62
+ activeWindow(): string | null;
63
+ logs(): {
64
+ now: number;
65
+ lines: {
66
+ t: number;
67
+ line: string;
68
+ }[];
69
+ };
70
+ notify(kind: string, line?: string): void;
71
+ review(id: string, opts?: {
72
+ model?: string;
73
+ instructions?: string;
74
+ }): {
75
+ ok: boolean;
76
+ error?: string;
77
+ };
78
+ reviewReport(id: string): {
79
+ report: string;
80
+ at: number;
81
+ verdict?: {
82
+ approved: boolean;
83
+ gaps: number;
84
+ reason?: string;
85
+ } | null;
86
+ } | null;
87
+ reviewRunning(): string | null;
88
+ reviewConfig(): {
89
+ model: string;
90
+ instructions: string;
91
+ };
92
+ profiles(): {
93
+ name: string;
94
+ site: string | null;
95
+ sessions: number;
96
+ sizeMb: number;
97
+ lastUsed: number;
98
+ open: boolean;
99
+ status?: {
100
+ auth: 'valid' | 'needs-login' | 'unknown';
101
+ loginUrl?: string;
102
+ checkedAt: string;
103
+ };
104
+ }[];
105
+ profileNew(name: string): {
106
+ ok: boolean;
107
+ error?: string;
108
+ };
109
+ profileOpen(name: string): Promise<{
110
+ ok: true;
111
+ } | {
112
+ ok: false;
113
+ error: string;
114
+ }>;
115
+ profileRename(from: string, to: string): {
116
+ ok: boolean;
117
+ error?: string;
118
+ };
119
+ profileDelete(name: string): {
120
+ ok: boolean;
121
+ };
122
+ profileStatus(name: string, site: string): Promise<{
123
+ ok: true;
124
+ auth: 'valid' | 'needs-login' | 'unknown';
125
+ loginUrl?: string;
126
+ checkedAt: string;
127
+ } | {
128
+ ok: false;
129
+ error: string;
130
+ }>;
131
+ profileReset(name: string): {
132
+ ok: boolean;
133
+ error?: string;
134
+ };
135
+ reviewFramePath(session: string, file: string): string | null;
136
+ }
137
+ export declare function startDashboard(store: IMapStore, creds: CredStore, opts?: DashboardOpts, rec?: RecordingsDeps): Server;