liteagents 2.4.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/CHANGELOG.md +441 -0
- package/LICENSE +21 -0
- package/README.md +179 -0
- package/cli.js +230 -0
- package/docs/.gitkeep +1 -0
- package/docs/CONTRIBUTING.md +739 -0
- package/docs/DUAL_PUBLISH_SUMMARY.md +177 -0
- package/docs/ERROR_HANDLING_IMPLEMENTATION.md +327 -0
- package/docs/GITHUB_PACKAGES.md +181 -0
- package/docs/GITHUB_SETUP.md +158 -0
- package/docs/INSTALLATION_DEMO.md +691 -0
- package/docs/INSTALLATION_LOCATIONS.md +299 -0
- package/docs/INSTALLER_GUIDE.md +1586 -0
- package/docs/INTEGRATION_ISSUES_9.1.md +341 -0
- package/docs/KNOWLEDGE_BASE.md +727 -0
- package/docs/MIGRATION.md +384 -0
- package/docs/PACKAGE_BASELINE.md +557 -0
- package/docs/PACKAGE_VALIDATION_REPORT.md +427 -0
- package/docs/PASS_INTEGRATION.md +307 -0
- package/docs/PASS_QUICK_START.md +150 -0
- package/docs/PRIVACY.md +203 -0
- package/docs/PUBLISHING.md +494 -0
- package/docs/QUICK-START.md +318 -0
- package/docs/RELEASE_NOTES_1.2.0.md +323 -0
- package/docs/SECURITY.md +317 -0
- package/docs/SILENT_MODE_GUIDE.md +526 -0
- package/docs/SKILLS_CONVERSION.md +154 -0
- package/docs/TESTING.md +582 -0
- package/docs/TEST_COVERAGE.md +347 -0
- package/docs/TROUBLESHOOTING.md +788 -0
- package/docs/UPDATED_VARIANT_CONFIGURATION.md +274 -0
- package/docs/VARIANT_CONFIGURATION.md +440 -0
- package/installer/cli.js +761 -0
- package/installer/installation-engine.js +1536 -0
- package/installer/package-manager.js +640 -0
- package/installer/path-manager.js +427 -0
- package/installer/report-template.js +298 -0
- package/installer/verification-system.js +274 -0
- package/package.json +83 -0
- package/packages/ampcode/AGENT.md +58 -0
- package/packages/ampcode/README.md +17 -0
- package/packages/ampcode/agents/1-create-prd.md +175 -0
- package/packages/ampcode/agents/2-generate-tasks.md +190 -0
- package/packages/ampcode/agents/3-process-task-list.md +225 -0
- package/packages/ampcode/agents/code-developer.md +198 -0
- package/packages/ampcode/agents/context-builder.md +142 -0
- package/packages/ampcode/agents/feature-planner.md +199 -0
- package/packages/ampcode/agents/market-researcher.md +89 -0
- package/packages/ampcode/agents/orchestrator.md +116 -0
- package/packages/ampcode/agents/quality-assurance.md +115 -0
- package/packages/ampcode/agents/system-architect.md +135 -0
- package/packages/ampcode/agents/ui-designer.md +184 -0
- package/packages/ampcode/commands/brainstorming.md +56 -0
- package/packages/ampcode/commands/code-review.md +107 -0
- package/packages/ampcode/commands/condition-based-waiting/example.ts +158 -0
- package/packages/ampcode/commands/condition-based-waiting.md +122 -0
- package/packages/ampcode/commands/debug.md +20 -0
- package/packages/ampcode/commands/docs-builder/templates.md +572 -0
- package/packages/ampcode/commands/docs-builder.md +106 -0
- package/packages/ampcode/commands/explain.md +18 -0
- package/packages/ampcode/commands/git-commit.md +14 -0
- package/packages/ampcode/commands/optimize.md +20 -0
- package/packages/ampcode/commands/refactor.md +21 -0
- package/packages/ampcode/commands/review.md +18 -0
- package/packages/ampcode/commands/root-cause-tracing/find-polluter.sh +63 -0
- package/packages/ampcode/commands/root-cause-tracing.md +176 -0
- package/packages/ampcode/commands/security.md +21 -0
- package/packages/ampcode/commands/ship.md +18 -0
- package/packages/ampcode/commands/skill-creator/scripts/init_skill.py +303 -0
- package/packages/ampcode/commands/skill-creator/scripts/package_skill.py +110 -0
- package/packages/ampcode/commands/skill-creator/scripts/quick_validate.py +65 -0
- package/packages/ampcode/commands/skill-creator.md +211 -0
- package/packages/ampcode/commands/stash.md +45 -0
- package/packages/ampcode/commands/systematic-debugging.md +297 -0
- package/packages/ampcode/commands/test-driven-development.md +390 -0
- package/packages/ampcode/commands/test-generate.md +18 -0
- package/packages/ampcode/commands/testing-anti-patterns.md +304 -0
- package/packages/ampcode/commands/verification-before-completion.md +152 -0
- package/packages/ampcode/settings.json +13 -0
- package/packages/ampcode/variants.json +8 -0
- package/packages/claude/CLAUDE.md +58 -0
- package/packages/claude/README.md +23 -0
- package/packages/claude/agents/1-create-prd.md +175 -0
- package/packages/claude/agents/2-generate-tasks.md +190 -0
- package/packages/claude/agents/3-process-task-list.md +225 -0
- package/packages/claude/agents/code-developer.md +198 -0
- package/packages/claude/agents/context-builder.md +142 -0
- package/packages/claude/agents/feature-planner.md +199 -0
- package/packages/claude/agents/market-researcher.md +89 -0
- package/packages/claude/agents/orchestrator.md +117 -0
- package/packages/claude/agents/quality-assurance.md +115 -0
- package/packages/claude/agents/system-architect.md +135 -0
- package/packages/claude/agents/ui-designer.md +184 -0
- package/packages/claude/commands/debug.md +20 -0
- package/packages/claude/commands/explain.md +18 -0
- package/packages/claude/commands/git-commit.md +14 -0
- package/packages/claude/commands/optimize.md +20 -0
- package/packages/claude/commands/refactor.md +21 -0
- package/packages/claude/commands/review.md +18 -0
- package/packages/claude/commands/security.md +21 -0
- package/packages/claude/commands/ship.md +18 -0
- package/packages/claude/commands/stash.md +45 -0
- package/packages/claude/commands/test-generate.md +18 -0
- package/packages/claude/skills/brainstorming/SKILL.md +56 -0
- package/packages/claude/skills/code-review/SKILL.md +107 -0
- package/packages/claude/skills/code-review/code-reviewer.md +146 -0
- package/packages/claude/skills/condition-based-waiting/SKILL.md +122 -0
- package/packages/claude/skills/condition-based-waiting/example.ts +158 -0
- package/packages/claude/skills/docs-builder/SKILL.md +106 -0
- package/packages/claude/skills/docs-builder/references/templates.md +572 -0
- package/packages/claude/skills/root-cause-tracing/SKILL.md +176 -0
- package/packages/claude/skills/root-cause-tracing/find-polluter.sh +63 -0
- package/packages/claude/skills/skill-creator/LICENSE.txt +202 -0
- package/packages/claude/skills/skill-creator/SKILL.md +211 -0
- package/packages/claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/packages/claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/packages/claude/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/packages/claude/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/packages/claude/skills/systematic-debugging/SKILL.md +296 -0
- package/packages/claude/skills/systematic-debugging/test-academic.md +14 -0
- package/packages/claude/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/packages/claude/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/packages/claude/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/packages/claude/skills/test-driven-development/SKILL.md +392 -0
- package/packages/claude/skills/testing-anti-patterns/SKILL.md +304 -0
- package/packages/claude/skills/verification-before-completion/SKILL.md +152 -0
- package/packages/claude/variants.json +9 -0
- package/packages/droid/AGENTS.md +52 -0
- package/packages/droid/README.md +17 -0
- package/packages/droid/change_settings.json +61 -0
- package/packages/droid/commands/brainstorming.md +56 -0
- package/packages/droid/commands/code-review.md +107 -0
- package/packages/droid/commands/condition-based-waiting/example.ts +158 -0
- package/packages/droid/commands/condition-based-waiting.md +122 -0
- package/packages/droid/commands/debug.md +20 -0
- package/packages/droid/commands/docs-builder/templates.md +572 -0
- package/packages/droid/commands/docs-builder.md +106 -0
- package/packages/droid/commands/explain.md +18 -0
- package/packages/droid/commands/git-commit.md +14 -0
- package/packages/droid/commands/optimize.md +20 -0
- package/packages/droid/commands/refactor.md +21 -0
- package/packages/droid/commands/review.md +18 -0
- package/packages/droid/commands/root-cause-tracing/find-polluter.sh +63 -0
- package/packages/droid/commands/root-cause-tracing.md +176 -0
- package/packages/droid/commands/security.md +21 -0
- package/packages/droid/commands/ship.md +18 -0
- package/packages/droid/commands/skill-creator/scripts/init_skill.py +303 -0
- package/packages/droid/commands/skill-creator/scripts/package_skill.py +110 -0
- package/packages/droid/commands/skill-creator/scripts/quick_validate.py +65 -0
- package/packages/droid/commands/skill-creator.md +211 -0
- package/packages/droid/commands/stash.md +45 -0
- package/packages/droid/commands/systematic-debugging.md +297 -0
- package/packages/droid/commands/test-driven-development.md +390 -0
- package/packages/droid/commands/test-generate.md +18 -0
- package/packages/droid/commands/testing-anti-patterns.md +304 -0
- package/packages/droid/commands/verification-before-completion.md +152 -0
- package/packages/droid/droids/1-create-prd.md +170 -0
- package/packages/droid/droids/2-generate-tasks.md +190 -0
- package/packages/droid/droids/3-process-task-list.md +225 -0
- package/packages/droid/droids/code-developer.md +198 -0
- package/packages/droid/droids/context-builder.md +142 -0
- package/packages/droid/droids/feature-planner.md +199 -0
- package/packages/droid/droids/market-researcher.md +89 -0
- package/packages/droid/droids/orchestrator.md +116 -0
- package/packages/droid/droids/quality-assurance.md +115 -0
- package/packages/droid/droids/system-architect.md +135 -0
- package/packages/droid/droids/ui-designer.md +184 -0
- package/packages/droid/variants.json +8 -0
- package/packages/opencode/AGENTS.md +52 -0
- package/packages/opencode/README.md +17 -0
- package/packages/opencode/agent/1-create-prd.md +179 -0
- package/packages/opencode/agent/2-generate-tasks.md +194 -0
- package/packages/opencode/agent/3-process-task-list.md +229 -0
- package/packages/opencode/agent/code-developer.md +202 -0
- package/packages/opencode/agent/context-builder.md +146 -0
- package/packages/opencode/agent/feature-planner.md +203 -0
- package/packages/opencode/agent/market-researcher.md +93 -0
- package/packages/opencode/agent/orchestrator.md +120 -0
- package/packages/opencode/agent/quality-assurance.md +119 -0
- package/packages/opencode/agent/system-architect.md +139 -0
- package/packages/opencode/agent/ui-designer.md +188 -0
- package/packages/opencode/command/brainstorming.md +56 -0
- package/packages/opencode/command/code-review.md +107 -0
- package/packages/opencode/command/condition-based-waiting/example.ts +158 -0
- package/packages/opencode/command/condition-based-waiting.md +122 -0
- package/packages/opencode/command/debug.md +20 -0
- package/packages/opencode/command/docs-builder/templates.md +572 -0
- package/packages/opencode/command/docs-builder.md +106 -0
- package/packages/opencode/command/explain.md +18 -0
- package/packages/opencode/command/git-commit.md +14 -0
- package/packages/opencode/command/optimize.md +20 -0
- package/packages/opencode/command/refactor.md +21 -0
- package/packages/opencode/command/review.md +18 -0
- package/packages/opencode/command/root-cause-tracing/find-polluter.sh +63 -0
- package/packages/opencode/command/root-cause-tracing.md +176 -0
- package/packages/opencode/command/security.md +21 -0
- package/packages/opencode/command/ship.md +18 -0
- package/packages/opencode/command/skill-creator/scripts/init_skill.py +303 -0
- package/packages/opencode/command/skill-creator/scripts/package_skill.py +110 -0
- package/packages/opencode/command/skill-creator/scripts/quick_validate.py +65 -0
- package/packages/opencode/command/skill-creator.md +211 -0
- package/packages/opencode/command/stash.md +45 -0
- package/packages/opencode/command/systematic-debugging.md +297 -0
- package/packages/opencode/command/test-driven-development.md +390 -0
- package/packages/opencode/command/test-generate.md +18 -0
- package/packages/opencode/command/testing-anti-patterns.md +304 -0
- package/packages/opencode/command/verification-before-completion.md +152 -0
- package/packages/opencode/opencode.jsonc +201 -0
- package/packages/opencode/variants.json +8 -0
- package/packages/subagentic-manual.md +349 -0
- package/postinstall.js +21 -0
- package/tools/ampcode/manifest-template.json +14 -0
- package/tools/claude/manifest-template.json +14 -0
- package/tools/droid/manifest-template.json +14 -0
- package/tools/opencode/manifest-template.json +14 -0
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package Manager for Agentic Kit Installer
|
|
3
|
+
*
|
|
4
|
+
* Manages tool-specific variant packages and content selection
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
class PackageManager {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.packagesDir = path.join(__dirname, '..', 'packages');
|
|
13
|
+
this.variants = ['pro'];
|
|
14
|
+
this.tools = ['claude', 'opencode', 'ampcode', 'droid'];
|
|
15
|
+
this.variantConfigCache = {}; // Cache for loaded variant configurations
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Load and parse variants.json for a specific tool
|
|
20
|
+
* @param {string} toolId - Tool identifier (claude, opencode, ampcode, droid)
|
|
21
|
+
* @returns {Promise<Object>} Parsed variant configuration
|
|
22
|
+
* @throws {Error} If file not found, invalid JSON, or missing required variants
|
|
23
|
+
*/
|
|
24
|
+
async loadVariantConfig(toolId) {
|
|
25
|
+
// Return cached configuration if available
|
|
26
|
+
if (this.variantConfigCache[toolId]) {
|
|
27
|
+
return this.variantConfigCache[toolId];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const variantsFilePath = path.join(this.packagesDir, toolId, 'variants.json');
|
|
31
|
+
|
|
32
|
+
// Check if variants.json exists
|
|
33
|
+
if (!fs.existsSync(variantsFilePath)) {
|
|
34
|
+
throw new Error(`Variants file not found for tool: ${toolId}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Read and parse JSON with security checks
|
|
38
|
+
let config;
|
|
39
|
+
try {
|
|
40
|
+
// Check file size before reading (max 1MB to prevent DoS)
|
|
41
|
+
const stats = await fs.promises.stat(variantsFilePath);
|
|
42
|
+
const maxSize = 1024 * 1024; // 1MB
|
|
43
|
+
if (stats.size > maxSize) {
|
|
44
|
+
throw new Error(`Variants file for tool ${toolId} is too large (${stats.size} bytes, max ${maxSize} bytes)`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const fileContent = await fs.promises.readFile(variantsFilePath, 'utf8');
|
|
48
|
+
|
|
49
|
+
// Check for null bytes (security risk)
|
|
50
|
+
if (fileContent.includes('\0')) {
|
|
51
|
+
throw new Error(`Variants file for tool ${toolId} contains null bytes (security risk)`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
config = JSON.parse(fileContent);
|
|
55
|
+
|
|
56
|
+
// Validate config is an object (not array or primitive)
|
|
57
|
+
if (typeof config !== 'object' || config === null || Array.isArray(config)) {
|
|
58
|
+
throw new Error(`Variants file for tool ${toolId} must contain a JSON object`);
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error instanceof SyntaxError) {
|
|
62
|
+
throw new Error(`Invalid JSON in variants.json for tool ${toolId}: ${error.message}`);
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Validate required variants exist
|
|
68
|
+
const requiredVariants = ['pro'];
|
|
69
|
+
for (const variant of requiredVariants) {
|
|
70
|
+
if (!config[variant]) {
|
|
71
|
+
throw new Error(`Required variant '${variant}' not found in variants.json for tool ${toolId}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Validate each variant has required fields
|
|
76
|
+
// Note: 'skills', 'commands', 'resources', 'hooks' are optional
|
|
77
|
+
for (const variant of requiredVariants) {
|
|
78
|
+
const variantConfig = config[variant];
|
|
79
|
+
const requiredFields = ['name', 'description', 'agents'];
|
|
80
|
+
|
|
81
|
+
for (const field of requiredFields) {
|
|
82
|
+
if (variantConfig[field] === undefined) {
|
|
83
|
+
throw new Error(`Required field '${field}' missing in '${variant}' variant for tool ${toolId}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Cache the configuration
|
|
89
|
+
this.variantConfigCache[toolId] = config;
|
|
90
|
+
|
|
91
|
+
return config;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get metadata for a specific variant
|
|
96
|
+
* @param {string} toolId - Tool identifier
|
|
97
|
+
* @param {string} variant - Variant name (lite, standard, pro)
|
|
98
|
+
* @returns {Promise<Object>} Variant metadata (name, description, useCase, targetUsers)
|
|
99
|
+
* @throws {Error} If variant not found
|
|
100
|
+
*/
|
|
101
|
+
async getVariantMetadata(toolId, variant) {
|
|
102
|
+
const config = await this.loadVariantConfig(toolId);
|
|
103
|
+
|
|
104
|
+
if (!config[variant]) {
|
|
105
|
+
throw new Error(`Variant '${variant}' not found for tool ${toolId}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const variantConfig = config[variant];
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
name: variantConfig.name,
|
|
112
|
+
description: variantConfig.description,
|
|
113
|
+
useCase: variantConfig.useCase,
|
|
114
|
+
targetUsers: variantConfig.targetUsers
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Select content based on variant configuration
|
|
120
|
+
* @param {string} toolId - Tool identifier (claude, opencode, ampcode, droid)
|
|
121
|
+
* @param {string} variant - Variant name (lite, standard, pro)
|
|
122
|
+
* @param {Object} availableContent - Object with arrays of available agents, skills, resources, hooks
|
|
123
|
+
* @returns {Promise<Object>} Object with filtered arrays of selected content
|
|
124
|
+
* @throws {Error} If variant not found or specified items don't exist
|
|
125
|
+
*/
|
|
126
|
+
async selectVariantContent(toolId, variant, availableContent) {
|
|
127
|
+
// Load variant configuration
|
|
128
|
+
const config = await this.loadVariantConfig(toolId);
|
|
129
|
+
|
|
130
|
+
if (!config[variant]) {
|
|
131
|
+
throw new Error(`Variant '${variant}' not found for tool ${toolId}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const variantConfig = config[variant];
|
|
135
|
+
const selected = {
|
|
136
|
+
agents: [],
|
|
137
|
+
skills: [],
|
|
138
|
+
commands: [],
|
|
139
|
+
resources: [],
|
|
140
|
+
hooks: []
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Helper function to process each content category
|
|
144
|
+
const selectItems = (category, variantSelection, availableItems, skipMissing = false) => {
|
|
145
|
+
// Handle wildcard: "*" expands to all available items
|
|
146
|
+
if (variantSelection === '*') {
|
|
147
|
+
return [...availableItems];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Handle array selection (specific items or empty array)
|
|
151
|
+
if (Array.isArray(variantSelection)) {
|
|
152
|
+
// Empty array [] means no items selected
|
|
153
|
+
if (variantSelection.length === 0) {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Specific selection: validate all items exist (unless skipMissing)
|
|
158
|
+
const selectedItems = [];
|
|
159
|
+
for (const item of variantSelection) {
|
|
160
|
+
if (!availableItems.includes(item)) {
|
|
161
|
+
if (skipMissing) {
|
|
162
|
+
// Skip missing items silently (e.g., removed commands like subagent-spawning)
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Item '${item}' specified in ${variant} variant ${category} not found in available content. ` +
|
|
167
|
+
`Available ${category}: ${availableItems.join(', ')}`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
selectedItems.push(item);
|
|
171
|
+
}
|
|
172
|
+
return selectedItems;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// If neither wildcard nor array, treat as empty selection
|
|
176
|
+
return [];
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Process each content category
|
|
180
|
+
selected.agents = selectItems('agents', variantConfig.agents, availableContent.agents || []);
|
|
181
|
+
selected.skills = selectItems('skills', variantConfig.skills, availableContent.skills || []);
|
|
182
|
+
selected.commands = selectItems('commands', variantConfig.commands, availableContent.commands || [], true);
|
|
183
|
+
selected.resources = selectItems('resources', variantConfig.resources, availableContent.resources || []);
|
|
184
|
+
selected.hooks = selectItems('hooks', variantConfig.hooks, availableContent.hooks || []);
|
|
185
|
+
|
|
186
|
+
return selected;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get available variants for a tool
|
|
191
|
+
*/
|
|
192
|
+
getAvailableVariants(toolId) {
|
|
193
|
+
const toolDir = path.join(this.packagesDir, toolId);
|
|
194
|
+
|
|
195
|
+
if (!fs.existsSync(toolDir)) {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return this.variants.filter(variant => {
|
|
200
|
+
const variantDir = path.join(toolDir, variant);
|
|
201
|
+
return fs.existsSync(variantDir);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get package contents for a specific tool and variant
|
|
207
|
+
* Uses variant filtering to return only selected content based on variants.json
|
|
208
|
+
*/
|
|
209
|
+
async getPackageContents(toolId, variant) {
|
|
210
|
+
// Use base package directory (not variant-specific)
|
|
211
|
+
const packageDir = path.join(this.packagesDir, toolId);
|
|
212
|
+
|
|
213
|
+
if (!fs.existsSync(packageDir)) {
|
|
214
|
+
throw new Error(`Package not found: ${toolId}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Get all available content from the package (includes dynamic directory names)
|
|
218
|
+
const availableContent = await this.getAvailableContent(packageDir);
|
|
219
|
+
|
|
220
|
+
// Use selectVariantContent to filter based on variant configuration
|
|
221
|
+
const selectedContent = await this.selectVariantContent(toolId, variant, availableContent);
|
|
222
|
+
|
|
223
|
+
// Get file paths for selected content
|
|
224
|
+
const contents = {
|
|
225
|
+
agents: [],
|
|
226
|
+
skills: [],
|
|
227
|
+
commands: [],
|
|
228
|
+
resources: [],
|
|
229
|
+
hooks: [],
|
|
230
|
+
totalFiles: 0,
|
|
231
|
+
totalSize: 0
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Build file paths for selected agents (use dynamic directory name)
|
|
235
|
+
const agentsDirName = availableContent.agentsDir || 'agents';
|
|
236
|
+
const agentsDir = path.join(packageDir, agentsDirName);
|
|
237
|
+
if (fs.existsSync(agentsDir)) {
|
|
238
|
+
for (const agent of selectedContent.agents) {
|
|
239
|
+
const agentPath = path.join(agentsDir, `${agent}.md`);
|
|
240
|
+
if (fs.existsSync(agentPath)) {
|
|
241
|
+
contents.agents.push(agentPath);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Build directory paths for selected skills (skills are directories)
|
|
247
|
+
const skillsDir = path.join(packageDir, 'skills');
|
|
248
|
+
if (fs.existsSync(skillsDir)) {
|
|
249
|
+
for (const skill of selectedContent.skills) {
|
|
250
|
+
const skillPath = path.join(skillsDir, skill);
|
|
251
|
+
if (fs.existsSync(skillPath)) {
|
|
252
|
+
// Store the skill directory path (not individual files within it)
|
|
253
|
+
// The installation engine will handle copying the entire directory
|
|
254
|
+
contents.skills.push(skillPath);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Build file paths for selected commands (use dynamic directory name)
|
|
260
|
+
const commandsDirName = availableContent.commandsDir || 'commands';
|
|
261
|
+
const commandsDir = path.join(packageDir, commandsDirName);
|
|
262
|
+
if (fs.existsSync(commandsDir) && selectedContent.commands) {
|
|
263
|
+
for (const command of selectedContent.commands) {
|
|
264
|
+
const commandPath = path.join(commandsDir, `${command}.md`);
|
|
265
|
+
if (fs.existsSync(commandPath)) {
|
|
266
|
+
contents.commands.push(commandPath);
|
|
267
|
+
}
|
|
268
|
+
// Also check for command subdirectories (like docs-builder/templates.md)
|
|
269
|
+
const commandSubDir = path.join(commandsDir, command);
|
|
270
|
+
if (fs.existsSync(commandSubDir) && fs.statSync(commandSubDir).isDirectory()) {
|
|
271
|
+
contents.commands.push(commandSubDir);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Build file paths for selected resources
|
|
277
|
+
const resourcesDir = path.join(packageDir, 'resources');
|
|
278
|
+
if (fs.existsSync(resourcesDir)) {
|
|
279
|
+
for (const resource of selectedContent.resources) {
|
|
280
|
+
const resourcePath = path.join(resourcesDir, resource);
|
|
281
|
+
if (fs.existsSync(resourcePath)) {
|
|
282
|
+
contents.resources.push(resourcePath);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Build file paths for selected hooks
|
|
288
|
+
const hooksDir = path.join(packageDir, 'hooks');
|
|
289
|
+
if (fs.existsSync(hooksDir)) {
|
|
290
|
+
for (const hook of selectedContent.hooks) {
|
|
291
|
+
const hookPath = path.join(hooksDir, hook);
|
|
292
|
+
if (fs.existsSync(hookPath)) {
|
|
293
|
+
contents.hooks.push(hookPath);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Calculate total files
|
|
299
|
+
contents.totalFiles = contents.agents.length +
|
|
300
|
+
contents.skills.length +
|
|
301
|
+
contents.commands.length +
|
|
302
|
+
contents.resources.length +
|
|
303
|
+
contents.hooks.length;
|
|
304
|
+
|
|
305
|
+
return contents;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get all available content from a package directory
|
|
310
|
+
* Helper method for getPackageContents
|
|
311
|
+
*/
|
|
312
|
+
async getAvailableContent(packageDir) {
|
|
313
|
+
const getItemsInDir = async (dir, isAgentDir = false) => {
|
|
314
|
+
if (!fs.existsSync(dir)) return { items: [], dirName: null };
|
|
315
|
+
const items = await fs.promises.readdir(dir);
|
|
316
|
+
const result = [];
|
|
317
|
+
|
|
318
|
+
for (const item of items) {
|
|
319
|
+
const itemPath = path.join(dir, item);
|
|
320
|
+
const stat = await fs.promises.stat(itemPath);
|
|
321
|
+
|
|
322
|
+
if (stat.isFile()) {
|
|
323
|
+
// For agents, strip the .md extension
|
|
324
|
+
if (isAgentDir) {
|
|
325
|
+
result.push(item.replace('.md', ''));
|
|
326
|
+
} else if (dir.includes('resources') || dir.includes('hooks')) {
|
|
327
|
+
// For resources and hooks, keep the full filename
|
|
328
|
+
result.push(item);
|
|
329
|
+
}
|
|
330
|
+
// For skills directory, ignore files (only directories are skills)
|
|
331
|
+
} else if (stat.isDirectory()) {
|
|
332
|
+
// For skills (which are directories), include the directory name
|
|
333
|
+
result.push(item);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return { items: result, dirName: path.basename(dir) };
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// Check for both plural and singular directory names
|
|
341
|
+
const agentsDir = fs.existsSync(path.join(packageDir, 'agents')) ? 'agents' :
|
|
342
|
+
fs.existsSync(path.join(packageDir, 'agent')) ? 'agent' :
|
|
343
|
+
fs.existsSync(path.join(packageDir, 'droids')) ? 'droids' : 'agents';
|
|
344
|
+
const commandsDir = fs.existsSync(path.join(packageDir, 'commands')) ? 'commands' :
|
|
345
|
+
fs.existsSync(path.join(packageDir, 'command')) ? 'command' : 'commands';
|
|
346
|
+
|
|
347
|
+
const agentsResult = await getItemsInDir(path.join(packageDir, agentsDir), true);
|
|
348
|
+
const skillsResult = await getItemsInDir(path.join(packageDir, 'skills'));
|
|
349
|
+
const commandsResult = await getItemsInDir(path.join(packageDir, commandsDir), true);
|
|
350
|
+
const resourcesResult = await getItemsInDir(path.join(packageDir, 'resources'));
|
|
351
|
+
const hooksResult = await getItemsInDir(path.join(packageDir, 'hooks'));
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
agents: agentsResult.items,
|
|
355
|
+
agentsDir: agentsDir,
|
|
356
|
+
skills: skillsResult.items,
|
|
357
|
+
commands: commandsResult.items,
|
|
358
|
+
commandsDir: commandsDir,
|
|
359
|
+
resources: resourcesResult.items,
|
|
360
|
+
hooks: hooksResult.items
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Count files in a directory recursively
|
|
366
|
+
*/
|
|
367
|
+
async countFiles(dir) {
|
|
368
|
+
const files = [];
|
|
369
|
+
|
|
370
|
+
async function traverse(currentDir) {
|
|
371
|
+
const items = await fs.promises.readdir(currentDir);
|
|
372
|
+
|
|
373
|
+
for (const item of items) {
|
|
374
|
+
const itemPath = path.join(currentDir, item);
|
|
375
|
+
const stat = await fs.promises.stat(itemPath);
|
|
376
|
+
|
|
377
|
+
if (stat.isDirectory()) {
|
|
378
|
+
await traverse(itemPath);
|
|
379
|
+
} else {
|
|
380
|
+
files.push(itemPath);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
await traverse(dir);
|
|
386
|
+
return files;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Get package size information based on variant-filtered content
|
|
391
|
+
* @param {string} toolId - Tool identifier (claude, opencode, ampcode, droid)
|
|
392
|
+
* @param {string} variant - Variant name (lite, standard, pro)
|
|
393
|
+
* @returns {Promise<Object>} Object with size (bytes) and formattedSize
|
|
394
|
+
*/
|
|
395
|
+
async getPackageSize(toolId, variant) {
|
|
396
|
+
// Get variant-filtered package contents
|
|
397
|
+
const contents = await this.getPackageContents(toolId, variant);
|
|
398
|
+
|
|
399
|
+
let totalSize = 0;
|
|
400
|
+
|
|
401
|
+
// Helper function to calculate size of a file or directory recursively
|
|
402
|
+
const calculatePathSize = async (itemPath) => {
|
|
403
|
+
try {
|
|
404
|
+
const stat = await fs.promises.stat(itemPath);
|
|
405
|
+
|
|
406
|
+
if (stat.isFile()) {
|
|
407
|
+
return stat.size;
|
|
408
|
+
} else if (stat.isDirectory()) {
|
|
409
|
+
// Recursively calculate directory size
|
|
410
|
+
let dirSize = 0;
|
|
411
|
+
const items = await fs.promises.readdir(itemPath);
|
|
412
|
+
|
|
413
|
+
for (const item of items) {
|
|
414
|
+
const subPath = path.join(itemPath, item);
|
|
415
|
+
dirSize += await calculatePathSize(subPath);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return dirSize;
|
|
419
|
+
}
|
|
420
|
+
} catch (error) {
|
|
421
|
+
// Skip files that don't exist or can't be accessed
|
|
422
|
+
return 0;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return 0;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
// Calculate size for all selected agents (files)
|
|
429
|
+
for (const agentPath of contents.agents) {
|
|
430
|
+
totalSize += await calculatePathSize(agentPath);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Calculate size for all selected skills (directories)
|
|
434
|
+
for (const skillPath of contents.skills) {
|
|
435
|
+
totalSize += await calculatePathSize(skillPath);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Calculate size for all selected resources (files)
|
|
439
|
+
for (const resourcePath of contents.resources) {
|
|
440
|
+
totalSize += await calculatePathSize(resourcePath);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Calculate size for all selected hooks (files)
|
|
444
|
+
for (const hookPath of contents.hooks) {
|
|
445
|
+
totalSize += await calculatePathSize(hookPath);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
size: totalSize,
|
|
450
|
+
formattedSize: this.formatBytes(totalSize)
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Format bytes to human readable format
|
|
456
|
+
*/
|
|
457
|
+
formatBytes(bytes) {
|
|
458
|
+
if (bytes === 0) return '0 Bytes';
|
|
459
|
+
|
|
460
|
+
const k = 1024;
|
|
461
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
462
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
463
|
+
|
|
464
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Validate package integrity with variant support
|
|
469
|
+
* Checks variants.json exists and is valid, and validates that variant-selected content exists
|
|
470
|
+
* @param {string} toolId - Tool identifier (claude, opencode, ampcode, droid)
|
|
471
|
+
* @param {string} variant - Variant name (lite, standard, pro)
|
|
472
|
+
* @returns {Promise<Object>} Validation result with detailed information
|
|
473
|
+
*/
|
|
474
|
+
async validatePackage(toolId, variant) {
|
|
475
|
+
const packageDir = path.join(this.packagesDir, toolId);
|
|
476
|
+
const issues = [];
|
|
477
|
+
let checkedFiles = 0;
|
|
478
|
+
let missingFiles = 0;
|
|
479
|
+
|
|
480
|
+
// Check 1: Package directory exists
|
|
481
|
+
if (!fs.existsSync(packageDir)) {
|
|
482
|
+
return {
|
|
483
|
+
valid: false,
|
|
484
|
+
error: 'Package directory not found',
|
|
485
|
+
issues: [`Package directory not found: ${packageDir}`],
|
|
486
|
+
checkedFiles: 0,
|
|
487
|
+
missingFiles: 0
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Check 2: variants.json exists
|
|
492
|
+
const variantsFilePath = path.join(packageDir, 'variants.json');
|
|
493
|
+
if (!fs.existsSync(variantsFilePath)) {
|
|
494
|
+
return {
|
|
495
|
+
valid: false,
|
|
496
|
+
error: 'variants.json file not found',
|
|
497
|
+
issues: [`variants.json file not found in package: ${toolId}`],
|
|
498
|
+
checkedFiles: 0,
|
|
499
|
+
missingFiles: 0
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Check 3: variants.json is valid JSON
|
|
504
|
+
let config;
|
|
505
|
+
try {
|
|
506
|
+
config = await this.loadVariantConfig(toolId);
|
|
507
|
+
} catch (error) {
|
|
508
|
+
return {
|
|
509
|
+
valid: false,
|
|
510
|
+
error: error.message,
|
|
511
|
+
issues: [`Invalid variants.json: ${error.message}`],
|
|
512
|
+
checkedFiles: 0,
|
|
513
|
+
missingFiles: 0
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Check 4: Required variant is present (pro)
|
|
518
|
+
const requiredVariants = ['pro'];
|
|
519
|
+
for (const reqVariant of requiredVariants) {
|
|
520
|
+
if (!config[reqVariant]) {
|
|
521
|
+
return {
|
|
522
|
+
valid: false,
|
|
523
|
+
error: `Required variant '${reqVariant}' not found`,
|
|
524
|
+
issues: [`Required variant '${reqVariant}' not found in variants.json`],
|
|
525
|
+
checkedFiles: 0,
|
|
526
|
+
missingFiles: 0
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Check 5: Each variant has required fields
|
|
532
|
+
// Note: 'skills', 'commands', 'resources', 'hooks' are optional
|
|
533
|
+
const requiredFields = ['name', 'description', 'agents'];
|
|
534
|
+
for (const reqVariant of requiredVariants) {
|
|
535
|
+
const variantConfig = config[reqVariant];
|
|
536
|
+
for (const field of requiredFields) {
|
|
537
|
+
if (variantConfig[field] === undefined) {
|
|
538
|
+
return {
|
|
539
|
+
valid: false,
|
|
540
|
+
error: `Required field '${field}' missing in '${reqVariant}' variant`,
|
|
541
|
+
issues: [`Required field '${field}' missing in '${reqVariant}' variant`],
|
|
542
|
+
checkedFiles: 0,
|
|
543
|
+
missingFiles: 0
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Check 6: Validate that variant-selected content actually exists
|
|
550
|
+
try {
|
|
551
|
+
// Get available content
|
|
552
|
+
const availableContent = await this.getAvailableContent(packageDir);
|
|
553
|
+
|
|
554
|
+
// Get selected content for the specified variant
|
|
555
|
+
const selectedContent = await this.selectVariantContent(toolId, variant, availableContent);
|
|
556
|
+
|
|
557
|
+
// Helper function to check if file/directory exists
|
|
558
|
+
const checkContentExists = async (category, items, dirName, addExtension = false) => {
|
|
559
|
+
const categoryDir = path.join(packageDir, dirName);
|
|
560
|
+
|
|
561
|
+
// If directory doesn't exist but items are expected, that's an issue
|
|
562
|
+
if (items.length > 0 && !fs.existsSync(categoryDir)) {
|
|
563
|
+
issues.push(`Directory '${dirName}' not found but ${category} are selected in ${variant} variant`);
|
|
564
|
+
missingFiles += items.length;
|
|
565
|
+
checkedFiles += items.length;
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Check each item
|
|
570
|
+
for (const item of items) {
|
|
571
|
+
checkedFiles++;
|
|
572
|
+
let itemPath;
|
|
573
|
+
|
|
574
|
+
if (addExtension) {
|
|
575
|
+
// For agents, add .md extension
|
|
576
|
+
itemPath = path.join(categoryDir, `${item}.md`);
|
|
577
|
+
} else if (dirName === 'skills') {
|
|
578
|
+
// For skills, check directory exists
|
|
579
|
+
itemPath = path.join(categoryDir, item);
|
|
580
|
+
} else {
|
|
581
|
+
// For resources and hooks, use filename as-is
|
|
582
|
+
itemPath = path.join(categoryDir, item);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (!fs.existsSync(itemPath)) {
|
|
586
|
+
const displayPath = addExtension ? `${item}.md` : item;
|
|
587
|
+
issues.push(`${category.slice(0, -1)} '${displayPath}' not found in ${dirName}/ (required by ${variant} variant)`);
|
|
588
|
+
missingFiles++;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// Validate agents (use dynamic directory name)
|
|
594
|
+
const agentsDirName = availableContent.agentsDir || 'agents';
|
|
595
|
+
await checkContentExists('agents', selectedContent.agents, agentsDirName, true);
|
|
596
|
+
|
|
597
|
+
// Validate skills (directories)
|
|
598
|
+
await checkContentExists('skills', selectedContent.skills || [], 'skills', false);
|
|
599
|
+
|
|
600
|
+
// Validate commands (use dynamic directory name)
|
|
601
|
+
const commandsDirName = availableContent.commandsDir || 'commands';
|
|
602
|
+
await checkContentExists('commands', selectedContent.commands || [], commandsDirName, true);
|
|
603
|
+
|
|
604
|
+
} catch (error) {
|
|
605
|
+
return {
|
|
606
|
+
valid: false,
|
|
607
|
+
error: error.message,
|
|
608
|
+
issues: [`Content validation failed: ${error.message}`],
|
|
609
|
+
checkedFiles,
|
|
610
|
+
missingFiles
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Final result
|
|
615
|
+
const valid = issues.length === 0;
|
|
616
|
+
|
|
617
|
+
return {
|
|
618
|
+
valid,
|
|
619
|
+
issues,
|
|
620
|
+
checkedFiles,
|
|
621
|
+
missingFiles,
|
|
622
|
+
...(valid ? {} : { error: `Package validation failed: ${issues.length} issue(s) found` })
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Get tool-specific manifest template
|
|
628
|
+
*/
|
|
629
|
+
getManifestTemplate(toolId) {
|
|
630
|
+
const templatePath = path.join(__dirname, '..', 'tools', toolId, 'manifest-template.json');
|
|
631
|
+
|
|
632
|
+
if (!fs.existsSync(templatePath)) {
|
|
633
|
+
throw new Error(`Manifest template not found for tool: ${toolId}`);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return JSON.parse(fs.readFileSync(templatePath, 'utf8'));
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
module.exports = PackageManager;
|