codexport 0.1.1 → 0.1.2
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 +57 -15
- package/dist/index.js +5 -5
- package/docs/prd.md +22 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
`codexport` replicates a canonical
|
|
11
|
+
`codexport` replicates a canonical master Codex setup to follower machines. it is built for operators who want one trusted `~/.codex` source of truth, follower-local overlays, and a low-friction `npx` join path without committing plaintext secrets to GitHub.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
the master serves a content-hashed bundle from its `~/.codex` directory. followers pin the master's fingerprint on join, fetch updates over a Tailscale-reachable HTTP address, and apply updates at Codex `SessionStart` through a short best-effort hook.
|
|
14
14
|
|
|
15
15
|
[npm](https://www.npmjs.com/package/codexport) | [github](https://github.com/Microck/codexport)
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ Machine1 serves a content-hashed bundle from its `~/.codex` directory. followers
|
|
|
18
18
|
|
|
19
19
|
if you keep a carefully tuned Codex setup on one machine and want the same defaults elsewhere, `codexport` gives you a practical pull-based sync path.
|
|
20
20
|
|
|
21
|
-
- keep
|
|
21
|
+
- keep the master as the canonical Codex configuration source
|
|
22
22
|
- let followers preserve local MCPs, local skills, trust entries, and path overrides
|
|
23
23
|
- sync auth-bearing files through the private Tailscale path instead of a plaintext GitHub commit
|
|
24
24
|
- refresh followers at Codex session startup without interrupting active sessions
|
|
@@ -28,18 +28,18 @@ if you keep a carefully tuned Codex setup on one machine and want the same defau
|
|
|
28
28
|
|
|
29
29
|
`codexport` requires Node.js 20+.
|
|
30
30
|
|
|
31
|
-
on
|
|
31
|
+
on the master:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
npx codexport master init
|
|
35
35
|
npx codexport master service install
|
|
36
|
-
npx codexport master link --host
|
|
36
|
+
npx codexport master link --host master.tailnet.ts.net
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
on a follower:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
npx codexport follower join "codexport://join?host=
|
|
42
|
+
npx codexport follower join "codexport://join?host=master.tailnet.ts.net&port=17342&fingerprint=..."
|
|
43
43
|
npx codexport hook install
|
|
44
44
|
```
|
|
45
45
|
|
|
@@ -52,16 +52,58 @@ npx codexport status
|
|
|
52
52
|
|
|
53
53
|
## sync model
|
|
54
54
|
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
```mermaid
|
|
56
|
+
flowchart LR
|
|
57
|
+
subgraph master["master machine"]
|
|
58
|
+
masterCodex["~/.codex canonical state"]
|
|
59
|
+
masterCli["codexport master serve"]
|
|
60
|
+
masterBundle["content-hashed bundle"]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
subgraph privateNet["tailscale network"]
|
|
64
|
+
http["http://master.tailnet.ts.net:17342"]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
subgraph follower["follower machine"]
|
|
68
|
+
localOverlay["~/.codexport local overlay"]
|
|
69
|
+
sessionHook["Codex SessionStart hook"]
|
|
70
|
+
generatedCodex["generated ~/.codex"]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
masterCodex -->|select files and hash content| masterBundle
|
|
74
|
+
masterBundle --> masterCli
|
|
75
|
+
masterCli -->|serve bundle and fingerprint| http
|
|
76
|
+
sessionHook -->|check revision before session| http
|
|
77
|
+
http -->|download changed bundle| sessionHook
|
|
78
|
+
localOverlay -->|merge MCPs, skills, path variables| sessionHook
|
|
79
|
+
sessionHook -->|backup and apply| generatedCodex
|
|
61
80
|
```
|
|
62
81
|
|
|
63
82
|
followers trust the provided Tailscale address and store the master fingerprint. later syncs refuse changed fingerprints by default, so a changed master identity requires intentional re-enrollment.
|
|
64
83
|
|
|
84
|
+
## trust flow
|
|
85
|
+
|
|
86
|
+
```mermaid
|
|
87
|
+
sequenceDiagram
|
|
88
|
+
participant Operator as operator
|
|
89
|
+
participant Master as master
|
|
90
|
+
participant Follower as follower
|
|
91
|
+
participant Codex as codex session
|
|
92
|
+
|
|
93
|
+
Operator->>Master: codexport master link
|
|
94
|
+
Master-->>Operator: join link with host, port, fingerprint
|
|
95
|
+
Operator->>Follower: codexport follower join "codexport://join?..."
|
|
96
|
+
Follower->>Master: GET /meta
|
|
97
|
+
Master-->>Follower: fingerprint and revision
|
|
98
|
+
Follower->>Follower: pin trusted fingerprint
|
|
99
|
+
Follower->>Master: GET /bundle
|
|
100
|
+
Master-->>Follower: content-hashed bundle
|
|
101
|
+
Follower->>Follower: apply bundle plus local overlay
|
|
102
|
+
Codex->>Follower: SessionStart hook
|
|
103
|
+
Follower->>Master: check revision and fingerprint
|
|
104
|
+
Follower-->>Codex: continue with latest applied config
|
|
105
|
+
```
|
|
106
|
+
|
|
65
107
|
## included state
|
|
66
108
|
|
|
67
109
|
the master bundle includes canonical Codex config, auth files, hooks, prompts,
|
|
@@ -100,7 +142,7 @@ the follower's `local.toml` before writing the generated `~/.codex/config.toml`.
|
|
|
100
142
|
|
|
101
143
|
| command | purpose |
|
|
102
144
|
| --- | --- |
|
|
103
|
-
| `codexport master init` | create or refresh
|
|
145
|
+
| `codexport master init` | create or refresh the master identity and bundle state |
|
|
104
146
|
| `codexport master serve` | serve the current canonical bundle over HTTP |
|
|
105
147
|
| `codexport master link` | print a durable follower join link and fallback command |
|
|
106
148
|
| `codexport master rebuild` | force rebuild the master bundle for repair/debugging |
|
|
@@ -125,14 +167,14 @@ followers do not need a background service in v1. the hook runs a short best-eff
|
|
|
125
167
|
generate a copy-paste join command:
|
|
126
168
|
|
|
127
169
|
```bash
|
|
128
|
-
codexport master link --host
|
|
170
|
+
codexport master link --host master.tailnet.ts.net
|
|
129
171
|
```
|
|
130
172
|
|
|
131
173
|
join with explicit trust metadata:
|
|
132
174
|
|
|
133
175
|
```bash
|
|
134
176
|
codexport follower join \
|
|
135
|
-
--master http://
|
|
177
|
+
--master http://master.tailnet.ts.net:17342 \
|
|
136
178
|
--fingerprint <fingerprint> \
|
|
137
179
|
--apply
|
|
138
180
|
```
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { homedir, platform } from "node:os";
|
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import { spawn } from "node:child_process";
|
|
13
13
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
14
|
-
const VERSION = "0.1.
|
|
14
|
+
const VERSION = "0.1.2";
|
|
15
15
|
const DEFAULT_PORT = 17342;
|
|
16
16
|
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
17
17
|
const CODEXPORT_DIR = ".codexport";
|
|
@@ -533,7 +533,7 @@ async function commandMasterLink(ctx, options) {
|
|
|
533
533
|
const identity = await loadMasterIdentity(ctx);
|
|
534
534
|
const local = await readLocalConfig(ctx);
|
|
535
535
|
const port = options.port ?? local.port ?? DEFAULT_PORT;
|
|
536
|
-
const host = options.host ?? "
|
|
536
|
+
const host = options.host ?? "master.tailnet.ts.net";
|
|
537
537
|
const url = masterUrl(host, port);
|
|
538
538
|
print(ctx, {
|
|
539
539
|
joinLink: buildJoinLink(url, identity.fingerprint),
|
|
@@ -674,7 +674,7 @@ async function main(argv) {
|
|
|
674
674
|
.name("codexport")
|
|
675
675
|
.description("Replicate a canonical Codex setup from a master machine to follower machines.")
|
|
676
676
|
.version(VERSION);
|
|
677
|
-
const master = program.command("master").description("Manage the canonical
|
|
677
|
+
const master = program.command("master").description("Manage the canonical master export.");
|
|
678
678
|
master.command("init")
|
|
679
679
|
.description("Create or refresh master identity and canonical bundle state.")
|
|
680
680
|
.option("--port <port>", "default serve port", parsePositiveInt, DEFAULT_PORT)
|
|
@@ -684,7 +684,7 @@ async function main(argv) {
|
|
|
684
684
|
.action(async (_options, command) => commandMasterRebuild(contextFromCommand(command)));
|
|
685
685
|
master.command("link")
|
|
686
686
|
.description("Print a durable follower join link and copy-paste command.")
|
|
687
|
-
.option("--host <host>", "Tailscale host, IP, or full URL", "
|
|
687
|
+
.option("--host <host>", "Tailscale host, IP, or full URL", "master.tailnet.ts.net")
|
|
688
688
|
.option("--port <port>", "master port", parsePositiveInt, DEFAULT_PORT)
|
|
689
689
|
.action(async (options, command) => commandMasterLink(contextFromCommand(command), options));
|
|
690
690
|
master.command("serve")
|
|
@@ -706,7 +706,7 @@ async function main(argv) {
|
|
|
706
706
|
const follower = program.command("follower").description("Enroll and manage a follower machine.");
|
|
707
707
|
follower.command("join [link]")
|
|
708
708
|
.description("Enroll this follower from a codexport://join link or explicit master URL.")
|
|
709
|
-
.option("--master <url>", "master URL, for example http://
|
|
709
|
+
.option("--master <url>", "master URL, for example http://master.tailnet.ts.net:17342")
|
|
710
710
|
.option("--fingerprint <hex>", "expected master fingerprint")
|
|
711
711
|
.option("--apply", "download and apply immediately after enrollment")
|
|
712
712
|
.option("--timeout-ms <ms>", "network timeout", parsePositiveInt, DEFAULT_TIMEOUT_MS)
|
package/docs/prd.md
CHANGED
|
@@ -7,14 +7,14 @@ Status: implemented as an initial Node.js CLI in this repository.
|
|
|
7
7
|
Build a low-friction way to replicate the useful parts of a master machine's
|
|
8
8
|
Codex setup to follower machines.
|
|
9
9
|
|
|
10
|
-
The main target is `~/.codex`.
|
|
10
|
+
The main target is `~/.codex`. The master is the source of truth. Follower
|
|
11
11
|
machines should stay up to date automatically while still being allowed to keep
|
|
12
12
|
machine-local additions such as local MCPs, local skills, local trust entries,
|
|
13
13
|
and local path overrides.
|
|
14
14
|
|
|
15
15
|
## Product Principles
|
|
16
16
|
|
|
17
|
-
-
|
|
17
|
+
- The master owns the canonical Codex configuration.
|
|
18
18
|
- Followers are read-only consumers of canonical state.
|
|
19
19
|
- Followers can keep local-only overlays unless those overlays explicitly
|
|
20
20
|
conflict with canonical names.
|
|
@@ -37,32 +37,32 @@ The project will live at:
|
|
|
37
37
|
|
|
38
38
|
### Authority Model
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
The master is canonical. Follower machines do not push changes back by default.
|
|
41
41
|
|
|
42
42
|
Follower-specific additions are allowed, but they remain local unless promoted
|
|
43
|
-
from
|
|
43
|
+
from the master intentionally.
|
|
44
44
|
|
|
45
45
|
### Sync Transport
|
|
46
46
|
|
|
47
47
|
Use a Tailscale-compatible pull model:
|
|
48
48
|
|
|
49
49
|
```text
|
|
50
|
-
|
|
50
|
+
Master:
|
|
51
51
|
codexport serve
|
|
52
52
|
|
|
53
53
|
Follower:
|
|
54
54
|
codexport join
|
|
55
|
-
> Master Tailscale IP/name:
|
|
55
|
+
> Master Tailscale IP/name: master.tailnet.ts.net
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
Followers pull from
|
|
58
|
+
Followers pull from the master. The master does not need to track and push to every
|
|
59
59
|
follower.
|
|
60
60
|
|
|
61
61
|
Tailscale reachability is sufficient for follower enrollment. The first version
|
|
62
62
|
does not require a separate one-time pairing code.
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
Followers should be able to reconnect and sync whenever
|
|
64
|
+
The master should run the master server persistently as a user-level service.
|
|
65
|
+
Followers should be able to reconnect and sync whenever the master is online.
|
|
66
66
|
|
|
67
67
|
On first join, the follower trusts the provided Tailscale address and stores the
|
|
68
68
|
master instance fingerprint. Later syncs must verify that fingerprint. If the
|
|
@@ -71,7 +71,7 @@ re-enroll or trust-reset command.
|
|
|
71
71
|
|
|
72
72
|
### One-Click Join
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
The master should be able to generate a durable join artifact for followers.
|
|
75
75
|
|
|
76
76
|
Preferred UX:
|
|
77
77
|
|
|
@@ -83,14 +83,14 @@ Outputs a permanent join link or command that a follower can run without
|
|
|
83
83
|
manually typing the master address:
|
|
84
84
|
|
|
85
85
|
```text
|
|
86
|
-
npx codexport follower join "codexport://join?host=
|
|
86
|
+
npx codexport follower join "codexport://join?host=master.tailnet.ts.net&port=17342&fingerprint=..."
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
If custom URL handling is too complex for the first implementation, the
|
|
90
90
|
fallback is a one-time copy-paste command:
|
|
91
91
|
|
|
92
92
|
```text
|
|
93
|
-
npx codexport follower join --master http://
|
|
93
|
+
npx codexport follower join --master http://master.tailnet.ts.net:17342 --fingerprint ...
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
The join link should not include plaintext Codex secrets. It should include only
|
|
@@ -128,9 +128,9 @@ Followers should sync automatically through a follower-only Codex `SessionStart`
|
|
|
128
128
|
hook:
|
|
129
129
|
|
|
130
130
|
- The hook runs a short best-effort sync before a new Codex session starts.
|
|
131
|
-
- If
|
|
131
|
+
- If the master is reachable and the content hash changed, the follower applies
|
|
132
132
|
the update before the session continues.
|
|
133
|
-
- If
|
|
133
|
+
- If the master is unavailable, the hook exits cleanly and Codex starts with the
|
|
134
134
|
most recently applied config.
|
|
135
135
|
|
|
136
136
|
This means followers are guaranteed to refresh at the Codex session boundary,
|
|
@@ -142,7 +142,7 @@ available when an immediate refresh is needed outside session startup.
|
|
|
142
142
|
The tool should generate the final `~/.codex/config.toml` from layers:
|
|
143
143
|
|
|
144
144
|
```text
|
|
145
|
-
canonical config from
|
|
145
|
+
canonical config from the master
|
|
146
146
|
local follower overlay
|
|
147
147
|
generated final ~/.codex/config.toml
|
|
148
148
|
```
|
|
@@ -213,7 +213,7 @@ The user wants auth and important state to sync for a one-click experience.
|
|
|
213
213
|
|
|
214
214
|
The current design should not store plaintext secrets in GitHub. Instead:
|
|
215
215
|
|
|
216
|
-
-
|
|
216
|
+
- The master can include secret-bearing files in a private export bundle served
|
|
217
217
|
over Tailscale.
|
|
218
218
|
- Followers fetch and apply that bundle during `join` or automatic sync.
|
|
219
219
|
- GitHub stores code and non-secret portable config, not plaintext token blobs.
|
|
@@ -265,7 +265,7 @@ Recommended package shape:
|
|
|
265
265
|
- Optional later installer that writes the master service and follower hook.
|
|
266
266
|
|
|
267
267
|
The tool may still install or configure Bun as part of the synced development
|
|
268
|
-
toolchain if
|
|
268
|
+
toolchain if the master's `mise.toml` requests it.
|
|
269
269
|
|
|
270
270
|
The published package should require Node.js 20 or newer.
|
|
271
271
|
|
|
@@ -307,10 +307,10 @@ Rejected for v1:
|
|
|
307
307
|
|
|
308
308
|
Expected roles:
|
|
309
309
|
|
|
310
|
-
- `master init`: create
|
|
310
|
+
- `master init`: create the master canonical state.
|
|
311
311
|
- `master serve`: serve canonical bundle over Tailscale-reachable HTTP.
|
|
312
312
|
- `master link`: print a durable follower join link or copy-paste command.
|
|
313
|
-
- `follower join`: enroll a follower by asking for
|
|
313
|
+
- `follower join`: enroll a follower by asking for the master Tailscale IP/name.
|
|
314
314
|
- `sync`: fetch and stage/apply updates.
|
|
315
315
|
- `apply`: apply already available canonical state plus local overlays.
|
|
316
316
|
- `hook install`: install follower-only Codex SessionStart sync hook.
|
|
@@ -352,7 +352,7 @@ Follower hook:
|
|
|
352
352
|
- Downloads and validates new revisions when changed.
|
|
353
353
|
- Verifies the stored master fingerprint before accepting an update.
|
|
354
354
|
- Applies before the new Codex session proceeds.
|
|
355
|
-
- Uses a short timeout so Codex startup is not blocked for long when
|
|
355
|
+
- Uses a short timeout so Codex startup is not blocked for long when the master is
|
|
356
356
|
offline.
|
|
357
357
|
- Does not require a follower background service in v1.
|
|
358
358
|
|
|
@@ -405,7 +405,7 @@ Canonical names win by default. A same-name local MCP or skill fails unless
|
|
|
405
405
|
12. Should master export rebuilds require a manual command or happen
|
|
406
406
|
automatically? Decision: master watches selected paths and rebuilds
|
|
407
407
|
automatically; manual rebuild exists only for repair/debugging.
|
|
408
|
-
13. Should
|
|
408
|
+
13. Should the master generate a permanent follower join link or require manual
|
|
409
409
|
master address entry? Decision: generate a durable join link, with a
|
|
410
410
|
copy-paste command fallback if custom URL handling is not implemented in the
|
|
411
411
|
first version.
|
|
@@ -428,7 +428,7 @@ Canonical names win by default. A same-name local MCP or skill fails unless
|
|
|
428
428
|
|
|
429
429
|
- macOS support.
|
|
430
430
|
- GitHub-based plaintext secret sync.
|
|
431
|
-
- Push-based orchestration from
|
|
431
|
+
- Push-based orchestration from the master to followers.
|
|
432
432
|
- Mid-session mutation of active Codex behavior.
|
|
433
433
|
- Automatic promotion of follower-local changes.
|
|
434
434
|
- Syncing Codex sessions, history, logs, or SQLite runtime state.
|