create-egregore 0.17.4 → 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 (110) hide show
  1. package/assets/egregore-launcher.js +1965 -0
  2. package/assets/egregore-launcher.sh +385 -0
  3. package/bin/cli.js +472 -81
  4. package/lib/api.js +2 -2
  5. package/lib/auth.js +255 -20
  6. package/lib/codex-runtime.js +268 -0
  7. package/lib/commands.js +31 -0
  8. package/lib/context.js +1 -1
  9. package/lib/local.js +616 -189
  10. package/lib/setup.js +822 -84
  11. package/lib/ui.js +780 -84
  12. package/package.json +11 -3
  13. package/runtime/codex/.codex/config.toml +15 -0
  14. package/runtime/codex/.codex/hooks/branch-guard.js +234 -0
  15. package/runtime/codex/.codex/hooks.json +17 -0
  16. package/runtime/codex/.codex/skills/activity/SKILL.md +70 -0
  17. package/runtime/codex/.codex/skills/add/SKILL.md +25 -0
  18. package/runtime/codex/.codex/skills/announce/SKILL.md +54 -0
  19. package/runtime/codex/.codex/skills/answer/SKILL.md +25 -0
  20. package/runtime/codex/.codex/skills/archive/SKILL.md +41 -0
  21. package/runtime/codex/.codex/skills/ask/SKILL.md +59 -0
  22. package/runtime/codex/.codex/skills/branch/SKILL.md +25 -0
  23. package/runtime/codex/.codex/skills/checkup/SKILL.md +41 -0
  24. package/runtime/codex/.codex/skills/comment/SKILL.md +25 -0
  25. package/runtime/codex/.codex/skills/commit/SKILL.md +25 -0
  26. package/runtime/codex/.codex/skills/contribute/SKILL.md +25 -0
  27. package/runtime/codex/.codex/skills/dark-mode/SKILL.md +25 -0
  28. package/runtime/codex/.codex/skills/dashboard/SKILL.md +59 -0
  29. package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +70 -0
  30. package/runtime/codex/.codex/skills/delete-user/SKILL.md +25 -0
  31. package/runtime/codex/.codex/skills/edit/SKILL.md +25 -0
  32. package/runtime/codex/.codex/skills/edit-process/SKILL.md +25 -0
  33. package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +25 -0
  34. package/runtime/codex/.codex/skills/emissary/SKILL.md +41 -0
  35. package/runtime/codex/.codex/skills/env/SKILL.md +25 -0
  36. package/runtime/codex/.codex/skills/handoff/SKILL.md +67 -0
  37. package/runtime/codex/.codex/skills/harvest/SKILL.md +107 -0
  38. package/runtime/codex/.codex/skills/infra/SKILL.md +41 -0
  39. package/runtime/codex/.codex/skills/ingest/SKILL.md +25 -0
  40. package/runtime/codex/.codex/skills/ingest-google/SKILL.md +25 -0
  41. package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +41 -0
  42. package/runtime/codex/.codex/skills/invite/SKILL.md +40 -0
  43. package/runtime/codex/.codex/skills/issue/SKILL.md +41 -0
  44. package/runtime/codex/.codex/skills/me/SKILL.md +25 -0
  45. package/runtime/codex/.codex/skills/meeting/SKILL.md +41 -0
  46. package/runtime/codex/.codex/skills/note/SKILL.md +25 -0
  47. package/runtime/codex/.codex/skills/onboarding/SKILL.md +41 -0
  48. package/runtime/codex/.codex/skills/pr/SKILL.md +25 -0
  49. package/runtime/codex/.codex/skills/project/SKILL.md +41 -0
  50. package/runtime/codex/.codex/skills/pull/SKILL.md +25 -0
  51. package/runtime/codex/.codex/skills/push/SKILL.md +25 -0
  52. package/runtime/codex/.codex/skills/quest/SKILL.md +77 -0
  53. package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +25 -0
  54. package/runtime/codex/.codex/skills/reflect/SKILL.md +41 -0
  55. package/runtime/codex/.codex/skills/review-pr/SKILL.md +41 -0
  56. package/runtime/codex/.codex/skills/save/SKILL.md +62 -0
  57. package/runtime/codex/.codex/skills/setup/SKILL.md +25 -0
  58. package/runtime/codex/.codex/skills/summon/SKILL.md +41 -0
  59. package/runtime/codex/.codex/skills/sync-repos/SKILL.md +25 -0
  60. package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +25 -0
  61. package/runtime/codex/.codex/skills/telemetry/SKILL.md +25 -0
  62. package/runtime/codex/.codex/skills/test/SKILL.md +41 -0
  63. package/runtime/codex/.codex/skills/the-spiral/SKILL.md +84 -0
  64. package/runtime/codex/.codex/skills/todo/SKILL.md +41 -0
  65. package/runtime/codex/.codex/skills/tui-design/SKILL.md +41 -0
  66. package/runtime/codex/.codex/skills/tutorial/SKILL.md +41 -0
  67. package/runtime/codex/.codex/skills/update/SKILL.md +25 -0
  68. package/runtime/codex/.codex/skills/view/SKILL.md +41 -0
  69. package/runtime/codex/.codex/skills/visual-explain/SKILL.md +41 -0
  70. package/runtime/codex/.codex/skills/wrap/SKILL.md +55 -0
  71. package/runtime/codex/.codex/spec-manifest.json +108 -0
  72. package/runtime/codex/AGENTS.md +328 -0
  73. package/runtime/codex/bin/activity-data.sh +180 -0
  74. package/runtime/codex/bin/agent.sh +669 -0
  75. package/runtime/codex/bin/codex-install-skills.sh +78 -0
  76. package/runtime/codex/bin/codex-render-spec.mjs +308 -0
  77. package/runtime/codex/bin/codex-session-start.sh +238 -0
  78. package/runtime/codex/bin/codex-skill-render.mjs +315 -0
  79. package/runtime/codex/bin/codex-sync-skills.sh +223 -0
  80. package/runtime/codex/bin/dashboard-data.sh +178 -0
  81. package/runtime/codex/bin/graph-batch.sh +60 -0
  82. package/runtime/codex/bin/graph-op.sh +397 -0
  83. package/runtime/codex/bin/graph-wal.sh +228 -0
  84. package/runtime/codex/bin/graph.sh +141 -0
  85. package/runtime/codex/bin/handoff-pr-backfill.sh +86 -0
  86. package/runtime/codex/bin/handoff-run.sh +569 -0
  87. package/runtime/codex/bin/handoff-save-egregore.sh +131 -0
  88. package/runtime/codex/bin/index-handoff.sh +306 -0
  89. package/runtime/codex/bin/lib/artifact-id.sh +46 -0
  90. package/runtime/codex/bin/lib/config.sh +71 -0
  91. package/runtime/codex/bin/lib/context.sh +511 -0
  92. package/runtime/codex/bin/lib/dashboard-artifact.sh +142 -0
  93. package/runtime/codex/bin/lib/edit-loopback.mjs +652 -0
  94. package/runtime/codex/bin/lib/ensure-develop.sh +183 -0
  95. package/runtime/codex/bin/lib/git-sync.sh +262 -0
  96. package/runtime/codex/bin/lib/greeting.sh +432 -0
  97. package/runtime/codex/bin/lib/hash.sh +13 -0
  98. package/runtime/codex/bin/lib/identity.sh +164 -0
  99. package/runtime/codex/bin/lib/permissions.sh +106 -0
  100. package/runtime/codex/bin/lib/time.sh +56 -0
  101. package/runtime/codex/bin/lib/worktree-links.sh +54 -0
  102. package/runtime/codex/bin/notify.sh +207 -0
  103. package/runtime/codex/bin/publish-artifact.sh +187 -0
  104. package/runtime/codex/bin/publish-references.sh +144 -0
  105. package/runtime/codex/bin/repo-state.sh +99 -0
  106. package/runtime/codex/bin/telemetry.sh +350 -0
  107. package/runtime/codex/bin/worktree-create.sh +115 -0
  108. package/runtime/codex/bin/worktree-remove.sh +50 -0
  109. package/runtime/codex/bin/worktree.sh +199 -0
  110. package/runtime/codex/manifest.json +162 -0
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+
4
+ const WIDTH = 72;
5
+
6
+ function usage() {
7
+ console.error(`usage:
8
+ node bin/codex-skill-render.mjs classify-graph [--mode connected|local] [--attempt initial|retry] [file|-]
9
+ node bin/codex-skill-render.mjs activity-card [file|-]
10
+ node bin/codex-skill-render.mjs dashboard-card [file|-]
11
+ node bin/codex-skill-render.mjs handoff-card [file|-]`);
12
+ process.exit(2);
13
+ }
14
+
15
+ function readInput(file) {
16
+ if (!file || file === '-') {
17
+ return fs.readFileSync(0, 'utf8');
18
+ }
19
+ return fs.readFileSync(file, 'utf8');
20
+ }
21
+
22
+ function readJson(file) {
23
+ const raw = readInput(file).trim() || '{}';
24
+ try {
25
+ return JSON.parse(raw);
26
+ } catch (error) {
27
+ console.error(`invalid JSON${file ? ` in ${file}` : ''}: ${error.message}`);
28
+ process.exit(1);
29
+ }
30
+ }
31
+
32
+ function parseFlags(args) {
33
+ const flags = {};
34
+ const rest = [];
35
+ for (let i = 0; i < args.length; i += 1) {
36
+ const arg = args[i];
37
+ if (arg === '--mode' || arg === '--attempt') {
38
+ flags[arg.slice(2)] = args[i + 1];
39
+ i += 1;
40
+ } else {
41
+ rest.push(arg);
42
+ }
43
+ }
44
+ return { flags, rest };
45
+ }
46
+
47
+ function asArray(value) {
48
+ return Array.isArray(value) ? value : [];
49
+ }
50
+
51
+ function firstArray(data, paths) {
52
+ for (const p of paths) {
53
+ const value = p.split('.').reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : undefined), data);
54
+ if (Array.isArray(value)) return value;
55
+ }
56
+ return [];
57
+ }
58
+
59
+ function valueOf(item, keys, fallback = '') {
60
+ for (const key of keys) {
61
+ if (item && item[key] !== undefined && item[key] !== null && String(item[key]).trim() !== '') {
62
+ return String(item[key]);
63
+ }
64
+ }
65
+ return fallback;
66
+ }
67
+
68
+ function truncate(value, max) {
69
+ const text = String(value || '').replace(/\s+/g, ' ').trim();
70
+ if (text.length <= max) return text;
71
+ return `${text.slice(0, Math.max(0, max - 3))}...`;
72
+ }
73
+
74
+ function padLine(left, right = '') {
75
+ const l = truncate(left, WIDTH - 4);
76
+ const r = truncate(right, WIDTH - 4);
77
+ const space = Math.max(1, WIDTH - 4 - l.length - r.length);
78
+ return `│ ${l}${' '.repeat(space)}${r} │`;
79
+ }
80
+
81
+ function topRule() {
82
+ return `┌${'─'.repeat(WIDTH - 2)}┐`;
83
+ }
84
+
85
+ function rule() {
86
+ return `├${'─'.repeat(WIDTH - 2)}┤`;
87
+ }
88
+
89
+ function bottomRule() {
90
+ return `└${'─'.repeat(WIDTH - 2)}┘`;
91
+ }
92
+
93
+ function graphFields(data) {
94
+ return {
95
+ status: data.graph_status || data.graphStatus || data.graph?.status || '',
96
+ reason: data.graph_reason || data.graphReason || data.graph?.reason || '',
97
+ };
98
+ }
99
+
100
+ function classifyGraph(data, options = {}) {
101
+ const mode = options.mode || data.mode || 'connected';
102
+ const attempt = options.attempt || 'initial';
103
+ const { status, reason } = graphFields(data);
104
+
105
+ if (mode === 'local') {
106
+ return {
107
+ status: 'local',
108
+ retry: false,
109
+ reason: reason || 'local_mode',
110
+ message: 'Local mode uses memory files as the source of truth.',
111
+ };
112
+ }
113
+
114
+ if (status === 'connected') {
115
+ return {
116
+ status: 'connected',
117
+ retry: false,
118
+ reason: '',
119
+ message: 'Graph connected.',
120
+ };
121
+ }
122
+
123
+ if (reason === 'unreachable' && attempt !== 'retry') {
124
+ return {
125
+ status: 'retry',
126
+ retry: true,
127
+ reason,
128
+ message: 'Connected-mode graph was unreachable; retry with network escalation.',
129
+ };
130
+ }
131
+
132
+ const finalReason = reason || status || 'offline';
133
+ return {
134
+ status: 'offline',
135
+ retry: false,
136
+ reason: finalReason,
137
+ message: `Graph offline (${finalReason}). Render filesystem fallback.`,
138
+ };
139
+ }
140
+
141
+ function renderActivity(data) {
142
+ const graph = classifyGraph(data, { mode: data.mode || 'connected', attempt: 'retry' });
143
+ const org = data.org || data.organization || 'Egregore';
144
+ const date = data.date || '';
145
+ const me = data.me?.github || data.me?.name || data.github_username || '';
146
+ const handoffs = firstArray(data, ['handoffs', 'pending_handoffs', 'me.handoffs', 'addressed_handoffs']);
147
+ const questions = firstArray(data, ['questions', 'pending_questions', 'me.questions']);
148
+ const prs = asArray(data.prs);
149
+ const mine = firstArray(data, ['local_sessions.my_sessions', 'my_sessions', 'sessions']);
150
+ const team = firstArray(data, ['local_sessions.team_sessions', 'team_sessions', 'team.sessions']);
151
+
152
+ const lines = [topRule(), padLine('ACTIVITY', [org, me, date].filter(Boolean).join(' · ')), rule()];
153
+ if (graph.status !== 'connected') {
154
+ lines.push(padLine(`graph: ${graph.status}`, graph.reason));
155
+ lines.push(rule());
156
+ }
157
+
158
+ lines.push(padLine('FOR YOU'));
159
+ if (questions.length === 0 && handoffs.length === 0) {
160
+ lines.push(padLine('nothing pending'));
161
+ } else {
162
+ questions.slice(0, 4).forEach((q) => {
163
+ lines.push(padLine(`? ${valueOf(q, ['from', 'author', 'asker'], 'someone')}: ${valueOf(q, ['topic', 'title', 'question'], 'question')}`));
164
+ });
165
+ handoffs.slice(0, 4).forEach((h) => {
166
+ lines.push(padLine(`> ${valueOf(h, ['author', 'from'], 'someone')}: ${valueOf(h, ['topic', 'title'], 'handoff')}`));
167
+ });
168
+ }
169
+
170
+ lines.push(rule());
171
+ lines.push(padLine('RECENT WORK'));
172
+ if (mine.length === 0) {
173
+ lines.push(padLine('no recent local sessions found'));
174
+ } else {
175
+ mine.slice(0, 5).forEach((s) => {
176
+ lines.push(padLine(`${valueOf(s, ['date', 'when'], '')} ${valueOf(s, ['topic', 'title', 'branch'], 'session')}`, valueOf(s, ['type', 'status', 'duration'], '')));
177
+ });
178
+ }
179
+
180
+ if (team.length > 0 || prs.length > 0) {
181
+ lines.push(rule());
182
+ lines.push(padLine('AROUND'));
183
+ team.slice(0, 3).forEach((s) => {
184
+ lines.push(padLine(`${valueOf(s, ['author', 'name'], 'teammate')}: ${valueOf(s, ['topic', 'title', 'branch'], 'session')}`));
185
+ });
186
+ prs.slice(0, 3).forEach((pr) => {
187
+ lines.push(padLine(`#${valueOf(pr, ['number'], '')} ${valueOf(pr, ['title'], 'pull request')}`, valueOf(pr.author || {}, ['login', 'name'], '')));
188
+ });
189
+ }
190
+
191
+ lines.push(rule());
192
+ lines.push(padLine('Focus choices: handoffs, questions, recent work, dashboard, done'));
193
+ lines.push(bottomRule());
194
+ return lines.join('\n');
195
+ }
196
+
197
+ function humanTopic(session) {
198
+ const topic = valueOf(session, ['topic', 'title'], '');
199
+ if (topic) return topic;
200
+ const branch = valueOf(session, ['branch'], '');
201
+ if (branch && !['develop', 'main', 'master'].includes(branch)) {
202
+ return branch.replace(/^dev\/[^/]+\//, '').replace(/[-_]+/g, ' ');
203
+ }
204
+ return valueOf(session, ['id', 'date'], 'session');
205
+ }
206
+
207
+ function renderDashboard(data) {
208
+ const graph = classifyGraph(data, { mode: data.mode || 'connected', attempt: 'retry' });
209
+ const org = data.org || 'Egregore';
210
+ const date = data.date || '';
211
+ const me = data.me?.github || data.me?.name || data.github_username || '';
212
+ const current = data.current_session || {};
213
+ const sessions = firstArray(data, ['sessions', 'local_sessions.sessions']);
214
+ const todos = firstArray(data, ['todos', 'open_todos']);
215
+ const quests = firstArray(data, ['quests', 'active_quests']);
216
+ const handoffs = firstArray(data, ['handoffs', 'pending_handoffs']);
217
+ const threads = firstArray(data, ['open_threads', 'threads']);
218
+ const stats = data.stats || {};
219
+
220
+ const lines = [topRule(), padLine('DASHBOARD', [org, me, date].filter(Boolean).join(' · ')), rule()];
221
+ if (graph.status !== 'connected') {
222
+ lines.push(padLine(`graph: ${graph.status}`, graph.reason));
223
+ lines.push(rule());
224
+ }
225
+
226
+ lines.push(padLine(`* ${humanTopic(current) || '(starting...)'}`, 'active'));
227
+
228
+ if (threads.length > 0) {
229
+ lines.push(rule());
230
+ lines.push(padLine('PICK UP WHERE YOU LEFT OFF'));
231
+ threads.slice(0, 5).forEach((thread) => {
232
+ lines.push(padLine(`- ${valueOf(thread, ['text', 'thread', 'title', 'topic'], 'open thread')}`));
233
+ });
234
+ }
235
+
236
+ if (todos.length > 0) {
237
+ lines.push(rule());
238
+ lines.push(padLine(`OPEN TODOS (${todos.length})`));
239
+ todos.slice(0, 3).forEach((todo, index) => {
240
+ lines.push(padLine(`[${index + 1}] ${valueOf(todo, ['text', 'title'], 'todo')}`, valueOf(todo, ['status'], '')));
241
+ });
242
+ }
243
+
244
+ if (handoffs.length > 0) {
245
+ lines.push(rule());
246
+ lines.push(padLine(`HANDOFFS (${handoffs.length} pending)`));
247
+ handoffs.slice(0, 3).forEach((handoff) => {
248
+ lines.push(padLine(`${valueOf(handoff, ['author', 'from'], 'someone')}: ${valueOf(handoff, ['topic', 'title'], 'handoff')}`, valueOf(handoff, ['when', 'date'], '')));
249
+ });
250
+ }
251
+
252
+ lines.push(rule());
253
+ lines.push(padLine(`SESSIONS (${data.range_label || 'recent'})`));
254
+ if (sessions.length === 0) {
255
+ lines.push(padLine('No activity recorded yet'));
256
+ } else {
257
+ sessions.slice(0, 5).forEach((session) => {
258
+ lines.push(padLine(`${valueOf(session, ['date', 'when'], '')} ${humanTopic(session)}`, valueOf(session, ['status', 'type'], '')));
259
+ });
260
+ }
261
+
262
+ if (quests.length > 0) {
263
+ lines.push(rule());
264
+ lines.push(padLine(`QUESTS (${quests.length} active)`));
265
+ quests.slice(0, 3).forEach((quest) => {
266
+ lines.push(padLine(valueOf(quest, ['id', 'quest', 'slug', 'title'], 'quest'), valueOf(quest, ['status', 'summary'], '')));
267
+ });
268
+ }
269
+
270
+ lines.push(rule());
271
+ const total = stats.totalSessions ?? data.local_sessions?.session_count ?? sessions.length;
272
+ const wrapped = stats.wrappedSessions ?? sessions.filter((s) => valueOf(s, ['status', 'type'], '').includes('wrap')).length;
273
+ lines.push(padLine(`${total} sessions · ${wrapped}/${total} wrapped · ${stats.openTodos ?? todos.length} open todos`));
274
+ lines.push(padLine("What's next?"));
275
+ lines.push(bottomRule());
276
+ return lines.join('\n');
277
+ }
278
+
279
+ function renderHandoff(data) {
280
+ const file = data.file ? `memory/${data.file}` : '';
281
+ const statuses = [
282
+ data.graphStatus ? `graph=${data.graphStatus}` : '',
283
+ data.memoryStatus ? `memory=${data.memoryStatus}` : '',
284
+ data.notifyStatus ? `notify=${data.notifyStatus}` : '',
285
+ ].filter(Boolean).join(' · ');
286
+
287
+ const lines = [topRule(), padLine('HANDOFF', data.author || ''), rule()];
288
+ lines.push(padLine(`Topic: ${data.topic || '(untitled)'}`));
289
+ if (data.recipient) lines.push(padLine(`To: ${data.recipient}`));
290
+ if (file) lines.push(padLine(`Path: ${file}`));
291
+ if (data.artifactUrl) lines.push(padLine(`Link: ${data.artifactUrl}`));
292
+ if (statuses) lines.push(padLine(statuses));
293
+ lines.push(bottomRule());
294
+ return lines.join('\n');
295
+ }
296
+
297
+ const [command, ...rawArgs] = process.argv.slice(2);
298
+ if (!command) usage();
299
+
300
+ if (command === 'classify-graph') {
301
+ const { flags, rest } = parseFlags(rawArgs);
302
+ const data = readJson(rest[0]);
303
+ console.log(JSON.stringify(classifyGraph(data, flags)));
304
+ } else if (command === 'activity-card') {
305
+ const data = readJson(rawArgs[0]);
306
+ console.log(renderActivity(data));
307
+ } else if (command === 'dashboard-card') {
308
+ const data = readJson(rawArgs[0]);
309
+ console.log(renderDashboard(data));
310
+ } else if (command === 'handoff-card') {
311
+ const data = readJson(rawArgs[0]);
312
+ console.log(renderHandoff(data));
313
+ } else {
314
+ usage();
315
+ }
@@ -0,0 +1,223 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5
+ CLAUDE_DIR="$SCRIPT_DIR/.claude/skills"
6
+ CODEX_DIR="$SCRIPT_DIR/.codex/skills"
7
+ CHECK=0
8
+
9
+ NATIVE_SKILLS=(
10
+ activity
11
+ handoff
12
+ wrap
13
+ announce
14
+ harvest
15
+ the-spiral
16
+ dashboard
17
+ deep-reflect
18
+ quest
19
+ invite
20
+ ask
21
+ save
22
+ )
23
+
24
+ STRUCTURED_UX_SKILLS=(
25
+ activity
26
+ dashboard
27
+ handoff
28
+ wrap
29
+ todo
30
+ quest
31
+ project
32
+ issue
33
+ test
34
+ qa
35
+ checkup
36
+ infra
37
+ graph-maintain
38
+ telemetry-admin
39
+ waitlist
40
+ hosting
41
+ review-pr
42
+ triage
43
+ eval
44
+ eval-multiagent
45
+ summon
46
+ reflect
47
+ deep-reflect
48
+ archive
49
+ meeting
50
+ ingest-user-interview
51
+ harvest
52
+ tutorial
53
+ onboarding
54
+ emissary
55
+ launch-site
56
+ view
57
+ visual-explain
58
+ tui-design
59
+ )
60
+
61
+ GENERATED_MARKER='generated-by: bin/codex-sync-skills.sh'
62
+
63
+ while [ $# -gt 0 ]; do
64
+ case "$1" in
65
+ --check) CHECK=1; shift ;;
66
+ --help|-h)
67
+ echo "usage: bin/codex-sync-skills.sh [--check]"
68
+ exit 0
69
+ ;;
70
+ *) echo "unknown option: $1" >&2; exit 2 ;;
71
+ esac
72
+ done
73
+
74
+ is_native() {
75
+ local name="$1"
76
+ local native
77
+ for native in "${NATIVE_SKILLS[@]}"; do
78
+ [ "$name" = "$native" ] && return 0
79
+ done
80
+ return 1
81
+ }
82
+
83
+ is_structured_ux() {
84
+ local name="$1"
85
+ local structured
86
+ for structured in "${STRUCTURED_UX_SKILLS[@]}"; do
87
+ [ "$name" = "$structured" ] && return 0
88
+ done
89
+ return 1
90
+ }
91
+
92
+ skill_description() {
93
+ local file="$1"
94
+ awk '
95
+ NR == 1 && $0 == "---" { in_fm = 1; next }
96
+ in_fm && $0 == "---" { exit }
97
+ in_fm && /^description:/ {
98
+ sub(/^description:[[:space:]]*/, "")
99
+ gsub(/^"|"$/, "")
100
+ print
101
+ exit
102
+ }
103
+ ' "$file"
104
+ }
105
+
106
+ yaml_single_quote() {
107
+ local value="$1"
108
+ printf "'%s'" "$(printf '%s' "$value" | sed "s/'/''/g")"
109
+ }
110
+
111
+ render_adapter() {
112
+ local name="$1"
113
+ local source="$2"
114
+ local description="$3"
115
+ local structured="$4"
116
+ [ -n "$description" ] || description="Fallback Codex adapter for the Egregore ${name} workflow."
117
+ local description_yaml
118
+ description_yaml="$(yaml_single_quote "$description")"
119
+ cat <<EOF
120
+ ---
121
+ name: ${name}
122
+ description: ${description_yaml}
123
+ ---
124
+
125
+ <!-- ${GENERATED_MARKER} -->
126
+
127
+ # Egregore ${name} Adapter
128
+
129
+ This is fallback coverage for a long-tail Egregore workflow that has not been
130
+ ported to a hand-written Codex-native skill yet.
131
+
132
+ Use the project shell and filesystem directly. Do not invoke Claude Code
133
+ commands. Translate interactive choices to structured Codex question tooling
134
+ when it is available; otherwise render compact numbered choices with an
135
+ \`Other:\` option and wait for the user.
136
+
137
+ EOF
138
+ if [ "$structured" = "1" ]; then
139
+ cat <<EOF
140
+ ## Structured UX parity
141
+
142
+ This workflow has a Claude skill with user-visible structured output. After
143
+ reading \`${source}\`, reproduce the same visible UX in Codex:
144
+
145
+ - Preserve TUI boxes, markdown tables, rich cards, browser artifact rendering,
146
+ exact confirmation blocks, and "no preamble" rules from the source skill.
147
+ - Use the source skill's frame width, section order, labels, status footer,
148
+ and examples as the contract for the final response.
149
+ - Never replace a required box/table/card/artifact view with a prose summary
150
+ unless the user explicitly asks for a summary.
151
+ - When the source says to output a TUI box directly, paste that box as the
152
+ visible response, preferably in a \`text\` fenced block.
153
+ - Never show raw JSON, raw command output, or unformatted script output when
154
+ the source skill requires formatted status or rendered output.
155
+
156
+ EOF
157
+ fi
158
+ cat <<EOF
159
+ 1. Read \`${source}\` for the workflow details.
160
+ 2. Run the referenced \`bin/\` scripts directly from Codex.
161
+ 3. Treat graph, publish, and notify steps as best-effort unless that workflow
162
+ explicitly says they are required.
163
+ 4. Keep local-mode behavior filesystem-first and avoid graph or notification
164
+ calls when \`egregore.json\` declares \`"mode": "local"\`.
165
+ 5. Never call the deprecated \`egregore-handoff\` CLI for Egregore project
166
+ handoffs.
167
+ EOF
168
+ }
169
+
170
+ [ -d "$CLAUDE_DIR" ] || { echo "missing Claude skills directory: $CLAUDE_DIR" >&2; exit 1; }
171
+ mkdir -p "$CODEX_DIR"
172
+
173
+ changed=0
174
+ native_present=0
175
+ adapter_present=0
176
+
177
+ for native in "${NATIVE_SKILLS[@]}"; do
178
+ if [ -f "$CODEX_DIR/$native/SKILL.md" ]; then
179
+ native_present=$((native_present + 1))
180
+ else
181
+ echo "missing native Codex skill: $native" >&2
182
+ changed=1
183
+ fi
184
+ done
185
+
186
+ while IFS= read -r source_file; do
187
+ name="$(basename "$(dirname "$source_file")")"
188
+ is_native "$name" && continue
189
+
190
+ target_dir="$CODEX_DIR/$name"
191
+ target_file="$target_dir/SKILL.md"
192
+ if [ -f "$target_file" ] && ! grep -q "$GENERATED_MARKER" "$target_file" 2>/dev/null; then
193
+ continue
194
+ fi
195
+
196
+ description="$(skill_description "$source_file")"
197
+ rel_source=".claude/skills/$name/SKILL.md"
198
+ structured=0
199
+ is_structured_ux "$name" && structured=1
200
+ tmp="$(mktemp -t codex-skill-adapter-XXXXXX)"
201
+ render_adapter "$name" "$rel_source" "$description" "$structured" > "$tmp"
202
+
203
+ if [ ! -f "$target_file" ] || ! cmp -s "$tmp" "$target_file"; then
204
+ changed=1
205
+ if [ "$CHECK" = "1" ]; then
206
+ echo "adapter out of date: $name"
207
+ else
208
+ mkdir -p "$target_dir"
209
+ cp "$tmp" "$target_file"
210
+ echo "generated adapter: $name"
211
+ fi
212
+ fi
213
+ rm -f "$tmp"
214
+ done < <(find "$CLAUDE_DIR" -mindepth 2 -maxdepth 2 -name SKILL.md | sort)
215
+
216
+ adapter_present="$({ grep -rl "$GENERATED_MARKER" "$CODEX_DIR"/*/SKILL.md 2>/dev/null || true; } | wc -l | tr -d ' ')"
217
+
218
+ if [ "$CHECK" = "1" ] && [ "$changed" -ne 0 ]; then
219
+ echo "codex skills out of sync (native: $native_present/${#NATIVE_SKILLS[@]}, adapters: $adapter_present)" >&2
220
+ exit 1
221
+ fi
222
+
223
+ echo "codex skills synced (native: $native_present/${#NATIVE_SKILLS[@]}, adapters: $adapter_present)"