@agentprojectcontext/apx 1.76.0 → 1.77.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/core/net/lan.js +114 -0
- package/src/core/profiles/bundled/secretary/PROFILE.md +6 -5
- package/src/core/profiles/bundled/secretary/profile.json +19 -6
- package/src/core/stores/agent-inbox.js +153 -0
- package/src/core/stores/conversations.js +21 -1
- package/src/host/daemon/api/inbox.js +45 -0
- package/src/host/daemon/api/web.js +3 -2
- package/src/host/daemon/api.js +2 -0
- package/src/host/daemon/db.js +6 -0
- package/src/host/daemon/index.js +31 -2
- package/src/interfaces/cli/commands/panel.js +131 -0
- package/src/interfaces/cli/index.js +28 -0
- package/src/interfaces/web/dist/assets/{index-Bjlk9ttU.js → index-BWWpTTSd.js} +135 -135
- package/src/interfaces/web/dist/assets/index-BWWpTTSd.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-D_EJEA1n.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/src/App.tsx +2 -0
- package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +20 -1
- package/src/interfaces/web/src/components/settings/ProfilePanel.tsx +105 -72
- package/src/interfaces/web/src/hooks/useInbox.ts +12 -0
- package/src/interfaces/web/src/i18n/en.ts +16 -0
- package/src/interfaces/web/src/i18n/es.ts +16 -0
- package/src/interfaces/web/src/lib/api/inbox.ts +26 -0
- package/src/interfaces/web/src/screens/InboxScreen.tsx +103 -0
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +1 -1
- package/src/core/profiles/bundled/secretary/PROFILE.es.md +0 -44
- package/src/interfaces/web/dist/assets/index-Bjlk9ttU.js.map +0 -1
- package/src/interfaces/web/dist/assets/index-CQ5kyFej.css +0 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// apx panel — reach the admin panel from another device on the same network.
|
|
2
|
+
//
|
|
3
|
+
// apx panel status
|
|
4
|
+
// apx panel share [--host 192.168.1.40]
|
|
5
|
+
// apx panel unshare
|
|
6
|
+
//
|
|
7
|
+
// This binds a second, specific LAN address. It is NOT a tunnel: nothing leaves
|
|
8
|
+
// the local network. Loopback stays the default, sharing is always explicit,
|
|
9
|
+
// and the daemon's auth is untouched — the URL carries the token because
|
|
10
|
+
// /admin/web-token is loopback-only by design, so a phone cannot fetch it.
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import { readConfig, writeConfig, effectivePort, effectiveHost } from "#core/config/index.js";
|
|
13
|
+
import { TOKEN_PATH } from "#core/config/paths.js";
|
|
14
|
+
import { detectLanAddresses, validateBindHost, isLoopback, isWildcard } from "#core/net/lan.js";
|
|
15
|
+
|
|
16
|
+
export const PANEL_USAGE = {
|
|
17
|
+
status: "apx panel status",
|
|
18
|
+
share: "apx panel share [--host <ip>]",
|
|
19
|
+
unshare: "apx panel unshare",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function readToken() {
|
|
23
|
+
try {
|
|
24
|
+
return fs.readFileSync(TOKEN_PATH, "utf8").trim();
|
|
25
|
+
} catch {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function panelUrl(host, port, token) {
|
|
31
|
+
// /admin/web-token refuses anything that is not loopback, so the panel on a
|
|
32
|
+
// phone can only authenticate from the fragment. The fragment never reaches
|
|
33
|
+
// the server or a proxy log — that is why it is a fragment and not a query.
|
|
34
|
+
return `http://${host}:${port}/` + (token ? `#token=${token}` : "");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function cmdPanelStatus() {
|
|
38
|
+
const cfg = readConfig();
|
|
39
|
+
const host = effectiveHost(cfg);
|
|
40
|
+
const port = effectivePort(cfg);
|
|
41
|
+
|
|
42
|
+
if (isLoopback(host)) {
|
|
43
|
+
console.log(`panel: local only — http://${host}:${port}`);
|
|
44
|
+
console.log(" Nothing on your network can reach it.");
|
|
45
|
+
console.log(" To reach it from your phone: apx panel share");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 0.0.0.0 is a bigger promise than "reachable on my network": it also covers
|
|
50
|
+
// every interface that appears LATER — a VPN, a hotspot, a bridged container.
|
|
51
|
+
// Worth calling out separately rather than reporting it as ordinary sharing.
|
|
52
|
+
if (isWildcard(host)) {
|
|
53
|
+
console.log(`panel: bound to EVERY interface — ${host}:${port}`);
|
|
54
|
+
const addrs = detectLanAddresses();
|
|
55
|
+
if (addrs.length) {
|
|
56
|
+
console.log(` Currently reachable at: ${addrs.map((a) => `http://${a.address}:${port}`).join(", ")}`);
|
|
57
|
+
}
|
|
58
|
+
console.log(" This also covers interfaces that appear later — a VPN, a hotspot, a");
|
|
59
|
+
console.log(" container bridge. Auth still applies, but the surface is wider than");
|
|
60
|
+
console.log(" it needs to be.");
|
|
61
|
+
console.log("");
|
|
62
|
+
console.log(" Narrow it to one address: apx panel share");
|
|
63
|
+
console.log(" Turn it off entirely: apx panel unshare");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`panel: SHARED on your network — http://${host}:${port}`);
|
|
68
|
+
console.log(` Anyone on this network who has the URL and the token can open it.`);
|
|
69
|
+
console.log(` To stop: apx panel unshare`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function cmdPanelShare(args) {
|
|
73
|
+
const cfg = readConfig();
|
|
74
|
+
const port = effectivePort(cfg);
|
|
75
|
+
|
|
76
|
+
const requested = args?.flags?.host;
|
|
77
|
+
const candidates = detectLanAddresses();
|
|
78
|
+
|
|
79
|
+
if (!requested && candidates.length === 0) {
|
|
80
|
+
console.error("apx panel share: no network address found on this machine.");
|
|
81
|
+
console.error(" Are you connected to a network? Pass one explicitly with --host <ip>.");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const host = requested || candidates[0].address;
|
|
86
|
+
const check = validateBindHost(host);
|
|
87
|
+
if (!check.ok) {
|
|
88
|
+
console.error(`apx panel share: ${check.reason}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
cfg.host = host;
|
|
93
|
+
writeConfig(cfg);
|
|
94
|
+
|
|
95
|
+
const token = readToken();
|
|
96
|
+
const iface = candidates.find((c) => c.address === host)?.iface;
|
|
97
|
+
|
|
98
|
+
console.log(`panel shared on ${host}:${port}${iface ? ` (${iface})` : ""}`);
|
|
99
|
+
console.log("");
|
|
100
|
+
console.log(" Open this on your phone — the token is in the URL:");
|
|
101
|
+
console.log(` ${panelUrl(host, port, token)}`);
|
|
102
|
+
console.log("");
|
|
103
|
+
// Say plainly what changed. A LAN can be a café or a coworking space.
|
|
104
|
+
console.log(" What this means: anyone on this network can now REACH the panel.");
|
|
105
|
+
console.log(" They still need the token above, which is not guessable — treat that");
|
|
106
|
+
console.log(" URL like a password, and run `apx panel unshare` when you are done.");
|
|
107
|
+
if (candidates.length > 1) {
|
|
108
|
+
const others = candidates.filter((c) => c.address !== host).map((c) => `${c.address} (${c.iface})`);
|
|
109
|
+
console.log(` Other addresses on this machine: ${others.join(", ")}`);
|
|
110
|
+
}
|
|
111
|
+
console.log("");
|
|
112
|
+
console.log(" Restart the daemon for the new binding to take effect: apx restart");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function cmdPanelUnshare() {
|
|
116
|
+
const cfg = readConfig();
|
|
117
|
+
const port = effectivePort(cfg);
|
|
118
|
+
const was = effectiveHost(cfg);
|
|
119
|
+
|
|
120
|
+
if (isLoopback(was)) {
|
|
121
|
+
console.log("panel is already local only — nothing to do");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
cfg.host = "127.0.0.1";
|
|
126
|
+
writeConfig(cfg);
|
|
127
|
+
|
|
128
|
+
console.log(`panel is local only again — http://127.0.0.1:${port}`);
|
|
129
|
+
console.log(` Was ${was}. Nothing on your network can reach it now.`);
|
|
130
|
+
console.log(" Restart the daemon to apply: apx restart");
|
|
131
|
+
}
|
|
@@ -161,6 +161,11 @@ import {
|
|
|
161
161
|
cmdProfileDoctor,
|
|
162
162
|
cmdProfileUninstall,
|
|
163
163
|
} from "./commands/profile.js";
|
|
164
|
+
import {
|
|
165
|
+
cmdPanelStatus,
|
|
166
|
+
cmdPanelShare,
|
|
167
|
+
cmdPanelUnshare,
|
|
168
|
+
} from "./commands/panel.js";
|
|
164
169
|
import {
|
|
165
170
|
cmdOrgShow,
|
|
166
171
|
cmdOrgAreaAdd,
|
|
@@ -1617,6 +1622,19 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
1617
1622
|
examples: ["apx plugins status telegram"],
|
|
1618
1623
|
}),
|
|
1619
1624
|
|
|
1625
|
+
panel: topic({
|
|
1626
|
+
title: "apx panel",
|
|
1627
|
+
summary:
|
|
1628
|
+
"Reach the admin panel from another device on the same network. Not a tunnel — nothing leaves your LAN, and loopback stays the default.",
|
|
1629
|
+
usage: ["apx panel <status|share|unshare>"],
|
|
1630
|
+
commands: [
|
|
1631
|
+
["status", "Whether the panel is local only or shared, and on what address."],
|
|
1632
|
+
["share", "Bind a LAN address and print the URL to open on your phone."],
|
|
1633
|
+
["unshare", "Go back to local only."],
|
|
1634
|
+
],
|
|
1635
|
+
options: [["--host <ip>", "share: bind this address instead of the detected one."]],
|
|
1636
|
+
examples: ["apx panel status", "apx panel share", "apx panel unshare"],
|
|
1637
|
+
}),
|
|
1620
1638
|
profile: topic({
|
|
1621
1639
|
title: "apx profile",
|
|
1622
1640
|
summary:
|
|
@@ -2805,6 +2823,16 @@ async function dispatch(cmd, rest) {
|
|
|
2805
2823
|
break;
|
|
2806
2824
|
}
|
|
2807
2825
|
|
|
2826
|
+
case "panel": {
|
|
2827
|
+
const sub = rest[0];
|
|
2828
|
+
const a = parseArgs(rest.slice(1));
|
|
2829
|
+
if (!sub || sub === "status") await cmdPanelStatus(a);
|
|
2830
|
+
else if (sub === "share") await cmdPanelShare(a);
|
|
2831
|
+
else if (sub === "unshare") await cmdPanelUnshare(a);
|
|
2832
|
+
else die(`unknown panel subcommand: ${sub}\nUsage: apx panel <status|share|unshare>`);
|
|
2833
|
+
break;
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2808
2836
|
case "profile":
|
|
2809
2837
|
case "profiles": {
|
|
2810
2838
|
const sub = rest[0];
|