newmark-agent 0.4.5 → 0.4.6
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/dist/cli-commands.d.ts +1 -1
- package/dist/cli-commands.js +89 -7
- package/dist/cli-discovery.js +8 -0
- package/dist/conversation-utility-host.bundle.cjs +3 -0
- package/dist/core/config.js +3 -0
- package/dist/core/dshCompatibility.d.ts +23 -6
- package/dist/core/dshCompatibility.js +99 -1
- package/dist/core/installUpdate.d.ts +67 -0
- package/dist/core/installUpdate.js +265 -0
- package/dist/core/mobilePairing.d.ts +46 -0
- package/dist/core/mobilePairing.js +207 -0
- package/dist/main.js +49 -0
- package/dist/preload.js +5 -0
- package/dist/server.js +195 -3
- package/dist/tui/src/adapters/core-runtime-adapter.js +17 -1
- package/dist/tui/src/app.js +41 -0
- package/dist/tui/src/data.js +1 -0
- package/dist/tui/src/render.js +11 -0
- package/dist/tui/src/settings-schema.js +3 -1
- package/dist/tui/src/state.js +21 -1
- package/dist/ui/index.html +455 -13
- package/dist/ui/lucide-sprite.svg +10 -0
- package/dist/wsl-agent-host.bundle.cjs +3 -0
- package/package.json +12 -8
package/dist/server.js
CHANGED
|
@@ -37,6 +37,7 @@ exports.runServer = runServer;
|
|
|
37
37
|
const http = __importStar(require("http"));
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
40
41
|
const uiPreferences_1 = require("./core/uiPreferences");
|
|
41
42
|
const child_process_1 = require("child_process");
|
|
42
43
|
const agent_1 = require("./core/agent");
|
|
@@ -45,10 +46,76 @@ const config_1 = require("./core/config");
|
|
|
45
46
|
const flow_1 = require("./core/flow");
|
|
46
47
|
const workspaceFileRouter_1 = require("./core/workspaceFileRouter");
|
|
47
48
|
const nativeBash_1 = require("./core/nativeBash");
|
|
49
|
+
const installUpdate_1 = require("./core/installUpdate");
|
|
50
|
+
const mobilePairing_1 = require("./core/mobilePairing");
|
|
48
51
|
const PORT = 47890;
|
|
49
52
|
let agent = null;
|
|
50
53
|
let automation = null;
|
|
51
54
|
let workspaceFileRouter = null;
|
|
55
|
+
let mobileToken = '';
|
|
56
|
+
let appRoot = '';
|
|
57
|
+
function mobileAuthorized(req) {
|
|
58
|
+
if (!mobileToken)
|
|
59
|
+
return false;
|
|
60
|
+
const bearer = String(req.headers.authorization || '');
|
|
61
|
+
if (bearer.startsWith('Bearer '))
|
|
62
|
+
return bearer.slice('Bearer '.length).trim() === mobileToken;
|
|
63
|
+
try {
|
|
64
|
+
const url = new URL(req.url || '/', `http://localhost:${PORT}`);
|
|
65
|
+
const queryToken = url.searchParams.get('token') || '';
|
|
66
|
+
return queryToken === mobileToken;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function mobileJson(res, data, code = 200) {
|
|
73
|
+
const body = JSON.stringify(data);
|
|
74
|
+
res.writeHead(code, {
|
|
75
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
76
|
+
'Access-Control-Allow-Origin': '*',
|
|
77
|
+
});
|
|
78
|
+
res.end(body);
|
|
79
|
+
}
|
|
80
|
+
function handleMobileEvents(req, res) {
|
|
81
|
+
if (agent && !agent.config.getBool('remote', 'touch_enabled')) {
|
|
82
|
+
mobileJson(res, { error: 'Remote touch disabled' }, 403);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!mobileAuthorized(req)) {
|
|
86
|
+
mobileJson(res, { error: 'Unauthorized' }, 401);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!agent) {
|
|
90
|
+
mobileJson(res, { error: 'Agent not initialized' }, 500);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
res.writeHead(200, {
|
|
94
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
95
|
+
'Cache-Control': 'no-cache',
|
|
96
|
+
'Connection': 'keep-alive',
|
|
97
|
+
'Access-Control-Allow-Origin': '*',
|
|
98
|
+
});
|
|
99
|
+
res.write('retry: 3000\n\n');
|
|
100
|
+
const unsubscribe = agent.subscribeWorkEvents(event => {
|
|
101
|
+
try {
|
|
102
|
+
res.write(`event: work\ndata: ${JSON.stringify(event)}\n\n`);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// socket is gone; the close handler will clean up
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
const heartbeat = setInterval(() => {
|
|
109
|
+
try {
|
|
110
|
+
res.write(': ping\n\n');
|
|
111
|
+
}
|
|
112
|
+
catch { }
|
|
113
|
+
}, 15000);
|
|
114
|
+
req.on('close', () => {
|
|
115
|
+
clearInterval(heartbeat);
|
|
116
|
+
unsubscribe();
|
|
117
|
+
});
|
|
118
|
+
}
|
|
52
119
|
function resolveAppPath(root, targetPath) {
|
|
53
120
|
if (!targetPath)
|
|
54
121
|
return root;
|
|
@@ -214,6 +281,16 @@ function jsonResponse(res, data, code = 200) {
|
|
|
214
281
|
async function handleApi(req, res, body) {
|
|
215
282
|
const url = new URL(req.url || '/', `http://localhost:${PORT}`);
|
|
216
283
|
const pathname = url.pathname;
|
|
284
|
+
if (pathname.startsWith('/api/mobile/')) {
|
|
285
|
+
if (agent && !agent.config.getBool('remote', 'touch_enabled')) {
|
|
286
|
+
mobileJson(res, { error: 'Remote touch disabled' }, 403);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (!mobileAuthorized(req)) {
|
|
290
|
+
mobileJson(res, { error: 'Unauthorized' }, 401);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
217
294
|
if (!agent) {
|
|
218
295
|
jsonResponse(res, { error: 'Agent not initialized' }, 500);
|
|
219
296
|
return;
|
|
@@ -603,15 +680,114 @@ async function handleApi(req, res, body) {
|
|
|
603
680
|
jsonResponse(res, { content: agent.readArchive(aName2) });
|
|
604
681
|
return;
|
|
605
682
|
}
|
|
683
|
+
case '/api/mobile/pair-confirm': {
|
|
684
|
+
let pairingId = url.searchParams.get('pairingId') || '';
|
|
685
|
+
if (!pairingId) {
|
|
686
|
+
try {
|
|
687
|
+
pairingId = String(JSON.parse(body || '{}').pairingId || '');
|
|
688
|
+
}
|
|
689
|
+
catch { }
|
|
690
|
+
}
|
|
691
|
+
const result = (0, mobilePairing_1.confirmPairing)(appRoot, pairingId, mobileToken);
|
|
692
|
+
mobileJson(res, result.ok
|
|
693
|
+
? { ok: true, status: result.status }
|
|
694
|
+
: { ok: false, error: result.error, status: result.status }, result.ok ? 200 : 401);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
case '/api/mobile/pair-status': {
|
|
698
|
+
mobileJson(res, { ok: true, status: (0, mobilePairing_1.pairingStatus)(appRoot) });
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
case '/api/mobile/hello': {
|
|
702
|
+
mobileJson(res, {
|
|
703
|
+
ok: true,
|
|
704
|
+
version: (0, installUpdate_1.currentAppVersion)(),
|
|
705
|
+
hostname: os.hostname(),
|
|
706
|
+
platform: process.platform,
|
|
707
|
+
tailscaleIpv4: (0, mobilePairing_1.tailscaleIpv4)(),
|
|
708
|
+
workspace: agent.workspace.current ? {
|
|
709
|
+
id: agent.workspace.current.id,
|
|
710
|
+
name: agent.workspace.current.name,
|
|
711
|
+
path: agent.workspace.current.path,
|
|
712
|
+
} : null,
|
|
713
|
+
conversationCount: agent.listConversationStates().length,
|
|
714
|
+
activeConversationId: agent.activeConversationId,
|
|
715
|
+
});
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
case '/api/mobile/state': {
|
|
719
|
+
const active = agent.getConversationSnapshot(agent.activeConversationId, { window: 200 });
|
|
720
|
+
mobileJson(res, {
|
|
721
|
+
mode: agent.mode,
|
|
722
|
+
model: agent.modelSelectionValue(),
|
|
723
|
+
status: agent.status,
|
|
724
|
+
activeConversationId: agent.activeConversationId,
|
|
725
|
+
conversations: agent.listConversationStates(),
|
|
726
|
+
workspaces: { internal: agent.workspace.internal, external: agent.workspace.external, current: agent.workspace.current },
|
|
727
|
+
pendingOptions: agent.pendingOptions,
|
|
728
|
+
contextWindow: agent.contextWindow(),
|
|
729
|
+
chatMessages: active.chatMessages,
|
|
730
|
+
totalMessages: active.totalMessages,
|
|
731
|
+
conversationLocked: agent.isConversationLocked(),
|
|
732
|
+
});
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
case '/api/mobile/conversations': {
|
|
736
|
+
mobileJson(res, agent.listConversationStates());
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
case '/api/mobile/conversation': {
|
|
740
|
+
const conversationId = url.searchParams.get('conversationId') || agent.activeConversationId;
|
|
741
|
+
const windowParam = url.searchParams.get('window');
|
|
742
|
+
const beforeParam = url.searchParams.get('before');
|
|
743
|
+
const windowSize = windowParam ? Math.max(1, Math.min(500, Number(windowParam) || 200)) : 200;
|
|
744
|
+
const before = beforeParam ? Math.max(0, Number(beforeParam) || 0) : undefined;
|
|
745
|
+
const snapshot = agent.getConversationSnapshot(String(conversationId), { window: windowSize, before });
|
|
746
|
+
mobileJson(res, snapshot);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
case '/api/mobile/workspaces': {
|
|
750
|
+
mobileJson(res, { internal: agent.workspace.internal, external: agent.workspace.external, current: agent.workspace.current });
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
case '/api/mobile/send': {
|
|
754
|
+
const params = JSON.parse(body || '{}');
|
|
755
|
+
const message = String(params.message || '');
|
|
756
|
+
if (!message) {
|
|
757
|
+
mobileJson(res, { error: 'No message' }, 400);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (params.conversationId)
|
|
761
|
+
agent.setConversation(String(params.conversationId));
|
|
762
|
+
const tokens = await agent.process(message);
|
|
763
|
+
const snapshot = agent.getConversationSnapshot(agent.activeConversationId, { window: 200 });
|
|
764
|
+
mobileJson(res, {
|
|
765
|
+
ok: true,
|
|
766
|
+
conversationId: agent.activeConversationId,
|
|
767
|
+
response: tokens.map(token => token.text).join(''),
|
|
768
|
+
tokens: tokens.map(token => ({ type: token.type, text: token.text })),
|
|
769
|
+
options: agent.pendingOptions,
|
|
770
|
+
status: agent.status,
|
|
771
|
+
conversations: agent.listConversationStates(),
|
|
772
|
+
chatMessages: snapshot.chatMessages,
|
|
773
|
+
totalMessages: snapshot.totalMessages,
|
|
774
|
+
});
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
606
777
|
default:
|
|
607
778
|
jsonResponse(res, { error: 'Unknown API' }, 404);
|
|
608
779
|
}
|
|
609
780
|
}
|
|
610
781
|
catch (e) {
|
|
611
|
-
|
|
782
|
+
if (pathname.startsWith('/api/mobile/'))
|
|
783
|
+
mobileJson(res, { error: e.message }, 500);
|
|
784
|
+
else
|
|
785
|
+
jsonResponse(res, { error: e.message }, 500);
|
|
612
786
|
}
|
|
613
787
|
}
|
|
614
788
|
function startServer(root) {
|
|
789
|
+
mobileToken = (0, mobilePairing_1.ensureMobileToken)(root);
|
|
790
|
+
appRoot = root;
|
|
615
791
|
agent = new agent_1.Agent(root);
|
|
616
792
|
workspaceFileRouter = new workspaceFileRouter_1.WorkspaceFileRouter(() => path.resolve(agent?.workspace.current?.path || root));
|
|
617
793
|
automation = new automation_1.AutomationManager(agent.config, async (prompt, model, item) => {
|
|
@@ -645,11 +821,19 @@ function startServer(root) {
|
|
|
645
821
|
const uiDir = path.join(__dirname, 'ui');
|
|
646
822
|
const server = http.createServer(async (req, res) => {
|
|
647
823
|
if (req.method === 'OPTIONS') {
|
|
648
|
-
res.writeHead(204
|
|
824
|
+
res.writeHead(204, {
|
|
825
|
+
'Access-Control-Allow-Origin': '*',
|
|
826
|
+
'Access-Control-Allow-Headers': 'Authorization, Content-Type',
|
|
827
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
828
|
+
});
|
|
649
829
|
res.end();
|
|
650
830
|
return;
|
|
651
831
|
}
|
|
652
832
|
const url = new URL(req.url || '/', `http://localhost:${PORT}`);
|
|
833
|
+
if (url.pathname === '/api/mobile/events') {
|
|
834
|
+
handleMobileEvents(req, res);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
653
837
|
if (req.method === 'POST' && url.pathname.startsWith('/api/')) {
|
|
654
838
|
let body = '';
|
|
655
839
|
req.on('data', chunk => body += chunk);
|
|
@@ -667,9 +851,17 @@ function startServer(root) {
|
|
|
667
851
|
const fullPath = path.join(uiDir, filePath);
|
|
668
852
|
serveFile(res, fullPath);
|
|
669
853
|
});
|
|
670
|
-
|
|
854
|
+
const bindHost = process.env.NEWMARK_BIND_HOST || '0.0.0.0';
|
|
855
|
+
const tailscale = (0, mobilePairing_1.tailscaleIpv4)();
|
|
856
|
+
const tokenPath = path.join(root, '.newmark-mobile-token');
|
|
857
|
+
server.listen(PORT, bindHost, () => {
|
|
671
858
|
console.log(`\n Newmark Agent v1.0 - Server Mode`);
|
|
859
|
+
console.log(` Bind: ${bindHost}:${PORT}`);
|
|
672
860
|
console.log(` GUI: http://localhost:${PORT}`);
|
|
861
|
+
console.log(` Tailscale IPv4: ${tailscale || 'not detected (install/start Tailscale)'}`);
|
|
862
|
+
console.log(` Mobile endpoint: http://${tailscale || '<tailscale-ip>'}:${PORT}/api/mobile/hello?token=<token>`);
|
|
863
|
+
console.log(` Mobile token file: ${tokenPath}`);
|
|
864
|
+
console.log(` Mobile events (SSE): http://${tailscale || '<tailscale-ip>'}:${PORT}/api/mobile/events?token=<token>`);
|
|
673
865
|
console.log(` Press Ctrl+C to stop\n`);
|
|
674
866
|
});
|
|
675
867
|
}
|
|
@@ -154,6 +154,7 @@ function createCoreRuntimeAdapter(options = {}) {
|
|
|
154
154
|
const { AutomationManager } = require(path.join(desktopDist, "core", "automation.js"));
|
|
155
155
|
const { FlowEngine } = require(path.join(desktopDist, "core", "flow.js"));
|
|
156
156
|
const installUpdate = require(path.join(desktopDist, "core", "installUpdate.js"));
|
|
157
|
+
const mobilePairing = require(path.join(desktopDist, "core", "mobilePairing.js"));
|
|
157
158
|
const root = path.resolve(options.root || path.join(os.homedir(), ".Newmark"));
|
|
158
159
|
const workspacePath = safeWorkspacePath(root, options.workspacePath || process.cwd());
|
|
159
160
|
ensureRuntimeRoot(root, configModule);
|
|
@@ -211,7 +212,8 @@ function createCoreRuntimeAdapter(options = {}) {
|
|
|
211
212
|
defaultTerminalShell: agent.config.getStr("terminal", "default_shell") || (process.platform === "win32" ? "powershell" : "bash"),
|
|
212
213
|
status: agent.status,
|
|
213
214
|
connected: true,
|
|
214
|
-
runtimeRoot: root
|
|
215
|
+
runtimeRoot: root,
|
|
216
|
+
remoteTouchEnabled: agent.config.getBool('remote', 'touch_enabled')
|
|
215
217
|
});
|
|
216
218
|
|
|
217
219
|
const snapshotFor = (requested = currentTarget()) => {
|
|
@@ -473,6 +475,20 @@ function createCoreRuntimeAdapter(options = {}) {
|
|
|
473
475
|
updateVersion() {
|
|
474
476
|
return { version: installUpdate.currentAppVersion(), root };
|
|
475
477
|
},
|
|
478
|
+
async pairingQr() {
|
|
479
|
+
const qr = await mobilePairing.pairingQrAscii(root);
|
|
480
|
+
return {
|
|
481
|
+
ascii: qr.ascii,
|
|
482
|
+
url: qr.session.url,
|
|
483
|
+
pairingId: qr.session.pairingId,
|
|
484
|
+
expiresAt: qr.session.expiresAt,
|
|
485
|
+
tokenFile: mobilePairing.pairingTokenPath(root),
|
|
486
|
+
tailscaleIpv4: mobilePairing.tailscaleIpv4(),
|
|
487
|
+
};
|
|
488
|
+
},
|
|
489
|
+
pairingStatus() {
|
|
490
|
+
return mobilePairing.pairingStatus(root);
|
|
491
|
+
},
|
|
476
492
|
updateCheckGithub(input = {}) {
|
|
477
493
|
return installUpdate.checkGitHubUpdate(input.repo, input.tag, input.asset, input.token);
|
|
478
494
|
},
|
package/dist/tui/src/app.js
CHANGED
|
@@ -93,6 +93,46 @@ function executeAction(state, action) {
|
|
|
93
93
|
const appearance = applyThemeAppearance(state, state.theme === "dark" ? "Light" : "Dark");
|
|
94
94
|
state.adapter.saveConfig(appearance);
|
|
95
95
|
state.notice = `${state.theme === "dark" ? "Dark" : "Light"} terminal theme`;
|
|
96
|
+
} else if (action === "pair-mobile") {
|
|
97
|
+
state.overlay = "pair";
|
|
98
|
+
state.pairingQrLines = ["Loading pairing QR…"];
|
|
99
|
+
state.pairingUrl = "";
|
|
100
|
+
state.pairingTokenFile = "";
|
|
101
|
+
if (typeof state.requestPaint === "function") state.requestPaint();
|
|
102
|
+
if (typeof state.adapter.pairingQr !== "function") {
|
|
103
|
+
state.pairingQrLines = ["Pairing QR is unavailable in this adapter."];
|
|
104
|
+
if (typeof state.requestPaint === "function") state.requestPaint();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
Promise.resolve(state.adapter.pairingQr())
|
|
108
|
+
.then((pairing) => {
|
|
109
|
+
state.pairingQrLines = String(pairing?.ascii || "").split(/\r?\n/);
|
|
110
|
+
state.pairingUrl = String(pairing?.url || "");
|
|
111
|
+
state.pairingTokenFile = String(pairing?.tokenFile || "");
|
|
112
|
+
if (typeof state.requestPaint === "function") state.requestPaint();
|
|
113
|
+
if (typeof state.adapter.pairingStatus === "function") {
|
|
114
|
+
if (state._pairingPoll) clearInterval(state._pairingPoll);
|
|
115
|
+
state._pairingPoll = setInterval(() => {
|
|
116
|
+
const status = state.adapter.pairingStatus();
|
|
117
|
+
if (!status) return;
|
|
118
|
+
if (status.confirmed) {
|
|
119
|
+
clearInterval(state._pairingPoll);
|
|
120
|
+
state.overlay = null;
|
|
121
|
+
state.notice = "Mobile device connected";
|
|
122
|
+
if (typeof state.requestPaint === "function") state.requestPaint();
|
|
123
|
+
} else if (status.expired || !status.active) {
|
|
124
|
+
clearInterval(state._pairingPoll);
|
|
125
|
+
state.overlay = null;
|
|
126
|
+
state.notice = "Pairing window expired";
|
|
127
|
+
if (typeof state.requestPaint === "function") state.requestPaint();
|
|
128
|
+
}
|
|
129
|
+
}, 1000);
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
.catch((error) => {
|
|
133
|
+
state.pairingQrLines = [`Pairing failed: ${error?.message || error}`];
|
|
134
|
+
if (typeof state.requestPaint === "function") state.requestPaint();
|
|
135
|
+
});
|
|
96
136
|
} else if (action === "help") {
|
|
97
137
|
state.overlay = "help";
|
|
98
138
|
}
|
|
@@ -166,6 +206,7 @@ function start(options = {}) {
|
|
|
166
206
|
return;
|
|
167
207
|
}
|
|
168
208
|
const state = createState({ adapter });
|
|
209
|
+
state.requestPaint = () => paint();
|
|
169
210
|
let timer = null;
|
|
170
211
|
let animationTimer = null;
|
|
171
212
|
let closing = false;
|
package/dist/tui/src/data.js
CHANGED
|
@@ -315,6 +315,7 @@ const commands = [
|
|
|
315
315
|
{ label: "Open Automations", hint: "", action: "view:automation" },
|
|
316
316
|
{ label: "Open WorkFlow", hint: "", action: "view:workflow" },
|
|
317
317
|
{ label: "Open Settings", hint: "", action: "view:settings" },
|
|
318
|
+
{ label: "Show mobile pairing QR", hint: "", action: "pair-mobile" },
|
|
318
319
|
{ label: "New conversation", hint: "N", action: "new-chat" },
|
|
319
320
|
{ label: "Toggle theme", hint: "T", action: "theme" },
|
|
320
321
|
{ label: "Keyboard shortcuts", hint: "?", action: "help" }
|
package/dist/tui/src/render.js
CHANGED
|
@@ -1154,6 +1154,17 @@ function overlayLines(state, width, p) {
|
|
|
1154
1154
|
`${p.cyan}Press Esc or ? to close${p.reset}`
|
|
1155
1155
|
], Math.min(78, width - 4), p, "Keyboard shortcuts");
|
|
1156
1156
|
}
|
|
1157
|
+
if (state.overlay === "pair") {
|
|
1158
|
+
const qrLines = Array.isArray(state.pairingQrLines) ? state.pairingQrLines : [];
|
|
1159
|
+
const rows = [
|
|
1160
|
+
...qrLines,
|
|
1161
|
+
"",
|
|
1162
|
+
...(state.pairingUrl ? [`${p.muted}${state.pairingUrl}${p.reset}`] : []),
|
|
1163
|
+
...(state.pairingTokenFile ? [`${p.muted}Token: ${state.pairingTokenFile}${p.reset}`] : []),
|
|
1164
|
+
`${p.cyan}Scan with the Newmark mobile app · Esc close${p.reset}`
|
|
1165
|
+
];
|
|
1166
|
+
return card(rows, Math.min(88, width - 4), p, "Pair mobile device");
|
|
1167
|
+
}
|
|
1157
1168
|
if (state.overlay === "palette") {
|
|
1158
1169
|
const commands = filteredCommands(state);
|
|
1159
1170
|
const paletteViewport = Math.min(7, Math.max(1, commands.length));
|
|
@@ -23,7 +23,9 @@ function settingsRows(state, tab = state.settingsTab) {
|
|
|
23
23
|
{ key: "dialogStyle", label: "Conversation style", value: s.general.dialogStyle, choices: ["Formal", "Friendly"], save: ["config", "dialogStyle"] },
|
|
24
24
|
{ key: "feedbackLevel", label: "Option feedback", value: s.general.feedbackLevel, choices: ["Default", "Ask more", "Ask less", "Autonomous"], save: ["config", "feedbackLevel"] },
|
|
25
25
|
{ key: "closeBehavior", label: "Close behavior", value: s.general.closeBehavior, choices: ["Close app", "Minimize to tray"], save: ["setting", "general", "close_behavior"] },
|
|
26
|
-
{ key: "expandTools", label: "Expand tool usage", value: s.general.expandTools, choices: [true, false], save: ["setting", "general", "expand_tools"] }
|
|
26
|
+
{ key: "expandTools", label: "Expand tool usage", value: s.general.expandTools, choices: [true, false], save: ["setting", "general", "expand_tools"] },
|
|
27
|
+
{ key: "remoteTouch", label: "Remote behavior · Mobile remote-touch", value: s.general.remoteTouch, choices: [true, false], save: ["setting", "remote", "touch_enabled"] },
|
|
28
|
+
{ key: "remotePair", label: "Remote behavior · Start connection", value: "Open QR", choices: [], action: "pair-mobile" }
|
|
27
29
|
];
|
|
28
30
|
if (tab === "personalization") return [
|
|
29
31
|
{ key: "theme", label: "Theme", value: s.personalization.theme, choices: ["Dark", "Light", "System"], save: ["config", "theme"] },
|
package/dist/tui/src/state.js
CHANGED
|
@@ -253,7 +253,8 @@ function createState(options = {}) {
|
|
|
253
253
|
dialogStyle: snapshot.dialogStyle === "friendly" ? "Friendly" : "Formal",
|
|
254
254
|
feedbackLevel: { ask_more: "Ask more", ask_less: "Ask less", fully_autonomous: "Autonomous" }[snapshot.feedback] || "Default",
|
|
255
255
|
closeBehavior: snapshot.closeBehavior === "minimize" ? "Minimize to tray" : "Close app",
|
|
256
|
-
expandTools: snapshot.expandToolsDefault !== false
|
|
256
|
+
expandTools: snapshot.expandToolsDefault !== false,
|
|
257
|
+
remoteTouch: snapshot.remoteTouchEnabled !== false
|
|
257
258
|
},
|
|
258
259
|
personalization: {
|
|
259
260
|
theme: { light: "Light", system: "System", dark: "Dark" }[String(snapshot.darkMode || "dark").toLowerCase()] || "Dark",
|
|
@@ -1071,6 +1072,25 @@ function toggleSelected(state) {
|
|
|
1071
1072
|
}
|
|
1072
1073
|
const row = settingsRows(state)[state.selected];
|
|
1073
1074
|
if (!row) return;
|
|
1075
|
+
if (row.action === "pair-mobile") {
|
|
1076
|
+
state.overlay = "pair";
|
|
1077
|
+
state.pairingQrLines = ["Loading pairing QR…"];
|
|
1078
|
+
state.pairingUrl = "";
|
|
1079
|
+
state.pairingTokenFile = "";
|
|
1080
|
+
if (typeof state.adapter.pairingQr !== "function") {
|
|
1081
|
+
state.pairingQrLines = ["Pairing QR is unavailable in this adapter."];
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
return Promise.resolve(state.adapter.pairingQr()).then((pairing) => {
|
|
1085
|
+
state.pairingQrLines = String(pairing?.ascii || "").split(/\r?\n/);
|
|
1086
|
+
state.pairingUrl = String(pairing?.url || "");
|
|
1087
|
+
state.pairingTokenFile = String(pairing?.tokenFile || "");
|
|
1088
|
+
return true;
|
|
1089
|
+
}).catch((error) => {
|
|
1090
|
+
state.pairingQrLines = [`Pairing failed: ${error?.message || error}`];
|
|
1091
|
+
return false;
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1074
1094
|
if (state.settingsTab === "general" && row.key === "inputBehavior") {
|
|
1075
1095
|
const current = row.choices.findIndex((value) => value === row.value);
|
|
1076
1096
|
state.settingChoiceTab = state.settingsTab;
|