create-egregore 0.19.5 → 0.19.7

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