@agent-native/core 0.176.3 → 0.176.4-nightly-20260901193441
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/corpus/README.md +1 -1
- package/corpus/templates/calendar/changelog/2026-09-01-calendar-keeps-the-learn-more-link-clear-of-the-language-con.md +6 -0
- package/corpus/templates/calendar/server/plugins/auth.ts +1 -0
- package/dist/client/auth/AuthPage.d.ts +1 -0
- package/dist/client/auth/AuthPage.js +9 -3
- package/dist/client/terminal/AgentTerminal.d.ts +3 -1
- package/dist/client/terminal/AgentTerminal.js +36 -4
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth-marketing.d.ts +1 -0
- package/dist/server/auth-marketing.js +1 -0
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/onboarding-html.d.ts +1 -0
- package/dist/server/onboarding-html.js +19 -9
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/terminal/cli-registry.d.ts +3 -0
- package/dist/terminal/cli-registry.js +54 -8
- package/dist/terminal/pty-server.d.ts +22 -0
- package/dist/terminal/pty-server.js +182 -55
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -14,6 +14,7 @@ export default createAuthPlugin({
|
|
|
14
14
|
screenshotWidth: 914,
|
|
15
15
|
screenshotHeight: 818,
|
|
16
16
|
learnMoreUrl: "https://agent-native.com/apps/calendar",
|
|
17
|
+
learnMorePlacement: "bottom-right",
|
|
17
18
|
tagline:
|
|
18
19
|
"Your AI agent schedules, reschedules, and manages your calendar so you never have to.",
|
|
19
20
|
features: [
|
|
@@ -1831,9 +1831,15 @@ export function AuthPage(props) {
|
|
|
1831
1831
|
React.createElement("span", { "aria-hidden": "true" }, " - "),
|
|
1832
1832
|
React.createElement("span", { className: "auth-marketing-learn-more-link" }, t("learnMore")))) : null, auth: React.createElement(React.Fragment, null,
|
|
1833
1833
|
authCard,
|
|
1834
|
-
localNote), className:
|
|
1835
|
-
|
|
1836
|
-
|
|
1834
|
+
localNote), className: [
|
|
1835
|
+
"auth-marketing-home",
|
|
1836
|
+
marketingCopy.screenshotSrc ? "has-product-screenshot" : "",
|
|
1837
|
+
marketingCopy.learnMorePlacement === "bottom-right"
|
|
1838
|
+
? "has-bottom-right-learn-more"
|
|
1839
|
+
: "",
|
|
1840
|
+
]
|
|
1841
|
+
.filter(Boolean)
|
|
1842
|
+
.join(" ") }, marketingCopy.screenshotSrc ? (React.createElement("div", { className: "auth-marketing-screenshot-wrap" },
|
|
1837
1843
|
React.createElement("img", { className: "auth-marketing-screenshot", src: marketingCopy.screenshotSrc, alt: `${marketingCopy.appName} preview`, width: marketingCopy.screenshotWidth, height: marketingCopy.screenshotHeight, fetchPriority: "high", decoding: "async" }))) : (React.createElement("div", { className: "marketing-content" },
|
|
1838
1844
|
React.createElement("h2", { className: "app-name" },
|
|
1839
1845
|
React.createElement("img", { className: "brand-mark", src: brandMarkSrc, alt: "", "aria-hidden": "true" }),
|
|
@@ -22,6 +22,8 @@ export interface AgentTerminalProps {
|
|
|
22
22
|
theme?: Record<string, string>;
|
|
23
23
|
/** Font size. Default: 12 */
|
|
24
24
|
fontSize?: number;
|
|
25
|
+
/** Focus the terminal on mount and when it becomes active. Default: true */
|
|
26
|
+
autoFocus?: boolean;
|
|
25
27
|
/** CSS class for the container */
|
|
26
28
|
className?: string;
|
|
27
29
|
/** Inline styles for the container */
|
|
@@ -40,4 +42,4 @@ export interface AgentTerminalSubmitRequest {
|
|
|
40
42
|
text: string;
|
|
41
43
|
}
|
|
42
44
|
export declare function formatWebSocketHostname(hostname: string): string;
|
|
43
|
-
export declare function AgentTerminal({ command, flags, wsUrl: wsUrlProp, hideInFrame, theme, fontSize, className, style, onConnectionChange, onAgentRunningChange, submitRequest, onPromptSubmitted, }: AgentTerminalProps): import("react").JSX.Element | null;
|
|
45
|
+
export declare function AgentTerminal({ command, flags, wsUrl: wsUrlProp, hideInFrame, theme, fontSize, autoFocus, className, style, onConnectionChange, onAgentRunningChange, submitRequest, onPromptSubmitted, }: AgentTerminalProps): import("react").JSX.Element | null;
|
|
@@ -33,9 +33,20 @@ function injectXtermCss() {
|
|
|
33
33
|
.xterm .composition-view { display: none; position: absolute; white-space: nowrap; z-index: 1; }
|
|
34
34
|
.xterm .composition-view.active { display: block; }
|
|
35
35
|
.xterm .xterm-viewport {
|
|
36
|
-
background-color: var(--agent-terminal-background); overflow-y:
|
|
36
|
+
background-color: var(--agent-terminal-background); overflow-y: auto;
|
|
37
|
+
scrollbar-width: thin;
|
|
38
|
+
scrollbar-color: hsl(var(--muted-foreground) / 0.22) transparent;
|
|
37
39
|
cursor: default; position: absolute; right: 0; left: 0; top: 0; bottom: 0;
|
|
38
40
|
}
|
|
41
|
+
.xterm .xterm-viewport::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
42
|
+
.xterm .xterm-viewport::-webkit-scrollbar-track { background: transparent; }
|
|
43
|
+
.xterm .xterm-viewport::-webkit-scrollbar-thumb {
|
|
44
|
+
background: hsl(var(--muted-foreground) / 0.22);
|
|
45
|
+
border-radius: 999px;
|
|
46
|
+
}
|
|
47
|
+
.xterm .xterm-viewport::-webkit-scrollbar-thumb:hover {
|
|
48
|
+
background: hsl(var(--muted-foreground) / 0.36);
|
|
49
|
+
}
|
|
39
50
|
.xterm .xterm-screen { position: relative; }
|
|
40
51
|
.xterm .xterm-screen canvas { position: absolute; left: 0; top: 0; }
|
|
41
52
|
.xterm .xterm-scroll-area { visibility: hidden; }
|
|
@@ -85,10 +96,13 @@ export function formatWebSocketHostname(hostname) {
|
|
|
85
96
|
? `[${hostname}]`
|
|
86
97
|
: hostname;
|
|
87
98
|
}
|
|
88
|
-
export function AgentTerminal({ command, flags, wsUrl: wsUrlProp, hideInFrame = true, theme, fontSize = 12, className, style, onConnectionChange, onAgentRunningChange, submitRequest, onPromptSubmitted, }) {
|
|
99
|
+
export function AgentTerminal({ command, flags, wsUrl: wsUrlProp, hideInFrame = true, theme, fontSize = 12, autoFocus = true, className, style, onConnectionChange, onAgentRunningChange, submitRequest, onPromptSubmitted, }) {
|
|
89
100
|
const termRef = useRef(null);
|
|
90
101
|
const submitPromptRef = useRef(null);
|
|
91
102
|
const pendingSubmitRequestRef = useRef(null);
|
|
103
|
+
const autoFocusRef = useRef(autoFocus);
|
|
104
|
+
autoFocusRef.current = autoFocus;
|
|
105
|
+
const focusTerminalRef = useRef(null);
|
|
92
106
|
const onPromptSubmittedRef = useRef(onPromptSubmitted);
|
|
93
107
|
onPromptSubmittedRef.current = onPromptSubmitted;
|
|
94
108
|
const [connected, setConnected] = useState(false);
|
|
@@ -111,6 +125,17 @@ export function AgentTerminal({ command, flags, wsUrl: wsUrlProp, hideInFrame =
|
|
|
111
125
|
useEffect(() => {
|
|
112
126
|
onConnectionChange?.(connected);
|
|
113
127
|
}, [connected, onConnectionChange]);
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
if (autoFocus) {
|
|
130
|
+
focusTerminalRef.current?.();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const activeElement = document.activeElement;
|
|
134
|
+
if (activeElement instanceof HTMLElement &&
|
|
135
|
+
termRef.current?.contains(activeElement)) {
|
|
136
|
+
activeElement.blur();
|
|
137
|
+
}
|
|
138
|
+
}, [autoFocus]);
|
|
114
139
|
// Main terminal setup
|
|
115
140
|
useEffect(() => {
|
|
116
141
|
if (typeof window === "undefined")
|
|
@@ -148,6 +173,10 @@ export function AgentTerminal({ command, flags, wsUrl: wsUrlProp, hideInFrame =
|
|
|
148
173
|
term.loadAddon(fitAddon);
|
|
149
174
|
term.loadAddon(webLinksAddon);
|
|
150
175
|
term.open(container);
|
|
176
|
+
const focusTerminal = () => term.focus();
|
|
177
|
+
focusTerminalRef.current = focusTerminal;
|
|
178
|
+
if (autoFocusRef.current)
|
|
179
|
+
focusTerminal();
|
|
151
180
|
let fitPending = false;
|
|
152
181
|
function fitAndResize() {
|
|
153
182
|
if (fitPending)
|
|
@@ -190,6 +219,9 @@ export function AgentTerminal({ command, flags, wsUrl: wsUrlProp, hideInFrame =
|
|
|
190
219
|
window.removeEventListener("focus", handleVisibilityOrFocus);
|
|
191
220
|
document.removeEventListener("visibilitychange", handleVisibilityOrFocus);
|
|
192
221
|
resizeObserver.disconnect();
|
|
222
|
+
if (focusTerminalRef.current === focusTerminal) {
|
|
223
|
+
focusTerminalRef.current = null;
|
|
224
|
+
}
|
|
193
225
|
term.dispose();
|
|
194
226
|
}
|
|
195
227
|
function disposeIfCancelled() {
|
|
@@ -236,7 +268,6 @@ export function AgentTerminal({ command, flags, wsUrl: wsUrlProp, hideInFrame =
|
|
|
236
268
|
}
|
|
237
269
|
if (flags)
|
|
238
270
|
fullWsUrl.searchParams.set("flags", flags);
|
|
239
|
-
term.write(`\x1b[2m[terminal] Starting ${resolvedCommand || "CLI"}...\x1b[0m\r\n`);
|
|
240
271
|
// Connect WebSocket
|
|
241
272
|
let agentRunning = false;
|
|
242
273
|
let idleTimer = null;
|
|
@@ -284,6 +315,8 @@ export function AgentTerminal({ command, flags, wsUrl: wsUrlProp, hideInFrame =
|
|
|
284
315
|
socket.onopen = () => {
|
|
285
316
|
setConnected(true);
|
|
286
317
|
setError(null);
|
|
318
|
+
if (autoFocusRef.current)
|
|
319
|
+
focusTerminal();
|
|
287
320
|
socket.send(JSON.stringify({
|
|
288
321
|
type: "resize",
|
|
289
322
|
cols: term.cols,
|
|
@@ -333,7 +366,6 @@ export function AgentTerminal({ command, flags, wsUrl: wsUrlProp, hideInFrame =
|
|
|
333
366
|
socket.onclose = () => {
|
|
334
367
|
setConnected(false);
|
|
335
368
|
if (connectionId === thisId && !disposed) {
|
|
336
|
-
term.write("\r\n\x1b[31m[terminal] Connection closed. Reconnecting in 3s...\x1b[0m\r\n");
|
|
337
369
|
setTimeout(() => {
|
|
338
370
|
if (connectionId === thisId && !disposed) {
|
|
339
371
|
connect(url);
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
26
26
|
* Body: { update: string (base64), requestSource?: string }
|
|
27
27
|
*/
|
|
28
28
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
29
|
-
ok?: undefined;
|
|
30
29
|
error: string;
|
|
30
|
+
ok?: undefined;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
33
33
|
ok: boolean;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -3,25 +3,25 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
3
3
|
url?: string;
|
|
4
4
|
filename?: string;
|
|
5
5
|
}, {
|
|
6
|
-
id?: undefined;
|
|
7
6
|
error: string;
|
|
8
7
|
configured?: undefined;
|
|
9
8
|
connectPath?: undefined;
|
|
10
9
|
url?: undefined;
|
|
10
|
+
id?: undefined;
|
|
11
11
|
provider?: undefined;
|
|
12
12
|
} | {
|
|
13
|
-
id?: undefined;
|
|
14
13
|
error: string;
|
|
15
14
|
configured: boolean;
|
|
16
15
|
connectPath: string;
|
|
17
16
|
url?: undefined;
|
|
17
|
+
id?: undefined;
|
|
18
18
|
provider?: undefined;
|
|
19
19
|
} | {
|
|
20
|
+
error?: undefined;
|
|
20
21
|
configured?: undefined;
|
|
21
22
|
connectPath?: undefined;
|
|
22
23
|
url: string;
|
|
23
24
|
id: string;
|
|
24
25
|
provider: string;
|
|
25
|
-
error?: undefined;
|
|
26
26
|
}>;
|
|
27
27
|
export default _default;
|
|
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
+
error?: undefined;
|
|
44
45
|
summary: import("./types.js").TraceSummary;
|
|
45
46
|
spans: import("./types.js").TraceSpan[];
|
|
46
|
-
id?: undefined;
|
|
47
|
-
error?: undefined;
|
|
48
47
|
ok?: undefined;
|
|
48
|
+
id?: undefined;
|
|
49
49
|
} | {
|
|
50
|
+
error?: undefined;
|
|
50
51
|
summary?: undefined;
|
|
51
52
|
spans?: undefined;
|
|
52
53
|
id: string;
|
|
53
|
-
error?: undefined;
|
|
54
54
|
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
56
|
summary?: undefined;
|
|
57
57
|
spans?: undefined;
|
|
58
|
-
id?: undefined;
|
|
59
58
|
error: any;
|
|
60
59
|
ok?: undefined;
|
|
60
|
+
id?: undefined;
|
|
61
61
|
} | {
|
|
62
|
+
error?: undefined;
|
|
62
63
|
summary?: undefined;
|
|
63
64
|
spans?: undefined;
|
|
64
|
-
id?: undefined;
|
|
65
65
|
ok: boolean;
|
|
66
|
-
|
|
66
|
+
id?: undefined;
|
|
67
67
|
}>>;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -37,17 +37,17 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
37
37
|
ok?: undefined;
|
|
38
38
|
status?: undefined;
|
|
39
39
|
} | {
|
|
40
|
+
error?: undefined;
|
|
40
41
|
ok: boolean;
|
|
41
42
|
status: string;
|
|
42
|
-
error?: undefined;
|
|
43
43
|
} | {
|
|
44
44
|
ok?: undefined;
|
|
45
45
|
error: string;
|
|
46
46
|
removed?: undefined;
|
|
47
47
|
} | {
|
|
48
|
+
error?: undefined;
|
|
48
49
|
ok: boolean;
|
|
49
50
|
removed: boolean;
|
|
50
|
-
error?: undefined;
|
|
51
51
|
}>>;
|
|
52
52
|
/**
|
|
53
53
|
* POST /_agent-native/secrets/:key/test — validate an optional candidate value
|
|
@@ -58,13 +58,13 @@ export declare function createTestSecretHandler(): import("h3").EventHandlerWith
|
|
|
58
58
|
error: string;
|
|
59
59
|
note?: undefined;
|
|
60
60
|
} | {
|
|
61
|
+
error?: undefined;
|
|
61
62
|
ok: boolean;
|
|
62
63
|
note?: undefined;
|
|
63
|
-
error?: undefined;
|
|
64
64
|
} | {
|
|
65
|
+
error?: undefined;
|
|
65
66
|
ok: boolean;
|
|
66
67
|
note: string;
|
|
67
|
-
error?: undefined;
|
|
68
68
|
} | {
|
|
69
69
|
note?: undefined;
|
|
70
70
|
ok: boolean;
|
|
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
|
|
|
95
95
|
export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
|
|
96
96
|
error: string;
|
|
97
97
|
} | {
|
|
98
|
+
error?: undefined;
|
|
98
99
|
ok: boolean;
|
|
99
100
|
key: string;
|
|
100
|
-
error?: undefined;
|
|
101
101
|
} | {
|
|
102
|
+
error?: undefined;
|
|
102
103
|
ok: boolean;
|
|
103
104
|
removed: boolean;
|
|
104
|
-
error?: undefined;
|
|
105
105
|
}>>;
|
|
@@ -34,10 +34,10 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
|
|
|
34
34
|
export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
35
35
|
error: any;
|
|
36
36
|
} | {
|
|
37
|
+
error?: undefined;
|
|
37
38
|
ok: boolean;
|
|
38
39
|
key: string;
|
|
39
40
|
baseUrlKey?: string;
|
|
40
41
|
scope: AgentEngineApiKeyScope;
|
|
41
|
-
error?: undefined;
|
|
42
42
|
}>>;
|
|
43
43
|
export {};
|
|
@@ -7,6 +7,7 @@ export interface AuthMarketingContent {
|
|
|
7
7
|
screenshotWidth?: number;
|
|
8
8
|
screenshotHeight?: number;
|
|
9
9
|
learnMoreUrl?: string;
|
|
10
|
+
learnMorePlacement?: "top-right" | "bottom-right";
|
|
10
11
|
/** @deprecated Local execution is no longer offered from auth pages. */
|
|
11
12
|
runLocalCommand?: string;
|
|
12
13
|
signupLocalModeNote?: {
|
|
@@ -32,6 +32,7 @@ export const BUILT_IN_AUTH_MARKETING = {
|
|
|
32
32
|
"Manages availability and booking links automatically",
|
|
33
33
|
"Answers schedule questions and resolves conflicts instantly",
|
|
34
34
|
],
|
|
35
|
+
learnMorePlacement: "bottom-right",
|
|
35
36
|
},
|
|
36
37
|
clips: {
|
|
37
38
|
appName: "Agent-Native Clips",
|
package/dist/server/auth.d.ts
CHANGED
|
@@ -132,6 +132,7 @@ export interface AuthOptions {
|
|
|
132
132
|
screenshotWidth?: number;
|
|
133
133
|
screenshotHeight?: number;
|
|
134
134
|
learnMoreUrl?: string;
|
|
135
|
+
learnMorePlacement?: "top-right" | "bottom-right";
|
|
135
136
|
/** @deprecated Local execution is no longer offered from auth pages. */
|
|
136
137
|
runLocalCommand?: string;
|
|
137
138
|
};
|
|
@@ -33,6 +33,7 @@ export interface OnboardingHtmlOptions {
|
|
|
33
33
|
screenshotWidth?: number;
|
|
34
34
|
screenshotHeight?: number;
|
|
35
35
|
learnMoreUrl?: string;
|
|
36
|
+
learnMorePlacement?: "top-right" | "bottom-right";
|
|
36
37
|
/** @deprecated Local execution is no longer offered from auth pages. */
|
|
37
38
|
runLocalCommand?: string;
|
|
38
39
|
};
|
|
@@ -1281,6 +1281,7 @@ export function getOnboardingHtml(opts = {}) {
|
|
|
1281
1281
|
: undefined,
|
|
1282
1282
|
screenshotWidth: marketing.screenshotWidth,
|
|
1283
1283
|
screenshotHeight: marketing.screenshotHeight,
|
|
1284
|
+
learnMorePlacement: marketing.learnMorePlacement,
|
|
1284
1285
|
learnMoreUrl: marketing.learnMoreUrl ??
|
|
1285
1286
|
(marketingSlug
|
|
1286
1287
|
? `https://agent-native.com/apps/${marketingSlug}`
|
|
@@ -1350,13 +1351,11 @@ export function getOnboardingHtml(opts = {}) {
|
|
|
1350
1351
|
width: 2rem;
|
|
1351
1352
|
height: 2rem;
|
|
1352
1353
|
padding: 0;
|
|
1353
|
-
background:
|
|
1354
|
+
background: transparent;
|
|
1354
1355
|
color: #e5e5e5;
|
|
1355
|
-
border:
|
|
1356
|
-
border-radius: 8px;
|
|
1356
|
+
border: 0;
|
|
1357
1357
|
cursor: pointer;
|
|
1358
1358
|
outline: none;
|
|
1359
|
-
backdrop-filter: blur(12px);
|
|
1360
1359
|
}
|
|
1361
1360
|
.locale-trigger svg {
|
|
1362
1361
|
width: 1rem;
|
|
@@ -1369,11 +1368,10 @@ export function getOnboardingHtml(opts = {}) {
|
|
|
1369
1368
|
}
|
|
1370
1369
|
.locale-trigger:hover,
|
|
1371
1370
|
.locale-trigger[aria-expanded="true"] {
|
|
1372
|
-
border-color:
|
|
1373
|
-
background:
|
|
1371
|
+
border-color: transparent;
|
|
1372
|
+
background: transparent;
|
|
1374
1373
|
}
|
|
1375
1374
|
.locale-trigger:focus {
|
|
1376
|
-
border-color: rgba(255,255,255,0.42);
|
|
1377
1375
|
box-shadow: 0 0 0 3px rgba(255,255,255,0.08);
|
|
1378
1376
|
}
|
|
1379
1377
|
.locale-menu {
|
|
@@ -1940,13 +1938,17 @@ ${embeddedAuthCss}
|
|
|
1940
1938
|
justify-content: flex-end;
|
|
1941
1939
|
align-items: center;
|
|
1942
1940
|
position: absolute;
|
|
1943
|
-
height: 2rem;
|
|
1944
1941
|
padding: 0;
|
|
1945
1942
|
top: max(1rem, env(safe-area-inset-top));
|
|
1946
1943
|
inset-inline-end: calc(max(1rem, env(safe-area-inset-right)) + 2.5rem);
|
|
1947
1944
|
z-index: 2;
|
|
1948
1945
|
}
|
|
1949
|
-
.auth-marketing-learn-more { font-size: 0.
|
|
1946
|
+
.auth-marketing-learn-more { font-size: 0.8rem; }
|
|
1947
|
+
.auth-marketing-home.has-bottom-right-learn-more .auth-marketing-top-right {
|
|
1948
|
+
top: auto;
|
|
1949
|
+
bottom: max(1rem, env(safe-area-inset-bottom));
|
|
1950
|
+
inset-inline-end: max(1rem, env(safe-area-inset-right));
|
|
1951
|
+
}
|
|
1950
1952
|
.auth-marketing-home .auth-marketing-layout {
|
|
1951
1953
|
min-height: 100vh;
|
|
1952
1954
|
display: flex;
|
|
@@ -1981,9 +1983,17 @@ ${embeddedAuthCss}
|
|
|
1981
1983
|
}
|
|
1982
1984
|
.auth-marketing-home .auth-marketing-top-right {
|
|
1983
1985
|
top: max(1rem, env(safe-area-inset-top));
|
|
1986
|
+
bottom: auto;
|
|
1984
1987
|
inset-inline-start: max(1rem, env(safe-area-inset-left));
|
|
1985
1988
|
inset-inline-end: auto;
|
|
1986
1989
|
}
|
|
1990
|
+
.auth-marketing-home.has-bottom-right-learn-more .auth-marketing-top-right {
|
|
1991
|
+
top: auto;
|
|
1992
|
+
bottom: max(1rem, env(safe-area-inset-bottom));
|
|
1993
|
+
inset-inline-start: 50%;
|
|
1994
|
+
inset-inline-end: auto;
|
|
1995
|
+
transform: translateX(-50%);
|
|
1996
|
+
}
|
|
1987
1997
|
.auth-marketing-home .auth-marketing-layout { min-height: auto; }
|
|
1988
1998
|
.auth-marketing-home .auth-marketing-shell { display: block; }
|
|
1989
1999
|
.auth-marketing-home .auth-marketing-shell-with-top-right { display: flex; }
|
|
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
26
26
|
expiresAt?: undefined;
|
|
27
27
|
ttlSeconds?: undefined;
|
|
28
28
|
} | {
|
|
29
|
-
error?: undefined;
|
|
30
29
|
token: string;
|
|
31
30
|
expiresAt: string;
|
|
32
31
|
ttlSeconds: number;
|
|
32
|
+
error?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -10,8 +10,11 @@ export interface CliEntry {
|
|
|
10
10
|
/** Env vars to strip when spawning (prevents nesting) */
|
|
11
11
|
stripEnv: string[];
|
|
12
12
|
}
|
|
13
|
+
export declare function terminalPath(environment?: NodeJS.ProcessEnv): Promise<string>;
|
|
13
14
|
export declare const CLI_REGISTRY: Record<string, CliEntry>;
|
|
14
15
|
/** Check if a command name is in the CLI_REGISTRY allowlist */
|
|
15
16
|
export declare function isAllowedCommand(cmd: string): boolean;
|
|
17
|
+
/** Resolve a CLI from the user's shell locations, including GUI launch paths. */
|
|
18
|
+
export declare function resolveCommandPath(cmd: string, environment?: NodeJS.ProcessEnv): Promise<string | null>;
|
|
16
19
|
/** Check if a CLI command exists on PATH (safe — no shell interpolation) */
|
|
17
20
|
export declare function commandExists(cmd: string): Promise<boolean>;
|
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
* CLI Registry — known AI coding CLIs and their metadata.
|
|
3
3
|
* Used by the embedded terminal in the agent panel.
|
|
4
4
|
*/
|
|
5
|
+
export async function terminalPath(environment = process.env) {
|
|
6
|
+
const [fs, os, path] = await Promise.all([
|
|
7
|
+
import("node:fs"),
|
|
8
|
+
import("node:os"),
|
|
9
|
+
import("node:path"),
|
|
10
|
+
]);
|
|
11
|
+
const home = environment.HOME || os.homedir();
|
|
12
|
+
const nvmDirectories = home
|
|
13
|
+
? (() => {
|
|
14
|
+
try {
|
|
15
|
+
return fs
|
|
16
|
+
.readdirSync(path.join(home, ".nvm", "versions", "node"))
|
|
17
|
+
.map((version) => path.join(home, ".nvm", "versions", "node", version, "bin"));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
if (error.code === "ENOENT")
|
|
21
|
+
return [];
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
})()
|
|
25
|
+
: [];
|
|
26
|
+
const entries = [
|
|
27
|
+
...(environment.PATH ?? environment.Path ?? "")
|
|
28
|
+
.split(path.delimiter)
|
|
29
|
+
.filter(Boolean),
|
|
30
|
+
environment.PNPM_HOME,
|
|
31
|
+
home ? path.join(home, ".local", "bin") : undefined,
|
|
32
|
+
home ? path.join(home, ".local", "share", "pnpm") : undefined,
|
|
33
|
+
home ? path.join(home, "Library", "pnpm") : undefined,
|
|
34
|
+
home ? path.join(home, ".opencode", "bin") : undefined,
|
|
35
|
+
home ? path.join(home, ".cargo", "bin") : undefined,
|
|
36
|
+
...nvmDirectories,
|
|
37
|
+
"/opt/homebrew/bin",
|
|
38
|
+
"/usr/local/bin",
|
|
39
|
+
"/usr/bin",
|
|
40
|
+
"/bin",
|
|
41
|
+
].filter((entry) => Boolean(entry));
|
|
42
|
+
return [...new Set(entries)].join(path.delimiter);
|
|
43
|
+
}
|
|
5
44
|
export const CLI_REGISTRY = {
|
|
6
45
|
claude: {
|
|
7
46
|
label: "Claude Code",
|
|
@@ -38,14 +77,21 @@ export const CLI_REGISTRY = {
|
|
|
38
77
|
export function isAllowedCommand(cmd) {
|
|
39
78
|
return Object.prototype.hasOwnProperty.call(CLI_REGISTRY, cmd);
|
|
40
79
|
}
|
|
80
|
+
/** Resolve a CLI from the user's shell locations, including GUI launch paths. */
|
|
81
|
+
export async function resolveCommandPath(cmd, environment = process.env) {
|
|
82
|
+
const { spawnSync } = await import("node:child_process");
|
|
83
|
+
const resolver = process.platform === "win32" ? "where.exe" : "which";
|
|
84
|
+
const result = spawnSync(resolver, [cmd], {
|
|
85
|
+
encoding: "utf8",
|
|
86
|
+
env: { ...environment, PATH: await terminalPath(environment) },
|
|
87
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
88
|
+
});
|
|
89
|
+
if (result.status !== 0)
|
|
90
|
+
return null;
|
|
91
|
+
const resolved = result.stdout?.trim().split(/\r?\n/)[0];
|
|
92
|
+
return resolved || cmd;
|
|
93
|
+
}
|
|
41
94
|
/** Check if a CLI command exists on PATH (safe — no shell interpolation) */
|
|
42
95
|
export async function commandExists(cmd) {
|
|
43
|
-
|
|
44
|
-
const { spawnSync } = await import("node:child_process");
|
|
45
|
-
const result = spawnSync("which", [cmd], { stdio: "ignore" });
|
|
46
|
-
return result.status === 0;
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
96
|
+
return (await resolveCommandPath(cmd)) !== null;
|
|
51
97
|
}
|
|
@@ -21,9 +21,26 @@ export interface PtyServerOptions {
|
|
|
21
21
|
authCheck?: (req: IncomingMessage) => boolean | Promise<boolean>;
|
|
22
22
|
/** Trusted host arguments appended to each validated CLI command. */
|
|
23
23
|
getCommandArgs?: (command: string) => string[] | Promise<string[]>;
|
|
24
|
+
/** Trusted environment additions for the validated CLI command. */
|
|
25
|
+
getEnvironment?: (command: string) => NodeJS.ProcessEnv | Promise<NodeJS.ProcessEnv>;
|
|
26
|
+
/** Per-connection setup for trusted host capabilities and cleanup. */
|
|
27
|
+
getSessionSetup?: (command: string, context: PtySessionContext | null) => PtySessionSetup | Promise<PtySessionSetup>;
|
|
24
28
|
/** Log prefix for console output. Defaults to '[terminal]' */
|
|
25
29
|
logPrefix?: string;
|
|
26
30
|
}
|
|
31
|
+
export interface PtySessionContext {
|
|
32
|
+
appId: string;
|
|
33
|
+
path?: string;
|
|
34
|
+
view?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface PtySessionSetup {
|
|
37
|
+
/** Trusted arguments appended to the selected CLI command. */
|
|
38
|
+
commandArgs?: string[];
|
|
39
|
+
/** Trusted environment additions for the selected CLI command. */
|
|
40
|
+
environment?: NodeJS.ProcessEnv;
|
|
41
|
+
/** Releases per-connection resources such as scoped MCP relays. */
|
|
42
|
+
onClose?: () => void | Promise<void>;
|
|
43
|
+
}
|
|
27
44
|
export interface PtyServerResult {
|
|
28
45
|
/** The underlying HTTP server */
|
|
29
46
|
server: HttpServer;
|
|
@@ -33,3 +50,8 @@ export interface PtyServerResult {
|
|
|
33
50
|
close: () => void;
|
|
34
51
|
}
|
|
35
52
|
export declare function createPtyWebSocketServer(options?: PtyServerOptions): Promise<PtyServerResult>;
|
|
53
|
+
export declare function preparePtySpawn(commandPath: string, args: string[], platform?: NodeJS.Platform): {
|
|
54
|
+
command: string;
|
|
55
|
+
args: string[];
|
|
56
|
+
};
|
|
57
|
+
export declare function parseTerminalArguments(value: string, platform?: NodeJS.Platform): string[];
|
|
@@ -11,7 +11,7 @@ import * as fs from "node:fs";
|
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import os from "os";
|
|
13
13
|
import path from "path";
|
|
14
|
-
import { CLI_REGISTRY,
|
|
14
|
+
import { CLI_REGISTRY, isAllowedCommand, resolveCommandPath, terminalPath, } from "./cli-registry.js";
|
|
15
15
|
// Lazy singletons for Node-only modules (only available in Node.js)
|
|
16
16
|
let _cp;
|
|
17
17
|
async function getChildProcess() {
|
|
@@ -50,24 +50,6 @@ export function ensurePtySpawnHelperPermissions() {
|
|
|
50
50
|
console.warn("[terminal] Could not verify node-pty spawn-helper permissions:", err.message);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
function resolveTerminalShell() {
|
|
54
|
-
if (os.platform() === "win32") {
|
|
55
|
-
// config-ok: COMSPEC is an inherited host shell fact, not application configuration.
|
|
56
|
-
return process.env["COMSPEC"] || "cmd.exe";
|
|
57
|
-
}
|
|
58
|
-
// config-ok: SHELL is the user's inherited host shell, not application configuration.
|
|
59
|
-
const configuredShell = process.env.SHELL;
|
|
60
|
-
if (configuredShell && path.isAbsolute(configuredShell)) {
|
|
61
|
-
try {
|
|
62
|
-
if (fs.statSync(configuredShell).isFile())
|
|
63
|
-
return configuredShell;
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
// coercion-ok: an invalid SHELL falls through to known system shells.
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return (["/bin/zsh", "/bin/bash", "/bin/sh"].find((candidate) => fs.existsSync(candidate)) ?? "/bin/sh");
|
|
70
|
-
}
|
|
71
53
|
/**
|
|
72
54
|
* Kill a process and all its descendants.
|
|
73
55
|
* node-pty's kill() only sends a signal to the shell, but child processes
|
|
@@ -146,13 +128,12 @@ async function killProcessTree(pid, _logPrefix, killParent, isParentExited = ()
|
|
|
146
128
|
}, 500);
|
|
147
129
|
}
|
|
148
130
|
export async function createPtyWebSocketServer(options = {}) {
|
|
149
|
-
const { appDir = process.cwd(), command: defaultCommand = "claude", port = 0, authCheck, getCommandArgs, logPrefix = "[terminal]", } = options;
|
|
131
|
+
const { appDir = process.cwd(), command: defaultCommand = "claude", port = 0, authCheck, getCommandArgs, getEnvironment, getSessionSetup, logPrefix = "[terminal]", } = options;
|
|
150
132
|
ensurePtySpawnHelperPermissions();
|
|
151
133
|
// Dynamic imports for optional native dependencies
|
|
152
134
|
const { WebSocketServer, WebSocket } = await import("ws");
|
|
153
135
|
const pty = await import("node-pty");
|
|
154
136
|
const resolvedAppDir = path.resolve(appDir);
|
|
155
|
-
const shell = resolveTerminalShell();
|
|
156
137
|
const server = createHttpServer((req, res) => {
|
|
157
138
|
// CORS headers
|
|
158
139
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
@@ -225,6 +206,7 @@ export async function createPtyWebSocketServer(options = {}) {
|
|
|
225
206
|
const url = new URL(req.url || "", `http://${req.headers.host}`);
|
|
226
207
|
const command = url.searchParams.get("command") || defaultCommand;
|
|
227
208
|
const extraFlags = url.searchParams.get("flags") || "";
|
|
209
|
+
const context = readPtySessionContext(url);
|
|
228
210
|
console.log(`${logPrefix} WebSocket connected for command: ${command}`);
|
|
229
211
|
const sendStatus = (status, message) => {
|
|
230
212
|
if (ws.readyState === WebSocket.OPEN) {
|
|
@@ -245,32 +227,42 @@ export async function createPtyWebSocketServer(options = {}) {
|
|
|
245
227
|
ws.close();
|
|
246
228
|
return;
|
|
247
229
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (registry?.installPackage) {
|
|
258
|
-
console.log(`${logPrefix} ${command} CLI not found, will use npx`);
|
|
259
|
-
useNpx = true;
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
sendStatus("not-found", `"${command}" not found on PATH. Please install it manually.`);
|
|
263
|
-
if (ws.readyState === WebSocket.OPEN)
|
|
264
|
-
ws.close();
|
|
230
|
+
let connectionClosed = false;
|
|
231
|
+
const markConnectionClosed = () => {
|
|
232
|
+
connectionClosed = true;
|
|
233
|
+
};
|
|
234
|
+
ws.once("close", markConnectionClosed);
|
|
235
|
+
let sessionSetup;
|
|
236
|
+
let sessionSetupClosed = false;
|
|
237
|
+
const closeSessionSetup = () => {
|
|
238
|
+
if (!sessionSetup || sessionSetupClosed)
|
|
265
239
|
return;
|
|
266
|
-
|
|
267
|
-
|
|
240
|
+
sessionSetupClosed = true;
|
|
241
|
+
void Promise.resolve(sessionSetup.onClose?.()).catch((error) => {
|
|
242
|
+
console.warn(`${logPrefix} Session cleanup failed:`, error);
|
|
243
|
+
});
|
|
244
|
+
};
|
|
268
245
|
let commandArgs = [];
|
|
246
|
+
let commandEnvironment = {};
|
|
269
247
|
try {
|
|
270
|
-
if (
|
|
271
|
-
|
|
248
|
+
if (getSessionSetup) {
|
|
249
|
+
sessionSetup = await getSessionSetup(command, context);
|
|
250
|
+
commandArgs = sessionSetup.commandArgs ?? [];
|
|
251
|
+
commandEnvironment = sessionSetup.environment ?? {};
|
|
252
|
+
}
|
|
253
|
+
if (getCommandArgs) {
|
|
254
|
+
const additionalArgs = await getCommandArgs(command);
|
|
255
|
+
commandArgs = [...commandArgs, ...additionalArgs];
|
|
256
|
+
}
|
|
257
|
+
if (getEnvironment) {
|
|
258
|
+
commandEnvironment = {
|
|
259
|
+
...commandEnvironment,
|
|
260
|
+
...(await getEnvironment(command)),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
272
263
|
}
|
|
273
264
|
catch (error) {
|
|
265
|
+
closeSessionSetup();
|
|
274
266
|
if (closed) {
|
|
275
267
|
ws.close();
|
|
276
268
|
return;
|
|
@@ -282,32 +274,93 @@ export async function createPtyWebSocketServer(options = {}) {
|
|
|
282
274
|
ws.close();
|
|
283
275
|
return;
|
|
284
276
|
}
|
|
285
|
-
if (closed) {
|
|
277
|
+
if (connectionClosed || ws.readyState !== WebSocket.OPEN || closed) {
|
|
278
|
+
closeSessionSetup();
|
|
279
|
+
ws.close();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
let extraArgumentList = [];
|
|
283
|
+
try {
|
|
284
|
+
extraArgumentList = parseTerminalArguments(extraFlags);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
closeSessionSetup();
|
|
288
|
+
sendStatus("failed", error instanceof Error ? error.message : "Invalid terminal flags.");
|
|
289
|
+
if (ws.readyState === WebSocket.OPEN)
|
|
290
|
+
ws.close();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (connectionClosed || ws.readyState !== WebSocket.OPEN || closed) {
|
|
294
|
+
closeSessionSetup();
|
|
286
295
|
ws.close();
|
|
287
296
|
return;
|
|
288
297
|
}
|
|
289
|
-
// Build the command — use npx if CLI not found locally
|
|
290
|
-
const baseCommand = useNpx
|
|
291
|
-
? `npx --yes ${CLI_REGISTRY[command].installPackage}`
|
|
292
|
-
: command;
|
|
293
|
-
const generatedFlags = commandArgs.map(shellQuote).join(" ");
|
|
294
|
-
const fullCommand = [baseCommand, generatedFlags, extraFlags]
|
|
295
|
-
.filter(Boolean)
|
|
296
|
-
.join(" ");
|
|
297
|
-
console.log(`${logPrefix} Spawning PTY for ${command}`);
|
|
298
298
|
// Build env, stripping CLI-specific nesting vars
|
|
299
299
|
const registry = CLI_REGISTRY[command];
|
|
300
300
|
const env = {
|
|
301
301
|
...process.env,
|
|
302
|
+
...commandEnvironment,
|
|
302
303
|
TERM: "xterm-256color",
|
|
303
304
|
};
|
|
304
305
|
if (registry) {
|
|
305
306
|
for (const v of registry.stripEnv)
|
|
306
307
|
delete env[v];
|
|
307
308
|
}
|
|
309
|
+
let commandPath;
|
|
310
|
+
try {
|
|
311
|
+
env.PATH = await terminalPath(env);
|
|
312
|
+
commandPath = await resolveCommandPath(command, env);
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
closeSessionSetup();
|
|
316
|
+
if (closed) {
|
|
317
|
+
ws.close();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
sendStatus("failed", error instanceof Error
|
|
321
|
+
? error.message
|
|
322
|
+
: "The terminal command could not be resolved.");
|
|
323
|
+
if (ws.readyState === WebSocket.OPEN)
|
|
324
|
+
ws.close();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (connectionClosed || ws.readyState !== WebSocket.OPEN || closed) {
|
|
328
|
+
closeSessionSetup();
|
|
329
|
+
ws.close();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
let spawnCommand = commandPath;
|
|
333
|
+
let spawnArgs = [...commandArgs, ...extraArgumentList];
|
|
334
|
+
if (!spawnCommand) {
|
|
335
|
+
if (!registry?.installPackage) {
|
|
336
|
+
closeSessionSetup();
|
|
337
|
+
sendStatus("not-found", `"${command}" not found on PATH. Please install it manually.`);
|
|
338
|
+
if (ws.readyState === WebSocket.OPEN)
|
|
339
|
+
ws.close();
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const npxPath = await resolveCommandPath("npx", env);
|
|
343
|
+
if (connectionClosed || ws.readyState !== WebSocket.OPEN || closed) {
|
|
344
|
+
closeSessionSetup();
|
|
345
|
+
ws.close();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (!npxPath) {
|
|
349
|
+
closeSessionSetup();
|
|
350
|
+
sendStatus("not-found", `"${command}" not found on PATH and npx is unavailable.`);
|
|
351
|
+
if (ws.readyState === WebSocket.OPEN)
|
|
352
|
+
ws.close();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
console.log(`${logPrefix} ${command} CLI not found, will use npx`);
|
|
356
|
+
spawnCommand = npxPath;
|
|
357
|
+
spawnArgs = ["--yes", registry.installPackage, ...spawnArgs];
|
|
358
|
+
}
|
|
359
|
+
console.log(`${logPrefix} Spawning PTY for ${command}`);
|
|
360
|
+
const preparedSpawn = preparePtySpawn(spawnCommand, spawnArgs);
|
|
308
361
|
let ptyProcess;
|
|
309
362
|
try {
|
|
310
|
-
ptyProcess = pty.spawn(
|
|
363
|
+
ptyProcess = pty.spawn(preparedSpawn.command, preparedSpawn.args, {
|
|
311
364
|
name: "xterm-256color",
|
|
312
365
|
cols: 120,
|
|
313
366
|
rows: 40,
|
|
@@ -321,6 +374,7 @@ export async function createPtyWebSocketServer(options = {}) {
|
|
|
321
374
|
sendStatus("failed", `Failed to spawn PTY: ${err instanceof Error ? err.message : String(err)}`);
|
|
322
375
|
ws.close();
|
|
323
376
|
}
|
|
377
|
+
closeSessionSetup();
|
|
324
378
|
return;
|
|
325
379
|
}
|
|
326
380
|
let disposed = false;
|
|
@@ -330,6 +384,7 @@ export async function createPtyWebSocketServer(options = {}) {
|
|
|
330
384
|
return;
|
|
331
385
|
disposed = true;
|
|
332
386
|
activeDisposers.delete(dispose);
|
|
387
|
+
closeSessionSetup();
|
|
333
388
|
const killPty = () => {
|
|
334
389
|
try {
|
|
335
390
|
ptyProcess.kill();
|
|
@@ -435,6 +490,78 @@ export async function createPtyWebSocketServer(options = {}) {
|
|
|
435
490
|
});
|
|
436
491
|
});
|
|
437
492
|
}
|
|
438
|
-
function
|
|
439
|
-
|
|
493
|
+
function readPtySessionContext(url) {
|
|
494
|
+
const appId = url.searchParams.get("appId")?.trim();
|
|
495
|
+
if (!appId)
|
|
496
|
+
return null;
|
|
497
|
+
const pathValue = url.searchParams.get("path")?.trim();
|
|
498
|
+
const view = url.searchParams.get("view")?.trim();
|
|
499
|
+
return {
|
|
500
|
+
appId,
|
|
501
|
+
...(pathValue ? { path: pathValue } : {}),
|
|
502
|
+
...(view ? { view } : {}),
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
export function preparePtySpawn(commandPath, args, platform = process.platform) {
|
|
506
|
+
if (platform !== "win32")
|
|
507
|
+
return { command: commandPath, args };
|
|
508
|
+
const extension = path.extname(commandPath).toLowerCase();
|
|
509
|
+
if (extension !== ".cmd" && extension !== ".bat") {
|
|
510
|
+
return { command: commandPath, args };
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
command: "cmd.exe",
|
|
514
|
+
args: ["/d", "/s", "/c", `"${commandPath}"`, ...args],
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
export function parseTerminalArguments(value, platform = process.platform) {
|
|
518
|
+
if (!value)
|
|
519
|
+
return [];
|
|
520
|
+
const args = [];
|
|
521
|
+
let current = "";
|
|
522
|
+
let quote = null;
|
|
523
|
+
let escaped = false;
|
|
524
|
+
const preserveBackslashes = platform === "win32";
|
|
525
|
+
const pushCurrent = () => {
|
|
526
|
+
if (current)
|
|
527
|
+
args.push(current);
|
|
528
|
+
current = "";
|
|
529
|
+
};
|
|
530
|
+
for (let index = 0; index < value.length; index++) {
|
|
531
|
+
const character = value[index];
|
|
532
|
+
if (escaped) {
|
|
533
|
+
current += character;
|
|
534
|
+
escaped = false;
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
if (character === "\\" && quote !== "'") {
|
|
538
|
+
if (!preserveBackslashes || value[index + 1] === '"') {
|
|
539
|
+
escaped = true;
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
current += character;
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if (quote) {
|
|
546
|
+
if (character === quote)
|
|
547
|
+
quote = null;
|
|
548
|
+
else
|
|
549
|
+
current += character;
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if (character === '"' || character === "'") {
|
|
553
|
+
quote = character;
|
|
554
|
+
}
|
|
555
|
+
else if (/\s/.test(character)) {
|
|
556
|
+
pushCurrent();
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
current += character;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (escaped || quote) {
|
|
563
|
+
throw new Error("Invalid flags: unterminated terminal argument");
|
|
564
|
+
}
|
|
565
|
+
pushCurrent();
|
|
566
|
+
return args;
|
|
440
567
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.176.
|
|
3
|
+
"version": "0.176.4-nightly-20260901193441",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|