customer-map-codex-bridge 0.5.1 → 0.5.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/README.md +52 -40
- package/authorization-macos.applescript +35 -0
- package/authorization-windows.ps1 +300 -0
- package/codex-process.mjs +43 -0
- package/index.mjs +663 -616
- package/install-protocol-macos.sh +85 -0
- package/install-protocol-windows.ps1 +43 -0
- package/mail-action.mjs +363 -363
- package/package.json +26 -20
- package/protocol-install.mjs +35 -0
package/README.md
CHANGED
|
@@ -1,52 +1,64 @@
|
|
|
1
|
-
# Customer Map Codex Bridge
|
|
2
|
-
|
|
3
|
-
This local helper connects a user's already-authenticated Codex installation to Customer Map without sending Codex credentials or API keys to Customer Map.
|
|
4
|
-
|
|
5
|
-
## Requirements
|
|
6
|
-
|
|
7
|
-
- Codex CLI installed and already signed in locally (`codex login`).
|
|
8
|
-
- Optional Gmail actions require `gog` v0.11.0+ with the intended Gmail account already authorized locally.
|
|
9
|
-
- Node.js 18+.
|
|
10
|
-
- The Customer Map Agent settings panel must provide a one-time `CMAP-CODEX-*` code.
|
|
11
|
-
|
|
12
|
-
The settings panel reports **Connected** only after both the local Codex app-server and the Customer Map relay WebSocket are online. A claimed code by itself is only a pending binding.
|
|
13
|
-
|
|
14
|
-
## Run
|
|
15
|
-
|
|
1
|
+
# Customer Map Codex Bridge
|
|
2
|
+
|
|
3
|
+
This local helper connects a user's already-authenticated Codex installation to Customer Map without sending Codex credentials or API keys to Customer Map.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Codex CLI installed and already signed in locally (`codex login`).
|
|
8
|
+
- Optional Gmail actions require `gog` v0.11.0+ with the intended Gmail account already authorized locally.
|
|
9
|
+
- Node.js 18+.
|
|
10
|
+
- The Customer Map Agent settings panel must provide a one-time `CMAP-CODEX-*` code.
|
|
11
|
+
|
|
12
|
+
The settings panel reports **Connected** only after both the local Codex app-server and the Customer Map relay WebSocket are online. A claimed code by itself is only a pending binding.
|
|
13
|
+
|
|
14
|
+
## Run
|
|
15
|
+
|
|
16
16
|
```bash
|
|
17
|
-
npx -y customer-map-codex-bridge@0.5.
|
|
17
|
+
npx -y customer-map-codex-bridge@0.5.3 \
|
|
18
18
|
--site https://your-customer-map.example \
|
|
19
19
|
--code CMAP-CODEX-XXXXXXXXXXXX
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
The Bridge automatically reconnects to the Relay after temporary network or Relay interruptions, using exponential backoff from 1 second up to 30 seconds. It keeps the local Codex process and bridge token in memory during retries. A removed connection, an expired token, or a Bridge replaced by a newer Bridge exits instead of retrying.
|
|
25
|
-
|
|
26
|
-
The bridge initializes Codex App Server with native live web search enabled, then explicitly applies a read-only sandbox with no approval prompts and command network access disabled to every new thread and every turn. This lets the assistants search and read public pages without granting model-generated shell commands arbitrary network or file-write access. Credentials stay on the local machine, and thread IDs are stored under `~/.customer-map-codex/state.json` and resumed after Bridge restarts. Task Assistant, Workbench Assistant, and Sales Assistant use separate thread scopes.
|
|
22
|
+
## Click-to-authorize (Windows and macOS)
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
If the protocol handler cannot find `codex`, pass its absolute path explicitly:
|
|
24
|
+
The Customer Map button can open the local authorization window directly after the protocol handler is registered once. Run this one-time command in a terminal on the computer where Codex is installed:
|
|
31
25
|
|
|
32
26
|
```bash
|
|
33
|
-
npx -y customer-map-codex-bridge@0.5.
|
|
34
|
-
--site https://your-customer-map.example \
|
|
35
|
-
--code CMAP-CODEX-XXXXXXXXXXXX \
|
|
36
|
-
--codex /absolute/path/to/codex
|
|
27
|
+
npx -y customer-map-codex-bridge@0.5.3 --install-protocol
|
|
37
28
|
```
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
The installer registers the `customer-map-codex://` URL scheme for the current user and stores the local Codex path. It does not upload credentials or start a Bridge. Return to Customer Map and click **Open Codex authorization**; the native authorization window will appear on Windows or macOS.
|
|
31
|
+
|
|
32
|
+
If the browser still does not open the window, use the **fallback command** shown in Agent settings. This is also the supported path on Linux, where click-to-authorize is not currently registered.
|
|
33
|
+
|
|
34
|
+
Starting a newly authorized Bridge automatically replaces the older Bridge for the same Customer Map account. Clicking **Remove connection** in Customer Map disconnects the running Bridge, so users do not need to find or kill local processes.
|
|
35
|
+
|
|
36
|
+
The Bridge automatically reconnects to the Relay after temporary network or Relay interruptions, using exponential backoff from 1 second up to 30 seconds. It keeps the local Codex process and bridge token in memory during retries. A removed connection, an expired token, or a Bridge replaced by a newer Bridge exits instead of retrying.
|
|
37
|
+
|
|
38
|
+
The bridge initializes Codex App Server with native live web search enabled, then explicitly applies a read-only sandbox with no approval prompts and command network access disabled to every new thread and every turn. This lets the assistants search and read public pages without granting model-generated shell commands arbitrary network or file-write access. Credentials stay on the local machine, and thread IDs are stored under `~/.customer-map-codex/state.json` and resumed after Bridge restarts. Task Assistant, Workbench Assistant, and Sales Assistant use separate thread scopes.
|
|
39
|
+
|
|
40
|
+
When the user explicitly chooses **Save Gmail draft** or **Send email** in Customer Map, the bridge bypasses the Codex turn and executes only a validated, fixed `gog gmail drafts create` or `gog gmail send` argument list. It verifies the content hash and requires a real Gmail message/draft ID before reporting success. Chat cannot turn this into an arbitrary shell or network action.
|
|
41
|
+
|
|
42
|
+
If the protocol handler cannot find `codex`, pass its absolute path explicitly:
|
|
43
|
+
|
|
41
44
|
```bash
|
|
42
|
-
npx -y customer-map-codex-bridge@0.5.
|
|
43
|
-
--site https://your-customer-map.example \
|
|
44
|
-
--code CMAP-CODEX-XXXXXXXXXXXX \
|
|
45
|
-
--
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
The website can then open:
|
|
49
|
-
|
|
50
|
-
```text
|
|
51
|
-
customer-map-codex://connect?site=https%3A%2F%2Fyour-customer-map.example&code=CMAP-CODEX-XXXXXXXXXXXX
|
|
45
|
+
npx -y customer-map-codex-bridge@0.5.3 \
|
|
46
|
+
--site https://your-customer-map.example \
|
|
47
|
+
--code CMAP-CODEX-XXXXXXXXXXXX \
|
|
48
|
+
--codex /absolute/path/to/codex
|
|
52
49
|
```
|
|
50
|
+
|
|
51
|
+
If `gog` is outside the standard Homebrew/system paths, pass it explicitly:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx -y customer-map-codex-bridge@0.5.3 \
|
|
55
|
+
--site https://your-customer-map.example \
|
|
56
|
+
--code CMAP-CODEX-XXXXXXXXXXXX \
|
|
57
|
+
--gog /absolute/path/to/gog
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The website can then open:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
customer-map-codex://connect?site=https%3A%2F%2Fyour-customer-map.example&code=CMAP-CODEX-XXXXXXXXXXXX
|
|
64
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
use framework "Cocoa"
|
|
2
|
+
use scripting additions
|
|
3
|
+
|
|
4
|
+
on run argv
|
|
5
|
+
if (count of argv) is less than 1 then return "ERROR"
|
|
6
|
+
|
|
7
|
+
set siteUrl to item 1 of argv
|
|
8
|
+
set alert to current application's NSAlert's alloc()'s init()
|
|
9
|
+
alert's setMessageText:"Connect Customer Map to Codex"
|
|
10
|
+
alert's setInformativeText:("Use the Codex already signed in on this Mac." & return & return & ¬
|
|
11
|
+
"Read-only Codex conversations and public-web search are available for your sales tasks." & return & ¬
|
|
12
|
+
"File writes and model-generated command networking remain blocked." & return & ¬
|
|
13
|
+
"Only explicit Gmail draft or send actions may use your local gog installation.")
|
|
14
|
+
alert's setAlertStyle:(current application's NSInformationalAlertStyle)
|
|
15
|
+
|
|
16
|
+
set siteField to current application's NSTextField's alloc()'s initWithFrame:{{0, 0}, {360, 28}}
|
|
17
|
+
siteField's setStringValue:siteUrl
|
|
18
|
+
siteField's setEditable:false
|
|
19
|
+
siteField's setSelectable:true
|
|
20
|
+
siteField's setBezeled:true
|
|
21
|
+
siteField's setDrawsBackground:true
|
|
22
|
+
siteField's setBackgroundColor:(current application's NSColor's colorWithCalibratedWhite:0.96 alpha:1.0)
|
|
23
|
+
siteField's setTextColor:(current application's NSColor's colorWithCalibratedWhite:0.22 alpha:1.0)
|
|
24
|
+
siteField's setFont:(current application's NSFont's monospacedSystemFontOfSize:11 weight:(current application's NSFontWeightRegular))
|
|
25
|
+
siteField's setLineBreakMode:(current application's NSLineBreakByTruncatingMiddle)
|
|
26
|
+
alert's setAccessoryView:siteField
|
|
27
|
+
|
|
28
|
+
alert's addButtonWithTitle:"Cancel"
|
|
29
|
+
alert's addButtonWithTitle:"Allow connection"
|
|
30
|
+
|
|
31
|
+
current application's NSApp's activateIgnoringOtherApps:true
|
|
32
|
+
set response to alert's runModal()
|
|
33
|
+
if response is (current application's NSAlertSecondButtonReturn) then return "ALLOW"
|
|
34
|
+
return "CANCEL"
|
|
35
|
+
end run
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(Mandatory = $true)]
|
|
3
|
+
[string]$Site,
|
|
4
|
+
[switch]$TestMode,
|
|
5
|
+
[string]$RenderPath = ''
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
$ErrorActionPreference = 'Stop'
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
Add-Type -AssemblyName PresentationFramework
|
|
12
|
+
Add-Type -AssemblyName PresentationCore
|
|
13
|
+
Add-Type -AssemblyName WindowsBase
|
|
14
|
+
|
|
15
|
+
[xml]$xaml = @'
|
|
16
|
+
<Window
|
|
17
|
+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
18
|
+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
19
|
+
Title="Customer Map - Codex authorization"
|
|
20
|
+
Width="560"
|
|
21
|
+
Height="540"
|
|
22
|
+
WindowStartupLocation="CenterScreen"
|
|
23
|
+
ResizeMode="NoResize"
|
|
24
|
+
ShowActivated="True"
|
|
25
|
+
Topmost="True"
|
|
26
|
+
Background="#F6F5F2"
|
|
27
|
+
FontFamily="Segoe UI"
|
|
28
|
+
UseLayoutRounding="True"
|
|
29
|
+
SnapsToDevicePixels="True">
|
|
30
|
+
<Window.Resources>
|
|
31
|
+
<Style x:Key="QuietButton" TargetType="Button">
|
|
32
|
+
<Setter Property="MinWidth" Value="104"/>
|
|
33
|
+
<Setter Property="Height" Value="38"/>
|
|
34
|
+
<Setter Property="Padding" Value="18,0"/>
|
|
35
|
+
<Setter Property="FontSize" Value="13"/>
|
|
36
|
+
<Setter Property="FontWeight" Value="SemiBold"/>
|
|
37
|
+
<Setter Property="Cursor" Value="Hand"/>
|
|
38
|
+
<Setter Property="Template">
|
|
39
|
+
<Setter.Value>
|
|
40
|
+
<ControlTemplate TargetType="Button">
|
|
41
|
+
<Border
|
|
42
|
+
x:Name="ButtonSurface"
|
|
43
|
+
Background="{TemplateBinding Background}"
|
|
44
|
+
BorderBrush="{TemplateBinding BorderBrush}"
|
|
45
|
+
BorderThickness="1"
|
|
46
|
+
CornerRadius="6"
|
|
47
|
+
RenderTransformOrigin="0.5,0.5">
|
|
48
|
+
<Border.RenderTransform>
|
|
49
|
+
<ScaleTransform ScaleX="1" ScaleY="1"/>
|
|
50
|
+
</Border.RenderTransform>
|
|
51
|
+
<ContentPresenter
|
|
52
|
+
HorizontalAlignment="Center"
|
|
53
|
+
VerticalAlignment="Center"
|
|
54
|
+
RecognizesAccessKey="True"/>
|
|
55
|
+
</Border>
|
|
56
|
+
<ControlTemplate.Triggers>
|
|
57
|
+
<Trigger Property="IsMouseOver" Value="True">
|
|
58
|
+
<Setter TargetName="ButtonSurface" Property="Opacity" Value="0.9"/>
|
|
59
|
+
</Trigger>
|
|
60
|
+
<Trigger Property="IsPressed" Value="True">
|
|
61
|
+
<Setter TargetName="ButtonSurface" Property="Opacity" Value="0.82"/>
|
|
62
|
+
<Setter TargetName="ButtonSurface" Property="RenderTransform">
|
|
63
|
+
<Setter.Value>
|
|
64
|
+
<ScaleTransform ScaleX="0.98" ScaleY="0.98"/>
|
|
65
|
+
</Setter.Value>
|
|
66
|
+
</Setter>
|
|
67
|
+
</Trigger>
|
|
68
|
+
<Trigger Property="IsKeyboardFocused" Value="True">
|
|
69
|
+
<Setter TargetName="ButtonSurface" Property="BorderBrush" Value="#315F7D"/>
|
|
70
|
+
<Setter TargetName="ButtonSurface" Property="BorderThickness" Value="2"/>
|
|
71
|
+
</Trigger>
|
|
72
|
+
<Trigger Property="IsEnabled" Value="False">
|
|
73
|
+
<Setter TargetName="ButtonSurface" Property="Opacity" Value="0.45"/>
|
|
74
|
+
</Trigger>
|
|
75
|
+
</ControlTemplate.Triggers>
|
|
76
|
+
</ControlTemplate>
|
|
77
|
+
</Setter.Value>
|
|
78
|
+
</Setter>
|
|
79
|
+
</Style>
|
|
80
|
+
<Style x:Key="PrimaryButton" TargetType="Button" BasedOn="{StaticResource QuietButton}">
|
|
81
|
+
<Setter Property="MinWidth" Value="124"/>
|
|
82
|
+
<Setter Property="Background" Value="#1F2328"/>
|
|
83
|
+
<Setter Property="BorderBrush" Value="#1F2328"/>
|
|
84
|
+
<Setter Property="Foreground" Value="#FFFFFF"/>
|
|
85
|
+
</Style>
|
|
86
|
+
<Style x:Key="SecondaryButton" TargetType="Button" BasedOn="{StaticResource QuietButton}">
|
|
87
|
+
<Setter Property="Background" Value="#FFFFFF"/>
|
|
88
|
+
<Setter Property="BorderBrush" Value="#B7C8D1"/>
|
|
89
|
+
<Setter Property="Foreground" Value="#1F2328"/>
|
|
90
|
+
</Style>
|
|
91
|
+
</Window.Resources>
|
|
92
|
+
|
|
93
|
+
<Grid Margin="12">
|
|
94
|
+
<Border
|
|
95
|
+
Background="#FFFFFF"
|
|
96
|
+
BorderBrush="#E2E8F0"
|
|
97
|
+
BorderThickness="1"
|
|
98
|
+
CornerRadius="8">
|
|
99
|
+
<Grid>
|
|
100
|
+
<Grid.RowDefinitions>
|
|
101
|
+
<RowDefinition Height="3"/>
|
|
102
|
+
<RowDefinition Height="Auto"/>
|
|
103
|
+
<RowDefinition Height="*"/>
|
|
104
|
+
<RowDefinition Height="68"/>
|
|
105
|
+
</Grid.RowDefinitions>
|
|
106
|
+
|
|
107
|
+
<Border Grid.Row="0" Background="#315F7D" CornerRadius="8,8,0,0"/>
|
|
108
|
+
|
|
109
|
+
<Grid Grid.Row="1" Margin="22,18,22,14">
|
|
110
|
+
<Grid.ColumnDefinitions>
|
|
111
|
+
<ColumnDefinition Width="42"/>
|
|
112
|
+
<ColumnDefinition Width="*"/>
|
|
113
|
+
</Grid.ColumnDefinitions>
|
|
114
|
+
<Border
|
|
115
|
+
Width="38"
|
|
116
|
+
Height="38"
|
|
117
|
+
HorizontalAlignment="Left"
|
|
118
|
+
VerticalAlignment="Top"
|
|
119
|
+
Background="#DBE8F0"
|
|
120
|
+
CornerRadius="6">
|
|
121
|
+
<TextBlock
|
|
122
|
+
Text="CM"
|
|
123
|
+
HorizontalAlignment="Center"
|
|
124
|
+
VerticalAlignment="Center"
|
|
125
|
+
Foreground="#1F2328"
|
|
126
|
+
FontSize="12"
|
|
127
|
+
FontWeight="Bold"/>
|
|
128
|
+
</Border>
|
|
129
|
+
<StackPanel Grid.Column="1" Margin="12,0,0,0">
|
|
130
|
+
<TextBlock
|
|
131
|
+
Text="Connect Customer Map to Codex"
|
|
132
|
+
Foreground="#1F2328"
|
|
133
|
+
FontSize="16"
|
|
134
|
+
FontWeight="SemiBold"/>
|
|
135
|
+
<TextBlock
|
|
136
|
+
Margin="0,4,0,0"
|
|
137
|
+
Text="Use the Codex already signed in on this computer."
|
|
138
|
+
Foreground="#667085"
|
|
139
|
+
FontSize="12.5"/>
|
|
140
|
+
</StackPanel>
|
|
141
|
+
</Grid>
|
|
142
|
+
|
|
143
|
+
<StackPanel Grid.Row="2" Margin="22,0,22,18">
|
|
144
|
+
<TextBlock
|
|
145
|
+
Text="REQUESTING WEBSITE"
|
|
146
|
+
Foreground="#667085"
|
|
147
|
+
FontSize="10.5"
|
|
148
|
+
FontWeight="SemiBold"/>
|
|
149
|
+
<Border
|
|
150
|
+
Margin="0,7,0,16"
|
|
151
|
+
Padding="12,10"
|
|
152
|
+
Background="#FBFAF8"
|
|
153
|
+
BorderBrush="#E2E8F0"
|
|
154
|
+
BorderThickness="1"
|
|
155
|
+
CornerRadius="6">
|
|
156
|
+
<TextBlock
|
|
157
|
+
x:Name="SiteText"
|
|
158
|
+
Foreground="#1F2328"
|
|
159
|
+
FontFamily="Consolas"
|
|
160
|
+
FontSize="12"
|
|
161
|
+
TextTrimming="CharacterEllipsis"/>
|
|
162
|
+
</Border>
|
|
163
|
+
|
|
164
|
+
<TextBlock
|
|
165
|
+
Text="WHAT THIS ALLOWS"
|
|
166
|
+
Foreground="#667085"
|
|
167
|
+
FontSize="10.5"
|
|
168
|
+
FontWeight="SemiBold"/>
|
|
169
|
+
|
|
170
|
+
<Grid Margin="0,9,0,0">
|
|
171
|
+
<Grid.ColumnDefinitions>
|
|
172
|
+
<ColumnDefinition Width="18"/>
|
|
173
|
+
<ColumnDefinition Width="*"/>
|
|
174
|
+
</Grid.ColumnDefinitions>
|
|
175
|
+
<Ellipse Width="7" Height="7" Fill="#315F7D" VerticalAlignment="Top" Margin="2,5,0,0"/>
|
|
176
|
+
<StackPanel Grid.Column="1">
|
|
177
|
+
<TextBlock Text="Read-only Codex conversations" Foreground="#1F2328" FontSize="12.5" FontWeight="SemiBold"/>
|
|
178
|
+
<TextBlock Text="File writes and model-generated command networking stay blocked." Foreground="#667085" FontSize="11.5" Margin="0,2,0,0"/>
|
|
179
|
+
</StackPanel>
|
|
180
|
+
</Grid>
|
|
181
|
+
|
|
182
|
+
<Grid Margin="0,11,0,0">
|
|
183
|
+
<Grid.ColumnDefinitions>
|
|
184
|
+
<ColumnDefinition Width="18"/>
|
|
185
|
+
<ColumnDefinition Width="*"/>
|
|
186
|
+
</Grid.ColumnDefinitions>
|
|
187
|
+
<Ellipse Width="7" Height="7" Fill="#315F7D" VerticalAlignment="Top" Margin="2,5,0,0"/>
|
|
188
|
+
<StackPanel Grid.Column="1">
|
|
189
|
+
<TextBlock Text="Live public-web search" Foreground="#1F2328" FontSize="12.5" FontWeight="SemiBold"/>
|
|
190
|
+
<TextBlock Text="Codex may search and read public websites for your sales tasks." Foreground="#667085" FontSize="11.5" Margin="0,2,0,0"/>
|
|
191
|
+
</StackPanel>
|
|
192
|
+
</Grid>
|
|
193
|
+
|
|
194
|
+
<Grid Margin="0,11,0,0">
|
|
195
|
+
<Grid.ColumnDefinitions>
|
|
196
|
+
<ColumnDefinition Width="18"/>
|
|
197
|
+
<ColumnDefinition Width="*"/>
|
|
198
|
+
</Grid.ColumnDefinitions>
|
|
199
|
+
<Ellipse Width="7" Height="7" Fill="#315F7D" VerticalAlignment="Top" Margin="2,5,0,0"/>
|
|
200
|
+
<StackPanel Grid.Column="1">
|
|
201
|
+
<TextBlock Text="Local credentials stay local" Foreground="#1F2328" FontSize="12.5" FontWeight="SemiBold"/>
|
|
202
|
+
<TextBlock Text="Customer Map never receives your Codex login or API keys." Foreground="#667085" FontSize="11.5" Margin="0,2,0,0"/>
|
|
203
|
+
</StackPanel>
|
|
204
|
+
</Grid>
|
|
205
|
+
|
|
206
|
+
<Border
|
|
207
|
+
Margin="0,16,0,0"
|
|
208
|
+
Padding="11,9"
|
|
209
|
+
Background="#E4EBE2"
|
|
210
|
+
CornerRadius="6">
|
|
211
|
+
<TextBlock
|
|
212
|
+
Text="Only explicit Gmail draft or send actions may use your local gog installation."
|
|
213
|
+
Foreground="#33413A"
|
|
214
|
+
FontSize="11.5"
|
|
215
|
+
TextWrapping="Wrap"/>
|
|
216
|
+
</Border>
|
|
217
|
+
</StackPanel>
|
|
218
|
+
|
|
219
|
+
<Border
|
|
220
|
+
Grid.Row="3"
|
|
221
|
+
Background="#FBFAF8"
|
|
222
|
+
BorderBrush="#E2E8F0"
|
|
223
|
+
BorderThickness="0,1,0,0"
|
|
224
|
+
CornerRadius="0,0,8,8">
|
|
225
|
+
<Grid Margin="22,14">
|
|
226
|
+
<Grid.ColumnDefinitions>
|
|
227
|
+
<ColumnDefinition Width="*"/>
|
|
228
|
+
<ColumnDefinition Width="Auto"/>
|
|
229
|
+
<ColumnDefinition Width="10"/>
|
|
230
|
+
<ColumnDefinition Width="Auto"/>
|
|
231
|
+
</Grid.ColumnDefinitions>
|
|
232
|
+
<TextBlock
|
|
233
|
+
VerticalAlignment="Center"
|
|
234
|
+
Text="Manage this connection in Customer Map settings."
|
|
235
|
+
TextTrimming="CharacterEllipsis"
|
|
236
|
+
Foreground="#667085"
|
|
237
|
+
FontSize="10.5"/>
|
|
238
|
+
<Button
|
|
239
|
+
x:Name="CancelButton"
|
|
240
|
+
Grid.Column="1"
|
|
241
|
+
Content="Cancel"
|
|
242
|
+
IsCancel="True"
|
|
243
|
+
Style="{StaticResource SecondaryButton}"/>
|
|
244
|
+
<Button
|
|
245
|
+
x:Name="AllowButton"
|
|
246
|
+
Grid.Column="3"
|
|
247
|
+
Content="Allow connection"
|
|
248
|
+
IsDefault="True"
|
|
249
|
+
Style="{StaticResource PrimaryButton}"/>
|
|
250
|
+
</Grid>
|
|
251
|
+
</Border>
|
|
252
|
+
</Grid>
|
|
253
|
+
</Border>
|
|
254
|
+
</Grid>
|
|
255
|
+
</Window>
|
|
256
|
+
'@
|
|
257
|
+
|
|
258
|
+
$reader = New-Object System.Xml.XmlNodeReader $xaml
|
|
259
|
+
$window = [Windows.Markup.XamlReader]::Load($reader)
|
|
260
|
+
$window.FindName('SiteText').Text = $Site
|
|
261
|
+
|
|
262
|
+
if ($TestMode) {
|
|
263
|
+
if ($RenderPath) {
|
|
264
|
+
$window.WindowStartupLocation = 'Manual'
|
|
265
|
+
$window.Left = -10000
|
|
266
|
+
$window.Top = -10000
|
|
267
|
+
$window.ShowInTaskbar = $false
|
|
268
|
+
$window.Show()
|
|
269
|
+
$window.UpdateLayout()
|
|
270
|
+
|
|
271
|
+
$width = [Math]::Max(1, [int][Math]::Ceiling($window.ActualWidth))
|
|
272
|
+
$height = [Math]::Max(1, [int][Math]::Ceiling($window.ActualHeight))
|
|
273
|
+
$bitmap = New-Object Windows.Media.Imaging.RenderTargetBitmap($width, $height, 96, 96, [Windows.Media.PixelFormats]::Pbgra32)
|
|
274
|
+
$bitmap.Render($window)
|
|
275
|
+
$encoder = New-Object Windows.Media.Imaging.PngBitmapEncoder
|
|
276
|
+
$encoder.Frames.Add([Windows.Media.Imaging.BitmapFrame]::Create($bitmap))
|
|
277
|
+
$stream = [IO.File]::Open($RenderPath, [IO.FileMode]::Create)
|
|
278
|
+
try { $encoder.Save($stream) } finally { $stream.Dispose() }
|
|
279
|
+
$window.Close()
|
|
280
|
+
}
|
|
281
|
+
exit 0
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
$script:approved = $false
|
|
285
|
+
[void]$window.FindName('AllowButton').Focus()
|
|
286
|
+
$window.FindName('AllowButton').Add_Click({
|
|
287
|
+
$script:approved = $true
|
|
288
|
+
$window.Close()
|
|
289
|
+
})
|
|
290
|
+
$window.FindName('CancelButton').Add_Click({
|
|
291
|
+
$window.Close()
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
[void]$window.ShowDialog()
|
|
295
|
+
if ($script:approved) { exit 0 }
|
|
296
|
+
exit 1
|
|
297
|
+
} catch {
|
|
298
|
+
Write-Error $_
|
|
299
|
+
exit 2
|
|
300
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { delimiter, dirname } from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
|
|
5
|
+
export const CODEX_APP_SERVER_ARGS = [
|
|
6
|
+
'app-server',
|
|
7
|
+
'--listen',
|
|
8
|
+
'stdio://',
|
|
9
|
+
'-c',
|
|
10
|
+
'web_search="live"',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export function startCodexAppServer(cwd, command) {
|
|
14
|
+
const pathValue = [dirname(process.execPath), dirname(command), process.env.PATH || '']
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.join(delimiter);
|
|
17
|
+
const options = {
|
|
18
|
+
cwd,
|
|
19
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
20
|
+
windowsHide: true,
|
|
21
|
+
env: { ...process.env, PATH: pathValue },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (process.platform !== 'win32') return spawn(command, CODEX_APP_SERVER_ARGS, options);
|
|
25
|
+
|
|
26
|
+
const commandShell = process.env.ComSpec || process.env.COMSPEC || 'C:\\Windows\\System32\\cmd.exe';
|
|
27
|
+
// cmd.exe needs one outer pair of quotes around the complete /c command.
|
|
28
|
+
// windowsVerbatimArguments keeps Node from escaping those quotes.
|
|
29
|
+
const commandLine = buildWindowsCodexCommandLine(command, CODEX_APP_SERVER_ARGS);
|
|
30
|
+
return spawn(commandShell, ['/d', '/s', '/c', commandLine], {
|
|
31
|
+
...options,
|
|
32
|
+
windowsVerbatimArguments: true,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function buildWindowsCodexCommandLine(command, args = CODEX_APP_SERVER_ARGS) {
|
|
37
|
+
return '"' + [command, ...args].map(quoteWindowsArg).join(' ') + '"';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function quoteWindowsArg(value) {
|
|
41
|
+
// Within a cmd.exe command, embedded quotes are escaped by doubling them.
|
|
42
|
+
return '"' + String(value).replaceAll('"', '""') + '"';
|
|
43
|
+
}
|