@agentprojectcontext/apx 1.77.1 → 1.77.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.77.1",
3
+ "version": "1.77.3",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -20,7 +20,10 @@
20
20
  import { randomUUID } from "node:crypto";
21
21
  import os from "node:os";
22
22
 
23
- const PAIRING_TTL_MS = 90_000;
23
+ // 90s was not enough for the real sequence: pick up the phone, unlock it,
24
+ // open the camera, scan, wait for the browser. Five minutes is still short
25
+ // enough that a nonce left on screen is not a standing invitation.
26
+ const PAIRING_TTL_MS = 5 * 60_000;
24
27
 
25
28
  // Reachable base URLs a device on the LAN can hit. When the daemon binds to
26
29
  // the wildcard (0.0.0.0/::) we enumerate non-internal IPv4s; when it binds to
@@ -8,9 +8,7 @@
8
8
  // the local network. Loopback stays the default, sharing is always explicit,
9
9
  // and the daemon's auth is untouched — the URL carries the token because
10
10
  // /admin/web-token is loopback-only by design, so a phone cannot fetch it.
11
- import fs from "node:fs";
12
11
  import { readConfig, writeConfig, effectivePort, effectiveHost } from "#core/config/index.js";
13
- import { TOKEN_PATH } from "#core/config/paths.js";
14
12
  import { detectLanAddresses, validateBindHost, isLoopback, isWildcard } from "#core/net/lan.js";
15
13
 
16
14
  export const PANEL_USAGE = {
@@ -19,21 +17,6 @@ export const PANEL_USAGE = {
19
17
  unshare: "apx panel unshare",
20
18
  };
21
19
 
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
20
  export async function cmdPanelStatus() {
38
21
  const cfg = readConfig();
39
22
  const host = effectiveHost(cfg);
@@ -92,18 +75,27 @@ export async function cmdPanelShare(args) {
92
75
  cfg.host = host;
93
76
  writeConfig(cfg);
94
77
 
95
- const token = readToken();
96
78
  const iface = candidates.find((c) => c.address === host)?.iface;
97
79
 
98
80
  console.log(`panel shared on ${host}:${port}${iface ? ` (${iface})` : ""}`);
99
81
  console.log("");
100
- console.log(" Open this on your phone — the token is in the URL:");
101
- console.log(` ${panelUrl(host, port, token)}`);
82
+ console.log(` URL for the other device: http://${host}:${port}/`);
83
+ console.log("");
84
+ // Deliberately NOT the daemon's master token. Handing that to a phone gives
85
+ // it the same power as the CLI, cannot be revoked without rotating the token
86
+ // every local tool depends on, and leaves no record the device exists —
87
+ // which is the exact thing pairing was built to avoid (see token-store.js).
88
+ // It would not even last: the master token is regenerated on every restart.
89
+ console.log(" Authorise the device by PAIRING it, not by copying a token:");
90
+ console.log(" the panel → Settings → Devices → Pair device, then scan the QR.");
91
+ console.log("");
92
+ console.log(" Pairing gives that device its OWN token, which survives restarts,");
93
+ console.log(" shows up under Paired devices, and can be revoked on its own without");
94
+ console.log(" disturbing anything else.");
102
95
  console.log("");
103
96
  // Say plainly what changed. A LAN can be a café or a coworking space.
104
97
  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.");
98
+ console.log(" They cannot use it without pairing. Run `apx panel unshare` when done.");
107
99
  if (candidates.length > 1) {
108
100
  const others = candidates.filter((c) => c.address !== host).map((c) => `${c.address} (${c.iface})`);
109
101
  console.log(` Other addresses on this machine: ${others.join(", ")}`);