helloagents 2.3.8 → 3.0.2-beta.1
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/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +21 -0
- package/.codex-plugin/plugin.json +46 -0
- package/README.md +494 -636
- package/README_CN.md +778 -0
- package/assets/dogdoing/complete.wav +0 -0
- package/assets/dogdoing/confirm.wav +0 -0
- package/assets/dogdoing/error.wav +0 -0
- package/assets/dogdoing/idle.wav +0 -0
- package/assets/dogdoing/warning.wav +0 -0
- package/assets/icons/icon-large.png +0 -0
- package/assets/icons/icon.png +0 -0
- package/assets/sounds/complete.wav +0 -0
- package/assets/sounds/confirm.wav +0 -0
- package/assets/sounds/error.wav +0 -0
- package/assets/sounds/idle.wav +0 -0
- package/assets/sounds/warning.wav +0 -0
- package/bootstrap-lite.md +199 -0
- package/bootstrap.md +296 -0
- package/cli.mjs +453 -0
- package/gemini-extension.json +8 -0
- package/hooks/hooks-claude.json +88 -0
- package/hooks/hooks.json +76 -0
- package/package.json +36 -6
- package/scripts/cli-codex.mjs +428 -0
- package/scripts/cli-config.mjs +37 -0
- package/scripts/cli-hosts.mjs +75 -0
- package/scripts/cli-messages.mjs +92 -0
- package/scripts/cli-toml.mjs +251 -0
- package/scripts/cli-utils.mjs +139 -0
- package/scripts/guard.mjs +217 -0
- package/scripts/notify-context.mjs +123 -0
- package/scripts/notify-events.mjs +11 -0
- package/scripts/notify-shared.mjs +47 -0
- package/scripts/notify-ui.mjs +92 -0
- package/scripts/notify.mjs +219 -0
- package/scripts/ralph-loop.mjs +246 -0
- package/skills/_meta/SKILL.md +19 -0
- package/skills/commands/auto/SKILL.md +91 -0
- package/skills/commands/clean/SKILL.md +21 -0
- package/skills/commands/commit/SKILL.md +26 -0
- package/skills/commands/design/SKILL.md +108 -0
- package/skills/commands/help/SKILL.md +45 -0
- package/skills/commands/init/SKILL.md +60 -0
- package/skills/commands/loop/SKILL.md +98 -0
- package/skills/commands/prd/SKILL.md +151 -0
- package/skills/commands/review/SKILL.md +16 -0
- package/skills/commands/test/SKILL.md +16 -0
- package/skills/commands/verify/SKILL.md +21 -0
- package/skills/hello-api/SKILL.md +40 -0
- package/skills/hello-arch/SKILL.md +38 -0
- package/skills/hello-data/SKILL.md +39 -0
- package/skills/hello-debug/SKILL.md +58 -0
- package/skills/hello-errors/SKILL.md +39 -0
- package/skills/hello-perf/SKILL.md +40 -0
- package/skills/hello-reflect/SKILL.md +34 -0
- package/skills/hello-review/SKILL.md +33 -0
- package/skills/hello-security/SKILL.md +40 -0
- package/skills/hello-subagent/SKILL.md +32 -0
- package/skills/hello-test/SKILL.md +55 -0
- package/skills/hello-ui/SKILL.md +197 -0
- package/skills/hello-verify/SKILL.md +132 -0
- package/skills/hello-write/SKILL.md +33 -0
- package/skills/helloagents/SKILL.md +107 -0
- package/templates/CHANGELOG.md +5 -0
- package/templates/DESIGN.md +19 -0
- package/templates/STATE.md +19 -0
- package/templates/archive/_index.md +4 -0
- package/templates/context.md +19 -0
- package/templates/guidelines.md +15 -0
- package/templates/modules/module.md +13 -0
- package/templates/plans/decisions.md +10 -0
- package/templates/plans/design.md +14 -0
- package/templates/plans/prd/00-overview.md +23 -0
- package/templates/plans/prd/01-user-stories.md +19 -0
- package/templates/plans/prd/02-functional.md +30 -0
- package/templates/plans/prd/03-ui-design.md +31 -0
- package/templates/plans/prd/04-technical.md +25 -0
- package/templates/plans/prd/05-nonfunctional.md +28 -0
- package/templates/plans/prd/06-i18n-l10n.md +23 -0
- package/templates/plans/prd/07-accessibility.md +20 -0
- package/templates/plans/prd/08-content.md +20 -0
- package/templates/plans/prd/09-testing.md +22 -0
- package/templates/plans/prd/10-deployment.md +23 -0
- package/templates/plans/prd/11-legal-privacy.md +18 -0
- package/templates/plans/prd/12-timeline.md +21 -0
- package/templates/plans/requirements.md +18 -0
- package/templates/plans/tasks.md +10 -0
- package/templates/verify.yaml +9 -0
- package/bin/cli.mjs +0 -106
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
import { join, dirname } from 'node:path';
|
|
2
|
+
import { existsSync, copyFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import {
|
|
4
|
+
ensureDir, safeRead, safeWrite, removeIfExists,
|
|
5
|
+
readJsonOrThrow, copyEntries,
|
|
6
|
+
createLink, removeLink, injectMarkedContent, removeMarkedContent,
|
|
7
|
+
} from './cli-utils.mjs';
|
|
8
|
+
import {
|
|
9
|
+
upsertTopLevelTomlKey,
|
|
10
|
+
upsertTopLevelTomlBlock,
|
|
11
|
+
readTopLevelTomlLine,
|
|
12
|
+
readTopLevelTomlBlock,
|
|
13
|
+
ensureTopLevelTomlLine,
|
|
14
|
+
ensureTopLevelTomlBlock,
|
|
15
|
+
readTomlKeyInSection,
|
|
16
|
+
removeTomlKeyInSection,
|
|
17
|
+
removeTopLevelTomlBlock,
|
|
18
|
+
ensureTomlKeyInSection,
|
|
19
|
+
stripTomlSection,
|
|
20
|
+
removeTopLevelTomlLines,
|
|
21
|
+
} from './cli-toml.mjs';
|
|
22
|
+
|
|
23
|
+
export const CODEX_MARKETPLACE_NAME = 'local-plugins';
|
|
24
|
+
export const CODEX_PLUGIN_NAME = 'helloagents';
|
|
25
|
+
export const CODEX_PLUGIN_KEY = `${CODEX_PLUGIN_NAME}@${CODEX_MARKETPLACE_NAME}`;
|
|
26
|
+
export const CODEX_PLUGIN_CONFIG_HEADER = `[plugins."${CODEX_PLUGIN_KEY}"]`;
|
|
27
|
+
export const CODEX_MANAGED_TOML_COMMENT = '# helloagents-managed';
|
|
28
|
+
export const CODEX_RUNTIME_CARRIER = 'AGENTS.md';
|
|
29
|
+
const CODEX_BACKUP_TIMESTAMP_RE = /^\d{8}-\d{6}$/;
|
|
30
|
+
const CODEX_CONFIG_BASENAME = 'config.toml';
|
|
31
|
+
const CODEX_DEVELOPER_INSTRUCTIONS_BACKUP_BASENAME = 'developer_instructions';
|
|
32
|
+
export const CODEX_DEVELOPER_INSTRUCTIONS = `CRITICAL: These are HelloAGENTS global defaults for Codex. Use them as the baseline for main-agent behavior. Spawned sub-agents should focus on the delegated task unless they are explicitly required to follow main-agent-only workflow.
|
|
33
|
+
If the current workspace contains a project-level AGENTS.md or other repo-specific instructions, treat those as the more specific and authoritative instructions. Use these global defaults only where they do not conflict. Standby/global behavior is determined by the active workspace instructions, not by this global default block.
|
|
34
|
+
If work was already in progress and earlier context was compressed, first restore the active project state from the most relevant project state files or other project-local context artifacts, then continue from the actual interruption point without restarting the workflow or repeating completed steps.`;
|
|
35
|
+
export const CODEX_RUNTIME_ENTRIES = [
|
|
36
|
+
'.codex-plugin',
|
|
37
|
+
'assets',
|
|
38
|
+
'bootstrap.md',
|
|
39
|
+
'hooks',
|
|
40
|
+
'LICENSE.md',
|
|
41
|
+
'package.json',
|
|
42
|
+
'README.md',
|
|
43
|
+
'README_CN.md',
|
|
44
|
+
'scripts',
|
|
45
|
+
'skills',
|
|
46
|
+
'templates',
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
function formatBackupTimestamp(date = new Date()) {
|
|
50
|
+
const pad = (value, size = 2) => String(value).padStart(size, '0');
|
|
51
|
+
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getTimestampedBackupPath(filePath, backupBaseName) {
|
|
55
|
+
return join(dirname(filePath), `${backupBaseName}_${formatBackupTimestamp()}.bak`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function listTimestampedBackups(directory, backupBaseName) {
|
|
59
|
+
if (!existsSync(directory)) return [];
|
|
60
|
+
return readdirSync(directory)
|
|
61
|
+
.filter((name) => name.startsWith(`${backupBaseName}_`) && name.endsWith('.bak'))
|
|
62
|
+
.filter((name) => CODEX_BACKUP_TIMESTAMP_RE.test(name.slice(backupBaseName.length + 1, -4)))
|
|
63
|
+
.sort();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getLatestTimestampedBackupPath(filePath, backupBaseName) {
|
|
67
|
+
const directory = dirname(filePath);
|
|
68
|
+
const backups = listTimestampedBackups(directory, backupBaseName);
|
|
69
|
+
const latest = backups.at(-1);
|
|
70
|
+
return latest ? join(directory, latest) : '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readLatestTimestampedBackup(filePath, backupBaseName) {
|
|
74
|
+
const backupPath = getLatestTimestampedBackupPath(filePath, backupBaseName);
|
|
75
|
+
return backupPath ? safeRead(backupPath) || '' : '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function removeLatestTimestampedBackup(filePath, backupBaseName) {
|
|
79
|
+
const backupPath = getLatestTimestampedBackupPath(filePath, backupBaseName);
|
|
80
|
+
if (backupPath) removeIfExists(backupPath);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function ensureTimestampedBackup(filePath, backupBaseName) {
|
|
84
|
+
if (!existsSync(filePath)) return '';
|
|
85
|
+
const existingBackup = getLatestTimestampedBackupPath(filePath, backupBaseName);
|
|
86
|
+
if (existingBackup) return existingBackup;
|
|
87
|
+
const backupPath = getTimestampedBackupPath(filePath, backupBaseName);
|
|
88
|
+
copyFileSync(filePath, backupPath);
|
|
89
|
+
return backupPath;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readCodexBackup(filePath, backupBaseName) {
|
|
93
|
+
const latest = readLatestTimestampedBackup(filePath, backupBaseName);
|
|
94
|
+
if (latest) return latest;
|
|
95
|
+
const legacyPath = `${filePath}.bak`;
|
|
96
|
+
return safeRead(legacyPath) || '';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function removeCodexBackup(filePath, backupBaseName) {
|
|
100
|
+
removeLatestTimestampedBackup(filePath, backupBaseName);
|
|
101
|
+
removeIfExists(`${filePath}.bak`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isManagedCodexStandbyInstructionPath(normalized = '') {
|
|
105
|
+
return /\/\.codex\/AGENTS\.md/i.test(normalized)
|
|
106
|
+
|| /\/\.codex\/helloagents\/bootstrap-lite\.md/i.test(normalized);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isManagedCodexGlobalInstructionPath(normalized = '') {
|
|
110
|
+
return /\/plugins\/helloagents\/AGENTS\.md/i.test(normalized)
|
|
111
|
+
|| /\/plugins\/helloagents\/bootstrap\.md/i.test(normalized);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function upsertCodexPluginConfig(text) {
|
|
115
|
+
const stripped = stripTomlSection(text, CODEX_PLUGIN_CONFIG_HEADER).text.trimEnd();
|
|
116
|
+
const block = `${CODEX_PLUGIN_CONFIG_HEADER}\nenabled = true`;
|
|
117
|
+
return stripped ? `${stripped}\n\n${block}\n` : `${block}\n`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function removeCodexPluginConfig(text) {
|
|
121
|
+
return stripTomlSection(text, CODEX_PLUGIN_CONFIG_HEADER).text;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isManagedCodexModelInstruction(line = '') {
|
|
125
|
+
const normalized = String(line || '').replace(/\\/g, '/');
|
|
126
|
+
return line.includes('model_instructions_file')
|
|
127
|
+
&& (
|
|
128
|
+
line.includes(CODEX_MANAGED_TOML_COMMENT)
|
|
129
|
+
|| isManagedCodexStandbyInstructionPath(normalized)
|
|
130
|
+
|| isManagedCodexGlobalInstructionPath(normalized)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isManagedCodexNotify(line = '') {
|
|
135
|
+
return line.includes('codex-notify') || (line.includes('helloagents') && line.includes('notify'));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isManagedCodexBackupInstruction(line = '') {
|
|
139
|
+
return line.includes(CODEX_MANAGED_TOML_COMMENT);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatManagedCodexDeveloperInstructions() {
|
|
143
|
+
return `"""\n${CODEX_DEVELOPER_INSTRUCTIONS}\n"""`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function backupUserCodexDeveloperInstructions(configPath, existingBlock) {
|
|
147
|
+
if (!existingBlock || existingBlock.includes('HelloAGENTS')) return;
|
|
148
|
+
const backupPath = getTimestampedBackupPath(configPath, CODEX_DEVELOPER_INSTRUCTIONS_BACKUP_BASENAME);
|
|
149
|
+
safeWrite(backupPath, `${existingBlock}\n`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function sanitizeCodexDeveloperInstructionsBackup(block = '') {
|
|
153
|
+
const normalized = String(block || '').trim();
|
|
154
|
+
if (!normalized.startsWith('developer_instructions =')) return '';
|
|
155
|
+
if (normalized.includes(CODEX_DEVELOPER_INSTRUCTIONS)) return '';
|
|
156
|
+
return normalized;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function readCodexDeveloperInstructionsBackup(configPath) {
|
|
160
|
+
return sanitizeCodexDeveloperInstructionsBackup(
|
|
161
|
+
readCodexBackup(configPath, CODEX_DEVELOPER_INSTRUCTIONS_BACKUP_BASENAME),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function removeCodexDeveloperInstructionsBackup(configPath) {
|
|
166
|
+
removeCodexBackup(configPath, CODEX_DEVELOPER_INSTRUCTIONS_BACKUP_BASENAME);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function installCodexDeveloperInstructions(configPath, toml) {
|
|
170
|
+
const existing = readTopLevelTomlBlock(toml, 'developer_instructions');
|
|
171
|
+
backupUserCodexDeveloperInstructions(configPath, existing);
|
|
172
|
+
return upsertTopLevelTomlBlock(toml, 'developer_instructions', formatManagedCodexDeveloperInstructions());
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function uninstallCodexDeveloperInstructions(configPath, toml) {
|
|
176
|
+
const existing = readTopLevelTomlBlock(toml, 'developer_instructions');
|
|
177
|
+
if (!existing.includes('HelloAGENTS')) return toml;
|
|
178
|
+
let next = removeTopLevelTomlBlock(toml, 'developer_instructions');
|
|
179
|
+
const backupDeveloperInstructions = readCodexDeveloperInstructionsBackup(configPath);
|
|
180
|
+
next = ensureTopLevelTomlBlock(next, 'developer_instructions', backupDeveloperInstructions);
|
|
181
|
+
removeCodexDeveloperInstructionsBackup(configPath);
|
|
182
|
+
return next;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getDefaultCodexMarketplace() {
|
|
186
|
+
return {
|
|
187
|
+
name: CODEX_MARKETPLACE_NAME,
|
|
188
|
+
interface: {
|
|
189
|
+
displayName: 'Local Plugins',
|
|
190
|
+
},
|
|
191
|
+
plugins: [],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function updateCodexMarketplace(marketplaceFile) {
|
|
196
|
+
const marketplace = readJsonOrThrow(marketplaceFile, 'Codex marketplace 配置') || getDefaultCodexMarketplace();
|
|
197
|
+
marketplace.name = CODEX_MARKETPLACE_NAME;
|
|
198
|
+
marketplace.interface = marketplace.interface || { displayName: 'Local Plugins' };
|
|
199
|
+
marketplace.plugins = Array.isArray(marketplace.plugins) ? marketplace.plugins : [];
|
|
200
|
+
|
|
201
|
+
const nextEntry = {
|
|
202
|
+
name: CODEX_PLUGIN_NAME,
|
|
203
|
+
source: {
|
|
204
|
+
source: 'local',
|
|
205
|
+
path: `./plugins/${CODEX_PLUGIN_NAME}`,
|
|
206
|
+
},
|
|
207
|
+
policy: {
|
|
208
|
+
installation: 'AVAILABLE',
|
|
209
|
+
authentication: 'ON_INSTALL',
|
|
210
|
+
},
|
|
211
|
+
category: 'Coding',
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const existingIndex = marketplace.plugins.findIndex((plugin) => plugin?.name === CODEX_PLUGIN_NAME);
|
|
215
|
+
if (existingIndex >= 0) {
|
|
216
|
+
marketplace.plugins.splice(existingIndex, 1, nextEntry);
|
|
217
|
+
} else {
|
|
218
|
+
marketplace.plugins.push(nextEntry);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
safeWrite(marketplaceFile, JSON.stringify(marketplace, null, 2) + '\n');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function removeCodexMarketplaceEntry(marketplaceFile) {
|
|
225
|
+
if (!existsSync(marketplaceFile)) return false;
|
|
226
|
+
const marketplace = readJsonOrThrow(marketplaceFile, 'Codex marketplace 配置');
|
|
227
|
+
const plugins = Array.isArray(marketplace?.plugins) ? marketplace.plugins : [];
|
|
228
|
+
const nextPlugins = plugins.filter((plugin) => plugin?.name !== CODEX_PLUGIN_NAME);
|
|
229
|
+
const removedHelloagents = nextPlugins.length !== plugins.length;
|
|
230
|
+
const isManagedMarketplace = (marketplace?.name || CODEX_MARKETPLACE_NAME) === CODEX_MARKETPLACE_NAME;
|
|
231
|
+
if (!nextPlugins.length && isManagedMarketplace) {
|
|
232
|
+
removeIfExists(marketplaceFile);
|
|
233
|
+
return removedHelloagents || true;
|
|
234
|
+
}
|
|
235
|
+
if (!removedHelloagents) return false;
|
|
236
|
+
if (!nextPlugins.length) {
|
|
237
|
+
removeIfExists(marketplaceFile);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
marketplace.plugins = nextPlugins;
|
|
241
|
+
safeWrite(marketplaceFile, JSON.stringify(marketplace, null, 2) + '\n');
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function normalizePath(path) {
|
|
246
|
+
return path.replace(/\\/g, '/');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function buildCodexRuntimeCarrier(bootstrapContent) {
|
|
250
|
+
const normalized = String(bootstrapContent || '').trim();
|
|
251
|
+
return normalized ? `${normalized}\n` : '';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function writeCodexRuntimeCarrier(filePath, bootstrapPath) {
|
|
255
|
+
const bootstrapContent = safeRead(bootstrapPath);
|
|
256
|
+
if (!bootstrapContent) return false;
|
|
257
|
+
safeWrite(filePath, buildCodexRuntimeCarrier(bootstrapContent));
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function installCodexStandby(home, pkgRoot) {
|
|
262
|
+
const codexDir = join(home, '.codex');
|
|
263
|
+
if (!existsSync(codexDir)) return false;
|
|
264
|
+
ensureDir(codexDir);
|
|
265
|
+
|
|
266
|
+
const codexAgentsPath = join(codexDir, CODEX_RUNTIME_CARRIER);
|
|
267
|
+
const bootstrapContent = safeRead(join(pkgRoot, 'bootstrap-lite.md'));
|
|
268
|
+
if (bootstrapContent) {
|
|
269
|
+
injectMarkedContent(
|
|
270
|
+
codexAgentsPath,
|
|
271
|
+
buildCodexRuntimeCarrier(bootstrapContent).trimEnd(),
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const configPath = join(codexDir, 'config.toml');
|
|
276
|
+
let toml = safeRead(configPath) || '';
|
|
277
|
+
ensureTimestampedBackup(configPath, CODEX_CONFIG_BASENAME);
|
|
278
|
+
|
|
279
|
+
toml = upsertTopLevelTomlKey(toml, 'notify', `["node", "${normalizePath(join(pkgRoot, 'scripts', 'notify.mjs'))}", "codex-notify"]`);
|
|
280
|
+
toml = installCodexDeveloperInstructions(configPath, toml);
|
|
281
|
+
safeWrite(configPath, toml);
|
|
282
|
+
|
|
283
|
+
createLink(pkgRoot, join(codexDir, 'helloagents'));
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function uninstallCodexStandby(home) {
|
|
288
|
+
const codexDir = join(home, '.codex');
|
|
289
|
+
let changed = false;
|
|
290
|
+
|
|
291
|
+
if (existsSync(codexDir)) {
|
|
292
|
+
removeMarkedContent(join(codexDir, 'AGENTS.md'));
|
|
293
|
+
|
|
294
|
+
const configPath = join(codexDir, 'config.toml');
|
|
295
|
+
const backupToml = readCodexBackup(configPath, CODEX_CONFIG_BASENAME);
|
|
296
|
+
let toml = safeRead(configPath) || '';
|
|
297
|
+
if (toml.includes('helloagents') || toml.includes('HelloAGENTS')) {
|
|
298
|
+
toml = removeTopLevelTomlLines(toml, (line) => {
|
|
299
|
+
if (!line) return false;
|
|
300
|
+
if (line.startsWith('model_instructions_file =') && isManagedCodexModelInstruction(line)) return true;
|
|
301
|
+
if (line.startsWith('notify =') && line.includes('codex-notify')) return true;
|
|
302
|
+
return false;
|
|
303
|
+
}).text;
|
|
304
|
+
toml = uninstallCodexDeveloperInstructions(configPath, toml);
|
|
305
|
+
toml = removeTomlKeyInSection(toml, '[features]', 'codex_hooks');
|
|
306
|
+
const backupModelInstructions = readTopLevelTomlLine(backupToml, 'model_instructions_file');
|
|
307
|
+
const backupNotify = readTopLevelTomlLine(backupToml, 'notify');
|
|
308
|
+
toml = ensureTopLevelTomlLine(
|
|
309
|
+
toml,
|
|
310
|
+
'model_instructions_file',
|
|
311
|
+
isManagedCodexBackupInstruction(backupModelInstructions) ? '' : backupModelInstructions,
|
|
312
|
+
);
|
|
313
|
+
toml = ensureTopLevelTomlLine(
|
|
314
|
+
toml,
|
|
315
|
+
'notify',
|
|
316
|
+
isManagedCodexNotify(backupNotify) ? '' : backupNotify,
|
|
317
|
+
);
|
|
318
|
+
toml = ensureTomlKeyInSection(toml, '[features]', 'codex_hooks', readTomlKeyInSection(backupToml, '[features]', 'codex_hooks'));
|
|
319
|
+
if (toml.trim()) safeWrite(configPath, toml);
|
|
320
|
+
else removeIfExists(configPath);
|
|
321
|
+
changed = true;
|
|
322
|
+
}
|
|
323
|
+
removeCodexBackup(configPath, CODEX_CONFIG_BASENAME);
|
|
324
|
+
removeIfExists(join(codexDir, 'hooks.json'));
|
|
325
|
+
removeLink(join(codexDir, 'helloagents'));
|
|
326
|
+
changed = true;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
for (const path of [join(codexDir, 'skills', 'helloagents'), join(home, '.agents', 'skills', 'helloagents')]) {
|
|
330
|
+
changed = removeLink(path) || changed;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return changed;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function installCodexGlobal(home, pkgRoot) {
|
|
337
|
+
const codexDir = join(home, '.codex');
|
|
338
|
+
if (!existsSync(codexDir)) return false;
|
|
339
|
+
|
|
340
|
+
const pluginRoot = join(home, 'plugins', CODEX_PLUGIN_NAME);
|
|
341
|
+
const installedPluginRoot = join(
|
|
342
|
+
codexDir,
|
|
343
|
+
'plugins',
|
|
344
|
+
'cache',
|
|
345
|
+
CODEX_MARKETPLACE_NAME,
|
|
346
|
+
CODEX_PLUGIN_NAME,
|
|
347
|
+
'local',
|
|
348
|
+
);
|
|
349
|
+
const marketplaceFile = join(home, '.agents', 'plugins', 'marketplace.json');
|
|
350
|
+
const configPath = join(codexDir, 'config.toml');
|
|
351
|
+
|
|
352
|
+
ensureDir(codexDir);
|
|
353
|
+
removeIfExists(pluginRoot);
|
|
354
|
+
removeIfExists(join(codexDir, 'plugins', 'cache', CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME));
|
|
355
|
+
|
|
356
|
+
ensureDir(join(home, 'plugins'));
|
|
357
|
+
ensureDir(installedPluginRoot);
|
|
358
|
+
|
|
359
|
+
copyEntries(pkgRoot, pluginRoot, CODEX_RUNTIME_ENTRIES);
|
|
360
|
+
copyEntries(pkgRoot, installedPluginRoot, CODEX_RUNTIME_ENTRIES);
|
|
361
|
+
writeCodexRuntimeCarrier(
|
|
362
|
+
join(pluginRoot, CODEX_RUNTIME_CARRIER),
|
|
363
|
+
join(pluginRoot, 'bootstrap.md'),
|
|
364
|
+
);
|
|
365
|
+
writeCodexRuntimeCarrier(
|
|
366
|
+
join(installedPluginRoot, CODEX_RUNTIME_CARRIER),
|
|
367
|
+
join(installedPluginRoot, 'bootstrap.md'),
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
ensureDir(join(home, '.agents', 'plugins'));
|
|
371
|
+
updateCodexMarketplace(marketplaceFile);
|
|
372
|
+
|
|
373
|
+
let toml = safeRead(configPath) || '';
|
|
374
|
+
ensureTimestampedBackup(configPath, CODEX_CONFIG_BASENAME);
|
|
375
|
+
toml = upsertTopLevelTomlKey(
|
|
376
|
+
toml,
|
|
377
|
+
'notify',
|
|
378
|
+
`["node", "${normalizePath(join(pluginRoot, 'scripts', 'notify.mjs'))}", "codex-notify"]`,
|
|
379
|
+
);
|
|
380
|
+
toml = upsertCodexPluginConfig(toml);
|
|
381
|
+
toml = installCodexDeveloperInstructions(configPath, toml);
|
|
382
|
+
safeWrite(configPath, toml);
|
|
383
|
+
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function uninstallCodexGlobal(home) {
|
|
388
|
+
const codexDir = join(home, '.codex');
|
|
389
|
+
|
|
390
|
+
const pluginRoot = join(home, 'plugins', CODEX_PLUGIN_NAME);
|
|
391
|
+
const pluginCacheRoot = join(codexDir, 'plugins', 'cache', CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME);
|
|
392
|
+
const marketplaceFile = join(home, '.agents', 'plugins', 'marketplace.json');
|
|
393
|
+
const configPath = join(codexDir, 'config.toml');
|
|
394
|
+
|
|
395
|
+
removeIfExists(pluginRoot);
|
|
396
|
+
removeIfExists(pluginCacheRoot);
|
|
397
|
+
removeCodexMarketplaceEntry(marketplaceFile);
|
|
398
|
+
|
|
399
|
+
const backupToml = readCodexBackup(configPath, CODEX_CONFIG_BASENAME);
|
|
400
|
+
let toml = safeRead(configPath) || '';
|
|
401
|
+
toml = removeCodexPluginConfig(toml);
|
|
402
|
+
toml = uninstallCodexDeveloperInstructions(configPath, toml);
|
|
403
|
+
toml = removeTomlKeyInSection(toml, '[features]', 'codex_hooks');
|
|
404
|
+
toml = removeTopLevelTomlLines(toml, (line) =>
|
|
405
|
+
line.startsWith('model_instructions_file =')
|
|
406
|
+
&& isManagedCodexModelInstruction(line)).text;
|
|
407
|
+
toml = removeTopLevelTomlLines(toml, (line) =>
|
|
408
|
+
line.startsWith('notify =')
|
|
409
|
+
&& line.includes('/plugins/helloagents/scripts/notify.mjs')).text;
|
|
410
|
+
const backupModelInstructions = readTopLevelTomlLine(backupToml, 'model_instructions_file');
|
|
411
|
+
const backupNotify = readTopLevelTomlLine(backupToml, 'notify');
|
|
412
|
+
toml = ensureTopLevelTomlLine(
|
|
413
|
+
toml,
|
|
414
|
+
'model_instructions_file',
|
|
415
|
+
isManagedCodexBackupInstruction(backupModelInstructions) ? '' : backupModelInstructions,
|
|
416
|
+
);
|
|
417
|
+
toml = ensureTopLevelTomlLine(
|
|
418
|
+
toml,
|
|
419
|
+
'notify',
|
|
420
|
+
isManagedCodexNotify(backupNotify) ? '' : backupNotify,
|
|
421
|
+
);
|
|
422
|
+
toml = ensureTomlKeyInSection(toml, '[features]', 'codex_hooks', readTomlKeyInSection(backupToml, '[features]', 'codex_hooks'));
|
|
423
|
+
if (toml.trim()) safeWrite(configPath, toml);
|
|
424
|
+
else removeIfExists(configPath);
|
|
425
|
+
removeCodexBackup(configPath, CODEX_CONFIG_BASENAME);
|
|
426
|
+
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
export const DEFAULTS = {
|
|
4
|
+
output_language: '',
|
|
5
|
+
output_format: true,
|
|
6
|
+
notify_level: 0,
|
|
7
|
+
ralph_loop_enabled: true,
|
|
8
|
+
guard_enabled: true,
|
|
9
|
+
kb_create_mode: 1,
|
|
10
|
+
commit_attribution: '',
|
|
11
|
+
install_mode: 'standby',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function loadPackageVersion(pkgRoot) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(`${pkgRoot}/package.json`, 'utf-8'));
|
|
17
|
+
} catch {
|
|
18
|
+
return { version: '0.0.0' };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ensureConfig(helloagentsHome, configFile, safeJson, ensureDir) {
|
|
23
|
+
ensureDir(helloagentsHome);
|
|
24
|
+
if (!existsSync(configFile)) {
|
|
25
|
+
writeFileSync(configFile, JSON.stringify(DEFAULTS, null, 2), 'utf-8');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const existing = safeJson(configFile) || {};
|
|
30
|
+
const reconciled = { ...existing };
|
|
31
|
+
for (const [key, val] of Object.entries(DEFAULTS)) {
|
|
32
|
+
if (!(key in reconciled)) reconciled[key] = val;
|
|
33
|
+
}
|
|
34
|
+
if (JSON.stringify(reconciled) !== JSON.stringify(existing)) {
|
|
35
|
+
writeFileSync(configFile, JSON.stringify(reconciled, null, 2), 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import {
|
|
4
|
+
ensureDir,
|
|
5
|
+
safeRead,
|
|
6
|
+
removeIfExists,
|
|
7
|
+
createLink,
|
|
8
|
+
removeLink,
|
|
9
|
+
injectMarkedContent,
|
|
10
|
+
removeMarkedContent,
|
|
11
|
+
mergeSettingsHooks,
|
|
12
|
+
cleanSettingsHooks,
|
|
13
|
+
loadHooksWithAbsPath,
|
|
14
|
+
} from './cli-utils.mjs';
|
|
15
|
+
|
|
16
|
+
export function installClaudeStandby(home, pkgRoot) {
|
|
17
|
+
const claudeDir = join(home, '.claude');
|
|
18
|
+
ensureDir(claudeDir);
|
|
19
|
+
|
|
20
|
+
const bootstrapContent = safeRead(join(pkgRoot, 'bootstrap-lite.md'));
|
|
21
|
+
if (bootstrapContent) {
|
|
22
|
+
injectMarkedContent(join(claudeDir, 'CLAUDE.md'), bootstrapContent);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
createLink(pkgRoot, join(claudeDir, 'helloagents'));
|
|
26
|
+
|
|
27
|
+
const settingsPath = join(claudeDir, 'settings.json');
|
|
28
|
+
const hooksData = loadHooksWithAbsPath(pkgRoot, 'hooks-claude.json', '${CLAUDE_PLUGIN_ROOT}');
|
|
29
|
+
if (hooksData) {
|
|
30
|
+
mergeSettingsHooks(settingsPath, hooksData, ['Read(~/.claude/helloagents/**)']);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function uninstallClaudeStandby(home) {
|
|
37
|
+
const claudeDir = join(home, '.claude');
|
|
38
|
+
if (!existsSync(claudeDir)) return false;
|
|
39
|
+
|
|
40
|
+
removeMarkedContent(join(claudeDir, 'CLAUDE.md'));
|
|
41
|
+
removeLink(join(claudeDir, 'helloagents'));
|
|
42
|
+
cleanSettingsHooks(join(claudeDir, 'settings.json'), true);
|
|
43
|
+
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function installGeminiStandby(home, pkgRoot) {
|
|
48
|
+
const geminiDir = join(home, '.gemini');
|
|
49
|
+
ensureDir(geminiDir);
|
|
50
|
+
|
|
51
|
+
const bootstrapContent = safeRead(join(pkgRoot, 'bootstrap-lite.md'));
|
|
52
|
+
if (bootstrapContent) {
|
|
53
|
+
injectMarkedContent(join(geminiDir, 'GEMINI.md'), bootstrapContent);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
createLink(pkgRoot, join(geminiDir, 'helloagents'));
|
|
57
|
+
|
|
58
|
+
const settingsPath = join(geminiDir, 'settings.json');
|
|
59
|
+
const hooksData = loadHooksWithAbsPath(pkgRoot, 'hooks.json', '${extensionPath}');
|
|
60
|
+
if (hooksData) mergeSettingsHooks(settingsPath, hooksData);
|
|
61
|
+
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function uninstallGeminiStandby(home) {
|
|
66
|
+
const geminiDir = join(home, '.gemini');
|
|
67
|
+
if (!existsSync(geminiDir)) return false;
|
|
68
|
+
|
|
69
|
+
removeMarkedContent(join(geminiDir, 'GEMINI.md'));
|
|
70
|
+
removeLink(join(geminiDir, 'helloagents'));
|
|
71
|
+
cleanSettingsHooks(join(geminiDir, 'settings.json'));
|
|
72
|
+
removeIfExists(join(geminiDir, 'helloagents-hooks.json'));
|
|
73
|
+
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function createMessageHelpers(isCN) {
|
|
5
|
+
const msg = (cn, en) => (isCN ? cn : en);
|
|
6
|
+
const ok = (message) => console.log(` ✓ ${message}`);
|
|
7
|
+
return { msg, ok };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createInstallMessagePrinter({ home, pkgVersion, msg }) {
|
|
11
|
+
const codexStandbyStatus = () => existsSync(join(home, '.codex'))
|
|
12
|
+
? msg('已自动配置', 'Auto-configured')
|
|
13
|
+
: msg('安装 Codex CLI 后重新运行 npm install -g helloagents', 'Install Codex CLI then re-run npm install -g helloagents');
|
|
14
|
+
|
|
15
|
+
const codexGlobalStatus = () => existsSync(join(home, '.codex'))
|
|
16
|
+
? msg('已自动安装原生本地插件', 'Native local plugin auto-installed')
|
|
17
|
+
: msg('安装 Codex CLI 后重新运行 npm install -g helloagents', 'Install Codex CLI then re-run npm install -g helloagents');
|
|
18
|
+
|
|
19
|
+
const PLUGIN_CMDS = ' Claude Code: /plugin marketplace add hellowind777/helloagents\n /plugin install helloagents@helloagents\n Gemini CLI: gemini extensions install https://github.com/hellowind777/helloagents';
|
|
20
|
+
const REMOVE_HINT = msg(
|
|
21
|
+
'如已安装 Claude Code 插件,建议手动移除: /plugin remove helloagents\n 如已安装 Gemini CLI 扩展,建议手动移除: gemini extensions uninstall helloagents',
|
|
22
|
+
'If Claude Code plugin installed, consider removing: /plugin remove helloagents\n If Gemini CLI extension installed, consider removing: gemini extensions uninstall helloagents',
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
function printInstallMsg(mode, context) {
|
|
26
|
+
const isSwitch = context === 'switch';
|
|
27
|
+
const isRefresh = context === 'refresh';
|
|
28
|
+
const isInstall = !isSwitch && !isRefresh;
|
|
29
|
+
if (mode === 'global') {
|
|
30
|
+
if (isInstall) console.log(msg(
|
|
31
|
+
`\n ✅ HelloAGENTS 已安装(global 模式)!\n\n${PLUGIN_CMDS}\n Codex: ${codexGlobalStatus()}(~/.agents/plugins/marketplace.json + ~/plugins/helloagents)\n\n 切换模式:\n helloagents --standby 标准模式(默认,非插件安装)`,
|
|
32
|
+
`\n ✅ HelloAGENTS installed (global mode)!\n\n${PLUGIN_CMDS}\n Codex: ${codexGlobalStatus()} (~/.agents/plugins/marketplace.json + ~/plugins/helloagents)\n\n Switch modes:\n helloagents --standby Standby mode (default, non-plugin install)`,
|
|
33
|
+
));
|
|
34
|
+
else console.log(msg(
|
|
35
|
+
isRefresh
|
|
36
|
+
? ' global 模式已刷新。\n Claude Code / Gemini 请保持插件已安装;Codex 原生本地插件链路已重装并同步最新文件。'
|
|
37
|
+
: ' 所有项目将自动启用完整 HelloAGENTS 规则。\n Claude Code / Gemini 请手动安装插件;Codex 已自动走原生本地插件链路。',
|
|
38
|
+
isRefresh
|
|
39
|
+
? ' Global mode refreshed.\n Keep Claude Code / Gemini plugins installed; Codex native local-plugin files were reinstalled and synced.'
|
|
40
|
+
: ' All projects will use full HelloAGENTS rules.\n Install Claude Code / Gemini plugins manually; Codex now uses the native local-plugin path automatically.',
|
|
41
|
+
));
|
|
42
|
+
} else {
|
|
43
|
+
if (isInstall) console.log(msg(
|
|
44
|
+
`\n ✅ HelloAGENTS 已安装(standby 模式)!\n\n Claude Code: 已自动配置(~/.claude/CLAUDE.md + hooks)\n Gemini CLI: 已自动配置(~/.gemini/GEMINI.md)\n Codex: ${codexStandbyStatus()}\n\n standby 模式下,hello-* 技能不会自动触发。\n 在项目中使用 ~init 激活完整功能,或使用 ~command 按需调用。\n\n 切换模式:\n helloagents --global 全局模式(Claude/Gemini 装插件;Codex 自动装原生本地插件)`,
|
|
45
|
+
`\n ✅ HelloAGENTS installed (standby mode)!\n\n Claude Code: Auto-configured (~/.claude/CLAUDE.md + hooks)\n Gemini CLI: Auto-configured (~/.gemini/GEMINI.md)\n Codex: ${codexStandbyStatus()}\n\n In standby mode, hello-* skills won't auto-trigger.\n Use ~init in a project to activate full features, or use ~command on demand.\n\n Switch modes:\n helloagents --global Global mode (manual plugins for Claude/Gemini; native local plugin auto-install for Codex)`,
|
|
46
|
+
));
|
|
47
|
+
else console.log(msg(
|
|
48
|
+
isRefresh
|
|
49
|
+
? ` standby 模式已刷新,CLI 注入与链接已同步最新文件。\n ${REMOVE_HINT}`
|
|
50
|
+
: ` 项目需通过 ~init 激活完整功能,未激活项目仅注入通用规则。\n ${REMOVE_HINT}`,
|
|
51
|
+
isRefresh
|
|
52
|
+
? ` Standby mode refreshed; injected files and links were synchronized.\n ${REMOVE_HINT}`
|
|
53
|
+
: ` Projects need ~init to activate full features. Unactivated projects get lite rules only.\n ${REMOVE_HINT}`,
|
|
54
|
+
));
|
|
55
|
+
}
|
|
56
|
+
if (isInstall || isRefresh) console.log();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function printHelp() {
|
|
60
|
+
console.log(`
|
|
61
|
+
HelloAGENTS v${pkgVersion} — The orchestration kernel for AI CLIs
|
|
62
|
+
|
|
63
|
+
${msg('安装', 'Install')}:
|
|
64
|
+
npm install -g helloagents ${msg('(只安装包与命令;CLI 部署需显式执行 helloagents install ...)', '(installs the package/command only; deploy to CLIs explicitly with helloagents install ...)')}
|
|
65
|
+
helloagents-js ${msg('(稳定别名,避免与系统中同名可执行文件冲突)', '(stable alias to avoid conflicts with system executables of the same name)')}
|
|
66
|
+
|
|
67
|
+
${msg('模式切换', 'Mode switching')}:
|
|
68
|
+
helloagents --global ${msg('全局模式(Claude/Gemini 装插件;Codex 自动装原生本地插件)', 'Global mode (manual plugins for Claude/Gemini; native local plugin auto-install for Codex)')}
|
|
69
|
+
helloagents --standby ${msg('标准模式(非插件安装,hello-* 不自动触发,默认)', "Standby mode (non-plugin install, hello-* won't auto-trigger, default)")}
|
|
70
|
+
|
|
71
|
+
${msg('单 CLI 管理', 'Scoped CLI management')}:
|
|
72
|
+
helloagents install codex --standby
|
|
73
|
+
helloagents install --all --global
|
|
74
|
+
helloagents update codex
|
|
75
|
+
helloagents cleanup claude --global
|
|
76
|
+
helloagents uninstall gemini
|
|
77
|
+
${msg('支持: claude | gemini | codex | --all;省略模式时优先沿用该 CLI 已记录/已检测的模式,否则回退 standby', 'Hosts: claude | gemini | codex | --all; omit mode to reuse the tracked/detected mode for that CLI, then fall back to standby')}
|
|
78
|
+
|
|
79
|
+
${msg('卸载', 'Uninstall')}:
|
|
80
|
+
helloagents cleanup ${msg('(推荐先执行,显式清理所有 CLI 注入/链接)', '(recommended first, explicitly cleans CLI injections/links)')}
|
|
81
|
+
npm uninstall -g helloagents
|
|
82
|
+
${msg('如已安装插件,另需手动移除:', 'If plugins installed, also remove manually:')}
|
|
83
|
+
Claude Code: /plugin remove helloagents
|
|
84
|
+
Gemini CLI: gemini extensions uninstall helloagents
|
|
85
|
+
`.trim());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
printHelp,
|
|
90
|
+
printInstallMsg,
|
|
91
|
+
};
|
|
92
|
+
}
|