@flrande/browserctl 0.4.0 → 0.5.0-dev.19.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/apps/browserctl/src/commands/act.test.ts +71 -0
- package/apps/browserctl/src/commands/act.ts +45 -1
- package/apps/browserctl/src/commands/command-wrappers.test.ts +302 -0
- package/apps/browserctl/src/commands/console-list.test.ts +102 -0
- package/apps/browserctl/src/commands/console-list.ts +89 -1
- package/apps/browserctl/src/commands/har-export.test.ts +112 -0
- package/apps/browserctl/src/commands/har-export.ts +120 -0
- package/apps/browserctl/src/commands/memory-delete.ts +20 -0
- package/apps/browserctl/src/commands/memory-inspect.ts +20 -0
- package/apps/browserctl/src/commands/memory-list.ts +90 -0
- package/apps/browserctl/src/commands/memory-mode-set.ts +29 -0
- package/apps/browserctl/src/commands/memory-purge.ts +16 -0
- package/apps/browserctl/src/commands/memory-resolve.ts +56 -0
- package/apps/browserctl/src/commands/memory-status.ts +16 -0
- package/apps/browserctl/src/commands/memory-ttl-set.ts +28 -0
- package/apps/browserctl/src/commands/memory-upsert.ts +142 -0
- package/apps/browserctl/src/commands/network-list.test.ts +110 -0
- package/apps/browserctl/src/commands/network-list.ts +112 -0
- package/apps/browserctl/src/commands/session-drop.test.ts +36 -0
- package/apps/browserctl/src/commands/session-drop.ts +16 -0
- package/apps/browserctl/src/commands/session-list.test.ts +81 -0
- package/apps/browserctl/src/commands/session-list.ts +70 -0
- package/apps/browserctl/src/commands/trace-get.test.ts +61 -0
- package/apps/browserctl/src/commands/trace-get.ts +62 -0
- package/apps/browserctl/src/commands/wait-element.test.ts +80 -0
- package/apps/browserctl/src/commands/wait-element.ts +76 -0
- package/apps/browserctl/src/commands/wait-text.test.ts +110 -0
- package/apps/browserctl/src/commands/wait-text.ts +93 -0
- package/apps/browserctl/src/commands/wait-url.test.ts +80 -0
- package/apps/browserctl/src/commands/wait-url.ts +76 -0
- package/apps/browserctl/src/main.dispatch.test.ts +206 -1
- package/apps/browserctl/src/main.test.ts +30 -0
- package/apps/browserctl/src/main.ts +246 -4
- package/apps/browserd/src/container.ts +1603 -48
- package/apps/browserd/src/main.test.ts +538 -1
- package/apps/browserd/src/tool-matrix.test.ts +492 -3
- package/package.json +5 -1
- package/packages/core/src/driver.ts +1 -1
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/navigation-memory.test.ts +259 -0
- package/packages/core/src/navigation-memory.ts +360 -0
- package/packages/core/src/session-store.test.ts +33 -0
- package/packages/core/src/session-store.ts +111 -6
- package/packages/driver-chrome-relay/src/chrome-relay-driver.test.ts +112 -2
- package/packages/driver-chrome-relay/src/chrome-relay-driver.ts +233 -10
- package/packages/driver-managed/src/managed-driver.test.ts +124 -0
- package/packages/driver-managed/src/managed-driver.ts +233 -17
- package/packages/driver-managed/src/managed-local-driver.test.ts +104 -2
- package/packages/driver-managed/src/managed-local-driver.ts +232 -10
- package/packages/driver-remote-cdp/src/remote-cdp-driver.test.ts +112 -2
- package/packages/driver-remote-cdp/src/remote-cdp-driver.ts +232 -10
- package/packages/transport-mcp-stdio/src/tool-map.ts +18 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const { callDaemonToolMock } = vi.hoisted(() => ({
|
|
4
|
+
callDaemonToolMock: vi.fn()
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock("../daemon-client", () => ({
|
|
8
|
+
callDaemonTool: callDaemonToolMock
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
import { runHarExportCommand } from "./har-export";
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
callDaemonToolMock.mockReset();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("runHarExportCommand", () => {
|
|
18
|
+
it("forwards export options", async () => {
|
|
19
|
+
callDaemonToolMock.mockResolvedValue({ ok: true });
|
|
20
|
+
|
|
21
|
+
await runHarExportCommand([
|
|
22
|
+
"target:1",
|
|
23
|
+
"--include-bodies",
|
|
24
|
+
"--url-contains",
|
|
25
|
+
"/api/",
|
|
26
|
+
"--method",
|
|
27
|
+
"get",
|
|
28
|
+
"--status",
|
|
29
|
+
"200",
|
|
30
|
+
"--since",
|
|
31
|
+
"2026-01-01T00:00:00.000Z",
|
|
32
|
+
"--limit",
|
|
33
|
+
"30"
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
expect(callDaemonToolMock).toHaveBeenCalledTimes(1);
|
|
37
|
+
expect(callDaemonToolMock).toHaveBeenCalledWith("browser.network.harExport", {
|
|
38
|
+
sessionId: "cli:local",
|
|
39
|
+
targetId: "target:1",
|
|
40
|
+
includeBodies: true,
|
|
41
|
+
urlContains: "/api/",
|
|
42
|
+
method: "GET",
|
|
43
|
+
status: 200,
|
|
44
|
+
since: "2026-01-01T00:00:00.000Z",
|
|
45
|
+
limit: 30
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it.each([
|
|
50
|
+
{
|
|
51
|
+
label: "--url-contains",
|
|
52
|
+
args: ["target:1", "--url-contains"],
|
|
53
|
+
message: "Missing value for --url-contains."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: "--method",
|
|
57
|
+
args: ["target:1", "--method"],
|
|
58
|
+
message: "Missing value for --method."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
label: "--status",
|
|
62
|
+
args: ["target:1", "--status"],
|
|
63
|
+
message: "Missing value for --status."
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: "--since",
|
|
67
|
+
args: ["target:1", "--since"],
|
|
68
|
+
message: "Missing value for --since."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
label: "--limit",
|
|
72
|
+
args: ["target:1", "--limit"],
|
|
73
|
+
message: "Missing value for --limit."
|
|
74
|
+
}
|
|
75
|
+
])("throws when $label is missing a value", async ({ args, message }) => {
|
|
76
|
+
await expect(runHarExportCommand(args)).rejects.toThrow(message);
|
|
77
|
+
expect(callDaemonToolMock).not.toHaveBeenCalled();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it.each([
|
|
81
|
+
{
|
|
82
|
+
label: "empty --url-contains",
|
|
83
|
+
args: ["target:1", "--url-contains", " "],
|
|
84
|
+
message: "--url-contains must be a non-empty string."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
label: "status <= 0",
|
|
88
|
+
args: ["target:1", "--status", "0"],
|
|
89
|
+
message: "--status must be a positive integer."
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
label: "status non-strict integer text",
|
|
93
|
+
args: ["target:1", "--status", "201ok"],
|
|
94
|
+
message: "--status must be a positive integer."
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
label: "limit <= 0",
|
|
98
|
+
args: ["target:1", "--limit", "0"],
|
|
99
|
+
message: "--limit must be a positive integer."
|
|
100
|
+
}
|
|
101
|
+
])("throws on invalid option value: $label", async ({ args, message }) => {
|
|
102
|
+
await expect(runHarExportCommand(args)).rejects.toThrow(message);
|
|
103
|
+
expect(callDaemonToolMock).not.toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("throws on unknown option token", async () => {
|
|
107
|
+
await expect(runHarExportCommand(["target:1", "--unknown"]))
|
|
108
|
+
.rejects.toThrow("Unknown har-export option: --unknown");
|
|
109
|
+
|
|
110
|
+
expect(callDaemonToolMock).not.toHaveBeenCalled();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type HarExportCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
function parsePositiveInteger(value: string, optionName: string): number {
|
|
7
|
+
const normalized = value.trim();
|
|
8
|
+
if (!/^[1-9]\d*$/.test(normalized)) {
|
|
9
|
+
throw new Error(`${optionName} must be a positive integer.`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return Number.parseInt(normalized, 10);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseHarExportOptions(tokens: string[]): {
|
|
16
|
+
includeBodies?: boolean;
|
|
17
|
+
urlContains?: string;
|
|
18
|
+
method?: string;
|
|
19
|
+
status?: number;
|
|
20
|
+
since?: string;
|
|
21
|
+
limit?: number;
|
|
22
|
+
} {
|
|
23
|
+
let includeBodies = false;
|
|
24
|
+
let urlContains: string | undefined;
|
|
25
|
+
let method: string | undefined;
|
|
26
|
+
let status: number | undefined;
|
|
27
|
+
let since: string | undefined;
|
|
28
|
+
let limit: number | undefined;
|
|
29
|
+
|
|
30
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
31
|
+
const token = tokens[index];
|
|
32
|
+
if (token === "--include-bodies") {
|
|
33
|
+
includeBodies = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (token === "--url-contains") {
|
|
38
|
+
const value = tokens[index + 1];
|
|
39
|
+
if (value === undefined) {
|
|
40
|
+
throw new Error("Missing value for --url-contains.");
|
|
41
|
+
}
|
|
42
|
+
const trimmed = value.trim();
|
|
43
|
+
if (trimmed.length === 0) {
|
|
44
|
+
throw new Error("--url-contains must be a non-empty string.");
|
|
45
|
+
}
|
|
46
|
+
urlContains = trimmed;
|
|
47
|
+
index += 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (token === "--method") {
|
|
52
|
+
const value = tokens[index + 1];
|
|
53
|
+
if (value === undefined) {
|
|
54
|
+
throw new Error("Missing value for --method.");
|
|
55
|
+
}
|
|
56
|
+
method = value.trim().toUpperCase();
|
|
57
|
+
index += 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (token === "--status") {
|
|
62
|
+
const value = tokens[index + 1];
|
|
63
|
+
if (value === undefined) {
|
|
64
|
+
throw new Error("Missing value for --status.");
|
|
65
|
+
}
|
|
66
|
+
status = parsePositiveInteger(value, "--status");
|
|
67
|
+
index += 1;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (token === "--since") {
|
|
72
|
+
const value = tokens[index + 1];
|
|
73
|
+
if (value === undefined) {
|
|
74
|
+
throw new Error("Missing value for --since.");
|
|
75
|
+
}
|
|
76
|
+
const trimmed = value.trim();
|
|
77
|
+
if (trimmed.length === 0) {
|
|
78
|
+
throw new Error("--since must be a non-empty ISO timestamp.");
|
|
79
|
+
}
|
|
80
|
+
since = trimmed;
|
|
81
|
+
index += 1;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (token === "--limit") {
|
|
86
|
+
const value = tokens[index + 1];
|
|
87
|
+
if (value === undefined) {
|
|
88
|
+
throw new Error("Missing value for --limit.");
|
|
89
|
+
}
|
|
90
|
+
limit = parsePositiveInteger(value, "--limit");
|
|
91
|
+
index += 1;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw new Error(`Unknown har-export option: ${token}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
...(includeBodies ? { includeBodies: true } : {}),
|
|
100
|
+
...(urlContains !== undefined ? { urlContains } : {}),
|
|
101
|
+
...(method !== undefined ? { method } : {}),
|
|
102
|
+
...(status !== undefined ? { status } : {}),
|
|
103
|
+
...(since !== undefined ? { since } : {}),
|
|
104
|
+
...(limit !== undefined ? { limit } : {})
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function runHarExportCommand(args: string[]): Promise<HarExportCommandResult> {
|
|
109
|
+
const context = parseCommandContext(args);
|
|
110
|
+
const targetId = requirePositionalArg(context, 0, "targetId");
|
|
111
|
+
const options = parseHarExportOptions(context.positional.slice(1));
|
|
112
|
+
|
|
113
|
+
return await callDaemonTool<HarExportCommandResult>(
|
|
114
|
+
"browser.network.harExport",
|
|
115
|
+
buildToolArguments(context, {
|
|
116
|
+
targetId,
|
|
117
|
+
...options
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type MemoryDeleteCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runMemoryDeleteCommand(args: string[]): Promise<MemoryDeleteCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
const id = requirePositionalArg(context, 0, "id");
|
|
9
|
+
|
|
10
|
+
if (context.positional.length > 1) {
|
|
11
|
+
throw new Error(`Unknown memory-delete option: ${context.positional[1]}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return await callDaemonTool<MemoryDeleteCommandResult>(
|
|
15
|
+
"browser.memory.delete",
|
|
16
|
+
buildToolArguments(context, {
|
|
17
|
+
id
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type MemoryInspectCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runMemoryInspectCommand(args: string[]): Promise<MemoryInspectCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
const id = requirePositionalArg(context, 0, "id");
|
|
9
|
+
|
|
10
|
+
if (context.positional.length > 1) {
|
|
11
|
+
throw new Error(`Unknown memory-inspect option: ${context.positional[1]}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return await callDaemonTool<MemoryInspectCommandResult>(
|
|
15
|
+
"browser.memory.inspect",
|
|
16
|
+
buildToolArguments(context, {
|
|
17
|
+
id
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext } from "./common";
|
|
3
|
+
|
|
4
|
+
export type MemoryListCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
type MemoryListOptions = {
|
|
7
|
+
domain?: string;
|
|
8
|
+
profileId?: string;
|
|
9
|
+
intentKey?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function requireOptionValue(tokens: string[], index: number, optionName: string): string {
|
|
13
|
+
const value = tokens[index + 1];
|
|
14
|
+
if (value === undefined || value.startsWith("--")) {
|
|
15
|
+
throw new Error(`Missing value for ${optionName}.`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseListOptions(tokens: string[]): MemoryListOptions {
|
|
22
|
+
let domain: string | undefined;
|
|
23
|
+
let profileId: string | undefined;
|
|
24
|
+
let intentKey: string | undefined;
|
|
25
|
+
|
|
26
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
27
|
+
const token = tokens[index];
|
|
28
|
+
if (token === "--domain") {
|
|
29
|
+
const value = requireOptionValue(tokens, index, "--domain");
|
|
30
|
+
|
|
31
|
+
const trimmedValue = value.trim();
|
|
32
|
+
if (trimmedValue.length === 0) {
|
|
33
|
+
throw new Error("--domain must be a non-empty string.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
domain = trimmedValue;
|
|
37
|
+
index += 1;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (token === "--profile-id") {
|
|
42
|
+
const value = requireOptionValue(tokens, index, "--profile-id");
|
|
43
|
+
|
|
44
|
+
const trimmedValue = value.trim();
|
|
45
|
+
if (trimmedValue.length === 0) {
|
|
46
|
+
throw new Error("--profile-id must be a non-empty string.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
profileId = trimmedValue;
|
|
50
|
+
index += 1;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (token === "--intent-key") {
|
|
55
|
+
const value = requireOptionValue(tokens, index, "--intent-key");
|
|
56
|
+
|
|
57
|
+
const trimmedValue = value.trim();
|
|
58
|
+
if (trimmedValue.length === 0) {
|
|
59
|
+
throw new Error("--intent-key must be a non-empty string.");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
intentKey = trimmedValue;
|
|
63
|
+
index += 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new Error(`Unknown memory-list option: ${token}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
...(domain !== undefined ? { domain } : {}),
|
|
72
|
+
...(profileId !== undefined ? { profileId } : {}),
|
|
73
|
+
...(intentKey !== undefined ? { intentKey } : {})
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function runMemoryListCommand(args: string[]): Promise<MemoryListCommandResult> {
|
|
78
|
+
const context = parseCommandContext(args);
|
|
79
|
+
const options = parseListOptions(context.positional);
|
|
80
|
+
const profileId = options.profileId ?? context.profile;
|
|
81
|
+
|
|
82
|
+
return await callDaemonTool<MemoryListCommandResult>(
|
|
83
|
+
"browser.memory.list",
|
|
84
|
+
buildToolArguments(context, {
|
|
85
|
+
...(options.domain !== undefined ? { domain: options.domain } : {}),
|
|
86
|
+
...(profileId !== undefined ? { profileId } : {}),
|
|
87
|
+
...(options.intentKey !== undefined ? { intentKey: options.intentKey } : {})
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type MemoryModeSetCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
function parseMemoryMode(value: string): "off" | "ask" | "auto" {
|
|
7
|
+
const normalizedValue = value.trim().toLowerCase();
|
|
8
|
+
if (normalizedValue === "off" || normalizedValue === "ask" || normalizedValue === "auto") {
|
|
9
|
+
return normalizedValue;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
throw new Error("mode must be one of: off, ask, auto.");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function runMemoryModeSetCommand(args: string[]): Promise<MemoryModeSetCommandResult> {
|
|
16
|
+
const context = parseCommandContext(args);
|
|
17
|
+
const mode = parseMemoryMode(requirePositionalArg(context, 0, "mode"));
|
|
18
|
+
|
|
19
|
+
if (context.positional.length > 1) {
|
|
20
|
+
throw new Error(`Unknown memory-mode-set option: ${context.positional[1]}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return await callDaemonTool<MemoryModeSetCommandResult>(
|
|
24
|
+
"browser.memory.mode.set",
|
|
25
|
+
buildToolArguments(context, {
|
|
26
|
+
mode
|
|
27
|
+
})
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext } from "./common";
|
|
3
|
+
|
|
4
|
+
export type MemoryPurgeCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runMemoryPurgeCommand(args: string[]): Promise<MemoryPurgeCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
if (context.positional.length > 0) {
|
|
9
|
+
throw new Error(`Unknown memory-purge option: ${context.positional[0]}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return await callDaemonTool<MemoryPurgeCommandResult>(
|
|
13
|
+
"browser.memory.purge",
|
|
14
|
+
buildToolArguments(context)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type MemoryResolveCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
function requireOptionValue(tokens: string[], index: number, optionName: string): string {
|
|
7
|
+
const value = tokens[index + 1];
|
|
8
|
+
if (value === undefined || value.startsWith("--")) {
|
|
9
|
+
throw new Error(`Missing value for ${optionName}.`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseResolveOptions(tokens: string[]): {
|
|
16
|
+
profileId?: string;
|
|
17
|
+
} {
|
|
18
|
+
let profileId: string | undefined;
|
|
19
|
+
|
|
20
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
21
|
+
const token = tokens[index];
|
|
22
|
+
if (token === "--profile-id") {
|
|
23
|
+
const value = requireOptionValue(tokens, index, "--profile-id");
|
|
24
|
+
|
|
25
|
+
const trimmedValue = value.trim();
|
|
26
|
+
if (trimmedValue.length === 0) {
|
|
27
|
+
throw new Error("--profile-id must be a non-empty string.");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
profileId = trimmedValue;
|
|
31
|
+
index += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
throw new Error(`Unknown memory-resolve option: ${token}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return profileId === undefined ? {} : { profileId };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function runMemoryResolveCommand(args: string[]): Promise<MemoryResolveCommandResult> {
|
|
42
|
+
const context = parseCommandContext(args);
|
|
43
|
+
const domain = requirePositionalArg(context, 0, "domain");
|
|
44
|
+
const intentKey = requirePositionalArg(context, 1, "intentKey");
|
|
45
|
+
const options = parseResolveOptions(context.positional.slice(2));
|
|
46
|
+
const profileId = options.profileId ?? context.profile ?? "default";
|
|
47
|
+
|
|
48
|
+
return await callDaemonTool<MemoryResolveCommandResult>(
|
|
49
|
+
"browser.memory.resolve",
|
|
50
|
+
buildToolArguments(context, {
|
|
51
|
+
domain,
|
|
52
|
+
profileId,
|
|
53
|
+
intentKey
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext } from "./common";
|
|
3
|
+
|
|
4
|
+
export type MemoryStatusCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
export async function runMemoryStatusCommand(args: string[]): Promise<MemoryStatusCommandResult> {
|
|
7
|
+
const context = parseCommandContext(args);
|
|
8
|
+
if (context.positional.length > 0) {
|
|
9
|
+
throw new Error(`Unknown memory-status option: ${context.positional[0]}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return await callDaemonTool<MemoryStatusCommandResult>(
|
|
13
|
+
"browser.memory.status",
|
|
14
|
+
buildToolArguments(context)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type MemoryTtlSetCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
function parseNonNegativeIntegerString(value: string): number {
|
|
7
|
+
if (!/^(0|[1-9]\d*)$/.test(value)) {
|
|
8
|
+
throw new Error("ttlDays must be a non-negative integer string.");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return Number.parseInt(value, 10);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function runMemoryTtlSetCommand(args: string[]): Promise<MemoryTtlSetCommandResult> {
|
|
15
|
+
const context = parseCommandContext(args);
|
|
16
|
+
const ttlDays = parseNonNegativeIntegerString(requirePositionalArg(context, 0, "ttlDays"));
|
|
17
|
+
|
|
18
|
+
if (context.positional.length > 1) {
|
|
19
|
+
throw new Error(`Unknown memory-ttl-set option: ${context.positional[1]}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return await callDaemonTool<MemoryTtlSetCommandResult>(
|
|
23
|
+
"browser.memory.ttl.set",
|
|
24
|
+
buildToolArguments(context, {
|
|
25
|
+
ttlDays
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { callDaemonTool } from "../daemon-client";
|
|
2
|
+
import { buildToolArguments, parseCommandContext, requirePositionalArg } from "./common";
|
|
3
|
+
|
|
4
|
+
export type MemoryUpsertCommandResult = Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
type MemoryUpsertOptions = {
|
|
7
|
+
profileId?: string;
|
|
8
|
+
signals: unknown[];
|
|
9
|
+
confidence?: number;
|
|
10
|
+
confirmed?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function requireOptionValue(tokens: string[], index: number, optionName: string): string {
|
|
14
|
+
const value = tokens[index + 1];
|
|
15
|
+
if (value === undefined || value.startsWith("--")) {
|
|
16
|
+
throw new Error(`Missing value for ${optionName}.`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseNumber(value: string, optionName: string): number {
|
|
23
|
+
const parsed = Number(value);
|
|
24
|
+
if (!Number.isFinite(parsed)) {
|
|
25
|
+
throw new Error(`${optionName} must be a valid number.`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseBoolean(value: string, optionName: string): boolean {
|
|
32
|
+
const normalizedValue = value.trim().toLowerCase();
|
|
33
|
+
if (normalizedValue === "true") {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (normalizedValue === "false") {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
throw new Error(`${optionName} must be true or false when provided with a value.`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseSignalsJson(value: string): unknown[] {
|
|
45
|
+
let parsedValue: unknown;
|
|
46
|
+
try {
|
|
47
|
+
parsedValue = JSON.parse(value);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
50
|
+
throw new Error(`Invalid JSON for --signals-json: ${message}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!Array.isArray(parsedValue)) {
|
|
54
|
+
throw new Error("--signals-json must decode to a JSON array.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return parsedValue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseUpsertOptions(tokens: string[]): MemoryUpsertOptions {
|
|
61
|
+
let profileId: string | undefined;
|
|
62
|
+
let signals: unknown[] | undefined;
|
|
63
|
+
let confidence: number | undefined;
|
|
64
|
+
let confirmed: boolean | undefined;
|
|
65
|
+
|
|
66
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
67
|
+
const token = tokens[index];
|
|
68
|
+
if (token === "--profile-id") {
|
|
69
|
+
const value = requireOptionValue(tokens, index, "--profile-id");
|
|
70
|
+
|
|
71
|
+
const trimmedValue = value.trim();
|
|
72
|
+
if (trimmedValue.length === 0) {
|
|
73
|
+
throw new Error("--profile-id must be a non-empty string.");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
profileId = trimmedValue;
|
|
77
|
+
index += 1;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (token === "--signals-json") {
|
|
82
|
+
const value = requireOptionValue(tokens, index, "--signals-json");
|
|
83
|
+
|
|
84
|
+
signals = parseSignalsJson(value);
|
|
85
|
+
index += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (token === "--confidence") {
|
|
90
|
+
const value = requireOptionValue(tokens, index, "--confidence");
|
|
91
|
+
|
|
92
|
+
confidence = parseNumber(value, "--confidence");
|
|
93
|
+
index += 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (token === "--confirmed") {
|
|
98
|
+
const maybeValue = tokens[index + 1];
|
|
99
|
+
if (maybeValue === undefined || maybeValue.startsWith("--")) {
|
|
100
|
+
confirmed = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
confirmed = parseBoolean(maybeValue, "--confirmed");
|
|
105
|
+
index += 1;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
throw new Error(`Unknown memory-upsert option: ${token}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (signals === undefined) {
|
|
113
|
+
throw new Error("Missing value for --signals-json.");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
signals,
|
|
118
|
+
...(profileId !== undefined ? { profileId } : {}),
|
|
119
|
+
...(confidence !== undefined ? { confidence } : {}),
|
|
120
|
+
...(confirmed !== undefined ? { confirmed } : {})
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function runMemoryUpsertCommand(args: string[]): Promise<MemoryUpsertCommandResult> {
|
|
125
|
+
const context = parseCommandContext(args);
|
|
126
|
+
const domain = requirePositionalArg(context, 0, "domain");
|
|
127
|
+
const intentKey = requirePositionalArg(context, 1, "intentKey");
|
|
128
|
+
const options = parseUpsertOptions(context.positional.slice(2));
|
|
129
|
+
const profileId = options.profileId ?? context.profile ?? "default";
|
|
130
|
+
|
|
131
|
+
return await callDaemonTool<MemoryUpsertCommandResult>(
|
|
132
|
+
"browser.memory.upsert",
|
|
133
|
+
buildToolArguments(context, {
|
|
134
|
+
domain,
|
|
135
|
+
profileId,
|
|
136
|
+
intentKey,
|
|
137
|
+
signals: options.signals,
|
|
138
|
+
...(options.confidence !== undefined ? { confidence: options.confidence } : {}),
|
|
139
|
+
...(options.confirmed !== undefined ? { confirmed: options.confirmed } : {})
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
}
|