contextos-agents 1.6.0 → 1.7.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.
- package/.agents/AGENTS.md +5 -0
- package/.agents/adapters/aider/export.js +4 -2
- package/.agents/adapters/copilot/export.js +4 -2
- package/.agents/adapters/cursor/export.js +48 -12
- package/.agents/adapters/zed/export.js +53 -14
- package/.agents/core/profiles/mvp.yaml +1 -0
- package/.agents/core/skills/adapters/skill.yaml +6 -0
- package/.agents/core/skills/architecture-diagrams/skill.yaml +4 -0
- package/.agents/core/skills/brutalist-design/skill.yaml +4 -0
- package/.agents/core/skills/context-manager/references/context-rules.md +59 -0
- package/.agents/core/skills/context-manager/skill.yaml +7 -0
- package/.agents/core/skills/context-os/skill.yaml +11 -0
- package/.agents/core/skills/database/skill.yaml +6 -0
- package/.agents/core/skills/ddd/skill.yaml +7 -0
- package/.agents/core/skills/decisions/skill.yaml +6 -0
- package/.agents/core/skills/docker/skill.yaml +6 -0
- package/.agents/core/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/core/skills/engineering-workflow/skill.yaml +6 -0
- package/.agents/core/skills/fastapi/skill.yaml +7 -0
- package/.agents/core/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/core/skills/gemini-precision/skill.yaml +4 -0
- package/.agents/core/skills/generators/skill.yaml +15 -0
- package/.agents/core/skills/graphify/SKILL.md +2 -2
- package/.agents/core/skills/graphify/skill.yaml +6 -1
- package/.agents/core/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/core/skills/gstack-roles/skill.yaml +6 -0
- package/.agents/core/skills/impeccable-design/skill.yaml +6 -0
- package/.agents/core/skills/interview-me/skill.yaml +4 -0
- package/.agents/core/skills/microservices/skill.yaml +7 -0
- package/.agents/core/skills/minimalist-design/skill.yaml +4 -0
- package/.agents/core/skills/nestjs/skill.yaml +7 -0
- package/.agents/core/skills/nextjs/skill.yaml +7 -0
- package/.agents/core/skills/node/skill.yaml +7 -0
- package/.agents/core/skills/performance/skill.yaml +7 -0
- package/.agents/core/skills/ponytail-mindset/skill.yaml +6 -0
- package/.agents/core/skills/react/skill.yaml +7 -0
- package/.agents/core/skills/react-best-practices/skill.yaml +4 -0
- package/.agents/core/skills/redesign-audit/skill.yaml +4 -0
- package/.agents/core/skills/security/skill.yaml +7 -0
- package/.agents/core/skills/soft-design/skill.yaml +4 -0
- package/.agents/core/skills/state-management/skill.yaml +6 -0
- package/.agents/core/skills/subagent-orchestrator/SKILL.md +17 -0
- package/.agents/core/skills/subagent-orchestrator/skill.yaml +4 -0
- package/.agents/core/skills/system-design/skill.yaml +7 -0
- package/.agents/core/skills/testing/skill.yaml +6 -0
- package/.agents/core/skills/typescript/skill.yaml +7 -0
- package/.agents/core/skills/ui-design/skill.yaml +7 -0
- package/.agents/core/skills/ui-ux-pro/skill.yaml +6 -0
- package/.agents/core/skills/ux-design/skill.yaml +7 -0
- package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +131 -0
- package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +142 -0
- package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +143 -0
- package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +174 -0
- package/.agents/core/skills/vercel-optimize/skill.yaml +8 -0
- package/.agents/core/skills/web-accessibility/skill.yaml +7 -0
- package/.agents/ctx.js +44 -0
- package/.agents/doctor.js +193 -4
- package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/generated/claude/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/generated/claude/skills/graphify/SKILL.md +1 -1
- package/.agents/generated/claude/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +17 -0
- package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/generated/gemini/skills/graphify/SKILL.md +2 -2
- package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +17 -0
- package/.agents/plugins/contextos/hooks.json +25 -0
- package/.agents/plugins/contextos/plugin.json +19 -0
- package/.agents/plugins.js +180 -20
- package/.agents/profiles.js +26 -0
- package/.agents/resolver.js +150 -30
- package/.agents/stats.js +22 -9
- package/.agents/validate.js +152 -13
- package/README.md +77 -47
- package/bin/commands/uninstall.js +207 -0
- package/bin/commands/update.js +262 -0
- package/bin/commands.js +305 -0
- package/bin/index.js +275 -119
- package/bin/lib/detector.js +106 -0
- package/bin/lib/lockfile.js +251 -0
- package/bin/lib/safe-writer.js +259 -0
- package/package.json +6 -9
- package/registry.json +13 -5
- package/registry.schema.json +3 -1
- package/.agents/mcp/runtime.py +0 -470
- package/.agents/mcp/server.mjs +0 -189386
- package/benchmarks/gemini-issues.js +0 -533
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bin/commands/update.js
|
|
3
|
+
* ContextOS CLI — Safe Update Command
|
|
4
|
+
*
|
|
5
|
+
* Updates ContextOS installation non-destructively:
|
|
6
|
+
* - Scans distribution package against target project .agents directory
|
|
7
|
+
* - Plans all mutations: CREATE, UPDATE, CONFLICT, SKIP, DELETE
|
|
8
|
+
* - Never overwrites user-customized files (generates .contextos.new conflicts)
|
|
9
|
+
* - Never touches or deletes unmanaged user files or custom skills
|
|
10
|
+
* - Supports --dry-run to preview changes without modifying filesystem
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const {
|
|
16
|
+
loadLockfile,
|
|
17
|
+
saveLockfile,
|
|
18
|
+
migrateExistingInstallation,
|
|
19
|
+
createLockfileData,
|
|
20
|
+
} = require('../lib/lockfile.js');
|
|
21
|
+
const {
|
|
22
|
+
planFileWrite,
|
|
23
|
+
executeFileWrite,
|
|
24
|
+
planFileDelete,
|
|
25
|
+
executeFileDelete,
|
|
26
|
+
} = require('../lib/safe-writer.js');
|
|
27
|
+
|
|
28
|
+
const pkg = require('../../package.json');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Collect all distributable files from the source .agents directory.
|
|
32
|
+
*/
|
|
33
|
+
function collectDistributionFiles(sourceAgentsDir, options = {}) {
|
|
34
|
+
const files = [];
|
|
35
|
+
const excludedSkills = new Set(options.excludedSkills || []);
|
|
36
|
+
|
|
37
|
+
function walk(currentDir) {
|
|
38
|
+
let entries;
|
|
39
|
+
try {
|
|
40
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
41
|
+
} catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const full = path.join(currentDir, entry.name);
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
// Skip .git or temp dirs if any
|
|
49
|
+
if (entry.name === '.git' || entry.name === 'node_modules') continue;
|
|
50
|
+
|
|
51
|
+
// Skip excluded skills by profile
|
|
52
|
+
const relToAgents = path.relative(sourceAgentsDir, full).replace(/\\/g, '/');
|
|
53
|
+
const coreMatch = relToAgents.match(/^core\/skills\/([^/]+)$/);
|
|
54
|
+
if (coreMatch && excludedSkills.has(coreMatch[1])) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const genMatch = relToAgents.match(/^generated\/[^/]+\/skills\/([^/]+)$/);
|
|
58
|
+
if (genMatch && excludedSkills.has(genMatch[1])) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
walk(full);
|
|
63
|
+
} else if (entry.isFile()) {
|
|
64
|
+
// Do not distribute lockfile itself or backup files
|
|
65
|
+
if (entry.name === 'contextos.lock.json' || entry.name.endsWith('.bak')) continue;
|
|
66
|
+
|
|
67
|
+
const relToAgents = path.relative(sourceAgentsDir, full).replace(/\\/g, '/');
|
|
68
|
+
files.push({
|
|
69
|
+
relPath: `.agents/${relToAgents}`,
|
|
70
|
+
fullSourcePath: full,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
walk(sourceAgentsDir);
|
|
77
|
+
return files;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Plan the update operations for a project.
|
|
82
|
+
*/
|
|
83
|
+
function planUpdate(projectDir, sourceAgentsDir, options = {}) {
|
|
84
|
+
const targetAgentsDir = path.join(projectDir, '.agents');
|
|
85
|
+
if (!fs.existsSync(targetAgentsDir)) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: 'NO_AGENTS_DIR',
|
|
89
|
+
message: 'No .agents/ directory found in the target project. Run `contextos` to install first.',
|
|
90
|
+
plans: [],
|
|
91
|
+
summary: { create: 0, update: 0, conflict: 0, skip: 0, delete: 0 },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Load existing lockfile or safely migrate
|
|
96
|
+
let lockData = loadLockfile(projectDir);
|
|
97
|
+
let migrated = false;
|
|
98
|
+
if (!lockData) {
|
|
99
|
+
lockData = migrateExistingInstallation(projectDir, sourceAgentsDir, {
|
|
100
|
+
dryRun: Boolean(options.dryRun),
|
|
101
|
+
version: pkg.version,
|
|
102
|
+
});
|
|
103
|
+
migrated = true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Determine active profile exclusions if available
|
|
107
|
+
let excludedSkills = [];
|
|
108
|
+
try {
|
|
109
|
+
const profilesPath = path.join(projectDir, '.agents', 'profiles.js');
|
|
110
|
+
if (fs.existsSync(profilesPath)) {
|
|
111
|
+
const profilesModule = require(profilesPath);
|
|
112
|
+
const active = profilesModule.getActiveProfile(projectDir);
|
|
113
|
+
if (active && Array.isArray(active.exclude_skills)) {
|
|
114
|
+
excludedSkills = active.exclude_skills;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} catch {}
|
|
118
|
+
|
|
119
|
+
const distFiles = collectDistributionFiles(sourceAgentsDir, { excludedSkills });
|
|
120
|
+
const distRelPaths = new Set(distFiles.map(f => f.relPath));
|
|
121
|
+
|
|
122
|
+
const plans = [];
|
|
123
|
+
|
|
124
|
+
// 1. Plan writes for distribution files
|
|
125
|
+
for (const item of distFiles) {
|
|
126
|
+
try {
|
|
127
|
+
const content = fs.readFileSync(item.fullSourcePath, 'utf8');
|
|
128
|
+
const plan = planFileWrite({
|
|
129
|
+
projectDir,
|
|
130
|
+
relPath: item.relPath,
|
|
131
|
+
content,
|
|
132
|
+
lockData,
|
|
133
|
+
});
|
|
134
|
+
plans.push(plan);
|
|
135
|
+
} catch {}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 2. Plan deletions for stale managed files that no longer exist in distribution
|
|
139
|
+
if (lockData && lockData.managedFiles) {
|
|
140
|
+
for (const managedRel of Object.keys(lockData.managedFiles)) {
|
|
141
|
+
if (managedRel.startsWith('.agents/') && !distRelPaths.has(managedRel)) {
|
|
142
|
+
const delPlan = planFileDelete({
|
|
143
|
+
projectDir,
|
|
144
|
+
relPath: managedRel,
|
|
145
|
+
lockData,
|
|
146
|
+
});
|
|
147
|
+
plans.push(delPlan);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const summary = {
|
|
153
|
+
create: plans.filter(p => p.action === 'CREATE').length,
|
|
154
|
+
update: plans.filter(p => p.action === 'UPDATE').length,
|
|
155
|
+
conflict: plans.filter(p => p.action === 'CONFLICT').length,
|
|
156
|
+
skip: plans.filter(p => p.action === 'SKIP').length,
|
|
157
|
+
delete: plans.filter(p => p.action === 'DELETE').length,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
ok: true,
|
|
162
|
+
lockData,
|
|
163
|
+
migrated,
|
|
164
|
+
plans,
|
|
165
|
+
summary,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Run the update command against a target project.
|
|
171
|
+
*/
|
|
172
|
+
function runUpdate(projectDir = process.cwd(), options = {}) {
|
|
173
|
+
const sourceAgentsDir = options.sourceAgentsDir || path.join(__dirname, '..', '..', '.agents');
|
|
174
|
+
const dryRun = Boolean(options.dryRun);
|
|
175
|
+
|
|
176
|
+
console.log(`\nContextOS Safe Update ${dryRun ? '[DRY-RUN]' : ''}`);
|
|
177
|
+
console.log(`Target: ${projectDir}`);
|
|
178
|
+
|
|
179
|
+
const planned = planUpdate(projectDir, sourceAgentsDir, options);
|
|
180
|
+
if (!planned.ok) {
|
|
181
|
+
console.error(`\n[ERROR] ${planned.message}`);
|
|
182
|
+
return planned;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const { lockData, plans, summary } = planned;
|
|
186
|
+
|
|
187
|
+
// Print classified plan preview
|
|
188
|
+
console.log('\nPlanned Actions:');
|
|
189
|
+
for (const item of plans) {
|
|
190
|
+
if (item.action === 'CREATE') {
|
|
191
|
+
console.log(` + CREATE ${item.relPath}`);
|
|
192
|
+
} else if (item.action === 'UPDATE') {
|
|
193
|
+
console.log(` ~ UPDATE ${item.relPath}`);
|
|
194
|
+
} else if (item.action === 'CONFLICT') {
|
|
195
|
+
console.log(` ! CONFLICT ${item.relPath} → ${item.conflictPath || item.relPath + '.contextos.new'} (customized by user)`);
|
|
196
|
+
} else if (item.action === 'DELETE') {
|
|
197
|
+
console.log(` - DELETE ${item.relPath} (stale managed file)`);
|
|
198
|
+
} else if (item.action === 'SKIP' && item.reason === 'UNMANAGED_USER_FILE') {
|
|
199
|
+
console.log(` = PRESERVE ${item.relPath} (unmanaged user file)`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log('\nSummary:');
|
|
204
|
+
console.log(` Created: ${summary.create}`);
|
|
205
|
+
console.log(` Updated: ${summary.update}`);
|
|
206
|
+
console.log(` Conflicts: ${summary.conflict} (written as .contextos.new)`);
|
|
207
|
+
console.log(` Deleted: ${summary.delete}`);
|
|
208
|
+
console.log(` Skipped: ${summary.skip}`);
|
|
209
|
+
|
|
210
|
+
if (dryRun) {
|
|
211
|
+
console.log('\n[DRY-RUN] No changes were written to disk.');
|
|
212
|
+
return { ...planned, executed: false };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Execute plans
|
|
216
|
+
let executedCount = 0;
|
|
217
|
+
for (const item of plans) {
|
|
218
|
+
if (item.action === 'CREATE' || item.action === 'UPDATE' || item.action === 'CONFLICT') {
|
|
219
|
+
executeFileWrite(item, lockData, { dryRun: false });
|
|
220
|
+
if (item.action !== 'SKIP') executedCount++;
|
|
221
|
+
} else if (item.action === 'DELETE') {
|
|
222
|
+
executeFileDelete(item, lockData, { dryRun: false });
|
|
223
|
+
executedCount++;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Update lockfile metadata
|
|
228
|
+
lockData.version = options.packageVersion || pkg.version;
|
|
229
|
+
lockData.updatedAt = new Date().toISOString();
|
|
230
|
+
saveLockfile(projectDir, lockData);
|
|
231
|
+
|
|
232
|
+
console.log(`\n[OK] ContextOS successfully updated to v${lockData.version}!`);
|
|
233
|
+
if (summary.conflict > 0) {
|
|
234
|
+
console.warn(`[WARN] ${summary.conflict} customized file(s) had upstream changes.`);
|
|
235
|
+
console.warn(' Inspect the .contextos.new file(s) to merge upstream updates.');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Re-compile if ctx.js exists and not skipped
|
|
239
|
+
if (!options.skipCompile) {
|
|
240
|
+
const ctxPath = path.join(projectDir, '.agents', 'ctx.js');
|
|
241
|
+
if (fs.existsSync(ctxPath)) {
|
|
242
|
+
try {
|
|
243
|
+
const { execFileSync } = require('child_process');
|
|
244
|
+
console.log('\nRecompiling skills...');
|
|
245
|
+
execFileSync(process.execPath, [ctxPath, 'export', 'gemini'], {
|
|
246
|
+
cwd: projectDir,
|
|
247
|
+
stdio: 'inherit',
|
|
248
|
+
});
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.warn(`[WARN] Post-update compilation failed: ${err.message}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return { ...planned, executed: true, executedCount };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
module.exports = {
|
|
259
|
+
planUpdate,
|
|
260
|
+
runUpdate,
|
|
261
|
+
collectDistributionFiles,
|
|
262
|
+
};
|
package/bin/commands.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bin/commands.js
|
|
3
|
+
* ContextOS — Declarative CLI Command Registry & Status Engine
|
|
4
|
+
*
|
|
5
|
+
* Defines the canonical registry of CLI commands, options, and handlers.
|
|
6
|
+
* Supports structured execution and machine-readable --json output.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { version } = require('../package.json');
|
|
14
|
+
const profiles = require('../.agents/profiles.js');
|
|
15
|
+
const lockfileLib = require('./lib/lockfile.js');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Collects holistic status metadata for a target project.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} [projectDir=process.cwd()] - Project directory
|
|
21
|
+
* @returns {Object} Structured project status
|
|
22
|
+
*/
|
|
23
|
+
function getStatus(projectDir = process.cwd()) {
|
|
24
|
+
const agentsDir = path.join(projectDir, '.agents');
|
|
25
|
+
const hasAgents = fs.existsSync(agentsDir);
|
|
26
|
+
const lockData = lockfileLib.loadLockfile(projectDir);
|
|
27
|
+
const activeProfile = hasAgents ? profiles.getActiveProfile(projectDir) : null;
|
|
28
|
+
|
|
29
|
+
let skillsCount = 0;
|
|
30
|
+
const skillsDir = path.join(agentsDir, 'core', 'skills');
|
|
31
|
+
if (fs.existsSync(skillsDir)) {
|
|
32
|
+
try {
|
|
33
|
+
skillsCount = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
34
|
+
.filter(d => d.isDirectory() && fs.existsSync(path.join(skillsDir, d.name, 'SKILL.md')))
|
|
35
|
+
.length;
|
|
36
|
+
} catch {
|
|
37
|
+
skillsCount = 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const mcpServerPath = path.join(agentsDir, 'mcp', 'server.mjs');
|
|
42
|
+
const mcpInstalled = fs.existsSync(mcpServerPath);
|
|
43
|
+
|
|
44
|
+
// Check compiled adapter outputs
|
|
45
|
+
const compiledAdapters = [];
|
|
46
|
+
if (fs.existsSync(path.join(agentsDir, 'generated', 'gemini', 'skills'))) compiledAdapters.push('gemini');
|
|
47
|
+
if (fs.existsSync(path.join(agentsDir, 'generated', 'claude', 'skills'))) compiledAdapters.push('claude');
|
|
48
|
+
if (fs.existsSync(path.join(projectDir, '.cursorrules')) || fs.existsSync(path.join(projectDir, '.cursor', 'rules'))) compiledAdapters.push('cursor');
|
|
49
|
+
if (fs.existsSync(path.join(projectDir, '.github', 'copilot-instructions.md'))) compiledAdapters.push('copilot');
|
|
50
|
+
if (fs.existsSync(path.join(projectDir, '.aider.conf.yml'))) compiledAdapters.push('aider');
|
|
51
|
+
if (fs.existsSync(path.join(projectDir, '.zed', 'rules.md'))) compiledAdapters.push('zed');
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
package: 'contextos-agents',
|
|
55
|
+
version,
|
|
56
|
+
projectDir,
|
|
57
|
+
initialized: hasAgents,
|
|
58
|
+
activeProfile: activeProfile ? activeProfile.profile : (lockData?.selectedProfile || 'none'),
|
|
59
|
+
skillsCount,
|
|
60
|
+
mcpInstalled,
|
|
61
|
+
compiledAdapters,
|
|
62
|
+
lockfile: lockData ? {
|
|
63
|
+
schemaVersion: lockData.schemaVersion,
|
|
64
|
+
version: lockData.version,
|
|
65
|
+
managedFilesCount: Object.keys(lockData.managedFiles || {}).length,
|
|
66
|
+
selectedProfile: lockData.selectedProfile,
|
|
67
|
+
installedAt: lockData.installedAt,
|
|
68
|
+
updatedAt: lockData.updatedAt,
|
|
69
|
+
} : null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Formats status object as human-readable text.
|
|
75
|
+
*
|
|
76
|
+
* @param {Object} status - Status object
|
|
77
|
+
* @returns {string} Formatted text
|
|
78
|
+
*/
|
|
79
|
+
function formatStatusText(status) {
|
|
80
|
+
const lines = [
|
|
81
|
+
`ContextOS Project Status (v${status.version})`,
|
|
82
|
+
`──────────────────────────────────────────────`,
|
|
83
|
+
` Project Dir : ${status.projectDir}`,
|
|
84
|
+
` Initialized : ${status.initialized ? 'Yes (.agents/ found)' : 'No (run: npx contextos-agents init)'}`,
|
|
85
|
+
` Active Profile : ${status.activeProfile}`,
|
|
86
|
+
` Skills Installed : ${status.skillsCount}`,
|
|
87
|
+
` MCP Server : ${status.mcpInstalled ? 'Installed (.agents/mcp/)' : 'Not installed'}`,
|
|
88
|
+
` Adapters Compiled : ${status.compiledAdapters.length ? status.compiledAdapters.join(', ') : 'none'}`,
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
if (status.lockfile) {
|
|
92
|
+
lines.push(` Lockfile : Valid (${status.lockfile.managedFilesCount} managed files, v${status.lockfile.version})`);
|
|
93
|
+
} else if (status.initialized) {
|
|
94
|
+
lines.push(` Lockfile : Missing (.agents/contextos.lock.json)`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return lines.join('\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Registry of canonical ContextOS CLI commands.
|
|
102
|
+
*/
|
|
103
|
+
const COMMAND_REGISTRY = {
|
|
104
|
+
init: {
|
|
105
|
+
name: 'init',
|
|
106
|
+
description: 'Install and configure .agents/ in target project',
|
|
107
|
+
usage: 'npx contextos-agents init [options]',
|
|
108
|
+
requiresProject: false,
|
|
109
|
+
options: [
|
|
110
|
+
{ flag: '--agent <target>', desc: 'Target agent (gemini, claude, cursor, auto)' },
|
|
111
|
+
{ flag: '--profile <name>', desc: 'Apply specific profile (mvp, startup, enterprise, etc.)' },
|
|
112
|
+
{ flag: '--auto', desc: 'Auto-detect tech stack and recommended profile' },
|
|
113
|
+
{ flag: '--minimal', desc: 'Install only 5 core essential skills' },
|
|
114
|
+
{ flag: '--with-mcp', desc: 'Install with MCP execution server enabled' },
|
|
115
|
+
{ flag: '--force', desc: 'Overwrite existing installation' },
|
|
116
|
+
{ flag: '--dry-run', desc: 'Preview installation actions without writing' },
|
|
117
|
+
{ flag: '--skip-compile', desc: 'Skip auto-compiling skills after install' },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
status: {
|
|
121
|
+
name: 'status',
|
|
122
|
+
description: 'Display project configuration, active profile, and lockfile status',
|
|
123
|
+
usage: 'contextos status [--json]',
|
|
124
|
+
requiresProject: false,
|
|
125
|
+
options: [
|
|
126
|
+
{ flag: '--json', desc: 'Output status in JSON format' },
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
update: {
|
|
130
|
+
name: 'update',
|
|
131
|
+
description: 'Safely update skills without overwriting user custom changes',
|
|
132
|
+
usage: 'contextos update [--dry-run] [--profile <name>] [--json]',
|
|
133
|
+
requiresProject: true,
|
|
134
|
+
options: [
|
|
135
|
+
{ flag: '--dry-run', desc: 'Preview updates without modifying disk' },
|
|
136
|
+
{ flag: '--profile <name>', desc: 'Switch profile during update' },
|
|
137
|
+
{ flag: '--json', desc: 'Output results in JSON format' },
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
uninstall: {
|
|
141
|
+
name: 'uninstall',
|
|
142
|
+
description: 'Safely uninstall managed ContextOS files (preserves user custom skills)',
|
|
143
|
+
usage: 'contextos uninstall [--dry-run] [--json]',
|
|
144
|
+
requiresProject: true,
|
|
145
|
+
options: [
|
|
146
|
+
{ flag: '--dry-run', desc: 'Preview deletions without modifying disk' },
|
|
147
|
+
{ flag: '--json', desc: 'Output results in JSON format' },
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
doctor: {
|
|
151
|
+
name: 'doctor',
|
|
152
|
+
description: 'Run project diagnostic health check',
|
|
153
|
+
usage: 'contextos doctor [--json]',
|
|
154
|
+
requiresProject: false,
|
|
155
|
+
options: [
|
|
156
|
+
{ flag: '--json', desc: 'Output health report in JSON format' },
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
validate: {
|
|
160
|
+
name: 'validate',
|
|
161
|
+
description: 'Validate skill sources, frontmatter, dependency graph, and adapter sync',
|
|
162
|
+
usage: 'contextos validate',
|
|
163
|
+
requiresProject: true,
|
|
164
|
+
aliases: ['audit'],
|
|
165
|
+
options: [],
|
|
166
|
+
},
|
|
167
|
+
audit: {
|
|
168
|
+
name: 'audit',
|
|
169
|
+
target: 'validate',
|
|
170
|
+
description: 'Alias for validate (check skills and sync)',
|
|
171
|
+
usage: 'contextos audit',
|
|
172
|
+
requiresProject: true,
|
|
173
|
+
options: [],
|
|
174
|
+
},
|
|
175
|
+
export: {
|
|
176
|
+
name: 'export',
|
|
177
|
+
description: 'Compile skills for target agent (gemini, claude, cursor, copilot, aider, zed, all)',
|
|
178
|
+
usage: 'contextos export <target> [--profile <name>]',
|
|
179
|
+
requiresProject: true,
|
|
180
|
+
options: [
|
|
181
|
+
{ flag: '--profile <name>', desc: 'Apply profile for this export run' },
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
profile: {
|
|
185
|
+
name: 'profile',
|
|
186
|
+
description: 'Manage project profiles (list, show, apply, remove)',
|
|
187
|
+
usage: 'contextos profile <list|show|apply|remove> [name]',
|
|
188
|
+
requiresProject: true,
|
|
189
|
+
options: [],
|
|
190
|
+
},
|
|
191
|
+
resolve: {
|
|
192
|
+
name: 'resolve',
|
|
193
|
+
description: 'Dynamically resolve minimal skills needed for a prompt or files',
|
|
194
|
+
usage: 'contextos resolve <prompt> [--files <a,b>] [--phase <p>] [--json]',
|
|
195
|
+
requiresProject: true,
|
|
196
|
+
options: [
|
|
197
|
+
{ flag: '--files <list>', desc: 'Comma-separated list of touched files' },
|
|
198
|
+
{ flag: '--phase <name>', desc: 'Lifecycle phase (Define, Plan, Build, Verify, Review, Ship)' },
|
|
199
|
+
{ flag: '--json', desc: 'Output resolution in JSON format' },
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
stats: {
|
|
203
|
+
name: 'stats',
|
|
204
|
+
description: 'Display token context savings report',
|
|
205
|
+
usage: 'contextos stats [--json]',
|
|
206
|
+
requiresProject: true,
|
|
207
|
+
options: [
|
|
208
|
+
{ flag: '--json', desc: 'Output context savings in JSON format' },
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
watch: {
|
|
212
|
+
name: 'watch',
|
|
213
|
+
description: 'Start continuous file watcher and auto-sync daemon',
|
|
214
|
+
usage: 'contextos watch',
|
|
215
|
+
requiresProject: true,
|
|
216
|
+
options: [],
|
|
217
|
+
},
|
|
218
|
+
detect: {
|
|
219
|
+
name: 'detect',
|
|
220
|
+
description: 'Auto-detect project tech stack and recommend profile',
|
|
221
|
+
usage: 'contextos detect [--json]',
|
|
222
|
+
requiresProject: false,
|
|
223
|
+
options: [
|
|
224
|
+
{ flag: '--json', desc: 'Output detection result in JSON format' },
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
'install-skill': {
|
|
228
|
+
name: 'install-skill',
|
|
229
|
+
description: 'Interactive community skill installer or install by reference',
|
|
230
|
+
usage: 'contextos install-skill [<ref> | --from-repo <ref>]',
|
|
231
|
+
requiresProject: true,
|
|
232
|
+
options: [
|
|
233
|
+
{ flag: '--from-repo <ref>', desc: 'Install directly from repository ref' },
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
skill: {
|
|
237
|
+
name: 'skill',
|
|
238
|
+
description: 'Manage community plugin skills (add, remove, list, search)',
|
|
239
|
+
usage: 'contextos skill <add|remove|list|search> [args...]',
|
|
240
|
+
requiresProject: true,
|
|
241
|
+
options: [],
|
|
242
|
+
},
|
|
243
|
+
'setup-mcp': {
|
|
244
|
+
name: 'setup-mcp',
|
|
245
|
+
description: 'Add MCP execution server to an existing .agents/ project',
|
|
246
|
+
usage: 'contextos setup-mcp [--force]',
|
|
247
|
+
requiresProject: true,
|
|
248
|
+
options: [
|
|
249
|
+
{ flag: '--force', desc: 'Overwrite existing .agents/mcp_config.json' },
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
'clean-worktrees': {
|
|
253
|
+
name: 'clean-worktrees',
|
|
254
|
+
description: 'Clean up lingering .swarm-worktrees and swarm/* branches',
|
|
255
|
+
usage: 'contextos clean-worktrees',
|
|
256
|
+
requiresProject: false,
|
|
257
|
+
options: [],
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Resolves a command name or alias to canonical command definition.
|
|
263
|
+
*
|
|
264
|
+
* @param {string} name - Command name or alias
|
|
265
|
+
* @returns {Object|null} Command definition or null if unknown
|
|
266
|
+
*/
|
|
267
|
+
function getCommand(name) {
|
|
268
|
+
if (!name) return null;
|
|
269
|
+
const lower = name.toLowerCase().trim();
|
|
270
|
+
if (COMMAND_REGISTRY[lower]) {
|
|
271
|
+
const cmd = COMMAND_REGISTRY[lower];
|
|
272
|
+
if (cmd.target && COMMAND_REGISTRY[cmd.target]) {
|
|
273
|
+
return COMMAND_REGISTRY[cmd.target];
|
|
274
|
+
}
|
|
275
|
+
return cmd;
|
|
276
|
+
}
|
|
277
|
+
// Check aliases
|
|
278
|
+
for (const cmd of Object.values(COMMAND_REGISTRY)) {
|
|
279
|
+
if (cmd.aliases && cmd.aliases.includes(lower)) {
|
|
280
|
+
if (cmd.target && COMMAND_REGISTRY[cmd.target]) {
|
|
281
|
+
return COMMAND_REGISTRY[cmd.target];
|
|
282
|
+
}
|
|
283
|
+
return cmd;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Checks if a command name is recognized by the registry.
|
|
291
|
+
*
|
|
292
|
+
* @param {string} name - Command name to check
|
|
293
|
+
* @returns {boolean} True if known
|
|
294
|
+
*/
|
|
295
|
+
function isKnownCommand(name) {
|
|
296
|
+
return getCommand(name) !== null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
module.exports = {
|
|
300
|
+
COMMAND_REGISTRY,
|
|
301
|
+
getCommand,
|
|
302
|
+
isKnownCommand,
|
|
303
|
+
getStatus,
|
|
304
|
+
formatStatusText,
|
|
305
|
+
};
|