appduct 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -24
- package/dist/artifact-inspect.d.ts +38 -39
- package/dist/bin.js +106 -7
- package/dist/cli/result-types.d.ts +35 -2
- package/dist/cli/routes/daemon/index.js +90 -2
- package/dist/cli/routes/daemon/run.js +1088 -517
- package/dist/cli/routes/daemon/start.js +98 -2
- package/dist/cli/routes/daemon/status.js +99 -2
- package/dist/cli/routes/daemon/stop.js +98 -2
- package/dist/cli/routes/doctor.js +98 -3
- package/dist/cli/routes/events/index.d.ts +0 -3
- package/dist/cli/routes/events/index.js +92 -3
- package/dist/cli/routes/events/ls.d.ts +5 -0
- package/dist/cli/routes/events/ls.js +1837 -0
- package/dist/cli/routes/events/since.js +98 -2
- package/dist/cli/routes/events/tail.js +98 -2
- package/dist/cli/routes/init.js +265 -108
- package/dist/cli/routes/keygen.js +90 -2
- package/dist/cli/routes/mcp.js +243 -25
- package/dist/cli/routes/sessions/index.js +90 -2
- package/dist/cli/routes/sessions/link.js +199 -22
- package/dist/cli/routes/sessions/ls.js +98 -2
- package/dist/cli/routes/sessions/revoke.js +98 -2
- package/dist/cli/routes/tools/call.js +98 -2
- package/dist/cli/routes/tools/describe.js +98 -2
- package/dist/cli/routes/tools/index.js +90 -2
- package/dist/cli/routes/tools/ls.js +98 -2
- package/dist/client/attach-page.d.ts +39 -0
- package/dist/client/bootstrap.d.ts +17 -3
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.js +286 -112
- package/dist/commands/doctor.d.ts +3 -4
- package/dist/commands/events-ls.d.ts +21 -0
- package/dist/commands/init.d.ts +8 -2
- package/dist/commands/link.d.ts +10 -1
- package/dist/daemon/calls.d.ts +3 -1
- package/dist/daemon/config.d.ts +6 -0
- package/dist/daemon/daemon.d.ts +6 -1
- package/dist/daemon/event-bus.d.ts +10 -0
- package/dist/daemon/links.d.ts +9 -5
- package/dist/daemon/listener.d.ts +28 -1
- package/dist/daemon/registry.d.ts +20 -12
- package/dist/daemon/rpc-errors.d.ts +6 -0
- package/dist/daemon/sessions.d.ts +14 -8
- package/dist/daemon/tls.d.ts +0 -3
- package/dist/devtools-relay/attach-tab.d.ts +33 -0
- package/dist/devtools-relay/index.d.ts +8 -0
- package/dist/devtools-relay/memory-daemon-sockets.d.ts +20 -0
- package/dist/devtools-relay/memory-devtools-browser.d.ts +10 -0
- package/dist/devtools-relay/memory-page-channel.d.ts +16 -0
- package/dist/devtools-relay/node-daemon-socket.d.ts +3 -0
- package/dist/devtools-relay/node-devtools-browser.d.ts +3 -0
- package/dist/devtools-relay/ports.d.ts +48 -0
- package/dist/devtools-relay/relay.d.ts +16 -0
- package/dist/index.js +106 -7
- package/dist/link.d.ts +35 -1
- package/dist/mcp/app-tools.d.ts +19 -0
- package/dist/mcp/connect-tool.d.ts +15 -2
- package/dist/mcp/events-tool.d.ts +39 -0
- package/dist/native-scheme.d.ts +11 -7
- package/dist/scheme.d.ts +5 -1
- package/package.json +15 -5
|
@@ -44,6 +44,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
44
44
|
"daemonLogMaxBytes",
|
|
45
45
|
"eventsLogMaxBytes",
|
|
46
46
|
"policy",
|
|
47
|
+
"webOrigins",
|
|
47
48
|
"advertisedIp",
|
|
48
49
|
"scheme",
|
|
49
50
|
"restartDaemonOnVersionMismatch"
|
|
@@ -104,6 +105,7 @@ var defaultConfig = (paths) => {
|
|
|
104
105
|
daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
|
|
105
106
|
eventsLogMaxBytes: 10 * 1024 * 1024,
|
|
106
107
|
restartDaemonOnVersionMismatch: false,
|
|
108
|
+
webOrigins: [],
|
|
107
109
|
policy: {
|
|
108
110
|
default: "allow",
|
|
109
111
|
destructive: "allow"
|
|
@@ -174,6 +176,12 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
174
176
|
"restartDaemonOnVersionMismatch"
|
|
175
177
|
);
|
|
176
178
|
}
|
|
179
|
+
if (parsed.webOrigins !== void 0) {
|
|
180
|
+
if (!Array.isArray(parsed.webOrigins)) {
|
|
181
|
+
throw configError("webOrigins", "must be an array of origin strings.");
|
|
182
|
+
}
|
|
183
|
+
config.webOrigins = parsed.webOrigins.map((origin) => requireNonEmptyString(origin, "webOrigins"));
|
|
184
|
+
}
|
|
177
185
|
if (parsed.policy !== void 0) {
|
|
178
186
|
if (typeof parsed.policy !== "object" || parsed.policy === null || Array.isArray(parsed.policy)) {
|
|
179
187
|
throw configError("policy", "must be an object.");
|
|
@@ -1077,7 +1085,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
|
|
|
1077
1085
|
// EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
|
|
1078
1086
|
// missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
|
|
1079
1087
|
// code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
|
|
1080
|
-
// assertion failure
|
|
1088
|
+
// assertion failure.
|
|
1081
1089
|
inspection_error: 66,
|
|
1082
1090
|
connection_error: 70,
|
|
1083
1091
|
session_error: 71,
|
|
@@ -1239,6 +1247,7 @@ var formatJson = (value, flags) => {
|
|
|
1239
1247
|
import pc from "picocolors";
|
|
1240
1248
|
import {
|
|
1241
1249
|
formatAgentWebSocketUrl,
|
|
1250
|
+
renderEventSignature,
|
|
1242
1251
|
renderToolSignature,
|
|
1243
1252
|
summarizeToolDescription
|
|
1244
1253
|
} from "@appduct/shared";
|
|
@@ -1334,6 +1343,13 @@ var formatDevice = (device) => {
|
|
|
1334
1343
|
const label = parts.length > 0 ? parts.join(" ") : "unknown device";
|
|
1335
1344
|
return device.os ? `${label} (${device.os})` : label;
|
|
1336
1345
|
};
|
|
1346
|
+
var SUSPEND_REASON_LABELS = {
|
|
1347
|
+
app_backgrounded: "app in background",
|
|
1348
|
+
connection_lost: "connection lost"
|
|
1349
|
+
};
|
|
1350
|
+
var formatSessionState = (session) => {
|
|
1351
|
+
return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
|
|
1352
|
+
};
|
|
1337
1353
|
var renderLsData = (colors, data, now) => {
|
|
1338
1354
|
if (data.length === 0) {
|
|
1339
1355
|
return [colors.dim("Sessions"), " No Appduct sessions are registered."];
|
|
@@ -1342,7 +1358,7 @@ var renderLsData = (colors, data, now) => {
|
|
|
1342
1358
|
const headers = ["Alias", "State", "Device", "Tools", "Age"];
|
|
1343
1359
|
const rows = data.map((session) => [
|
|
1344
1360
|
session.alias,
|
|
1345
|
-
session
|
|
1361
|
+
formatSessionState(session),
|
|
1346
1362
|
formatDevice(session.device),
|
|
1347
1363
|
String(session.toolCount),
|
|
1348
1364
|
formatAge(session.claimedAt ?? session.createdAt, nowIso)
|
|
@@ -1516,10 +1532,87 @@ var renderToolsListData = (colors, data, flags, full) => {
|
|
|
1516
1532
|
}
|
|
1517
1533
|
return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
|
|
1518
1534
|
};
|
|
1535
|
+
var renderEventsTruncationLine = (data) => {
|
|
1536
|
+
const offset = data.offset ?? 0;
|
|
1537
|
+
if (data.events.length >= data.total) {
|
|
1538
|
+
return [];
|
|
1539
|
+
}
|
|
1540
|
+
return [
|
|
1541
|
+
"",
|
|
1542
|
+
`Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
|
|
1543
|
+
];
|
|
1544
|
+
};
|
|
1545
|
+
var renderEventsListData = (colors, data, flags) => {
|
|
1546
|
+
const [only] = data.events;
|
|
1547
|
+
if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
|
|
1548
|
+
return renderFields(
|
|
1549
|
+
colors.green(`Event: ${only.name}`),
|
|
1550
|
+
[
|
|
1551
|
+
["Signature", renderEventSignature(only)],
|
|
1552
|
+
["Description", only.description],
|
|
1553
|
+
["Payload schema", only.payload_schema]
|
|
1554
|
+
],
|
|
1555
|
+
flags
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
if (data.events.length === 0 && data.total > 0) {
|
|
1559
|
+
return [
|
|
1560
|
+
colors.green("Events"),
|
|
1561
|
+
` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
|
|
1562
|
+
];
|
|
1563
|
+
}
|
|
1564
|
+
if (data.events.length === 0) {
|
|
1565
|
+
return [
|
|
1566
|
+
colors.green("Events"),
|
|
1567
|
+
data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
|
|
1568
|
+
];
|
|
1569
|
+
}
|
|
1570
|
+
const lines = data.events.flatMap((event) => [
|
|
1571
|
+
` ${renderEventSignature(event)}`,
|
|
1572
|
+
` ${summarizeToolDescription(event.description)}`
|
|
1573
|
+
]);
|
|
1574
|
+
return [
|
|
1575
|
+
colors.green("Events"),
|
|
1576
|
+
...lines,
|
|
1577
|
+
...renderEventsTruncationLine(data),
|
|
1578
|
+
"",
|
|
1579
|
+
"Run `appduct events ls --name <name>` for an event's full payload schema."
|
|
1580
|
+
];
|
|
1581
|
+
};
|
|
1519
1582
|
var renderInvokeData = (colors, data, flags) => {
|
|
1520
1583
|
return [colors.green("Result"), formatScalar(data, flags)];
|
|
1521
1584
|
};
|
|
1522
1585
|
var renderLinkData = (colors, data, flags, qr) => {
|
|
1586
|
+
if ("attached" in data) {
|
|
1587
|
+
return [
|
|
1588
|
+
colors.green("Page Attached"),
|
|
1589
|
+
...renderFields(
|
|
1590
|
+
"Page",
|
|
1591
|
+
[
|
|
1592
|
+
["Session", data.sessionId],
|
|
1593
|
+
["Tab", data.targetId],
|
|
1594
|
+
["URL", data.url],
|
|
1595
|
+
["Expires", new Date(data.expiresAt * 1e3).toISOString()]
|
|
1596
|
+
],
|
|
1597
|
+
flags
|
|
1598
|
+
)
|
|
1599
|
+
];
|
|
1600
|
+
}
|
|
1601
|
+
if ("script" in data) {
|
|
1602
|
+
return [
|
|
1603
|
+
colors.green("Link Created"),
|
|
1604
|
+
...renderFields(
|
|
1605
|
+
"Link",
|
|
1606
|
+
[
|
|
1607
|
+
["Session", data.sessionId],
|
|
1608
|
+
["URL", data.url],
|
|
1609
|
+
["Script", data.script],
|
|
1610
|
+
["Expires", new Date(data.expiresAt * 1e3).toISOString()]
|
|
1611
|
+
],
|
|
1612
|
+
flags
|
|
1613
|
+
)
|
|
1614
|
+
];
|
|
1615
|
+
}
|
|
1523
1616
|
const lines = [
|
|
1524
1617
|
colors.green("Link Created"),
|
|
1525
1618
|
...renderFields(
|
|
@@ -1640,6 +1733,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
|
|
|
1640
1733
|
["PID", data.daemon.pid],
|
|
1641
1734
|
["Started at", data.daemon.started_at],
|
|
1642
1735
|
["WSS port", data.daemon.wss_port],
|
|
1736
|
+
["Web port", data.daemon.web_port],
|
|
1643
1737
|
["Pinned keys", data.daemon.pinned_keys.length],
|
|
1644
1738
|
["Sessions", data.daemon.session_count]
|
|
1645
1739
|
],
|
|
@@ -1707,6 +1801,8 @@ var renderSuccessData = (colors, command, data, options) => {
|
|
|
1707
1801
|
return renderToolsListData(colors, data, flags, options.full);
|
|
1708
1802
|
case "tools describe":
|
|
1709
1803
|
return renderToolDetail(colors, data, flags);
|
|
1804
|
+
case "events ls":
|
|
1805
|
+
return renderEventsListData(colors, data, flags);
|
|
1710
1806
|
case "tools call":
|
|
1711
1807
|
return renderInvokeData(colors, data, flags);
|
|
1712
1808
|
case "sessions revoke":
|
|
@@ -24,6 +24,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
24
24
|
"daemonLogMaxBytes",
|
|
25
25
|
"eventsLogMaxBytes",
|
|
26
26
|
"policy",
|
|
27
|
+
"webOrigins",
|
|
27
28
|
"advertisedIp",
|
|
28
29
|
"scheme",
|
|
29
30
|
"restartDaemonOnVersionMismatch"
|
|
@@ -84,6 +85,7 @@ var defaultConfig = (paths) => {
|
|
|
84
85
|
daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
|
|
85
86
|
eventsLogMaxBytes: 10 * 1024 * 1024,
|
|
86
87
|
restartDaemonOnVersionMismatch: false,
|
|
88
|
+
webOrigins: [],
|
|
87
89
|
policy: {
|
|
88
90
|
default: "allow",
|
|
89
91
|
destructive: "allow"
|
|
@@ -154,6 +156,12 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
154
156
|
"restartDaemonOnVersionMismatch"
|
|
155
157
|
);
|
|
156
158
|
}
|
|
159
|
+
if (parsed.webOrigins !== void 0) {
|
|
160
|
+
if (!Array.isArray(parsed.webOrigins)) {
|
|
161
|
+
throw configError("webOrigins", "must be an array of origin strings.");
|
|
162
|
+
}
|
|
163
|
+
config.webOrigins = parsed.webOrigins.map((origin) => requireNonEmptyString(origin, "webOrigins"));
|
|
164
|
+
}
|
|
157
165
|
if (parsed.policy !== void 0) {
|
|
158
166
|
if (typeof parsed.policy !== "object" || parsed.policy === null || Array.isArray(parsed.policy)) {
|
|
159
167
|
throw configError("policy", "must be an object.");
|
|
@@ -913,7 +921,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
|
|
|
913
921
|
// EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
|
|
914
922
|
// missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
|
|
915
923
|
// code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
|
|
916
|
-
// assertion failure
|
|
924
|
+
// assertion failure.
|
|
917
925
|
inspection_error: 66,
|
|
918
926
|
connection_error: 70,
|
|
919
927
|
session_error: 71,
|
|
@@ -1181,6 +1189,7 @@ var formatJson = (value, flags) => {
|
|
|
1181
1189
|
import pc from "picocolors";
|
|
1182
1190
|
import {
|
|
1183
1191
|
formatAgentWebSocketUrl,
|
|
1192
|
+
renderEventSignature,
|
|
1184
1193
|
renderToolSignature,
|
|
1185
1194
|
summarizeToolDescription
|
|
1186
1195
|
} from "@appduct/shared";
|
|
@@ -1276,6 +1285,13 @@ var formatDevice = (device) => {
|
|
|
1276
1285
|
const label = parts.length > 0 ? parts.join(" ") : "unknown device";
|
|
1277
1286
|
return device.os ? `${label} (${device.os})` : label;
|
|
1278
1287
|
};
|
|
1288
|
+
var SUSPEND_REASON_LABELS = {
|
|
1289
|
+
app_backgrounded: "app in background",
|
|
1290
|
+
connection_lost: "connection lost"
|
|
1291
|
+
};
|
|
1292
|
+
var formatSessionState = (session) => {
|
|
1293
|
+
return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
|
|
1294
|
+
};
|
|
1279
1295
|
var renderLsData = (colors, data, now) => {
|
|
1280
1296
|
if (data.length === 0) {
|
|
1281
1297
|
return [colors.dim("Sessions"), " No Appduct sessions are registered."];
|
|
@@ -1284,7 +1300,7 @@ var renderLsData = (colors, data, now) => {
|
|
|
1284
1300
|
const headers = ["Alias", "State", "Device", "Tools", "Age"];
|
|
1285
1301
|
const rows = data.map((session) => [
|
|
1286
1302
|
session.alias,
|
|
1287
|
-
session
|
|
1303
|
+
formatSessionState(session),
|
|
1288
1304
|
formatDevice(session.device),
|
|
1289
1305
|
String(session.toolCount),
|
|
1290
1306
|
formatAge(session.claimedAt ?? session.createdAt, nowIso)
|
|
@@ -1458,10 +1474,87 @@ var renderToolsListData = (colors, data, flags, full) => {
|
|
|
1458
1474
|
}
|
|
1459
1475
|
return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
|
|
1460
1476
|
};
|
|
1477
|
+
var renderEventsTruncationLine = (data) => {
|
|
1478
|
+
const offset = data.offset ?? 0;
|
|
1479
|
+
if (data.events.length >= data.total) {
|
|
1480
|
+
return [];
|
|
1481
|
+
}
|
|
1482
|
+
return [
|
|
1483
|
+
"",
|
|
1484
|
+
`Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
|
|
1485
|
+
];
|
|
1486
|
+
};
|
|
1487
|
+
var renderEventsListData = (colors, data, flags) => {
|
|
1488
|
+
const [only] = data.events;
|
|
1489
|
+
if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
|
|
1490
|
+
return renderFields(
|
|
1491
|
+
colors.green(`Event: ${only.name}`),
|
|
1492
|
+
[
|
|
1493
|
+
["Signature", renderEventSignature(only)],
|
|
1494
|
+
["Description", only.description],
|
|
1495
|
+
["Payload schema", only.payload_schema]
|
|
1496
|
+
],
|
|
1497
|
+
flags
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
if (data.events.length === 0 && data.total > 0) {
|
|
1501
|
+
return [
|
|
1502
|
+
colors.green("Events"),
|
|
1503
|
+
` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
|
|
1504
|
+
];
|
|
1505
|
+
}
|
|
1506
|
+
if (data.events.length === 0) {
|
|
1507
|
+
return [
|
|
1508
|
+
colors.green("Events"),
|
|
1509
|
+
data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
|
|
1510
|
+
];
|
|
1511
|
+
}
|
|
1512
|
+
const lines = data.events.flatMap((event) => [
|
|
1513
|
+
` ${renderEventSignature(event)}`,
|
|
1514
|
+
` ${summarizeToolDescription(event.description)}`
|
|
1515
|
+
]);
|
|
1516
|
+
return [
|
|
1517
|
+
colors.green("Events"),
|
|
1518
|
+
...lines,
|
|
1519
|
+
...renderEventsTruncationLine(data),
|
|
1520
|
+
"",
|
|
1521
|
+
"Run `appduct events ls --name <name>` for an event's full payload schema."
|
|
1522
|
+
];
|
|
1523
|
+
};
|
|
1461
1524
|
var renderInvokeData = (colors, data, flags) => {
|
|
1462
1525
|
return [colors.green("Result"), formatScalar(data, flags)];
|
|
1463
1526
|
};
|
|
1464
1527
|
var renderLinkData = (colors, data, flags, qr) => {
|
|
1528
|
+
if ("attached" in data) {
|
|
1529
|
+
return [
|
|
1530
|
+
colors.green("Page Attached"),
|
|
1531
|
+
...renderFields(
|
|
1532
|
+
"Page",
|
|
1533
|
+
[
|
|
1534
|
+
["Session", data.sessionId],
|
|
1535
|
+
["Tab", data.targetId],
|
|
1536
|
+
["URL", data.url],
|
|
1537
|
+
["Expires", new Date(data.expiresAt * 1e3).toISOString()]
|
|
1538
|
+
],
|
|
1539
|
+
flags
|
|
1540
|
+
)
|
|
1541
|
+
];
|
|
1542
|
+
}
|
|
1543
|
+
if ("script" in data) {
|
|
1544
|
+
return [
|
|
1545
|
+
colors.green("Link Created"),
|
|
1546
|
+
...renderFields(
|
|
1547
|
+
"Link",
|
|
1548
|
+
[
|
|
1549
|
+
["Session", data.sessionId],
|
|
1550
|
+
["URL", data.url],
|
|
1551
|
+
["Script", data.script],
|
|
1552
|
+
["Expires", new Date(data.expiresAt * 1e3).toISOString()]
|
|
1553
|
+
],
|
|
1554
|
+
flags
|
|
1555
|
+
)
|
|
1556
|
+
];
|
|
1557
|
+
}
|
|
1465
1558
|
const lines = [
|
|
1466
1559
|
colors.green("Link Created"),
|
|
1467
1560
|
...renderFields(
|
|
@@ -1582,6 +1675,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
|
|
|
1582
1675
|
["PID", data.daemon.pid],
|
|
1583
1676
|
["Started at", data.daemon.started_at],
|
|
1584
1677
|
["WSS port", data.daemon.wss_port],
|
|
1678
|
+
["Web port", data.daemon.web_port],
|
|
1585
1679
|
["Pinned keys", data.daemon.pinned_keys.length],
|
|
1586
1680
|
["Sessions", data.daemon.session_count]
|
|
1587
1681
|
],
|
|
@@ -1649,6 +1743,8 @@ var renderSuccessData = (colors, command, data, options) => {
|
|
|
1649
1743
|
return renderToolsListData(colors, data, flags, options.full);
|
|
1650
1744
|
case "tools describe":
|
|
1651
1745
|
return renderToolDetail(colors, data, flags);
|
|
1746
|
+
case "events ls":
|
|
1747
|
+
return renderEventsListData(colors, data, flags);
|
|
1652
1748
|
case "tools call":
|
|
1653
1749
|
return renderInvokeData(colors, data, flags);
|
|
1654
1750
|
case "sessions revoke":
|
|
@@ -93,7 +93,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
|
|
|
93
93
|
// EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
|
|
94
94
|
// missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
|
|
95
95
|
// code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
|
|
96
|
-
// assertion failure
|
|
96
|
+
// assertion failure.
|
|
97
97
|
inspection_error: 66,
|
|
98
98
|
connection_error: 70,
|
|
99
99
|
session_error: 71,
|
|
@@ -213,6 +213,7 @@ var formatJson = (value, flags) => {
|
|
|
213
213
|
import pc from "picocolors";
|
|
214
214
|
import {
|
|
215
215
|
formatAgentWebSocketUrl,
|
|
216
|
+
renderEventSignature,
|
|
216
217
|
renderToolSignature,
|
|
217
218
|
summarizeToolDescription
|
|
218
219
|
} from "@appduct/shared";
|
|
@@ -308,6 +309,13 @@ var formatDevice = (device) => {
|
|
|
308
309
|
const label = parts.length > 0 ? parts.join(" ") : "unknown device";
|
|
309
310
|
return device.os ? `${label} (${device.os})` : label;
|
|
310
311
|
};
|
|
312
|
+
var SUSPEND_REASON_LABELS = {
|
|
313
|
+
app_backgrounded: "app in background",
|
|
314
|
+
connection_lost: "connection lost"
|
|
315
|
+
};
|
|
316
|
+
var formatSessionState = (session) => {
|
|
317
|
+
return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
|
|
318
|
+
};
|
|
311
319
|
var renderLsData = (colors, data, now) => {
|
|
312
320
|
if (data.length === 0) {
|
|
313
321
|
return [colors.dim("Sessions"), " No Appduct sessions are registered."];
|
|
@@ -316,7 +324,7 @@ var renderLsData = (colors, data, now) => {
|
|
|
316
324
|
const headers = ["Alias", "State", "Device", "Tools", "Age"];
|
|
317
325
|
const rows = data.map((session) => [
|
|
318
326
|
session.alias,
|
|
319
|
-
session
|
|
327
|
+
formatSessionState(session),
|
|
320
328
|
formatDevice(session.device),
|
|
321
329
|
String(session.toolCount),
|
|
322
330
|
formatAge(session.claimedAt ?? session.createdAt, nowIso)
|
|
@@ -490,10 +498,87 @@ var renderToolsListData = (colors, data, flags, full) => {
|
|
|
490
498
|
}
|
|
491
499
|
return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
|
|
492
500
|
};
|
|
501
|
+
var renderEventsTruncationLine = (data) => {
|
|
502
|
+
const offset = data.offset ?? 0;
|
|
503
|
+
if (data.events.length >= data.total) {
|
|
504
|
+
return [];
|
|
505
|
+
}
|
|
506
|
+
return [
|
|
507
|
+
"",
|
|
508
|
+
`Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
|
|
509
|
+
];
|
|
510
|
+
};
|
|
511
|
+
var renderEventsListData = (colors, data, flags) => {
|
|
512
|
+
const [only] = data.events;
|
|
513
|
+
if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
|
|
514
|
+
return renderFields(
|
|
515
|
+
colors.green(`Event: ${only.name}`),
|
|
516
|
+
[
|
|
517
|
+
["Signature", renderEventSignature(only)],
|
|
518
|
+
["Description", only.description],
|
|
519
|
+
["Payload schema", only.payload_schema]
|
|
520
|
+
],
|
|
521
|
+
flags
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
if (data.events.length === 0 && data.total > 0) {
|
|
525
|
+
return [
|
|
526
|
+
colors.green("Events"),
|
|
527
|
+
` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
|
|
528
|
+
];
|
|
529
|
+
}
|
|
530
|
+
if (data.events.length === 0) {
|
|
531
|
+
return [
|
|
532
|
+
colors.green("Events"),
|
|
533
|
+
data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
|
|
534
|
+
];
|
|
535
|
+
}
|
|
536
|
+
const lines = data.events.flatMap((event) => [
|
|
537
|
+
` ${renderEventSignature(event)}`,
|
|
538
|
+
` ${summarizeToolDescription(event.description)}`
|
|
539
|
+
]);
|
|
540
|
+
return [
|
|
541
|
+
colors.green("Events"),
|
|
542
|
+
...lines,
|
|
543
|
+
...renderEventsTruncationLine(data),
|
|
544
|
+
"",
|
|
545
|
+
"Run `appduct events ls --name <name>` for an event's full payload schema."
|
|
546
|
+
];
|
|
547
|
+
};
|
|
493
548
|
var renderInvokeData = (colors, data, flags) => {
|
|
494
549
|
return [colors.green("Result"), formatScalar(data, flags)];
|
|
495
550
|
};
|
|
496
551
|
var renderLinkData = (colors, data, flags, qr) => {
|
|
552
|
+
if ("attached" in data) {
|
|
553
|
+
return [
|
|
554
|
+
colors.green("Page Attached"),
|
|
555
|
+
...renderFields(
|
|
556
|
+
"Page",
|
|
557
|
+
[
|
|
558
|
+
["Session", data.sessionId],
|
|
559
|
+
["Tab", data.targetId],
|
|
560
|
+
["URL", data.url],
|
|
561
|
+
["Expires", new Date(data.expiresAt * 1e3).toISOString()]
|
|
562
|
+
],
|
|
563
|
+
flags
|
|
564
|
+
)
|
|
565
|
+
];
|
|
566
|
+
}
|
|
567
|
+
if ("script" in data) {
|
|
568
|
+
return [
|
|
569
|
+
colors.green("Link Created"),
|
|
570
|
+
...renderFields(
|
|
571
|
+
"Link",
|
|
572
|
+
[
|
|
573
|
+
["Session", data.sessionId],
|
|
574
|
+
["URL", data.url],
|
|
575
|
+
["Script", data.script],
|
|
576
|
+
["Expires", new Date(data.expiresAt * 1e3).toISOString()]
|
|
577
|
+
],
|
|
578
|
+
flags
|
|
579
|
+
)
|
|
580
|
+
];
|
|
581
|
+
}
|
|
497
582
|
const lines = [
|
|
498
583
|
colors.green("Link Created"),
|
|
499
584
|
...renderFields(
|
|
@@ -614,6 +699,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
|
|
|
614
699
|
["PID", data.daemon.pid],
|
|
615
700
|
["Started at", data.daemon.started_at],
|
|
616
701
|
["WSS port", data.daemon.wss_port],
|
|
702
|
+
["Web port", data.daemon.web_port],
|
|
617
703
|
["Pinned keys", data.daemon.pinned_keys.length],
|
|
618
704
|
["Sessions", data.daemon.session_count]
|
|
619
705
|
],
|
|
@@ -681,6 +767,8 @@ var renderSuccessData = (colors, command, data, options) => {
|
|
|
681
767
|
return renderToolsListData(colors, data, flags, options.full);
|
|
682
768
|
case "tools describe":
|
|
683
769
|
return renderToolDetail(colors, data, flags);
|
|
770
|
+
case "events ls":
|
|
771
|
+
return renderEventsListData(colors, data, flags);
|
|
684
772
|
case "tools call":
|
|
685
773
|
return renderInvokeData(colors, data, flags);
|
|
686
774
|
case "sessions revoke":
|
|
@@ -24,6 +24,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
24
24
|
"daemonLogMaxBytes",
|
|
25
25
|
"eventsLogMaxBytes",
|
|
26
26
|
"policy",
|
|
27
|
+
"webOrigins",
|
|
27
28
|
"advertisedIp",
|
|
28
29
|
"scheme",
|
|
29
30
|
"restartDaemonOnVersionMismatch"
|
|
@@ -84,6 +85,7 @@ var defaultConfig = (paths) => {
|
|
|
84
85
|
daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
|
|
85
86
|
eventsLogMaxBytes: 10 * 1024 * 1024,
|
|
86
87
|
restartDaemonOnVersionMismatch: false,
|
|
88
|
+
webOrigins: [],
|
|
87
89
|
policy: {
|
|
88
90
|
default: "allow",
|
|
89
91
|
destructive: "allow"
|
|
@@ -154,6 +156,12 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
154
156
|
"restartDaemonOnVersionMismatch"
|
|
155
157
|
);
|
|
156
158
|
}
|
|
159
|
+
if (parsed.webOrigins !== void 0) {
|
|
160
|
+
if (!Array.isArray(parsed.webOrigins)) {
|
|
161
|
+
throw configError("webOrigins", "must be an array of origin strings.");
|
|
162
|
+
}
|
|
163
|
+
config.webOrigins = parsed.webOrigins.map((origin) => requireNonEmptyString(origin, "webOrigins"));
|
|
164
|
+
}
|
|
157
165
|
if (parsed.policy !== void 0) {
|
|
158
166
|
if (typeof parsed.policy !== "object" || parsed.policy === null || Array.isArray(parsed.policy)) {
|
|
159
167
|
throw configError("policy", "must be an object.");
|
|
@@ -913,7 +921,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
|
|
|
913
921
|
// EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
|
|
914
922
|
// missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
|
|
915
923
|
// code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
|
|
916
|
-
// assertion failure
|
|
924
|
+
// assertion failure.
|
|
917
925
|
inspection_error: 66,
|
|
918
926
|
connection_error: 70,
|
|
919
927
|
session_error: 71,
|
|
@@ -1175,6 +1183,7 @@ var formatJson = (value, flags) => {
|
|
|
1175
1183
|
import pc from "picocolors";
|
|
1176
1184
|
import {
|
|
1177
1185
|
formatAgentWebSocketUrl,
|
|
1186
|
+
renderEventSignature,
|
|
1178
1187
|
renderToolSignature,
|
|
1179
1188
|
summarizeToolDescription
|
|
1180
1189
|
} from "@appduct/shared";
|
|
@@ -1270,6 +1279,13 @@ var formatDevice = (device) => {
|
|
|
1270
1279
|
const label = parts.length > 0 ? parts.join(" ") : "unknown device";
|
|
1271
1280
|
return device.os ? `${label} (${device.os})` : label;
|
|
1272
1281
|
};
|
|
1282
|
+
var SUSPEND_REASON_LABELS = {
|
|
1283
|
+
app_backgrounded: "app in background",
|
|
1284
|
+
connection_lost: "connection lost"
|
|
1285
|
+
};
|
|
1286
|
+
var formatSessionState = (session) => {
|
|
1287
|
+
return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
|
|
1288
|
+
};
|
|
1273
1289
|
var renderLsData = (colors, data, now) => {
|
|
1274
1290
|
if (data.length === 0) {
|
|
1275
1291
|
return [colors.dim("Sessions"), " No Appduct sessions are registered."];
|
|
@@ -1278,7 +1294,7 @@ var renderLsData = (colors, data, now) => {
|
|
|
1278
1294
|
const headers = ["Alias", "State", "Device", "Tools", "Age"];
|
|
1279
1295
|
const rows = data.map((session) => [
|
|
1280
1296
|
session.alias,
|
|
1281
|
-
session
|
|
1297
|
+
formatSessionState(session),
|
|
1282
1298
|
formatDevice(session.device),
|
|
1283
1299
|
String(session.toolCount),
|
|
1284
1300
|
formatAge(session.claimedAt ?? session.createdAt, nowIso)
|
|
@@ -1452,10 +1468,87 @@ var renderToolsListData = (colors, data, flags, full) => {
|
|
|
1452
1468
|
}
|
|
1453
1469
|
return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
|
|
1454
1470
|
};
|
|
1471
|
+
var renderEventsTruncationLine = (data) => {
|
|
1472
|
+
const offset = data.offset ?? 0;
|
|
1473
|
+
if (data.events.length >= data.total) {
|
|
1474
|
+
return [];
|
|
1475
|
+
}
|
|
1476
|
+
return [
|
|
1477
|
+
"",
|
|
1478
|
+
`Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
|
|
1479
|
+
];
|
|
1480
|
+
};
|
|
1481
|
+
var renderEventsListData = (colors, data, flags) => {
|
|
1482
|
+
const [only] = data.events;
|
|
1483
|
+
if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
|
|
1484
|
+
return renderFields(
|
|
1485
|
+
colors.green(`Event: ${only.name}`),
|
|
1486
|
+
[
|
|
1487
|
+
["Signature", renderEventSignature(only)],
|
|
1488
|
+
["Description", only.description],
|
|
1489
|
+
["Payload schema", only.payload_schema]
|
|
1490
|
+
],
|
|
1491
|
+
flags
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
if (data.events.length === 0 && data.total > 0) {
|
|
1495
|
+
return [
|
|
1496
|
+
colors.green("Events"),
|
|
1497
|
+
` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
|
|
1498
|
+
];
|
|
1499
|
+
}
|
|
1500
|
+
if (data.events.length === 0) {
|
|
1501
|
+
return [
|
|
1502
|
+
colors.green("Events"),
|
|
1503
|
+
data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
|
|
1504
|
+
];
|
|
1505
|
+
}
|
|
1506
|
+
const lines = data.events.flatMap((event) => [
|
|
1507
|
+
` ${renderEventSignature(event)}`,
|
|
1508
|
+
` ${summarizeToolDescription(event.description)}`
|
|
1509
|
+
]);
|
|
1510
|
+
return [
|
|
1511
|
+
colors.green("Events"),
|
|
1512
|
+
...lines,
|
|
1513
|
+
...renderEventsTruncationLine(data),
|
|
1514
|
+
"",
|
|
1515
|
+
"Run `appduct events ls --name <name>` for an event's full payload schema."
|
|
1516
|
+
];
|
|
1517
|
+
};
|
|
1455
1518
|
var renderInvokeData = (colors, data, flags) => {
|
|
1456
1519
|
return [colors.green("Result"), formatScalar(data, flags)];
|
|
1457
1520
|
};
|
|
1458
1521
|
var renderLinkData = (colors, data, flags, qr) => {
|
|
1522
|
+
if ("attached" in data) {
|
|
1523
|
+
return [
|
|
1524
|
+
colors.green("Page Attached"),
|
|
1525
|
+
...renderFields(
|
|
1526
|
+
"Page",
|
|
1527
|
+
[
|
|
1528
|
+
["Session", data.sessionId],
|
|
1529
|
+
["Tab", data.targetId],
|
|
1530
|
+
["URL", data.url],
|
|
1531
|
+
["Expires", new Date(data.expiresAt * 1e3).toISOString()]
|
|
1532
|
+
],
|
|
1533
|
+
flags
|
|
1534
|
+
)
|
|
1535
|
+
];
|
|
1536
|
+
}
|
|
1537
|
+
if ("script" in data) {
|
|
1538
|
+
return [
|
|
1539
|
+
colors.green("Link Created"),
|
|
1540
|
+
...renderFields(
|
|
1541
|
+
"Link",
|
|
1542
|
+
[
|
|
1543
|
+
["Session", data.sessionId],
|
|
1544
|
+
["URL", data.url],
|
|
1545
|
+
["Script", data.script],
|
|
1546
|
+
["Expires", new Date(data.expiresAt * 1e3).toISOString()]
|
|
1547
|
+
],
|
|
1548
|
+
flags
|
|
1549
|
+
)
|
|
1550
|
+
];
|
|
1551
|
+
}
|
|
1459
1552
|
const lines = [
|
|
1460
1553
|
colors.green("Link Created"),
|
|
1461
1554
|
...renderFields(
|
|
@@ -1576,6 +1669,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
|
|
|
1576
1669
|
["PID", data.daemon.pid],
|
|
1577
1670
|
["Started at", data.daemon.started_at],
|
|
1578
1671
|
["WSS port", data.daemon.wss_port],
|
|
1672
|
+
["Web port", data.daemon.web_port],
|
|
1579
1673
|
["Pinned keys", data.daemon.pinned_keys.length],
|
|
1580
1674
|
["Sessions", data.daemon.session_count]
|
|
1581
1675
|
],
|
|
@@ -1643,6 +1737,8 @@ var renderSuccessData = (colors, command, data, options) => {
|
|
|
1643
1737
|
return renderToolsListData(colors, data, flags, options.full);
|
|
1644
1738
|
case "tools describe":
|
|
1645
1739
|
return renderToolDetail(colors, data, flags);
|
|
1740
|
+
case "events ls":
|
|
1741
|
+
return renderEventsListData(colors, data, flags);
|
|
1646
1742
|
case "tools call":
|
|
1647
1743
|
return renderInvokeData(colors, data, flags);
|
|
1648
1744
|
case "sessions revoke":
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `attachPage()` (issue #177): runs a web page's Appduct session through a Playwright page instead of
|
|
3
|
+
* a socket the page opens itself. The page and the daemon talk through a binding Playwright exposes,
|
|
4
|
+
* so it works on an `https` page with no permission prompt and no debugging port. The binding belongs
|
|
5
|
+
* to the page's target: a popup, or a navigation that creates a new target, is not relayed, and the
|
|
6
|
+
* session stays on the page it was attached to.
|
|
7
|
+
*/
|
|
8
|
+
import { type RelayLink } from "../devtools-relay/index.js";
|
|
9
|
+
/** The part of a Playwright `Page` that `attachPage` uses, so `appduct` has no Playwright dependency. */
|
|
10
|
+
export type AttachablePage = {
|
|
11
|
+
exposeBinding(name: string, callback: (source: {
|
|
12
|
+
frame: unknown;
|
|
13
|
+
}, json: string) => void): Promise<void>;
|
|
14
|
+
addInitScript(script: string): Promise<void>;
|
|
15
|
+
evaluate(expression: string): Promise<unknown>;
|
|
16
|
+
waitForFunction(expression: string): Promise<unknown>;
|
|
17
|
+
mainFrame(): unknown;
|
|
18
|
+
on(event: "close", handler: () => void): unknown;
|
|
19
|
+
};
|
|
20
|
+
export type AttachPageOptions = {
|
|
21
|
+
/** The result of `link({ target: "web", url })` for the page's address. */
|
|
22
|
+
link: RelayLink;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Connects the page, which must already load `@appduct/web`, to the daemon through its Playwright
|
|
26
|
+
* binding. Resolves once the session is claimed. A reload resumes the session over the same binding.
|
|
27
|
+
*
|
|
28
|
+
* Attaching a page that is already attached closes the first link's socket, so the daemon suspends
|
|
29
|
+
* the first session for its grace period and it stays listed as `suspended`. While it is, a
|
|
30
|
+
* `connect()` with no selector finds two sessions and fails with `ambiguous_session`: select the new
|
|
31
|
+
* one with `connect({ selector: link.sessionId })` or `waitForSession`. An invalid `link` rejects
|
|
32
|
+
* before anything is dropped.
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* await page.goto("https://staging.example.com");
|
|
36
|
+
* await attachPage(page, { link: await link({ target: "web", url: page.url() }) });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare const attachPage: (page: AttachablePage, options: AttachPageOptions) => Promise<void>;
|