xaq-cli 0.0.0 → 0.1.0-nightly.71
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/LICENSE +21 -0
- package/README.md +79 -0
- package/dist/admin-5gThRwgj.js +162 -0
- package/dist/admin-5gThRwgj.js.map +1 -0
- package/dist/admin-Btrm8mCd.js +218 -0
- package/dist/admin-Btrm8mCd.js.map +1 -0
- package/dist/admin-CWofnT_Q.js +2 -0
- package/dist/api-BFpElwqK.js +89 -0
- package/dist/api-BFpElwqK.js.map +1 -0
- package/dist/audit-BB7jK9Z5.js +2 -0
- package/dist/audit-BVYknW2i.js +79 -0
- package/dist/audit-BVYknW2i.js.map +1 -0
- package/dist/auth-CXmBdMqw.js +2 -0
- package/dist/auth-D-GmwNv-.js +550 -0
- package/dist/auth-D-GmwNv-.js.map +1 -0
- package/dist/brand--ZB8jTn1.js +32 -0
- package/dist/brand--ZB8jTn1.js.map +1 -0
- package/dist/bus-Cz0flS37.js +12 -0
- package/dist/bus-Cz0flS37.js.map +1 -0
- package/dist/bus-OKIGut72.js +2 -0
- package/dist/cli.js +234 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands-BNVLjYXA.js +167 -0
- package/dist/commands-BNVLjYXA.js.map +1 -0
- package/dist/commands-CdA6-WE7.js +112 -0
- package/dist/commands-CdA6-WE7.js.map +1 -0
- package/dist/config-C9XmYZWO.js +94 -0
- package/dist/config-C9XmYZWO.js.map +1 -0
- package/dist/config-CNVi0v5t.js +2 -0
- package/dist/connect-TtJ9n0Rw.js +362 -0
- package/dist/connect-TtJ9n0Rw.js.map +1 -0
- package/dist/content-C1lKMxFS.js +1170 -0
- package/dist/content-C1lKMxFS.js.map +1 -0
- package/dist/content-worker.js +28 -0
- package/dist/content-worker.js.map +1 -0
- package/dist/crypto-fv2fj3S8.js +75 -0
- package/dist/crypto-fv2fj3S8.js.map +1 -0
- package/dist/db-BEUEUPyP.js +561 -0
- package/dist/db-BEUEUPyP.js.map +1 -0
- package/dist/db-Q5BQuwga.js +2 -0
- package/dist/errors-FbMS3QBo.js +39 -0
- package/dist/errors-FbMS3QBo.js.map +1 -0
- package/dist/filter-Ck6OMxvu.js +158 -0
- package/dist/filter-Ck6OMxvu.js.map +1 -0
- package/dist/hermes-CKyzt1mH.js +3 -0
- package/dist/hermes-DHnDTYJp.js +1023 -0
- package/dist/hermes-DHnDTYJp.js.map +1 -0
- package/dist/import-DsN5NZT3.js +910 -0
- package/dist/import-DsN5NZT3.js.map +1 -0
- package/dist/instance-CwWbFbJ8.js +2 -0
- package/dist/instance-Dj9TpJB2.js +195 -0
- package/dist/instance-Dj9TpJB2.js.map +1 -0
- package/dist/listen-B9iO7HgQ.js +1568 -0
- package/dist/listen-B9iO7HgQ.js.map +1 -0
- package/dist/listen-CXVKG9XD.js +2 -0
- package/dist/manage-Bo6COB4p.js +477 -0
- package/dist/manage-Bo6COB4p.js.map +1 -0
- package/dist/manage-DoXqvjnU.js +2 -0
- package/dist/mcpheader-B8c5YO1B.js +2 -0
- package/dist/mcpheader-C9H4vudu.js +185 -0
- package/dist/mcpheader-C9H4vudu.js.map +1 -0
- package/dist/mcpsetup-BnHudwpq.js +2 -0
- package/dist/mcpsetup-DXXo2qjf.js +1053 -0
- package/dist/mcpsetup-DXXo2qjf.js.map +1 -0
- package/dist/node-check-CjTk6VNL.js +24 -0
- package/dist/node-check-CjTk6VNL.js.map +1 -0
- package/dist/paths-d-C2kdjo.js +2 -0
- package/dist/paths-yzOKD6ow.js +46 -0
- package/dist/paths-yzOKD6ow.js.map +1 -0
- package/dist/profile-CcMZ4hn7.js +2 -0
- package/dist/profile-NmMKAczz.js +222 -0
- package/dist/profile-NmMKAczz.js.map +1 -0
- package/dist/prompt-CKMQx_MJ.js +114 -0
- package/dist/prompt-CKMQx_MJ.js.map +1 -0
- package/dist/recipes-C7TIDlRj.js +3 -0
- package/dist/recipes-DG_-doGq.js +314 -0
- package/dist/recipes-DG_-doGq.js.map +1 -0
- package/dist/send-Dk-t8ia-.js +108 -0
- package/dist/send-Dk-t8ia-.js.map +1 -0
- package/dist/server-CDqDp1q3.js +20640 -0
- package/dist/server-CDqDp1q3.js.map +1 -0
- package/dist/service-BVn3soZZ.js +766 -0
- package/dist/service-BVn3soZZ.js.map +1 -0
- package/dist/service-BaWGPmSd.js +2 -0
- package/dist/service-C7jfo-Ex.js +457 -0
- package/dist/service-C7jfo-Ex.js.map +1 -0
- package/dist/service-DxDyKDQ-.js +2 -0
- package/dist/settings-B5qSHxRM.js +134 -0
- package/dist/settings-B5qSHxRM.js.map +1 -0
- package/dist/settings-DQvZaMN1.js +2 -0
- package/dist/setup-token-CwuSzfLK.js +48 -0
- package/dist/setup-token-CwuSzfLK.js.map +1 -0
- package/dist/skill/SKILL.md +445 -0
- package/dist/skill-1UQj3qgW.js +69 -0
- package/dist/skill-1UQj3qgW.js.map +1 -0
- package/dist/skill-pelq18JQ.js +2 -0
- package/dist/ssrf-BlVpGZ7C.js +181 -0
- package/dist/ssrf-BlVpGZ7C.js.map +1 -0
- package/dist/types-BhJDbSzE.js +135 -0
- package/dist/types-BhJDbSzE.js.map +1 -0
- package/dist/ui-B8Sd4Y7Y.js +81 -0
- package/dist/ui-B8Sd4Y7Y.js.map +1 -0
- package/dist/urls-Dp8pBGMU.js +2 -0
- package/dist/urls-U_LfYV61.js +27 -0
- package/dist/urls-U_LfYV61.js.map +1 -0
- package/dist/version-BxH7OuWD.js +20 -0
- package/dist/version-BxH7OuWD.js.map +1 -0
- package/dist/web/assets/_agentId-DonOCOlC.js +1 -0
- package/dist/web/assets/_app-B7t5lEia.js +1 -0
- package/dist/web/assets/_app-DMTGqYpM.js +65 -0
- package/dist/web/assets/_connectionId-DR5vFXOg.js +1 -0
- package/dist/web/assets/_webhookId-LfUlsULp.js +1 -0
- package/dist/web/assets/account-status-CEszs13C.js +1 -0
- package/dist/web/assets/accounts-5m7aiXuZ.js +1 -0
- package/dist/web/assets/accounts-list-YfJzThZa.js +1 -0
- package/dist/web/assets/activity-DOIe7SAg.js +1 -0
- package/dist/web/assets/add-connection-C8JR-13R.js +1 -0
- package/dist/web/assets/agent-create-form-CPxnPYH4.js +1 -0
- package/dist/web/assets/agents-B2Qj5KxT.js +1 -0
- package/dist/web/assets/approvals-BGaVaoFy.js +1 -0
- package/dist/web/assets/approve._id-BLrQnzPM.js +1 -0
- package/dist/web/assets/arrow-left-DZVAXoKj.js +1 -0
- package/dist/web/assets/arrow-right-C2zcqRr2.js +1 -0
- package/dist/web/assets/attention-gO9Nw5uW.js +1 -0
- package/dist/web/assets/audit-BPXXre8S.js +1 -0
- package/dist/web/assets/audit-bits-CGyIprzK.js +1 -0
- package/dist/web/assets/bell-ring-ig3zn1iS.js +1 -0
- package/dist/web/assets/bot-Zk76q_39.js +1 -0
- package/dist/web/assets/calendar-notice-DsCfq3VJ.js +1 -0
- package/dist/web/assets/calendar-plus-Cycfkk0y.js +1 -0
- package/dist/web/assets/calendar-setup-Pn61Wj1n.js +2 -0
- package/dist/web/assets/card-GSO5o-Ug.js +1 -0
- package/dist/web/assets/check-check-Ds1bPIGv.js +1 -0
- package/dist/web/assets/checkbox-B937U-Fd.js +1 -0
- package/dist/web/assets/chevron-right-Cqh4mDk1.js +1 -0
- package/dist/web/assets/chips-input-2xWDofmE.js +1 -0
- package/dist/web/assets/choice-cards-CgAIqBGX.js +1 -0
- package/dist/web/assets/circle-x-DcU-0zVh.js +1 -0
- package/dist/web/assets/command-khhto70y.js +1 -0
- package/dist/web/assets/common-BPZV8TFb.js +1 -0
- package/dist/web/assets/connect-agent-guide-CJdw31p2.js +1 -0
- package/dist/web/assets/connect-host-lr2c3xWh.js +6 -0
- package/dist/web/assets/connection-row-DYx_5o0c.js +1 -0
- package/dist/web/assets/connections-C4NOFb5R.js +1 -0
- package/dist/web/assets/data-table-kW1pkRB5.js +1 -0
- package/dist/web/assets/empty-DzTDioBP.js +1 -0
- package/dist/web/assets/events-C9zJnPKK.js +1 -0
- package/dist/web/assets/eye-B9gT1QSq.js +1 -0
- package/dist/web/assets/eye-off-Diktqz_g.js +1 -0
- package/dist/web/assets/field-C6ejurju.js +1 -0
- package/dist/web/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/web/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/web/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/web/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/web/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/dist/web/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/dist/web/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/dist/web/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/dist/web/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/dist/web/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/dist/web/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/web/assets/get-started-C38bq1gZ.js +2 -0
- package/dist/web/assets/hidden-chars-Dr9TxfvF.js +1 -0
- package/dist/web/assets/index-DGzo3kGh.js +11 -0
- package/dist/web/assets/index-zKOCQLGX.css +2 -0
- package/dist/web/assets/input-CWwJgozn.js +1 -0
- package/dist/web/assets/input-group-HG8OXcJ4.js +1 -0
- package/dist/web/assets/label-multi-select-sH8mllw8.js +1 -0
- package/dist/web/assets/labels-B5UqVwFo.js +1 -0
- package/dist/web/assets/laptop-BxJ4Nbj7.js +1 -0
- package/dist/web/assets/last-call-D-O5O94z.js +1 -0
- package/dist/web/assets/listener-handled-HNCuVrQ7.js +1 -0
- package/dist/web/assets/login-DaeKclcf.js +1 -0
- package/dist/web/assets/mail-C5fm2pTC.js +1 -0
- package/dist/web/assets/mcp-call-UoOTPFyh.js +1 -0
- package/dist/web/assets/mcp-call-card-Cr-OEDX2.js +3 -0
- package/dist/web/assets/message-square-quote-se8Bf3RP.js +1 -0
- package/dist/web/assets/mutations-CLPqJ6cj.js +1 -0
- package/dist/web/assets/network-igNw0VVx.js +1 -0
- package/dist/web/assets/pairing-XjuaVr2u.js +1 -0
- package/dist/web/assets/pairing-notifier-KPzOEZSO.js +1 -0
- package/dist/web/assets/paperclip-znS4iTrg.js +1 -0
- package/dist/web/assets/password-input-DV3aqcNf.js +1 -0
- package/dist/web/assets/pause-o-dK3khK.js +1 -0
- package/dist/web/assets/pencil-DkPFAhEq.js +1 -0
- package/dist/web/assets/play-DvCHND93.js +1 -0
- package/dist/web/assets/plug-CXAniEiR.js +1 -0
- package/dist/web/assets/plug-zap-BwR-l5wb.js +1 -0
- package/dist/web/assets/plus-B_TUslu3.js +1 -0
- package/dist/web/assets/preload-helper-Cc_aKyUb.js +1 -0
- package/dist/web/assets/public-url-rF2Xjyel.js +1 -0
- package/dist/web/assets/queries-Cv3nXx0U.js +42 -0
- package/dist/web/assets/refresh-cw-Ch5FNrby.js +1 -0
- package/dist/web/assets/revealed-keys-C_9NtJPY.js +1 -0
- package/dist/web/assets/rolldown-runtime-XKyaHtl9.js +1 -0
- package/dist/web/assets/rotate-ccw-B-mzPDk0.js +1 -0
- package/dist/web/assets/search-CTVKGomS.js +1 -0
- package/dist/web/assets/select-Bb806bpq.js +1 -0
- package/dist/web/assets/send-DuMVuYs1.js +1 -0
- package/dist/web/assets/settings-BWauhfFf.js +1 -0
- package/dist/web/assets/setup-B7bNKQOp.js +1 -0
- package/dist/web/assets/sheet-CYPwpUsH.js +1 -0
- package/dist/web/assets/shield-alert-CSYB-ThV.js +1 -0
- package/dist/web/assets/shield-check-BulpsYqK.js +1 -0
- package/dist/web/assets/sparkles-CUTiob4K.js +1 -0
- package/dist/web/assets/stamp-BNXUGNa3.js +1 -0
- package/dist/web/assets/step-url-51aMNak-.js +1 -0
- package/dist/web/assets/stream-Du7fY5_Y.js +1 -0
- package/dist/web/assets/tag-I5_-1jFL.js +1 -0
- package/dist/web/assets/terminal-BvxDFTJc.js +1 -0
- package/dist/web/assets/test-email-button-DYi7V_6X.js +1 -0
- package/dist/web/assets/toggle-group-Bdo1gmQ1.js +1 -0
- package/dist/web/assets/trash-Ci0aICEl.js +1 -0
- package/dist/web/assets/use-delivery-speed-BBh7W2q6.js +1 -0
- package/dist/web/assets/use-label-names-BXicesnP.js +1 -0
- package/dist/web/assets/use-sign-out-CPrY_oVO.js +1 -0
- package/dist/web/assets/useInfiniteQuery-DJLIM8yJ.js +1 -0
- package/dist/web/assets/useQuery-MrTwwNy5.js +1 -0
- package/dist/web/assets/useRouterState-D4OBK8l5.js +1 -0
- package/dist/web/assets/webhook-DjeeZiNC.js +1 -0
- package/dist/web/assets/webhook-bits-jw-y2D3u.js +1 -0
- package/dist/web/assets/webhook-sheet-B8h0skDe.js +34 -0
- package/dist/web/assets/webhooks-Cfsdpoyd.js +1 -0
- package/dist/web/assets/x-MF2TZ9aG.js +1 -0
- package/dist/web/favicon.svg +1 -0
- package/dist/web/index.html +27 -0
- package/package.json +98 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
//#region server/bus.ts
|
|
4
|
+
function createBus() {
|
|
5
|
+
const bus = new EventEmitter();
|
|
6
|
+
bus.setMaxListeners(0);
|
|
7
|
+
return bus;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { createBus as t };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=bus-Cz0flS37.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bus-Cz0flS37.js","names":[],"sources":["../server/bus.ts"],"sourcesContent":["import { EventEmitter } from \"node:events\";\nimport type { Account, Agent, Approval, AuditEntry, Delivery, MailEvent, PairingRequest, Settings } from \"../shared/types.ts\";\n\n/**\n * In-process notifications. Fired *after* the change is committed to the database, so listeners\n * can read it back. Used by the console SSE stream, `wait_for_events`, the webhook worker and the\n * ingest loop (settings changes). Listeners must not throw.\n */\nexport interface BusEvents {\n /** A new event row was committed (and its webhook deliveries enqueued). */\n event: [MailEvent];\n /** A delivery was created or changed state. */\n delivery: [Delivery];\n /** An approval was created or decided. */\n approval: [Approval];\n audit: [AuditEntry];\n /** An account was connected or its status/sync state changed. */\n account: [Account];\n settings: [Settings];\n /**\n * An agent's access changed: live listener streams re-check the key/grants and close on revocation.\n * Emitted by policy on update (incl. disable), key rotation, deletion and grant upsert/delete.\n */\n agent: [{ agentId: string; reason: \"updated\" | \"key_rotated\" | \"deleted\" | \"grant_changed\" }];\n pairing: [PairingRequest];\n /**\n * The agent as the console shows it, after its state changed: listener connect/disconnect/ack,\n * key use (lastUsedAt/lastMcpAt writes) and access changes. Coalesced per agent (policy.publishAgent).\n * Sent to the console stream as `{ kind: \"agent\" }`.\n */\n agentState: [Agent];\n}\n\nexport type Bus = EventEmitter<BusEvents>;\n\nexport function createBus(): Bus {\n const bus = new EventEmitter<BusEvents>();\n // Every SSE client and pending wait_for_events call adds a listener.\n bus.setMaxListeners(0);\n return bus;\n}\n"],"mappings":";;;AAmCA,SAAgB,YAAiB;CAC/B,MAAM,MAAM,IAAI,aAAwB;CAExC,IAAI,gBAAgB,CAAC;CACrB,OAAO;AACT"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./node-check-CjTk6VNL.js";
|
|
3
|
+
import { i as underService, n as envVar } from "./brand--ZB8jTn1.js";
|
|
4
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
5
|
+
import { basename, join, sep } from "node:path";
|
|
6
|
+
//#region server/cli.ts
|
|
7
|
+
const { Command, InvalidArgumentError } = await import("commander");
|
|
8
|
+
const { default: pc } = await import("picocolors");
|
|
9
|
+
process.title = "xaq";
|
|
10
|
+
if (basename(process.argv[1] ?? "") === "agentinbox" && !globalThis.__xaqShimNotice && !underService()) process.stderr.write("agentinbox is now xaq: use `npx xaq-cli …` (or `xaq …` once installed with npm i -g xaq-cli).\n");
|
|
11
|
+
const emitWarning = process.emitWarning.bind(process);
|
|
12
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
13
|
+
const message = typeof warning === "string" ? warning : warning.message;
|
|
14
|
+
if (((typeof rest[0] === "string" ? rest[0] : rest[0]?.type) === "ExperimentalWarning" || warning.name === "ExperimentalWarning") && /SQLite/i.test(message)) return;
|
|
15
|
+
return emitWarning(warning, ...rest);
|
|
16
|
+
});
|
|
17
|
+
/** package.json sits one level above both dist/cli.js and server/cli.ts. */
|
|
18
|
+
function packageVersion() {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(readFileSync(join(import.meta.dirname, "..", "package.json"), "utf8")).version;
|
|
21
|
+
} catch {
|
|
22
|
+
return "0.0.0";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const version = packageVersion();
|
|
26
|
+
const validPort = (value) => /^\d+$/.test(value) && Number(value) <= 65535;
|
|
27
|
+
function parsePort(value) {
|
|
28
|
+
if (!validPort(value)) throw new InvalidArgumentError("Port must be a number from 0 to 65535.");
|
|
29
|
+
return Number(value);
|
|
30
|
+
}
|
|
31
|
+
/** --port, else a validated PORT env (a typo there would otherwise become NaN deep inside). */
|
|
32
|
+
function portOption(flag) {
|
|
33
|
+
if (flag !== void 0) return flag;
|
|
34
|
+
const env = process.env.PORT?.trim();
|
|
35
|
+
if (!env) return void 0;
|
|
36
|
+
if (!validPort(env)) throw new Error(`PORT="${process.env.PORT}" is not a valid port. Use a number from 0 to 65535.`);
|
|
37
|
+
return Number(env);
|
|
38
|
+
}
|
|
39
|
+
const withServerOptions = (cmd) => cmd.option("-p, --port <port>", "port to listen on (default 8425, env PORT)", parsePort).option("--host <host>", "bind address (default 0.0.0.0, env HOST)").option("-d, --data <dir>", "data directory (default ~/.xaq, or an existing ~/.agentinbox; env XAQ_DATA)");
|
|
40
|
+
const configInput = (o) => ({
|
|
41
|
+
port: portOption(o.port),
|
|
42
|
+
host: o.host,
|
|
43
|
+
dataDir: o.data
|
|
44
|
+
});
|
|
45
|
+
const program = new Command("xaq").description("Self-hosted MCP broker for AI agents (Notion, WHOOP, any MCP server, Gmail and Calendar). Start it, open the link it prints, and the web console walks you through the rest (what to connect, your first agent).").version(version, "-v, --version", "print the version").helpOption("-h, --help", "show help").addHelpText("after", `
|
|
46
|
+
Examples:
|
|
47
|
+
npx xaq-cli start the server and print the setup link
|
|
48
|
+
npx xaq-cli service install keep it running in the background, also after a reboot
|
|
49
|
+
npx xaq-cli reset-password print a new setup link (forgot the console password)
|
|
50
|
+
npx xaq-cli connect https://mail.example.com --tool claude-code --setup-mcp
|
|
51
|
+
on an agent's machine: pair it (you approve in the console)
|
|
52
|
+
npx xaq-cli listen --recipe claude-code
|
|
53
|
+
on an agent's machine: wake Claude Code on new mail
|
|
54
|
+
|
|
55
|
+
Run any command with --help for details. Install it with npm i -g xaq-cli to type xaq instead.
|
|
56
|
+
Coming from agentinbox? Your data, keys and services carry over: see "Upgrading from agentinbox" in the README.
|
|
57
|
+
Docs: https://github.com/wynn-dev/xaq/blob/main/docs/reference.md`).showSuggestionAfterError();
|
|
58
|
+
const SERVER_GROUP = "Run xaq:";
|
|
59
|
+
const AGENT_GROUP = "On an agent's machine:";
|
|
60
|
+
withServerOptions(program.command("start", { isDefault: true }).description("run xaq in this terminal and print the setup link (the default command)").summary("run it in this terminal (the default command)").helpGroup(SERVER_GROUP)).option("--no-open", "don't open a browser on first run").option("--dev", "development: API only, the console runs on Vite (:5173)").allowExcessArguments().action(async (o, cmd) => {
|
|
61
|
+
if (cmd.args.length) throw new Error(unknownCommand(cmd.args[0]));
|
|
62
|
+
const { runForeground } = await import("./server-CDqDp1q3.js");
|
|
63
|
+
await runForeground({
|
|
64
|
+
...configInput(o),
|
|
65
|
+
dev: !!o.dev,
|
|
66
|
+
open: o.open,
|
|
67
|
+
version
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
const service = program.command("service").description("run xaq in the background as a systemd / launchd service, started at boot").summary("run it in the background (systemd / launchd)").helpGroup(SERVER_GROUP);
|
|
71
|
+
withServerOptions(service.command("install").description("install and start the service; re-run it to change the port or update (replaces an agentinbox service, keeping its data)")).action(async (o) => {
|
|
72
|
+
const { loadConfig } = await import("./config-CNVi0v5t.js");
|
|
73
|
+
const { installService, serviceInstallInput, systemOps } = await import("./service-DxDyKDQ-.js");
|
|
74
|
+
await installService(loadConfig(serviceInstallInput(configInput(o), systemOps()), version));
|
|
75
|
+
});
|
|
76
|
+
service.command("uninstall").description("stop and remove the service (keeps your data)").option("-d, --data <dir>", "data directory, if the installed service doesn't say (default ~/.xaq, or an existing ~/.agentinbox; env XAQ_DATA)").action(async (o) => {
|
|
77
|
+
const { uninstallService } = await import("./service-DxDyKDQ-.js");
|
|
78
|
+
const { resolveDataDir } = await import("./config-CNVi0v5t.js");
|
|
79
|
+
uninstallService({ dataDir: resolveDataDir(o.data, { quiet: true }) });
|
|
80
|
+
});
|
|
81
|
+
service.command("status").description("show service status").action(async () => {
|
|
82
|
+
const { serviceStatus } = await import("./service-DxDyKDQ-.js");
|
|
83
|
+
serviceStatus();
|
|
84
|
+
});
|
|
85
|
+
const skill = program.command("skill").description("the xaq agent skill (SKILL.md): teaches an agent how to use xaq's tools").summary("install the agent skill (SKILL.md)").helpGroup(AGENT_GROUP);
|
|
86
|
+
skill.command("print").description("print SKILL.md to stdout").option("--url <url>", "xaq server URL to fill in (env XAQ_URL)").action(async (o) => {
|
|
87
|
+
const { renderSkill } = await import("./skill-pelq18JQ.js");
|
|
88
|
+
process.stdout.write(renderSkill(await serverUrl(o.url)));
|
|
89
|
+
});
|
|
90
|
+
skill.command("install").description("install SKILL.md for an agent (no running server needed)").option("-t, --target <target>", "claude, codex, agents, or a skills directory path", "claude").option("--url <url>", "xaq server URL agents connect to (env XAQ_URL)").action(async (o) => {
|
|
91
|
+
const { removeLegacySkill, renderSkill, SKILL_TARGETS, skillPath, writeSkill } = await import("./skill-pelq18JQ.js");
|
|
92
|
+
checkSkillTarget(o.target, Object.keys(SKILL_TARGETS));
|
|
93
|
+
const url = await serverUrl(o.url);
|
|
94
|
+
const path = skillPath(o.target);
|
|
95
|
+
writeSkill(path, renderSkill(url));
|
|
96
|
+
console.log(`Installed the xaq skill to ${pc.cyan(path)} (server ${url}).`);
|
|
97
|
+
const replaced = removeLegacySkill(o.target);
|
|
98
|
+
if (replaced) console.log(`Removed the agentinbox skill it replaces (${replaced}).`);
|
|
99
|
+
if (!o.url && !envVar("URL")) console.log(pc.dim("Agents on other machines need the server's address: re-run with --url https://your-server."));
|
|
100
|
+
});
|
|
101
|
+
program.command("reset-password").description("forgot the console password? Removes it and prints a new setup link (or the first-run link)").summary("print a new setup link (forgot the password?)").helpGroup(SERVER_GROUP).option("-d, --data <dir>", "data directory (default ~/.xaq, or an existing ~/.agentinbox; env XAQ_DATA)").option("-p, --port <port>", "port of the running instance, used for the printed link", parsePort).option("--host <host>", "host of the running instance, used for the printed link").action(async (o) => {
|
|
102
|
+
const { dataFiles, resolveDataDir } = await import("./config-CNVi0v5t.js");
|
|
103
|
+
const dataDir = resolveDataDir(o.data);
|
|
104
|
+
const dbPath = dataFiles(dataDir).db;
|
|
105
|
+
if (!existsSync(dbPath)) throw new Error(`No xaq data in ${dataDir}. If xaq runs as a system service, run this with sudo; otherwise pass the data directory it uses with --data.`);
|
|
106
|
+
const { loadConfig } = await import("./config-CNVi0v5t.js");
|
|
107
|
+
const { Db } = await import("./db-Q5BQuwga.js");
|
|
108
|
+
const { createBus } = await import("./bus-OKIGut72.js");
|
|
109
|
+
const { SettingsStore } = await import("./settings-DQvZaMN1.js");
|
|
110
|
+
const { AuditLog } = await import("./audit-BB7jK9Z5.js");
|
|
111
|
+
const { currentSetupToken, isClaimed, resetAdminPassword } = await import("./auth-CXmBdMqw.js");
|
|
112
|
+
const { runningInstance } = await import("./instance-CwWbFbJ8.js");
|
|
113
|
+
const { consoleUrls } = await import("./urls-Dp8pBGMU.js");
|
|
114
|
+
const running = runningInstance(dataDir);
|
|
115
|
+
const config = loadConfig({
|
|
116
|
+
dataDir,
|
|
117
|
+
port: portOption(o.port) ?? running?.port,
|
|
118
|
+
host: o.host ?? (process.env.HOST || running?.host)
|
|
119
|
+
}, version);
|
|
120
|
+
const db = new Db(dbPath);
|
|
121
|
+
try {
|
|
122
|
+
const bus = createBus();
|
|
123
|
+
const core = {
|
|
124
|
+
config,
|
|
125
|
+
db,
|
|
126
|
+
settings: new SettingsStore(db, bus, config),
|
|
127
|
+
audit: new AuditLog(db, bus)
|
|
128
|
+
};
|
|
129
|
+
const claimed = isClaimed(core);
|
|
130
|
+
const token = claimed ? resetAdminPassword(core) : currentSetupToken(core);
|
|
131
|
+
console.log(claimed ? "\n The admin password was removed and all console sessions were signed out.\n Open this link to set a new password:\n" : "\n This instance hasn't been claimed yet, so there is no password to reset.\n Open this link to claim it:\n");
|
|
132
|
+
const urls = consoleUrls(config.host, config.port);
|
|
133
|
+
const publicUrl = core.settings.publicUrl();
|
|
134
|
+
if (!urls.includes(publicUrl)) urls.push(publicUrl);
|
|
135
|
+
for (const url of urls) console.log(` ${pc.cyan(`${url}/setup?token=${token}`)}`);
|
|
136
|
+
console.log(pc.dim(`\n ${running ? "The running xaq picks this up immediately; no restart needed." : "Start xaq to use it."}\n`));
|
|
137
|
+
} finally {
|
|
138
|
+
db.close();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
const { registerListenCommands } = await import("./commands-BNVLjYXA.js");
|
|
142
|
+
registerListenCommands(program);
|
|
143
|
+
const { registerConnectionsCommands } = await import("./commands-CdA6-WE7.js");
|
|
144
|
+
registerConnectionsCommands(program);
|
|
145
|
+
const AGENT_SUMMARIES = {
|
|
146
|
+
connect: "pair an agent; you approve it in the console",
|
|
147
|
+
"setup-mcp": "add xaq to the agent's MCP config",
|
|
148
|
+
"mcp-header": "print the agent's auth header (MCP clients)",
|
|
149
|
+
listen: "wake the agent when new mail arrives"
|
|
150
|
+
};
|
|
151
|
+
for (const cmd of program.commands) {
|
|
152
|
+
const summary = AGENT_SUMMARIES[cmd.name()];
|
|
153
|
+
if (summary) cmd.summary(summary).helpGroup(AGENT_GROUP);
|
|
154
|
+
}
|
|
155
|
+
program.commandsGroup(SERVER_GROUP).helpCommand("help [command]", "show help for a command").helpCommand(true);
|
|
156
|
+
/**
|
|
157
|
+
* The URL written into SKILL.md: --url, XAQ_URL (AGENTINBOX_URL), else the xaq running on this
|
|
158
|
+
* machine (its lock file), else http://localhost:8425.
|
|
159
|
+
*/
|
|
160
|
+
async function serverUrl(flag) {
|
|
161
|
+
let raw = flag ?? envVar("URL");
|
|
162
|
+
if (!raw) {
|
|
163
|
+
const { runningInstance } = await import("./instance-CwWbFbJ8.js");
|
|
164
|
+
const { resolveDataDir } = await import("./config-CNVi0v5t.js");
|
|
165
|
+
const running = runningInstance(resolveDataDir(void 0, { quiet: true }));
|
|
166
|
+
const host = running && ![
|
|
167
|
+
"0.0.0.0",
|
|
168
|
+
"::",
|
|
169
|
+
""
|
|
170
|
+
].includes(running.host) ? running.host : "localhost";
|
|
171
|
+
raw = `http://${host.includes(":") ? `[${host}]` : host}:${running?.port || process.env.PORT || 8425}`;
|
|
172
|
+
}
|
|
173
|
+
let url;
|
|
174
|
+
try {
|
|
175
|
+
url = new URL(raw);
|
|
176
|
+
} catch {
|
|
177
|
+
throw new Error(`Invalid --url "${raw}". Use a full URL such as https://mail.example.com.`);
|
|
178
|
+
}
|
|
179
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("--url must start with http:// or https://");
|
|
180
|
+
return (url.origin + url.pathname).replace(/\/+$/, "");
|
|
181
|
+
}
|
|
182
|
+
/** A known agent name, or something that is clearly a path; typos like "claud" are refused. */
|
|
183
|
+
function checkSkillTarget(target, known) {
|
|
184
|
+
if (known.includes(target) || target.includes("/") || target.includes(sep) || target.startsWith(".") || isDir(target)) return;
|
|
185
|
+
const near = closest(target, known);
|
|
186
|
+
throw new Error(`Unknown skill target "${target}".${near ? ` Did you mean ${near}?` : ""} Use ${known.join(", ")}, or a directory path such as ./skills.`);
|
|
187
|
+
}
|
|
188
|
+
function isDir(path) {
|
|
189
|
+
try {
|
|
190
|
+
return statSync(path).isDirectory();
|
|
191
|
+
} catch {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function unknownCommand(name) {
|
|
196
|
+
const near = closest(name, [...program.commands.map((c) => c.name()), "help"]);
|
|
197
|
+
return `unknown command '${name}'${near ? `\n(Did you mean ${near}?)` : ""}\nRun xaq --help for the list of commands.`;
|
|
198
|
+
}
|
|
199
|
+
/** The candidate within edit distance 2 (or a prefix match), if any. */
|
|
200
|
+
function closest(word, candidates) {
|
|
201
|
+
const distance = (a, b) => {
|
|
202
|
+
const row = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
203
|
+
for (let i = 1; i <= a.length; i++) {
|
|
204
|
+
let prev = row[0];
|
|
205
|
+
row[0] = i;
|
|
206
|
+
for (let j = 1; j <= b.length; j++) {
|
|
207
|
+
const cur = row[j];
|
|
208
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
209
|
+
prev = cur;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return row[b.length];
|
|
213
|
+
};
|
|
214
|
+
let best = null;
|
|
215
|
+
for (const c of candidates) {
|
|
216
|
+
const d = c.startsWith(word) && word.length >= 3 ? 0 : distance(word.toLowerCase(), c);
|
|
217
|
+
if (d <= 2 && (!best || d < best.d)) best = {
|
|
218
|
+
name: c,
|
|
219
|
+
d
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return best?.name ?? null;
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
await program.parseAsync(process.argv);
|
|
226
|
+
} catch (err) {
|
|
227
|
+
console.error(pc.red(err instanceof Error ? err.message : String(err)));
|
|
228
|
+
if (envVar("DEBUG") && err instanceof Error) console.error(err.stack);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
export {};
|
|
233
|
+
|
|
234
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../server/cli.ts"],"sourcesContent":["// Must stay the first import: exits with a clear message on a too-old Node.\nimport \"./node-check.ts\";\nimport { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, join, sep } from \"node:path\";\nimport type { Command as CommandType } from \"commander\";\nimport { envVar, underService } from \"./brand.ts\";\n\n// Loaded after the Node check (static imports are hoisted above it in the bundle).\nconst { Command, InvalidArgumentError } = await import(\"commander\");\nconst { default: pc } = await import(\"picocolors\");\n\nprocess.title = \"xaq\";\n\n// The `agentinbox` command now comes from the agentinbox-cli shim, which says this itself and sets the\n// flag before running us; the check stays for anything else that starts us under that name.\nif (basename(process.argv[1] ?? \"\") === \"agentinbox\" && !(globalThis as { __xaqShimNotice?: boolean }).__xaqShimNotice && !underService()) {\n process.stderr.write(\"agentinbox is now xaq: use `npx xaq-cli …` (or `xaq …` once installed with npm i -g xaq-cli).\\n\");\n}\n\n// node:sqlite is stable enough for us but still warns on Node 22. This runs before any server\n// module loads: everything that touches node:sqlite is imported dynamically below.\nconst emitWarning = process.emitWarning.bind(process);\nprocess.emitWarning = ((warning: string | Error, ...rest: unknown[]) => {\n const message = typeof warning === \"string\" ? warning : warning.message;\n const type = typeof rest[0] === \"string\" ? rest[0] : (rest[0] as { type?: string } | undefined)?.type;\n if ((type === \"ExperimentalWarning\" || (warning as Error).name === \"ExperimentalWarning\") && /SQLite/i.test(message)) return;\n return (emitWarning as (...args: unknown[]) => void)(warning, ...rest);\n}) as typeof process.emitWarning;\n\n/** package.json sits one level above both dist/cli.js and server/cli.ts. */\nfunction packageVersion(): string {\n try {\n return (JSON.parse(readFileSync(join(import.meta.dirname, \"..\", \"package.json\"), \"utf8\")) as { version: string }).version;\n } catch {\n return \"0.0.0\";\n }\n}\n\nconst version = packageVersion();\n\ninterface ServerFlags {\n port?: number;\n host?: string;\n data?: string;\n}\n\nconst validPort = (value: string) => /^\\d+$/.test(value) && Number(value) <= 65535;\n\nfunction parsePort(value: string): number {\n if (!validPort(value)) throw new InvalidArgumentError(\"Port must be a number from 0 to 65535.\");\n return Number(value);\n}\n\n/** --port, else a validated PORT env (a typo there would otherwise become NaN deep inside). */\nfunction portOption(flag: number | undefined): number | undefined {\n if (flag !== undefined) return flag;\n const env = process.env.PORT?.trim();\n if (!env) return undefined;\n if (!validPort(env)) throw new Error(`PORT=\"${process.env.PORT}\" is not a valid port. Use a number from 0 to 65535.`);\n return Number(env);\n}\n\nconst withServerOptions = (cmd: CommandType) =>\n cmd\n .option(\"-p, --port <port>\", \"port to listen on (default 8425, env PORT)\", parsePort)\n .option(\"--host <host>\", \"bind address (default 0.0.0.0, env HOST)\")\n .option(\"-d, --data <dir>\", \"data directory (default ~/.xaq, or an existing ~/.agentinbox; env XAQ_DATA)\");\n\nconst configInput = (o: ServerFlags) => ({ port: portOption(o.port), host: o.host, dataDir: o.data });\n\nconst program = new Command(\"xaq\")\n .description(\n \"Self-hosted MCP broker for AI agents (Notion, WHOOP, any MCP server, Gmail and Calendar). Start it, open the \" +\n \"link it prints, and the web console walks you through the rest (what to connect, your first agent).\",\n )\n .version(version, \"-v, --version\", \"print the version\")\n .helpOption(\"-h, --help\", \"show help\")\n .addHelpText(\n \"after\",\n `\nExamples:\n npx xaq-cli start the server and print the setup link\n npx xaq-cli service install keep it running in the background, also after a reboot\n npx xaq-cli reset-password print a new setup link (forgot the console password)\n npx xaq-cli connect https://mail.example.com --tool claude-code --setup-mcp\n on an agent's machine: pair it (you approve in the console)\n npx xaq-cli listen --recipe claude-code\n on an agent's machine: wake Claude Code on new mail\n\nRun any command with --help for details. Install it with npm i -g xaq-cli to type xaq instead.\nComing from agentinbox? Your data, keys and services carry over: see \"Upgrading from agentinbox\" in the README.\nDocs: https://github.com/wynn-dev/xaq/blob/main/docs/reference.md`,\n )\n .showSuggestionAfterError();\n\nconst SERVER_GROUP = \"Run xaq:\";\nconst AGENT_GROUP = \"On an agent's machine:\";\n\nwithServerOptions(\n program\n .command(\"start\", { isDefault: true })\n .description(\"run xaq in this terminal and print the setup link (the default command)\")\n .summary(\"run it in this terminal (the default command)\")\n .helpGroup(SERVER_GROUP),\n)\n .option(\"--no-open\", \"don't open a browser on first run\")\n .option(\"--dev\", \"development: API only, the console runs on Vite (:5173)\")\n // `xaq typo` lands here as an extra argument: report it as the unknown command it is.\n .allowExcessArguments()\n .action(async (o: ServerFlags & { open: boolean; dev?: boolean }, cmd: CommandType) => {\n if (cmd.args.length) throw new Error(unknownCommand(cmd.args[0]!));\n const { runForeground } = await import(\"./server.ts\");\n await runForeground({ ...configInput(o), dev: !!o.dev, open: o.open, version });\n });\n\nconst service = program\n .command(\"service\")\n .description(\"run xaq in the background as a systemd / launchd service, started at boot\")\n .summary(\"run it in the background (systemd / launchd)\")\n .helpGroup(SERVER_GROUP);\n\nwithServerOptions(\n service.command(\"install\").description(\"install and start the service; re-run it to change the port or update (replaces an agentinbox service, keeping its data)\"),\n).action(async (o: ServerFlags) => {\n const { loadConfig } = await import(\"./config.ts\");\n const { installService, serviceInstallInput, systemOps } = await import(\"./service.ts\");\n // Replacing a service from before the rename keeps its data directory, port and host unless told otherwise.\n await installService(loadConfig(serviceInstallInput(configInput(o), systemOps()), version));\n});\n\nservice\n .command(\"uninstall\")\n .description(\"stop and remove the service (keeps your data)\")\n .option(\"-d, --data <dir>\", \"data directory, if the installed service doesn't say (default ~/.xaq, or an existing ~/.agentinbox; env XAQ_DATA)\")\n .action(async (o: ServerFlags) => {\n const { uninstallService } = await import(\"./service.ts\");\n const { resolveDataDir } = await import(\"./config.ts\");\n // No loadConfig: uninstalling must not create a data directory.\n uninstallService({ dataDir: resolveDataDir(o.data, { quiet: true }) });\n });\n\nservice\n .command(\"status\")\n .description(\"show service status\")\n .action(async () => {\n const { serviceStatus } = await import(\"./service.ts\");\n serviceStatus();\n });\n\nconst skill = program\n .command(\"skill\")\n .description(\"the xaq agent skill (SKILL.md): teaches an agent how to use xaq's tools\")\n .summary(\"install the agent skill (SKILL.md)\")\n .helpGroup(AGENT_GROUP);\n\nskill\n .command(\"print\")\n .description(\"print SKILL.md to stdout\")\n .option(\"--url <url>\", \"xaq server URL to fill in (env XAQ_URL)\")\n .action(async (o: { url?: string }) => {\n const { renderSkill } = await import(\"./skill/index.ts\");\n process.stdout.write(renderSkill(await serverUrl(o.url)));\n });\n\nskill\n .command(\"install\")\n .description(\"install SKILL.md for an agent (no running server needed)\")\n .option(\"-t, --target <target>\", \"claude, codex, agents, or a skills directory path\", \"claude\")\n .option(\"--url <url>\", \"xaq server URL agents connect to (env XAQ_URL)\")\n .action(async (o: { target: string; url?: string }) => {\n const { removeLegacySkill, renderSkill, SKILL_TARGETS, skillPath, writeSkill } = await import(\"./skill/index.ts\");\n checkSkillTarget(o.target, Object.keys(SKILL_TARGETS));\n const url = await serverUrl(o.url);\n const path = skillPath(o.target);\n writeSkill(path, renderSkill(url));\n console.log(`Installed the xaq skill to ${pc.cyan(path)} (server ${url}).`);\n const replaced = removeLegacySkill(o.target);\n if (replaced) console.log(`Removed the agentinbox skill it replaces (${replaced}).`);\n if (!o.url && !envVar(\"URL\")) {\n console.log(pc.dim(\"Agents on other machines need the server's address: re-run with --url https://your-server.\"));\n }\n });\n\nprogram\n .command(\"reset-password\")\n .description(\"forgot the console password? Removes it and prints a new setup link (or the first-run link)\")\n .summary(\"print a new setup link (forgot the password?)\")\n .helpGroup(SERVER_GROUP)\n .option(\"-d, --data <dir>\", \"data directory (default ~/.xaq, or an existing ~/.agentinbox; env XAQ_DATA)\")\n .option(\"-p, --port <port>\", \"port of the running instance, used for the printed link\", parsePort)\n .option(\"--host <host>\", \"host of the running instance, used for the printed link\")\n .action(\n async (o: ServerFlags) => {\n // Never create a data directory or master key here: a wrong --data (or a service running as\n // another user) must not silently produce a fresh instance.\n const { dataFiles, resolveDataDir } = await import(\"./config.ts\");\n const dataDir = resolveDataDir(o.data);\n const dbPath = dataFiles(dataDir).db;\n if (!existsSync(dbPath)) {\n throw new Error(\n `No xaq data in ${dataDir}. If xaq runs as a system service, run this with sudo; ` +\n \"otherwise pass the data directory it uses with --data.\",\n );\n }\n const { loadConfig } = await import(\"./config.ts\");\n const { Db } = await import(\"./db/index.ts\");\n const { createBus } = await import(\"./bus.ts\");\n const { SettingsStore } = await import(\"./settings.ts\");\n const { AuditLog } = await import(\"./audit.ts\");\n const { currentSetupToken, isClaimed, resetAdminPassword } = await import(\"./auth/index.ts\");\n const { runningInstance } = await import(\"./instance.ts\");\n const { consoleUrls } = await import(\"./urls.ts\");\n\n const running = runningInstance(dataDir);\n const config = loadConfig(\n { dataDir, port: portOption(o.port) ?? running?.port, host: o.host ?? (process.env.HOST || running?.host) },\n version,\n );\n const db = new Db(dbPath);\n try {\n const bus = createBus();\n const core = { config, db, settings: new SettingsStore(db, bus, config), audit: new AuditLog(db, bus) };\n const claimed = isClaimed(core);\n const token = claimed ? resetAdminPassword(core) : currentSetupToken(core)!;\n console.log(\n claimed\n ? \"\\n The admin password was removed and all console sessions were signed out.\\n Open this link to set a new password:\\n\"\n : \"\\n This instance hasn't been claimed yet, so there is no password to reset.\\n Open this link to claim it:\\n\",\n );\n const urls = consoleUrls(config.host, config.port);\n const publicUrl = core.settings.publicUrl();\n if (!urls.includes(publicUrl)) urls.push(publicUrl);\n for (const url of urls) console.log(` ${pc.cyan(`${url}/setup?token=${token}`)}`);\n console.log(pc.dim(`\\n ${running ? \"The running xaq picks this up immediately; no restart needed.\" : \"Start xaq to use it.\"}\\n`));\n } finally {\n db.close();\n }\n },\n);\n\n// Agent side: `connect` (device-flow pairing), `setup-mcp`, `mcp-header` and `listen …` (see\n// docs/listen.md). Imported after the Node check, like everything else.\nconst { registerListenCommands } = await import(\"./listen/commands.ts\");\nregisterListenCommands(program);\n// Daemon side, behind the brokerUpstreams setting: `connections …` and `import --from …` (server/connections-cli).\nconst { registerConnectionsCommands } = await import(\"./connections-cli/commands.ts\");\nregisterConnectionsCommands(program);\n// Short one-liners for the command list; each command's own --help keeps its full description.\nconst AGENT_SUMMARIES: Record<string, string> = {\n connect: \"pair an agent; you approve it in the console\",\n \"setup-mcp\": \"add xaq to the agent's MCP config\",\n \"mcp-header\": \"print the agent's auth header (MCP clients)\",\n listen: \"wake the agent when new mail arrives\",\n};\nfor (const cmd of program.commands) {\n const summary = AGENT_SUMMARIES[cmd.name()];\n if (summary) cmd.summary(summary).helpGroup(AGENT_GROUP);\n}\nprogram.commandsGroup(SERVER_GROUP).helpCommand(\"help [command]\", \"show help for a command\").helpCommand(true);\n\n/**\n * The URL written into SKILL.md: --url, XAQ_URL (AGENTINBOX_URL), else the xaq running on this\n * machine (its lock file), else http://localhost:8425.\n */\nasync function serverUrl(flag: string | undefined): Promise<string> {\n let raw = flag ?? envVar(\"URL\");\n if (!raw) {\n const { runningInstance } = await import(\"./instance.ts\");\n const { resolveDataDir } = await import(\"./config.ts\");\n const running = runningInstance(resolveDataDir(undefined, { quiet: true }));\n const host = running && ![\"0.0.0.0\", \"::\", \"\"].includes(running.host) ? running.host : \"localhost\";\n raw = `http://${host.includes(\":\") ? `[${host}]` : host}:${running?.port || process.env.PORT || 8425}`;\n }\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new Error(`Invalid --url \"${raw}\". Use a full URL such as https://mail.example.com.`);\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") throw new Error(\"--url must start with http:// or https://\");\n return (url.origin + url.pathname).replace(/\\/+$/, \"\");\n}\n\n/** A known agent name, or something that is clearly a path; typos like \"claud\" are refused. */\nfunction checkSkillTarget(target: string, known: string[]): void {\n if (known.includes(target) || target.includes(\"/\") || target.includes(sep) || target.startsWith(\".\") || isDir(target)) return;\n const near = closest(target, known);\n throw new Error(\n `Unknown skill target \"${target}\".${near ? ` Did you mean ${near}?` : \"\"} Use ${known.join(\", \")}, or a directory path such as ./skills.`,\n );\n}\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\nfunction unknownCommand(name: string): string {\n const near = closest(name, [...program.commands.map((c) => c.name()), \"help\"]);\n return `unknown command '${name}'${near ? `\\n(Did you mean ${near}?)` : \"\"}\\nRun xaq --help for the list of commands.`;\n}\n\n/** The candidate within edit distance 2 (or a prefix match), if any. */\nfunction closest(word: string, candidates: string[]): string | null {\n const distance = (a: string, b: string) => {\n const row = Array.from({ length: b.length + 1 }, (_, j) => j);\n for (let i = 1; i <= a.length; i++) {\n let prev = row[0]!;\n row[0] = i;\n for (let j = 1; j <= b.length; j++) {\n const cur = row[j]!;\n row[j] = Math.min(row[j]! + 1, row[j - 1]! + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));\n prev = cur;\n }\n }\n return row[b.length]!;\n };\n let best: { name: string; d: number } | null = null;\n for (const c of candidates) {\n const d = c.startsWith(word) && word.length >= 3 ? 0 : distance(word.toLowerCase(), c);\n if (d <= 2 && (!best || d < best.d)) best = { name: c, d };\n }\n return best?.name ?? null;\n}\n\ntry {\n await program.parseAsync(process.argv);\n} catch (err) {\n console.error(pc.red(err instanceof Error ? err.message : String(err)));\n if (envVar(\"DEBUG\") && err instanceof Error) console.error(err.stack);\n process.exit(1);\n}\n"],"mappings":";;;;;;AAQA,MAAM,EAAE,SAAS,yBAAyB,MAAM,OAAO;AACvD,MAAM,EAAE,SAAS,OAAO,MAAM,OAAO;AAErC,QAAQ,QAAQ;AAIhB,IAAI,SAAS,QAAQ,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAE,WAA6C,mBAAmB,CAAC,aAAa,GACtI,QAAQ,OAAO,MAAM,iGAAiG;AAKxH,MAAM,cAAc,QAAQ,YAAY,KAAK,OAAO;AACpD,QAAQ,gBAAgB,SAAyB,GAAG,SAAoB;CACtE,MAAM,UAAU,OAAO,YAAY,WAAW,UAAU,QAAQ;CAEhE,MADa,OAAO,KAAK,OAAO,WAAW,KAAK,KAAM,KAAK,EAAE,EAAoC,UACnF,yBAA0B,QAAkB,SAAS,0BAA0B,UAAU,KAAK,OAAO,GAAG;CACtH,OAAQ,YAA6C,SAAS,GAAG,IAAI;AACvE;;AAGA,SAAS,iBAAyB;CAChC,IAAI;EACF,OAAQ,KAAK,MAAM,aAAa,KAAK,YAAY,SAAS,MAAM,cAAc,GAAG,MAAM,CAAC,CAAC,CAAyB;CACpH,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAM,UAAU,eAAe;AAQ/B,MAAM,aAAa,UAAkB,QAAQ,KAAK,KAAK,KAAK,OAAO,KAAK,KAAK;AAE7E,SAAS,UAAU,OAAuB;CACxC,IAAI,CAAC,UAAU,KAAK,GAAG,MAAM,IAAI,qBAAqB,wCAAwC;CAC9F,OAAO,OAAO,KAAK;AACrB;;AAGA,SAAS,WAAW,MAA8C;CAChE,IAAI,SAAS,KAAA,GAAW,OAAO;CAC/B,MAAM,MAAM,QAAQ,IAAI,MAAM,KAAK;CACnC,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,IAAI,CAAC,UAAU,GAAG,GAAG,MAAM,IAAI,MAAM,SAAS,QAAQ,IAAI,KAAK,qDAAqD;CACpH,OAAO,OAAO,GAAG;AACnB;AAEA,MAAM,qBAAqB,QACzB,IACG,OAAO,qBAAqB,8CAA8C,SAAS,CAAC,CACpF,OAAO,iBAAiB,0CAA0C,CAAC,CACnE,OAAO,oBAAoB,6EAA6E;AAE7G,MAAM,eAAe,OAAoB;CAAE,MAAM,WAAW,EAAE,IAAI;CAAG,MAAM,EAAE;CAAM,SAAS,EAAE;AAAK;AAEnG,MAAM,UAAU,IAAI,QAAQ,KAAK,CAAC,CAC/B,YACC,kNAEF,CAAC,CACA,QAAQ,SAAS,iBAAiB,mBAAmB,CAAC,CACtD,WAAW,cAAc,WAAW,CAAC,CACrC,YACC,SACA;;;;;;;;;;;;kEAaF,CAAC,CACA,yBAAyB;AAE5B,MAAM,eAAe;AACrB,MAAM,cAAc;AAEpB,kBACE,QACG,QAAQ,SAAS,EAAE,WAAW,KAAK,CAAC,CAAC,CACrC,YAAY,yEAAyE,CAAC,CACtF,QAAQ,+CAA+C,CAAC,CACxD,UAAU,YAAY,CAC3B,CAAC,CACE,OAAO,aAAa,mCAAmC,CAAC,CACxD,OAAO,SAAS,yDAAyD,CAAC,CAE1E,qBAAqB,CAAC,CACtB,OAAO,OAAO,GAAmD,QAAqB;CACrF,IAAI,IAAI,KAAK,QAAQ,MAAM,IAAI,MAAM,eAAe,IAAI,KAAK,EAAG,CAAC;CACjE,MAAM,EAAE,kBAAkB,MAAM,OAAO;CACvC,MAAM,cAAc;EAAE,GAAG,YAAY,CAAC;EAAG,KAAK,CAAC,CAAC,EAAE;EAAK,MAAM,EAAE;EAAM;CAAQ,CAAC;AAChF,CAAC;AAEH,MAAM,UAAU,QACb,QAAQ,SAAS,CAAC,CAClB,YAAY,2EAA2E,CAAC,CACxF,QAAQ,8CAA8C,CAAC,CACvD,UAAU,YAAY;AAEzB,kBACE,QAAQ,QAAQ,SAAS,CAAC,CAAC,YAAY,0HAA0H,CACnK,CAAC,CAAC,OAAO,OAAO,MAAmB;CACjC,MAAM,EAAE,eAAe,MAAM,OAAO;CACpC,MAAM,EAAE,gBAAgB,qBAAqB,cAAc,MAAM,OAAO;CAExE,MAAM,eAAe,WAAW,oBAAoB,YAAY,CAAC,GAAG,UAAU,CAAC,GAAG,OAAO,CAAC;AAC5F,CAAC;AAED,QACG,QAAQ,WAAW,CAAC,CACpB,YAAY,+CAA+C,CAAC,CAC5D,OAAO,oBAAoB,mHAAmH,CAAC,CAC/I,OAAO,OAAO,MAAmB;CAChC,MAAM,EAAE,qBAAqB,MAAM,OAAO;CAC1C,MAAM,EAAE,mBAAmB,MAAM,OAAO;CAExC,iBAAiB,EAAE,SAAS,eAAe,EAAE,MAAM,EAAE,OAAO,KAAK,CAAC,EAAE,CAAC;AACvE,CAAC;AAEH,QACG,QAAQ,QAAQ,CAAC,CACjB,YAAY,qBAAqB,CAAC,CAClC,OAAO,YAAY;CAClB,MAAM,EAAE,kBAAkB,MAAM,OAAO;CACvC,cAAc;AAChB,CAAC;AAEH,MAAM,QAAQ,QACX,QAAQ,OAAO,CAAC,CAChB,YAAY,yEAAyE,CAAC,CACtF,QAAQ,oCAAoC,CAAC,CAC7C,UAAU,WAAW;AAExB,MACG,QAAQ,OAAO,CAAC,CAChB,YAAY,0BAA0B,CAAC,CACvC,OAAO,eAAe,yCAAyC,CAAC,CAChE,OAAO,OAAO,MAAwB;CACrC,MAAM,EAAE,gBAAgB,MAAM,OAAO;CACrC,QAAQ,OAAO,MAAM,YAAY,MAAM,UAAU,EAAE,GAAG,CAAC,CAAC;AAC1D,CAAC;AAEH,MACG,QAAQ,SAAS,CAAC,CAClB,YAAY,0DAA0D,CAAC,CACvE,OAAO,yBAAyB,qDAAqD,QAAQ,CAAC,CAC9F,OAAO,eAAe,gDAAgD,CAAC,CACvE,OAAO,OAAO,MAAwC;CACrD,MAAM,EAAE,mBAAmB,aAAa,eAAe,WAAW,eAAe,MAAM,OAAO;CAC9F,iBAAiB,EAAE,QAAQ,OAAO,KAAK,aAAa,CAAC;CACrD,MAAM,MAAM,MAAM,UAAU,EAAE,GAAG;CACjC,MAAM,OAAO,UAAU,EAAE,MAAM;CAC/B,WAAW,MAAM,YAAY,GAAG,CAAC;CACjC,QAAQ,IAAI,8BAA8B,GAAG,KAAK,IAAI,EAAE,WAAW,IAAI,GAAG;CAC1E,MAAM,WAAW,kBAAkB,EAAE,MAAM;CAC3C,IAAI,UAAU,QAAQ,IAAI,6CAA6C,SAAS,GAAG;CACnF,IAAI,CAAC,EAAE,OAAO,CAAC,OAAO,KAAK,GACzB,QAAQ,IAAI,GAAG,IAAI,4FAA4F,CAAC;AAEpH,CAAC;AAEH,QACG,QAAQ,gBAAgB,CAAC,CACzB,YAAY,6FAA6F,CAAC,CAC1G,QAAQ,+CAA+C,CAAC,CACxD,UAAU,YAAY,CAAC,CACvB,OAAO,oBAAoB,6EAA6E,CAAC,CACzG,OAAO,qBAAqB,2DAA2D,SAAS,CAAC,CACjG,OAAO,iBAAiB,yDAAyD,CAAC,CAClF,OACD,OAAO,MAAmB;CAGxB,MAAM,EAAE,WAAW,mBAAmB,MAAM,OAAO;CACnD,MAAM,UAAU,eAAe,EAAE,IAAI;CACrC,MAAM,SAAS,UAAU,OAAO,CAAC,CAAC;CAClC,IAAI,CAAC,WAAW,MAAM,GACpB,MAAM,IAAI,MACR,kBAAkB,QAAQ,8GAE5B;CAEF,MAAM,EAAE,eAAe,MAAM,OAAO;CACpC,MAAM,EAAE,OAAO,MAAM,OAAO;CAC5B,MAAM,EAAE,cAAc,MAAM,OAAO;CACnC,MAAM,EAAE,kBAAkB,MAAM,OAAO;CACvC,MAAM,EAAE,aAAa,MAAM,OAAO;CAClC,MAAM,EAAE,mBAAmB,WAAW,uBAAuB,MAAM,OAAO;CAC1E,MAAM,EAAE,oBAAoB,MAAM,OAAO;CACzC,MAAM,EAAE,gBAAgB,MAAM,OAAO;CAErC,MAAM,UAAU,gBAAgB,OAAO;CACvC,MAAM,SAAS,WACb;EAAE;EAAS,MAAM,WAAW,EAAE,IAAI,KAAK,SAAS;EAAM,MAAM,EAAE,SAAS,QAAQ,IAAI,QAAQ,SAAS;CAAM,GAC1G,OACF;CACA,MAAM,KAAK,IAAI,GAAG,MAAM;CACxB,IAAI;EACF,MAAM,MAAM,UAAU;EACtB,MAAM,OAAO;GAAE;GAAQ;GAAI,UAAU,IAAI,cAAc,IAAI,KAAK,MAAM;GAAG,OAAO,IAAI,SAAS,IAAI,GAAG;EAAE;EACtG,MAAM,UAAU,UAAU,IAAI;EAC9B,MAAM,QAAQ,UAAU,mBAAmB,IAAI,IAAI,kBAAkB,IAAI;EACzE,QAAQ,IACN,UACI,4HACA,+GACN;EACA,MAAM,OAAO,YAAY,OAAO,MAAM,OAAO,IAAI;EACjD,MAAM,YAAY,KAAK,SAAS,UAAU;EAC1C,IAAI,CAAC,KAAK,SAAS,SAAS,GAAG,KAAK,KAAK,SAAS;EAClD,KAAK,MAAM,OAAO,MAAM,QAAQ,IAAI,KAAK,GAAG,KAAK,GAAG,IAAI,eAAe,OAAO,GAAG;EACjF,QAAQ,IAAI,GAAG,IAAI,OAAO,UAAU,kEAAkE,uBAAuB,GAAG,CAAC;CACnI,UAAU;EACR,GAAG,MAAM;CACX;AACF,CACF;AAIA,MAAM,EAAE,2BAA2B,MAAM,OAAO;AAChD,uBAAuB,OAAO;AAE9B,MAAM,EAAE,gCAAgC,MAAM,OAAO;AACrD,4BAA4B,OAAO;AAEnC,MAAM,kBAA0C;CAC9C,SAAS;CACT,aAAa;CACb,cAAc;CACd,QAAQ;AACV;AACA,KAAK,MAAM,OAAO,QAAQ,UAAU;CAClC,MAAM,UAAU,gBAAgB,IAAI,KAAK;CACzC,IAAI,SAAS,IAAI,QAAQ,OAAO,CAAC,CAAC,UAAU,WAAW;AACzD;AACA,QAAQ,cAAc,YAAY,CAAC,CAAC,YAAY,kBAAkB,yBAAyB,CAAC,CAAC,YAAY,IAAI;;;;;AAM7G,eAAe,UAAU,MAA2C;CAClE,IAAI,MAAM,QAAQ,OAAO,KAAK;CAC9B,IAAI,CAAC,KAAK;EACR,MAAM,EAAE,oBAAoB,MAAM,OAAO;EACzC,MAAM,EAAE,mBAAmB,MAAM,OAAO;EACxC,MAAM,UAAU,gBAAgB,eAAe,KAAA,GAAW,EAAE,OAAO,KAAK,CAAC,CAAC;EAC1E,MAAM,OAAO,WAAW,CAAC;GAAC;GAAW;GAAM;EAAE,CAAC,CAAC,SAAS,QAAQ,IAAI,IAAI,QAAQ,OAAO;EACvF,MAAM,UAAU,KAAK,SAAS,GAAG,IAAI,IAAI,KAAK,KAAK,KAAK,GAAG,SAAS,QAAQ,QAAQ,IAAI,QAAQ;CAClG;CACA,IAAI;CACJ,IAAI;EACF,MAAM,IAAI,IAAI,GAAG;CACnB,QAAQ;EACN,MAAM,IAAI,MAAM,kBAAkB,IAAI,oDAAoD;CAC5F;CACA,IAAI,IAAI,aAAa,WAAW,IAAI,aAAa,UAAU,MAAM,IAAI,MAAM,2CAA2C;CACtH,QAAQ,IAAI,SAAS,IAAI,SAAA,CAAU,QAAQ,QAAQ,EAAE;AACvD;;AAGA,SAAS,iBAAiB,QAAgB,OAAuB;CAC/D,IAAI,MAAM,SAAS,MAAM,KAAK,OAAO,SAAS,GAAG,KAAK,OAAO,SAAS,GAAG,KAAK,OAAO,WAAW,GAAG,KAAK,MAAM,MAAM,GAAG;CACvH,MAAM,OAAO,QAAQ,QAAQ,KAAK;CAClC,MAAM,IAAI,MACR,yBAAyB,OAAO,IAAI,OAAO,iBAAiB,KAAK,KAAK,GAAG,OAAO,MAAM,KAAK,IAAI,EAAE,wCACnG;AACF;AAEA,SAAS,MAAM,MAAuB;CACpC,IAAI;EACF,OAAO,SAAS,IAAI,CAAC,CAAC,YAAY;CACpC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,eAAe,MAAsB;CAC5C,MAAM,OAAO,QAAQ,MAAM,CAAC,GAAG,QAAQ,SAAS,KAAK,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC;CAC7E,OAAO,oBAAoB,KAAK,GAAG,OAAO,mBAAmB,KAAK,MAAM,GAAG;AAC7E;;AAGA,SAAS,QAAQ,MAAc,YAAqC;CAClE,MAAM,YAAY,GAAW,MAAc;EACzC,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;EAC5D,KAAK,IAAI,IAAI,GAAG,KAAK,EAAE,QAAQ,KAAK;GAClC,IAAI,OAAO,IAAI;GACf,IAAI,KAAK;GACT,KAAK,IAAI,IAAI,GAAG,KAAK,EAAE,QAAQ,KAAK;IAClC,MAAM,MAAM,IAAI;IAChB,IAAI,KAAK,KAAK,IAAI,IAAI,KAAM,GAAG,IAAI,IAAI,KAAM,GAAG,QAAQ,EAAE,IAAI,OAAO,EAAE,IAAI,KAAK,IAAI,EAAE;IACtF,OAAO;GACT;EACF;EACA,OAAO,IAAI,EAAE;CACf;CACA,IAAI,OAA2C;CAC/C,KAAK,MAAM,KAAK,YAAY;EAC1B,MAAM,IAAI,EAAE,WAAW,IAAI,KAAK,KAAK,UAAU,IAAI,IAAI,SAAS,KAAK,YAAY,GAAG,CAAC;EACrF,IAAI,KAAK,MAAM,CAAC,QAAQ,IAAI,KAAK,IAAI,OAAO;GAAE,MAAM;GAAG;EAAE;CAC3D;CACA,OAAO,MAAM,QAAQ;AACvB;AAEA,IAAI;CACF,MAAM,QAAQ,WAAW,QAAQ,IAAI;AACvC,SAAS,KAAK;CACZ,QAAQ,MAAM,GAAG,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,CAAC;CACtE,IAAI,OAAO,OAAO,KAAK,eAAe,OAAO,QAAQ,MAAM,IAAI,KAAK;CACpE,QAAQ,KAAK,CAAC;AAChB"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as LEGACY_PRODUCT } from "./brand--ZB8jTn1.js";
|
|
3
|
+
import { s as EVENT_TYPES } from "./types-BhJDbSzE.js";
|
|
4
|
+
import { t as NPX } from "./version-BxH7OuWD.js";
|
|
5
|
+
import { t as DEFAULT_PROFILE } from "./paths-yzOKD6ow.js";
|
|
6
|
+
import { _ as recipesHelp, i as TOOLS, n as RECIPE_NAMES } from "./recipes-DG_-doGq.js";
|
|
7
|
+
import { InvalidArgumentError } from "commander";
|
|
8
|
+
//#region server/listen/commands.ts
|
|
9
|
+
/** `xaq connect`, `xaq setup-mcp` and `xaq listen …`, all lazily loaded. */
|
|
10
|
+
/**
|
|
11
|
+
* Options of a `listen` subcommand plus any its ancestors parsed: `listen` declares the same flags
|
|
12
|
+
* (--profile, --exec…), and commander lets the parent claim them wherever they appear.
|
|
13
|
+
*/
|
|
14
|
+
const all = (cmd) => cmd.optsWithGlobals();
|
|
15
|
+
const collect = (value, previous) => [...previous ?? [], value];
|
|
16
|
+
const int = (name) => (value) => {
|
|
17
|
+
if (!/^\d+$/.test(value)) throw new InvalidArgumentError(`${name} must be a whole number.`);
|
|
18
|
+
return Number(value);
|
|
19
|
+
};
|
|
20
|
+
const profileOption = (cmd) => cmd.option("--profile <name>", `profile to use (default "${DEFAULT_PROFILE}")`);
|
|
21
|
+
function listenFlags(cmd) {
|
|
22
|
+
return profileOption(cmd).option("--exec <command>", "run this command for each event (via /bin/sh; the event arrives on stdin as JSON, never in the command)").option("--recipe <name>", `use a built-in target: ${RECIPE_NAMES.join(", ")} (hermes forwards to your Hermes gateway; see it with --print-recipe)`).option("--forward <url>", "POST each event to this localhost URL, signed per Standard Webhooks").option("--types <list>", `comma-separated event types, or "all" (default: message.received, new mail only; also ${EVENT_TYPES.filter((t) => t !== "message.received").join(", ")}; calendar.* need calendar.read)`).option("--from <glob>", "only senders (calendar: organizers) matching this glob, e.g. \"*@stripe.com\" (repeatable)", collect).option("--to <glob>", "only when a to/cc address (calendar: an attendee) matches (repeatable)", collect).option("--label <id>", "only messages with this Gmail label id, e.g. INBOX or Label_123 (repeatable)", collect).option("--subject <regex>", "only subjects (calendar: event titles) matching this case-insensitive regular expression").option("--not-from <glob>", "skip senders matching this glob, e.g. \"*@github.com\" (repeatable; --not-from \"\" --save clears saved exclusions)", collect).option("--not-label <id>", "skip messages with this Gmail label id, e.g. CATEGORY_UPDATES (repeatable)", collect).option("--all-categories", "don't skip Gmail's Promotions, Social and Forums tabs (skipped by default)").option("--include-category <name>", "take one category back from the default skip: promotions, social or forums (repeatable)", collect).option("--default-categories", "back to the default: skip Promotions, Social and Forums (undoes a saved --all-categories / --include-category)").option("--batch <duration>", `collect events this long (e.g. 60s, 2m) and run --exec once for all of them (up to 20); 0 turns it off`).option("--include-body", "include the cleaned message body (calendar: the event description) in each event").option("--no-include-body", "don't include bodies (overrides a saved setting)").option("--concurrency <n>", "events handled at once (default 2)", int("--concurrency")).option("--timeout <seconds>", "per-event time limit; the whole process group is killed after it (default 300)", int("--timeout")).option("--max-attempts <n>", "attempts before an event goes to dead letters (default 5)", int("--max-attempts")).option("--consumer <name>", "server-side cursor name (default <hostname>-<profile>)").option("--cwd <dir>", "directory --exec commands run in (default: a private dir in the state dir; must be yours and not group/world-writable)").option("--insecure", "allow plain http to a non-loopback, non-Tailscale daemon (loud warning)").option("--save", "save these settings to the profile");
|
|
23
|
+
}
|
|
24
|
+
/** `listen hermes --help`. */
|
|
25
|
+
function hermesHelp() {
|
|
26
|
+
return `
|
|
27
|
+
--recipe hermes forwards each event to your running Hermes gateway (http://127.0.0.1:8644/webhooks/xaq; for
|
|
28
|
+
another profile the route is xaq-<profile>; a profile set up before the rename keeps its ${LEGACY_PRODUCT}[-<profile>] route,
|
|
29
|
+
toolset and server name). Hermes verifies the Standard Webhooks signature with the
|
|
30
|
+
listener's secret, runs the triage, and delivers the result where this route says. Mail that isn't important gets
|
|
31
|
+
a [SILENT] reply, which Hermes doesn't deliver.
|
|
32
|
+
|
|
33
|
+
A Hermes webhook run normally gets web tools (web_search, web_extract, vision_analyze, clarify) and every MCP
|
|
34
|
+
server enabled in Hermes: with email as the trigger, an attacker could make it read your mail and send it out.
|
|
35
|
+
--run therefore also limits the route to the xaq MCP server ("toolsets": ["mcp-xaq"] in
|
|
36
|
+
webhook_subscriptions.json, which \`hermes webhook subscribe\` can't set). That limit only holds when xaq is an
|
|
37
|
+
enabled MCP server in Hermes' config.yaml (else Hermes adds every MCP server), so this refuses until
|
|
38
|
+
\`${NPX} setup-mcp --tool hermes\` has added it. --run creates the route disabled with the real secret under Hermes'
|
|
39
|
+
lock (python3), runs \`hermes webhook subscribe\` without --secret (hermes keeps the secret and the disabled state),
|
|
40
|
+
then sets the toolsets and enables it. The secret is never in a command line, the route never runs unconfined, and
|
|
41
|
+
any failure removes it again. Re-run it after any \`hermes webhook subscribe\` for this route (that resets the
|
|
42
|
+
toolsets). The Hermes home follows HERMES_HOME or Hermes' sticky active_profile.
|
|
43
|
+
|
|
44
|
+
Without --run it prints both steps for you to do by hand; the toolsets edit is required.
|
|
45
|
+
|
|
46
|
+
--cron-job hands the email to that job's own tools, so it is accepted only when the job's enabled_toolsets is
|
|
47
|
+
exactly ["xaq"] (the bare server name; "mcp-xaq" doesn't narrow MCP servers for cron jobs).
|
|
48
|
+
|
|
49
|
+
Needs python3, the gateway's webhook platform (hermes gateway setup, or WEBHOOK_ENABLED=true in ~/.hermes/.env) and
|
|
50
|
+
a running gateway. Check it: curl http://127.0.0.1:8644/health
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
${NPX} listen hermes --deliver telegram --run
|
|
54
|
+
${NPX} listen hermes --cron-job email-watch --run # only for a job limited to ["xaq"]
|
|
55
|
+
${NPX} listen service install --recipe hermes`;
|
|
56
|
+
}
|
|
57
|
+
/** `setup-mcp --help`: exactly what it changes, per tool. */
|
|
58
|
+
function mcpSetupHelp() {
|
|
59
|
+
return `
|
|
60
|
+
What it changes (the server is named "xaq", or "xaq-<profile>" for --profile; a profile set up before the
|
|
61
|
+
rename keeps the "${LEGACY_PRODUCT}[-<profile>]" name it already has in your tools):
|
|
62
|
+
claude-code runs \`claude mcp remove xaq --scope user\` then
|
|
63
|
+
\`claude mcp add-json --scope user xaq '{"type":"http","url":"<daemon>/mcp","headersHelper":"…"}'\`.
|
|
64
|
+
The header helper (~/.config/xaq/bin/mcp-header.mjs) reads the key from your profile, so
|
|
65
|
+
the key is in neither the command line nor ~/.claude.json. Backs up ~/.claude.json first
|
|
66
|
+
($CLAUDE_CONFIG_DIR/.claude.json if set).
|
|
67
|
+
codex adds or replaces [mcp_servers.xaq] in ~/.codex/config.toml ($CODEX_HOME), with the key in
|
|
68
|
+
http_headers; the file becomes 0600.
|
|
69
|
+
cursor adds or replaces mcpServers.xaq in ~/.cursor/mcp.json, with the key in headers; 0600.
|
|
70
|
+
hermes adds or replaces mcp_servers.xaq in ~/.hermes/config.yaml ($HERMES_HOME) and puts the key in
|
|
71
|
+
~/.hermes/.env as XAQ_API_KEY; both 0600.
|
|
72
|
+
Every file is backed up first as <file>.xaq-backup-<timestamp> (0600; backups of files holding the key hold it
|
|
73
|
+
too). It also refreshes ~/.config/xaq/mcp/<profile>.claude.json (no key), which the listen recipe uses.
|
|
74
|
+
|
|
75
|
+
Check it afterwards: claude mcp list · codex mcp list · agent mcp list (Cursor) · hermes mcp test xaq
|
|
76
|
+
|
|
77
|
+
Example:
|
|
78
|
+
${NPX} setup-mcp --tool claude-code --dry-run`;
|
|
79
|
+
}
|
|
80
|
+
function registerListenCommands(program) {
|
|
81
|
+
program.command("connect").description("pair this machine's agent with a xaq daemon (approve the code in the console)").argument("<daemon-url>", "the xaq daemon, e.g. https://mail.example.com or http://100.101.102.103:8425").option("--name <name>", "how the agent appears in the console (default \"<Tool> on <hostname>\")").option("--tool <tool>", `the agent's tool: ${TOOLS.join(", ")}; tailors the next steps`).option("--account <email>", "mailbox to request access to (repeatable; the approver decides)", collect).option("--capabilities <list>", "capabilities to request, e.g. read,draft or read,calendar.read (the approver decides)").option("--reason <text>", "why the agent needs access (shown to the approver)").option("--setup-mcp", "also register the MCP server in the chosen tool (the key is never shown; `setup-mcp --help` lists exactly what changes)").option("--profile <name>", `profile to save the key in (default "${DEFAULT_PROFILE}")`).option("--consumer <name>", "listener cursor name (default <hostname>-<profile>)").option("--log <file>", "also write the output to this file (created 0600; for running connect in the background)").option("--insecure", "allow plain http to a non-loopback, non-Tailscale daemon (loud warning)").option("--force", "re-pair an existing profile (keeps its listener settings)").addHelpText("after", `
|
|
82
|
+
Exit codes: 0 approved, 2 denied, 3 expired, 4 already consumed, 130 cancelled, 1 error.
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
${NPX} connect https://mail.example.com --tool claude-code --setup-mcp`).action(async (url, flags) => {
|
|
86
|
+
const { runConnect } = await import("./connect-TtJ9n0Rw.js");
|
|
87
|
+
process.exit(await runConnect(url, flags, { handleSignals: true }));
|
|
88
|
+
});
|
|
89
|
+
profileOption(program.command("setup-mcp").description("register the xaq MCP server in your agent's tool, without showing the key")).option("--tool <tool>", `${TOOLS.filter((t) => t !== "other").join(", ")} (default: the tool given to connect)`).option("--dry-run", "print the exact commands and file changes, and change nothing").addHelpText("after", mcpSetupHelp()).action(async (flags) => {
|
|
90
|
+
const { parseTool, printMcpResult } = await import("./connect-TtJ9n0Rw.js");
|
|
91
|
+
const { loadProfile } = await import("./profile-CcMZ4hn7.js");
|
|
92
|
+
const { setupMcp, mcpTarget } = await import("./mcpsetup-BnHudwpq.js");
|
|
93
|
+
const { profilePath } = await import("./paths-d-C2kdjo.js");
|
|
94
|
+
const name = flags.profile ?? "default";
|
|
95
|
+
const profile = loadProfile(name);
|
|
96
|
+
const tool = flags.tool ? parseTool(flags.tool) : profile.tool ?? "other";
|
|
97
|
+
if (tool === "other") throw new Error(`Which tool? Pass --tool ${TOOLS.filter((t) => t !== "other").join("|")}.`);
|
|
98
|
+
const { claudeMcpConfigPath, writeClaudeMcpConfig } = await import("./mcpheader-B8c5YO1B.js");
|
|
99
|
+
const r = setupMcp(tool, mcpTarget(name, profile), profilePath(name), { dryRun: flags.dryRun });
|
|
100
|
+
if (flags.dryRun) r.changes = [...r.changes ?? [], `refresh ${claudeMcpConfigPath(name)} (keyless MCP config the Claude Code listen recipe loads)`];
|
|
101
|
+
else writeClaudeMcpConfig(name, `${profile.url}/mcp`);
|
|
102
|
+
printMcpResult(r);
|
|
103
|
+
if (!r.ok) process.exitCode = 1;
|
|
104
|
+
});
|
|
105
|
+
profileOption(program.command("mcp-header").description("print the agent's Authorization header as JSON for an MCP client's header helper (refuses to print to a terminal)")).action(async (flags) => {
|
|
106
|
+
const { printMcpHeader } = await import("./mcpheader-B8c5YO1B.js");
|
|
107
|
+
process.exitCode = printMcpHeader(flags.profile ?? "default");
|
|
108
|
+
});
|
|
109
|
+
const listen = listenFlags(program.command("listen").description("wake a local agent on new mail: hold an outbound stream to the daemon and run a command or POST to localhost per event")).option("--dry-run", "print matching events without running anything (uses a throwaway <consumer>-dry-run cursor)").option("--from-start", "a NEW consumer starts at the oldest stored event (no effect once it exists: use --backfill)").option("--backfill", "run the mail from before this listener started through --exec/--forward once, oldest first, then exit; safe while the listener runs (own cursor)").option("--until <seq>", "with --backfill: replay up to this event number instead of where the listener started", int("--until")).option("--print-recipe <name>", `print what --recipe <name> runs (hermes: its URL), for ${RECIPE_NAMES.join(", ")} (with this machine's paths), and exit`).addHelpText("after", recipesHelp()).action(async (flags) => {
|
|
110
|
+
if (flags.printRecipe !== void 0) {
|
|
111
|
+
const { parseRecipeTool, recipe } = await import("./recipes-C7TIDlRj.js");
|
|
112
|
+
const name = flags.printRecipe.trim().toLowerCase();
|
|
113
|
+
const r = recipe(name === "other" ? "other" : parseRecipeTool(name), flags.profile ?? "default");
|
|
114
|
+
process.stdout.write(`${r.exec ?? r.forward}\n`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const { runListener } = await import("./listen-CXVKG9XD.js");
|
|
118
|
+
process.exit(await runListener({
|
|
119
|
+
flags,
|
|
120
|
+
handleSignals: true
|
|
121
|
+
}));
|
|
122
|
+
});
|
|
123
|
+
profileOption(listen.command("status").description("show the listener's connection, cursor, recent events and dead letters")).action(async (_o, cmd) => {
|
|
124
|
+
const flags = all(cmd);
|
|
125
|
+
const { printStatus } = await import("./admin-Btrm8mCd.js");
|
|
126
|
+
await printStatus(flags.profile ?? "default");
|
|
127
|
+
});
|
|
128
|
+
profileOption(listen.command("secret").description("print the forward-mode signing secret (whsec_…) for your local receiver")).option("--rotate", "generate a new secret (restart the listener afterwards)").action(async (_o, cmd) => {
|
|
129
|
+
const flags = all(cmd);
|
|
130
|
+
const { printSecret } = await import("./admin-Btrm8mCd.js");
|
|
131
|
+
printSecret(flags.profile ?? "default", flags);
|
|
132
|
+
});
|
|
133
|
+
profileOption(listen.command("hermes").description("create the Hermes gateway webhook route that --recipe hermes forwards to, limited to the xaq tools")).option("--deliver <target>", "where Hermes sends its triage: telegram, discord, slack… (a platform connected to your gateway), or log").option("--deliver-chat-id <id>", "with --deliver: a specific chat instead of the platform's home channel").option("--cron-job <job>", "instead of --deliver: fire this existing Hermes cron job (id or name; `hermes cron list`); its prompt, tools and delivery apply, so its enabled_toolsets must be exactly [\"xaq\"]").option("--script <file>", "a Hermes filter script under ~/.hermes/scripts (runs before the agent; can drop events at no LLM cost)").option("--run", "set the route up now: run hermes, then set the real secret and limit the route to the xaq tools (recommended)").addHelpText("after", hermesHelp()).action(async (_o, cmd) => {
|
|
134
|
+
const flags = all(cmd);
|
|
135
|
+
const { hermesSubscribe } = await import("./hermes-CKyzt1mH.js");
|
|
136
|
+
process.exitCode = await hermesSubscribe(flags.profile ?? "default", flags);
|
|
137
|
+
});
|
|
138
|
+
listenFlags(listen.command("dead").description("list events that failed every attempt; --replay runs them again")).option("--replay", "run dead events again (successes are removed)").option("--id <eventId>", "only this event (with --replay or --clear)").option("--clear", "delete dead events without running them").option("--json", "print raw JSON lines").action(async (_o, cmd) => {
|
|
139
|
+
const flags = all(cmd);
|
|
140
|
+
const admin = await import("./admin-Btrm8mCd.js");
|
|
141
|
+
const profile = flags.profile ?? "default";
|
|
142
|
+
if (flags.clear) admin.clearDead(profile, flags.id);
|
|
143
|
+
else if (flags.replay) {
|
|
144
|
+
if (await admin.replayDead(flags) > 0) process.exitCode = 1;
|
|
145
|
+
} else admin.printDead(profile, flags);
|
|
146
|
+
});
|
|
147
|
+
const service = listen.command("service").description("run the listener in the background (systemd user service / launchd agent, one per profile)");
|
|
148
|
+
listenFlags(service.command("install").description("install and start the listener service; flags are saved to the profile. Re-run to update")).action(async (_o, cmd) => {
|
|
149
|
+
const flags = all(cmd);
|
|
150
|
+
const { installListenService } = await import("./service-BaWGPmSd.js");
|
|
151
|
+
await installListenService(flags);
|
|
152
|
+
});
|
|
153
|
+
profileOption(service.command("uninstall").description("stop and remove the listener service (keeps the profile)")).action(async (_o, cmd) => {
|
|
154
|
+
const flags = all(cmd);
|
|
155
|
+
const { uninstallListenService } = await import("./service-BaWGPmSd.js");
|
|
156
|
+
uninstallListenService(flags.profile ?? "default");
|
|
157
|
+
});
|
|
158
|
+
profileOption(service.command("status").description("show the service manager's status for the listener")).action(async (_o, cmd) => {
|
|
159
|
+
const flags = all(cmd);
|
|
160
|
+
const { listenServiceStatus } = await import("./service-BaWGPmSd.js");
|
|
161
|
+
listenServiceStatus(flags.profile ?? "default");
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
export { registerListenCommands };
|
|
166
|
+
|
|
167
|
+
//# sourceMappingURL=commands-BNVLjYXA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands-BNVLjYXA.js","names":[],"sources":["../server/listen/commands.ts"],"sourcesContent":["import { type Command, InvalidArgumentError } from \"commander\";\nimport { LEGACY_PRODUCT } from \"../brand.ts\";\nimport { EVENT_TYPES } from \"../../shared/types.ts\";\nimport type { ConnectFlags } from \"./connect.ts\";\nimport type { HermesFlags } from \"./hermes.ts\";\nimport type { ListenFlags } from \"./options.ts\";\nimport { DEFAULT_PROFILE } from \"./paths.ts\";\nimport { RECIPE_NAMES, recipesHelp, TOOLS } from \"./recipes.ts\";\nimport { NPX } from \"./version.ts\";\n\n/** `xaq connect`, `xaq setup-mcp` and `xaq listen …`, all lazily loaded. */\n\n/**\n * Options of a `listen` subcommand plus any its ancestors parsed: `listen` declares the same flags\n * (--profile, --exec…), and commander lets the parent claim them wherever they appear.\n */\nconst all = <T>(cmd: Command): T => cmd.optsWithGlobals() as T;\n\nconst collect = (value: string, previous: string[] | undefined) => [...(previous ?? []), value];\n\nconst int = (name: string) => (value: string) => {\n if (!/^\\d+$/.test(value)) throw new InvalidArgumentError(`${name} must be a whole number.`);\n return Number(value);\n};\n\nconst profileOption = (cmd: Command) => cmd.option(\"--profile <name>\", `profile to use (default \"${DEFAULT_PROFILE}\")`);\n\nfunction listenFlags(cmd: Command): Command {\n return profileOption(cmd)\n .option(\"--exec <command>\", \"run this command for each event (via /bin/sh; the event arrives on stdin as JSON, never in the command)\")\n .option(\"--recipe <name>\", `use a built-in target: ${RECIPE_NAMES.join(\", \")} (hermes forwards to your Hermes gateway; see it with --print-recipe)`)\n .option(\"--forward <url>\", \"POST each event to this localhost URL, signed per Standard Webhooks\")\n .option(\"--types <list>\", `comma-separated event types, or \"all\" (default: message.received, new mail only; also ${EVENT_TYPES.filter((t) => t !== \"message.received\").join(\", \")}; calendar.* need calendar.read)`)\n .option(\"--from <glob>\", 'only senders (calendar: organizers) matching this glob, e.g. \"*@stripe.com\" (repeatable)', collect)\n .option(\"--to <glob>\", \"only when a to/cc address (calendar: an attendee) matches (repeatable)\", collect)\n .option(\"--label <id>\", \"only messages with this Gmail label id, e.g. INBOX or Label_123 (repeatable)\", collect)\n .option(\"--subject <regex>\", \"only subjects (calendar: event titles) matching this case-insensitive regular expression\")\n .option(\"--not-from <glob>\", 'skip senders matching this glob, e.g. \"*@github.com\" (repeatable; --not-from \"\" --save clears saved exclusions)', collect)\n .option(\"--not-label <id>\", \"skip messages with this Gmail label id, e.g. CATEGORY_UPDATES (repeatable)\", collect)\n .option(\"--all-categories\", \"don't skip Gmail's Promotions, Social and Forums tabs (skipped by default)\")\n .option(\"--include-category <name>\", \"take one category back from the default skip: promotions, social or forums (repeatable)\", collect)\n .option(\"--default-categories\", \"back to the default: skip Promotions, Social and Forums (undoes a saved --all-categories / --include-category)\")\n .option(\"--batch <duration>\", `collect events this long (e.g. 60s, 2m) and run --exec once for all of them (up to 20); 0 turns it off`)\n .option(\"--include-body\", \"include the cleaned message body (calendar: the event description) in each event\")\n .option(\"--no-include-body\", \"don't include bodies (overrides a saved setting)\")\n .option(\"--concurrency <n>\", \"events handled at once (default 2)\", int(\"--concurrency\"))\n .option(\"--timeout <seconds>\", \"per-event time limit; the whole process group is killed after it (default 300)\", int(\"--timeout\"))\n .option(\"--max-attempts <n>\", \"attempts before an event goes to dead letters (default 5)\", int(\"--max-attempts\"))\n .option(\"--consumer <name>\", \"server-side cursor name (default <hostname>-<profile>)\")\n .option(\"--cwd <dir>\", \"directory --exec commands run in (default: a private dir in the state dir; must be yours and not group/world-writable)\")\n .option(\"--insecure\", \"allow plain http to a non-loopback, non-Tailscale daemon (loud warning)\")\n .option(\"--save\", \"save these settings to the profile\");\n}\n\n/** `listen hermes --help`. */\nfunction hermesHelp(): string {\n return `\n--recipe hermes forwards each event to your running Hermes gateway (http://127.0.0.1:8644/webhooks/xaq; for\nanother profile the route is xaq-<profile>; a profile set up before the rename keeps its ${LEGACY_PRODUCT}[-<profile>] route,\ntoolset and server name). Hermes verifies the Standard Webhooks signature with the\nlistener's secret, runs the triage, and delivers the result where this route says. Mail that isn't important gets\na [SILENT] reply, which Hermes doesn't deliver.\n\nA Hermes webhook run normally gets web tools (web_search, web_extract, vision_analyze, clarify) and every MCP\nserver enabled in Hermes: with email as the trigger, an attacker could make it read your mail and send it out.\n--run therefore also limits the route to the xaq MCP server (\"toolsets\": [\"mcp-xaq\"] in\nwebhook_subscriptions.json, which \\`hermes webhook subscribe\\` can't set). That limit only holds when xaq is an\nenabled MCP server in Hermes' config.yaml (else Hermes adds every MCP server), so this refuses until\n\\`${NPX} setup-mcp --tool hermes\\` has added it. --run creates the route disabled with the real secret under Hermes'\nlock (python3), runs \\`hermes webhook subscribe\\` without --secret (hermes keeps the secret and the disabled state),\nthen sets the toolsets and enables it. The secret is never in a command line, the route never runs unconfined, and\nany failure removes it again. Re-run it after any \\`hermes webhook subscribe\\` for this route (that resets the\ntoolsets). The Hermes home follows HERMES_HOME or Hermes' sticky active_profile.\n\nWithout --run it prints both steps for you to do by hand; the toolsets edit is required.\n\n--cron-job hands the email to that job's own tools, so it is accepted only when the job's enabled_toolsets is\nexactly [\"xaq\"] (the bare server name; \"mcp-xaq\" doesn't narrow MCP servers for cron jobs).\n\nNeeds python3, the gateway's webhook platform (hermes gateway setup, or WEBHOOK_ENABLED=true in ~/.hermes/.env) and\na running gateway. Check it: curl http://127.0.0.1:8644/health\n\nExamples:\n ${NPX} listen hermes --deliver telegram --run\n ${NPX} listen hermes --cron-job email-watch --run # only for a job limited to [\"xaq\"]\n ${NPX} listen service install --recipe hermes`;\n}\n\n/** `setup-mcp --help`: exactly what it changes, per tool. */\nfunction mcpSetupHelp(): string {\n return `\nWhat it changes (the server is named \"xaq\", or \"xaq-<profile>\" for --profile; a profile set up before the\nrename keeps the \"${LEGACY_PRODUCT}[-<profile>]\" name it already has in your tools):\n claude-code runs \\`claude mcp remove xaq --scope user\\` then\n \\`claude mcp add-json --scope user xaq '{\"type\":\"http\",\"url\":\"<daemon>/mcp\",\"headersHelper\":\"…\"}'\\`.\n The header helper (~/.config/xaq/bin/mcp-header.mjs) reads the key from your profile, so\n the key is in neither the command line nor ~/.claude.json. Backs up ~/.claude.json first\n ($CLAUDE_CONFIG_DIR/.claude.json if set).\n codex adds or replaces [mcp_servers.xaq] in ~/.codex/config.toml ($CODEX_HOME), with the key in\n http_headers; the file becomes 0600.\n cursor adds or replaces mcpServers.xaq in ~/.cursor/mcp.json, with the key in headers; 0600.\n hermes adds or replaces mcp_servers.xaq in ~/.hermes/config.yaml ($HERMES_HOME) and puts the key in\n ~/.hermes/.env as XAQ_API_KEY; both 0600.\nEvery file is backed up first as <file>.xaq-backup-<timestamp> (0600; backups of files holding the key hold it\ntoo). It also refreshes ~/.config/xaq/mcp/<profile>.claude.json (no key), which the listen recipe uses.\n\nCheck it afterwards: claude mcp list · codex mcp list · agent mcp list (Cursor) · hermes mcp test xaq\n\nExample:\n ${NPX} setup-mcp --tool claude-code --dry-run`;\n}\n\nexport function registerListenCommands(program: Command): void {\n program\n .command(\"connect\")\n .description(\"pair this machine's agent with a xaq daemon (approve the code in the console)\")\n .argument(\"<daemon-url>\", \"the xaq daemon, e.g. https://mail.example.com or http://100.101.102.103:8425\")\n .option(\"--name <name>\", 'how the agent appears in the console (default \"<Tool> on <hostname>\")')\n .option(\"--tool <tool>\", `the agent's tool: ${TOOLS.join(\", \")}; tailors the next steps`)\n .option(\"--account <email>\", \"mailbox to request access to (repeatable; the approver decides)\", collect)\n .option(\"--capabilities <list>\", \"capabilities to request, e.g. read,draft or read,calendar.read (the approver decides)\")\n .option(\"--reason <text>\", \"why the agent needs access (shown to the approver)\")\n .option(\"--setup-mcp\", \"also register the MCP server in the chosen tool (the key is never shown; `setup-mcp --help` lists exactly what changes)\")\n .option(\"--profile <name>\", `profile to save the key in (default \"${DEFAULT_PROFILE}\")`)\n .option(\"--consumer <name>\", \"listener cursor name (default <hostname>-<profile>)\")\n .option(\"--log <file>\", \"also write the output to this file (created 0600; for running connect in the background)\")\n .option(\"--insecure\", \"allow plain http to a non-loopback, non-Tailscale daemon (loud warning)\")\n .option(\"--force\", \"re-pair an existing profile (keeps its listener settings)\")\n .addHelpText(\n \"after\",\n `\nExit codes: 0 approved, 2 denied, 3 expired, 4 already consumed, 130 cancelled, 1 error.\n\nExample:\n ${NPX} connect https://mail.example.com --tool claude-code --setup-mcp`,\n )\n .action(async (url: string, flags: ConnectFlags) => {\n const { runConnect } = await import(\"./connect.ts\");\n process.exit(await runConnect(url, flags, { handleSignals: true }));\n });\n\n profileOption(program.command(\"setup-mcp\").description(\"register the xaq MCP server in your agent's tool, without showing the key\"))\n .option(\"--tool <tool>\", `${TOOLS.filter((t) => t !== \"other\").join(\", \")} (default: the tool given to connect)`)\n .option(\"--dry-run\", \"print the exact commands and file changes, and change nothing\")\n .addHelpText(\"after\", mcpSetupHelp())\n .action(async (flags: { tool?: string; profile?: string; dryRun?: boolean }) => {\n const { parseTool, printMcpResult } = await import(\"./connect.ts\");\n const { loadProfile } = await import(\"./profile.ts\");\n const { setupMcp, mcpTarget } = await import(\"./mcpsetup.ts\");\n const { profilePath } = await import(\"./paths.ts\");\n const name = flags.profile ?? DEFAULT_PROFILE;\n const profile = loadProfile(name);\n const tool = flags.tool ? parseTool(flags.tool) : (profile.tool ?? \"other\");\n if (tool === \"other\") throw new Error(`Which tool? Pass --tool ${TOOLS.filter((t) => t !== \"other\").join(\"|\")}.`);\n const { claudeMcpConfigPath, writeClaudeMcpConfig } = await import(\"./mcpheader.ts\");\n const r = setupMcp(tool, mcpTarget(name, profile), profilePath(name), { dryRun: flags.dryRun });\n if (flags.dryRun) r.changes = [...(r.changes ?? []), `refresh ${claudeMcpConfigPath(name)} (keyless MCP config the Claude Code listen recipe loads)`];\n else writeClaudeMcpConfig(name, `${profile.url}/mcp`);\n printMcpResult(r);\n if (!r.ok) process.exitCode = 1;\n });\n\n profileOption(\n program.command(\"mcp-header\").description(\"print the agent's Authorization header as JSON for an MCP client's header helper (refuses to print to a terminal)\"),\n ).action(async (flags: { profile?: string }) => {\n const { printMcpHeader } = await import(\"./mcpheader.ts\");\n process.exitCode = printMcpHeader(flags.profile ?? DEFAULT_PROFILE);\n });\n\n const listen = listenFlags(\n program\n .command(\"listen\")\n .description(\"wake a local agent on new mail: hold an outbound stream to the daemon and run a command or POST to localhost per event\"),\n )\n .option(\"--dry-run\", \"print matching events without running anything (uses a throwaway <consumer>-dry-run cursor)\")\n .option(\"--from-start\", \"a NEW consumer starts at the oldest stored event (no effect once it exists: use --backfill)\")\n .option(\"--backfill\", \"run the mail from before this listener started through --exec/--forward once, oldest first, then exit; safe while the listener runs (own cursor)\")\n .option(\"--until <seq>\", \"with --backfill: replay up to this event number instead of where the listener started\", int(\"--until\"))\n .option(\"--print-recipe <name>\", `print what --recipe <name> runs (hermes: its URL), for ${RECIPE_NAMES.join(\", \")} (with this machine's paths), and exit`)\n .addHelpText(\"after\", recipesHelp())\n .action(async (flags: ListenFlags & { printRecipe?: string }) => {\n if (flags.printRecipe !== undefined) {\n const { parseRecipeTool, recipe } = await import(\"./recipes.ts\");\n const name = flags.printRecipe.trim().toLowerCase();\n const r = recipe(name === \"other\" ? \"other\" : parseRecipeTool(name), flags.profile ?? DEFAULT_PROFILE);\n // Exec recipes print the bare command (to edit and pass back with --exec); hermes its forward URL.\n process.stdout.write(`${r.exec ?? r.forward}\\n`);\n return;\n }\n const { runListener } = await import(\"./listen.ts\");\n process.exit(await runListener({ flags, handleSignals: true }));\n });\n\n profileOption(listen.command(\"status\").description(\"show the listener's connection, cursor, recent events and dead letters\")).action(async (_o: unknown, cmd: Command) => {\n const flags = all<{ profile?: string }>(cmd);\n const { printStatus } = await import(\"./admin.ts\");\n await printStatus(flags.profile ?? DEFAULT_PROFILE);\n });\n\n profileOption(listen.command(\"secret\").description(\"print the forward-mode signing secret (whsec_…) for your local receiver\"))\n .option(\"--rotate\", \"generate a new secret (restart the listener afterwards)\")\n .action(async (_o: unknown, cmd: Command) => {\n const flags = all<{ profile?: string; rotate?: boolean }>(cmd);\n const { printSecret } = await import(\"./admin.ts\");\n printSecret(flags.profile ?? DEFAULT_PROFILE, flags);\n });\n\n profileOption(\n listen\n .command(\"hermes\")\n .description(\"create the Hermes gateway webhook route that --recipe hermes forwards to, limited to the xaq tools\"),\n )\n .option(\"--deliver <target>\", \"where Hermes sends its triage: telegram, discord, slack… (a platform connected to your gateway), or log\")\n .option(\"--deliver-chat-id <id>\", \"with --deliver: a specific chat instead of the platform's home channel\")\n .option(\"--cron-job <job>\", \"instead of --deliver: fire this existing Hermes cron job (id or name; `hermes cron list`); its prompt, tools and delivery apply, so its enabled_toolsets must be exactly [\\\"xaq\\\"]\")\n .option(\"--script <file>\", \"a Hermes filter script under ~/.hermes/scripts (runs before the agent; can drop events at no LLM cost)\")\n .option(\"--run\", \"set the route up now: run hermes, then set the real secret and limit the route to the xaq tools (recommended)\")\n .addHelpText(\"after\", hermesHelp())\n .action(async (_o: unknown, cmd: Command) => {\n const flags = all<HermesFlags>(cmd);\n const { hermesSubscribe } = await import(\"./hermes.ts\");\n process.exitCode = await hermesSubscribe(flags.profile ?? DEFAULT_PROFILE, flags);\n });\n\n listenFlags(listen.command(\"dead\").description(\"list events that failed every attempt; --replay runs them again\"))\n .option(\"--replay\", \"run dead events again (successes are removed)\")\n .option(\"--id <eventId>\", \"only this event (with --replay or --clear)\")\n .option(\"--clear\", \"delete dead events without running them\")\n .option(\"--json\", \"print raw JSON lines\")\n .action(async (_o: unknown, cmd: Command) => {\n const flags = all<ListenFlags & { replay?: boolean; id?: string; clear?: boolean; json?: boolean }>(cmd);\n const admin = await import(\"./admin.ts\");\n const profile = flags.profile ?? DEFAULT_PROFILE;\n if (flags.clear) admin.clearDead(profile, flags.id);\n else if (flags.replay) {\n if ((await admin.replayDead(flags)) > 0) process.exitCode = 1;\n } else admin.printDead(profile, flags);\n });\n\n const service = listen.command(\"service\").description(\"run the listener in the background (systemd user service / launchd agent, one per profile)\");\n listenFlags(service.command(\"install\").description(\"install and start the listener service; flags are saved to the profile. Re-run to update\")).action(\n async (_o: unknown, cmd: Command) => {\n const flags = all<ListenFlags>(cmd);\n const { installListenService } = await import(\"./service.ts\");\n await installListenService(flags);\n },\n );\n profileOption(service.command(\"uninstall\").description(\"stop and remove the listener service (keeps the profile)\")).action(async (_o: unknown, cmd: Command) => {\n const flags = all<{ profile?: string }>(cmd);\n const { uninstallListenService } = await import(\"./service.ts\");\n uninstallListenService(flags.profile ?? DEFAULT_PROFILE);\n });\n profileOption(service.command(\"status\").description(\"show the service manager's status for the listener\")).action(async (_o: unknown, cmd: Command) => {\n const flags = all<{ profile?: string }>(cmd);\n const { listenServiceStatus } = await import(\"./service.ts\");\n listenServiceStatus(flags.profile ?? DEFAULT_PROFILE);\n });\n}\n"],"mappings":"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"}
|