appduct 0.12.0 → 0.14.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 +43 -37
- package/dist/bin.js +102 -37
- package/dist/cli/command-options.d.ts +7 -17
- package/dist/cli/result-types.d.ts +19 -9
- package/dist/cli/routes/daemon/index.js +70 -17
- package/dist/cli/routes/daemon/run.js +288 -50
- package/dist/cli/routes/daemon/start.js +70 -17
- package/dist/cli/routes/daemon/status.js +70 -17
- package/dist/cli/routes/daemon/stop.js +70 -17
- package/dist/cli/routes/doctor.js +70 -17
- package/dist/cli/routes/events/index.d.ts +6 -0
- package/dist/cli/routes/events/index.js +900 -0
- package/dist/cli/routes/events/ls.d.ts +5 -0
- package/dist/cli/routes/events/ls.js +1798 -0
- package/dist/cli/routes/events/shared-options.d.ts +12 -0
- package/dist/cli/routes/events/shared-options.js +102 -0
- package/dist/cli/routes/events/since.d.ts +6 -0
- package/dist/cli/routes/events/since.js +2034 -0
- package/dist/cli/routes/events/tail.d.ts +6 -0
- package/dist/cli/routes/{events.js → events/tail.js} +235 -163
- package/dist/cli/routes/init.js +72 -19
- package/dist/cli/routes/keygen.js +70 -17
- package/dist/cli/routes/mcp.js +289 -166
- package/dist/cli/routes/sessions/index.d.ts +7 -0
- package/dist/cli/routes/sessions/index.js +900 -0
- package/dist/cli/routes/sessions/link.d.ts +4 -0
- package/dist/cli/routes/{link.js → sessions/link.js} +71 -18
- package/dist/cli/routes/sessions/ls.d.ts +4 -0
- package/dist/cli/routes/{ls.js → sessions/ls.js} +71 -18
- package/dist/cli/routes/sessions/revoke.d.ts +4 -0
- package/dist/cli/routes/{revoke.js → sessions/revoke.js} +72 -19
- package/dist/cli/routes/tools/call.d.ts +4 -0
- package/dist/cli/routes/{invoke.js → tools/call.js} +73 -20
- package/dist/cli/routes/tools/describe.d.ts +6 -0
- package/dist/cli/routes/tools/describe.js +1894 -0
- package/dist/cli/routes/tools/index.d.ts +6 -0
- package/dist/cli/routes/tools/index.js +900 -0
- package/dist/cli/routes/tools/ls.d.ts +4 -0
- package/dist/cli/routes/{tools.js → tools/ls.js} +98 -74
- package/dist/cli/routes/tools/shared-options.d.ts +16 -0
- package/dist/cli/routes/tools/shared-options.js +122 -0
- package/dist/client/app-client.d.ts +96 -37
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.js +178 -138
- package/dist/commands/events-ls.d.ts +21 -0
- package/dist/commands/events.d.ts +31 -14
- package/dist/commands/invoke.d.ts +1 -1
- package/dist/commands/link.d.ts +1 -1
- package/dist/commands/ls.d.ts +1 -1
- package/dist/commands/revoke.d.ts +1 -1
- package/dist/commands/tools.d.ts +12 -13
- package/dist/daemon/calls.d.ts +3 -1
- package/dist/daemon/config.d.ts +1 -1
- package/dist/daemon/event-bus.d.ts +42 -0
- package/dist/daemon/registry.d.ts +20 -12
- package/dist/daemon/rpc-errors.d.ts +4 -1
- package/dist/daemon/sessions.d.ts +4 -2
- package/dist/errors.d.ts +1 -1
- package/dist/events/index.d.ts +60 -0
- package/dist/index.js +114 -40
- package/dist/link.d.ts +1 -1
- package/dist/mcp/app-tools.d.ts +22 -2
- package/dist/mcp/connect-tool.d.ts +1 -1
- package/dist/mcp/events-tool.d.ts +67 -27
- package/dist/native-scheme.d.ts +1 -1
- package/dist/output.d.ts +29 -6
- package/dist/rpc/client.d.ts +1 -1
- package/dist/scheme.d.ts +4 -4
- package/package.json +2 -2
- package/dist/cli/routes/events.d.ts +0 -3
- package/dist/cli/routes/invoke.d.ts +0 -3
- package/dist/cli/routes/link.d.ts +0 -3
- package/dist/cli/routes/ls.d.ts +0 -3
- package/dist/cli/routes/revoke.d.ts +0 -3
- package/dist/cli/routes/tools.d.ts +0 -3
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Route for `appduct events tail` — loaded by `routes/events/index.ts`'s router only when it
|
|
2
|
+
* runs (issue #96; replaces the removed `appduct events` streaming form). `--follow` is accepted
|
|
3
|
+
* (declared at the `events` noun level in `create-cli.ts`) but never read here: the default
|
|
4
|
+
* behavior already follows, so the flag exists only for script readability. */
|
|
5
|
+
import type { Route } from "../../router.js";
|
|
6
|
+
export declare const route: Route;
|
|
@@ -1015,12 +1015,14 @@ var handleEventsSinceCommand = async (options, context) => {
|
|
|
1015
1015
|
try {
|
|
1016
1016
|
const since = await stream.call(RPC_METHODS2.eventsSince, {
|
|
1017
1017
|
selector: options.selector,
|
|
1018
|
-
since: options.since
|
|
1018
|
+
since: options.since,
|
|
1019
|
+
name: options.name,
|
|
1020
|
+
payloadMaxBytes: options.payloadMaxBytes
|
|
1019
1021
|
});
|
|
1020
1022
|
for (const event of since.events) {
|
|
1021
1023
|
context.onEvent(event);
|
|
1022
1024
|
}
|
|
1023
|
-
context.onCursor?.(since.cursor);
|
|
1025
|
+
context.onCursor?.({ cursor: since.cursor, dropped: since.dropped, remaining: since.remaining });
|
|
1024
1026
|
} finally {
|
|
1025
1027
|
stream.close();
|
|
1026
1028
|
}
|
|
@@ -1035,7 +1037,9 @@ var handleEventsCommand = async (options, context) => {
|
|
|
1035
1037
|
try {
|
|
1036
1038
|
await stream.call(RPC_METHODS2.eventsSubscribe, {
|
|
1037
1039
|
sessionSelector: options.selector,
|
|
1038
|
-
kinds: ["app_event"]
|
|
1040
|
+
kinds: ["app_event"],
|
|
1041
|
+
name: options.name,
|
|
1042
|
+
payloadMaxBytes: options.payloadMaxBytes
|
|
1039
1043
|
});
|
|
1040
1044
|
} catch (error) {
|
|
1041
1045
|
stream.close();
|
|
@@ -1071,128 +1075,11 @@ var handleEventsCommand = async (options, context) => {
|
|
|
1071
1075
|
};
|
|
1072
1076
|
};
|
|
1073
1077
|
|
|
1074
|
-
// src/errors.ts
|
|
1075
|
-
var EXIT_CODE_BY_ERROR_TYPE = {
|
|
1076
|
-
usage_error: 64,
|
|
1077
|
-
validation_error: 65,
|
|
1078
|
-
// EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
|
|
1079
|
-
// missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
|
|
1080
|
-
// code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
|
|
1081
|
-
// assertion failure (docs/tasks/08-appduct-doctor.md).
|
|
1082
|
-
inspection_error: 66,
|
|
1083
|
-
connection_error: 70,
|
|
1084
|
-
session_error: 71,
|
|
1085
|
-
tool_error: 72,
|
|
1086
|
-
permission_error: 77,
|
|
1087
|
-
internal_error: 1,
|
|
1088
|
-
// `doctor --assert-present`/`--assert-absent`: the artifact was inspected successfully and the
|
|
1089
|
-
// assertion did not hold. Distinct from `inspection_error` (66) — this means the answer is known
|
|
1090
|
-
// and it's "no", not "couldn't tell" — and distinct from `internal_error` (1) so a caller can't
|
|
1091
|
-
// conflate a normal assertion failure with an unexpected crash.
|
|
1092
|
-
assertion_error: 3
|
|
1093
|
-
};
|
|
1094
|
-
var RPC_ERROR_EXIT_CLASS = {
|
|
1095
|
-
no_session: "session_error",
|
|
1096
|
-
ambiguous_session: "session_error",
|
|
1097
|
-
unknown_session: "session_error",
|
|
1098
|
-
session_not_active: "session_error",
|
|
1099
|
-
session_suspended: "session_error",
|
|
1100
|
-
tool_not_found: "tool_error",
|
|
1101
|
-
tool_input_validation_error: "tool_error",
|
|
1102
|
-
tool_output_validation_error: "tool_error",
|
|
1103
|
-
tool_execution_error: "tool_error",
|
|
1104
|
-
tool_serialization_error: "tool_error",
|
|
1105
|
-
tool_timeout: "tool_error",
|
|
1106
|
-
tool_cancelled: "tool_error",
|
|
1107
|
-
policy_denied: "permission_error",
|
|
1108
|
-
invalid_request: "validation_error"
|
|
1109
|
-
};
|
|
1110
|
-
var AppductCliError = class extends Error {
|
|
1111
|
-
type;
|
|
1112
|
-
details;
|
|
1113
|
-
constructor(type, message, details) {
|
|
1114
|
-
super(message);
|
|
1115
|
-
this.name = "AppductCliError";
|
|
1116
|
-
this.type = type;
|
|
1117
|
-
this.details = details;
|
|
1118
|
-
}
|
|
1119
|
-
};
|
|
1120
|
-
var usageError = (message, details) => {
|
|
1121
|
-
return new AppductCliError("usage_error", message, details);
|
|
1122
|
-
};
|
|
1123
|
-
var isAppductCliError = (value) => {
|
|
1124
|
-
return value instanceof AppductCliError;
|
|
1125
|
-
};
|
|
1126
|
-
var rpcErrorClass = (error) => {
|
|
1127
|
-
const type = error.data?.type;
|
|
1128
|
-
return type ? RPC_ERROR_EXIT_CLASS[type] : void 0;
|
|
1129
|
-
};
|
|
1130
|
-
var toCliError = (error) => {
|
|
1131
|
-
if (isAppductCliError(error)) {
|
|
1132
|
-
return {
|
|
1133
|
-
type: error.type,
|
|
1134
|
-
message: error.message,
|
|
1135
|
-
details: error.details
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
|
-
if (error instanceof DaemonVersionMismatchError) {
|
|
1139
|
-
return {
|
|
1140
|
-
type: "connection_error",
|
|
1141
|
-
message: error.message,
|
|
1142
|
-
details: {
|
|
1143
|
-
daemon_version: error.daemonVersion,
|
|
1144
|
-
client_version: error.clientVersion,
|
|
1145
|
-
session_count: error.sessionCount,
|
|
1146
|
-
pending_link_count: error.pendingLinkCount
|
|
1147
|
-
}
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1150
|
-
if (error instanceof DaemonRpcError && error.data?.type) {
|
|
1151
|
-
return {
|
|
1152
|
-
type: error.data.type,
|
|
1153
|
-
message: error.message,
|
|
1154
|
-
details: error.data.details
|
|
1155
|
-
};
|
|
1156
|
-
}
|
|
1157
|
-
if (isDaemonUnreachableError(error)) {
|
|
1158
|
-
return {
|
|
1159
|
-
type: "connection_error",
|
|
1160
|
-
message: error instanceof Error ? error.message : "The Appduct daemon is unreachable."
|
|
1161
|
-
};
|
|
1162
|
-
}
|
|
1163
|
-
if (error instanceof Error) {
|
|
1164
|
-
return {
|
|
1165
|
-
type: "internal_error",
|
|
1166
|
-
message: error.message
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
return {
|
|
1170
|
-
type: "internal_error",
|
|
1171
|
-
message: "An unexpected error occurred.",
|
|
1172
|
-
details: error
|
|
1173
|
-
};
|
|
1174
|
-
};
|
|
1175
|
-
var getExitCodeForError = (error) => {
|
|
1176
|
-
if (isAppductCliError(error)) {
|
|
1177
|
-
return EXIT_CODE_BY_ERROR_TYPE[error.type];
|
|
1178
|
-
}
|
|
1179
|
-
if (error instanceof DaemonVersionMismatchError) {
|
|
1180
|
-
return EXIT_CODE_BY_ERROR_TYPE.connection_error;
|
|
1181
|
-
}
|
|
1182
|
-
if (error instanceof DaemonRpcError) {
|
|
1183
|
-
const cliErrorClass = rpcErrorClass(error);
|
|
1184
|
-
return EXIT_CODE_BY_ERROR_TYPE[cliErrorClass ?? "internal_error"];
|
|
1185
|
-
}
|
|
1186
|
-
if (isDaemonUnreachableError(error)) {
|
|
1187
|
-
return EXIT_CODE_BY_ERROR_TYPE.connection_error;
|
|
1188
|
-
}
|
|
1189
|
-
return EXIT_CODE_BY_ERROR_TYPE.internal_error;
|
|
1190
|
-
};
|
|
1191
|
-
|
|
1192
1078
|
// src/output.ts
|
|
1193
1079
|
import pc from "picocolors";
|
|
1194
1080
|
import {
|
|
1195
1081
|
formatAgentWebSocketUrl,
|
|
1082
|
+
renderEventSignature,
|
|
1196
1083
|
renderToolSignature,
|
|
1197
1084
|
summarizeToolDescription
|
|
1198
1085
|
} from "@appduct/shared";
|
|
@@ -1293,6 +1180,13 @@ var formatDevice = (device) => {
|
|
|
1293
1180
|
const label = parts.length > 0 ? parts.join(" ") : "unknown device";
|
|
1294
1181
|
return device.os ? `${label} (${device.os})` : label;
|
|
1295
1182
|
};
|
|
1183
|
+
var SUSPEND_REASON_LABELS = {
|
|
1184
|
+
app_backgrounded: "app in background",
|
|
1185
|
+
connection_lost: "connection lost"
|
|
1186
|
+
};
|
|
1187
|
+
var formatSessionState = (session) => {
|
|
1188
|
+
return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
|
|
1189
|
+
};
|
|
1296
1190
|
var renderLsData = (colors, data, now) => {
|
|
1297
1191
|
if (data.length === 0) {
|
|
1298
1192
|
return [colors.dim("Sessions"), " No Appduct sessions are registered."];
|
|
@@ -1301,7 +1195,7 @@ var renderLsData = (colors, data, now) => {
|
|
|
1301
1195
|
const headers = ["Alias", "State", "Device", "Tools", "Age"];
|
|
1302
1196
|
const rows = data.map((session) => [
|
|
1303
1197
|
session.alias,
|
|
1304
|
-
session
|
|
1198
|
+
formatSessionState(session),
|
|
1305
1199
|
formatDevice(session.device),
|
|
1306
1200
|
String(session.toolCount),
|
|
1307
1201
|
formatAge(session.claimedAt ?? session.createdAt, nowIso)
|
|
@@ -1315,9 +1209,6 @@ var renderLsData = (colors, data, now) => {
|
|
|
1315
1209
|
...rows.map((row) => ` ${row.map((cell, index) => cell.padEnd(widths[index])).join(" ")}`)
|
|
1316
1210
|
];
|
|
1317
1211
|
};
|
|
1318
|
-
var isToolsListing = (data) => {
|
|
1319
|
-
return typeof data === "object" && data !== null && Array.isArray(data.tools);
|
|
1320
|
-
};
|
|
1321
1212
|
var isToolGroupsListing = (data) => {
|
|
1322
1213
|
return typeof data === "object" && data !== null && Array.isArray(data.groups) && !Array.isArray(data.tools);
|
|
1323
1214
|
};
|
|
@@ -1335,7 +1226,7 @@ var renderEmptyToolsLine = (data) => {
|
|
|
1335
1226
|
}
|
|
1336
1227
|
if (data.group !== void 0) {
|
|
1337
1228
|
const match = data.filter === void 0 ? "" : ` match ${JSON.stringify(data.filter)}`;
|
|
1338
|
-
return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools --groups\` to see the session's groups.`;
|
|
1229
|
+
return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools ls --groups\` to see the session's groups.`;
|
|
1339
1230
|
}
|
|
1340
1231
|
return data.filter === void 0 ? " No tools registered." : ` No tools match ${JSON.stringify(data.filter)}.`;
|
|
1341
1232
|
};
|
|
@@ -1417,7 +1308,7 @@ var renderToolSummaryTable = (colors, data) => {
|
|
|
1417
1308
|
...grouped ? renderGroupedToolLines(colors, data.tools) : data.tools.flatMap((tool) => renderToolSummaryLines(tool, " ")),
|
|
1418
1309
|
...renderTruncationLine(data),
|
|
1419
1310
|
"",
|
|
1420
|
-
"Run `appduct tools <name>` for a tool's full schema."
|
|
1311
|
+
"Run `appduct tools describe <name>` for a tool's full schema."
|
|
1421
1312
|
];
|
|
1422
1313
|
};
|
|
1423
1314
|
var renderToolDetail = (colors, tool, flags) => {
|
|
@@ -1469,18 +1360,62 @@ var renderToolGroups = (colors, data) => {
|
|
|
1469
1360
|
colors.green("Groups"),
|
|
1470
1361
|
...data.groups.map((entry) => ` ${label(entry).padEnd(width)} ${String(entry.total).padStart(countWidth)}`),
|
|
1471
1362
|
"",
|
|
1472
|
-
`${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools --group <name>` to list one group's tools." : "No tool declares a group.")
|
|
1363
|
+
`${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools ls --group <name>` to list one group's tools." : "No tool declares a group.")
|
|
1473
1364
|
];
|
|
1474
1365
|
};
|
|
1475
|
-
var
|
|
1366
|
+
var renderToolsListData = (colors, data, flags, full) => {
|
|
1476
1367
|
if (isToolGroupsListing(data)) {
|
|
1477
1368
|
return renderToolGroups(colors, data);
|
|
1478
1369
|
}
|
|
1479
|
-
if (!isToolsListing(data)) {
|
|
1480
|
-
return renderToolDetail(colors, data, flags);
|
|
1481
|
-
}
|
|
1482
1370
|
return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
|
|
1483
1371
|
};
|
|
1372
|
+
var renderEventsTruncationLine = (data) => {
|
|
1373
|
+
const offset = data.offset ?? 0;
|
|
1374
|
+
if (data.events.length >= data.total) {
|
|
1375
|
+
return [];
|
|
1376
|
+
}
|
|
1377
|
+
return [
|
|
1378
|
+
"",
|
|
1379
|
+
`Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
|
|
1380
|
+
];
|
|
1381
|
+
};
|
|
1382
|
+
var renderEventsListData = (colors, data, flags) => {
|
|
1383
|
+
const [only] = data.events;
|
|
1384
|
+
if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
|
|
1385
|
+
return renderFields(
|
|
1386
|
+
colors.green(`Event: ${only.name}`),
|
|
1387
|
+
[
|
|
1388
|
+
["Signature", renderEventSignature(only)],
|
|
1389
|
+
["Description", only.description],
|
|
1390
|
+
["Payload schema", only.payload_schema]
|
|
1391
|
+
],
|
|
1392
|
+
flags
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
if (data.events.length === 0 && data.total > 0) {
|
|
1396
|
+
return [
|
|
1397
|
+
colors.green("Events"),
|
|
1398
|
+
` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
|
|
1399
|
+
];
|
|
1400
|
+
}
|
|
1401
|
+
if (data.events.length === 0) {
|
|
1402
|
+
return [
|
|
1403
|
+
colors.green("Events"),
|
|
1404
|
+
data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
|
|
1405
|
+
];
|
|
1406
|
+
}
|
|
1407
|
+
const lines = data.events.flatMap((event) => [
|
|
1408
|
+
` ${renderEventSignature(event)}`,
|
|
1409
|
+
` ${summarizeToolDescription(event.description)}`
|
|
1410
|
+
]);
|
|
1411
|
+
return [
|
|
1412
|
+
colors.green("Events"),
|
|
1413
|
+
...lines,
|
|
1414
|
+
...renderEventsTruncationLine(data),
|
|
1415
|
+
"",
|
|
1416
|
+
"Run `appduct events ls --name <name>` for an event's full payload schema."
|
|
1417
|
+
];
|
|
1418
|
+
};
|
|
1484
1419
|
var renderInvokeData = (colors, data, flags) => {
|
|
1485
1420
|
return [colors.green("Result"), formatScalar(data, flags)];
|
|
1486
1421
|
};
|
|
@@ -1664,15 +1599,19 @@ var renderSuccessData = (colors, command, data, options) => {
|
|
|
1664
1599
|
return renderInitData(colors, data, flags);
|
|
1665
1600
|
case "keygen":
|
|
1666
1601
|
return renderKeygenData(colors, data, flags);
|
|
1667
|
-
case "link":
|
|
1602
|
+
case "sessions link":
|
|
1668
1603
|
return renderLinkData(colors, data, flags, options.qr);
|
|
1669
|
-
case "ls":
|
|
1604
|
+
case "sessions ls":
|
|
1670
1605
|
return renderLsData(colors, data, options.now ?? /* @__PURE__ */ new Date());
|
|
1671
|
-
case "tools":
|
|
1672
|
-
return
|
|
1673
|
-
case "
|
|
1606
|
+
case "tools ls":
|
|
1607
|
+
return renderToolsListData(colors, data, flags, options.full);
|
|
1608
|
+
case "tools describe":
|
|
1609
|
+
return renderToolDetail(colors, data, flags);
|
|
1610
|
+
case "events ls":
|
|
1611
|
+
return renderEventsListData(colors, data, flags);
|
|
1612
|
+
case "tools call":
|
|
1674
1613
|
return renderInvokeData(colors, data, flags);
|
|
1675
|
-
case "revoke":
|
|
1614
|
+
case "sessions revoke":
|
|
1676
1615
|
return renderRevokeData(colors, data);
|
|
1677
1616
|
case "daemon run":
|
|
1678
1617
|
return renderDaemonRunData(colors, data, flags);
|
|
@@ -1742,25 +1681,158 @@ var renderEventLine = (event, flags) => {
|
|
|
1742
1681
|
const dataSuffix = event.data === void 0 ? "" : ` ${formatJson(event.data, flags)}`;
|
|
1743
1682
|
return `${colors.dim(timestamp)} ${colors.green(event.kind)}${target ? ` ${target}` : ""}${dataSuffix}`;
|
|
1744
1683
|
};
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1684
|
+
|
|
1685
|
+
// src/errors.ts
|
|
1686
|
+
var EXIT_CODE_BY_ERROR_TYPE = {
|
|
1687
|
+
usage_error: 64,
|
|
1688
|
+
validation_error: 65,
|
|
1689
|
+
// EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
|
|
1690
|
+
// missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
|
|
1691
|
+
// code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
|
|
1692
|
+
// assertion failure (docs/tasks/08-appduct-doctor.md).
|
|
1693
|
+
inspection_error: 66,
|
|
1694
|
+
connection_error: 70,
|
|
1695
|
+
session_error: 71,
|
|
1696
|
+
tool_error: 72,
|
|
1697
|
+
permission_error: 77,
|
|
1698
|
+
internal_error: 1,
|
|
1699
|
+
// `doctor --assert-present`/`--assert-absent`: the artifact was inspected successfully and the
|
|
1700
|
+
// assertion did not hold. Distinct from `inspection_error` (66) — this means the answer is known
|
|
1701
|
+
// and it's "no", not "couldn't tell" — and distinct from `internal_error` (1) so a caller can't
|
|
1702
|
+
// conflate a normal assertion failure with an unexpected crash.
|
|
1703
|
+
assertion_error: 3
|
|
1704
|
+
};
|
|
1705
|
+
var RPC_ERROR_EXIT_CLASS = {
|
|
1706
|
+
no_session: "session_error",
|
|
1707
|
+
ambiguous_session: "session_error",
|
|
1708
|
+
unknown_session: "session_error",
|
|
1709
|
+
session_not_active: "session_error",
|
|
1710
|
+
session_suspended: "session_error",
|
|
1711
|
+
tool_not_found: "tool_error",
|
|
1712
|
+
tool_input_validation_error: "tool_error",
|
|
1713
|
+
tool_output_validation_error: "tool_error",
|
|
1714
|
+
tool_execution_error: "tool_error",
|
|
1715
|
+
tool_serialization_error: "tool_error",
|
|
1716
|
+
tool_timeout: "tool_error",
|
|
1717
|
+
tool_cancelled: "tool_error",
|
|
1718
|
+
policy_denied: "permission_error",
|
|
1719
|
+
invalid_request: "validation_error"
|
|
1720
|
+
};
|
|
1721
|
+
var AppductCliError = class extends Error {
|
|
1722
|
+
type;
|
|
1723
|
+
details;
|
|
1724
|
+
constructor(type, message, details) {
|
|
1725
|
+
super(message);
|
|
1726
|
+
this.name = "AppductCliError";
|
|
1727
|
+
this.type = type;
|
|
1728
|
+
this.details = details;
|
|
1748
1729
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1730
|
+
};
|
|
1731
|
+
var usageError = (message, details) => {
|
|
1732
|
+
return new AppductCliError("usage_error", message, details);
|
|
1733
|
+
};
|
|
1734
|
+
var isAppductCliError = (value) => {
|
|
1735
|
+
return value instanceof AppductCliError;
|
|
1736
|
+
};
|
|
1737
|
+
var rpcErrorClass = (error) => {
|
|
1738
|
+
const type = error.data?.type;
|
|
1739
|
+
return type ? RPC_ERROR_EXIT_CLASS[type] : void 0;
|
|
1740
|
+
};
|
|
1741
|
+
var toCliError = (error) => {
|
|
1742
|
+
if (isAppductCliError(error)) {
|
|
1743
|
+
return {
|
|
1744
|
+
type: error.type,
|
|
1745
|
+
message: error.message,
|
|
1746
|
+
details: error.details
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
if (error instanceof DaemonVersionMismatchError) {
|
|
1750
|
+
return {
|
|
1751
|
+
type: "connection_error",
|
|
1752
|
+
message: error.message,
|
|
1753
|
+
details: {
|
|
1754
|
+
daemon_version: error.daemonVersion,
|
|
1755
|
+
client_version: error.clientVersion,
|
|
1756
|
+
session_count: error.sessionCount,
|
|
1757
|
+
pending_link_count: error.pendingLinkCount
|
|
1758
|
+
}
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
if (error instanceof DaemonRpcError && error.data?.type) {
|
|
1762
|
+
return {
|
|
1763
|
+
type: error.data.type,
|
|
1764
|
+
message: error.message,
|
|
1765
|
+
details: error.data.details
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
if (isDaemonUnreachableError(error)) {
|
|
1769
|
+
return {
|
|
1770
|
+
type: "connection_error",
|
|
1771
|
+
message: error instanceof Error ? error.message : "The Appduct daemon is unreachable."
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
if (error instanceof Error) {
|
|
1775
|
+
return {
|
|
1776
|
+
type: "internal_error",
|
|
1777
|
+
message: error.message
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
return {
|
|
1781
|
+
type: "internal_error",
|
|
1782
|
+
message: "An unexpected error occurred.",
|
|
1783
|
+
details: error
|
|
1784
|
+
};
|
|
1785
|
+
};
|
|
1786
|
+
var getExitCodeForError = (error) => {
|
|
1787
|
+
if (isAppductCliError(error)) {
|
|
1788
|
+
return EXIT_CODE_BY_ERROR_TYPE[error.type];
|
|
1789
|
+
}
|
|
1790
|
+
if (error instanceof DaemonVersionMismatchError) {
|
|
1791
|
+
return EXIT_CODE_BY_ERROR_TYPE.connection_error;
|
|
1792
|
+
}
|
|
1793
|
+
if (error instanceof DaemonRpcError) {
|
|
1794
|
+
const cliErrorClass = rpcErrorClass(error);
|
|
1795
|
+
return EXIT_CODE_BY_ERROR_TYPE[cliErrorClass ?? "internal_error"];
|
|
1796
|
+
}
|
|
1797
|
+
if (isDaemonUnreachableError(error)) {
|
|
1798
|
+
return EXIT_CODE_BY_ERROR_TYPE.connection_error;
|
|
1799
|
+
}
|
|
1800
|
+
return EXIT_CODE_BY_ERROR_TYPE.internal_error;
|
|
1751
1801
|
};
|
|
1752
1802
|
|
|
1753
1803
|
// src/cli/command-options.ts
|
|
1754
|
-
var
|
|
1804
|
+
var parsePositiveIntegerOption = (value, flagName) => {
|
|
1755
1805
|
if (value === void 0) {
|
|
1756
1806
|
return void 0;
|
|
1757
1807
|
}
|
|
1758
1808
|
const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
|
|
1759
|
-
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed
|
|
1760
|
-
throw usageError(`"${flagName}" must be a
|
|
1809
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed <= 0) {
|
|
1810
|
+
throw usageError(`"${flagName}" must be a positive integer.`);
|
|
1761
1811
|
}
|
|
1762
1812
|
return parsed;
|
|
1763
1813
|
};
|
|
1814
|
+
var readTextOption = (argv, value, flagName) => {
|
|
1815
|
+
if (value === void 0) {
|
|
1816
|
+
return void 0;
|
|
1817
|
+
}
|
|
1818
|
+
if (typeof value === "boolean") {
|
|
1819
|
+
throw usageError(`"${flagName}" requires a value.`);
|
|
1820
|
+
}
|
|
1821
|
+
let raw;
|
|
1822
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1823
|
+
const token = argv[index];
|
|
1824
|
+
if (token === "--") {
|
|
1825
|
+
break;
|
|
1826
|
+
}
|
|
1827
|
+
if (token === flagName && index + 1 < argv.length) {
|
|
1828
|
+
raw = argv[index + 1];
|
|
1829
|
+
index += 1;
|
|
1830
|
+
} else if (token.startsWith(`${flagName}=`)) {
|
|
1831
|
+
raw = token.slice(flagName.length + 1);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
return raw ?? String(Array.isArray(value) ? value.at(-1) : value);
|
|
1835
|
+
};
|
|
1764
1836
|
var splitOptionalSelector = (args, commandUsage) => {
|
|
1765
1837
|
if (args.length > 1) {
|
|
1766
1838
|
throw usageError(`Usage: ${commandUsage} (too many arguments).`);
|
|
@@ -1874,33 +1946,33 @@ var guarded = (context) => {
|
|
|
1874
1946
|
};
|
|
1875
1947
|
};
|
|
1876
1948
|
|
|
1877
|
-
// src/cli/routes/events.ts
|
|
1949
|
+
// src/cli/routes/events/shared-options.ts
|
|
1950
|
+
var parseEventsFilterFlags = (context) => {
|
|
1951
|
+
const { options } = context;
|
|
1952
|
+
return {
|
|
1953
|
+
name: readTextOption(context.argv, options.name, "--name"),
|
|
1954
|
+
payloadMaxBytes: parsePositiveIntegerOption(options.payloadMaxBytes, "--payload-max-bytes")
|
|
1955
|
+
};
|
|
1956
|
+
};
|
|
1957
|
+
|
|
1958
|
+
// src/cli/routes/events/tail.ts
|
|
1878
1959
|
var route = async (context) => {
|
|
1879
|
-
const {
|
|
1880
|
-
const follow = Boolean(options.follow);
|
|
1960
|
+
const { stateDir, env } = context;
|
|
1881
1961
|
return executeHostedCommand(
|
|
1882
1962
|
commandName(context),
|
|
1883
|
-
//
|
|
1884
|
-
//
|
|
1885
|
-
//
|
|
1886
|
-
// the daemon.
|
|
1963
|
+
// Argument parsing runs inside the handler (not the route body) so `executeHostedCommand`'s
|
|
1964
|
+
// own try/catch renders a bad argument as a normal usage_error instead of an uncaught
|
|
1965
|
+
// rejection, and before the version check so a typo never waits on the daemon.
|
|
1887
1966
|
() => {
|
|
1888
|
-
const { selector } = splitOptionalSelector(context.args, "events [selector]");
|
|
1889
|
-
const
|
|
1890
|
-
if (since !== void 0 && follow) {
|
|
1891
|
-
throw usageError('"--since" is a one-shot pull and cannot be combined with "--follow".');
|
|
1892
|
-
}
|
|
1967
|
+
const { selector } = splitOptionalSelector(context.args, "events tail [selector]");
|
|
1968
|
+
const { name, payloadMaxBytes } = parseEventsFilterFlags(context);
|
|
1893
1969
|
return guarded(context)(
|
|
1894
1970
|
() => handleEventsCommand(
|
|
1895
|
-
{ selector,
|
|
1971
|
+
{ selector, name, payloadMaxBytes },
|
|
1896
1972
|
{
|
|
1897
1973
|
stateDir,
|
|
1898
1974
|
onEvent: (event) => {
|
|
1899
1975
|
env.stdout.write(`${renderEventLine(event, env.flags)}
|
|
1900
|
-
`);
|
|
1901
|
-
},
|
|
1902
|
-
onCursor: (cursor) => {
|
|
1903
|
-
env.stdout.write(`${renderEventsCursorLine(cursor, env.flags)}
|
|
1904
1976
|
`);
|
|
1905
1977
|
}
|
|
1906
1978
|
}
|
package/dist/cli/routes/init.js
CHANGED
|
@@ -841,10 +841,10 @@ Run \`appduct init --scheme <scheme>\` with the scheme your app registers for de
|
|
|
841
841
|
nextSteps: [
|
|
842
842
|
'Add `import "@appduct/react-native/auto";` to your app entry (index.js / App.tsx) \u2014 it is what starts the in-app agent endpoint.',
|
|
843
843
|
`Add the Appduct MCP server entry to your agent's MCP config. "--scheme ${scheme}" keeps that entry self-contained; ${SCHEME_ENV_VAR} and this ${PROJECT_CONFIG_RELATIVE_PATH} work too.`,
|
|
844
|
-
"With the app running, pair a device: `appduct link --open ios-sim` (or `--open android`).",
|
|
844
|
+
"With the app running, pair a device: `appduct sessions link --open ios-sim` (or `--open android`).",
|
|
845
845
|
...origin === void 0 ? [] : [`Scheme "${scheme}" was read from ${origin}.`],
|
|
846
846
|
...iosAppId === void 0 && androidAppId === void 0 ? [
|
|
847
|
-
"Delivering to a physical iPhone (`--open ios-device`) or an Android device/emulator (`--open android`) needs the installed app's id: run `appduct init --ios-app-id <id> --android-app-id <id>`, or pass --app-id on `appduct link`."
|
|
847
|
+
"Delivering to a physical iPhone (`--open ios-device`) or an Android device/emulator (`--open android`) needs the installed app's id: run `appduct init --ios-app-id <id> --android-app-id <id>`, or pass --app-id on `appduct sessions link`."
|
|
848
848
|
] : [],
|
|
849
849
|
`This file is safe to commit \u2014 it holds only "scheme" and "appId". Do not point --state-dir at this directory: the state dir holds the daemon's private key and audit log, which must never be committed.`
|
|
850
850
|
]
|
|
@@ -861,6 +861,7 @@ var formatJson = (value, flags) => {
|
|
|
861
861
|
import pc from "picocolors";
|
|
862
862
|
import {
|
|
863
863
|
formatAgentWebSocketUrl,
|
|
864
|
+
renderEventSignature,
|
|
864
865
|
renderToolSignature,
|
|
865
866
|
summarizeToolDescription
|
|
866
867
|
} from "@appduct/shared";
|
|
@@ -956,6 +957,13 @@ var formatDevice = (device) => {
|
|
|
956
957
|
const label = parts.length > 0 ? parts.join(" ") : "unknown device";
|
|
957
958
|
return device.os ? `${label} (${device.os})` : label;
|
|
958
959
|
};
|
|
960
|
+
var SUSPEND_REASON_LABELS = {
|
|
961
|
+
app_backgrounded: "app in background",
|
|
962
|
+
connection_lost: "connection lost"
|
|
963
|
+
};
|
|
964
|
+
var formatSessionState = (session) => {
|
|
965
|
+
return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
|
|
966
|
+
};
|
|
959
967
|
var renderLsData = (colors, data, now) => {
|
|
960
968
|
if (data.length === 0) {
|
|
961
969
|
return [colors.dim("Sessions"), " No Appduct sessions are registered."];
|
|
@@ -964,7 +972,7 @@ var renderLsData = (colors, data, now) => {
|
|
|
964
972
|
const headers = ["Alias", "State", "Device", "Tools", "Age"];
|
|
965
973
|
const rows = data.map((session) => [
|
|
966
974
|
session.alias,
|
|
967
|
-
session
|
|
975
|
+
formatSessionState(session),
|
|
968
976
|
formatDevice(session.device),
|
|
969
977
|
String(session.toolCount),
|
|
970
978
|
formatAge(session.claimedAt ?? session.createdAt, nowIso)
|
|
@@ -978,9 +986,6 @@ var renderLsData = (colors, data, now) => {
|
|
|
978
986
|
...rows.map((row) => ` ${row.map((cell, index) => cell.padEnd(widths[index])).join(" ")}`)
|
|
979
987
|
];
|
|
980
988
|
};
|
|
981
|
-
var isToolsListing = (data) => {
|
|
982
|
-
return typeof data === "object" && data !== null && Array.isArray(data.tools);
|
|
983
|
-
};
|
|
984
989
|
var isToolGroupsListing = (data) => {
|
|
985
990
|
return typeof data === "object" && data !== null && Array.isArray(data.groups) && !Array.isArray(data.tools);
|
|
986
991
|
};
|
|
@@ -998,7 +1003,7 @@ var renderEmptyToolsLine = (data) => {
|
|
|
998
1003
|
}
|
|
999
1004
|
if (data.group !== void 0) {
|
|
1000
1005
|
const match = data.filter === void 0 ? "" : ` match ${JSON.stringify(data.filter)}`;
|
|
1001
|
-
return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools --groups\` to see the session's groups.`;
|
|
1006
|
+
return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools ls --groups\` to see the session's groups.`;
|
|
1002
1007
|
}
|
|
1003
1008
|
return data.filter === void 0 ? " No tools registered." : ` No tools match ${JSON.stringify(data.filter)}.`;
|
|
1004
1009
|
};
|
|
@@ -1080,7 +1085,7 @@ var renderToolSummaryTable = (colors, data) => {
|
|
|
1080
1085
|
...grouped ? renderGroupedToolLines(colors, data.tools) : data.tools.flatMap((tool) => renderToolSummaryLines(tool, " ")),
|
|
1081
1086
|
...renderTruncationLine(data),
|
|
1082
1087
|
"",
|
|
1083
|
-
"Run `appduct tools <name>` for a tool's full schema."
|
|
1088
|
+
"Run `appduct tools describe <name>` for a tool's full schema."
|
|
1084
1089
|
];
|
|
1085
1090
|
};
|
|
1086
1091
|
var renderToolDetail = (colors, tool, flags) => {
|
|
@@ -1132,18 +1137,62 @@ var renderToolGroups = (colors, data) => {
|
|
|
1132
1137
|
colors.green("Groups"),
|
|
1133
1138
|
...data.groups.map((entry) => ` ${label(entry).padEnd(width)} ${String(entry.total).padStart(countWidth)}`),
|
|
1134
1139
|
"",
|
|
1135
|
-
`${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools --group <name>` to list one group's tools." : "No tool declares a group.")
|
|
1140
|
+
`${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools ls --group <name>` to list one group's tools." : "No tool declares a group.")
|
|
1136
1141
|
];
|
|
1137
1142
|
};
|
|
1138
|
-
var
|
|
1143
|
+
var renderToolsListData = (colors, data, flags, full) => {
|
|
1139
1144
|
if (isToolGroupsListing(data)) {
|
|
1140
1145
|
return renderToolGroups(colors, data);
|
|
1141
1146
|
}
|
|
1142
|
-
if (!isToolsListing(data)) {
|
|
1143
|
-
return renderToolDetail(colors, data, flags);
|
|
1144
|
-
}
|
|
1145
1147
|
return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
|
|
1146
1148
|
};
|
|
1149
|
+
var renderEventsTruncationLine = (data) => {
|
|
1150
|
+
const offset = data.offset ?? 0;
|
|
1151
|
+
if (data.events.length >= data.total) {
|
|
1152
|
+
return [];
|
|
1153
|
+
}
|
|
1154
|
+
return [
|
|
1155
|
+
"",
|
|
1156
|
+
`Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
|
|
1157
|
+
];
|
|
1158
|
+
};
|
|
1159
|
+
var renderEventsListData = (colors, data, flags) => {
|
|
1160
|
+
const [only] = data.events;
|
|
1161
|
+
if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
|
|
1162
|
+
return renderFields(
|
|
1163
|
+
colors.green(`Event: ${only.name}`),
|
|
1164
|
+
[
|
|
1165
|
+
["Signature", renderEventSignature(only)],
|
|
1166
|
+
["Description", only.description],
|
|
1167
|
+
["Payload schema", only.payload_schema]
|
|
1168
|
+
],
|
|
1169
|
+
flags
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
if (data.events.length === 0 && data.total > 0) {
|
|
1173
|
+
return [
|
|
1174
|
+
colors.green("Events"),
|
|
1175
|
+
` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
|
|
1176
|
+
];
|
|
1177
|
+
}
|
|
1178
|
+
if (data.events.length === 0) {
|
|
1179
|
+
return [
|
|
1180
|
+
colors.green("Events"),
|
|
1181
|
+
data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
|
|
1182
|
+
];
|
|
1183
|
+
}
|
|
1184
|
+
const lines = data.events.flatMap((event) => [
|
|
1185
|
+
` ${renderEventSignature(event)}`,
|
|
1186
|
+
` ${summarizeToolDescription(event.description)}`
|
|
1187
|
+
]);
|
|
1188
|
+
return [
|
|
1189
|
+
colors.green("Events"),
|
|
1190
|
+
...lines,
|
|
1191
|
+
...renderEventsTruncationLine(data),
|
|
1192
|
+
"",
|
|
1193
|
+
"Run `appduct events ls --name <name>` for an event's full payload schema."
|
|
1194
|
+
];
|
|
1195
|
+
};
|
|
1147
1196
|
var renderInvokeData = (colors, data, flags) => {
|
|
1148
1197
|
return [colors.green("Result"), formatScalar(data, flags)];
|
|
1149
1198
|
};
|
|
@@ -1327,15 +1376,19 @@ var renderSuccessData = (colors, command, data, options) => {
|
|
|
1327
1376
|
return renderInitData(colors, data, flags);
|
|
1328
1377
|
case "keygen":
|
|
1329
1378
|
return renderKeygenData(colors, data, flags);
|
|
1330
|
-
case "link":
|
|
1379
|
+
case "sessions link":
|
|
1331
1380
|
return renderLinkData(colors, data, flags, options.qr);
|
|
1332
|
-
case "ls":
|
|
1381
|
+
case "sessions ls":
|
|
1333
1382
|
return renderLsData(colors, data, options.now ?? /* @__PURE__ */ new Date());
|
|
1334
|
-
case "tools":
|
|
1335
|
-
return
|
|
1336
|
-
case "
|
|
1383
|
+
case "tools ls":
|
|
1384
|
+
return renderToolsListData(colors, data, flags, options.full);
|
|
1385
|
+
case "tools describe":
|
|
1386
|
+
return renderToolDetail(colors, data, flags);
|
|
1387
|
+
case "events ls":
|
|
1388
|
+
return renderEventsListData(colors, data, flags);
|
|
1389
|
+
case "tools call":
|
|
1337
1390
|
return renderInvokeData(colors, data, flags);
|
|
1338
|
-
case "revoke":
|
|
1391
|
+
case "sessions revoke":
|
|
1339
1392
|
return renderRevokeData(colors, data);
|
|
1340
1393
|
case "daemon run":
|
|
1341
1394
|
return renderDaemonRunData(colors, data, flags);
|