@jaimevalasek/aioson 1.5.1 → 1.6.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/README.md +6 -0
- package/docs/design-previews/aurora-command-ui-website.html +884 -0
- package/docs/design-previews/aurora-command-ui.html +682 -0
- package/docs/design-previews/bold-editorial-ui-website.html +658 -0
- package/docs/design-previews/bold-editorial-ui.html +717 -0
- package/docs/design-previews/clean-saas-ui-website.html +1202 -0
- package/docs/design-previews/clean-saas-ui.html +549 -0
- package/docs/design-previews/cognitive-core-ui-website.html +1009 -0
- package/docs/design-previews/cognitive-core-ui.html +463 -0
- package/docs/design-previews/glassmorphism-ui-website.html +572 -0
- package/docs/design-previews/glassmorphism-ui.html +886 -0
- package/docs/design-previews/index.html +699 -0
- package/docs/design-previews/interface-design-website.html +1187 -0
- package/docs/design-previews/interface-design.html +513 -0
- package/docs/design-previews/neo-brutalist-ui-website.html +621 -0
- package/docs/design-previews/neo-brutalist-ui.html +797 -0
- package/docs/design-previews/premium-command-center-ui-website.html +1217 -0
- package/docs/design-previews/premium-command-center-ui.html +552 -0
- package/docs/design-previews/warm-craft-ui-website.html +684 -0
- package/docs/design-previews/warm-craft-ui.html +739 -0
- package/docs/en/cli-reference.md +20 -9
- package/docs/pt/README.md +7 -0
- package/docs/pt/agent-sharding.md +132 -0
- package/docs/pt/agentes.md +8 -2
- package/docs/pt/busca-de-contexto.md +129 -0
- package/docs/pt/cache-de-contexto.md +156 -0
- package/docs/pt/comandos-cli.md +28 -0
- package/docs/pt/design-hybrid-forge.md +107 -0
- package/docs/pt/inicio-rapido.md +54 -3
- package/docs/pt/inteligencia-adaptativa.md +324 -0
- package/docs/pt/monitor-de-contexto.md +104 -0
- package/docs/pt/recuperacao-de-sessao.md +125 -0
- package/docs/pt/sandbox.md +125 -0
- package/docs/pt/skills.md +98 -6
- package/package.json +1 -1
- package/src/agent-loader.js +280 -0
- package/src/cli.js +94 -0
- package/src/commands/agent-loader.js +85 -0
- package/src/commands/context-cache.js +90 -0
- package/src/commands/context-monitor.js +92 -0
- package/src/commands/context-search.js +66 -0
- package/src/commands/design-hybrid-options.js +385 -0
- package/src/commands/health.js +214 -0
- package/src/commands/init.js +54 -13
- package/src/commands/install.js +52 -13
- package/src/commands/learning-evolve.js +355 -0
- package/src/commands/live.js +34 -0
- package/src/commands/recovery.js +43 -0
- package/src/commands/sandbox.js +37 -0
- package/src/commands/setup-context.js +22 -2
- package/src/commands/setup.js +178 -0
- package/src/commands/skill.js +79 -32
- package/src/commands/tool-registry-cmd.js +232 -0
- package/src/commands/update.js +7 -0
- package/src/constants.js +9 -0
- package/src/context-cache.js +159 -0
- package/src/context-search.js +326 -0
- package/src/design-variation-catalog.js +503 -0
- package/src/i18n/messages/en.js +32 -2
- package/src/i18n/messages/es.js +30 -2
- package/src/i18n/messages/fr.js +30 -2
- package/src/i18n/messages/pt-BR.js +32 -2
- package/src/install-animation.js +260 -0
- package/src/install-profile.js +143 -0
- package/src/install-wizard.js +474 -0
- package/src/installer.js +38 -10
- package/src/parser.js +7 -1
- package/src/recovery-context-session.js +154 -0
- package/src/runtime-store.js +97 -1
- package/src/sandbox.js +177 -0
- package/src/tool-executor.js +94 -0
- package/src/updater.js +11 -3
- package/template/.aioson/agents/analyst.md +58 -3
- package/template/.aioson/agents/architect.md +38 -0
- package/template/.aioson/agents/design-hybrid-forge.md +127 -0
- package/template/.aioson/agents/dev.md +103 -0
- package/template/.aioson/agents/deyvin.md +57 -0
- package/template/.aioson/agents/pm.md +58 -0
- package/template/.aioson/agents/product.md +28 -0
- package/template/.aioson/agents/qa.md +79 -0
- package/template/.aioson/agents/setup.md +65 -3
- package/template/.aioson/agents/sheldon.md +107 -6
- package/template/.aioson/agents/tester.md +156 -0
- package/template/.aioson/config.md +15 -0
- package/template/.aioson/context/forensics/.gitkeep +0 -0
- package/template/.aioson/context/seeds/seed-example.md +27 -0
- package/template/.aioson/context/user-profile.md +42 -0
- package/template/.aioson/locales/en/agents/setup.md +33 -1
- package/template/.aioson/locales/es/agents/setup.md +33 -1
- package/template/.aioson/locales/fr/agents/setup.md +33 -1
- package/template/.aioson/locales/pt-BR/agents/setup.md +33 -1
- package/template/.aioson/skills/design/aurora-command-ui/SKILL.md +243 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/art-direction.md +293 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/components.md +827 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/dashboards.md +250 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/design-tokens.md +585 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/motion.md +365 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/patterns.md +482 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/websites.md +387 -0
- package/template/.aioson/skills/design/glassmorphism-ui/SKILL.md +222 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/art-direction.md +159 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/components.md +498 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/dashboards.md +236 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/design-tokens.md +274 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/motion.md +355 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/patterns.md +198 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/websites.md +307 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/SKILL.md +213 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/art-direction.md +228 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/components.md +855 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/dashboards.md +334 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/design-tokens.md +342 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/motion.md +286 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/patterns.md +458 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/websites.md +723 -0
- package/template/.aioson/skills/process/aioson-spec-driven/SKILL.md +45 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +109 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +44 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +37 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/hardening-lane.md +49 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +66 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/ui-language.md +75 -0
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +144 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/crossover-protocol.md +221 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/naming-registry.md +88 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +291 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +117 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +188 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/variation-library.md +125 -0
- package/template/AGENTS.md +23 -1
- package/template/CLAUDE.md +1 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const readline = require('node:readline');
|
|
4
|
+
const { getCliVersionSync } = require('./version');
|
|
5
|
+
|
|
6
|
+
const TOOLS = [
|
|
7
|
+
{ id: 'claude', label: 'Claude Code', desc: 'Slash commands, CLAUDE.md, .claude/' },
|
|
8
|
+
{ id: 'codex', label: 'Codex (OpenAI)', desc: 'AGENTS.md protocol' },
|
|
9
|
+
{ id: 'gemini', label: 'Gemini CLI', desc: 'GEMINI.md + .gemini/commands/' },
|
|
10
|
+
{ id: 'opencode', label: 'OpenCode', desc: 'OPENCODE.md protocol' }
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const USES = [
|
|
14
|
+
{
|
|
15
|
+
id: 'development',
|
|
16
|
+
label: 'Development',
|
|
17
|
+
desc: 'Agent workflow: setup → analyst → architect → dev → qa',
|
|
18
|
+
locked: true
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'squads',
|
|
22
|
+
label: 'Squads',
|
|
23
|
+
desc: 'Create and run AI squads (squad, genome, orache, profiler)',
|
|
24
|
+
locked: false
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const DESIGNS = [
|
|
29
|
+
{ id: 'none', label: 'None', desc: 'No design system installed' },
|
|
30
|
+
{ id: 'clean-saas-ui', label: 'Clean SaaS UI', desc: 'Minimal, functional — dashboards & tools' },
|
|
31
|
+
{ id: 'aurora-command-ui', label: 'Aurora Command UI', desc: 'Dark, glowing — command centers & apps' },
|
|
32
|
+
{ id: 'cognitive-core-ui', label: 'Cognitive Core UI', desc: 'Information-dense — data & analytics' },
|
|
33
|
+
{ id: 'bold-editorial-ui', label: 'Bold Editorial UI', desc: 'High contrast typography — content sites' },
|
|
34
|
+
{ id: 'warm-craft-ui', label: 'Warm Craft UI', desc: 'Warm tones, organic — consumer & lifestyle' },
|
|
35
|
+
{ id: 'glassmorphism-ui', label: 'Glassmorphism UI', desc: 'Translucent layers — immersive interfaces' },
|
|
36
|
+
{ id: 'neo-brutalist-ui', label: 'Neo-Brutalist UI', desc: 'Raw, high-contrast — bold statements' },
|
|
37
|
+
{ id: 'premium-command-center-ui',label: 'Premium Command Center UI',desc: 'Enterprise-grade — ops & monitoring' },
|
|
38
|
+
{ id: 'interface-design', label: 'Interface Design', desc: 'Foundational system — general purpose' }
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const LOCALES = [
|
|
42
|
+
{ id: 'en', label: 'English', flag: '🇺🇸' },
|
|
43
|
+
{ id: 'pt-BR', label: 'Português (Brasil)', flag: '🇧🇷' },
|
|
44
|
+
{ id: 'es', label: 'Español', flag: '🇪🇸' },
|
|
45
|
+
{ id: 'fr', label: 'Français', flag: '🇫🇷' }
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const BANNER_ART = [
|
|
49
|
+
'█████╗ ██╗ ██████╗ ███████╗ ██████╗ ███╗ ██╗',
|
|
50
|
+
'██╔══██╗██║██╔═══██╗██╔════╝██╔═══██╗████╗ ██║',
|
|
51
|
+
'███████║██║██║ ██║███████╗██║ ██║██╔██╗ ██║',
|
|
52
|
+
'██╔══██║██║██║ ██║╚════██║██║ ██║██║╚██╗██║',
|
|
53
|
+
'██║ ██║██║╚██████╔╝███████║╚██████╔╝██║ ╚████║',
|
|
54
|
+
'╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝'
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
function getBanner(version, stdout) {
|
|
58
|
+
const cols = (stdout && stdout.columns) || 80;
|
|
59
|
+
const noColor = process.env.NO_COLOR !== undefined;
|
|
60
|
+
const dumb = process.env.TERM === 'dumb';
|
|
61
|
+
|
|
62
|
+
if (dumb || cols < 60) {
|
|
63
|
+
return `AIOSON v${version}\n\n`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const cyan = noColor ? '' : '\x1b[1;36m';
|
|
67
|
+
const border = noColor ? '' : '\x1b[36m';
|
|
68
|
+
const dim = noColor ? '' : '\x1b[90m';
|
|
69
|
+
const reset = noColor ? '' : '\x1b[0m';
|
|
70
|
+
|
|
71
|
+
const artWidth = Math.max(...BANNER_ART.map(r => r.length));
|
|
72
|
+
const sidePad = 3;
|
|
73
|
+
const inner = artWidth + sidePad * 2;
|
|
74
|
+
const dashes = '─'.repeat(inner);
|
|
75
|
+
|
|
76
|
+
function centered(content, visibleLen) {
|
|
77
|
+
const left = Math.floor((inner - visibleLen) / 2);
|
|
78
|
+
const right = inner - left - visibleLen;
|
|
79
|
+
return ` ${border}│${reset}${' '.repeat(left)}${content}${' '.repeat(right)}${border}│${reset}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const emptyRow = ` ${border}│${reset}${' '.repeat(inner)}${border}│${reset}`;
|
|
83
|
+
const tagline = `AI Operating Framework v${version}`;
|
|
84
|
+
|
|
85
|
+
return [
|
|
86
|
+
` ${border}╭${dashes}╮${reset}`,
|
|
87
|
+
emptyRow,
|
|
88
|
+
...BANNER_ART.map(row => {
|
|
89
|
+
const left = Math.floor((inner - row.length) / 2);
|
|
90
|
+
const right = inner - left - row.length;
|
|
91
|
+
return ` ${border}│${reset}${' '.repeat(left)}${cyan}${row}${reset}${' '.repeat(right)}${border}│${reset}`;
|
|
92
|
+
}),
|
|
93
|
+
emptyRow,
|
|
94
|
+
centered(`${dim}${tagline}${reset}`, tagline.length),
|
|
95
|
+
emptyRow,
|
|
96
|
+
` ${border}╰${dashes}╯${reset}`,
|
|
97
|
+
''
|
|
98
|
+
].join('\n') + '\n';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function header(screen, total, stdout) {
|
|
102
|
+
stdout.write('\x1Bc');
|
|
103
|
+
stdout.write(getBanner(getCliVersionSync(), stdout));
|
|
104
|
+
stdout.write(` AIOSON — Installation Wizard (${screen}/${total})\n\n`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function renderScreen1(cursor, selected, warn, stdout) {
|
|
108
|
+
header(1, 4, stdout);
|
|
109
|
+
stdout.write(' Which AI tools will you use in this project?\n');
|
|
110
|
+
stdout.write(' (↑/↓ to move, space to select, enter to continue)\n\n');
|
|
111
|
+
for (let i = 0; i < TOOLS.length; i++) {
|
|
112
|
+
const tool = TOOLS[i];
|
|
113
|
+
const pointer = i === cursor ? '►' : ' ';
|
|
114
|
+
const check = selected.has(tool.id) ? '✓' : ' ';
|
|
115
|
+
stdout.write(` ${pointer} [${check}] ${tool.label.padEnd(20)} ${tool.desc}\n`);
|
|
116
|
+
}
|
|
117
|
+
if (warn) stdout.write('\n ⚠ Select at least one tool to continue.\n');
|
|
118
|
+
stdout.write('\n');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function renderScreen2(cursor, selected, stdout) {
|
|
122
|
+
header(2, 4, stdout);
|
|
123
|
+
stdout.write(' What will you do with AIOSON?\n');
|
|
124
|
+
stdout.write(' (space to select, enter to continue)\n\n');
|
|
125
|
+
for (let i = 0; i < USES.length; i++) {
|
|
126
|
+
const use = USES[i];
|
|
127
|
+
const pointer = i === cursor ? '►' : ' ';
|
|
128
|
+
const check = selected.has(use.id) ? '✓' : ' ';
|
|
129
|
+
const lock = use.locked ? ' (always on)' : '';
|
|
130
|
+
stdout.write(` ${pointer} [${check}] ${use.label}${lock}\n`);
|
|
131
|
+
stdout.write(` ${use.desc}\n`);
|
|
132
|
+
}
|
|
133
|
+
stdout.write('\n');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function renderScreen3(cursor, selected, warn, stdout) {
|
|
137
|
+
header(3, 4, stdout);
|
|
138
|
+
stdout.write(' Which design system? (optional — select multiple)\n');
|
|
139
|
+
stdout.write(' (↑/↓ to move, space to toggle, enter to continue)\n\n');
|
|
140
|
+
for (let i = 0; i < DESIGNS.length; i++) {
|
|
141
|
+
const d = DESIGNS[i];
|
|
142
|
+
const pointer = i === cursor ? '►' : ' ';
|
|
143
|
+
const check = selected.has(d.id) ? '✓' : ' ';
|
|
144
|
+
stdout.write(` ${pointer} [${check}] ${d.label.padEnd(28)} ${d.desc}\n`);
|
|
145
|
+
}
|
|
146
|
+
if (warn) stdout.write('\n ⚠ Select "None" or at least one design skill.\n');
|
|
147
|
+
stdout.write('\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function renderScreen4(cursor, stdout) {
|
|
151
|
+
header(4, 4, stdout);
|
|
152
|
+
stdout.write(' Which language for agents?\n');
|
|
153
|
+
stdout.write(' (↑/↓ to move, enter to select)\n\n');
|
|
154
|
+
for (let i = 0; i < LOCALES.length; i++) {
|
|
155
|
+
const loc = LOCALES[i];
|
|
156
|
+
const pointer = i === cursor ? '►' : ' ';
|
|
157
|
+
const bullet = i === cursor ? '◉' : '○';
|
|
158
|
+
stdout.write(` ${pointer} ${bullet} ${loc.flag} ${loc.label}\n`);
|
|
159
|
+
}
|
|
160
|
+
stdout.write('\n');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function renderConfirm(tools, uses, design, locale, existingProfile, t, stdout) {
|
|
164
|
+
const TOOL_NAMES = { claude: 'Claude Code', codex: 'Codex', gemini: 'Gemini CLI', opencode: 'OpenCode' };
|
|
165
|
+
const toolNames = tools.map(id => TOOL_NAMES[id] || id).join(', ');
|
|
166
|
+
const modeLabel = uses.includes('squads') ? 'Development + Squads' : 'Development';
|
|
167
|
+
// design can be string (single/id/all) or string[] (multiple)
|
|
168
|
+
const designList = Array.isArray(design)
|
|
169
|
+
? design.map(id => DESIGNS.find(d => d.id === id)?.label || id)
|
|
170
|
+
: [DESIGNS.find(d => d.id === design)?.label || design];
|
|
171
|
+
const designLabel = designList.join(', ');
|
|
172
|
+
const localeName = LOCALES.find(l => l.id === locale)?.label || locale;
|
|
173
|
+
|
|
174
|
+
stdout.write('\x1Bc');
|
|
175
|
+
stdout.write(` ${t('install_wizard.ready_to_install')}\n\n`);
|
|
176
|
+
stdout.write(` Tools → ${toolNames}\n`);
|
|
177
|
+
stdout.write(` Mode → ${modeLabel}\n`);
|
|
178
|
+
stdout.write(` Design → ${designLabel}\n`);
|
|
179
|
+
stdout.write(` Locale → ${localeName}\n\n`);
|
|
180
|
+
|
|
181
|
+
// Warn if reconfigure has deselected items (we don't auto-remove files)
|
|
182
|
+
if (existingProfile) {
|
|
183
|
+
const prevTools = new Set(Array.isArray(existingProfile.tools) ? existingProfile.tools : [existingProfile.tools]);
|
|
184
|
+
const prevDesign = new Set(Array.isArray(existingProfile.design) ? existingProfile.design : [existingProfile.design]);
|
|
185
|
+
const currTools = new Set(tools);
|
|
186
|
+
const currDesign = new Set(Array.isArray(design) ? design : [design]);
|
|
187
|
+
|
|
188
|
+
const removedTools = [...prevTools].filter(t => !currTools.has(t) && t !== 'none');
|
|
189
|
+
const removedDesign = [...prevDesign].filter(d => !currDesign.has(d) && d !== 'none' && d !== 'all');
|
|
190
|
+
|
|
191
|
+
if (removedTools.length > 0 || removedDesign.length > 0) {
|
|
192
|
+
stdout.write(` ${t('install_wizard.deselected_warning')}\n`);
|
|
193
|
+
stdout.write(`${t('install_wizard.deselected_hint')}\n\n`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
stdout.write(` ${t('install_wizard.press_enter_to_install')}\n\n`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function makeRawSession(io) {
|
|
201
|
+
const stdin = io.stdin || process.stdin;
|
|
202
|
+
const wasRaw = Boolean(stdin.isRaw);
|
|
203
|
+
const wasPaused = typeof stdin.isPaused === 'function' ? stdin.isPaused() : true;
|
|
204
|
+
|
|
205
|
+
readline.emitKeypressEvents(stdin);
|
|
206
|
+
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(true);
|
|
207
|
+
if (typeof stdin.resume === 'function') stdin.resume();
|
|
208
|
+
|
|
209
|
+
function cleanupListeners(onKeypress) {
|
|
210
|
+
stdin.removeListener('keypress', onKeypress);
|
|
211
|
+
if (stdin.listenerCount('keypress') === 0 && stdin.listenerCount('data') > 0) {
|
|
212
|
+
stdin.emit('data', Buffer.alloc(0));
|
|
213
|
+
}
|
|
214
|
+
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(wasRaw);
|
|
215
|
+
if (wasPaused && typeof stdin.pause === 'function') stdin.pause();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { stdin, cleanupListeners };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Generic multi-select prompt (checkbox)
|
|
222
|
+
async function promptCheckbox({ items, defaultSelected, lockFirst, render, io = {} }) {
|
|
223
|
+
const stdout = io.stdout || process.stdout;
|
|
224
|
+
const { stdin, cleanupListeners } = makeRawSession(io);
|
|
225
|
+
let cursor = 0;
|
|
226
|
+
const selected = new Set(defaultSelected);
|
|
227
|
+
let warn = false;
|
|
228
|
+
|
|
229
|
+
render(cursor, selected, warn, stdout);
|
|
230
|
+
|
|
231
|
+
return new Promise((resolve) => {
|
|
232
|
+
let cleanedUp = false;
|
|
233
|
+
function cleanup() {
|
|
234
|
+
if (cleanedUp) return;
|
|
235
|
+
cleanedUp = true;
|
|
236
|
+
cleanupListeners(onKeypress);
|
|
237
|
+
}
|
|
238
|
+
function onKeypress(_str, key) {
|
|
239
|
+
if (!key) return;
|
|
240
|
+
if ((key.ctrl && key.name === 'c') || key.name === 'q') { cleanup(); resolve(null); return; }
|
|
241
|
+
if (key.name === 'up') { cursor = cursor === 0 ? items.length - 1 : cursor - 1; render(cursor, selected, warn, stdout); return; }
|
|
242
|
+
if (key.name === 'down') { cursor = cursor === items.length - 1 ? 0 : cursor + 1; render(cursor, selected, warn, stdout); return; }
|
|
243
|
+
if (key.name === 'space') {
|
|
244
|
+
const item = items[cursor];
|
|
245
|
+
if (lockFirst && item.locked) return;
|
|
246
|
+
if (selected.has(item.id)) selected.delete(item.id);
|
|
247
|
+
else selected.add(item.id);
|
|
248
|
+
warn = false;
|
|
249
|
+
render(cursor, selected, warn, stdout);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (key.name === 'return') {
|
|
253
|
+
if (selected.size === 0) { warn = true; render(cursor, selected, warn, stdout); return; }
|
|
254
|
+
cleanup();
|
|
255
|
+
resolve([...selected]);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
stdin.on('keypress', onKeypress);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Generic single-select prompt (radio)
|
|
263
|
+
async function promptRadio({ items, defaultIndex, render, io = {} }) {
|
|
264
|
+
const stdout = io.stdout || process.stdout;
|
|
265
|
+
const { stdin, cleanupListeners } = makeRawSession(io);
|
|
266
|
+
let cursor = defaultIndex || 0;
|
|
267
|
+
|
|
268
|
+
render(cursor, stdout);
|
|
269
|
+
|
|
270
|
+
return new Promise((resolve) => {
|
|
271
|
+
let cleanedUp = false;
|
|
272
|
+
function cleanup() {
|
|
273
|
+
if (cleanedUp) return;
|
|
274
|
+
cleanedUp = true;
|
|
275
|
+
cleanupListeners(onKeypress);
|
|
276
|
+
}
|
|
277
|
+
function onKeypress(_str, key) {
|
|
278
|
+
if (!key) return;
|
|
279
|
+
if ((key.ctrl && key.name === 'c') || key.name === 'q') { cleanup(); resolve(null); return; }
|
|
280
|
+
if (key.name === 'up') { cursor = cursor === 0 ? items.length - 1 : cursor - 1; render(cursor, stdout); return; }
|
|
281
|
+
if (key.name === 'down') { cursor = cursor === items.length - 1 ? 0 : cursor + 1; render(cursor, stdout); return; }
|
|
282
|
+
if (key.name === 'return') { cleanup(); resolve(items[cursor].id); }
|
|
283
|
+
}
|
|
284
|
+
stdin.on('keypress', onKeypress);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Multi-select with exclusive option: when 'noneId' is selected it clears all others;
|
|
289
|
+
// when any other is selected it clears 'noneId'.
|
|
290
|
+
async function promptDesignCheckbox({ items, noneId, defaultSelected, render, io = {} }) {
|
|
291
|
+
const stdout = io.stdout || process.stdout;
|
|
292
|
+
const { stdin, cleanupListeners } = makeRawSession(io);
|
|
293
|
+
let cursor = 0;
|
|
294
|
+
const selected = new Set(defaultSelected);
|
|
295
|
+
let warn = false;
|
|
296
|
+
|
|
297
|
+
render(cursor, selected, warn, stdout);
|
|
298
|
+
|
|
299
|
+
return new Promise((resolve) => {
|
|
300
|
+
let cleanedUp = false;
|
|
301
|
+
function cleanup() {
|
|
302
|
+
if (cleanedUp) return;
|
|
303
|
+
cleanedUp = true;
|
|
304
|
+
cleanupListeners(onKeypress);
|
|
305
|
+
}
|
|
306
|
+
function onKeypress(_str, key) {
|
|
307
|
+
if (!key) return;
|
|
308
|
+
if ((key.ctrl && key.name === 'c') || key.name === 'q') { cleanup(); resolve(null); return; }
|
|
309
|
+
if (key.name === 'up') { cursor = cursor === 0 ? items.length - 1 : cursor - 1; render(cursor, selected, warn, stdout); return; }
|
|
310
|
+
if (key.name === 'down') { cursor = cursor === items.length - 1 ? 0 : cursor + 1; render(cursor, selected, warn, stdout); return; }
|
|
311
|
+
if (key.name === 'space') {
|
|
312
|
+
const item = items[cursor];
|
|
313
|
+
if (item.id === noneId) {
|
|
314
|
+
// Exclusive: selecting 'none' clears everything else
|
|
315
|
+
selected.clear();
|
|
316
|
+
selected.add(noneId);
|
|
317
|
+
} else {
|
|
318
|
+
// Selecting any other clears 'none'
|
|
319
|
+
selected.delete(noneId);
|
|
320
|
+
if (selected.has(item.id)) selected.delete(item.id);
|
|
321
|
+
else selected.add(item.id);
|
|
322
|
+
}
|
|
323
|
+
warn = false;
|
|
324
|
+
render(cursor, selected, warn, stdout);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (key.name === 'return') {
|
|
328
|
+
if (selected.size === 0) { warn = true; render(cursor, selected, warn, stdout); return; }
|
|
329
|
+
cleanup();
|
|
330
|
+
resolve([...selected]);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
stdin.on('keypress', onKeypress);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function promptConfirmScreen(tools, uses, design, locale, existingProfile, t, io = {}) {
|
|
338
|
+
const stdout = io.stdout || process.stdout;
|
|
339
|
+
const { stdin, cleanupListeners } = makeRawSession(io);
|
|
340
|
+
|
|
341
|
+
renderConfirm(tools, uses, design, locale, existingProfile, t, stdout);
|
|
342
|
+
|
|
343
|
+
return new Promise((resolve) => {
|
|
344
|
+
let cleanedUp = false;
|
|
345
|
+
function cleanup() {
|
|
346
|
+
if (cleanedUp) return;
|
|
347
|
+
cleanedUp = true;
|
|
348
|
+
cleanupListeners(onKeypress);
|
|
349
|
+
}
|
|
350
|
+
function onKeypress(_str, key) {
|
|
351
|
+
if (!key) return;
|
|
352
|
+
if ((key.ctrl && key.name === 'c') || key.name === 'q') { cleanup(); resolve(false); return; }
|
|
353
|
+
if (key.name === 'return') { cleanup(); resolve(true); }
|
|
354
|
+
}
|
|
355
|
+
stdin.on('keypress', onKeypress);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Runs the interactive install wizard.
|
|
361
|
+
* Returns { tools, uses, design, locale } or null (cancelled / non-TTY / --no-interactive).
|
|
362
|
+
* @param {object} options
|
|
363
|
+
* @param {object} [options.existingProfile] - Pre-existing profile to pre-select in wizard
|
|
364
|
+
* @param {function} [options.t] - Translator function for i18n strings
|
|
365
|
+
*/
|
|
366
|
+
async function runInstallWizard(options = {}, io = {}) {
|
|
367
|
+
const stdin = io.stdin || process.stdin;
|
|
368
|
+
const stdout = io.stdout || process.stdout;
|
|
369
|
+
const existingProfile = options.existingProfile || null;
|
|
370
|
+
const t = options.t || ((key) => key);
|
|
371
|
+
|
|
372
|
+
if (!stdin.isTTY || !stdout.isTTY) return null;
|
|
373
|
+
if (options.noInteractive) return null;
|
|
374
|
+
|
|
375
|
+
function finalCleanup() {
|
|
376
|
+
if (stdin === process.stdin) {
|
|
377
|
+
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(false);
|
|
378
|
+
stdin.pause();
|
|
379
|
+
if (typeof stdin.unref === 'function') stdin.unref();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Derive defaults from existing profile (supports both string and array design)
|
|
384
|
+
const defaultTools = existingProfile
|
|
385
|
+
? (Array.isArray(existingProfile.tools) ? existingProfile.tools : [existingProfile.tools])
|
|
386
|
+
: ['claude'];
|
|
387
|
+
const defaultUses = existingProfile
|
|
388
|
+
? (Array.isArray(existingProfile.uses) ? existingProfile.uses : [existingProfile.uses])
|
|
389
|
+
: ['development'];
|
|
390
|
+
const defaultDesign = existingProfile
|
|
391
|
+
? (Array.isArray(existingProfile.design)
|
|
392
|
+
? existingProfile.design
|
|
393
|
+
: (existingProfile.design === 'none' || existingProfile.design === 'all'
|
|
394
|
+
? [existingProfile.design]
|
|
395
|
+
: [existingProfile.design])) // single design skill
|
|
396
|
+
: ['none'];
|
|
397
|
+
const defaultLocale = existingProfile
|
|
398
|
+
? (LOCALES.findIndex(l => l.id === existingProfile.locale) || 0)
|
|
399
|
+
: 0;
|
|
400
|
+
|
|
401
|
+
// Screen 1 — Tools (multi-select)
|
|
402
|
+
const tools = await promptCheckbox({
|
|
403
|
+
items: TOOLS,
|
|
404
|
+
defaultSelected: defaultTools,
|
|
405
|
+
lockFirst: false,
|
|
406
|
+
render: (cursor, selected, warn, out) => renderScreen1(cursor, selected, warn, out),
|
|
407
|
+
io
|
|
408
|
+
});
|
|
409
|
+
if (!tools) { finalCleanup(); return null; }
|
|
410
|
+
|
|
411
|
+
// Screen 2 — Uses (multi-select, development locked)
|
|
412
|
+
const uses = await promptCheckbox({
|
|
413
|
+
items: USES,
|
|
414
|
+
defaultSelected: defaultUses,
|
|
415
|
+
lockFirst: true,
|
|
416
|
+
render: (cursor, selected, _warn, out) => renderScreen2(cursor, selected, out),
|
|
417
|
+
io
|
|
418
|
+
});
|
|
419
|
+
if (!uses) { finalCleanup(); return null; }
|
|
420
|
+
|
|
421
|
+
// Screen 3 — Design (multi-select with exclusive 'none')
|
|
422
|
+
const design = await promptDesignCheckbox({
|
|
423
|
+
items: DESIGNS,
|
|
424
|
+
noneId: 'none',
|
|
425
|
+
defaultSelected: defaultDesign,
|
|
426
|
+
render: (cursor, selected, warn, out) => renderScreen3(cursor, selected, warn, out),
|
|
427
|
+
io
|
|
428
|
+
});
|
|
429
|
+
if (design === null) { finalCleanup(); return null; }
|
|
430
|
+
|
|
431
|
+
// Screen 4 — Locale (single-select / radio)
|
|
432
|
+
const locale = await promptRadio({
|
|
433
|
+
items: LOCALES,
|
|
434
|
+
defaultIndex: defaultLocale,
|
|
435
|
+
render: (cursor, out) => renderScreen4(cursor, out),
|
|
436
|
+
io
|
|
437
|
+
});
|
|
438
|
+
if (locale === null) { finalCleanup(); return null; }
|
|
439
|
+
|
|
440
|
+
// Confirm screen
|
|
441
|
+
const confirmed = await promptConfirmScreen(tools, uses, design, locale, existingProfile, t, io);
|
|
442
|
+
if (!confirmed) { finalCleanup(); return null; }
|
|
443
|
+
|
|
444
|
+
stdout.write('\x1Bc');
|
|
445
|
+
finalCleanup();
|
|
446
|
+
|
|
447
|
+
// Normalize design: empty array → 'none', single 'none' → 'none'
|
|
448
|
+
const normalizedDesign = (design.length === 0 || (design.length === 1 && design[0] === 'none'))
|
|
449
|
+
? 'none'
|
|
450
|
+
: (design.length === 1 && design[0] === 'all')
|
|
451
|
+
? 'all'
|
|
452
|
+
: design;
|
|
453
|
+
|
|
454
|
+
return { tools, uses, design: normalizedDesign, locale };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
module.exports = {
|
|
458
|
+
runInstallWizard,
|
|
459
|
+
__test__: {
|
|
460
|
+
renderScreen1,
|
|
461
|
+
renderScreen2,
|
|
462
|
+
renderScreen3,
|
|
463
|
+
renderScreen4,
|
|
464
|
+
renderConfirm,
|
|
465
|
+
getBanner,
|
|
466
|
+
TOOLS,
|
|
467
|
+
USES,
|
|
468
|
+
DESIGNS,
|
|
469
|
+
LOCALES,
|
|
470
|
+
promptCheckbox,
|
|
471
|
+
promptRadio,
|
|
472
|
+
promptDesignCheckbox
|
|
473
|
+
}
|
|
474
|
+
};
|
package/src/installer.js
CHANGED
|
@@ -6,6 +6,7 @@ const { MANAGED_FILES } = require('./constants');
|
|
|
6
6
|
const { getCliVersion } = require('./version');
|
|
7
7
|
const { exists, ensureDir, copyFileWithDir, nowStamp, toRelativeSafe } = require('./utils');
|
|
8
8
|
const { ensureProjectRuntime } = require('./execution-gateway');
|
|
9
|
+
const { shouldIncludeForProfile } = require('./install-profile');
|
|
9
10
|
|
|
10
11
|
const ROOT_DIR = path.join(__dirname, '..');
|
|
11
12
|
const TEMPLATE_DIR = path.join(ROOT_DIR, 'template');
|
|
@@ -121,17 +122,24 @@ async function listFilesRecursive(dir) {
|
|
|
121
122
|
return out;
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Returns a skip-reason string if the file should be skipped, or false if it should be installed.
|
|
127
|
+
*/
|
|
128
|
+
function shouldSkipTemplatePath(rel, profile = null) {
|
|
126
129
|
if (rel === '.aioson/context/.gitkeep') return false;
|
|
130
|
+
if (rel.startsWith('.aioson/context/')) return 'context-protected';
|
|
127
131
|
// Never overwrite user-installed skills (only the .gitkeep is created)
|
|
128
|
-
if (rel.startsWith('.aioson/installed-skills/') && rel !== '.aioson/installed-skills/.gitkeep') return
|
|
132
|
+
if (rel.startsWith('.aioson/installed-skills/') && rel !== '.aioson/installed-skills/.gitkeep') return 'context-protected';
|
|
129
133
|
// Never overwrite custom agents (only the .gitkeep is created)
|
|
130
|
-
if (rel.startsWith('.aioson/my-agents/') && rel !== '.aioson/my-agents/.gitkeep') return
|
|
134
|
+
if (rel.startsWith('.aioson/my-agents/') && rel !== '.aioson/my-agents/.gitkeep') return 'context-protected';
|
|
135
|
+
|
|
136
|
+
// Profile-based filtering
|
|
137
|
+
if (profile && !shouldIncludeForProfile(rel, profile)) return 'not-in-profile';
|
|
138
|
+
|
|
131
139
|
return false;
|
|
132
140
|
}
|
|
133
141
|
|
|
134
|
-
async function writeInstallMetadata(targetDir, action, frameworkDetection) {
|
|
142
|
+
async function writeInstallMetadata(targetDir, action, frameworkDetection, installProfile) {
|
|
135
143
|
const metaPath = path.join(targetDir, '.aioson/install.json');
|
|
136
144
|
await ensureDir(path.dirname(metaPath));
|
|
137
145
|
const existing = (await exists(metaPath)) ? JSON.parse(await fs.readFile(metaPath, 'utf8')) : {};
|
|
@@ -143,12 +151,24 @@ async function writeInstallMetadata(targetDir, action, frameworkDetection) {
|
|
|
143
151
|
template_version: version,
|
|
144
152
|
last_action: action,
|
|
145
153
|
last_action_at: new Date().toISOString(),
|
|
146
|
-
framework_detected: frameworkDetection || existing.framework_detected || null
|
|
154
|
+
framework_detected: frameworkDetection || existing.framework_detected || null,
|
|
155
|
+
install_profile: installProfile || existing.install_profile || null
|
|
147
156
|
};
|
|
148
157
|
|
|
149
158
|
await fs.writeFile(metaPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
150
159
|
}
|
|
151
160
|
|
|
161
|
+
async function readInstallProfile(targetDir) {
|
|
162
|
+
const metaPath = path.join(targetDir, '.aioson/install.json');
|
|
163
|
+
if (!(await exists(metaPath))) return null;
|
|
164
|
+
try {
|
|
165
|
+
const data = JSON.parse(await fs.readFile(metaPath, 'utf8'));
|
|
166
|
+
return data.install_profile || null;
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
152
172
|
async function backupManagedFile(targetDir, relPath, backupRoot) {
|
|
153
173
|
const source = path.join(targetDir, relPath);
|
|
154
174
|
if (!(await exists(source))) return null;
|
|
@@ -164,7 +184,9 @@ async function installTemplate(targetDir, options = {}) {
|
|
|
164
184
|
dryRun = false,
|
|
165
185
|
mode = 'install',
|
|
166
186
|
backupOnOverwrite = mode === 'update',
|
|
167
|
-
frameworkDetection = null
|
|
187
|
+
frameworkDetection = null,
|
|
188
|
+
installProfile = null,
|
|
189
|
+
onProgress = null
|
|
168
190
|
} = options;
|
|
169
191
|
|
|
170
192
|
await ensureDir(targetDir);
|
|
@@ -183,8 +205,9 @@ async function installTemplate(targetDir, options = {}) {
|
|
|
183
205
|
|
|
184
206
|
for (const absPath of templateFiles) {
|
|
185
207
|
const rel = toRelativeSafe(TEMPLATE_DIR, absPath);
|
|
186
|
-
|
|
187
|
-
|
|
208
|
+
const skipReason = shouldSkipTemplatePath(rel, installProfile);
|
|
209
|
+
if (skipReason) {
|
|
210
|
+
skipped.push({ path: rel, reason: skipReason });
|
|
188
211
|
continue;
|
|
189
212
|
}
|
|
190
213
|
|
|
@@ -215,6 +238,10 @@ async function installTemplate(targetDir, options = {}) {
|
|
|
215
238
|
}
|
|
216
239
|
|
|
217
240
|
copied.push(rel);
|
|
241
|
+
|
|
242
|
+
if (onProgress) {
|
|
243
|
+
onProgress({ copied: copied.length, total: templateFiles.length, file: rel });
|
|
244
|
+
}
|
|
218
245
|
}
|
|
219
246
|
|
|
220
247
|
if (!dryRun) {
|
|
@@ -225,7 +252,7 @@ async function installTemplate(targetDir, options = {}) {
|
|
|
225
252
|
await fs.writeFile(gitkeep, '', 'utf8');
|
|
226
253
|
}
|
|
227
254
|
|
|
228
|
-
await writeInstallMetadata(targetDir, mode, frameworkDetection);
|
|
255
|
+
await writeInstallMetadata(targetDir, mode, frameworkDetection, installProfile);
|
|
229
256
|
|
|
230
257
|
await ensureProjectGitignorePolicy(targetDir);
|
|
231
258
|
|
|
@@ -252,6 +279,7 @@ module.exports = {
|
|
|
252
279
|
TEMPLATE_DIR,
|
|
253
280
|
detectExistingInstall,
|
|
254
281
|
installTemplate,
|
|
282
|
+
readInstallProfile,
|
|
255
283
|
listFilesRecursive,
|
|
256
284
|
ensureGitignoreEntry,
|
|
257
285
|
ensureGitignoreEntries,
|
package/src/parser.js
CHANGED
|
@@ -16,8 +16,14 @@ function parseArgv(argv) {
|
|
|
16
16
|
continue;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Boolean-only flags that never consume the next token
|
|
20
|
+
const boolOnly = new Set([
|
|
21
|
+
'all', 'force', 'dry-run', 'no-interactive', 'fix', 'json',
|
|
22
|
+
'help', 'version', 'no-launch', 'attach'
|
|
23
|
+
]);
|
|
24
|
+
|
|
19
25
|
const next = tokens[i + 1];
|
|
20
|
-
if (next && !next.startsWith('-')) {
|
|
26
|
+
if (next && !next.startsWith('-') && !boolOnly.has(k)) {
|
|
21
27
|
options[k] = next;
|
|
22
28
|
i += 1;
|
|
23
29
|
} else {
|