codexteer 0.16.0 → 0.18.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.
Files changed (85) hide show
  1. package/CHANGELOG.md +37 -2
  2. package/README.md +154 -16
  3. package/assets/remote-device-key.swift +94 -0
  4. package/bin/codexteer-update.mjs +3 -0
  5. package/bin/codexteer.mjs +6 -2
  6. package/docs/cloud.md +113 -0
  7. package/docs/desktop-cloud.md +36 -0
  8. package/docs/distribution.md +21 -9
  9. package/docs/orchestration.md +120 -0
  10. package/docs/remote-cli-validation.json +139 -0
  11. package/docs/remote-control.md +65 -0
  12. package/docs/remote-device-enrollment.md +91 -0
  13. package/docs/remote-observation.md +57 -0
  14. package/docs/remote-operations.md +89 -0
  15. package/docs/thread-create.md +90 -0
  16. package/package.json +13 -2
  17. package/scripts/update-desktop.applescript +51 -0
  18. package/src/agent-launch.mjs +27 -0
  19. package/src/app-server.mjs +34 -10
  20. package/src/checkpoint.mjs +52 -16
  21. package/src/cli.mjs +233 -19
  22. package/src/cloud-api.mjs +118 -0
  23. package/src/cloud-cli.mjs +319 -0
  24. package/src/cloud-connection.mjs +64 -0
  25. package/src/cloud-id.mjs +16 -0
  26. package/src/cloud-observe.mjs +178 -0
  27. package/src/cloud-send.mjs +33 -0
  28. package/src/cloud.mjs +172 -0
  29. package/src/connection.mjs +23 -5
  30. package/src/creation-verification.mjs +36 -0
  31. package/src/desktop-cloud-api.mjs +119 -0
  32. package/src/desktop-cloud-id.mjs +18 -0
  33. package/src/desktop-cloud-project.mjs +148 -0
  34. package/src/desktop-cloud.mjs +98 -0
  35. package/src/desktop.mjs +3 -1
  36. package/src/directive.mjs +5 -2
  37. package/src/distribution.mjs +6 -6
  38. package/src/evidence.mjs +3 -0
  39. package/src/findings.mjs +7 -7
  40. package/src/help.mjs +155 -26
  41. package/src/journal.mjs +37 -9
  42. package/src/launcher.mjs +16 -9
  43. package/src/monitor.mjs +18 -6
  44. package/src/native-command.mjs +100 -0
  45. package/src/native-rpc.mjs +116 -0
  46. package/src/native-worker.mjs +25 -0
  47. package/src/observe.mjs +21 -7
  48. package/src/orchestrate-prompt.mjs +96 -0
  49. package/src/orchestrate.mjs +29 -0
  50. package/src/paged-read.mjs +7 -1
  51. package/src/projects.mjs +104 -0
  52. package/src/prompt-command.mjs +25 -0
  53. package/src/prompt.mjs +35 -11
  54. package/src/remote-client-info.mjs +16 -0
  55. package/src/remote-control-auth.mjs +109 -0
  56. package/src/remote-control-socket.mjs +137 -0
  57. package/src/remote-control.mjs +134 -0
  58. package/src/remote-device-key.mjs +41 -0
  59. package/src/remote-device-login.mjs +76 -0
  60. package/src/remote-device-protocol.mjs +72 -0
  61. package/src/remote-device-store.mjs +71 -0
  62. package/src/remote-device-worker.mjs +48 -0
  63. package/src/remote-device.mjs +202 -0
  64. package/src/remote-io.mjs +163 -0
  65. package/src/remote-key-access.mjs +76 -0
  66. package/src/remote-key-worker.mjs +38 -0
  67. package/src/remote-keychain.mjs +72 -0
  68. package/src/remote-management.mjs +234 -0
  69. package/src/remote.mjs +132 -0
  70. package/src/resource.mjs +8 -6
  71. package/src/runner.mjs +6 -1
  72. package/src/runtime.mjs +4 -2
  73. package/src/socket-endpoint.mjs +40 -0
  74. package/src/standalone-create.mjs +135 -0
  75. package/src/store.mjs +6 -3
  76. package/src/supervise.mjs +32 -55
  77. package/src/supervision.mjs +41 -19
  78. package/src/target.mjs +25 -0
  79. package/src/task-id.mjs +23 -0
  80. package/src/thread-create.mjs +128 -0
  81. package/src/thread-id.mjs +21 -6
  82. package/src/thread-verify.mjs +108 -0
  83. package/src/turn-repair.mjs +228 -0
  84. package/src/update.mjs +158 -0
  85. package/src/wrapper.mjs +68 -5
package/CHANGELOG.md CHANGED
@@ -1,10 +1,45 @@
1
1
  # Changelog
2
2
 
3
- ## 0.16.0 — 未公開
3
+ ## Unreleased
4
4
 
5
+ - `thread repair <THREAD>`で古いターンの終了記録欠落を検出します。明示的な`--apply`はCodexの完全終了・検証済みバージョンを必須とし、バックアップ後に対象IDの中断記録だけを追記・照合します。既存の会話やDBは書き換えず、最新の未完了ターンは修復しません。
6
+ - macOS向けに`desktop update`と`desktop update-status`を追加しました。独立したworkerが公式更新UI(英語・日本語)を操作し、新ビルドと再起動を確認してから共有wrapperを再適用・検証します。外部NodeとターミナルのAccessibility・Automation権限が必要です。
7
+
8
+ - native/control/pairedのリモート経路、専用端末鍵の登録・ペアリング、ホストを保持する観測・監督に対応しました。
9
+ - リモートのプロジェクト・タスク一覧、指定ターンへの送信、Desktopによる作成・new-turn、checkpoint/resource実行とorchestrationを追加しました。
10
+ - 配送・実行結果が不明な場合は自動再送せず、pause/stopと指摘の重複防止を維持します。full-accessのthread-shellは明示選択が必要です。
11
+ - 準備後の監督付きシェル送信をpause/stopと排他し、RPC応答後に制御ロックを解放します。help・文書も実装に揃えました。
12
+ - リモート操作の手順と検証の集計を追加しました。リモートのUI送信・ホストのプロセス管理・履歴修復は未対応です。
13
+ - POSIXパスの大文字小文字を保持し、CLIの宛先はオプション値ではなく位置引数から選択します。監督の制御は登録時のtransportと一致する場合だけ受け付けます。
14
+ - リモート作成の再照合失敗時は古い認定フラグを除去してreceiptへ記録します。実行RPCの内部エラーでは実行済みの可能性を保持し、resource leaseを解放しません。
15
+
16
+ ## 0.18.0 — 未公開
17
+
18
+ - Cloud監督の登録前診断と、初回配送の受付不明を共通履歴から照合・回復できるよう修正しました。本文・client IDが一致する保存証拠だけを採用し、自動再送は行いません。
19
+
20
+ - Desktop Cloudの所属確認をdoctorへ追加し、プロジェクト一覧と環境一覧を分離しました。署名付きのthread assignは実環境・nonce・署名・保存結果を照合します。そのDesktop実行環境への結合が必要で、通常のCloud環境の拒否を成功扱いしたり別環境へ置き換えたりしません。作成後のタイトル設定・初回配送失敗時にも通常表示へ作成済みIDと復旧情報を残します。
21
+
22
+ - Desktop Cloudの環境構成一覧・新規作成・継続送信・共通監視を追加しました。初回送信にも選択済み環境と遅延起動指定を保持します。環境選択とDesktopプロジェクト所属を区別し、所属登録が確認できない`--project`作成は事前拒否します。受付不明の自動再送は行いません。
23
+
24
+ - Cloudの重複拒否で前回の試行IDを今回の配送履歴へ結び付けないよう修正しました。回復用IDを分離し、history checkは今回の本文・親ターンも照合して、拒否した指示の誤有効化を防ぎます。未公開の作成用--env別名も削除し、環境指定は--projectに統一しました。新規作成の本文がCloud ID/URLで始まっても、それを宛先と誤判定しません。
25
+
26
+ - 共通Cloud観測で公開commentaryの途中経過を保持し、途中経過だけの更新でもMonitorへ通知します。reasoning等の非公開内容は除外し、`--backend app-server`を宛先より前に置く場合のCloud振り分けも修正しました。
27
+
28
+ - Cloudタスクも共通の`supervise`・`read/watch`・`send`・`history/instructions/findings`で扱えます。監督の登録・pause/resume/stop・保存済みCLI・Monitorのdigestと復帰待ちを既存基盤へ接続し、実行中の指定ターンの中断と同一タスク内での継続、配送照合を追加しました。旧Desktop CLIとの記録互換性を保つCloud専用監督名前空間を使います。
29
+
30
+ - Cloud環境・タスクの一覧と新規作成は`--connection cloud projects list`・`threads list`・`thread create`で扱います。作成試行は`thread receipt`、配送照合・予約回復は`history show/check --attempt`、試行を選ぶ差分取得は`diff --attempt`に統一しました。未公開の重複サブコマンドと専用のカーソル・watch処理を削除し、結果不明時の自動再送は行いません。公式CLIの診断は私有一時領域で処理し、ローカル差分を暗黙に送信・適用しません。
31
+
32
+ - `projects list`と`thread create --project`を追加しました。プロジェクト名と初回指示だけで作成でき、既存チャット・`CODEX_THREAD_ID`・中継AIは不要です。App Serverで作成・Desktop購読・初回配送を行い、新規チャット自身の実在IDからnative RPCで所属登録し、全フォルダと初回指示を照合します。明示的な`--context-thread`は任意で、従来のnative直接作成を使います。配送不明・部分成功を保持し、自動再作成・再送を行いません。
33
+
34
+ - 内部RPCエラー(`-32603`など)では作成・配送が実行済みの可能性を保持し、`unknown`として記録します。メソッド・引数エラー(`-32601`/`-32602`)だけを実行前の拒否として区別し、自動再作成・再送は行いません。
35
+
36
+ ## 0.16.0 — 2026-09-21公開
37
+
38
+ - `supervise`と`supervise prompt`に`--use-path-cli`を追加しました。指定時はPATH上の`codexteer`を使うコマンドを本文に出し、CLIのコピー・NodeとCODEX_HOMEの固定を行いません。監督ID・接続方式と既存の監督制御は維持します。既定は従来どおり保存済みCLIを使います。
39
+ - Codex同梱CLI 0.155系列が作る保護ソケットへの公式シンボリックリンクに対応し、`desktop start`・共有接続・観測専用接続が`RUNTIME_UNSAFE`等で拒否される問題を修正しました。固定ディレクトリ、パスのハッシュ、所有者、権限、実ソケットを検証し、任意のリンクは引き続き拒否します。起動済みwrapperも更新後のCLIから利用でき、保存済み監督CLIにはプロンプトの再生成・監督の再起動で反映します。
5
40
  - `watch --stream`の既定を`digest`へ変更します。通常の進捗は20秒のsettle、読み取り専用コマンドなどは最長10分の保留でまとめ、ユーザー入力・注意状態・ターン・質問・読み取り専用以外の失敗は即時通知します。`--notify all`を明示すると従来の即時出力に戻せます。単発の`watch`と保存済みCLIの動作は変わりません。
6
41
  - `--settle-ms`と`--max-hold-ms`で待ち時間を調整できます。初回の読み取りは基準として扱い、実行中コマンドの出力だけの更新ではsettleを延長しません。保留分は接続断・終了時にも排出し、ページ途中の場合だけ`has_more:true`を保持します。200件/64KBはページ読了後の排出目安です。
7
- - 監督プロンプトを方針先頭の8節へ整理し、既定方針にコードの責務と作業の焦点を追加します。開始時の`help monitor`の重複読み取りをなくし、通知の読み方・無操作時の応答・Monitor期限切れ・議論の送信を明示します。生成する監視コマンドは`--notify digest`を明示します。
42
+ - 監督プロンプトを方針先頭の8節へ整理し、既定方針に設計上の責務と作業の焦点を追加します。設計上の責務では、事実・計算・判定を設計がどの層やシステムの担当と定めているかを先に確認し、別の層での検算や再計算など、同じ事実の持ち主が二つになる実装を観測します。基準は複数のリポジトリにまたがる設計の全体とし、分担が示されていない場合は推測せずユーザーに確認します。開始時の`help monitor`の重複読み取りをなくし、通知の読み方・無操作時の応答・Monitor期限切れ・議論の送信を明示します。生成する監視コマンドは`--notify digest`を明示します。
8
43
 
9
44
  ## 0.15.1 — 未公開
10
45
 
package/README.md CHANGED
@@ -1,16 +1,24 @@
1
1
  # codexteer
2
2
 
3
+ 登録済みプロジェクトへの初回指示付きチャット作成は、`projects list`と`thread create --project`で行えます。プロジェクト名と指示だけで使え、既存チャットや中継AIは不要です。Desktopのnative RPCで所属を登録し、複数フォルダや重複フォルダを持つ「サンプル」も、所属・全フォルダ・初回指示を照合します。作成済み・初回受付済みで照合だけ失敗した場合は、`thread verify <ATTEMPT-ID> --json`で再作成・再送せずに再照合できます。[使い方と実機検証](docs/thread-create.md)
4
+
5
+ ```bash
6
+ codexteer projects list --json
7
+ codexteer thread create --project 'サンプル' \
8
+ --title '関連リポジトリの調査' '関連リポジトリを読み、結果を報告してください' --json
9
+ ```
10
+
3
11
  普段のCodex Desktopを使いながら、別のAIが進捗を観測し、根拠を伴って実行途中に軌道修正を伝えるためのmacOS用CLIです。ターミナルからの単発送信にも使えます。
4
12
 
5
13
  ## 導入
6
14
 
7
- macOS、Node.js 20以降とnpm、`/Applications/ChatGPT.app`にインストールされたCodex Desktopが必要です。監督役に使うClaude CodeまたはCodex CLIを、PATH上の`claude`または`codex`で起動できる状態にします。
15
+ Desktop操作にはmacOS、Node.js 20以降とnpm、`/Applications/ChatGPT.app`にインストールされたCodex Desktopが必要です。監督役に使うClaude CodeまたはCodex CLIを、PATH上の`claude`または`codex`で起動できる状態にします。クラウド操作はDesktopが不要で、上記の公式Codex CLIを別途使用します。
8
16
 
9
17
  ### npxで使う
10
18
 
11
19
  リポジトリのcloneやグローバルインストールなしで実行できます。npmパッケージ名とCLI名は`codexteer`です。
12
20
 
13
- このブランチはマイナー更新`0.16.0`を準備します。`watch --stream`の通知集約と監督プロンプトの改善に加え、長いタスクへの送信前確認の修正を含みます。公開前に使う場合は「ソースから導入する」の手順で導入してください。変更点は[CHANGELOG](CHANGELOG.md)を参照してください。
21
+ npm公開済みの最新版は`0.17.0`です。このブランチは、リモート操作・Cloud対応などをまとめたマイナー更新`0.18.0`を準備します。公開前に使う場合は「ソースから導入する」の手順で導入してください。変更点は[CHANGELOG](CHANGELOG.md)を参照してください。
14
22
 
15
23
  ```bash
16
24
  npx -y codexteer --version
@@ -28,14 +36,22 @@ npx -y codexteer doctor --json
28
36
  <details>
29
37
  <summary>特定のバージョンで実行する場合(任意)</summary>
30
38
 
31
- 不具合の切り分けや、同じ版で監督を開始し直したい場合は、パッケージ名に`@バージョン`を付けられます。`0.16.0`の公開後にその版を選ぶ場合は、次のように指定します。
39
+ 不具合の切り分けや、同じ版で監督を開始し直したい場合は、パッケージ名に`@バージョン`を付けられます。`0.18.0`の公開後にその版を選ぶ場合は、次のように指定します。
32
40
 
33
41
  ```bash
34
- npx -y codexteer@0.16.0 supervise <thread-id>
42
+ npx -y codexteer@0.18.0 supervise <thread-id>
35
43
  ```
36
44
 
37
45
  </details>
38
46
 
47
+ ### DesktopのCloud環境を使う
48
+
49
+ DesktopのCloud環境構成は、`codexteer --connection desktop-cloud environments list --json`で取得します。新規作成は`thread create --environment <ID_OR_EXACT_NAME>`、継続・監視・監督は`durable_`付きIDまたは`hostId=durable`付きリンクで共通コマンドを使います。所属情報と登録APIの可否は`doctor --thread ... --project ...`で区別します。公式の署名付き`thread assign`は対象がそのDesktopの実行環境を使う場合に限り利用でき、通常のCloud環境では`--project`付き作成を現在も事前拒否します。[操作・確認済み範囲と制限](docs/desktop-cloud.md)
50
+
51
+ ### Codex Cloudを使う
52
+
53
+ CloudタスクのID・公式リンクで、同じ`supervise`・`read/watch`・`send`・`history/instructions/findings`を使えます。監督の`pause/resume/stop`や保存済みCLIも共通です。接続先をまだ選んでいない一覧と作成は`--connection cloud projects list`・`threads list`・`thread create --project <ENV_ID>`を使います。実行中のCloud送信は同じタスクの元ターンを中断して後続ターンを開始する方式です。[操作とサービス上の違い](docs/cloud.md)
54
+
39
55
  ### ソースから導入する
40
56
 
41
57
  こちらの方法ではGitとmakeも必要です。
@@ -73,6 +89,8 @@ codexteer doctor --json
73
89
 
74
90
  `desktop start`は実行ファイルと依存を`CODEX_HOME/codex-steer/runtimes/<version>-<sha256>`へ検証して配置し、そのwrapperからDesktopを起動します。元のリポジトリやnpxキャッシュに依存せず、起動後のhelperも同じ保存先を使います。配置済みの版は自動削除・上書きしません。
75
91
 
92
+ Codex同梱CLI 0.155系列の保護ソケット形式にも対応します。従来の実ソケットに加え、公式の固定ディレクトリとパスハッシュに一致するリンクだけを、所有者・権限・リンク先の種別を検証して利用します。旧CLIで`RUNTIME_UNSAFE`が出た場合、ソース導入先を更新すれば、すでに稼働しているwrapperへの接続も修復できます。保存済みCLIを使う監督には、監督の再起動またはプロンプトの再生成で更新を反映してください。
93
+
76
94
  監督AIが使うCLIの参照先については、次の監督手順を参照してください。MITライセンス、保存先の検証・復旧と公開手順は[配布ドキュメント](docs/distribution.md)にまとめています。
77
95
 
78
96
  ## 使い方
@@ -80,6 +98,32 @@ codexteer doctor --json
80
98
  以下の`<thread-id>`は対象のタスクIDに置き換えてください。`codex://threads/...`形式のタスクURLも使えます。
81
99
  コマンド一覧では`codexteer`と略記します。npxで手動実行する場合は、その部分を`npx -y codexteer`に置き換えてください。
82
100
 
101
+ ### プロジェクトと目標から担当編成と進行を任せる
102
+
103
+ 既存のスレッドを指定せず、目的に合わせてCodexの担当を作成・監督する対話CLIを起動できます。`prompt`を付けると本文だけを生成します。
104
+
105
+ ```bash
106
+ codexteer orchestrate --project 'Example Project' '達成したい目標' --use-path-cli
107
+ codexteer orchestrate --project 'Example Project' '達成したい目標' --agent codex
108
+ codexteer orchestrate --project 'My Project' '達成したい目標' --agent claude -- --model <MODEL>
109
+
110
+ codexteer orchestrate prompt --project 'Example Project' \
111
+ 'モーション図鑑で現状の動作を確認し、変更を指示できる状態にする'
112
+
113
+ codexteer orchestrate prompt --project 'My Project' '達成したい目標' --use-path-cli --json
114
+ codexteer orchestrate prompt --project 'My Project' '達成したい目標' --agent codex
115
+ codexteer orchestrate prompt --project 'My Project' - < goal.txt
116
+ codexteer help orchestrate prompt
117
+ ```
118
+
119
+ 直接起動は`supervise`と同じく、既定で`claude`、`--agent codex`ならCodex CLIを起動します。入出力・cwd・環境と終了コードを引き継ぎ、最初の`--`以降はエージェント引数です。`--use-path-cli`は本文内のcodexteerコマンドをPATH方式にします。`prompt`なしの`--json`は非対応です。
120
+
121
+ 生成専用の場合は本文を同じPCのオーケストレーターへ依頼として渡します。役割はモデルに依存せず、Codex自身も担当できます。オーケストレーターは目的・作業分担・共有資源・検収と統合を担当し、Codex担当は調査・設計判断・実装・検証に加え、依頼の前提への異論や改善案を出します。開始時と重要な節目に、高度(作業の先の成果)・深さ(一次資料と専門的手法・外部検証)・範囲(隣接分野の有効な技法)から目的と計画を再評価します。
122
+
123
+ `orchestrate prompt`では`--agent`は子の監督IDと送信元のCLI識別を明示する任意指定です。省略時は特定の実行役に固定せず、受け取ったオーケストレーターが自分のCLIを選びます。この生成専用モードではエージェントを起動しません。
124
+
125
+ 生成時はDesktopへ接続せず、チャット作成や監視を開始しません。既定は保存済みCLIを使い、`--use-path-cli`は実行環境のPATHとCODEX_HOMEを使います。作成後は各子に監督IDを付けて登録・観測し、同じ目的の未完了作業の継続まで追います。[使い方・委任と制御の範囲](docs/orchestration.md)も参照してください。
126
+
83
127
  ### Claude Code・Codex CLIに監視とステアリングを任せる
84
128
 
85
129
  上の導入を済ませ、監視したいCodexタスクのIDまたはURLをコピーします。別のターミナルの対象プロジェクトのディレクトリから、導入方法に合う方を実行してください。
@@ -113,7 +157,7 @@ codexteer supervise <thread-id> "セキュリティの問題だけを私へ報
113
157
 
114
158
  上書きは今回の生成・起動だけに適用し、次回の既定設定や保存したCLIには書き込みません。改行・空白・プレースホルダーに見える文字も指定文のまま本文に含めます。空文字・空白だけのメッセージはエラーです。呼び出し元のシェルで一つの引数として引用してください。`$`やバッククォートを文字として含めるときは、シェルが展開しない引用方法を使ってください。
115
159
 
116
- 両方とも、起動したCLI本体と依存を`CODEX_HOME/codex-steer/runtimes/<version>-<sha256>`へ検証して保存し、そのコピーへの実行コマンドを監督プロンプトに埋め込みます。監督役に渡す切り替えオプションは不要です。npxのキャッシュが新版に更新された場合や、ソース導入先を`git pull`・削除した場合も、開始済みの監督は保存した内容を使い続けます。
160
+ 既定では両方とも、起動したCLI本体と依存を`CODEX_HOME/codex-steer/runtimes/<version>-<sha256>`へ検証して保存し、そのコピーへの実行コマンドを監督プロンプトに埋め込みます。監督役に渡す切り替えオプションは不要です。npxのキャッシュが新版に更新された場合や、ソース導入先を`git pull`・削除した場合も、開始済みの監督は保存した内容を使い続けます。
117
161
 
118
162
  実行コマンドには、生成時に検証した`CODEX_HOME`の実体パス、監督ごとの`--supervisor` ID、選択した`--connection`も埋め込みます。先頭の指定とオプションを全て保持してください。貼り付け先の環境変数が未設定・別設定でも、生成元の接続先と履歴を使います。同じPCで使い、監督中は保存したコピーを保持してください。新しいCLIや別のプロファイルに切り替えるときは、その環境から監督を起動し直すか、プロンプトを生成し直します。同じ版・同じ内容のコピーは検証して再利用し、内容が変われば別の保存先を作ります。既存のコピーは自動削除・上書きしません。
119
163
 
@@ -121,6 +165,15 @@ Node本体はコピーせず、開始時の実体への絶対パスとNodeバー
121
165
 
122
166
  起動時にバージョンを指定するかどうかに関係なく、監督用CLIの保存処理は同じです。キャッシュの動作と保存処理の詳細は[配布ドキュメント](docs/distribution.md#監督役が使うcli)を参照してください。
123
167
 
168
+ 絶対パスを埋め込まず、PATH上の`codexteer`をプロンプトで案内するには、`--use-path-cli`を指定します。
169
+
170
+ ```bash
171
+ codexteer supervise <thread-id> --use-path-cli
172
+ codexteer supervise prompt <thread-id> --use-path-cli
173
+ ```
174
+
175
+ この場合の実行例は`codexteer --supervisor '<SESSION-ID>' --connection shared read <thread-id> --json`の形式です。CLIのコピー・Nodeの固定・`CODEX_HOME`の埋め込みを行わず、実行環境のPATHと`CODEX_HOME`を使います。CLIの更新は次の実行から反映されます。監督ID・接続方式・一時停止等の制御は維持するため、対象Desktopと同じプロファイルで実行してください。直接起動時も同じプロンプトを渡し、登録と終了時の停止を行います。`--agent`・監督方針・`--connection desktop`と併用でき、エージェント用の`--`区切りより前に指定します。`supervise prompt --json`の`data.cli_mode`は`path`、`data.deployment`と`data.node`は`null`です。指定しなければ`cli_mode`は`saved`で、従来の保存済みCLIを使います。
176
+
124
177
  モデルなどClaude側の起動引数は、`--`の後ろへ渡します。
125
178
 
126
179
  ```bash
@@ -155,6 +208,28 @@ codexteer supervise prompt <thread-id> "セキュリティの問題だけを私
155
208
 
156
209
  IDのコピーを省く場合は、Claudeに`codexteer threads list --desktop-only --json`で候補を表示してもらい、対象を選ぶこともできます。同じプロジェクトに複数のタスクがある場合、Claudeの起動場所だけでは監視対象を特定できません。
157
210
 
211
+ ### 古いターンの実行中状態が残る場合
212
+
213
+ 送信だけが止まる場合は、修正版の通常の`send`を使ってください。下記のオフライン修復は送信の前提条件ではありません。
214
+
215
+ `supervise stop`は監督の停止であり、Codexのターン履歴を修復しません。古いターンの終了記録が欠けている場合は、次のコマンドで検出できます。既定は読み取り専用です。
216
+
217
+ ```bash
218
+ codexteer thread repair <thread-id> --json
219
+ ```
220
+
221
+ 後続ターンが始まっているのに終了記録がない古いターンを`candidates`へ表示します。APIが停止中に`interrupted`を返していても、保存済みの状態が修復されたとは限らないため、ローカル履歴を検査します。最新の未完了ターンや未終了の入れ子ターンは自動修復しません。
222
+
223
+ 適用する場合は、作業とバックグラウンド処理を適切に停止し、**Codex/ChatGPTと全Codex CLI・app-serverを完全終了した後、外部ターミナル**で実行してください。完了までCodexを起動しないでください。
224
+
225
+ ```bash
226
+ codexteer thread repair <thread-id> --apply --json
227
+ ```
228
+
229
+ 履歴全体を所有者専用のバックアップへ保存・照合してから、対象IDの中断記録だけを末尾へ追記します。既存の会話・ordinal・DBの直接書き換え、プロセス終了、ターン再開、メッセージ送信は行いません。実際の停止時刻は不明なので、完了日時や所要時間を推定しません。履歴が変わった場合や、稼働プロセスを確認できない場合は拒否します。
230
+
231
+ 現在の適用対象は、隔離したCodexで再読込みを検証したCLI **0.162.0-alpha.17.2 / 0.164.0-alpha.11.1**、macOS、連続ordinalを持つpaginated履歴です。別バージョンは検証なしに適用しません。バックアップ分の空き容量を確保してください。`repaired`は保存履歴の追記とSHA256照合が成功した状態で、Codexの画面確認とは区別します。再起動後は状態を再確認し、read/watchのcursorを新しく取得してください。バックアップと修復記録は`CODEX_HOME/codex-steer/turn-repairs/`に残ります。失敗時は`history_changed`と`backup_path`/`receipt_path`を確認し、原本への上書きや再実行を盲目的に行わないでください。
232
+
158
233
  ### 監督状態の確認・一時停止・停止
159
234
 
160
235
  ユーザーの端末から、生成元と同じ`CODEX_HOME`で実行します。
@@ -209,6 +284,39 @@ codexteer findings dismiss <thread-id> <finding-id> --reason "既存の実装が
209
284
 
210
285
  評価は`useful`(有益)・`unnecessary`(不要)・`incorrect`(誤り)、未評価は`unrated`です。`stats`は現在のrevisionを1件として未送信候補も含め、評価済み件数と未評価件数を分けます。`useful_fraction`の母数は評価済み件数で、0件なら`null`です。介入の因果効果を証明する数値ではありません。詳細は`help findings`を参照してください。
211
286
 
287
+ ### リモートプロジェクトとチャットを観測する
288
+
289
+ `hostId`付きリンクで、Desktopが接続中のリモートチャットを読み取り・監視・観測専用の監督に使えます。
290
+
291
+ ```bash
292
+ codexteer projects list --remote --json
293
+ codexteer projects list --all --json
294
+ codexteer read 'codex://threads/<UUID>?hostId=remote-control%3Aenv_example' --json
295
+ codexteer supervise 'codex://threads/<UUID>?hostId=remote-control%3Aenv_example' --agent codex
296
+ ```
297
+
298
+ 既定native経路を通常の端末で使う場合は`--context-thread <LOCAL-CALLER-UUID>`を指定します。Codex内では実在する呼出し元の`CODEX_THREAD_ID`を使えます。プロジェクト一覧はDesktopの登録情報で、接続状態は`unverified`です。既定native経路のリモート監督は観測専用です。`--remote-transport control`を明示すると、既存activeターンへの指定ターン送信を伴う監督を生成・起動できます。controlは既存端末鍵へのアクセスが必要で、利用できない環境では接続前に停止します。利用環境で互換性を確認してください。[指定ターン送信の実装と検証範囲](docs/remote-control.md)も参照してください。[前提・制約・記録と実機確認](docs/remote-observation.md)
299
+
300
+ controlは接続前に公開鍵を照合し、失敗すれば`REMOTE_DEVICE_KEY_UNAVAILABLE`で停止します。`doctor`の`remote_control_auth`で、鍵取得の失敗段階・Desktop本体とNodeの公開entitlement差・同梱schemaの操作一覧を確認できます。鍵を利用できない環境では停止します。[認証の前提と制約](docs/remote-control.md)を参照してください。
301
+
302
+ pairedではCLI単体からリモートの新規チャット作成、タスク一覧・ID取得、new-turn、checkpoint/resource、orchestrateを使えます。正式project IDとDesktop登録IDを出し分け、作成/new-turn/openには実在ローカルcallerによるDesktop公式APIを使います。通常の端末では`--context-thread`を指定してください。Windowsで使える明示的なthread-shell実行とMac固有操作の制限を含め、[リモートCLIの対応範囲と手順](docs/remote-operations.md)を参照してください。
303
+
304
+ 専用鍵を使う場合は、利用者がセットアップを許可して`paired`経路を明示します。登録・ペアリングは、チャットの作成・送信とは別の操作です。対象ホストとチャットを照合し、許可された範囲だけで実行してください。動作確認には専用のテスト用チャットを使い、他のタスクに試験送信しないでください。代理承認やDesktop画面への表示は受付結果と区別します。
305
+
306
+ ```sh
307
+ codexteer remote prepare --key-provider keychain --json
308
+ codexteer remote enroll --json
309
+ # 対象ホストのDesktop設定 → Connections → Control this PC → Addで取得したコードを入力
310
+ codexteer --host 'remote-control:env_example' remote pair --code-stdin --json
311
+ codexteer remote verify --json
312
+ codexteer remote identity --json
313
+ codexteer --remote-transport paired doctor --thread '<hostId付きリンク>' --json
314
+ ```
315
+
316
+ keychain方式はXcode command-line toolsが必要で、codexteer自身のhelperとOSの非抽出P-256鍵を使います。既存Desktop鍵やその権限は変更しません。コードのTTY入力は非表示で、登録やペアリングの自動再試行はしません。[専用鍵の公式登録・ペアリング手順](docs/remote-device-enrollment.md)と`help remote-device`を参照してください。
317
+
318
+ 端末情報にはcodexteerの製品名・実OS・CPUを含めます。`remote status`の識別名は`codexteer_mac_os_arm64`など、`remote identity`の`server_display_name`はサーバーに保存された表示名です。任意のサーバー表示名の更新には対応していないため、`name_matches: false`として区別します。
319
+
212
320
  ### 通常起動したDesktopを再起動せずに観測する
213
321
 
214
322
  Desktopが共有ローカルdaemonを使っていて、`CODEX_HOME/app-server-control/app-server-control.sock`が存在する環境では、観測専用の直接接続を選べます。
@@ -224,7 +332,7 @@ codexteer supervise <thread-id> --agent codex --connection desktop
224
332
 
225
333
  直接接続で使えるのは観測です。送信・新規ターン・撤回送信・UI送信は`DESKTOP_READ_ONLY`で拒否します。送信は既存のshared方式を明示して使ってください。生成する監督プロンプトも接続方式を保持し、desktopでは観測専用の手順を使います。`doctor`は読み取り成功から送信・画面表示・承認継続の対応を推測しません。
226
334
 
227
- 詳細は`help connection`を参照してください。今回の開発環境にはこのソケットがなく、実Desktopへの直接接続は未検証です。読み取り・エンドポイント検査・送信拒否は隔離したUnixソケットの模擬サーバーで検証しています。同梱Codex CLI 0.153.4を一時プロファイルとローカルの偽モデルで動かし、実際のUnix接続から合成履歴を読む検証も通過しました(`npm run test:connection`)。実Desktopの画面表示・承認継続の確認とは区別しています。
335
+ 詳細は`help connection`を参照してください。今回の開発環境にはこのソケットがなく、実Desktopへの直接接続は未検証です。読み取り・エンドポイント検査・送信拒否は隔離したUnixソケットの模擬サーバーで検証しています。同梱Codex CLI 0.155.0-alpha.16.3を一時プロファイルとローカルの偽モデルで動かし、保護ソケットへのリンクを使って合成履歴を読む検証も通過しました(`npm run test:connection`)。実Desktopの画面表示・承認継続の確認とは区別しています。
228
336
 
229
337
  ### タスクを探して送信する
230
338
 
@@ -264,7 +372,7 @@ codexteer watch <thread-id> --until idle --timeout-ms 60000 --json
264
372
  変化するたびに1行のJSONを受け取るには、次のコマンドを使います。Ctrl-Cで停止します。
265
373
 
266
374
  ```bash
267
- codexteer watch <thread-id> --stream --json
375
+ codexteer watch <thread-id> --stream --notify all --json
268
376
  ```
269
377
 
270
378
  ### 詳細な使い方
@@ -325,6 +433,9 @@ codexteer supervise prompt codex://threads/<thread-id>
325
433
  # 標準の監督方針を置き換えて本文を生成
326
434
  codexteer supervise prompt <thread-id> "監督方針を指定するメッセージ"
327
435
 
436
+ # PATH上のcodexteerを案内する本文を生成
437
+ codexteer supervise prompt <thread-id> --use-path-cli
438
+
328
439
  # JSONで対象IDと本文を取得
329
440
  codexteer supervise prompt <thread-id> --json
330
441
 
@@ -332,9 +443,11 @@ codexteer supervise prompt <thread-id> --json
332
443
  codexteer help supervise prompt
333
444
  ```
334
445
 
335
- `supervise prompt`は、ClaudeやCodexなどの監督役(オーケストレーター)へ渡す初期プロンプトを生成します。対象IDと引数を検証し、CLI一式を内容ハッシュ別の保存先へ配置・検証してから、正規化したIDと保存先を本文へ埋め込みます。保存・検証に失敗した場合は本文を出力せず、直接起動の場合も監督エージェントを起動しません。Desktopの起動・接続、履歴取得、送信、監督エージェントの起動は行いません。タスクの存在と接続は監督開始時に確認します。本文には観測・根拠付き介入・結果確認・停止までの手順を含みます。
446
+ `supervise prompt`は、ClaudeやCodexなどの監督役(オーケストレーター)へ渡す初期プロンプトを生成します。対象IDと引数を検証し、既定ではCLI一式を内容ハッシュ別の保存先へ配置・検証してから、正規化したIDと保存先を本文へ埋め込みます。保存・検証に失敗した場合は本文を出力せず、直接起動の場合も監督エージェントを起動しません。Desktopの起動・接続、履歴取得、送信、監督エージェントの起動は行いません。タスクの存在と接続は監督開始時に確認します。本文には観測・根拠付き介入・結果確認・停止までの手順を含みます。
447
+
448
+ 既定の各コマンドは開始時のNodeと保存したCLIの絶対パスを使うため、本文を貼り付ける先のPATHに`codexteer`がなくても実行できます。先頭の`CODEX_HOME='…'`で生成元のプロファイルを指定します。相対パスやシンボリックリンクで指定していたホームも、検証した実体への絶対パスを使います。空白や引用符を含むパスはシェル引数として引用します。本文に埋め込むパスに改行等の制御文字があれば、本文を出力する前に`SUPERVISION_PATH_UNSAFE`で拒否します。`CODEX_HOME`指定・引用・`--require-node-version`を含め、実行コマンドをそのまま使ってください。別のPCや別のプロファイルを監督する場合は、そこで本文を生成し直してください。
336
449
 
337
- 各コマンドは開始時のNodeと保存したCLIの絶対パスを使うため、本文を貼り付ける先のPATHに`codexteer`がなくても実行できます。先頭の`CODEX_HOME='…'`で生成元のプロファイルを指定します。相対パスやシンボリックリンクで指定していたホームも、検証した実体への絶対パスを使います。空白や引用符を含むパスはシェル引数として引用します。本文に埋め込むパスに改行等の制御文字があれば、本文を出力する前に`SUPERVISION_PATH_UNSAFE`で拒否します。`CODEX_HOME`指定・引用・`--require-node-version`を含め、実行コマンドをそのまま使ってください。別のPCや別のプロファイルを監督する場合は、そこで本文を生成し直してください。
450
+ `--use-path-cli`を指定すると、本文はPATH上の`codexteer`を使い、CLIの保存や絶対パスの埋め込みを省略します。接続先と履歴は実行環境の`CODEX_HOME`に従います。
338
451
 
339
452
  通常出力は末尾改行付きの本文のみです。`--json`では既存CLIと同じ形式で返します。
340
453
 
@@ -343,13 +456,14 @@ codexteer help supervise prompt
343
456
  "ok": true,
344
457
  "command": "supervise.prompt",
345
458
  "data": {
346
- "thread_id": "01a04373-3770-71e0-a2e3-a3c196f5f5b1",
459
+ "thread_id": "aaaaaaaa-aaaa-4aaa-8aaa-000000000001",
347
460
  "prompt": "対象IDと保存したCLIの実行コマンドを含む監督プロンプト本文…",
461
+ "cli_mode": "saved",
348
462
  "deployment": {
349
463
  "codex_home": "/Users/me/.codex",
350
- "directory": "/Users/me/.codex/codex-steer/runtimes/0.16.0-<sha256>",
351
- "wrapper_path": "/Users/me/.codex/codex-steer/runtimes/0.16.0-<sha256>/bin/codexteer-wrapper.mjs",
352
- "version": "0.16.0",
464
+ "directory": "/Users/me/.codex/codex-steer/runtimes/0.18.0-<sha256>",
465
+ "wrapper_path": "/Users/me/.codex/codex-steer/runtimes/0.18.0-<sha256>/bin/codexteer-wrapper.mjs",
466
+ "version": "0.18.0",
353
467
  "sha256": "<sha256>",
354
468
  "reused": false
355
469
  },
@@ -398,13 +512,19 @@ codexteer open <thread-id>
398
512
  `ready`は接続条件と今回指定した検証の結果です。新規ターンだけが使えなくても、観測に成功すれば`ready: true`になります。操作別の仕様対応は`runtime_compatibility.operations`、新規ターン用ソケットの状態は`desktop_subscription`を確認してください。`supported`は仕様上の対応、`verified`は実際に行った観測の検証で、履歴全体、送信、画面表示、承認往復は保証しません。未検証の機能は`unverified_features`に明示します。例えば、従来の履歴形式を読み取った場合の結果は次の形です(主要項目のみ)。
399
513
 
400
514
  ```json
401
- {"ready":true,"connection":{"status":"connected"},"compatibility":{"status":"verified","scope":"target-observation","thread_id":"01a04373-3770-71e0-a2e3-a3c196f5f5b1","api_checks":{"initialize":"verified","thread/loaded/list":"verified","thread/read":"verified","thread/turns/list":"unverified","thread/items/list":"unverified"},"unverified_features":["steering","desktop_ui","approval_roundtrip"]},"failure":null}
515
+ {"ready":true,"connection":{"status":"connected"},"compatibility":{"status":"verified","scope":"target-observation","thread_id":"aaaaaaaa-aaaa-4aaa-8aaa-000000000001","api_checks":{"initialize":"verified","thread/loaded/list":"verified","thread/read":"verified","thread/turns/list":"unverified","thread/items/list":"unverified"},"unverified_features":["steering","desktop_ui","approval_roundtrip"]},"failure":null}
402
516
  ```
403
517
 
404
518
  `threads list`は`--desktop-only`を外すとDesktop以外のローカルタスクも含みます。`--limit`は1〜500件、既定は20件です。タスクや各種記録の保存先は`CODEX_HOME`に従い、未指定時は`~/.codex`を使います。
405
519
 
406
520
  #### メッセージ送信・ステアリング
407
521
 
522
+ 古いターンが実行中のまま履歴に残っていても、ページ形式の履歴では通常の`send`を使えます。チャットが稼働中で、最新ターンも進行中の場合だけそのIDを指定し、サーバー側で現在の実行中IDとの一致を確認します。不一致・受付不明時に別のターンへ自動再送しません。この対応にCodexの終了や保存履歴の修復は不要です。Codex本体の表示や保存履歴の修復とは別で、観測上の`active_turn_id`は複数候補なら`null`のままです。
523
+
524
+ `--based-on`は残留状態があっても最新ターンの変更を検出します。古い全履歴cursorで最新ターンを特定できない場合は、改めて`read`して取得したcursorを使ってください。修正版CLIは既存の共有wrapperで利用できます。保存済みCLIを使う監督には、監督の起動・プロンプト生成をやり直して反映します。`--use-path-cli`では次のコマンド実行から反映されます。
525
+
526
+ Codexが古いターンの表示を`interrupted`から`inProgress`へ戻すと、既存の`watch`や`--since`が`STALE_CURSOR`になる場合があります。その場合は`read`を`--since`なしで実行し、必要な履歴を確認してから新しいcursorで監視を再開してください。
527
+
408
528
  ```bash
409
529
  # 実行中タスクへ追加入力
410
530
  codexteer send <thread-id> "今の要件に必要な変更へ絞ってください" --json
@@ -474,7 +594,7 @@ codexteer watch <thread-id> --until idle --timeout-ms 60000 --json
474
594
  codexteer watch <thread-id> --until attention --json
475
595
 
476
596
  # 変化するたびにJSONを1行ずつ出力し続ける
477
- codexteer watch <thread-id> --stream --since <cursor> --include-output --json
597
+ codexteer watch <thread-id> --stream --notify all --since <cursor> --include-output --json
478
598
  ```
479
599
 
480
600
  | オプション | 対象・用途 |
@@ -514,7 +634,7 @@ codexteer watch <thread-id> --stream --since <cursor> --include-output --json
514
634
  接続状態の通知例です。`<cursor>`は実際の値に置き換わります。
515
635
 
516
636
  ```json
517
- {"ok":true,"command":"watch","data":{"type":"connection","thread_id":"01a04373-3770-71e0-a2e3-a3c196f5f5b1","state":"reconnecting","resume_cursor":"<cursor>","last_observed_at":"2026-09-12T10:00:00.000Z","observed_at":"2026-09-12T10:00:08.000Z","reconnect_attempts":0,"cause_code":"CONNECTION_FAILED","retry_timeout_ms":60000}}
637
+ {"ok":true,"command":"watch","data":{"type":"connection","thread_id":"aaaaaaaa-aaaa-4aaa-8aaa-000000000001","state":"reconnecting","resume_cursor":"<cursor>","last_observed_at":"2026-09-12T10:00:00.000Z","observed_at":"2026-09-12T10:00:08.000Z","reconnect_attempts":0,"cause_code":"CONNECTION_FAILED","retry_timeout_ms":60000}}
518
638
  ```
519
639
 
520
640
  監視の接続・読み取りで停止を伴うエラーは`ok:false`、`data.type:connection`、`data.state:needs_review|failed`、`error.code`と理由を返し、終了コード1です。復帰待ちの上限は`WATCH_RECONNECT_TIMEOUT`、無効cursorは`STALE_CURSOR`です。Ctrl-Cは接続・初期化・復帰待ちの途中でも監視だけを停止します。
@@ -625,3 +745,21 @@ codexteer debug-ui <thread-id> --wait-ms 1500
625
745
  ```
626
746
 
627
747
  UI方式は画面フォーカスに依存し、サイドチャットが開いている場合の宛先保証はありません。結果の`submitted_unverified`はキー入力の実行を表し、対象タスクへの配送確認ではありません。
748
+
749
+ ### Desktopを更新してwrapperを再適用する(macOS)
750
+
751
+ 作業を終えてから、アプリ外のNodeを使うターミナルで実行します。
752
+
753
+ ```bash
754
+ codexteer desktop update --dry-run --json
755
+ codexteer desktop update
756
+ codexteer desktop update-status --json
757
+ ```
758
+
759
+ `update`は独立したバックグラウンドプロセスを起動し、Codexアプリの「更新を確認…」メニューと既知の更新ボタン名を照合して操作します。アプリ内の全ウィンドウを調べるため、Sparkle固有のウィンドウ識別を保証するものではありません。更新以外のダイアログは閉じてから実行してください。英語・日本語のUIに対応し、ターミナルへのAccessibility・Automation権限が必要です。OSの権限ダイアログには自動応答しません。Codexは起動済みである必要があります。アプリ同梱Nodeと`--supervisor`付きの呼出しでは実行できません。
760
+
761
+ 更新によるビルド番号の変更と新しいアプリプロセスを確認してから、Codexを正常終了し、保存済みcodexteerの`desktop start`でwrapper付きで再起動します。公式更新とwrapper再適用で複数回再起動する場合があります。進行中のタスクの自動再開は行いません。
762
+
763
+ 起動受付は更新成功ではありません。`update-status`の`phase: complete`と`wrapper_verified: true`が完了条件です。更新不要の場合は`up_to_date`で終了し、再起動しません。状態は`CODEX_HOME/codex-steer/desktop-update/status.json`に保存されます。既定の更新待機時間は10分で、`--timeout-ms`で10秒〜1時間に変更できます。正常終了の待機には別途最大30秒、wrapper起動と診断にも時間がかかります。
764
+
765
+ 既知のラベルに一致しない画面や、更新失敗・再起動未完了では停止またはタイムアウトします。アプリを強制終了したり別の更新元へ切り替えたりはしません。`failed`の場合は記録された理由と公式更新画面を確認してください。更新が完了してから正常終了し、`codexteer desktop start`と`codexteer doctor --json`で復旧できます。workerが異常終了してlockが残った場合は自動で奪いません。workerと更新処理が停止したことを確認してからエラーに表示された空のlockディレクトリを手動で除去します。
@@ -0,0 +1,94 @@
1
+ import Foundation
2
+ import Security
3
+
4
+ // A codexteer-owned, nonextractable login-Keychain key. This helper has no
5
+ // network access code and never requests a private key's external representation.
6
+ enum Failure: Error { case invalid }
7
+ var stage = "input"
8
+ func fail(_ status: OSStatus) throws -> Never { throw NSError(domain: NSOSStatusErrorDomain, code: Int(status)) }
9
+ func query(_ id: String) throws -> [String: Any] {
10
+ guard id.range(of: "^codexteer_[a-f0-9-]{36}$", options: .regularExpression) != nil else { throw Failure.invalid }
11
+ return [kSecClass as String: kSecClassKey, kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
12
+ kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
13
+ kSecAttrApplicationTag as String: Data(("dev.codexteer.device-key." + id).utf8),
14
+ kSecUseDataProtectionKeychain as String: false]
15
+ }
16
+ func metadata(_ key: SecKey, _ id: String) throws -> [String: Any] {
17
+ guard let attrs = SecKeyCopyAttributes(key) as? [String: Any],
18
+ (attrs[kSecAttrIsExtractable as String] as? NSNumber)?.boolValue == false,
19
+ (attrs[kSecAttrKeySizeInBits as String] as? NSNumber)?.intValue == 256,
20
+ let pub = SecKeyCopyPublicKey(key) else { throw Failure.invalid }
21
+ var error: Unmanaged<CFError>?
22
+ guard let bytes = SecKeyCopyExternalRepresentation(pub, &error) as Data?, bytes.count == 65, bytes.first == 4 else { throw Failure.invalid }
23
+ let header: [UInt8] = [0x30,0x59,0x30,0x13,0x06,0x07,0x2a,0x86,0x48,0xce,0x3d,0x02,0x01,0x06,0x08,0x2a,0x86,0x48,0xce,0x3d,0x03,0x01,0x07,0x03,0x42,0x00]
24
+ return ["keyId": id, "algorithm": "ecdsa_p256_sha256", "protectionClass": "os_protected_nonextractable",
25
+ "publicKeySpkiDerBase64": (Data(header) + bytes).base64EncodedString()]
26
+ }
27
+ func supportedPayload(_ bytes: Data) throws {
28
+ guard let outer = try JSONSerialization.jsonObject(with: bytes) as? [String: Any], outer.count == 2,
29
+ outer["domain"] as? String == "codex-device-key-sign-payload/v1", let p = outer["payload"] as? [String: Any],
30
+ p["targetOrigin"] as? String == "https://chatgpt.com",
31
+ let account = p["accountUserId"] as? String, !account.isEmpty,
32
+ let client = p["clientId"] as? String, !client.isEmpty,
33
+ let nonce = p["nonce"] as? String, nonce.range(of: "^[A-Za-z0-9_-]{43,512}$", options: .regularExpression) != nil else { throw Failure.invalid }
34
+ switch p["type"] as? String {
35
+ case "remoteControlClientConnection":
36
+ guard p.count == 11, p["targetPath"] as? String == "/backend-api/codex/remote/control/client",
37
+ p["audience"] as? String == "remote_control_client_websocket",
38
+ p["scopes"] as? [String] == ["remote_control_controller_websocket"],
39
+ let expiry = p["tokenExpiresAt"] as? NSNumber, expiry.doubleValue > Date().timeIntervalSince1970 else { throw Failure.invalid }
40
+ case "remoteControlClientEnrollment":
41
+ guard p.count == 10, p["audience"] as? String == "remote_control_client_enrollment",
42
+ ["/backend-api/codex/remote/control/client/enroll/finish", "/backend-api/codex/remote/control/client/refresh/finish"].contains(p["targetPath"] as? String ?? "") else { throw Failure.invalid }
43
+ default: throw Failure.invalid
44
+ }
45
+ }
46
+ func output(_ object: [String: Any]) {
47
+ if let data = try? JSONSerialization.data(withJSONObject: object, options: [.sortedKeys]) { FileHandle.standardOutput.write(data) }
48
+ }
49
+ do {
50
+ guard CommandLine.arguments.count == 2 else { throw Failure.invalid }
51
+ let data = try Data(contentsOf: URL(fileURLWithPath: CommandLine.arguments[1]))
52
+ guard data.count <= 32768, let input = try JSONSerialization.jsonObject(with: data) as? [String: Any], let op = input["operation"] as? String else { throw Failure.invalid }
53
+ if op == "create" {
54
+ stage = "create"
55
+ guard let id = input["keyId"] as? String else { throw Failure.invalid }
56
+ let lookup = try query(id)
57
+ let existing = SecItemCopyMatching(lookup as CFDictionary, nil)
58
+ guard existing == errSecItemNotFound else { try fail(existing == errSecSuccess ? errSecDuplicateItem : existing) }
59
+ let attrs: [String: Any] = [kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits as String: 256,
60
+ kSecUseDataProtectionKeychain as String: false, kSecAttrIsExtractable as String: false, kSecAttrIsSensitive as String: true,
61
+ kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: true, kSecAttrIsExtractable as String: false,
62
+ kSecAttrApplicationTag as String: Data(("dev.codexteer.device-key." + id).utf8), kSecAttrLabel as String: "codexteer Remote Control"]]
63
+ var error: Unmanaged<CFError>?
64
+ guard let key = SecKeyCreateRandomKey(attrs as CFDictionary, &error) else {
65
+ if let error = error?.takeRetainedValue() { throw error }; throw Failure.invalid
66
+ }
67
+ stage = "validate"
68
+ output(["ok": true, "key": try metadata(key, id)])
69
+ } else {
70
+ guard ["inspect", "sign"].contains(op), let id = input["keyId"] as? String else { throw Failure.invalid }
71
+ stage = "public-key"
72
+ var q = try query(id); q[kSecReturnRef as String] = true
73
+ var item: CFTypeRef?
74
+ let status = SecItemCopyMatching(q as CFDictionary, &item)
75
+ guard status == errSecSuccess else { try fail(status) }
76
+ guard let item = item, CFGetTypeID(item) == SecKeyGetTypeID() else { throw Failure.invalid }
77
+ let key = item as! SecKey
78
+ let info = try metadata(key, id)
79
+ if op == "inspect" { output(["ok": true, "key": info]) }
80
+ else {
81
+ stage = "payload"
82
+ guard let encoded = input["signedPayloadBase64"] as? String, let payload = Data(base64Encoded: encoded) else { throw Failure.invalid }
83
+ try supportedPayload(payload)
84
+ stage = "sign"
85
+ var error: Unmanaged<CFError>?
86
+ guard let signature = SecKeyCreateSignature(key, .ecdsaSignatureMessageX962SHA256, payload as CFData, &error) as Data? else {
87
+ if let error = error?.takeRetainedValue() { throw error }; throw Failure.invalid
88
+ }
89
+ output(["ok": true, "signatureDerBase64": signature.base64EncodedString(), "key": info])
90
+ }
91
+ }
92
+ } catch {
93
+ output(["ok": false, "stage": stage, "os_status": (error as NSError).code])
94
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { updateWorker } from "../src/update.mjs";
3
+ await updateWorker(JSON.parse(process.argv[2]));
package/bin/codexteer.mjs CHANGED
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { main } from "../src/cli.mjs";
3
+ import { main, fail } from "../src/cli.mjs";
4
+ import { cloudInvocation, mainCloud } from "../src/cloud-cli.mjs";
4
5
 
5
- await main(process.argv.slice(2));
6
+ const args = process.argv.slice(2);
7
+ if (cloudInvocation(args)) {
8
+ try { await mainCloud(args); } catch (failure) { fail(failure, args.includes("--json")); }
9
+ } else await main(args);