triples-agentic 2.4.0 → 2.4.4
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 +16 -5
- package/package.json +1 -1
- package/src/bin/setup/codex.js +230 -0
- package/src/bin/setup.js +79 -75
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ npx triples-agentic all # all platforms
|
|
|
112
112
|
# Global install
|
|
113
113
|
npx triples-agentic claude --global # → ~/.claude/skills/
|
|
114
114
|
npx triples-agentic cursor --global # → ~/.cursor/rules/
|
|
115
|
-
npx triples-agentic codex --global # → ~/.codex/
|
|
115
|
+
npx triples-agentic codex --global # → ~/.codex/skills/
|
|
116
116
|
npx triples-agentic windsurf --global # → ~/.codeium/windsurf/rules/
|
|
117
117
|
|
|
118
118
|
# Install into a specific directory
|
|
@@ -141,7 +141,7 @@ triples-agentic claude # direct install for Claude Code
|
|
|
141
141
|
| **Claude Code** | `.claude/skills/*.md` | `.claude/settings.json` (PreToolUse hook) |
|
|
142
142
|
| **Cursor AI** | `.cursor/rules/*.mdc` | `.cursor/rules/triples-safety.mdc` (always-applied rule) |
|
|
143
143
|
| **GitHub Copilot** | `.github/instructions/*.instructions.md` | `.github/instructions/triples-safety.instructions.md` |
|
|
144
|
-
| **OpenAI Codex** |
|
|
144
|
+
| **OpenAI Codex** | `.codex/skills/<skill>/SKILL.md` (project) / `~/.codex/skills/<skill>/SKILL.md` (global) | `.codex/config.toml` / `~/.codex/config.toml` (PreToolUse hook) |
|
|
145
145
|
| **Windsurf** | `.windsurfrules` | `.windsurf/hooks.json` (pre_run_command hook) |
|
|
146
146
|
|
|
147
147
|
Global install paths:
|
|
@@ -150,7 +150,7 @@ Global install paths:
|
|
|
150
150
|
|---|---|---|
|
|
151
151
|
| Claude Code | `~/.claude/skills/` | `~/.claude/settings.json` |
|
|
152
152
|
| Cursor AI | `~/.cursor/rules/` | `~/.cursor/rules/triples-safety.mdc` |
|
|
153
|
-
| OpenAI Codex | `~/.codex/
|
|
153
|
+
| OpenAI Codex | `~/.codex/skills/` | `~/.codex/config.toml` |
|
|
154
154
|
| Windsurf | `~/.codeium/windsurf/rules/` | `~/.codeium/windsurf/hooks.json` |
|
|
155
155
|
|
|
156
156
|
---
|
|
@@ -214,6 +214,15 @@ SeoYeon walks you through the complete workflow and delegates to each agent.
|
|
|
214
214
|
/seoyeon status Check current run state
|
|
215
215
|
```
|
|
216
216
|
|
|
217
|
+
### In Codex
|
|
218
|
+
Use `/skills` to browse installed TripleS skills, or mention them directly in your prompt:
|
|
219
|
+
|
|
220
|
+
```text
|
|
221
|
+
Use $seoyeon to orchestrate this feature from PRD through QA.
|
|
222
|
+
Use $jiwoo-prd to draft the PRD for this feature.
|
|
223
|
+
Use $kaede-backend to implement the backend task from the breakdown.
|
|
224
|
+
```
|
|
225
|
+
|
|
217
226
|
### With other coding assistants
|
|
218
227
|
Ask for the agent by name — e.g., "Act as JiWoo and create a PRD for [description]"
|
|
219
228
|
|
|
@@ -280,7 +289,9 @@ triples-agentic/
|
|
|
280
289
|
│ │ ├── task-breakdown.md # → workspace/TASK_BREAKDOWN.md
|
|
281
290
|
│ │ └── test-case.md # → workspace/TEST_CASES.md
|
|
282
291
|
│ └── bin/
|
|
283
|
-
│
|
|
292
|
+
│ ├── setup.js # Platform installer CLI entrypoint
|
|
293
|
+
│ └── setup/
|
|
294
|
+
│ └── codex.js # Codex-specific installer
|
|
284
295
|
├── docs/
|
|
285
296
|
│ └── workflow.md # Full workflow diagram + agent roster
|
|
286
297
|
└── CHANGELOG.md
|
|
@@ -292,9 +303,9 @@ your-project/
|
|
|
292
303
|
│ └── settings.json # Claude Code PreToolUse safety hook
|
|
293
304
|
├── .cursor/rules/ # Cursor AI agent rules + safety rule
|
|
294
305
|
├── .github/instructions/ # Copilot agent instructions + safety instruction
|
|
306
|
+
├── .codex/skills/ # Codex skill bundles (project install)
|
|
295
307
|
├── .codex/config.toml # Codex PreToolUse safety hook
|
|
296
308
|
├── .windsurf/hooks.json # Windsurf pre_run_command safety hook
|
|
297
|
-
├── AGENTS.md # Codex agent context
|
|
298
309
|
└── .windsurfrules # Windsurf agent rules
|
|
299
310
|
```
|
|
300
311
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
const CODEX_AGENT_SKILL_METADATA = {
|
|
5
|
+
seoyeon: {
|
|
6
|
+
description: 'Coordinate the full TripleS delivery pipeline across PRD, RFC, task breakdown, development, test cases, and QA. Use when the user wants end-to-end orchestration, status, routing, or delivery summaries.',
|
|
7
|
+
shortDescription: 'Orchestrate the full TripleS delivery flow',
|
|
8
|
+
defaultPrompt: 'Use $seoyeon to orchestrate this feature from PRD through QA.',
|
|
9
|
+
},
|
|
10
|
+
'jiwoo-prd': {
|
|
11
|
+
description: 'Create, review, and refine implementation-ready product requirements documents for TripleS workflows. Use when the user needs a PRD with explicit quality gates and product clarifications.',
|
|
12
|
+
shortDescription: 'Create and review implementation-ready PRDs',
|
|
13
|
+
defaultPrompt: 'Use $jiwoo-prd to draft or revise the PRD for this feature.',
|
|
14
|
+
},
|
|
15
|
+
'yooyeon-rfc': {
|
|
16
|
+
description: 'Create, review, and refine implementation-ready technical RFCs that trace back to an approved PRD. Use when the user needs architecture decisions, API contracts, risks, or rollout plans documented.',
|
|
17
|
+
shortDescription: 'Create and review implementation-ready RFCs',
|
|
18
|
+
defaultPrompt: 'Use $yooyeon-rfc to turn this PRD into a technical RFC.',
|
|
19
|
+
},
|
|
20
|
+
'nakyoung-tasks': {
|
|
21
|
+
description: 'Break approved PRDs and RFCs into estimated, dependency-aware engineering tasks. Use when the user needs a delivery plan, story points, task sequencing, or platform assignments.',
|
|
22
|
+
shortDescription: 'Break work into ready-to-build tasks',
|
|
23
|
+
defaultPrompt: 'Use $nakyoung-tasks to create the task breakdown for this project.',
|
|
24
|
+
},
|
|
25
|
+
'yubin-frontend': {
|
|
26
|
+
description: 'Implement frontend web tasks from the approved TripleS task breakdown with strong accessibility, performance, and testing practices. Use when the user needs web UI implementation work.',
|
|
27
|
+
shortDescription: 'Implement web frontend tasks from breakdown',
|
|
28
|
+
defaultPrompt: 'Use $yubin-frontend to implement the assigned web frontend task.',
|
|
29
|
+
},
|
|
30
|
+
'kaede-backend': {
|
|
31
|
+
description: 'Implement backend APIs, services, and data access layers from the approved TripleS task breakdown. Use when the user needs backend implementation with strong reliability, security, and testing practices.',
|
|
32
|
+
shortDescription: 'Implement backend APIs and services',
|
|
33
|
+
defaultPrompt: 'Use $kaede-backend to implement the assigned backend task.',
|
|
34
|
+
},
|
|
35
|
+
'yeonji-android': {
|
|
36
|
+
description: 'Implement Android tasks in Kotlin and Jetpack Compose from the approved TripleS task breakdown. Use when the user needs Android-native feature work.',
|
|
37
|
+
shortDescription: 'Implement Android features in Kotlin',
|
|
38
|
+
defaultPrompt: 'Use $yeonji-android to implement the assigned Android task.',
|
|
39
|
+
},
|
|
40
|
+
'sohyun-ios': {
|
|
41
|
+
description: 'Implement iOS tasks in Swift and SwiftUI from the approved TripleS task breakdown. Use when the user needs iOS-native feature work with Apple platform conventions.',
|
|
42
|
+
shortDescription: 'Implement iOS features in SwiftUI',
|
|
43
|
+
defaultPrompt: 'Use $sohyun-ios to implement the assigned iOS task.',
|
|
44
|
+
},
|
|
45
|
+
'kotone-flutter': {
|
|
46
|
+
description: 'Implement Flutter tasks in Dart with Riverpod from the approved TripleS task breakdown. Use when the user needs cross-platform mobile feature work.',
|
|
47
|
+
shortDescription: 'Implement Flutter features with Riverpod',
|
|
48
|
+
defaultPrompt: 'Use $kotone-flutter to implement the assigned Flutter task.',
|
|
49
|
+
},
|
|
50
|
+
'lynn-testcase': {
|
|
51
|
+
description: 'Create, review, and refine implementation-ready QA test suites from approved PRDs and RFCs. Use when the user needs structured test cases, coverage gaps, or automation candidates documented.',
|
|
52
|
+
shortDescription: 'Create and review QA test suites',
|
|
53
|
+
defaultPrompt: 'Use $lynn-testcase to create the test case suite for this project.',
|
|
54
|
+
},
|
|
55
|
+
'shion-qa': {
|
|
56
|
+
description: 'Execute approved test suites, file defects, and produce a go/no-go QA recommendation. Use when the user needs test execution, bug reports, or release-readiness assessment.',
|
|
57
|
+
shortDescription: 'Execute tests and issue go/no-go QA reports',
|
|
58
|
+
defaultPrompt: 'Use $shion-qa to execute QA and produce a release recommendation.',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function codexSkillMetadata(agent) {
|
|
63
|
+
return CODEX_AGENT_SKILL_METADATA[agent.slug] || {
|
|
64
|
+
description: `Use when the user wants help from the TripleS ${agent.displayName} workflow.`,
|
|
65
|
+
shortDescription: `Use the ${agent.displayName} TripleS workflow`,
|
|
66
|
+
defaultPrompt: `Use $${agent.slug} for this TripleS workflow.`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function codexSkillFileContent(agent, helpers) {
|
|
71
|
+
const { stripAgentMetadataComments, yamlEscape } = helpers;
|
|
72
|
+
const meta = codexSkillMetadata(agent);
|
|
73
|
+
const references = [];
|
|
74
|
+
|
|
75
|
+
if (agent.knowledgePaths.length) {
|
|
76
|
+
references.push('- Bundled knowledge references live under `references/knowledge/`.');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (agent.templatePaths.length) {
|
|
80
|
+
references.push('- Bundled templates live under `references/templates/`.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (references.length) {
|
|
84
|
+
references.push('- When the original TripleS instructions mention `knowledge/...` or `templates/...`, resolve them to those bundled copies inside this skill directory.');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const preface = references.length
|
|
88
|
+
? ['## Bundled References', '', ...references, ''].join('\n')
|
|
89
|
+
: '';
|
|
90
|
+
|
|
91
|
+
return [
|
|
92
|
+
'---',
|
|
93
|
+
`name: "${yamlEscape(agent.slug)}"`,
|
|
94
|
+
`description: "${yamlEscape(meta.description)}"`,
|
|
95
|
+
'---',
|
|
96
|
+
'',
|
|
97
|
+
`# ${agent.displayName}`,
|
|
98
|
+
'',
|
|
99
|
+
preface,
|
|
100
|
+
stripAgentMetadataComments(agent.content),
|
|
101
|
+
'',
|
|
102
|
+
].join('\n');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function codexSkillUiYaml(agent, helpers) {
|
|
106
|
+
const { yamlEscape } = helpers;
|
|
107
|
+
const meta = codexSkillMetadata(agent);
|
|
108
|
+
return [
|
|
109
|
+
'interface:',
|
|
110
|
+
` display_name: "${yamlEscape(agent.displayName)}"`,
|
|
111
|
+
` short_description: "${yamlEscape(meta.shortDescription)}"`,
|
|
112
|
+
` default_prompt: "${yamlEscape(meta.defaultPrompt)}"`,
|
|
113
|
+
'',
|
|
114
|
+
'policy:',
|
|
115
|
+
' allow_implicit_invocation: true',
|
|
116
|
+
'',
|
|
117
|
+
].join('\n');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function writeCodexSkillBundle(skillsRoot, agent, ctx) {
|
|
121
|
+
const { ROOT, KNOWLEDGE_DIR, writeFile } = ctx;
|
|
122
|
+
const skillDir = join(skillsRoot, agent.slug);
|
|
123
|
+
writeFile(join(skillDir, 'SKILL.md'), codexSkillFileContent(agent, ctx));
|
|
124
|
+
writeFile(join(skillDir, 'agents', 'openai.yaml'), codexSkillUiYaml(agent, ctx));
|
|
125
|
+
|
|
126
|
+
for (const relPath of agent.knowledgePaths) {
|
|
127
|
+
const sourcePath = join(KNOWLEDGE_DIR, relPath);
|
|
128
|
+
if (existsSync(sourcePath)) {
|
|
129
|
+
writeFile(join(skillDir, 'references', 'knowledge', relPath), readFileSync(sourcePath, 'utf-8'));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const relPath of agent.templatePaths) {
|
|
134
|
+
const sourcePath = join(ROOT, 'templates', relPath);
|
|
135
|
+
if (existsSync(sourcePath)) {
|
|
136
|
+
writeFile(join(skillDir, 'references', 'templates', relPath), readFileSync(sourcePath, 'utf-8'));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function maybeRewriteLegacyCodexAgentsDoc(targetPath, skillsRoot, ctx) {
|
|
142
|
+
const { display, writeFile } = ctx;
|
|
143
|
+
if (!existsSync(targetPath)) return;
|
|
144
|
+
|
|
145
|
+
let existing = '';
|
|
146
|
+
try { existing = readFileSync(targetPath, 'utf-8'); } catch { return; }
|
|
147
|
+
if (!existing.includes('TripleS Agent Orchestrator')) return;
|
|
148
|
+
|
|
149
|
+
const compatDoc = [
|
|
150
|
+
'# TripleS Agentic for Codex',
|
|
151
|
+
'',
|
|
152
|
+
'TripleS workflows are installed as Codex skills, not as a generated agent catalog in this file.',
|
|
153
|
+
'',
|
|
154
|
+
`- Skills root: \`${display(skillsRoot)}\``,
|
|
155
|
+
'- In Codex, use `/skills` to browse them or mention a skill directly such as `$seoyeon` or `$jiwoo-prd`.',
|
|
156
|
+
'- The actual workflow instructions now live in each skill bundle under `SKILL.md`.',
|
|
157
|
+
'',
|
|
158
|
+
].join('\n');
|
|
159
|
+
|
|
160
|
+
writeFile(targetPath, compatDoc);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function installCodexSettings(base, ctx) {
|
|
164
|
+
const { GLOBAL_PATHS, isGlobal, projectDir, loadCodexHooks, tomlEscape, writeFile } = ctx;
|
|
165
|
+
const hookEntries = loadCodexHooks().filter(entry => entry.event === 'PreToolUse');
|
|
166
|
+
if (hookEntries.length === 0) return;
|
|
167
|
+
|
|
168
|
+
const configPath = isGlobal && !base
|
|
169
|
+
? join(GLOBAL_PATHS.codex, 'config.toml')
|
|
170
|
+
: join(base || projectDir, '.codex', 'config.toml');
|
|
171
|
+
let existing = existsSync(configPath) ? readFileSync(configPath, 'utf-8') : '';
|
|
172
|
+
|
|
173
|
+
existing = existing.replace(/\n?# triples-agentic hooks[\s\S]*?# end triples-agentic hooks\n?/g, '');
|
|
174
|
+
|
|
175
|
+
let block = '\n# triples-agentic hooks\n';
|
|
176
|
+
for (const entry of hookEntries) {
|
|
177
|
+
block += '\n[[hooks.PreToolUse]]\n';
|
|
178
|
+
if (entry.matcher) block += `matcher = "${tomlEscape(entry.matcher)}"\n`;
|
|
179
|
+
for (const hook of (entry.hooks || [])) {
|
|
180
|
+
block += '\n[[hooks.PreToolUse.hooks]]\n';
|
|
181
|
+
block += 'type = "command"\n';
|
|
182
|
+
block += `command = "${tomlEscape(hook.command)}"\n`;
|
|
183
|
+
if (hook.statusMessage) block += `statusMessage = "${tomlEscape(hook.statusMessage)}"\n`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
block += '\n# end triples-agentic hooks\n';
|
|
187
|
+
|
|
188
|
+
writeFile(configPath, existing.trimEnd() + block);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function installCodex(base, ctx) {
|
|
192
|
+
const { GLOBAL_PATHS, isGlobal, projectDir, allAgents, display } = ctx;
|
|
193
|
+
const codexRoot = isGlobal && !base
|
|
194
|
+
? GLOBAL_PATHS.codex
|
|
195
|
+
: join(base || projectDir, '.codex');
|
|
196
|
+
const skillsRoot = join(codexRoot, 'skills');
|
|
197
|
+
console.log(`\nInstalling OpenAI Codex skills → ${display(skillsRoot)}`);
|
|
198
|
+
|
|
199
|
+
for (const agent of allAgents()) {
|
|
200
|
+
writeCodexSkillBundle(skillsRoot, agent, ctx);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const legacyAgentsPath = isGlobal && !base
|
|
204
|
+
? join(GLOBAL_PATHS.codex, 'AGENTS.md')
|
|
205
|
+
: join(base || projectDir, 'AGENTS.md');
|
|
206
|
+
maybeRewriteLegacyCodexAgentsDoc(legacyAgentsPath, skillsRoot, ctx);
|
|
207
|
+
installCodexSettings(base, ctx);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function detectCodexInstallations(ctx) {
|
|
211
|
+
const { GLOBAL_PATHS, projectDir } = ctx;
|
|
212
|
+
const hasMarker = (file) => {
|
|
213
|
+
try { return readFileSync(file, 'utf-8').includes('TripleS Agent Orchestrator'); } catch { return false; }
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const found = [];
|
|
217
|
+
const codexGlobal = join(GLOBAL_PATHS.codex, 'AGENTS.md');
|
|
218
|
+
const codexGlobalSkills = join(GLOBAL_PATHS.codex, 'skills', 'seoyeon', 'SKILL.md');
|
|
219
|
+
if (existsSync(codexGlobalSkills) || (existsSync(codexGlobal) && hasMarker(codexGlobal))) {
|
|
220
|
+
found.push({ platform: 'codex', isGlobal: true });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const codexProject = join(projectDir, 'AGENTS.md');
|
|
224
|
+
const codexProjectSkills = join(projectDir, '.codex', 'skills', 'seoyeon', 'SKILL.md');
|
|
225
|
+
if (existsSync(codexProjectSkills) || (existsSync(codexProject) && hasMarker(codexProject))) {
|
|
226
|
+
found.push({ platform: 'codex', isGlobal: false });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return found;
|
|
230
|
+
}
|
package/src/bin/setup.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* TripleS Agentic — Skill Plugin Setup
|
|
4
4
|
*
|
|
5
|
-
* Installs all TripleS skill files (11 agents +
|
|
5
|
+
* Installs all TripleS skill files (11 agents + 41 knowledge references)
|
|
6
6
|
* into your coding assistant's config directory.
|
|
7
7
|
*
|
|
8
8
|
* Usage:
|
|
@@ -89,16 +89,51 @@ function parseComment(content, key) {
|
|
|
89
89
|
return m ? m[1].trim() : '';
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
function parseCommentList(content, key) {
|
|
93
|
+
const raw = parseComment(content, key);
|
|
94
|
+
if (!raw) return [];
|
|
95
|
+
return raw.split(',').map(item => item.trim()).filter(Boolean);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseHeading(content) {
|
|
99
|
+
const m = content.match(/^#\s+(.+)$/m);
|
|
100
|
+
return m ? m[1].trim() : '';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function stripAgentMetadataComments(content) {
|
|
104
|
+
return content.replace(/^<!-- .*? -->\r?\n/gm, '').trimStart();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function yamlEscape(value) {
|
|
108
|
+
return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function shortHeading(heading, fallback) {
|
|
112
|
+
if (!heading) return fallback;
|
|
113
|
+
return heading.split('—')[0].trim();
|
|
114
|
+
}
|
|
115
|
+
|
|
92
116
|
// ─── Source collectors ────────────────────────────────────────────────────────
|
|
93
117
|
|
|
94
118
|
function allAgents() {
|
|
95
119
|
return readdirSync(AGENTS_DIR)
|
|
96
|
-
.filter(f => f.endsWith('.md'))
|
|
120
|
+
.filter(f => f.endsWith('.md') && f !== 'README.md')
|
|
97
121
|
.map(f => {
|
|
98
122
|
const name = f.replace('.md', '');
|
|
99
123
|
const content = readFileSync(join(AGENTS_DIR, f), 'utf-8');
|
|
124
|
+
const slug = parseComment(content, 'triples-agent') || name;
|
|
100
125
|
const persona = parseComment(content, 'persona') || 'software engineering agent';
|
|
101
|
-
|
|
126
|
+
const heading = parseHeading(content);
|
|
127
|
+
return {
|
|
128
|
+
name,
|
|
129
|
+
slug,
|
|
130
|
+
content,
|
|
131
|
+
persona,
|
|
132
|
+
heading,
|
|
133
|
+
displayName: shortHeading(heading, name),
|
|
134
|
+
knowledgePaths: parseCommentList(content, 'knowledge'),
|
|
135
|
+
templatePaths: parseCommentList(content, 'templates'),
|
|
136
|
+
};
|
|
102
137
|
})
|
|
103
138
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
104
139
|
}
|
|
@@ -187,6 +222,30 @@ function tomlEscape(str) {
|
|
|
187
222
|
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
188
223
|
}
|
|
189
224
|
|
|
225
|
+
function createInstallerContext() {
|
|
226
|
+
return {
|
|
227
|
+
ROOT,
|
|
228
|
+
HOME,
|
|
229
|
+
AGENTS_DIR,
|
|
230
|
+
KNOWLEDGE_DIR,
|
|
231
|
+
KNOWLEDGE_GROUPS,
|
|
232
|
+
GLOBAL_PATHS,
|
|
233
|
+
projectDir,
|
|
234
|
+
isGlobal,
|
|
235
|
+
allAgents,
|
|
236
|
+
loadCodexHooks,
|
|
237
|
+
stripAgentMetadataComments,
|
|
238
|
+
yamlEscape,
|
|
239
|
+
tomlEscape,
|
|
240
|
+
display,
|
|
241
|
+
writeFile,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function loadCodexInstaller() {
|
|
246
|
+
return import('./setup/codex.js');
|
|
247
|
+
}
|
|
248
|
+
|
|
190
249
|
// ─── Platform installers ──────────────────────────────────────────────────────
|
|
191
250
|
|
|
192
251
|
/** Write (or merge) PreToolUse hooks from platforms.claude into .claude/settings.json */
|
|
@@ -215,35 +274,6 @@ function installClaudeSettings(claudeDir) {
|
|
|
215
274
|
writeFile(settingsPath, JSON.stringify(settings, null, 2));
|
|
216
275
|
}
|
|
217
276
|
|
|
218
|
-
/** Write (or append) PreToolUse hooks from platforms.codex into .codex/config.toml */
|
|
219
|
-
function installCodexSettings(base) {
|
|
220
|
-
const hookEntries = loadCodexHooks().filter(e => e.event === 'PreToolUse');
|
|
221
|
-
if (hookEntries.length === 0) return;
|
|
222
|
-
|
|
223
|
-
const configPath = isGlobal && !base
|
|
224
|
-
? join(GLOBAL_PATHS.codex, 'config.toml')
|
|
225
|
-
: join(base || projectDir, '.codex', 'config.toml');
|
|
226
|
-
let existing = existsSync(configPath) ? readFileSync(configPath, 'utf-8') : '';
|
|
227
|
-
|
|
228
|
-
// Remove previous triples-agentic block (idempotent reinstall)
|
|
229
|
-
existing = existing.replace(/\n?# triples-agentic hooks[\s\S]*?# end triples-agentic hooks\n?/g, '');
|
|
230
|
-
|
|
231
|
-
let block = '\n# triples-agentic hooks\n';
|
|
232
|
-
for (const entry of hookEntries) {
|
|
233
|
-
block += `\n[[hooks.PreToolUse]]\n`;
|
|
234
|
-
if (entry.matcher) block += `matcher = "${tomlEscape(entry.matcher)}"\n`;
|
|
235
|
-
for (const hook of (entry.hooks || [])) {
|
|
236
|
-
block += `\n[[hooks.PreToolUse.hooks]]\n`;
|
|
237
|
-
block += `type = "command"\n`;
|
|
238
|
-
block += `command = "${tomlEscape(hook.command)}"\n`;
|
|
239
|
-
if (hook.statusMessage) block += `statusMessage = "${tomlEscape(hook.statusMessage)}"\n`;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
block += '\n# end triples-agentic hooks\n';
|
|
243
|
-
|
|
244
|
-
writeFile(configPath, existing.trimEnd() + block);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
277
|
/** Write (or merge) Windsurf hooks from platforms.windsurf into .windsurf/hooks.json */
|
|
248
278
|
function installWindsurfSettings(base) {
|
|
249
279
|
const byEvent = loadWindsurfHooks();
|
|
@@ -335,32 +365,9 @@ function installCopilot(base) {
|
|
|
335
365
|
}
|
|
336
366
|
}
|
|
337
367
|
|
|
338
|
-
function installCodex(base) {
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
: join(base || projectDir, 'AGENTS.md');
|
|
342
|
-
console.log(`\nInstalling OpenAI Codex → ${display(dest)}`);
|
|
343
|
-
|
|
344
|
-
const safetyBody = loadSafetyRulesBody();
|
|
345
|
-
const lines = ['# TripleS Agent Orchestrator\n\n', ...(safetyBody ? [safetyBody + '\n\n'] : []), '## Agents\n\n'];
|
|
346
|
-
for (const { name, content } of allAgents()) {
|
|
347
|
-
lines.push(`---\n\n${content}\n\n`);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
lines.push('## Knowledge Skills\n\n');
|
|
351
|
-
for (const group of KNOWLEDGE_GROUPS) {
|
|
352
|
-
lines.push(`### ${group.charAt(0).toUpperCase() + group.slice(1)}\n\n`);
|
|
353
|
-
for (const { name, content } of allKnowledgeSkills().filter(s => s.group === group)) {
|
|
354
|
-
const body = stripFrontmatter(content);
|
|
355
|
-
lines.push(`#### ${name}\n\n${body}\n\n`);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
writeFileSync(dest, lines.join(''), 'utf-8');
|
|
360
|
-
console.log(` ✓ ${display(dest)}`);
|
|
361
|
-
|
|
362
|
-
// Hooks — enforced at harness level via .codex/config.toml
|
|
363
|
-
installCodexSettings(base);
|
|
368
|
+
async function installCodex(base) {
|
|
369
|
+
const { installCodex: runCodexInstall } = await loadCodexInstaller();
|
|
370
|
+
runCodexInstall(base, createInstallerContext());
|
|
364
371
|
}
|
|
365
372
|
|
|
366
373
|
function installWindsurf(base) {
|
|
@@ -443,7 +450,7 @@ const KNOWLEDGE_SUMMARY = {
|
|
|
443
450
|
* Detect existing TripleS installations by checking sentinel files.
|
|
444
451
|
* Returns an array of { platform, isGlobal } objects.
|
|
445
452
|
*/
|
|
446
|
-
function detectInstallations() {
|
|
453
|
+
async function detectInstallations() {
|
|
447
454
|
const found = [];
|
|
448
455
|
|
|
449
456
|
const hasFile = (...parts) => existsSync(join(...parts));
|
|
@@ -468,12 +475,8 @@ function detectInstallations() {
|
|
|
468
475
|
found.push({ platform: 'copilot', isGlobal: false });
|
|
469
476
|
|
|
470
477
|
// Codex
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
found.push({ platform: 'codex', isGlobal: true });
|
|
474
|
-
const codexProject = join(projectDir, 'AGENTS.md');
|
|
475
|
-
if (hasFile(codexProject) && hasMarker(codexProject))
|
|
476
|
-
found.push({ platform: 'codex', isGlobal: false });
|
|
478
|
+
const { detectCodexInstallations } = await loadCodexInstaller();
|
|
479
|
+
found.push(...detectCodexInstallations(createInstallerContext()));
|
|
477
480
|
|
|
478
481
|
// Windsurf
|
|
479
482
|
const windsurfGlobal = join(GLOBAL_PATHS.windsurf, '.windsurfrules');
|
|
@@ -487,7 +490,7 @@ function detectInstallations() {
|
|
|
487
490
|
}
|
|
488
491
|
|
|
489
492
|
async function runUpdate() {
|
|
490
|
-
const installations = detectInstallations();
|
|
493
|
+
const installations = await detectInstallations();
|
|
491
494
|
|
|
492
495
|
if (installations.length === 0) {
|
|
493
496
|
console.log('\n⚠ No existing TripleS Agentic installations detected.\n');
|
|
@@ -509,32 +512,33 @@ async function runUpdate() {
|
|
|
509
512
|
const savedIsGlobal = isGlobal;
|
|
510
513
|
for (const { platform, isGlobal: g } of installations) {
|
|
511
514
|
isGlobal = g;
|
|
512
|
-
INSTALLERS[platform](g ? null : projectDir);
|
|
515
|
+
await INSTALLERS[platform](g ? null : projectDir);
|
|
513
516
|
}
|
|
514
517
|
isGlobal = savedIsGlobal;
|
|
515
518
|
|
|
516
519
|
const totalKnowledge = Object.values(KNOWLEDGE_SUMMARY).reduce((n, g) => n + g.length, 0);
|
|
517
|
-
console.log(`\n✅ Updated ${installations.length} installation(s) — ${AGENT_COMMANDS.length} agents, ${totalKnowledge} knowledge
|
|
520
|
+
console.log(`\n✅ Updated ${installations.length} installation(s) — ${AGENT_COMMANDS.length} agents, ${totalKnowledge} knowledge references\n`);
|
|
518
521
|
}
|
|
519
522
|
|
|
520
523
|
// ─── Success banner ───────────────────────────────────────────────────────────
|
|
521
524
|
|
|
522
525
|
function printSuccessBanner() {
|
|
523
526
|
const totalKnowledge = Object.values(KNOWLEDGE_SUMMARY).reduce((n, g) => n + g.length, 0);
|
|
527
|
+
const knowledgeGroups = Object.keys(KNOWLEDGE_SUMMARY).length;
|
|
524
528
|
console.log('\n✅ TripleS Agentic installed successfully!\n');
|
|
525
529
|
|
|
526
|
-
console.log('── Agents
|
|
530
|
+
console.log('── Agents / Skills ───────────────────────────────────────────');
|
|
527
531
|
for (const [cmd, desc] of AGENT_COMMANDS) {
|
|
528
|
-
console.log(`
|
|
532
|
+
console.log(` ${cmd.padEnd(18)} ${desc}`);
|
|
529
533
|
}
|
|
530
534
|
|
|
531
|
-
console.log(`\n── Knowledge
|
|
535
|
+
console.log(`\n── Knowledge Library (${totalKnowledge} bundled references across ${knowledgeGroups} groups) ───────`);
|
|
532
536
|
for (const [group, skills] of Object.entries(KNOWLEDGE_SUMMARY)) {
|
|
533
537
|
const label = group.includes('/') ? group : group; // full path for sub-groups
|
|
534
538
|
console.log(` ${label.padEnd(16)} ${skills.map(s => `/${s}`).join(' ')}`);
|
|
535
539
|
}
|
|
536
540
|
|
|
537
|
-
console.log('\
|
|
541
|
+
console.log('\nUse `/seoyeon` in Claude Code, or `/skills` / `$seoyeon` in Codex.');
|
|
538
542
|
console.log('To update later run: npx triples-agentic update\n');
|
|
539
543
|
}
|
|
540
544
|
|
|
@@ -592,7 +596,7 @@ async function runWizard() {
|
|
|
592
596
|
|
|
593
597
|
console.log('');
|
|
594
598
|
for (const p of selectedPlatforms) {
|
|
595
|
-
INSTALLERS[p](useGlobal && GLOBAL_PATHS[p] ? null : projectDir);
|
|
599
|
+
await INSTALLERS[p](useGlobal && GLOBAL_PATHS[p] ? null : projectDir);
|
|
596
600
|
}
|
|
597
601
|
|
|
598
602
|
printSuccessBanner();
|
|
@@ -618,7 +622,7 @@ async function main() {
|
|
|
618
622
|
|
|
619
623
|
if (platformArg === 'all') {
|
|
620
624
|
for (const installer of Object.values(INSTALLERS)) {
|
|
621
|
-
installer(isGlobal ? null : projectDir);
|
|
625
|
+
await installer(isGlobal ? null : projectDir);
|
|
622
626
|
}
|
|
623
627
|
printSuccessBanner();
|
|
624
628
|
return;
|
|
@@ -630,7 +634,7 @@ async function main() {
|
|
|
630
634
|
process.exit(1);
|
|
631
635
|
}
|
|
632
636
|
|
|
633
|
-
INSTALLERS[platformArg](isGlobal ? null : projectDir);
|
|
637
|
+
await INSTALLERS[platformArg](isGlobal ? null : projectDir);
|
|
634
638
|
printSuccessBanner();
|
|
635
639
|
}
|
|
636
640
|
|