@givolt/i3-composer-agent 1.0.1 → 2.1.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/plugins/marketplace.json +1 -1
- package/.claude/commands/{build.md → i3-build.md} +10 -10
- package/.claude/commands/{code-simplify.md → i3-code-simplify.md} +2 -2
- package/.claude/commands/{constraints.md → i3-constraints.md} +4 -4
- package/.claude/commands/{plan.md → i3-plan.md} +1 -1
- package/.claude/commands/{review.md → i3-review.md} +3 -3
- package/.claude/commands/{ship.md → i3-ship.md} +4 -4
- package/.claude/commands/{spec.md → i3-spec.md} +1 -1
- package/.claude/commands/{test.md → i3-test.md} +2 -2
- package/.claude/commands/{webperf.md → i3-webperf.md} +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.gemini/commands/{build.toml → i3-build.toml} +10 -10
- package/.gemini/commands/{code-simplify.toml → i3-code-simplify.toml} +2 -2
- package/.gemini/commands/{constraints.toml → i3-constraints.toml} +4 -4
- package/.gemini/commands/{planning.toml → i3-planning.toml} +1 -1
- package/{commands/review.toml → .gemini/commands/i3-review.toml} +3 -3
- package/.gemini/commands/{ship.toml → i3-ship.toml} +4 -4
- package/.gemini/commands/{spec.toml → i3-spec.toml} +1 -1
- package/{commands/test.toml → .gemini/commands/i3-test.toml} +2 -2
- package/.gemini/commands/{webperf.toml → i3-webperf.toml} +1 -1
- package/README.md +72 -69
- package/agents/code-reviewer.md +2 -2
- package/agents/security-auditor.md +1 -1
- package/agents/test-engineer.md +1 -1
- package/agents/web-performance-auditor.md +3 -3
- package/bin/agent-targets.json +11 -0
- package/bin/asset-fingerprint.cjs +38 -0
- package/bin/cleanup-backups.cjs +16 -0
- package/bin/i3-composer-agent.cjs +29 -14
- package/bin/install.cjs +81 -19
- package/bin/native-content.cjs +29 -0
- package/bin/wizard.cjs +2 -1
- package/commands/{build.toml → i3-build.toml} +10 -10
- package/commands/{code-simplify.toml → i3-code-simplify.toml} +2 -2
- package/commands/{constraints.toml → i3-constraints.toml} +4 -4
- package/commands/{planning.toml → i3-planning.toml} +1 -1
- package/{.gemini/commands/review.toml → commands/i3-review.toml} +3 -3
- package/commands/{ship.toml → i3-ship.toml} +4 -4
- package/commands/{spec.toml → i3-spec.toml} +1 -1
- package/{.gemini/commands/test.toml → commands/i3-test.toml} +2 -2
- package/commands/{webperf.toml → i3-webperf.toml} +1 -1
- package/docs/adoption-guide.md +35 -35
- package/docs/agents.md +12 -12
- package/docs/antigravity-setup.md +14 -14
- package/docs/codebuddy-setup.md +27 -18
- package/docs/codex-setup.md +6 -6
- package/docs/commandcode-setup.md +5 -5
- package/docs/copilot-cli-setup.md +5 -5
- package/docs/copilot-setup.md +22 -22
- package/docs/cursor-setup.md +22 -22
- package/docs/gemini-cli-setup.md +26 -26
- package/docs/getting-started.md +36 -38
- package/docs/opencode-setup.md +31 -31
- package/docs/windsurf-setup.md +7 -7
- package/docs/workbuddy-setup.md +92 -0
- package/hooks/SDD-CACHE.md +3 -3
- package/hooks/SIMPLIFY-IGNORE.md +2 -2
- package/hooks/session-start-test.sh +1 -1
- package/hooks/session-start.sh +3 -3
- package/package.json +36 -20
- package/plugin.json +1 -1
- package/references/accessibility-checklist.md +1 -1
- package/references/definition-of-done.md +7 -7
- package/references/observability-checklist.md +3 -3
- package/references/orchestration-patterns.md +17 -17
- package/references/performance-checklist.md +2 -2
- package/references/security-checklist.md +1 -1
- package/references/testing-patterns.md +1 -1
- package/skills/{api-and-interface-design → i3-api-and-interface-design}/SKILL.md +2 -2
- package/skills/{browser-testing-with-devtools → i3-browser-testing-with-devtools}/SKILL.md +1 -1
- package/skills/{ci-cd-and-automation → i3-ci-cd-and-automation}/SKILL.md +2 -2
- package/skills/{code-review-and-quality → i3-code-review-and-quality}/SKILL.md +5 -5
- package/skills/{code-simplification → i3-code-simplification}/SKILL.md +1 -1
- package/skills/{constraint-driven-development → i3-constraint-driven-development}/SKILL.md +15 -15
- package/skills/{context-engineering → i3-context-engineering}/SKILL.md +1 -1
- package/skills/{debugging-and-error-recovery → i3-debugging-and-error-recovery}/SKILL.md +2 -2
- package/skills/{deprecation-and-migration → i3-deprecation-and-migration}/SKILL.md +2 -2
- package/skills/{documentation-and-adrs → i3-documentation-and-adrs}/SKILL.md +1 -1
- package/skills/{doubt-driven-development → i3-doubt-driven-development}/SKILL.md +8 -8
- package/skills/{frontend-ui-engineering → i3-frontend-ui-engineering}/SKILL.md +1 -1
- package/skills/{git-workflow-and-versioning → i3-git-workflow-and-versioning}/SKILL.md +4 -4
- package/skills/{idea-refine → i3-idea-refine}/SKILL.md +2 -2
- package/skills/{idea-refine → i3-idea-refine}/scripts/idea-refine.sh +1 -1
- package/skills/{incremental-implementation → i3-incremental-implementation}/SKILL.md +3 -3
- package/skills/{interview-me → i3-interview-me}/SKILL.md +12 -12
- package/skills/{observability-and-instrumentation → i3-observability-and-instrumentation}/SKILL.md +5 -5
- package/skills/{performance-optimization → i3-performance-optimization}/SKILL.md +1 -1
- package/skills/{performance-optimization → i3-performance-optimization}/references/optimization-patterns.md +1 -1
- package/skills/{planning-and-task-breakdown → i3-planning-and-task-breakdown}/SKILL.md +2 -2
- package/skills/{security-and-hardening → i3-security-and-hardening}/SKILL.md +3 -3
- package/skills/{security-and-hardening → i3-security-and-hardening}/references/hardening-patterns.md +1 -1
- package/skills/{shipping-and-launch → i3-shipping-and-launch}/SKILL.md +2 -2
- package/skills/{source-driven-development → i3-source-driven-development}/SKILL.md +2 -2
- package/skills/{spec-driven-development → i3-spec-driven-development}/SKILL.md +7 -7
- package/skills/{test-driven-development → i3-test-driven-development}/SKILL.md +2 -2
- package/skills/{using-agent-skills → i3-using-agent-skills}/SKILL.md +67 -67
- package/CHANGELOG.md +0 -34
- package/NOTICE.md +0 -14
- package/docs/advanced-per-agent-configuration.md +0 -70
- package/docs/comparison.md +0 -131
- package/docs/developer-onboarding.md +0 -116
- package/docs/skill-anatomy.md +0 -203
- package/evals/README.md +0 -101
- package/evals/cases/api-and-interface-design.json +0 -45
- package/evals/cases/browser-testing-with-devtools.json +0 -44
- package/evals/cases/ci-cd-and-automation.json +0 -45
- package/evals/cases/code-review-and-quality.json +0 -49
- package/evals/cases/code-simplification.json +0 -45
- package/evals/cases/constraint-driven-development.json +0 -79
- package/evals/cases/context-engineering.json +0 -44
- package/evals/cases/debugging-and-error-recovery.json +0 -58
- package/evals/cases/deprecation-and-migration.json +0 -52
- package/evals/cases/documentation-and-adrs.json +0 -44
- package/evals/cases/doubt-driven-development.json +0 -43
- package/evals/cases/frontend-ui-engineering.json +0 -52
- package/evals/cases/git-workflow-and-versioning.json +0 -47
- package/evals/cases/idea-refine.json +0 -43
- package/evals/cases/incremental-implementation.json +0 -57
- package/evals/cases/interview-me.json +0 -42
- package/evals/cases/observability-and-instrumentation.json +0 -44
- package/evals/cases/performance-optimization.json +0 -53
- package/evals/cases/planning-and-task-breakdown.json +0 -45
- package/evals/cases/security-and-hardening.json +0 -53
- package/evals/cases/shipping-and-launch.json +0 -56
- package/evals/cases/source-driven-development.json +0 -44
- package/evals/cases/spec-driven-development.json +0 -72
- package/evals/cases/test-driven-development.json +0 -73
- package/evals/cases/using-agent-skills.json +0 -44
- package/evals/fixtures/api-and-interface-design/service-brief.md +0 -19
- package/evals/fixtures/browser-testing-with-devtools/README.md +0 -5
- package/evals/fixtures/browser-testing-with-devtools/index.html +0 -24
- package/evals/fixtures/browser-testing-with-devtools/server.js +0 -15
- package/evals/fixtures/ci-cd-and-automation/package.json +0 -8
- package/evals/fixtures/ci-cd-and-automation/src/slug.js +0 -3
- package/evals/fixtures/ci-cd-and-automation/test/slug.test.js +0 -9
- package/evals/fixtures/code-review-and-quality/user-search.diff +0 -16
- package/evals/fixtures/code-simplification/config-parser.js +0 -46
- package/evals/fixtures/code-simplification/config-parser.test.js +0 -15
- package/evals/fixtures/context-engineering/context-audit.md +0 -15
- package/evals/fixtures/debugging-and-error-recovery/pagination.js +0 -8
- package/evals/fixtures/debugging-and-error-recovery/pagination.test.js +0 -9
- package/evals/fixtures/debugging-and-error-recovery/time-pressure.md +0 -6
- package/evals/fixtures/deprecation-and-migration/api-inventory.md +0 -9
- package/evals/fixtures/documentation-and-adrs/decision-context.md +0 -16
- package/evals/fixtures/doubt-driven-development/migration-plan.md +0 -19
- package/evals/fixtures/frontend-ui-engineering/Button.tsx +0 -7
- package/evals/fixtures/frontend-ui-engineering/design-system.md +0 -11
- package/evals/fixtures/git-workflow-and-versioning/.eval/working-tree.patch +0 -21
- package/evals/fixtures/git-workflow-and-versioning/app.js +0 -7
- package/evals/fixtures/git-workflow-and-versioning/app.test.js +0 -9
- package/evals/fixtures/incremental-implementation/reports.js +0 -7
- package/evals/fixtures/incremental-implementation/reports.test.js +0 -12
- package/evals/fixtures/incremental-implementation/tasks/plan.md +0 -8
- package/evals/fixtures/incremental-implementation-pressure/draft-export.js +0 -17
- package/evals/fixtures/incremental-implementation-pressure/scenario.md +0 -9
- package/evals/fixtures/observability-and-instrumentation/operations.md +0 -11
- package/evals/fixtures/observability-and-instrumentation/payment-retry.js +0 -14
- package/evals/fixtures/performance-optimization/benchmark.js +0 -15
- package/evals/fixtures/performance-optimization/products.js +0 -14
- package/evals/fixtures/planning-and-task-breakdown/notifications-spec.md +0 -18
- package/evals/fixtures/security-and-hardening/webhook.js +0 -11
- package/evals/fixtures/security-and-hardening/webhook.test.js +0 -13
- package/evals/fixtures/shipping-and-launch/authority-pressure.md +0 -6
- package/evals/fixtures/shipping-and-launch/launch-status.md +0 -11
- package/evals/fixtures/source-driven-development/framework-task.md +0 -10
- package/evals/fixtures/spec-driven-development/billing-brief.md +0 -16
- package/evals/fixtures/spec-driven-development-decomposition/portal-brief.md +0 -22
- package/evals/fixtures/test-driven-development/BUG.md +0 -10
- package/evals/fixtures/test-driven-development/README.md +0 -28
- package/evals/fixtures/test-driven-development/package.json +0 -9
- package/evals/fixtures/test-driven-development/src/split.js +0 -8
- package/evals/fixtures/test-driven-development/test/split.test.js +0 -13
- package/evals/fixtures/test-driven-development-ecosystem/README.md +0 -9
- package/evals/fixtures/test-driven-development-ecosystem/ledger.py +0 -15
- package/evals/fixtures/test-driven-development-ecosystem/test_ledger.py +0 -19
- package/evals/fixtures/using-agent-skills/incident.md +0 -6
- package/evals/plugin/code-review-fires/graders/off-by-one.md +0 -7
- package/evals/plugin/code-review-fires/graders/severity-labels.md +0 -4
- package/evals/plugin/code-review-fires/graders/skill-fired.md +0 -5
- package/evals/plugin/code-review-fires/prompt.md +0 -31
- package/evals/plugin/code-review-stays-quiet/graders/not-fired.md +0 -8
- package/evals/plugin/code-review-stays-quiet/graders/tdd-fired.md +0 -6
- package/evals/plugin/code-review-stays-quiet/prompt.md +0 -8
- package/evals/plugin/code-review-stays-quiet-on-commit-message/graders/not-fired.md +0 -8
- package/evals/plugin/code-review-stays-quiet-on-commit-message/prompt.md +0 -25
- package/evals/skill-impact.md +0 -6
- package/rebrand/INSTALLATION-SPEC.md +0 -46
- package/rebrand/MAINTENANCE.md +0 -113
- package/rebrand/NOTICE.md +0 -14
- package/rebrand/PR-WORKFLOW.md +0 -34
- package/rebrand/UNIFICATION-SPEC.md +0 -33
- package/rebrand/VALIDATION.md +0 -32
- package/rebrand/VERSIONING.md +0 -33
- package/rebrand/apply.cjs +0 -194
- package/rebrand/brand.json +0 -13
- package/rebrand/catalog.cjs +0 -56
- package/rebrand/fork-patches.json +0 -12
- package/rebrand/overlay/.github/workflows/test-plugin-install.yml +0 -177
- package/rebrand/overlay/CHANGELOG.md +0 -34
- package/rebrand/overlay/bin/agent-targets.json +0 -726
- package/rebrand/overlay/bin/i3-composer-agent.cjs +0 -190
- package/rebrand/overlay/bin/install.cjs +0 -164
- package/rebrand/overlay/bin/skills-registry.LICENSE +0 -21
- package/rebrand/overlay/bin/wizard.cjs +0 -66
- package/rebrand/overlay/scripts/validate-brand.cjs +0 -44
- package/rebrand/overlay/tests/bundle.test.cjs +0 -85
- package/rebrand/overlay/tests/installer.test.cjs +0 -193
- package/rebrand/package.template.json +0 -82
- package/rebrand/rebrand.sh +0 -4
- package/rebrand/record-upstream.cjs +0 -37
- package/rebrand/templates/README.md +0 -124
- package/rebrand/templates/codebuddy-setup.md +0 -88
- package/rebrand/templates/codex-setup.md +0 -45
- package/rebrand/tests/pipeline.test.cjs +0 -185
- package/rebrand/upstream.json +0 -171
- package/scripts/floor-guard-reference-test.js +0 -291
- package/scripts/lib/skill-lint-test.js +0 -650
- package/scripts/lib/skill-lint.js +0 -584
- package/scripts/run-evals-test.js +0 -623
- package/scripts/run-evals.js +0 -638
- package/scripts/validate-artifact-paths-test.js +0 -179
- package/scripts/validate-artifact-paths.js +0 -128
- package/scripts/validate-brand.cjs +0 -44
- package/scripts/validate-commands-test.js +0 -216
- package/scripts/validate-commands.js +0 -222
- package/scripts/validate-reference-links-test.js +0 -495
- package/scripts/validate-reference-links.js +0 -187
- package/scripts/validate-skills.js +0 -69
- package/scripts/validate-versions-test.js +0 -32
- package/scripts/validate-versions.js +0 -35
- package/tests/bundle.test.cjs +0 -85
- package/tests/installer.test.cjs +0 -193
- /package/skills/{constraint-driven-development → i3-constraint-driven-development}/references/floor-guard.md +0 -0
- /package/skills/{idea-refine → i3-idea-refine}/examples.md +0 -0
- /package/skills/{idea-refine → i3-idea-refine}/frameworks.md +0 -0
- /package/skills/{idea-refine → i3-idea-refine}/refinement-criteria.md +0 -0
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('node:fs');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
const crypto = require('node:crypto');
|
|
7
|
-
const root = path.resolve(__dirname, '..');
|
|
8
|
-
const pkg = require('../package.json');
|
|
9
|
-
const name = 'i3-composer-agent';
|
|
10
|
-
const args = process.argv.slice(2);
|
|
11
|
-
const identity = `i3 Composer Agent v${pkg.version}\nBased on agent-skills v${pkg.i3ComposerAgent.upstreamVersion}`;
|
|
12
|
-
const help = `${identity}
|
|
13
|
-
${pkg.name}
|
|
14
|
-
|
|
15
|
-
Install engineering skills with a multi-tool installation wizard.
|
|
16
|
-
|
|
17
|
-
Usage:
|
|
18
|
-
npx ${pkg.name}
|
|
19
|
-
npx ${pkg.name} --codebuddy
|
|
20
|
-
npx ${pkg.name} --agents codebuddy,codex --scope project --yes
|
|
21
|
-
npx ${pkg.name} --prepare --claude --dir <directory>
|
|
22
|
-
npx ${pkg.name} --extract --dir <directory>
|
|
23
|
-
|
|
24
|
-
Options:
|
|
25
|
-
--agents <ids> Comma-separated tool IDs; multiple tools are supported
|
|
26
|
-
--codebuddy Preselect CodeBuddy desktop IDE / CodeBuddy Code
|
|
27
|
-
--codex Preselect Codex
|
|
28
|
-
--claude Preselect Claude Code
|
|
29
|
-
--scope <scope> project or global (or use --global / --local)
|
|
30
|
-
--project-dir <dir> Project installation root (default: current directory)
|
|
31
|
-
--yes Confirm explicit noninteractive installation
|
|
32
|
-
--list-agents List all supported tool directories
|
|
33
|
-
--prepare Prepare a complete plugin for native plugin registration
|
|
34
|
-
--dir <directory> Plugin output directory; implies --prepare
|
|
35
|
-
--dry-run Show the installation or bundle plan without writing files
|
|
36
|
-
--force Replace existing assets, preserving backups
|
|
37
|
-
--version Print the package version
|
|
38
|
-
--help Show this help
|
|
39
|
-
|
|
40
|
-
The terminal wizard selects tools and scope, then installs native skill files.
|
|
41
|
-
CodeBuddy and Claude Code also receive short lifecycle commands and personas.
|
|
42
|
-
Other tools receive skills and references; command availability is host-specific.
|
|
43
|
-
Use --prepare for complete plugins, including hooks and host registration.
|
|
44
|
-
`;
|
|
45
|
-
|
|
46
|
-
function quote(value) { return "'" + value.replaceAll("'", "'\\''") + "'"; }
|
|
47
|
-
function realDestination(target) {
|
|
48
|
-
let existing = target;
|
|
49
|
-
const suffix = [];
|
|
50
|
-
while (!fs.existsSync(existing)) {
|
|
51
|
-
suffix.unshift(path.basename(existing));
|
|
52
|
-
const parent = path.dirname(existing);
|
|
53
|
-
if (parent === existing) throw new Error(`Cannot resolve destination: ${target}`);
|
|
54
|
-
existing = parent;
|
|
55
|
-
}
|
|
56
|
-
return path.join(fs.realpathSync(existing), ...suffix);
|
|
57
|
-
}
|
|
58
|
-
function prepare() {
|
|
59
|
-
let host;
|
|
60
|
-
let directory = path.join(process.cwd(), name);
|
|
61
|
-
let dryRun = false;
|
|
62
|
-
let force = false;
|
|
63
|
-
for (let i = 0; i < args.length; i++) {
|
|
64
|
-
const arg = args[i];
|
|
65
|
-
if (arg === '--prepare') continue;
|
|
66
|
-
if (['--codebuddy', '--codex', '--claude', '--extract'].includes(arg)) {
|
|
67
|
-
if (host) throw new Error('Choose exactly one host flag or --extract.');
|
|
68
|
-
host = arg.slice(2);
|
|
69
|
-
} else if (arg === '--dir') {
|
|
70
|
-
if (!args[i + 1] || args[i + 1].startsWith('--')) throw new Error('--dir requires a directory.');
|
|
71
|
-
directory = path.resolve(args[++i]);
|
|
72
|
-
} else if (arg === '--dry-run') dryRun = true;
|
|
73
|
-
else if (arg === '--force') force = true;
|
|
74
|
-
else throw new Error(`Unknown option: ${arg}`);
|
|
75
|
-
}
|
|
76
|
-
if (!host) throw new Error('Choose --codebuddy, --codex, --claude, or --extract.');
|
|
77
|
-
directory = realDestination(directory);
|
|
78
|
-
const sourceRoot = fs.realpathSync(root);
|
|
79
|
-
if (directory === sourceRoot || directory.startsWith(sourceRoot + path.sep) || sourceRoot.startsWith(directory + path.sep)) {
|
|
80
|
-
throw new Error('The output directory must be outside this package directory.');
|
|
81
|
-
}
|
|
82
|
-
let exists = false;
|
|
83
|
-
try { fs.lstatSync(directory); exists = true; } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
84
|
-
if (exists) {
|
|
85
|
-
if (!force) throw new Error(`Output already exists: ${directory}. Choose a new directory or use --force for an i3 bundle.`);
|
|
86
|
-
const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'plugin.json'), 'utf8'));
|
|
87
|
-
if (manifest.name !== name) throw new Error('Refusing to replace a directory belonging to another plugin.');
|
|
88
|
-
}
|
|
89
|
-
if (dryRun) {
|
|
90
|
-
console.log(JSON.stringify({ name, version: pkg.version, upstreamVersion: pkg.i3ComposerAgent.upstreamVersion, upstreamCommit: pkg.i3ComposerAgent.upstreamCommit, directory, host, files: pkg.i3ComposerAgent.pluginFiles, replacesExisting: exists }, null, 2));
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
fs.mkdirSync(path.dirname(directory), { recursive: true });
|
|
94
|
-
const staging = fs.mkdtempSync(path.join(path.dirname(directory), `.${name}-`));
|
|
95
|
-
let backup;
|
|
96
|
-
try {
|
|
97
|
-
for (const relative of pkg.i3ComposerAgent.pluginFiles) {
|
|
98
|
-
const source = path.join(root, relative);
|
|
99
|
-
const destination = path.join(staging, relative);
|
|
100
|
-
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
101
|
-
fs.cpSync(source, destination, {
|
|
102
|
-
recursive: true,
|
|
103
|
-
filter: file => {
|
|
104
|
-
if (path.basename(file) === '.DS_Store') return false;
|
|
105
|
-
if (fs.lstatSync(file).isSymbolicLink()) throw new Error(`Bundle contains a symlink: ${file}`);
|
|
106
|
-
return !/(?:^|[\\/])(?:results|node_modules|\.git)(?:[\\/]|$)/.test(path.relative(root, file));
|
|
107
|
-
},
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
if (exists) {
|
|
111
|
-
backup = `${directory}.backup-${crypto.randomUUID()}`;
|
|
112
|
-
fs.renameSync(directory, backup);
|
|
113
|
-
}
|
|
114
|
-
try { fs.renameSync(staging, directory); }
|
|
115
|
-
catch (error) { if (backup) fs.renameSync(backup, directory); throw error; }
|
|
116
|
-
} finally { if (fs.existsSync(staging)) fs.rmSync(staging, { recursive: true, force: true }); }
|
|
117
|
-
console.log(identity);
|
|
118
|
-
console.log(`Prepared ${name} at ${directory}`);
|
|
119
|
-
if (backup) console.log(`Previous bundle preserved at ${backup}`);
|
|
120
|
-
if (host === 'extract') return;
|
|
121
|
-
console.log('\nRegister and enable the plugin with these native commands:');
|
|
122
|
-
const selector = `${name}@${name}`;
|
|
123
|
-
if (host === 'codex') {
|
|
124
|
-
console.log(`codex plugin marketplace add ${quote(directory)}`);
|
|
125
|
-
console.log(`codex plugin add ${selector}`);
|
|
126
|
-
console.log('Start a new Codex session and invoke the existing skill names. Lifecycle slash-command adaptation is separate.');
|
|
127
|
-
} else {
|
|
128
|
-
console.log(`/plugin marketplace add ${quote(directory)}`);
|
|
129
|
-
console.log(`/plugin install ${selector}`);
|
|
130
|
-
console.log(`Use /${name}:spec, /${name}:plan, /${name}:build, and /${name}:ship in ${host === 'claude' ? 'Claude Code' : 'CodeBuddy'}.`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
async function main() {
|
|
134
|
-
if (args.includes('--help') || (!args.length && !process.stdin.isTTY)) { console.log(help); return; }
|
|
135
|
-
if (args.length === 1 && args[0] === '--version') { console.log(pkg.version); return; }
|
|
136
|
-
if (args.some(arg => ['--extract', '--prepare', '--dir'].includes(arg))) { prepare(); return; }
|
|
137
|
-
const installer = require('./install.cjs');
|
|
138
|
-
if (args.length === 1 && args[0] === '--list-agents') {
|
|
139
|
-
for (const agent of installer.registry.agents) console.log(`${agent.id.padEnd(20)} ${agent.label}${agent.globalSkillsDir ? '' : ' (project only)'}`);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
const options = { agents: [], projectDir: process.cwd() };
|
|
143
|
-
for (let i = 0; i < args.length; i++) {
|
|
144
|
-
const arg = args[i];
|
|
145
|
-
if (['--codebuddy', '--codex', '--claude'].includes(arg)) options.agents.push(arg.slice(2));
|
|
146
|
-
else if (['--agents', '--scope', '--project-dir'].includes(arg)) {
|
|
147
|
-
if (!args[i + 1] || args[i + 1].startsWith('--')) throw new Error(`${arg} requires a value.`);
|
|
148
|
-
const value = args[++i];
|
|
149
|
-
if (arg === '--agents') options.agents.push(value);
|
|
150
|
-
else if (arg === '--scope') { if (options.scope && options.scope !== value) throw new Error('Choose one installation scope.'); options.scope = value; }
|
|
151
|
-
else options.projectDir = path.resolve(value);
|
|
152
|
-
} else if (arg === '--global' || arg === '--local') {
|
|
153
|
-
const scope = arg === '--global' ? 'global' : 'project';
|
|
154
|
-
if (options.scope && options.scope !== scope) throw new Error('Choose one installation scope.');
|
|
155
|
-
options.scope = scope;
|
|
156
|
-
} else if (arg === '--yes') options.yes = true;
|
|
157
|
-
else if (arg === '--force') options.force = true;
|
|
158
|
-
else if (arg === '--dry-run') options.dryRun = true;
|
|
159
|
-
else throw new Error(`Unknown option: ${arg}`);
|
|
160
|
-
}
|
|
161
|
-
options.agents = installer.selectAgents(options.agents);
|
|
162
|
-
if (process.stdin.isTTY && process.stdout.isTTY && !options.yes && !options.dryRun) {
|
|
163
|
-
const selection = await require('./wizard.cjs').choose(options, identity);
|
|
164
|
-
if (!selection) { console.log('Installation cancelled. No files changed.'); return; }
|
|
165
|
-
Object.assign(options, selection);
|
|
166
|
-
} else if (!options.agents.length || !options.scope || (!options.yes && !options.dryRun)) {
|
|
167
|
-
throw new Error('Use an interactive terminal, or specify --agents <ids> --scope project|global --yes.');
|
|
168
|
-
}
|
|
169
|
-
const plan = installer.planInstallation(options);
|
|
170
|
-
if (options.dryRun) {
|
|
171
|
-
console.log(JSON.stringify({ name, version: pkg.version, upstreamVersion: pkg.i3ComposerAgent.upstreamVersion, upstreamCommit: pkg.i3ComposerAgent.upstreamCommit, mode: plan.mode, scope: plan.scope, agents: plan.agents, targets: plan.targets, conflicts: plan.conflicts }, null, 2));
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (!options.yes) {
|
|
175
|
-
const confirmed = await require('./wizard.cjs').confirm(plan);
|
|
176
|
-
if (!confirmed) { console.log('Installation cancelled. No files changed.'); return; }
|
|
177
|
-
options.force = plan.conflicts.length > 0;
|
|
178
|
-
}
|
|
179
|
-
const backups = installer.executeInstallation(plan, { force: options.force });
|
|
180
|
-
console.log(identity);
|
|
181
|
-
for (const target of plan.targets) {
|
|
182
|
-
console.log(`Installed ${target.agents.join(', ')} skills at ${target.skillsDirectory}`);
|
|
183
|
-
if (target.commandsDirectory) console.log(`Commands: /spec, /plan, /build, /ship (all ${fs.readdirSync(target.commandsDirectory).filter(file => file.endsWith('.md')).length} workflows) at ${target.commandsDirectory}`);
|
|
184
|
-
}
|
|
185
|
-
for (const backup of backups) console.log(`Previous assets preserved at ${backup}`);
|
|
186
|
-
console.log('Reload the IDE window or start a new host session to discover installed files.');
|
|
187
|
-
if (plan.agents.some(id => !['codebuddy', 'claude-code'].includes(id))) console.log('These tools use the existing skill IDs; lifecycle slash-command support depends on the host.');
|
|
188
|
-
console.log('Native skill installation does not enable plugin hooks. Use --prepare for complete plugin registration.');
|
|
189
|
-
}
|
|
190
|
-
main().catch(error => { console.error(`${name}: ${error.message}`); process.exitCode = 1; });
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const os = require('node:os');
|
|
6
|
-
const crypto = require('node:crypto');
|
|
7
|
-
const registry = require('./agent-targets.json');
|
|
8
|
-
const root = path.resolve(__dirname, '..');
|
|
9
|
-
const name = 'i3-composer-agent';
|
|
10
|
-
const aliases = { claude: 'claude-code', gemini: 'gemini-cli', copilot: 'github-copilot' };
|
|
11
|
-
function exists(file) {
|
|
12
|
-
try { fs.lstatSync(file); return true; }
|
|
13
|
-
catch (error) { if (error.code === 'ENOENT') return false; throw error; }
|
|
14
|
-
}
|
|
15
|
-
function selectAgents(values) {
|
|
16
|
-
const ids = [...new Set(values.flatMap(value => value.split(',')).map(id => aliases[id.trim()] || id.trim()).filter(Boolean))];
|
|
17
|
-
for (const id of ids) if (!registry.agents.some(agent => agent.id === id)) throw new Error(`Unknown tool: ${id}. Use --list-agents.`);
|
|
18
|
-
return ids;
|
|
19
|
-
}
|
|
20
|
-
function globalSkillsDir(agent, home, env) {
|
|
21
|
-
const config = agent.globalSkillsDir;
|
|
22
|
-
if (!config) throw new Error(`${agent.label} supports project installation only. Choose project scope or remove this tool.`);
|
|
23
|
-
let base = home;
|
|
24
|
-
if (config.base === 'config') base = env.XDG_CONFIG_HOME && path.isAbsolute(env.XDG_CONFIG_HOME) ? env.XDG_CONFIG_HOME : path.join(home, '.config');
|
|
25
|
-
if (config.base === 'env') base = env[config.env]?.trim() || path.join(home, config.fallback);
|
|
26
|
-
if (config.alternatives && !fs.existsSync(path.join(home, path.dirname(config.path)))) {
|
|
27
|
-
const alternative = config.alternatives.find(relative => fs.existsSync(path.join(home, path.dirname(relative))));
|
|
28
|
-
if (alternative) return path.join(home, alternative);
|
|
29
|
-
}
|
|
30
|
-
return path.resolve(base, config.path);
|
|
31
|
-
}
|
|
32
|
-
// Resolve parent aliases while keeping a leaf symlink replaceable as a link.
|
|
33
|
-
function destination(file) {
|
|
34
|
-
let parent = path.dirname(file);
|
|
35
|
-
const suffix = [path.basename(file)];
|
|
36
|
-
while (!exists(parent)) { suffix.unshift(path.basename(parent)); parent = path.dirname(parent); }
|
|
37
|
-
return path.join(fs.realpathSync(parent), ...suffix);
|
|
38
|
-
}
|
|
39
|
-
function checkDestination(file) {
|
|
40
|
-
const source = fs.realpathSync(root);
|
|
41
|
-
if (file === source || file.startsWith(source + path.sep) || source.startsWith(file + path.sep)) {
|
|
42
|
-
throw new Error('Installation destinations must be outside this package directory.');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
function planInstallation({ agents, scope, projectDir = process.cwd(), home = os.homedir(), env = process.env }) {
|
|
46
|
-
agents = selectAgents(agents);
|
|
47
|
-
if (!agents.length) throw new Error('Choose at least one tool.');
|
|
48
|
-
if (!['project', 'global'].includes(scope)) throw new Error('Choose --scope project or --scope global.');
|
|
49
|
-
const targets = new Map();
|
|
50
|
-
for (const id of agents) {
|
|
51
|
-
const agent = registry.agents.find(item => item.id === id);
|
|
52
|
-
const skillsDirectory = destination(scope === 'project' ? path.resolve(projectDir, agent.projectSkillsDir) : globalSkillsDir(agent, home, env));
|
|
53
|
-
const base = path.dirname(skillsDirectory);
|
|
54
|
-
if (!targets.has(skillsDirectory)) targets.set(skillsDirectory, { agents: [], skillsDirectory, referencesDirectory: path.join(base, 'references'), base });
|
|
55
|
-
const target = targets.get(skillsDirectory);
|
|
56
|
-
target.agents.push(id);
|
|
57
|
-
if (['codebuddy', 'claude-code'].includes(id)) {
|
|
58
|
-
target.commandsDirectory = path.join(base, 'commands');
|
|
59
|
-
target.personasDirectory = path.join(base, 'agents');
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
const assets = [];
|
|
63
|
-
function addDirectory(sourceDirectory, destinationDirectory, kind) {
|
|
64
|
-
if (exists(destinationDirectory) && fs.lstatSync(destinationDirectory).isSymbolicLink()) {
|
|
65
|
-
throw new Error(`Installation directory is a symlink: ${destinationDirectory}. Choose its canonical location instead.`);
|
|
66
|
-
}
|
|
67
|
-
for (const entry of fs.readdirSync(path.join(root, sourceDirectory)).sort()) {
|
|
68
|
-
if (entry === '.DS_Store') continue;
|
|
69
|
-
assets.push({ source: path.join(root, sourceDirectory, entry), destination: destination(path.join(destinationDirectory, entry)), kind });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
for (const target of targets.values()) {
|
|
73
|
-
const start = assets.length;
|
|
74
|
-
addDirectory('skills', target.skillsDirectory, 'skill');
|
|
75
|
-
addDirectory('references', target.referencesDirectory, 'reference');
|
|
76
|
-
if (target.commandsDirectory) {
|
|
77
|
-
addDirectory('.claude/commands', target.commandsDirectory, 'command');
|
|
78
|
-
addDirectory('agents', target.personasDirectory, 'persona');
|
|
79
|
-
const docs = path.join(target.base, 'docs');
|
|
80
|
-
if (exists(docs) && fs.lstatSync(docs).isSymbolicLink()) throw new Error(`Installation directory is a symlink: ${docs}.`);
|
|
81
|
-
assets.push({ source: path.join(root, 'docs/agents.md'), destination: destination(path.join(target.base, 'docs/agents.md')), kind: 'documentation' });
|
|
82
|
-
}
|
|
83
|
-
assets.push({ source: path.join(root, 'LICENSE'), destination: destination(path.join(target.base, `${name}-LICENSE`)), kind: 'attribution' });
|
|
84
|
-
assets.push({ source: path.join(root, 'NOTICE.md'), destination: destination(path.join(target.base, `${name}-NOTICE.md`)), kind: 'attribution' });
|
|
85
|
-
assets.push({ target, destination: destination(path.join(target.base, `.${name}.json`)), kind: 'manifest' });
|
|
86
|
-
for (const asset of assets.slice(start)) {
|
|
87
|
-
asset.base = target.base;
|
|
88
|
-
asset.codebuddyCommand = asset.kind === 'command' && target.agents.includes('codebuddy');
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
for (const asset of assets) { checkDestination(asset.destination); asset.existing = exists(asset.destination); }
|
|
92
|
-
return { mode: 'install', scope, agents, targets: [...targets.values()], assets, conflicts: assets.filter(asset => asset.existing).map(asset => asset.destination) };
|
|
93
|
-
}
|
|
94
|
-
function executeInstallation(plan, { force = false } = {}) {
|
|
95
|
-
if (plan.conflicts.length && !force) throw new Error('Existing installation assets require confirmation or --force; previous files will be backed up.');
|
|
96
|
-
const pkg = require('../package.json');
|
|
97
|
-
const contexts = new Map();
|
|
98
|
-
const createdDirectories = [];
|
|
99
|
-
const committed = [];
|
|
100
|
-
function mkdir(directory) {
|
|
101
|
-
if (exists(directory)) return;
|
|
102
|
-
mkdir(path.dirname(directory));
|
|
103
|
-
fs.mkdirSync(directory);
|
|
104
|
-
createdDirectories.push(directory);
|
|
105
|
-
}
|
|
106
|
-
function portable(file, markdown = false, codebuddyCommand = false) {
|
|
107
|
-
if (fs.statSync(file).isDirectory()) {
|
|
108
|
-
for (const entry of fs.readdirSync(file)) portable(path.join(file, entry), false, codebuddyCommand);
|
|
109
|
-
} else if (markdown || file.endsWith('.md')) {
|
|
110
|
-
let content = fs.readFileSync(file, 'utf8').replaceAll('i3-composer-agent:', '').replaceAll('agent-skills:', '');
|
|
111
|
-
if (codebuddyCommand) content = content.replaceAll('.claude/agents/', '.codebuddy/agents/');
|
|
112
|
-
fs.writeFileSync(file, content);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
try {
|
|
116
|
-
// Stage every target before replacing the first installed asset.
|
|
117
|
-
for (const [index, asset] of plan.assets.entries()) {
|
|
118
|
-
if (!contexts.has(asset.base)) {
|
|
119
|
-
mkdir(asset.base);
|
|
120
|
-
contexts.set(asset.base, { staging: fs.mkdtempSync(path.join(asset.base, `.${name}-stage-`)), backup: path.join(asset.base, `.${name}-backup-${crypto.randomUUID()}`) });
|
|
121
|
-
}
|
|
122
|
-
const context = contexts.get(asset.base);
|
|
123
|
-
asset.staged = path.join(context.staging, String(index));
|
|
124
|
-
if (asset.kind === 'manifest') {
|
|
125
|
-
fs.writeFileSync(asset.staged, JSON.stringify({ name, version: pkg.version, upstreamVersion: pkg.i3ComposerAgent.upstreamVersion, upstreamCommit: pkg.i3ComposerAgent.upstreamCommit, scope: plan.scope, agents: asset.target.agents, assets: plan.assets.filter(item => item.base === asset.base).map(item => path.relative(asset.base, item.destination)) }, null, 2) + '\n');
|
|
126
|
-
} else {
|
|
127
|
-
fs.cpSync(asset.source, asset.staged, { recursive: true, filter: file => {
|
|
128
|
-
if (fs.lstatSync(file).isSymbolicLink()) throw new Error(`Package contains a symlink: ${file}`);
|
|
129
|
-
return path.basename(file) !== '.DS_Store';
|
|
130
|
-
} });
|
|
131
|
-
portable(asset.staged, asset.source.endsWith('.md'), asset.codebuddyCommand);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
for (const asset of plan.assets) {
|
|
135
|
-
const context = contexts.get(asset.base);
|
|
136
|
-
mkdir(path.dirname(asset.destination));
|
|
137
|
-
// A destination may have appeared since planning; never replace it implicitly.
|
|
138
|
-
if (exists(asset.destination) && !force) throw new Error(`Destination appeared during installation: ${asset.destination}. Retry with confirmed replacement.`);
|
|
139
|
-
const change = { destination: asset.destination, written: false };
|
|
140
|
-
if (exists(asset.destination)) {
|
|
141
|
-
change.backup = path.join(context.backup, path.relative(asset.base, asset.destination));
|
|
142
|
-
mkdir(path.dirname(change.backup));
|
|
143
|
-
fs.renameSync(asset.destination, change.backup);
|
|
144
|
-
}
|
|
145
|
-
committed.push(change);
|
|
146
|
-
fs.renameSync(asset.staged, asset.destination);
|
|
147
|
-
change.written = true;
|
|
148
|
-
}
|
|
149
|
-
return [...contexts.values()].map(context => context.backup).filter(exists);
|
|
150
|
-
} catch (error) {
|
|
151
|
-
for (const change of committed.reverse()) {
|
|
152
|
-
if (change.written) fs.rmSync(change.destination, { recursive: true, force: true });
|
|
153
|
-
if (change.backup) fs.renameSync(change.backup, change.destination);
|
|
154
|
-
}
|
|
155
|
-
throw error;
|
|
156
|
-
} finally {
|
|
157
|
-
for (const context of contexts.values()) fs.rmSync(context.staging, { recursive: true, force: true });
|
|
158
|
-
for (const directory of createdDirectories.reverse()) {
|
|
159
|
-
try { fs.rmdirSync(directory); }
|
|
160
|
-
catch (error) { if (!['ENOENT', 'ENOTEMPTY', 'EEXIST'].includes(error.code)) throw error; }
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
module.exports = { registry, selectAgents, planInstallation, executeInstallation, exists };
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Vercel, Inc.
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const readline = require('node:readline');
|
|
4
|
-
const { registry, selectAgents } = require('./install.cjs');
|
|
5
|
-
|
|
6
|
-
async function session(action) {
|
|
7
|
-
const terminal = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8
|
-
let closed = false;
|
|
9
|
-
terminal.on('close', () => { closed = true; });
|
|
10
|
-
terminal.on('SIGINT', () => terminal.close());
|
|
11
|
-
const ask = prompt => new Promise(resolve => {
|
|
12
|
-
if (closed) { resolve(null); return; }
|
|
13
|
-
const cancelled = () => resolve(null);
|
|
14
|
-
terminal.once('close', cancelled);
|
|
15
|
-
terminal.question(prompt, answer => {
|
|
16
|
-
terminal.off('close', cancelled);
|
|
17
|
-
resolve(answer.trim());
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
try { return await action(ask); }
|
|
21
|
-
finally { terminal.close(); }
|
|
22
|
-
}
|
|
23
|
-
async function choose(options, identity) {
|
|
24
|
-
console.log(identity);
|
|
25
|
-
console.log('\nChoose tools (multiple selections are supported):');
|
|
26
|
-
const entries = registry.agents.map((agent, index) => `${index + 1}. ${agent.label} [${agent.id}]${agent.globalSkillsDir ? '' : ' (project only)'}`);
|
|
27
|
-
for (let index = 0; index < entries.length; index += 2) console.log(entries[index].padEnd(49) + (entries[index + 1] || ''));
|
|
28
|
-
return session(async ask => {
|
|
29
|
-
let agents;
|
|
30
|
-
while (!agents) {
|
|
31
|
-
const input = await ask(`Select tools by number or ID, comma-separated${options.agents.length ? ` [${options.agents.join(',')}]` : ''} (q cancels): `);
|
|
32
|
-
if (input === null || ['q', 'cancel'].includes(input.toLowerCase())) return null;
|
|
33
|
-
try {
|
|
34
|
-
const values = input === 'all' ? registry.agents.map(agent => agent.id) : input ? input.split(',').map(value => {
|
|
35
|
-
const trimmed = value.trim();
|
|
36
|
-
return /^\d+$/.test(trimmed) ? registry.agents[Number(trimmed) - 1]?.id || trimmed : trimmed;
|
|
37
|
-
}) : options.agents;
|
|
38
|
-
agents = selectAgents(values);
|
|
39
|
-
if (!agents.length) { agents = undefined; console.log('Choose at least one tool.'); }
|
|
40
|
-
} catch (error) { console.log(error.message); }
|
|
41
|
-
}
|
|
42
|
-
let scope = options.scope;
|
|
43
|
-
while (!scope) {
|
|
44
|
-
const input = await ask('Installation scope: 1. Project (current project), 2. Global (all projects) [1] (q cancels): ');
|
|
45
|
-
if (input === null || ['q', 'cancel'].includes(input.toLowerCase())) return null;
|
|
46
|
-
if (['', '1', 'project'].includes(input)) scope = 'project';
|
|
47
|
-
else if (['2', 'global'].includes(input)) scope = 'global';
|
|
48
|
-
else console.log('Choose 1 (project) or 2 (global).');
|
|
49
|
-
}
|
|
50
|
-
return { agents, scope };
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
async function confirm(plan) {
|
|
54
|
-
console.log(`\nInstallation scope: ${plan.scope}`);
|
|
55
|
-
for (const target of plan.targets) {
|
|
56
|
-
console.log(` ${target.agents.join(', ')}: ${target.skillsDirectory}`);
|
|
57
|
-
if (target.commandsDirectory) console.log(` Commands: ${target.commandsDirectory}`);
|
|
58
|
-
}
|
|
59
|
-
if (plan.conflicts.length) console.log(`${plan.conflicts.length} existing assets will be preserved in backups before replacement.`);
|
|
60
|
-
console.log('Includes all skills and shared references. Plugin hooks require native plugin registration.');
|
|
61
|
-
return session(async ask => {
|
|
62
|
-
const answer = await ask('Install these files? [y/N]: ');
|
|
63
|
-
return answer !== null && ['y', 'yes'].includes(answer.toLowerCase());
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
module.exports = { choose, confirm };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const fs = require('node:fs');
|
|
6
|
-
const path = require('node:path');
|
|
7
|
-
const root = path.resolve(__dirname, '..');
|
|
8
|
-
const brand = require('../rebrand/brand.json');
|
|
9
|
-
const pkg = require('../package.json');
|
|
10
|
-
const upstream = require('../rebrand/upstream.json');
|
|
11
|
-
const { validateCatalog } = require('../rebrand/catalog.cjs');
|
|
12
|
-
validateCatalog(root, upstream);
|
|
13
|
-
function json(relative) { return JSON.parse(fs.readFileSync(path.join(root, relative), 'utf8')); }
|
|
14
|
-
assert.equal(pkg.name, brand.npmName);
|
|
15
|
-
assert.equal(pkg.version, brand.releaseVersion);
|
|
16
|
-
assert.equal(brand.upstreamCommit, upstream.upstreamCommit);
|
|
17
|
-
assert.equal(pkg.i3ComposerAgent.upstreamVersion, upstream.upstreamVersion);
|
|
18
|
-
assert.equal(pkg.i3ComposerAgent.upstreamCommit, upstream.upstreamCommit);
|
|
19
|
-
assert.equal(pkg.i3ComposerAgent.upstreamRepository, brand.upstream);
|
|
20
|
-
assert.equal(Object.keys(pkg.bin).length, 1, 'npx must resolve one executable');
|
|
21
|
-
assert.ok(fs.existsSync(path.join(root, pkg.bin[brand.name])));
|
|
22
|
-
for (const relative of ['plugin.json', '.claude-plugin/plugin.json', '.codex-plugin/plugin.json']) {
|
|
23
|
-
assert.equal(json(relative).name, brand.name, `${relative}: inconsistent plugin identity`);
|
|
24
|
-
}
|
|
25
|
-
for (const relative of ['.claude-plugin/marketplace.json', '.agents/plugins/marketplace.json']) {
|
|
26
|
-
const marketplace = json(relative);
|
|
27
|
-
assert.equal(marketplace.name, brand.name);
|
|
28
|
-
assert.equal(marketplace.plugins[0].name, brand.name);
|
|
29
|
-
assert.ok(marketplace.plugins[0].source === './' || marketplace.plugins[0].source.path === './', 'marketplace must load this distribution');
|
|
30
|
-
}
|
|
31
|
-
assert.ok(json('.codex-plugin/plugin.json').interface.shortDescription.length <= 30);
|
|
32
|
-
const skills = fs.readdirSync(path.join(root, 'skills')).filter(name => fs.existsSync(path.join(root, 'skills', name, 'SKILL.md')));
|
|
33
|
-
assert.equal(skills.length, upstream.skills.length, 'brand conversion must preserve the reviewed upstream skills');
|
|
34
|
-
assert.ok(skills.includes('using-agent-skills'), 'meta-skill ID must remain stable');
|
|
35
|
-
const commands = fs.readdirSync(path.join(root, '.claude/commands')).filter(name => name.endsWith('.md'));
|
|
36
|
-
assert.equal(commands.length, upstream.commands.length, 'brand conversion must preserve the reviewed upstream commands');
|
|
37
|
-
for (const command of commands) {
|
|
38
|
-
const body = fs.readFileSync(path.join(root, '.claude/commands', command), 'utf8');
|
|
39
|
-
assert.ok(!body.includes('agent-skills:'), `${command}: stale plugin namespace`);
|
|
40
|
-
for (const match of body.matchAll(/i3-composer-agent:([a-z-]+)/g)) assert.ok(skills.includes(match[1]), `${command}: unknown skill ${match[1]}`);
|
|
41
|
-
}
|
|
42
|
-
for (const relative of pkg.i3ComposerAgent.pluginFiles) assert.ok(fs.existsSync(path.join(root, relative)), `bundle is missing ${relative}`);
|
|
43
|
-
assert.match(fs.readFileSync(path.join(root, 'LICENSE'), 'utf8'), /Copyright \(c\) 2025 Addy Osmani/);
|
|
44
|
-
console.log('Brand identity, package entrypoint, reviewed upstream catalog, and attribution are consistent.');
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const test = require('node:test');
|
|
4
|
-
const assert = require('node:assert/strict');
|
|
5
|
-
const fs = require('node:fs');
|
|
6
|
-
const os = require('node:os');
|
|
7
|
-
const path = require('node:path');
|
|
8
|
-
const { spawnSync } = require('node:child_process');
|
|
9
|
-
const root = path.resolve(__dirname, '..');
|
|
10
|
-
const upstream = require('../rebrand/upstream.json');
|
|
11
|
-
const cli = path.join(root, 'bin/i3-composer-agent.cjs');
|
|
12
|
-
function run(args) { return spawnSync(process.execPath, [cli, ...args], { encoding: 'utf8' }); }
|
|
13
|
-
function workspace(t) {
|
|
14
|
-
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'i3-agent-bundle-'));
|
|
15
|
-
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
|
16
|
-
return directory;
|
|
17
|
-
}
|
|
18
|
-
test('help and version identify the package without preparing files', () => {
|
|
19
|
-
const help = run(['--help']).stdout;
|
|
20
|
-
assert.match(help, /@givolt\/i3-composer-agent/);
|
|
21
|
-
assert.ok(help.includes(`i3 Composer Agent v${require('../package.json').version}`));
|
|
22
|
-
assert.ok(help.includes(`Based on agent-skills v${upstream.upstreamVersion}`));
|
|
23
|
-
assert.equal(run(['--version']).stdout.trim(), require('../package.json').version);
|
|
24
|
-
});
|
|
25
|
-
test('dry run and invalid arguments leave the target untouched', t => {
|
|
26
|
-
const target = path.join(workspace(t), 'plugin');
|
|
27
|
-
assert.equal(run(['--codex', '--dir', target, '--dry-run']).status, 0);
|
|
28
|
-
assert.equal(fs.existsSync(target), false);
|
|
29
|
-
assert.equal(run(['--codebuddy', '--codex', '--dir', target]).status, 1);
|
|
30
|
-
assert.equal(run(['--extract', '--dir']).status, 1);
|
|
31
|
-
assert.equal(fs.existsSync(target), false);
|
|
32
|
-
});
|
|
33
|
-
test('complete bundle preserves reference links and all host assets at a path with spaces', t => {
|
|
34
|
-
const target = path.join(workspace(t), 'plugin with spaces');
|
|
35
|
-
const result = run(['--codebuddy', '--dir', target]);
|
|
36
|
-
assert.equal(result.status, 0, result.stderr);
|
|
37
|
-
assert.ok(result.stdout.includes(`i3 Composer Agent v${require('../package.json').version}`));
|
|
38
|
-
assert.ok(result.stdout.includes(`Based on agent-skills v${upstream.upstreamVersion}`));
|
|
39
|
-
assert.match(result.stdout, /\/plugin install i3-composer-agent@i3-composer-agent/);
|
|
40
|
-
assert.equal(fs.readdirSync(path.join(target, 'skills')).length, upstream.skills.length);
|
|
41
|
-
assert.equal(fs.readdirSync(path.join(target, '.claude/commands')).length, upstream.commands.length);
|
|
42
|
-
assert.equal(fs.readdirSync(path.join(target, 'agents')).filter(name => name.endsWith('.md')).length, upstream.personas.length);
|
|
43
|
-
assert.equal(JSON.parse(fs.readFileSync(path.join(target, '.codex-plugin/plugin.json'), 'utf8')).name, 'i3-composer-agent');
|
|
44
|
-
const check = spawnSync(process.execPath, [path.join(target, 'scripts/validate-reference-links.js')], { cwd: target, encoding: 'utf8' });
|
|
45
|
-
assert.equal(check.status, 0, check.stdout + check.stderr);
|
|
46
|
-
assert.equal(fs.existsSync(path.join(target, 'AGENTS.md')), false);
|
|
47
|
-
assert.equal(fs.existsSync(path.join(target, 'CLAUDE.md')), false);
|
|
48
|
-
});
|
|
49
|
-
test('existing unrelated files cannot be replaced even with force', t => {
|
|
50
|
-
const target = path.join(workspace(t), 'existing');
|
|
51
|
-
fs.mkdirSync(target);
|
|
52
|
-
fs.writeFileSync(path.join(target, 'plugin.json'), '{"name":"another-plugin"}\n');
|
|
53
|
-
fs.writeFileSync(path.join(target, 'keep.txt'), 'business data');
|
|
54
|
-
assert.equal(run(['--extract', '--dir', target]).status, 1);
|
|
55
|
-
assert.equal(run(['--extract', '--dir', target, '--force']).status, 1);
|
|
56
|
-
assert.equal(fs.readFileSync(path.join(target, 'keep.txt'), 'utf8'), 'business data');
|
|
57
|
-
});
|
|
58
|
-
test('installed packages beneath node_modules remain fully extractable', t => {
|
|
59
|
-
const parent = workspace(t);
|
|
60
|
-
const installed = path.join(parent, 'node_modules', '@givolt', 'i3-composer-agent');
|
|
61
|
-
assert.equal(run(['--extract', '--dir', installed]).status, 0);
|
|
62
|
-
const target = path.join(parent, 'plugin');
|
|
63
|
-
const result = spawnSync(process.execPath, [path.join(installed, 'bin/i3-composer-agent.cjs'), '--extract', '--dir', target], { encoding: 'utf8' });
|
|
64
|
-
assert.equal(result.status, 0, result.stderr);
|
|
65
|
-
assert.equal(JSON.parse(fs.readFileSync(path.join(target, 'package.json'), 'utf8')).name, '@givolt/i3-composer-agent');
|
|
66
|
-
assert.equal(fs.readdirSync(path.join(target, 'skills')).length, upstream.skills.length);
|
|
67
|
-
});
|
|
68
|
-
test('replacement preserves the complete previous bundle in a backup', t => {
|
|
69
|
-
const parent = workspace(t);
|
|
70
|
-
const target = path.join(parent, 'plugin');
|
|
71
|
-
assert.equal(run(['--extract', '--dir', target]).status, 0);
|
|
72
|
-
fs.writeFileSync(path.join(target, 'custom-note.txt'), 'preserve me');
|
|
73
|
-
const result = run(['--extract', '--dir', target, '--force']);
|
|
74
|
-
assert.equal(result.status, 0, result.stderr);
|
|
75
|
-
const backups = fs.readdirSync(parent).filter(name => name.startsWith('plugin.backup-'));
|
|
76
|
-
assert.equal(backups.length, 1);
|
|
77
|
-
assert.equal(fs.readFileSync(path.join(parent, backups[0], 'custom-note.txt'), 'utf8'), 'preserve me');
|
|
78
|
-
assert.equal(fs.existsSync(path.join(target, 'custom-note.txt')), false);
|
|
79
|
-
});
|
|
80
|
-
test('source directories and aliases cannot be overwritten', t => {
|
|
81
|
-
assert.equal(run(['--extract', '--dir', root, '--force']).status, 1);
|
|
82
|
-
const alias = path.join(workspace(t), 'source-alias');
|
|
83
|
-
fs.symlinkSync(root, alias, 'dir');
|
|
84
|
-
assert.equal(run(['--extract', '--dir', alias, '--force']).status, 1);
|
|
85
|
-
});
|