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/bin/codex-profile CHANGED
@@ -3,12 +3,13 @@
3
3
  set -euo pipefail
4
4
 
5
5
  PROGRAM="${0##*/}"
6
- VERSION="0.2.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 < CODEX_PROFILE_QUIT_ATTEMPTS; attempt++)); do
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" "$workspace" > "$log_file" 2>&1 &)
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/desktop.log"
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 desktop log for $profile ($log_file)."
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.