codex-profile 0.2.0 → 0.3.0
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/CHANGELOG.md +51 -2
- package/README.md +247 -29
- package/SECURITY.md +5 -0
- package/bin/codex-profile +255 -16
- package/docs/.nojekyll +1 -0
- package/docs/geo-audit.md +67 -0
- package/docs/geo-measurement.md +62 -0
- package/docs/index.html +1136 -0
- package/docs/llms.txt +81 -0
- package/docs/robots.txt +4 -0
- package/docs/sitemap.xml +15 -0
- package/media/codex-profile-parallel-instances.png +0 -0
- package/media/codex-profiles-apple-reveal.mp4 +0 -0
- package/media/codex-profiles-saas-promo-frame.png +0 -0
- package/package.json +6 -3
package/bin/codex-profile
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
|
|
5
5
|
PROGRAM="${0##*/}"
|
|
6
|
-
VERSION="0.
|
|
6
|
+
VERSION="0.3.0"
|
|
7
7
|
CODEX_APP="${CODEX_APP:-/Applications/Codex.app}"
|
|
8
8
|
CODEX_APP_BIN="${CODEX_APP_BIN:-$CODEX_APP/Contents/MacOS/Codex}"
|
|
9
9
|
CODEX_BUNDLED_CLI="${CODEX_BUNDLED_CLI:-$CODEX_APP/Contents/Resources/codex}"
|
|
10
10
|
CODEX_PROFILE_QUIT_ATTEMPTS="${CODEX_PROFILE_QUIT_ATTEMPTS:-30}"
|
|
11
11
|
CODEX_PROFILE_QUIT_SLEEP="${CODEX_PROFILE_QUIT_SLEEP:-0.5}"
|
|
12
|
+
CODEX_PROFILE_APP_INSTANCE_ROOT="${CODEX_PROFILE_APP_INSTANCE_ROOT:-$HOME/Library/Application Support/codex-profile/app-instances}"
|
|
12
13
|
CODEX_PROFILE_UPGRADE_REPO="${CODEX_PROFILE_UPGRADE_REPO:-https://github.com/Ducksss/codex-profiles.git}"
|
|
13
14
|
CODEX_PROFILE_UPGRADE_REF="${CODEX_PROFILE_UPGRADE_REF:-main}"
|
|
14
15
|
|
|
@@ -18,6 +19,7 @@ $PROGRAM - run Codex with isolated CODEX_HOME profiles
|
|
|
18
19
|
|
|
19
20
|
Usage:
|
|
20
21
|
$PROGRAM app <profile> [workspace]
|
|
22
|
+
$PROGRAM app-instance <profile> [--rebuild] [workspace]
|
|
21
23
|
$PROGRAM cli <profile> [codex-args...]
|
|
22
24
|
$PROGRAM login <profile> [codex-login-args...]
|
|
23
25
|
$PROGRAM init <profile>
|
|
@@ -25,7 +27,7 @@ Usage:
|
|
|
25
27
|
$PROGRAM status [profile]
|
|
26
28
|
$PROGRAM status --json [profile]
|
|
27
29
|
$PROGRAM path <profile>
|
|
28
|
-
$PROGRAM logs <profile> [--path|--tail [lines]]
|
|
30
|
+
$PROGRAM logs <profile> [--instance] [--path|--tail [lines]]
|
|
29
31
|
$PROGRAM clone-config <source-profile> <target-profile> [--force]
|
|
30
32
|
$PROGRAM list
|
|
31
33
|
$PROGRAM doctor [--json]
|
|
@@ -37,6 +39,7 @@ Examples:
|
|
|
37
39
|
$PROGRAM login personal
|
|
38
40
|
$PROGRAM init work
|
|
39
41
|
$PROGRAM app personal ~/Dev/my-project
|
|
42
|
+
$PROGRAM app-instance work --rebuild ~/Dev/client-project
|
|
40
43
|
$PROGRAM cli personal exec "review this repo"
|
|
41
44
|
$PROGRAM status
|
|
42
45
|
$PROGRAM logs personal --tail 50
|
|
@@ -51,6 +54,7 @@ Environment:
|
|
|
51
54
|
CODEX_APP Override Codex.app path.
|
|
52
55
|
CODEX_APP_BIN Override Codex Desktop binary path.
|
|
53
56
|
CODEX_CLI Override Codex CLI binary path.
|
|
57
|
+
CODEX_PROFILE_APP_INSTANCE_ROOT Override experimental app-instance clone root.
|
|
54
58
|
CODEX_PROFILE_UPGRADE_REPO Override upgrade repository.
|
|
55
59
|
CODEX_PROFILE_UPGRADE_REF Override upgrade git ref.
|
|
56
60
|
CODEX_PROFILE_UPGRADE_CACHE Override upgrade cache checkout.
|
|
@@ -133,6 +137,12 @@ codex_desktop_running() {
|
|
|
133
137
|
return 1
|
|
134
138
|
}
|
|
135
139
|
|
|
140
|
+
force_quit_codex_app() {
|
|
141
|
+
osascript -e 'tell application "Codex" to quit' > /dev/null 2>&1 || true
|
|
142
|
+
pkill -x Codex > /dev/null 2>&1 || true
|
|
143
|
+
pkill -f "$CODEX_BUNDLED_CLI app-server" > /dev/null 2>&1 || true
|
|
144
|
+
}
|
|
145
|
+
|
|
136
146
|
codex_cli_error_message() {
|
|
137
147
|
if [[ -n "${CODEX_CLI:-}" ]]; then
|
|
138
148
|
printf 'CODEX_CLI is set but not executable: %s\n' "$CODEX_CLI"
|
|
@@ -174,7 +184,10 @@ find_codex_cli() {
|
|
|
174
184
|
}
|
|
175
185
|
|
|
176
186
|
quit_codex_app() {
|
|
177
|
-
local attempt
|
|
187
|
+
local attempt quit_attempts="$CODEX_PROFILE_QUIT_ATTEMPTS"
|
|
188
|
+
|
|
189
|
+
[[ "$quit_attempts" =~ ^[0-9]+$ ]] || quit_attempts=30
|
|
190
|
+
(( quit_attempts < 1 )) && quit_attempts=1
|
|
178
191
|
|
|
179
192
|
if ! codex_desktop_running; then
|
|
180
193
|
return 0
|
|
@@ -183,7 +196,17 @@ quit_codex_app() {
|
|
|
183
196
|
note "Quitting existing Codex app..."
|
|
184
197
|
osascript -e 'tell application "Codex" to quit' > /dev/null 2>&1 || true
|
|
185
198
|
|
|
186
|
-
for ((attempt = 0; attempt <
|
|
199
|
+
for ((attempt = 0; attempt < quit_attempts; attempt++)); do
|
|
200
|
+
if ! codex_desktop_running; then
|
|
201
|
+
return 0
|
|
202
|
+
fi
|
|
203
|
+
sleep "$CODEX_PROFILE_QUIT_SLEEP"
|
|
204
|
+
done
|
|
205
|
+
|
|
206
|
+
note "Codex did not quit cleanly; forcing shutdown..."
|
|
207
|
+
force_quit_codex_app
|
|
208
|
+
|
|
209
|
+
for ((attempt = 0; attempt < quit_attempts; attempt++)); do
|
|
187
210
|
if ! codex_desktop_running; then
|
|
188
211
|
return 0
|
|
189
212
|
fi
|
|
@@ -215,7 +238,210 @@ command_app() {
|
|
|
215
238
|
|
|
216
239
|
note "Launching Codex Desktop with CODEX_HOME=$codex_home"
|
|
217
240
|
note "Log: $log_file"
|
|
218
|
-
(umask 077 && nohup env CODEX_HOME="$codex_home" "$CODEX_APP_BIN"
|
|
241
|
+
(umask 077 && nohup env CODEX_HOME="$codex_home" "$CODEX_APP_BIN" > "$log_file" 2>&1 &)
|
|
242
|
+
|
|
243
|
+
local codex_cli
|
|
244
|
+
if codex_cli="$(try_find_codex_cli)"; then
|
|
245
|
+
(sleep 1 && env CODEX_HOME="$codex_home" "$codex_cli" app "$workspace" > /dev/null 2>&1 &)
|
|
246
|
+
fi
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
bundle_identifier_profile_suffix() {
|
|
250
|
+
local profile="$1"
|
|
251
|
+
local encoded
|
|
252
|
+
|
|
253
|
+
encoded="$(printf '%s' "$profile" | LC_ALL=C od -An -tx1 -v | tr -d '[:space:]')" || die "Cannot encode profile name for app instance bundle identifier."
|
|
254
|
+
printf 'p%s\n' "$encoded"
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
app_instance_bundle_identifier_for_profile() {
|
|
258
|
+
local profile="$1"
|
|
259
|
+
local bundle_suffix
|
|
260
|
+
|
|
261
|
+
bundle_suffix="$(bundle_identifier_profile_suffix "$profile")"
|
|
262
|
+
printf 'com.openai.codex.profile.%s\n' "$bundle_suffix"
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
app_instance_dir_for_profile() {
|
|
266
|
+
local profile="$1"
|
|
267
|
+
|
|
268
|
+
validate_profile "$profile"
|
|
269
|
+
printf '%s/%s\n' "$CODEX_PROFILE_APP_INSTANCE_ROOT" "$profile"
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
app_instance_app_for_profile() {
|
|
273
|
+
local profile="$1"
|
|
274
|
+
local instance_dir
|
|
275
|
+
|
|
276
|
+
instance_dir="$(app_instance_dir_for_profile "$profile")"
|
|
277
|
+
printf '%s/Codex %s.app\n' "$instance_dir" "$profile"
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
patch_app_instance_metadata() {
|
|
281
|
+
local profile="$1"
|
|
282
|
+
local app="$2"
|
|
283
|
+
local plist="$app/Contents/Info.plist"
|
|
284
|
+
local bundle_id display_name
|
|
285
|
+
|
|
286
|
+
[[ -f "$plist" ]] || die "Codex app clone is missing Info.plist: $plist"
|
|
287
|
+
|
|
288
|
+
bundle_id="$(app_instance_bundle_identifier_for_profile "$profile")"
|
|
289
|
+
display_name="Codex $profile"
|
|
290
|
+
|
|
291
|
+
if ! command -v plutil > /dev/null 2>&1; then
|
|
292
|
+
note "Warning: plutil not found; app instance metadata was not patched."
|
|
293
|
+
return 0
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
plutil -replace CFBundleIdentifier -string "$bundle_id" "$plist" > /dev/null || die "Cannot patch app instance bundle identifier."
|
|
297
|
+
plutil -replace CFBundleDisplayName -string "$display_name" "$plist" > /dev/null || die "Cannot patch app instance display name."
|
|
298
|
+
|
|
299
|
+
if command -v codesign > /dev/null 2>&1; then
|
|
300
|
+
codesign --force --deep --sign - "$app" > /dev/null 2>&1 || die "Cannot re-sign app instance: $app"
|
|
301
|
+
else
|
|
302
|
+
note "Warning: codesign not found; patched app instance may not launch on macOS."
|
|
303
|
+
fi
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
app_instance_plist_value() {
|
|
307
|
+
local app="$1"
|
|
308
|
+
local key="$2"
|
|
309
|
+
local plist="$app/Contents/Info.plist"
|
|
310
|
+
|
|
311
|
+
[[ -f "$plist" ]] || return 1
|
|
312
|
+
command -v plutil > /dev/null 2>&1 || return 2
|
|
313
|
+
plutil -extract "$key" raw -o - "$plist" 2> /dev/null
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
app_instance_is_incompatible() {
|
|
317
|
+
local profile="$1"
|
|
318
|
+
local app="$2"
|
|
319
|
+
local plist="$app/Contents/Info.plist"
|
|
320
|
+
local bundle_id bundle_name display_name expected_bundle_id expected_display_name
|
|
321
|
+
|
|
322
|
+
[[ -f "$plist" ]] || return 0
|
|
323
|
+
command -v plutil > /dev/null 2>&1 || return 1
|
|
324
|
+
|
|
325
|
+
bundle_name="$(app_instance_plist_value "$app" CFBundleName)" || return 0
|
|
326
|
+
[[ "$bundle_name" == "Codex" ]] || return 0
|
|
327
|
+
|
|
328
|
+
expected_bundle_id="$(app_instance_bundle_identifier_for_profile "$profile")"
|
|
329
|
+
bundle_id="$(app_instance_plist_value "$app" CFBundleIdentifier)" || return 0
|
|
330
|
+
[[ "$bundle_id" == "$expected_bundle_id" ]] || return 0
|
|
331
|
+
|
|
332
|
+
expected_display_name="Codex $profile"
|
|
333
|
+
display_name="$(app_instance_plist_value "$app" CFBundleDisplayName)" || return 0
|
|
334
|
+
[[ "$display_name" == "$expected_display_name" ]] || return 0
|
|
335
|
+
|
|
336
|
+
return 1
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
create_app_instance_bundle() {
|
|
340
|
+
local profile="$1"
|
|
341
|
+
local instance_dir instance_app tmp_app source_bin
|
|
342
|
+
|
|
343
|
+
[[ -d "$CODEX_APP" ]] || die "Codex.app not found at $CODEX_APP"
|
|
344
|
+
source_bin="$CODEX_APP/Contents/MacOS/Codex"
|
|
345
|
+
[[ -x "$source_bin" ]] || die "Codex Desktop binary not found at $source_bin"
|
|
346
|
+
|
|
347
|
+
ensure_home "$CODEX_PROFILE_APP_INSTANCE_ROOT"
|
|
348
|
+
instance_dir="$(app_instance_dir_for_profile "$profile")"
|
|
349
|
+
ensure_home "$instance_dir"
|
|
350
|
+
instance_app="$(app_instance_app_for_profile "$profile")"
|
|
351
|
+
tmp_app="$instance_dir/.Codex $profile.app.tmp.$$"
|
|
352
|
+
|
|
353
|
+
rm -rf -- "$tmp_app"
|
|
354
|
+
cp -R "$CODEX_APP" "$tmp_app" || die "Cannot copy Codex.app to app instance: $tmp_app"
|
|
355
|
+
patch_app_instance_metadata "$profile" "$tmp_app"
|
|
356
|
+
|
|
357
|
+
if [[ -e "$instance_app" ]]; then
|
|
358
|
+
rm -rf -- "$instance_app" || die "Cannot replace app instance: $instance_app"
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
mv "$tmp_app" "$instance_app" || die "Cannot install app instance: $instance_app"
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
ensure_app_instance_bundle() {
|
|
365
|
+
local profile="$1"
|
|
366
|
+
local rebuild="$2"
|
|
367
|
+
local instance_app instance_bin
|
|
368
|
+
|
|
369
|
+
instance_app="$(app_instance_app_for_profile "$profile")"
|
|
370
|
+
instance_bin="$instance_app/Contents/MacOS/Codex"
|
|
371
|
+
|
|
372
|
+
if [[ "$rebuild" == "yes" ]]; then
|
|
373
|
+
note "Rebuilding app instance for $profile"
|
|
374
|
+
create_app_instance_bundle "$profile"
|
|
375
|
+
return 0
|
|
376
|
+
fi
|
|
377
|
+
|
|
378
|
+
if [[ -x "$instance_bin" ]] && app_instance_is_incompatible "$profile" "$instance_app"; then
|
|
379
|
+
note "Rebuilding app instance for $profile because existing clone is incompatible"
|
|
380
|
+
create_app_instance_bundle "$profile"
|
|
381
|
+
return 0
|
|
382
|
+
fi
|
|
383
|
+
|
|
384
|
+
if [[ ! -x "$instance_bin" ]]; then
|
|
385
|
+
note "Creating app instance for $profile"
|
|
386
|
+
create_app_instance_bundle "$profile"
|
|
387
|
+
fi
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
command_app_instance() {
|
|
391
|
+
local profile="${1:-}"
|
|
392
|
+
[[ -n "$profile" ]] || die "Usage: $PROGRAM app-instance <profile> [--rebuild] [workspace]"
|
|
393
|
+
shift || true
|
|
394
|
+
|
|
395
|
+
local rebuild=no workspace=""
|
|
396
|
+
while [[ "$#" -gt 0 ]]; do
|
|
397
|
+
case "$1" in
|
|
398
|
+
--rebuild)
|
|
399
|
+
rebuild=yes
|
|
400
|
+
;;
|
|
401
|
+
--)
|
|
402
|
+
shift
|
|
403
|
+
[[ "$#" -le 1 ]] || die "Usage: $PROGRAM app-instance <profile> [--rebuild] [workspace]"
|
|
404
|
+
workspace="${1:-}"
|
|
405
|
+
break
|
|
406
|
+
;;
|
|
407
|
+
--*)
|
|
408
|
+
die "Usage: $PROGRAM app-instance <profile> [--rebuild] [workspace]"
|
|
409
|
+
;;
|
|
410
|
+
*)
|
|
411
|
+
[[ -z "$workspace" ]] || die "Usage: $PROGRAM app-instance <profile> [--rebuild] [workspace]"
|
|
412
|
+
workspace="$1"
|
|
413
|
+
;;
|
|
414
|
+
esac
|
|
415
|
+
shift
|
|
416
|
+
done
|
|
417
|
+
|
|
418
|
+
workspace="${workspace:-$PWD}"
|
|
419
|
+
|
|
420
|
+
local codex_home instance_app instance_bin user_data_dir log_dir log_file
|
|
421
|
+
codex_home="$(codex_home_for_profile "$profile")"
|
|
422
|
+
ensure_home "$codex_home"
|
|
423
|
+
ensure_app_instance_bundle "$profile" "$rebuild"
|
|
424
|
+
|
|
425
|
+
instance_app="$(app_instance_app_for_profile "$profile")"
|
|
426
|
+
instance_bin="$instance_app/Contents/MacOS/Codex"
|
|
427
|
+
[[ -x "$instance_bin" ]] || die "Codex app instance binary not found at $instance_bin"
|
|
428
|
+
command -v open > /dev/null 2>&1 || die "macOS open command not found; app-instance is macOS-only."
|
|
429
|
+
|
|
430
|
+
user_data_dir="$codex_home/electron-user-data"
|
|
431
|
+
ensure_home "$user_data_dir"
|
|
432
|
+
|
|
433
|
+
log_dir="$codex_home/logs"
|
|
434
|
+
log_file="$log_dir/desktop-instance.log"
|
|
435
|
+
ensure_home "$log_dir"
|
|
436
|
+
(umask 077 && : > "$log_file")
|
|
437
|
+
chmod 600 "$log_file" 2> /dev/null || true
|
|
438
|
+
|
|
439
|
+
note "Launching experimental Codex Desktop instance for $profile"
|
|
440
|
+
note "CODEX_HOME=$codex_home"
|
|
441
|
+
note "App bundle: $instance_app"
|
|
442
|
+
note "Electron user data: $user_data_dir"
|
|
443
|
+
note "Log: $log_file"
|
|
444
|
+
(umask 077 && open -n --env "CODEX_HOME=$codex_home" --stdout "$log_file" --stderr "$log_file" -a "$instance_app" "$workspace" --args "--user-data-dir=$user_data_dir") || die "Cannot launch app instance: $instance_app"
|
|
219
445
|
}
|
|
220
446
|
|
|
221
447
|
command_cli() {
|
|
@@ -542,14 +768,18 @@ command_path() {
|
|
|
542
768
|
|
|
543
769
|
command_logs() {
|
|
544
770
|
local profile="${1:-}"
|
|
545
|
-
[[ -n "$profile" ]] || die "Usage: $PROGRAM logs <profile> [--path|--tail [lines]]"
|
|
771
|
+
[[ -n "$profile" ]] || die "Usage: $PROGRAM logs <profile> [--instance] [--path|--tail [lines]]"
|
|
546
772
|
shift || true
|
|
547
773
|
|
|
548
774
|
local mode="cat"
|
|
775
|
+
local log_name="desktop.log"
|
|
549
776
|
local lines=50
|
|
550
777
|
|
|
551
778
|
while [[ "$#" -gt 0 ]]; do
|
|
552
779
|
case "$1" in
|
|
780
|
+
--instance)
|
|
781
|
+
log_name="desktop-instance.log"
|
|
782
|
+
;;
|
|
553
783
|
--path)
|
|
554
784
|
mode="path"
|
|
555
785
|
;;
|
|
@@ -564,7 +794,7 @@ command_logs() {
|
|
|
564
794
|
die "Unknown logs option '$1'."
|
|
565
795
|
;;
|
|
566
796
|
*)
|
|
567
|
-
die "Usage: $PROGRAM logs <profile> [--path|--tail [lines]]"
|
|
797
|
+
die "Usage: $PROGRAM logs <profile> [--instance] [--path|--tail [lines]]"
|
|
568
798
|
;;
|
|
569
799
|
esac
|
|
570
800
|
shift
|
|
@@ -572,16 +802,22 @@ command_logs() {
|
|
|
572
802
|
|
|
573
803
|
[[ "$lines" =~ ^[0-9]+$ ]] || die "Tail line count must be a non-negative integer."
|
|
574
804
|
|
|
575
|
-
local codex_home log_file
|
|
805
|
+
local codex_home log_file missing_label
|
|
576
806
|
codex_home="$(codex_home_for_profile "$profile")"
|
|
577
|
-
log_file="$codex_home/logs
|
|
807
|
+
log_file="$codex_home/logs/$log_name"
|
|
808
|
+
missing_label="$log_name"
|
|
809
|
+
if [[ "$log_name" == "desktop.log" ]]; then
|
|
810
|
+
missing_label="desktop log"
|
|
811
|
+
elif [[ "$log_name" == "desktop-instance.log" ]]; then
|
|
812
|
+
missing_label="desktop instance log"
|
|
813
|
+
fi
|
|
578
814
|
|
|
579
815
|
if [[ "$mode" == "path" ]]; then
|
|
580
816
|
printf '%s\n' "$log_file"
|
|
581
817
|
return 0
|
|
582
818
|
fi
|
|
583
819
|
|
|
584
|
-
[[ -f "$log_file" ]] || die "No
|
|
820
|
+
[[ -f "$log_file" ]] || die "No $missing_label for $profile ($log_file)."
|
|
585
821
|
|
|
586
822
|
if [[ "$mode" == "tail" ]]; then
|
|
587
823
|
tail -n "$lines" "$log_file"
|
|
@@ -872,12 +1108,12 @@ _codex_profile()
|
|
|
872
1108
|
command="${COMP_WORDS[1]}"
|
|
873
1109
|
|
|
874
1110
|
if [[ "$COMP_CWORD" -eq 1 ]]; then
|
|
875
|
-
COMPREPLY=( $(compgen -W "app cli login init remove status path logs clone-config list doctor completions upgrade version help" -- "$cur") )
|
|
1111
|
+
COMPREPLY=( $(compgen -W "app app-instance cli login init remove status path logs clone-config list doctor completions upgrade version help" -- "$cur") )
|
|
876
1112
|
return 0
|
|
877
1113
|
fi
|
|
878
1114
|
|
|
879
1115
|
case "$command" in
|
|
880
|
-
app|cli|login|init|remove|status|path|logs)
|
|
1116
|
+
app|app-instance|cli|login|init|remove|status|path|logs)
|
|
881
1117
|
if [[ "$COMP_CWORD" -eq 2 ]]; then
|
|
882
1118
|
profiles="$(codex-profile list 2>/dev/null)"
|
|
883
1119
|
COMPREPLY=( $(compgen -W "default personal work $profiles" -- "$cur") )
|
|
@@ -906,7 +1142,7 @@ EOF
|
|
|
906
1142
|
_codex_profile() {
|
|
907
1143
|
local -a commands profiles shells
|
|
908
1144
|
commands=(
|
|
909
|
-
app cli login init remove status path logs clone-config list doctor completions upgrade version help
|
|
1145
|
+
app app-instance cli login init remove status path logs clone-config list doctor completions upgrade version help
|
|
910
1146
|
)
|
|
911
1147
|
profiles=(${(f)"$(codex-profile list 2>/dev/null)"} default personal work)
|
|
912
1148
|
shells=(bash zsh fish)
|
|
@@ -917,7 +1153,7 @@ _codex_profile() {
|
|
|
917
1153
|
fi
|
|
918
1154
|
|
|
919
1155
|
case "$words[2]" in
|
|
920
|
-
app|cli|login|init|remove|status|path|logs)
|
|
1156
|
+
app|app-instance|cli|login|init|remove|status|path|logs)
|
|
921
1157
|
if (( CURRENT == 3 )); then
|
|
922
1158
|
_describe 'profile' profiles
|
|
923
1159
|
fi
|
|
@@ -941,8 +1177,8 @@ EOF
|
|
|
941
1177
|
fish)
|
|
942
1178
|
cat <<'EOF'
|
|
943
1179
|
complete -c codex-profile -f
|
|
944
|
-
complete -c codex-profile -n '__fish_is_first_arg' -a 'app cli login init remove status path logs clone-config list doctor completions upgrade version help'
|
|
945
|
-
complete -c codex-profile -n '__fish_seen_subcommand_from app cli login init remove status path logs' -a '(codex-profile list 2>/dev/null) default personal work'
|
|
1180
|
+
complete -c codex-profile -n '__fish_is_first_arg' -a 'app app-instance cli login init remove status path logs clone-config list doctor completions upgrade version help'
|
|
1181
|
+
complete -c codex-profile -n '__fish_seen_subcommand_from app app-instance cli login init remove status path logs' -a '(codex-profile list 2>/dev/null) default personal work'
|
|
946
1182
|
complete -c codex-profile -n '__fish_seen_subcommand_from clone-config' -a '(codex-profile list 2>/dev/null) default personal work'
|
|
947
1183
|
complete -c codex-profile -n '__fish_seen_subcommand_from completions' -a 'bash zsh fish'
|
|
948
1184
|
EOF
|
|
@@ -1029,6 +1265,9 @@ main() {
|
|
|
1029
1265
|
app)
|
|
1030
1266
|
command_app "$@"
|
|
1031
1267
|
;;
|
|
1268
|
+
app-instance)
|
|
1269
|
+
command_app_instance "$@"
|
|
1270
|
+
;;
|
|
1032
1271
|
cli)
|
|
1033
1272
|
command_cli "$@"
|
|
1034
1273
|
;;
|
package/docs/.nojekyll
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# GEO Audit for codex-profiles
|
|
2
|
+
|
|
3
|
+
This audit maps the public documentation layer to the GEO checklist supplied
|
|
4
|
+
for the project. The implementation target is a GitHub Pages site served from
|
|
5
|
+
`docs/`.
|
|
6
|
+
|
|
7
|
+
## Technical AI Readiness
|
|
8
|
+
|
|
9
|
+
| Check | Status | Implementation |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| AI bots allowed in robots.txt | Implemented | `docs/robots.txt` uses `User-agent: *` and `Allow: /`. |
|
|
12
|
+
| Priority URLs return 200 | Implemented after Pages deployment | `docs/index.html`, `docs/llms.txt`, and `docs/sitemap.xml` are static files. |
|
|
13
|
+
| Pages are indexable | Implemented | `docs/index.html` uses `index,follow` and does not contain `noindex`. |
|
|
14
|
+
| Canonicals are correct | Implemented | `docs/index.html` canonicalizes to `https://ducksss.github.io/codex-profiles/`. |
|
|
15
|
+
| Snippet settings allow extraction | Implemented | Robots meta uses unrestricted snippet, image, and video preview directives. |
|
|
16
|
+
| XML sitemap is clean | Implemented | `docs/sitemap.xml` lists the canonical page and LLM summary file. |
|
|
17
|
+
|
|
18
|
+
## Structured Data and Machine Understanding
|
|
19
|
+
|
|
20
|
+
| Check | Status | Implementation |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| Organization schema present | Implemented | JSON-LD includes the project publisher and official sameAs links. |
|
|
23
|
+
| Product schema added where relevant | Implemented | JSON-LD includes SoftwareApplication with repository, install, license, version, platform, features, and free offer data. |
|
|
24
|
+
| FAQ schema only when visible | Implemented | Every FAQPage question and answer is visible on `docs/index.html`. |
|
|
25
|
+
| Schema matches visible content | Implemented | The GEO test validates FAQ question and answer text against visible HTML. |
|
|
26
|
+
| Article schema correct on content pages | Not applicable | The current Pages site is a product page, not a blog or article section. |
|
|
27
|
+
| Local schema added where relevant | Not applicable | codex-profiles is a software project with no public local business location. |
|
|
28
|
+
| Schema validation is logged | Implemented | `node test/geo-site-test.mjs` validates JSON-LD parseability and required fields. |
|
|
29
|
+
|
|
30
|
+
## Content Structure and Citation Readiness
|
|
31
|
+
|
|
32
|
+
| Check | Status | Implementation |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| Question-based headings | Implemented | FAQ uses direct question headings. |
|
|
35
|
+
| Direct answer in first 1-3 sentences | Implemented | The first content section defines the product and isolation boundary immediately. |
|
|
36
|
+
| Bullets, tables, and commands | Implemented | The page includes feature cards, install commands, and a citation-ready facts table. |
|
|
37
|
+
| Short paragraphs | Implemented | Sections use concise, extractable paragraphs. |
|
|
38
|
+
| Facts and stats current | Implemented | Version, license, package name, platforms, and URLs match repository metadata as of 2026-06-03. |
|
|
39
|
+
| Clear About content | Implemented | Trust and methodology section states what the tool is, who maintains it, and what it does not claim. |
|
|
40
|
+
|
|
41
|
+
## Entity, Trust, and Brand Authority
|
|
42
|
+
|
|
43
|
+
| Check | Status | Implementation |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| Consistent project name | Implemented | Page, schema, package metadata, and llms.txt use codex-profiles and codex-profile consistently. |
|
|
46
|
+
| Consistent contact paths | Implemented | Official repository, issues, discussion, npm, license, and security links are present. |
|
|
47
|
+
| sameAs links to official profiles | Implemented | Organization schema points to GitHub and npm. |
|
|
48
|
+
| Real policies where advice is given | Implemented | Security boundaries link to the repository security policy and README security model. |
|
|
49
|
+
| Compare proof vs project pages | Implemented | The public page exposes concrete commands, platform limits, and non-claims rather than broad marketing language. |
|
|
50
|
+
|
|
51
|
+
## Measurement, Testing, and Outcomes
|
|
52
|
+
|
|
53
|
+
| Check | Status | Implementation |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| Define target prompt set | Implemented | `docs/geo-measurement.md` contains reusable prompts. |
|
|
56
|
+
| Retest prompts after changes | Implemented | Measurement plan requires baseline and post-change runs. |
|
|
57
|
+
| Track citation count and position | Implemented | Measurement plan includes citation and position columns. |
|
|
58
|
+
| Track cited pages over time | Implemented | Measurement plan records exact cited URLs per prompt. |
|
|
59
|
+
| Capture before/after screenshots | Implemented | Measurement plan includes screenshot evidence paths. |
|
|
60
|
+
| Report KPIs | Implemented | Measurement plan defines visibility, citation, accuracy, and lead/conversion KPIs. |
|
|
61
|
+
|
|
62
|
+
## Validation Commands
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
node test/geo-site-test.mjs
|
|
66
|
+
make test
|
|
67
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# GEO Measurement Plan for codex-profiles
|
|
2
|
+
|
|
3
|
+
Use this plan to retest AI visibility after documentation, metadata, or launch
|
|
4
|
+
changes. Keep raw screenshots or exports outside the published site unless they
|
|
5
|
+
are intentionally public.
|
|
6
|
+
|
|
7
|
+
## Target Prompt Set
|
|
8
|
+
|
|
9
|
+
Run these prompts in the same systems each measurement cycle. Use a clean
|
|
10
|
+
browser or account state where practical.
|
|
11
|
+
|
|
12
|
+
| Prompt ID | Prompt | Expected accurate answer |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| GEO-001 | What is codex-profiles? | A Bash utility for switching Codex CLI and Desktop profiles with isolated CODEX_HOME directories. |
|
|
15
|
+
| GEO-002 | How can I switch between work and personal Codex accounts without copying auth.json? | Use codex-profile to launch Codex with separate CODEX_HOME directories. |
|
|
16
|
+
| GEO-003 | Is codex-profiles an official OpenAI project? | No, it is community-maintained and not affiliated with OpenAI. |
|
|
17
|
+
| GEO-004 | How do I install codex-profiles? | npm install -g codex-profile or brew install Ducksss/tap/codex-profile. |
|
|
18
|
+
| GEO-005 | Does codex-profiles fully isolate OS credentials? | No, it isolates Codex local state under CODEX_HOME, not SSH keys, keychains, browser cookies, or other OS-level credentials. |
|
|
19
|
+
| GEO-006 | Can I run two Codex Desktop profiles at once? | Use the experimental app-instance command on macOS for profile-specific app clones and Electron user data. |
|
|
20
|
+
|
|
21
|
+
## Competitor and Citation Log
|
|
22
|
+
|
|
23
|
+
Record each run in this table or an equivalent spreadsheet.
|
|
24
|
+
|
|
25
|
+
| Date | System | Prompt ID | Answer cited codex-profiles? | Citation position | Cited URLs | Competing tools or pages mentioned | Accuracy notes |
|
|
26
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
27
|
+
| 2026-06-03 | Baseline | GEO-001 | | | | | |
|
|
28
|
+
|
|
29
|
+
## Before and After Evidence
|
|
30
|
+
|
|
31
|
+
For every material documentation change:
|
|
32
|
+
|
|
33
|
+
1. Save a baseline screenshot or export for each target prompt.
|
|
34
|
+
2. Apply the documentation or metadata change.
|
|
35
|
+
3. Wait for the target surface to be crawlable or indexed.
|
|
36
|
+
4. Rerun the same prompts.
|
|
37
|
+
5. Save after screenshots or exports with filenames that include date, system,
|
|
38
|
+
and prompt ID.
|
|
39
|
+
6. Link evidence paths from the measurement log.
|
|
40
|
+
|
|
41
|
+
Suggested private evidence path:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
evidence/geo/YYYY-MM-DD/<system>/<prompt-id>.png
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## KPI Reporting
|
|
48
|
+
|
|
49
|
+
Report these KPIs in launch or release notes when relevant:
|
|
50
|
+
|
|
51
|
+
| KPI | Definition |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| AI visibility rate | Target prompts where codex-profiles appears in the answer or citations divided by total tested prompts. |
|
|
54
|
+
| Citation count | Number of cited URLs pointing to the official project page, GitHub repository, npm package, README, or security policy. |
|
|
55
|
+
| Citation position | First visible position of a codex-profiles citation when the system exposes citation order. |
|
|
56
|
+
| Brand accuracy | Percentage of answers that correctly state package name, command names, affiliation, security boundary, and install commands. |
|
|
57
|
+
| Outcome path | Observable downstream action, such as GitHub visits, npm installs, Homebrew installs, issue creation, or discussion activity. |
|
|
58
|
+
|
|
59
|
+
## Review Cadence
|
|
60
|
+
|
|
61
|
+
Retest after each release, major README change, public listing campaign, or
|
|
62
|
+
GitHub Pages update. If no product changes ship, retest monthly.
|