@iamu-dev/iamu 0.2.0-beta.9 → 0.3.0-beta.1
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/dist/adapters/claude-plugin.d.ts +13 -0
- package/dist/adapters/registry.d.ts +6 -0
- package/dist/adapters/types.d.ts +19 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +5965 -1807
- package/dist/build-info.d.ts +53 -0
- package/dist/cloud-url.d.ts +19 -0
- package/dist/commands/auth-http.d.ts +37 -0
- package/dist/commands/auth-project-choice.d.ts +74 -0
- package/dist/commands/auth-token-input.d.ts +21 -0
- package/dist/commands/auth.d.ts +84 -0
- package/dist/commands/browse-actions.d.ts +61 -0
- package/dist/commands/browse-driver.d.ts +121 -0
- package/dist/commands/browse.d.ts +195 -0
- package/dist/commands/cloud-push.d.ts +27 -0
- package/dist/commands/complete.d.ts +132 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/context.d.ts +81 -0
- package/dist/commands/credentials.d.ts +144 -0
- package/dist/commands/diff-filter.d.ts +11 -0
- package/dist/commands/doctor.d.ts +33 -0
- package/dist/commands/edit-entity.d.ts +8 -0
- package/dist/commands/evaluate.d.ts +34 -0
- package/dist/commands/explore-crawl.d.ts +67 -0
- package/dist/commands/explore.d.ts +53 -0
- package/dist/commands/findings.d.ts +7 -0
- package/dist/commands/init.d.ts +27 -0
- package/dist/commands/instructions-walk.d.ts +69 -0
- package/dist/commands/instructions.d.ts +30 -0
- package/dist/commands/list.d.ts +5 -0
- package/dist/commands/live-run-client.d.ts +127 -0
- package/dist/commands/match.d.ts +15 -0
- package/dist/commands/mcp-config.d.ts +10 -0
- package/dist/commands/new-entity.d.ts +34 -0
- package/dist/commands/new-finding.d.ts +25 -0
- package/dist/commands/open.d.ts +8 -0
- package/dist/commands/port.d.ts +17 -0
- package/dist/commands/preflight.d.ts +26 -0
- package/dist/commands/prune.d.ts +11 -0
- package/dist/commands/pull.d.ts +5 -0
- package/dist/commands/register-cloud.d.ts +5 -0
- package/dist/commands/register-findings.d.ts +5 -0
- package/dist/commands/register-project.d.ts +5 -0
- package/dist/commands/register-walkthrough.d.ts +5 -0
- package/dist/commands/register.d.ts +10 -0
- package/dist/commands/report.d.ts +3 -0
- package/dist/commands/serve.d.ts +11 -0
- package/dist/commands/set-entity.d.ts +73 -0
- package/dist/commands/skills.d.ts +34 -0
- package/dist/commands/start-run.d.ts +56 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/step-numbering.d.ts +87 -0
- package/dist/commands/step.d.ts +96 -0
- package/dist/commands/summary.d.ts +20 -0
- package/dist/commands/sync.d.ts +6 -0
- package/dist/commands/triage.d.ts +21 -0
- package/dist/commands/update.d.ts +4 -0
- package/dist/commands/validate.d.ts +11 -0
- package/dist/commands/view-results.d.ts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +5894 -1782
- package/dist/sync-mcp/fatal.d.ts +42 -0
- package/dist/sync-mcp/server.d.ts +1 -0
- package/dist/sync-mcp/tools.d.ts +12 -0
- package/dist/templates/prompts/walk.md +62 -37
- package/dist/templates/skills/iamu-findings.md +141 -44
- package/dist/templates/skills/iamu-new.md +106 -60
- package/dist/templates/skills/iamu-propose.md +36 -25
- package/dist/templates/skills/iamu-run.md +303 -124
- package/dist/templates/skills/iamu-setup.md +256 -181
- package/dist/templates/skills/iamu-stage.md +164 -0
- package/dist/templates/skills/iamu-status.md +16 -11
- package/dist/templates.d.ts +10 -0
- package/dist/ui/journey-diff.d.ts +3 -0
- package/dist/ui/json.d.ts +6 -0
- package/dist/ui/logger.d.ts +5 -0
- package/dist/ui/panic.d.ts +11 -0
- package/dist/ui/symbols.d.ts +11 -0
- package/dist/ui/table.d.ts +20 -0
- package/dist/ui/update-check.d.ts +6 -0
- package/dist/worktree-state.d.ts +49 -0
- package/package.json +6 -6
- package/dist/eval/gemini-eval-cli.js +0 -976
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type BuildMetadata } from "@iamu-dev/protocol";
|
|
2
|
+
import { type WorktreeDetector } from "./worktree-state.js";
|
|
3
|
+
/** Environment variables, as `process.env` holds them. */
|
|
4
|
+
export type Env = Record<string, string | undefined>;
|
|
5
|
+
/** Runs `git <args>` in `cwd`; returns trimmed stdout or null. */
|
|
6
|
+
export type GitRunner = (args: string[], cwd: string) => string | null;
|
|
7
|
+
/** `git` via execFile, silent and bounded; null when git is absent or fails. */
|
|
8
|
+
export declare const runGit: GitRunner;
|
|
9
|
+
interface CiInfo {
|
|
10
|
+
git_sha?: string;
|
|
11
|
+
git_branch?: string;
|
|
12
|
+
base_sha?: string;
|
|
13
|
+
ci?: {
|
|
14
|
+
provider: string;
|
|
15
|
+
run_url: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* What the CI environment says about the build. Recognizes GitHub
|
|
20
|
+
* Actions and Azure Pipelines; an empty object anywhere else.
|
|
21
|
+
*/
|
|
22
|
+
export declare function detectCi(env: Env): CiInfo;
|
|
23
|
+
export interface DetectBuildOptions {
|
|
24
|
+
/** `--build <label>`. */
|
|
25
|
+
label?: string;
|
|
26
|
+
/** Directory to ask git about. */
|
|
27
|
+
cwd: string;
|
|
28
|
+
env?: Env;
|
|
29
|
+
git?: GitRunner;
|
|
30
|
+
/**
|
|
31
|
+
* Uncommitted changes outside `iamu/` (`git_dirty`, `worktree_hash`).
|
|
32
|
+
* Recorded alongside a `--build` label, not instead of it.
|
|
33
|
+
*/
|
|
34
|
+
worktree?: WorktreeDetector;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build metadata for a run started now. Flags win over CI variables,
|
|
38
|
+
* CI variables over the local checkout. Returns undefined when nothing
|
|
39
|
+
* at all is known.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const build = detectBuild({ cwd: projectRoot, label: "2.4.0" });
|
|
44
|
+
* // { label: "2.4.0", git_sha: "3f9c2a1…", git_branch: "main", git_dirty: false }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function detectBuild(opts: DetectBuildOptions): BuildMetadata | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* The run's environment: `--env <name>` when given, otherwise the
|
|
50
|
+
* target URL's origin.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveEnvironment(explicit: string | undefined, targetUrl: string): string | undefined;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves cloud web dashboard URLs for projects and walkthrough runs.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Resolves the web dashboard base URL from an API URL or environment variable.
|
|
6
|
+
*
|
|
7
|
+
* Precedence:
|
|
8
|
+
* 1. IAMU_WEB_URL environment variable (explicit override)
|
|
9
|
+
* 2. If apiUrl targets api.iamu.dev -> https://app.iamu.dev
|
|
10
|
+
* 3. If apiUrl targets localhost:3000 -> http://localhost:3001 (standard local dev split)
|
|
11
|
+
* unless overridden by IAMU_WEB_URL
|
|
12
|
+
* 4. If apiUrl starts with https://api. -> https://app.
|
|
13
|
+
* 5. Fallback: apiUrl without trailing slash
|
|
14
|
+
*/
|
|
15
|
+
export declare function getCloudWebBase(apiUrl?: string | null): string;
|
|
16
|
+
/**
|
|
17
|
+
* Builds the cloud dashboard URL for a project or specific walkthrough run.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getCloudDashboardUrl(apiUrl: string | null | undefined, projectId: string, walkthroughId?: string): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Per-request budget for auth endpoints, headers plus body. */
|
|
2
|
+
export declare const AUTH_REQUEST_TIMEOUT_MS = 10000;
|
|
3
|
+
/** A fully read response from an auth endpoint. */
|
|
4
|
+
export interface AuthHttpResponse {
|
|
5
|
+
status: number;
|
|
6
|
+
ok: boolean;
|
|
7
|
+
/** Raw body text. */
|
|
8
|
+
text: string;
|
|
9
|
+
/** Body parsed as JSON, or `null` when it is not JSON. */
|
|
10
|
+
json: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** Request options for {@link requestAuthApi}. */
|
|
13
|
+
export interface AuthRequestOptions {
|
|
14
|
+
method?: "GET" | "POST";
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
body?: string;
|
|
17
|
+
/** Budget for this request; default {@link AUTH_REQUEST_TIMEOUT_MS}. */
|
|
18
|
+
timeoutMs?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Call an auth endpoint and read the whole body within `timeoutMs`.
|
|
22
|
+
*
|
|
23
|
+
* @param url - Absolute endpoint URL.
|
|
24
|
+
* @param opts - Method, headers, body and timeout.
|
|
25
|
+
* @returns Status plus the body as text and (when parseable) JSON.
|
|
26
|
+
* @throws {@link IamuNetworkError} when the timeout fires; other fetch
|
|
27
|
+
* failures propagate unchanged.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const res = await requestAuthApi(`${apiUrl}/api/projects`, {
|
|
32
|
+
* headers: { Authorization: `Bearer ${token}` },
|
|
33
|
+
* });
|
|
34
|
+
* if (res.ok) console.log(res.json);
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function requestAuthApi(url: string, opts?: AuthRequestOptions): Promise<AuthHttpResponse>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which cloud project `iamu auth login` links to. Pure, so every branch
|
|
3
|
+
* is testable without a TTY or network.
|
|
4
|
+
*
|
|
5
|
+
* Linking pushes every local entity to the chosen project, so a wrong
|
|
6
|
+
* guess leaks one product's data into another. The rules therefore
|
|
7
|
+
* never pick between several projects on the user's behalf.
|
|
8
|
+
*/
|
|
9
|
+
/** A cloud project as `GET /api/projects` lists it. */
|
|
10
|
+
export interface CloudProjectSummary {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
slug: string;
|
|
14
|
+
}
|
|
15
|
+
/** `--project` value that asks for a new cloud project. */
|
|
16
|
+
export declare const CREATE_NEW_PROJECT = "new";
|
|
17
|
+
/** Inputs to {@link decideCloudProject}. */
|
|
18
|
+
export interface ProjectChoiceInput {
|
|
19
|
+
/** Projects the account can see. */
|
|
20
|
+
projects: readonly CloudProjectSummary[];
|
|
21
|
+
/** `--project` value, if given. */
|
|
22
|
+
requested?: string;
|
|
23
|
+
/** Project this repo is already linked to (credentials were lost). */
|
|
24
|
+
linkedProjectId?: string | null;
|
|
25
|
+
/** `--yes`: accept the only candidate. */
|
|
26
|
+
yes?: boolean;
|
|
27
|
+
/** Whether a human can be prompted. */
|
|
28
|
+
interactive: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Outcome of {@link decideCloudProject}. */
|
|
31
|
+
export type ProjectDecision = {
|
|
32
|
+
kind: "link";
|
|
33
|
+
projectId: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: "create";
|
|
36
|
+
} | {
|
|
37
|
+
kind: "prompt";
|
|
38
|
+
} | {
|
|
39
|
+
kind: "refuse";
|
|
40
|
+
reason: "ambiguous" | "unknown_project";
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Decide the project to link.
|
|
44
|
+
*
|
|
45
|
+
* - `--project new` creates; `--project <id>` must be one of `projects`.
|
|
46
|
+
* - An existing link is kept.
|
|
47
|
+
* - No projects at all: create one named after the local project.
|
|
48
|
+
* - `--yes` links the single project when there is exactly one; with
|
|
49
|
+
* several it does not pick, it falls through.
|
|
50
|
+
* - Otherwise prompt when interactive, refuse (and list) when not.
|
|
51
|
+
*
|
|
52
|
+
* @param input - Available projects plus flags and TTY state.
|
|
53
|
+
* @returns What to do next.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const d = decideCloudProject({ projects, requested: "new", interactive: false });
|
|
58
|
+
* // { kind: "create" }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function decideCloudProject(input: ProjectChoiceInput): ProjectDecision;
|
|
62
|
+
/**
|
|
63
|
+
* Narrow a `GET /api/projects` body to project summaries.
|
|
64
|
+
*
|
|
65
|
+
* @param body - Parsed JSON body.
|
|
66
|
+
* @returns The projects, or `null` when the body is not a project list.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* const projects = parseProjectList(res.json);
|
|
71
|
+
* if (!projects) throw new Error("unexpected response");
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function parseProjectList(body: unknown): CloudProjectSummary[] | null;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** The readable side `readTokenFromStdin` needs. */
|
|
2
|
+
export type TokenInputStream = AsyncIterable<string | Uint8Array> & {
|
|
3
|
+
isTTY?: boolean;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Read an API token piped on stdin (`--token-stdin`), so it never shows
|
|
7
|
+
* up in `ps`, shell history or CI logs the way `--token <value>` does.
|
|
8
|
+
* Surrounding whitespace (a trailing newline from `echo`) is dropped.
|
|
9
|
+
*
|
|
10
|
+
* @param stream - Input stream; defaults to `process.stdin`.
|
|
11
|
+
* @returns The token.
|
|
12
|
+
* @throws {@link IamuValidationError} when stdin is a terminal, empty,
|
|
13
|
+
* oversized, or holds more than one word.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* // printf %s "$IAMU_CI_TOKEN" | iamu auth login --token-stdin
|
|
18
|
+
* const token = await readTokenFromStdin();
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function readTokenFromStdin(stream?: TokenInputStream): Promise<string>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** Launches a program with its arguments as an argv array — no shell. */
|
|
2
|
+
export type CommandRunner = (file: string, args: readonly string[]) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Open `url` in the platform browser. The URL is always a separate argv
|
|
5
|
+
* element: interpolating it into a shell command line let a crafted
|
|
6
|
+
* login URL run commands.
|
|
7
|
+
*
|
|
8
|
+
* @param url - The URL to open.
|
|
9
|
+
* @param run - Process launcher; injectable so tests never spawn.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* openBrowser("https://iamu.dev/login");
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function openBrowser(url: string, run?: CommandRunner): void;
|
|
17
|
+
/** Flags of `iamu auth login`. */
|
|
18
|
+
export interface AuthLoginOptions {
|
|
19
|
+
url?: string;
|
|
20
|
+
/** Accept the only candidate project; never picks between several. */
|
|
21
|
+
yes?: boolean;
|
|
22
|
+
/** Cloud project id to link, or `new` to create one. */
|
|
23
|
+
project?: string;
|
|
24
|
+
browser?: boolean;
|
|
25
|
+
local?: boolean | string | number;
|
|
26
|
+
staging?: boolean;
|
|
27
|
+
/** Token on argv. Visible to `ps` and shell history; prefer `tokenStdin`. */
|
|
28
|
+
token?: string;
|
|
29
|
+
/** Read the token from stdin instead of argv. */
|
|
30
|
+
tokenStdin?: boolean;
|
|
31
|
+
webUrl?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Timing knobs for {@link authLogin}. Exposed so tests can shrink them.
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export interface AuthLoginTiming {
|
|
39
|
+
/** Pause between token polls. */
|
|
40
|
+
pollIntervalMs: number;
|
|
41
|
+
/** Hard deadline for the whole browser login. */
|
|
42
|
+
pollTimeoutMs: number;
|
|
43
|
+
/** Budget for each HTTP request. */
|
|
44
|
+
requestTimeoutMs: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `iamu auth login`: obtain a token (flag, stdin, stored/env, or browser
|
|
48
|
+
* device flow), store it, then link the project to a cloud project.
|
|
49
|
+
* Every failure sets exit code 1 (2 for conflicting flags); in a
|
|
50
|
+
* non-interactive shell it never chooses between cloud projects.
|
|
51
|
+
*
|
|
52
|
+
* @param opts - Command flags.
|
|
53
|
+
* @param timing - Test-only timing overrides.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* await authLogin({ project: "new" });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function authLogin(opts?: AuthLoginOptions, timing?: Partial<AuthLoginTiming>): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* `iamu auth disconnect`: unlink the project and forget the stored token
|
|
63
|
+
* for its API URL. Local entities are kept.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* await authDisconnect();
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function authDisconnect(): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* `iamu auth status`: whether a token is available and the project is
|
|
73
|
+
* linked. Never prints the token.
|
|
74
|
+
*
|
|
75
|
+
* @param opts - `json` prints a machine-readable object on stdout.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* await authStatus({ json: true });
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export declare function authStatus(opts?: {
|
|
83
|
+
json?: boolean;
|
|
84
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Page } from "playwright-core";
|
|
2
|
+
/** Options an action reads. */
|
|
3
|
+
export interface ActionOptions {
|
|
4
|
+
/** Pick the nth (0-based) match instead of the first. */
|
|
5
|
+
nth?: string;
|
|
6
|
+
/** Press Enter after fill/select. */
|
|
7
|
+
submit?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* One page action: how it is called, what it targets (for the "nothing
|
|
11
|
+
* matches" error), and what it does.
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export interface PageAction {
|
|
16
|
+
/** Call shape after `iamu browse`, e.g. `click <role> "<name>" [--nth N]`. */
|
|
17
|
+
usage: string;
|
|
18
|
+
/** Positional arguments required after the action name. */
|
|
19
|
+
minArgs: number;
|
|
20
|
+
/** What the action looks for, named the way the error message says it. */
|
|
21
|
+
target?: (args: string[]) => string;
|
|
22
|
+
run(page: Page, args: string[], opts: ActionOptions): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export interface IndexedElement {
|
|
25
|
+
index: number;
|
|
26
|
+
tag: string;
|
|
27
|
+
role: string;
|
|
28
|
+
name: string;
|
|
29
|
+
type?: string;
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare const INDEXED_SELECTOR: string;
|
|
33
|
+
export declare function markAndQueryIndexedElements(page: Page): Promise<IndexedElement[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Every page action, by name. `shot` does nothing to the page; the
|
|
36
|
+
* screenshot is taken after any action when one is asked for.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const action = PAGE_ACTIONS.click;
|
|
41
|
+
* if (args.length >= action.minArgs) await action.run(page, args, {});
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
export declare const PAGE_ACTIONS: Readonly<Record<string, PageAction>>;
|
|
47
|
+
/**
|
|
48
|
+
* Look up a page action by name, own properties only (so `toString`
|
|
49
|
+
* and friends are not actions).
|
|
50
|
+
*
|
|
51
|
+
* @param name - The action name as typed.
|
|
52
|
+
* @returns The action, or undefined.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* findPageAction("click")?.usage; // 'click <role> "<name>" [--nth N]'
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
61
|
+
export declare function findPageAction(name: string): PageAction | undefined;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Page } from "playwright-core";
|
|
2
|
+
/** A page size in CSS pixels. */
|
|
3
|
+
export interface Size {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The page a command acts on, plus how to let go of it. Detaching
|
|
9
|
+
* disconnects from the browser; it never closes the page.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export interface AttachedPage {
|
|
14
|
+
page: Page;
|
|
15
|
+
/** All non-devtools pages open in the context. */
|
|
16
|
+
pages?: Page[];
|
|
17
|
+
/** Dialog messages encountered while attached, e.g. `[alert] You must agree to terms`. */
|
|
18
|
+
dialogs?: string[];
|
|
19
|
+
detach(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* What `iamu browse` needs from the machine and the browser — the real
|
|
23
|
+
* one spawns Chrome and speaks CDP; tests pass a fake.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
export interface BrowserDriver {
|
|
28
|
+
/** Is a browser answering CDP on this port? Must not hang. */
|
|
29
|
+
isRunning(port: number): Promise<boolean>;
|
|
30
|
+
/** Launch Chrome detached with these arguments; returns its PID. */
|
|
31
|
+
launch(chrome: string, args: string[]): number | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Fit the running window's content area to the viewport
|
|
34
|
+
* (`Browser.setWindowBounds`), so a person watching sees the
|
|
35
|
+
* persona's screen. Best effort: a window cannot be phone-narrow, and
|
|
36
|
+
* every command emulates the stored viewport on its page anyway.
|
|
37
|
+
*/
|
|
38
|
+
resize(port: number, viewport: Size | null): Promise<void>;
|
|
39
|
+
/** Attach over CDP to the page the persona is on. Throws when nothing answers. */
|
|
40
|
+
attach(port: number, pageIndex?: number): Promise<AttachedPage>;
|
|
41
|
+
/** Ask the browser on this port to close (`Browser.close`). Best effort. */
|
|
42
|
+
closeBrowser(port: number): Promise<void>;
|
|
43
|
+
/** The full command line of `pid`, or null when it is gone or cannot be read. */
|
|
44
|
+
commandLine(pid: number): Promise<string | null>;
|
|
45
|
+
/** Send SIGTERM to `pid`. Never throws. */
|
|
46
|
+
kill(pid: number): void;
|
|
47
|
+
}
|
|
48
|
+
/** How long a CDP liveness probe may take before the port counts as dead. */
|
|
49
|
+
export declare const CDP_PROBE_TIMEOUT_MS = 1000;
|
|
50
|
+
/**
|
|
51
|
+
* Is a browser answering CDP on `port`? A process that accepts the
|
|
52
|
+
* connection but never answers (a hung Chrome, something else on the
|
|
53
|
+
* port) counts as not running once `timeoutMs` passes.
|
|
54
|
+
*
|
|
55
|
+
* @param port - CDP port on 127.0.0.1.
|
|
56
|
+
* @param timeoutMs - Give up after this long.
|
|
57
|
+
* @returns True when `/json/version` answers 2xx in time.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* if (!(await probeCdp(9333))) console.log("no browser");
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @public
|
|
65
|
+
*/
|
|
66
|
+
export declare function probeCdp(port: number, timeoutMs?: number): Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* The command line of a running process, read without a shell:
|
|
69
|
+
* `/proc/<pid>/cmdline` on Linux, else `ps -ww -o args= -p <pid>`.
|
|
70
|
+
* Windows has no equivalent we trust, so it answers null there — and
|
|
71
|
+
* callers then leave the process alone.
|
|
72
|
+
*
|
|
73
|
+
* @param pid - Process ID to inspect.
|
|
74
|
+
* @param platform - Defaults to `process.platform`.
|
|
75
|
+
* @returns The arguments joined by spaces, or null.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* const cmd = await readProcessCommandLine(12345);
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @public
|
|
83
|
+
*/
|
|
84
|
+
export declare function readProcessCommandLine(pid: number, platform?: NodeJS.Platform): Promise<string | null>;
|
|
85
|
+
/**
|
|
86
|
+
* Was this command line launched with exactly this `--user-data-dir`?
|
|
87
|
+
* The profile directory is ours alone, so a match means the PID is the
|
|
88
|
+
* Chrome `iamu browse start` launched, not a process that reused its PID.
|
|
89
|
+
*
|
|
90
|
+
* @param commandLine - From {@link readProcessCommandLine}.
|
|
91
|
+
* @param profileDir - The `--user-data-dir` we launched with.
|
|
92
|
+
* @returns True only on an exact flag match.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* isOurBrowser("chrome --user-data-dir=/h/.iamu/browse/profile", "/h/.iamu/browse/profile"); // true
|
|
97
|
+
* ```
|
|
98
|
+
*
|
|
99
|
+
* @public
|
|
100
|
+
*/
|
|
101
|
+
export declare function isOurBrowser(commandLine: string | null, profileDir: string): boolean;
|
|
102
|
+
/**
|
|
103
|
+
* SIGTERM `pid` only if it is verifiably our Chrome (see
|
|
104
|
+
* {@link isOurBrowser}). PIDs get reused; anything else is left alone
|
|
105
|
+
* and Chrome's own profile lock guards a second launch.
|
|
106
|
+
*
|
|
107
|
+
* @param pid - PID recorded in the browse state.
|
|
108
|
+
* @param profileDir - The `--user-data-dir` it was launched with.
|
|
109
|
+
* @param driver - Supplies `commandLine` and `kill`.
|
|
110
|
+
* @returns True when the process was signalled.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* await reapBrowser(state.pid, profileDir, cdpDriver);
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* @public
|
|
118
|
+
*/
|
|
119
|
+
export declare function reapBrowser(pid: number, profileDir: string, driver: Pick<BrowserDriver, "commandLine" | "kill">): Promise<boolean>;
|
|
120
|
+
/** The real driver: spawns Chrome and speaks CDP through playwright-core. */
|
|
121
|
+
export declare const cdpDriver: BrowserDriver;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import type { Page } from "playwright-core";
|
|
2
|
+
import { type BrowserDriver, type Size } from "./browse-driver.js";
|
|
3
|
+
export type { BrowserDriver } from "./browse-driver.js";
|
|
4
|
+
/** Parsed options for `iamu browse <command>`. */
|
|
5
|
+
export interface BrowseOptions {
|
|
6
|
+
shot?: string;
|
|
7
|
+
nth?: string;
|
|
8
|
+
submit?: boolean;
|
|
9
|
+
full?: boolean;
|
|
10
|
+
viewport?: string;
|
|
11
|
+
width?: string;
|
|
12
|
+
height?: string;
|
|
13
|
+
headless?: boolean;
|
|
14
|
+
port?: string | number;
|
|
15
|
+
/** `start`: size the browser for this run's persona. */
|
|
16
|
+
run?: string;
|
|
17
|
+
action?: string;
|
|
18
|
+
note?: string;
|
|
19
|
+
surface?: string;
|
|
20
|
+
maxSteps?: string | number;
|
|
21
|
+
stepFile?: string;
|
|
22
|
+
json?: boolean;
|
|
23
|
+
/** Annotate snapshot with 1-based index numbers [#N] for click-index / fill-index */
|
|
24
|
+
indexed?: boolean;
|
|
25
|
+
}
|
|
26
|
+
interface BrowseState {
|
|
27
|
+
port: number;
|
|
28
|
+
viewport: Size | null;
|
|
29
|
+
pid?: number;
|
|
30
|
+
headless?: boolean;
|
|
31
|
+
activeTabIndex?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The state `iamu browse start` left in `~/.iamu/browse/state.json`, or
|
|
35
|
+
* null when no browser was started (or the file is unreadable).
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* const state = readBrowseState();
|
|
40
|
+
* if (state) console.log(`CDP on :${state.port}`);
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
export declare function readBrowseState(): BrowseState | null;
|
|
46
|
+
/**
|
|
47
|
+
* Parse `"390x844"` into a size.
|
|
48
|
+
*
|
|
49
|
+
* @param value - `WxH`, 2–5 digits each.
|
|
50
|
+
* @returns The size, or null when absent or malformed.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* parseViewport("390x844"); // { width: 390, height: 844 }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
export declare function parseViewport(value: string | undefined): Size | null;
|
|
60
|
+
/**
|
|
61
|
+
* The installed Chrome/Chromium to drive: `IAMU_BROWSER`, else the
|
|
62
|
+
* usual install location for this platform.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* const chrome = findChrome();
|
|
67
|
+
* if (!chrome) console.error("Install Chrome or set IAMU_BROWSER");
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @public
|
|
71
|
+
*/
|
|
72
|
+
export declare function findChrome(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, exists?: (p: string) => boolean): string | null;
|
|
73
|
+
/**
|
|
74
|
+
* Should `start` launch without a window? `--headless` wins, then
|
|
75
|
+
* `IAMU_HEADLESS`, then CI or a Linux box with no `DISPLAY`.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* isHeadlessEnvironment({ headless: true }); // true
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @public
|
|
83
|
+
*/
|
|
84
|
+
export declare function isHeadlessEnvironment(opts?: BrowseOptions): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Should Chrome run with `--no-sandbox`? Only when it cannot start
|
|
87
|
+
* otherwise — as root (Chrome refuses to sandbox as root, typical in
|
|
88
|
+
* containers) — or when explicitly asked via `IAMU_BROWSE_NO_SANDBOX=1`.
|
|
89
|
+
* Being headless or in CI is not a reason: the sandbox is what keeps a
|
|
90
|
+
* page the persona visits away from the machine.
|
|
91
|
+
*
|
|
92
|
+
* @param env - Defaults to `process.env`.
|
|
93
|
+
* @param uid - Effective user ID; defaults to `process.getuid()` (undefined on Windows).
|
|
94
|
+
* @returns True when `--no-sandbox` should be passed.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* shouldDisableSandbox({ IAMU_BROWSE_NO_SANDBOX: "1" }, 1000); // true
|
|
99
|
+
* shouldDisableSandbox({ CI: "true" }, 1000); // false
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* @public
|
|
103
|
+
*/
|
|
104
|
+
export declare function shouldDisableSandbox(env?: NodeJS.ProcessEnv, uid?: number | undefined): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Start the browser, or converge the one already running: a running
|
|
107
|
+
* browser is reused, and resized when the requested viewport differs
|
|
108
|
+
* from the one it was started with — never silently left at another
|
|
109
|
+
* persona's size.
|
|
110
|
+
*
|
|
111
|
+
* A PID left by an earlier `start` whose CDP no longer answers is
|
|
112
|
+
* terminated only when its command line shows our `--user-data-dir`;
|
|
113
|
+
* a PID the OS has since handed to another process is left alone.
|
|
114
|
+
*
|
|
115
|
+
* @param opts - `viewport`, `run`, `port`, `width`, `height`, `headless`, `json`.
|
|
116
|
+
* @param driver - Machine/browser seam; tests pass a fake.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* await start({ viewport: "390x844" });
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @public
|
|
124
|
+
*/
|
|
125
|
+
export declare function start(opts: BrowseOptions, driver?: BrowserDriver): Promise<void>;
|
|
126
|
+
/** Where the page ended up after an action. */
|
|
127
|
+
export interface PageResult {
|
|
128
|
+
url: string;
|
|
129
|
+
title: string;
|
|
130
|
+
/** Absolute path of the screenshot written, or null when none was asked for. */
|
|
131
|
+
screenshot: string | null;
|
|
132
|
+
/** Dialog messages encountered while attached (e.g. alerts/confirms auto-handled). */
|
|
133
|
+
dialog?: string;
|
|
134
|
+
/** Lightweight semantic text summary of page content for stagnation detection. */
|
|
135
|
+
contentSnippet?: string;
|
|
136
|
+
/** Detected anti-bot challenge (e.g. Cloudflare Turnstile, Google reCAPTCHA, hCaptcha). */
|
|
137
|
+
challenge?: string;
|
|
138
|
+
}
|
|
139
|
+
/** Compute a fast fingerprint of page URL and visible text to detect stagnation across steps. */
|
|
140
|
+
export declare function computePageFingerprint(url: string, bodySnippet: string): string;
|
|
141
|
+
/**
|
|
142
|
+
* Track page state across consecutive steps in a run.
|
|
143
|
+
* Emits a warning when 3 consecutive steps produce zero observable URL/DOM transition.
|
|
144
|
+
*/
|
|
145
|
+
export declare function checkAndRecordStagnation(projectRoot: string, runId: string, fingerprint: string, action: string): {
|
|
146
|
+
count: number;
|
|
147
|
+
warning?: string;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Wait for the page to visually settle before taking a screenshot.
|
|
151
|
+
*
|
|
152
|
+
* SPA actions (state updates, CSS transitions, element mounts) do not trigger
|
|
153
|
+
* a navigation, so `domcontentloaded` returns immediately. Rather than a blunt
|
|
154
|
+
* hardcoded sleep, this flushes the rendering pipeline via double
|
|
155
|
+
* `requestAnimationFrame` and waits for in-flight CSS/web animations to finish,
|
|
156
|
+
* bounded by `maxWaitMs` (default 300ms) to ensure it never hangs on continuous
|
|
157
|
+
* animations like spinners.
|
|
158
|
+
*
|
|
159
|
+
* @public
|
|
160
|
+
*/
|
|
161
|
+
export declare function waitForVisualStability(page: Page, maxWaitMs?: number): Promise<void>;
|
|
162
|
+
/**
|
|
163
|
+
* Detect known anti-bot challenges (Cloudflare, reCAPTCHA, hCaptcha, Arkose, AWS WAF).
|
|
164
|
+
*
|
|
165
|
+
* @public
|
|
166
|
+
*/
|
|
167
|
+
export declare function detectAntiBotChallenge(page: Page, allowEvaluate?: boolean): Promise<string | undefined>;
|
|
168
|
+
/**
|
|
169
|
+
* Attach to the running browser, run `fn` on the page the persona is
|
|
170
|
+
* on, optionally screenshot it, and return where the page is. Prints
|
|
171
|
+
* nothing on success — the caller decides the output shape. Detaching
|
|
172
|
+
* never closes the page.
|
|
173
|
+
*
|
|
174
|
+
* @public
|
|
175
|
+
*/
|
|
176
|
+
export declare function withPage(opts: BrowseOptions, driver: BrowserDriver, fn: (page: Page) => Promise<void>,
|
|
177
|
+
/** What the action looks for, named for the error message. */
|
|
178
|
+
target?: string): Promise<PageResult | null>;
|
|
179
|
+
/**
|
|
180
|
+
* `iamu browse <command> [args…]` — dispatch one browse subcommand.
|
|
181
|
+
* Failures print to stderr and set exit code 1.
|
|
182
|
+
*
|
|
183
|
+
* @param command - `start`, `stop`, `step`, `snapshot`, `shot`, or a page action.
|
|
184
|
+
* @param args - Positional arguments after the command.
|
|
185
|
+
* @param opts - Parsed CLI options.
|
|
186
|
+
* @param driver - Machine/browser seam; tests pass a fake.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```ts
|
|
190
|
+
* await browse("click", ["button", "Sign in"], { json: true });
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @public
|
|
194
|
+
*/
|
|
195
|
+
export declare function browse(command: string, args: string[], opts: BrowseOptions, driver?: BrowserDriver): Promise<void>;
|