ftown 0.19.52 → 0.19.53
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/claude-mod/hooks/register.js +6 -5
- package/dist/bridge-auth.d.ts +1 -0
- package/dist/bridge-auth.js +2 -2
- package/dist/bridge-auth.js.map +1 -1
- package/dist/centrifugo-client.d.ts +11 -1
- package/dist/centrifugo-client.js +71 -69
- package/dist/centrifugo-client.js.map +1 -1
- package/dist/cli-client.d.ts +6 -0
- package/dist/cli-client.js +43 -18
- package/dist/cli-client.js.map +1 -1
- package/dist/command-rpc.d.ts +5 -2
- package/dist/command-rpc.js +41 -4
- package/dist/command-rpc.js.map +1 -1
- package/dist/direct-transport/loopback-server.d.ts +5 -1
- package/dist/direct-transport/loopback-server.js +19 -6
- package/dist/direct-transport/loopback-server.js.map +1 -1
- package/dist/direct-transport/peer-manager.d.ts +4 -0
- package/dist/direct-transport/peer-manager.js +16 -2
- package/dist/direct-transport/peer-manager.js.map +1 -1
- package/dist/ftown-cli.js +4 -1
- package/dist/ftown-cli.js.map +1 -1
- package/dist/index.js +54 -37
- package/dist/index.js.map +1 -1
- package/dist/local-api-server.d.ts +12 -0
- package/dist/local-api-server.js +182 -41
- package/dist/local-api-server.js.map +1 -1
- package/dist/local-pair-cli.d.ts +2 -3
- package/dist/local-pair-cli.js +12 -53
- package/dist/local-pair-cli.js.map +1 -1
- package/dist/mcp/fleet.d.ts +11 -9
- package/dist/mcp/fleet.js +61 -44
- package/dist/mcp/fleet.js.map +1 -1
- package/dist/mcp/http.d.ts +10 -9
- package/dist/mcp/http.js +133 -8
- package/dist/mcp/http.js.map +1 -1
- package/dist/mcp/legacy-relay.d.ts +2 -2
- package/dist/mcp/legacy-relay.js +4 -43
- package/dist/mcp/legacy-relay.js.map +1 -1
- package/dist/mcp/mcp-key-store.d.ts +52 -0
- package/dist/mcp/mcp-key-store.js +206 -0
- package/dist/mcp/mcp-key-store.js.map +1 -0
- package/dist/mcp/oauth.d.ts +5 -1
- package/dist/mcp/oauth.js +113 -39
- package/dist/mcp/oauth.js.map +1 -1
- package/dist/mcp/relay-handler.d.ts +2 -1
- package/dist/mcp/relay-handler.js +5 -40
- package/dist/mcp/relay-handler.js.map +1 -1
- package/dist/mcp/relay.d.ts +4 -4
- package/dist/mcp/relay.js +35 -85
- package/dist/mcp/relay.js.map +1 -1
- package/dist/mcp/secure-client.d.ts +23 -0
- package/dist/mcp/secure-client.js +124 -0
- package/dist/mcp/secure-client.js.map +1 -0
- package/dist/mcp/security-events.d.ts +65 -0
- package/dist/mcp/security-events.js +210 -0
- package/dist/mcp/security-events.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +2 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/test-support.d.ts +32 -0
- package/dist/mcp/test-support.js +120 -0
- package/dist/mcp/test-support.js.map +1 -0
- package/dist/mcp-cli.js +56 -36
- package/dist/mcp-cli.js.map +1 -1
- package/dist/pairing-client.d.ts +1 -0
- package/dist/pairing-client.js +2 -2
- package/dist/pairing-client.js.map +1 -1
- package/dist/push-notifier.js +3 -1
- package/dist/push-notifier.js.map +1 -1
- package/dist/security/bridge/audit.d.ts +37 -0
- package/dist/security/bridge/audit.js +185 -0
- package/dist/security/bridge/audit.js.map +1 -0
- package/dist/security/bridge/direct.d.ts +22 -0
- package/dist/security/bridge/direct.js +72 -0
- package/dist/security/bridge/direct.js.map +1 -0
- package/dist/security/bridge/errors.d.ts +7 -0
- package/dist/security/bridge/errors.js +12 -0
- package/dist/security/bridge/errors.js.map +1 -0
- package/dist/security/bridge/execute.d.ts +3 -0
- package/dist/security/bridge/execute.js +37 -0
- package/dist/security/bridge/execute.js.map +1 -0
- package/dist/security/bridge/identity.d.ts +5 -0
- package/dist/security/bridge/identity.js +42 -0
- package/dist/security/bridge/identity.js.map +1 -0
- package/dist/security/bridge/local-client.d.ts +4 -0
- package/dist/security/bridge/local-client.js +43 -0
- package/dist/security/bridge/local-client.js.map +1 -0
- package/dist/security/bridge/metadata.d.ts +99 -0
- package/dist/security/bridge/metadata.js +23 -0
- package/dist/security/bridge/metadata.js.map +1 -0
- package/dist/security/bridge/native-wake.d.ts +16 -0
- package/dist/security/bridge/native-wake.js +15 -0
- package/dist/security/bridge/native-wake.js.map +1 -0
- package/dist/security/bridge/permissions.d.ts +11 -0
- package/dist/security/bridge/permissions.js +97 -0
- package/dist/security/bridge/permissions.js.map +1 -0
- package/dist/security/bridge/policy.d.ts +35 -0
- package/dist/security/bridge/policy.js +263 -0
- package/dist/security/bridge/policy.js.map +1 -0
- package/dist/security/bridge/security.d.ts +50 -0
- package/dist/security/bridge/security.js +298 -0
- package/dist/security/bridge/security.js.map +1 -0
- package/dist/security/bridge/snapshot.d.ts +14 -0
- package/dist/security/bridge/snapshot.js +77 -0
- package/dist/security/bridge/snapshot.js.map +1 -0
- package/dist/security/crypto.d.ts +17 -0
- package/dist/security/crypto.js +280 -0
- package/dist/security/crypto.js.map +1 -0
- package/dist/security/protocol.d.ts +389 -0
- package/dist/security/protocol.js +9 -0
- package/dist/security/protocol.js.map +1 -0
- package/dist/security-cli.d.ts +3 -0
- package/dist/security-cli.js +62 -0
- package/dist/security-cli.js.map +1 -0
- package/dist/solo/solo-server.js +24 -2
- package/dist/solo/solo-server.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/wire-types.d.ts +3 -0
- package/dist/wire-types.js.map +1 -1
- package/hooks/notify.sh +1 -1
- package/muse-plugin/lib/ftown-hook.sh +6 -22
- package/opencode-plugin/ftown.js +33 -5
- package/package.json +4 -2
- package/pi-extension/ftown.js +33 -5
|
@@ -40,7 +40,7 @@ add_candidate() {
|
|
|
40
40
|
ENV_PORT="${FTOWN_HOOK_PORT:-}"
|
|
41
41
|
ENV_TOKEN="${FTOWN_HOOK_TOKEN:-}"
|
|
42
42
|
add_candidate "$ENV_PORT" "$ENV_TOKEN"
|
|
43
|
-
BRIDGE_JSON="${HOME:-}/.ftown/bridge.json"
|
|
43
|
+
BRIDGE_JSON="${FTOWN_BRIDGE_POINTER:-${HOME:-}/.ftown/bridge.json}"
|
|
44
44
|
if [ -n "${HOME:-}" ] && [ -f "$BRIDGE_JSON" ]; then
|
|
45
45
|
BPORT=$(jq -r '.port // empty' "$BRIDGE_JSON" 2>/dev/null)
|
|
46
46
|
BTOKEN=$(jq -r '.token // empty' "$BRIDGE_JSON" 2>/dev/null)
|
|
@@ -89,27 +89,11 @@ post_hook "$PAYLOAD"
|
|
|
89
89
|
if [ "$HOOK_EVENT" = "Stop" ]; then
|
|
90
90
|
SID_ENC=$(jq -n -r --arg s "$FTOWN_SESSION_ID" '$s | @uri' 2>/dev/null)
|
|
91
91
|
[ -n "$SID_ENC" ] || exit 0
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
_code=$(curl -s -o "$_tmp" -w '%{http_code}' \
|
|
98
|
-
"http://127.0.0.1:${_port}/api/sessions/${SID_ENC}/inbox?wait=0" \
|
|
99
|
-
-H "Authorization: Bearer ${_token}" \
|
|
100
|
-
--connect-timeout 1 --max-time 2 2>/dev/null)
|
|
101
|
-
else
|
|
102
|
-
_code=$(curl -s -o "$_tmp" -w '%{http_code}' \
|
|
103
|
-
"http://127.0.0.1:${_port}/api/sessions/${SID_ENC}/inbox?wait=0" \
|
|
104
|
-
--connect-timeout 1 --max-time 2 2>/dev/null)
|
|
105
|
-
fi
|
|
106
|
-
case "${_code:-}" in
|
|
107
|
-
2*) INBOX=$(cat "$_tmp" 2>/dev/null); break ;;
|
|
108
|
-
esac
|
|
109
|
-
done <<CANDIDATES_EOF
|
|
110
|
-
$CANDIDATES
|
|
111
|
-
CANDIDATES_EOF
|
|
112
|
-
rm -f "$_tmp"
|
|
92
|
+
FTOWN_SECURE_CLI=$(command -v ftown 2>/dev/null || true)
|
|
93
|
+
[ -n "$FTOWN_SECURE_CLI" ] || FTOWN_SECURE_CLI="${HOME:-}/.ftown/bin/ftown"
|
|
94
|
+
[ -x "$FTOWN_SECURE_CLI" ] || exit 0
|
|
95
|
+
REQUEST=$(jq -n -c --arg path "/api/sessions/${SID_ENC}/inbox?wait=0" '{method:"GET",path:$path,timeoutMs:2000}') || exit 0
|
|
96
|
+
INBOX=$(printf '%s' "$REQUEST" | "$FTOWN_SECURE_CLI" security proxy 2>/dev/null | jq -c 'select(.status >= 200 and .status < 300) | .body' 2>/dev/null)
|
|
113
97
|
[ -n "$INBOX" ] || exit 0
|
|
114
98
|
COUNT=$(printf '%s' "$INBOX" | jq -r '(.messages // []) | length' 2>/dev/null)
|
|
115
99
|
if [ -n "$COUNT" ] && [ "$COUNT" -gt 0 ] 2>/dev/null; then
|
package/opencode-plugin/ftown.js
CHANGED
|
@@ -1,21 +1,45 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
2
3
|
import { homedir } from 'node:os';
|
|
3
4
|
import { join } from 'node:path';
|
|
4
5
|
|
|
5
6
|
async function defaultReadBridgePointer() {
|
|
6
7
|
try {
|
|
7
|
-
const raw = await readFile(join(homedir(), '.ftown', 'bridge.json'), 'utf8');
|
|
8
|
+
const raw = await readFile(process.env.FTOWN_BRIDGE_POINTER || (process.env.FTOWN_DATA_DIR ? join(process.env.FTOWN_DATA_DIR, 'bridge.json') : join(homedir(), '.ftown', 'bridge.json')), 'utf8');
|
|
8
9
|
const parsed = JSON.parse(raw);
|
|
9
10
|
if (!parsed || typeof parsed.port !== 'number') return null;
|
|
10
|
-
return
|
|
11
|
-
port: parsed.port,
|
|
12
|
-
token: typeof parsed.token === 'string' ? parsed.token : undefined,
|
|
13
|
-
};
|
|
11
|
+
return parsed;
|
|
14
12
|
} catch {
|
|
15
13
|
return null;
|
|
16
14
|
}
|
|
17
15
|
}
|
|
18
16
|
|
|
17
|
+
|
|
18
|
+
async function secureProxy(env, pointer, path, init = {}) {
|
|
19
|
+
const cli = pointer?.harnessCli || join(homedir(), '.ftown', 'cli', 'dist', 'ftown-cli.js');
|
|
20
|
+
const args = [cli, 'security'];
|
|
21
|
+
const directory = env.FTOWN_DATA_DIR || pointer?.dataDir;
|
|
22
|
+
if (directory) args.push('--data-dir', directory);
|
|
23
|
+
args.push('proxy');
|
|
24
|
+
const request = { method: init.method || 'GET', path, timeoutMs: 60000 };
|
|
25
|
+
if (init.body !== undefined) request.body = typeof init.body === 'string' ? JSON.parse(init.body) : init.body;
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const child = spawn(process.execPath, args, { stdio: ['pipe', 'pipe', 'ignore'], signal: init.signal, env });
|
|
28
|
+
const chunks = []; let bytes = 0;
|
|
29
|
+
const timeout = setTimeout(() => child.kill(), 65000); timeout.unref?.();
|
|
30
|
+
child.on('error', () => { clearTimeout(timeout); reject(new Error('Encrypted bridge request failed')); });
|
|
31
|
+
child.stdout.on('data', data => { bytes += data.length; if (bytes > 1048576) child.kill(); else chunks.push(data); });
|
|
32
|
+
child.on('close', code => {
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
if (code !== 0 || bytes > 1048576) return reject(new Error('Encrypted bridge request failed'));
|
|
35
|
+
try { const response = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
36
|
+
resolve(new Response(JSON.stringify(response.body), { status: response.status })); }
|
|
37
|
+
catch { reject(new Error('Encrypted bridge response failed')); }
|
|
38
|
+
});
|
|
39
|
+
child.stdin.on('error', () => {}); child.stdin.end(JSON.stringify(request));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
19
43
|
function endpoints(env, pointer) {
|
|
20
44
|
const candidates = [];
|
|
21
45
|
const envPort = Number.parseInt(env.FTOWN_HOOK_PORT ?? '', 10);
|
|
@@ -82,6 +106,10 @@ export function registerFtownOpencodePlugin(client, options = {}) {
|
|
|
82
106
|
|
|
83
107
|
async function request(path, init = {}) {
|
|
84
108
|
const pointer = await readBridgePointer();
|
|
109
|
+
if (path.startsWith('/api/')) {
|
|
110
|
+
try { return await (options.proxy ?? ((p, i) => secureProxy(env, pointer, p, i)))(path, init); }
|
|
111
|
+
catch { return null; } // Delivery may be unknown: never retry a mutation through another endpoint.
|
|
112
|
+
}
|
|
85
113
|
if (closed) return null;
|
|
86
114
|
let lastResponse = null;
|
|
87
115
|
for (const endpoint of endpoints(env, pointer)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ftown",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.53",
|
|
4
4
|
"description": "CLI bridge for ftown — generic PTY-over-Centrifugo relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
"directory": "bridge"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
+
"@hpke/common": "1.10.1",
|
|
53
|
+
"@hpke/core": "1.9.0",
|
|
52
54
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
53
55
|
"@xterm/addon-serialize": "^0.14.0",
|
|
54
56
|
"@xterm/headless": "^6.0.0",
|
|
@@ -74,5 +76,5 @@
|
|
|
74
76
|
"tsx": "^4.19.0",
|
|
75
77
|
"typescript": "^5.7.0"
|
|
76
78
|
},
|
|
77
|
-
"gitHead": "
|
|
79
|
+
"gitHead": "8425849de980e684323f072e71957b630693301f"
|
|
78
80
|
}
|
package/pi-extension/ftown.js
CHANGED
|
@@ -1,21 +1,45 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
2
3
|
import { homedir } from 'node:os';
|
|
3
4
|
import { join } from 'node:path';
|
|
4
5
|
|
|
5
6
|
async function defaultReadBridgePointer() {
|
|
6
7
|
try {
|
|
7
|
-
const raw = await readFile(join(homedir(), '.ftown', 'bridge.json'), 'utf8');
|
|
8
|
+
const raw = await readFile(process.env.FTOWN_BRIDGE_POINTER || (process.env.FTOWN_DATA_DIR ? join(process.env.FTOWN_DATA_DIR, 'bridge.json') : join(homedir(), '.ftown', 'bridge.json')), 'utf8');
|
|
8
9
|
const parsed = JSON.parse(raw);
|
|
9
10
|
if (!parsed || typeof parsed.port !== 'number') return null;
|
|
10
|
-
return
|
|
11
|
-
port: parsed.port,
|
|
12
|
-
token: typeof parsed.token === 'string' ? parsed.token : undefined,
|
|
13
|
-
};
|
|
11
|
+
return parsed;
|
|
14
12
|
} catch {
|
|
15
13
|
return null;
|
|
16
14
|
}
|
|
17
15
|
}
|
|
18
16
|
|
|
17
|
+
|
|
18
|
+
async function secureProxy(env, pointer, path, init = {}) {
|
|
19
|
+
const cli = pointer?.harnessCli || join(homedir(), '.ftown', 'cli', 'dist', 'ftown-cli.js');
|
|
20
|
+
const args = [cli, 'security'];
|
|
21
|
+
const directory = env.FTOWN_DATA_DIR || pointer?.dataDir;
|
|
22
|
+
if (directory) args.push('--data-dir', directory);
|
|
23
|
+
args.push('proxy');
|
|
24
|
+
const request = { method: init.method || 'GET', path, timeoutMs: 60000 };
|
|
25
|
+
if (init.body !== undefined) request.body = typeof init.body === 'string' ? JSON.parse(init.body) : init.body;
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const child = spawn(process.execPath, args, { stdio: ['pipe', 'pipe', 'ignore'], signal: init.signal, env });
|
|
28
|
+
const chunks = []; let bytes = 0;
|
|
29
|
+
const timeout = setTimeout(() => child.kill(), 65000); timeout.unref?.();
|
|
30
|
+
child.on('error', () => { clearTimeout(timeout); reject(new Error('Encrypted bridge request failed')); });
|
|
31
|
+
child.stdout.on('data', data => { bytes += data.length; if (bytes > 1048576) child.kill(); else chunks.push(data); });
|
|
32
|
+
child.on('close', code => {
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
if (code !== 0 || bytes > 1048576) return reject(new Error('Encrypted bridge request failed'));
|
|
35
|
+
try { const response = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
36
|
+
resolve(new Response(JSON.stringify(response.body), { status: response.status })); }
|
|
37
|
+
catch { reject(new Error('Encrypted bridge response failed')); }
|
|
38
|
+
});
|
|
39
|
+
child.stdin.on('error', () => {}); child.stdin.end(JSON.stringify(request));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
19
43
|
function endpoints(env, pointer) {
|
|
20
44
|
const candidates = [];
|
|
21
45
|
const envPort = Number.parseInt(env.FTOWN_HOOK_PORT ?? '', 10);
|
|
@@ -156,6 +180,10 @@ export function registerFtownPiExtension(pi, options = {}) {
|
|
|
156
180
|
|
|
157
181
|
async function request(path, init = {}) {
|
|
158
182
|
const pointer = await readBridgePointer();
|
|
183
|
+
if (path.startsWith('/api/')) {
|
|
184
|
+
try { return await (options.proxy ?? ((p, i) => secureProxy(env, pointer, p, i)))(path, init); }
|
|
185
|
+
catch { return null; } // Delivery may be unknown: never retry a mutation through another endpoint.
|
|
186
|
+
}
|
|
159
187
|
let lastResponse = null;
|
|
160
188
|
for (const endpoint of endpoints(env, pointer)) {
|
|
161
189
|
try {
|