up-cc 0.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/up-depurador.md +357 -0
- package/agents/up-executor.md +409 -0
- package/agents/up-pesquisador-projeto.md +358 -0
- package/agents/up-planejador.md +390 -0
- package/agents/up-roteirista.md +401 -0
- package/agents/up-sintetizador.md +232 -0
- package/agents/up-verificador.md +357 -0
- package/bin/install.js +709 -0
- package/bin/lib/core.cjs +270 -0
- package/bin/up-tools.cjs +1361 -0
- package/commands/adicionar-fase.md +33 -0
- package/commands/ajuda.md +131 -0
- package/commands/discutir-fase.md +35 -0
- package/commands/executar-fase.md +40 -0
- package/commands/novo-projeto.md +39 -0
- package/commands/pausar.md +33 -0
- package/commands/planejar-fase.md +43 -0
- package/commands/progresso.md +33 -0
- package/commands/rapido.md +40 -0
- package/commands/remover-fase.md +34 -0
- package/commands/retomar.md +35 -0
- package/commands/verificar-trabalho.md +35 -0
- package/package.json +32 -0
- package/references/checkpoints.md +358 -0
- package/references/git-integration.md +208 -0
- package/references/questioning.md +156 -0
- package/references/ui-brand.md +124 -0
- package/templates/config.json +6 -0
- package/templates/continue-here.md +78 -0
- package/templates/project.md +184 -0
- package/templates/requirements.md +129 -0
- package/templates/roadmap.md +131 -0
- package/templates/state.md +130 -0
- package/templates/summary.md +174 -0
- package/workflows/discutir-fase.md +324 -0
- package/workflows/executar-fase.md +277 -0
- package/workflows/executar-plano.md +192 -0
- package/workflows/novo-projeto.md +561 -0
- package/workflows/pausar.md +111 -0
- package/workflows/planejar-fase.md +208 -0
- package/workflows/progresso.md +226 -0
- package/workflows/rapido.md +209 -0
- package/workflows/retomar.md +231 -0
- package/workflows/verificar-trabalho.md +261 -0
package/bin/install.js
ADDED
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* UP Installer — installs UP system into CLI tool config directories
|
|
5
|
+
*
|
|
6
|
+
* Supports: Claude Code, Gemini CLI, OpenCode, Codex
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node install.js # Interactive install
|
|
10
|
+
* node install.js --claude --global # Install for Claude Code globally
|
|
11
|
+
* node install.js --gemini --global # Install for Gemini globally
|
|
12
|
+
* node install.js --all --global # Install for all runtimes
|
|
13
|
+
* node install.js --uninstall # Remove UP files
|
|
14
|
+
*
|
|
15
|
+
* What gets installed:
|
|
16
|
+
* up/ → <config>/up/ (CLI, workflows, templates, references)
|
|
17
|
+
* agents/up-* → <config>/agents/ (UP agents)
|
|
18
|
+
* commands/up/ → <config>/commands/up/ (UP slash commands)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const os = require('os');
|
|
24
|
+
const readline = require('readline');
|
|
25
|
+
|
|
26
|
+
// Colors
|
|
27
|
+
const cyan = '\x1b[36m';
|
|
28
|
+
const green = '\x1b[32m';
|
|
29
|
+
const yellow = '\x1b[33m';
|
|
30
|
+
const red = '\x1b[31m';
|
|
31
|
+
const dim = '\x1b[2m';
|
|
32
|
+
const bold = '\x1b[1m';
|
|
33
|
+
const reset = '\x1b[0m';
|
|
34
|
+
|
|
35
|
+
// Version from package.json
|
|
36
|
+
const pkg = require('../package.json');
|
|
37
|
+
const VERSION = pkg.version;
|
|
38
|
+
|
|
39
|
+
// Parse args
|
|
40
|
+
const args = process.argv.slice(2);
|
|
41
|
+
const hasGlobal = args.includes('--global') || args.includes('-g');
|
|
42
|
+
const hasLocal = args.includes('--local') || args.includes('-l');
|
|
43
|
+
const hasClaude = args.includes('--claude');
|
|
44
|
+
const hasGemini = args.includes('--gemini');
|
|
45
|
+
const hasOpencode = args.includes('--opencode');
|
|
46
|
+
const hasAll = args.includes('--all');
|
|
47
|
+
const hasUninstall = args.includes('--uninstall') || args.includes('-u');
|
|
48
|
+
const hasHelp = args.includes('--help') || args.includes('-h');
|
|
49
|
+
|
|
50
|
+
// Runtime selection
|
|
51
|
+
let selectedRuntimes = [];
|
|
52
|
+
if (hasAll) {
|
|
53
|
+
selectedRuntimes = ['claude', 'gemini', 'opencode'];
|
|
54
|
+
} else {
|
|
55
|
+
if (hasClaude) selectedRuntimes.push('claude');
|
|
56
|
+
if (hasGemini) selectedRuntimes.push('gemini');
|
|
57
|
+
if (hasOpencode) selectedRuntimes.push('opencode');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const banner = '\n' +
|
|
61
|
+
cyan + ' ██╗ ██╗██████╗\n' +
|
|
62
|
+
' ██║ ██║██╔══██╗\n' +
|
|
63
|
+
' ██║ ██║██████╔╝\n' +
|
|
64
|
+
' ██║ ██║██╔═══╝\n' +
|
|
65
|
+
' ╚██████╔╝██║\n' +
|
|
66
|
+
' ╚═════╝ ╚═╝' + reset + '\n\n' +
|
|
67
|
+
' UP ' + dim + 'v' + VERSION + reset + '\n' +
|
|
68
|
+
' Simplified spec-driven development for Claude Code, Gemini and OpenCode.\n';
|
|
69
|
+
|
|
70
|
+
console.log(banner);
|
|
71
|
+
|
|
72
|
+
if (hasHelp) {
|
|
73
|
+
console.log(` ${yellow}Usage:${reset} node install.js [options]\n`);
|
|
74
|
+
console.log(` ${yellow}Options:${reset}`);
|
|
75
|
+
console.log(` ${cyan}-g, --global${reset} Install globally (to config directory)`);
|
|
76
|
+
console.log(` ${cyan}-l, --local${reset} Install locally (to current directory)`);
|
|
77
|
+
console.log(` ${cyan}--claude${reset} Install for Claude Code`);
|
|
78
|
+
console.log(` ${cyan}--gemini${reset} Install for Gemini CLI`);
|
|
79
|
+
console.log(` ${cyan}--opencode${reset} Install for OpenCode`);
|
|
80
|
+
console.log(` ${cyan}--all${reset} Install for all runtimes`);
|
|
81
|
+
console.log(` ${cyan}-u, --uninstall${reset} Remove all UP files`);
|
|
82
|
+
console.log(` ${cyan}-h, --help${reset} Show this help\n`);
|
|
83
|
+
console.log(` ${yellow}Examples:${reset}`);
|
|
84
|
+
console.log(` ${dim}# Interactive install${reset}`);
|
|
85
|
+
console.log(` node install.js\n`);
|
|
86
|
+
console.log(` ${dim}# Install for Claude Code globally${reset}`);
|
|
87
|
+
console.log(` node install.js --claude --global\n`);
|
|
88
|
+
console.log(` ${dim}# Install for Gemini globally${reset}`);
|
|
89
|
+
console.log(` node install.js --gemini --global\n`);
|
|
90
|
+
console.log(` ${dim}# Install for all runtimes${reset}`);
|
|
91
|
+
console.log(` node install.js --all --global\n`);
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Determine source directory (package root = one level up from bin/)
|
|
96
|
+
const scriptDir = __dirname;
|
|
97
|
+
const packageRoot = path.resolve(scriptDir, '..');
|
|
98
|
+
|
|
99
|
+
// ── Runtime Helpers ──
|
|
100
|
+
|
|
101
|
+
function getDirName(runtime) {
|
|
102
|
+
if (runtime === 'opencode') return '.opencode';
|
|
103
|
+
if (runtime === 'gemini') return '.gemini';
|
|
104
|
+
return '.claude';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getRuntimeLabel(runtime) {
|
|
108
|
+
if (runtime === 'opencode') return 'OpenCode';
|
|
109
|
+
if (runtime === 'gemini') return 'Gemini';
|
|
110
|
+
return 'Claude Code';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getGlobalDir(runtime) {
|
|
114
|
+
if (runtime === 'opencode') {
|
|
115
|
+
if (process.env.OPENCODE_CONFIG_DIR) return process.env.OPENCODE_CONFIG_DIR;
|
|
116
|
+
if (process.env.XDG_CONFIG_HOME) return path.join(process.env.XDG_CONFIG_HOME, 'opencode');
|
|
117
|
+
return path.join(os.homedir(), '.config', 'opencode');
|
|
118
|
+
}
|
|
119
|
+
if (runtime === 'gemini') {
|
|
120
|
+
if (process.env.GEMINI_CONFIG_DIR) return process.env.GEMINI_CONFIG_DIR;
|
|
121
|
+
return path.join(os.homedir(), '.gemini');
|
|
122
|
+
}
|
|
123
|
+
// Claude Code
|
|
124
|
+
if (process.env.CLAUDE_CONFIG_DIR) return process.env.CLAUDE_CONFIG_DIR;
|
|
125
|
+
return path.join(os.homedir(), '.claude');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getTargetDir(runtime, isGlobal) {
|
|
129
|
+
if (isGlobal) return getGlobalDir(runtime);
|
|
130
|
+
return path.join(process.cwd(), getDirName(runtime));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Convert absolute path to $HOME-relative form for portable path replacement
|
|
135
|
+
*/
|
|
136
|
+
function toHomePrefix(pathPrefix) {
|
|
137
|
+
const home = os.homedir().replace(/\\/g, '/');
|
|
138
|
+
const normalized = pathPrefix.replace(/\\/g, '/');
|
|
139
|
+
if (normalized.startsWith(home)) {
|
|
140
|
+
return '$HOME' + normalized.slice(home.length);
|
|
141
|
+
}
|
|
142
|
+
return normalized;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Tool Name Conversion ──
|
|
146
|
+
|
|
147
|
+
// Claude Code → Gemini CLI tool name mapping
|
|
148
|
+
const claudeToGeminiTools = {
|
|
149
|
+
Read: 'read_file',
|
|
150
|
+
Write: 'write_file',
|
|
151
|
+
Edit: 'replace',
|
|
152
|
+
Bash: 'run_shell_command',
|
|
153
|
+
Glob: 'glob',
|
|
154
|
+
Grep: 'search_file_content',
|
|
155
|
+
WebSearch: 'google_web_search',
|
|
156
|
+
WebFetch: 'web_fetch',
|
|
157
|
+
TodoWrite: 'write_todos',
|
|
158
|
+
AskUserQuestion: 'ask_user',
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Claude Code → OpenCode tool name mapping
|
|
162
|
+
const claudeToOpencodeTools = {
|
|
163
|
+
AskUserQuestion: 'question',
|
|
164
|
+
SlashCommand: 'skill',
|
|
165
|
+
TodoWrite: 'todowrite',
|
|
166
|
+
WebFetch: 'webfetch',
|
|
167
|
+
WebSearch: 'websearch',
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
function convertGeminiToolName(claudeTool) {
|
|
171
|
+
if (claudeTool.startsWith('mcp__')) return null; // Auto-discovered in Gemini
|
|
172
|
+
if (claudeToGeminiTools[claudeTool]) return claudeToGeminiTools[claudeTool];
|
|
173
|
+
return claudeTool.toLowerCase();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function convertOpencodeToolName(claudeTool) {
|
|
177
|
+
if (claudeToOpencodeTools[claudeTool]) return claudeToOpencodeTools[claudeTool];
|
|
178
|
+
if (claudeTool.startsWith('mcp__')) return claudeTool;
|
|
179
|
+
return claudeTool.toLowerCase();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Frontmatter Conversion ──
|
|
183
|
+
|
|
184
|
+
function extractFrontmatterAndBody(content) {
|
|
185
|
+
if (!content.startsWith('---')) return { frontmatter: null, body: content };
|
|
186
|
+
const endIndex = content.indexOf('---', 3);
|
|
187
|
+
if (endIndex === -1) return { frontmatter: null, body: content };
|
|
188
|
+
return {
|
|
189
|
+
frontmatter: content.substring(3, endIndex).trim(),
|
|
190
|
+
body: content.substring(endIndex + 3),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function extractFrontmatterField(frontmatter, fieldName) {
|
|
195
|
+
const regex = new RegExp(`^${fieldName}:\\s*(.+)$`, 'm');
|
|
196
|
+
const match = frontmatter.match(regex);
|
|
197
|
+
if (!match) return null;
|
|
198
|
+
return match[1].trim().replace(/^['"]|['"]$/g, '');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Convert Claude Code agent .md to Gemini CLI format
|
|
203
|
+
* - tools: must be a YAML array with Gemini tool names
|
|
204
|
+
* - color: must be removed (causes validation error)
|
|
205
|
+
* - ${VAR} patterns escaped to $VAR (Gemini template engine conflict)
|
|
206
|
+
*/
|
|
207
|
+
function convertAgentToGemini(content) {
|
|
208
|
+
const { frontmatter, body } = extractFrontmatterAndBody(content);
|
|
209
|
+
if (!frontmatter) return content;
|
|
210
|
+
|
|
211
|
+
const lines = frontmatter.split('\n');
|
|
212
|
+
const newLines = [];
|
|
213
|
+
const tools = [];
|
|
214
|
+
let inTools = false;
|
|
215
|
+
|
|
216
|
+
for (const line of lines) {
|
|
217
|
+
const trimmed = line.trim();
|
|
218
|
+
|
|
219
|
+
if (trimmed.startsWith('tools:')) {
|
|
220
|
+
const toolsValue = trimmed.substring(6).trim();
|
|
221
|
+
if (toolsValue) {
|
|
222
|
+
const parsed = toolsValue.split(',').map(t => t.trim()).filter(t => t);
|
|
223
|
+
for (const t of parsed) {
|
|
224
|
+
const mapped = convertGeminiToolName(t);
|
|
225
|
+
if (mapped) tools.push(mapped);
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
inTools = true;
|
|
229
|
+
}
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (trimmed.startsWith('color:')) continue; // Not supported by Gemini
|
|
234
|
+
|
|
235
|
+
if (inTools) {
|
|
236
|
+
if (trimmed.startsWith('- ')) {
|
|
237
|
+
const mapped = convertGeminiToolName(trimmed.substring(2).trim());
|
|
238
|
+
if (mapped) tools.push(mapped);
|
|
239
|
+
continue;
|
|
240
|
+
} else if (trimmed && !trimmed.startsWith('-')) {
|
|
241
|
+
inTools = false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!inTools) newLines.push(line);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (tools.length > 0) {
|
|
249
|
+
newLines.push('tools:');
|
|
250
|
+
for (const tool of tools) {
|
|
251
|
+
newLines.push(` - ${tool}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Escape ${VAR} to $VAR (Gemini templateString() conflicts)
|
|
256
|
+
const escapedBody = body.replace(/\$\{(\w+)\}/g, '$$$1');
|
|
257
|
+
// Strip <sub> tags (terminals can't render subscript)
|
|
258
|
+
const cleanBody = escapedBody.replace(/<sub>(.*?)<\/sub>/g, '*($1)*');
|
|
259
|
+
|
|
260
|
+
return `---\n${newLines.join('\n').trim()}\n---${cleanBody}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Convert Claude Code agent .md to OpenCode format
|
|
265
|
+
* - tools: must be object with tool: true pairs
|
|
266
|
+
* - color: must be hex
|
|
267
|
+
* - name: removed (OpenCode uses filename)
|
|
268
|
+
*/
|
|
269
|
+
const colorNameToHex = {
|
|
270
|
+
cyan: '#00FFFF', red: '#FF0000', green: '#00FF00', blue: '#0000FF',
|
|
271
|
+
yellow: '#FFFF00', magenta: '#FF00FF', orange: '#FFA500', purple: '#800080',
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
function convertAgentToOpencode(content) {
|
|
275
|
+
// Replace tool references in content
|
|
276
|
+
let converted = content;
|
|
277
|
+
converted = converted.replace(/\bAskUserQuestion\b/g, 'question');
|
|
278
|
+
converted = converted.replace(/\bSlashCommand\b/g, 'skill');
|
|
279
|
+
converted = converted.replace(/\bTodoWrite\b/g, 'todowrite');
|
|
280
|
+
converted = converted.replace(/\/up:/g, '/up-'); // OpenCode flat command structure
|
|
281
|
+
|
|
282
|
+
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
283
|
+
if (!frontmatter) return converted;
|
|
284
|
+
|
|
285
|
+
const lines = frontmatter.split('\n');
|
|
286
|
+
const newLines = [];
|
|
287
|
+
const tools = [];
|
|
288
|
+
let inTools = false;
|
|
289
|
+
|
|
290
|
+
for (const line of lines) {
|
|
291
|
+
const trimmed = line.trim();
|
|
292
|
+
|
|
293
|
+
if (trimmed.startsWith('name:')) continue; // OpenCode uses filename
|
|
294
|
+
if (trimmed.startsWith('tools:')) {
|
|
295
|
+
const toolsValue = trimmed.substring(6).trim();
|
|
296
|
+
if (toolsValue) {
|
|
297
|
+
tools.push(...toolsValue.split(',').map(t => t.trim()).filter(t => t));
|
|
298
|
+
} else {
|
|
299
|
+
inTools = true;
|
|
300
|
+
}
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (trimmed.startsWith('color:')) {
|
|
304
|
+
const colorValue = trimmed.substring(6).trim().toLowerCase();
|
|
305
|
+
const hex = colorNameToHex[colorValue];
|
|
306
|
+
if (hex) newLines.push(`color: "${hex}"`);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (inTools) {
|
|
310
|
+
if (trimmed.startsWith('- ')) {
|
|
311
|
+
tools.push(trimmed.substring(2).trim());
|
|
312
|
+
continue;
|
|
313
|
+
} else if (trimmed && !trimmed.startsWith('-')) {
|
|
314
|
+
inTools = false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (!inTools) newLines.push(line);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (tools.length > 0) {
|
|
321
|
+
newLines.push('tools:');
|
|
322
|
+
for (const tool of tools) {
|
|
323
|
+
newLines.push(` ${convertOpencodeToolName(tool)}: true`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return `---\n${newLines.join('\n').trim()}\n---${body}`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Convert Claude Code command .md to Gemini TOML format
|
|
332
|
+
*/
|
|
333
|
+
function convertCommandToGeminiToml(content) {
|
|
334
|
+
const { frontmatter, body } = extractFrontmatterAndBody(content);
|
|
335
|
+
let description = '';
|
|
336
|
+
if (frontmatter) {
|
|
337
|
+
description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
338
|
+
}
|
|
339
|
+
let toml = '';
|
|
340
|
+
if (description) toml += `description = ${JSON.stringify(description)}\n`;
|
|
341
|
+
toml += `prompt = ${JSON.stringify(body.trim())}\n`;
|
|
342
|
+
return toml;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ── File Copy ──
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Build path prefix string for $HOME-relative replacement
|
|
349
|
+
*/
|
|
350
|
+
function buildPathPrefix(targetDir, isGlobal, runtime) {
|
|
351
|
+
if (isGlobal) return `${targetDir.replace(/\\/g, '/')}/`;
|
|
352
|
+
return `./${getDirName(runtime)}/`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Replace path references in content for target runtime
|
|
357
|
+
*/
|
|
358
|
+
function replacePaths(content, pathPrefix, runtime) {
|
|
359
|
+
const homePrefix = toHomePrefix(pathPrefix);
|
|
360
|
+
content = content.replace(/\$HOME\/\.claude\//g, homePrefix);
|
|
361
|
+
content = content.replace(/~\/\.claude\//g, homePrefix);
|
|
362
|
+
|
|
363
|
+
if (runtime === 'opencode') {
|
|
364
|
+
content = content.replace(/\/up:/g, '/up-');
|
|
365
|
+
content = content.replace(/subagent_type="general-purpose"/g, 'subagent_type="general"');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return content;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Recursively copy directory with path replacement and optional runtime conversion
|
|
373
|
+
*/
|
|
374
|
+
function copyDirWithReplace(src, dest, pathPrefix, runtime, isCommand = false) {
|
|
375
|
+
if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true });
|
|
376
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
377
|
+
|
|
378
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
379
|
+
for (const entry of entries) {
|
|
380
|
+
const srcPath = path.join(src, entry.name);
|
|
381
|
+
const destPath = path.join(dest, entry.name);
|
|
382
|
+
|
|
383
|
+
if (entry.isDirectory()) {
|
|
384
|
+
copyDirWithReplace(srcPath, destPath, pathPrefix, runtime, isCommand);
|
|
385
|
+
} else if (entry.name.endsWith('.md') || entry.name.endsWith('.cjs') || entry.name.endsWith('.js') || entry.name.endsWith('.json')) {
|
|
386
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
387
|
+
content = replacePaths(content, pathPrefix, runtime);
|
|
388
|
+
|
|
389
|
+
// Convert commands to TOML for Gemini
|
|
390
|
+
if (runtime === 'gemini' && isCommand && entry.name.endsWith('.md')) {
|
|
391
|
+
const toml = convertCommandToGeminiToml(content);
|
|
392
|
+
fs.writeFileSync(destPath.replace(/\.md$/, '.toml'), toml);
|
|
393
|
+
} else {
|
|
394
|
+
fs.writeFileSync(destPath, content);
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
fs.copyFileSync(srcPath, destPath);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Copy commands flattened for OpenCode (commands/up/help.md → command/up-help.md)
|
|
404
|
+
*/
|
|
405
|
+
function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
|
|
406
|
+
if (!fs.existsSync(srcDir)) return;
|
|
407
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
408
|
+
|
|
409
|
+
// Remove old up-*.md files
|
|
410
|
+
if (fs.existsSync(destDir)) {
|
|
411
|
+
for (const file of fs.readdirSync(destDir)) {
|
|
412
|
+
if (file.startsWith(`${prefix}-`) && file.endsWith('.md')) {
|
|
413
|
+
fs.unlinkSync(path.join(destDir, file));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
419
|
+
for (const entry of entries) {
|
|
420
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
421
|
+
if (entry.isDirectory()) {
|
|
422
|
+
copyFlattenedCommands(srcPath, destDir, `${prefix}-${entry.name}`, pathPrefix, runtime);
|
|
423
|
+
} else if (entry.name.endsWith('.md')) {
|
|
424
|
+
const baseName = entry.name.replace('.md', '');
|
|
425
|
+
const destPath = path.join(destDir, `${prefix}-${baseName}.md`);
|
|
426
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
427
|
+
content = replacePaths(content, pathPrefix, runtime);
|
|
428
|
+
content = convertAgentToOpencode(content);
|
|
429
|
+
fs.writeFileSync(destPath, content);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ── Utility ──
|
|
435
|
+
|
|
436
|
+
function rmDir(dirPath) {
|
|
437
|
+
if (fs.existsSync(dirPath)) fs.rmSync(dirPath, { recursive: true, force: true });
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function countFiles(dirPath) {
|
|
441
|
+
if (!fs.existsSync(dirPath)) return 0;
|
|
442
|
+
let count = 0;
|
|
443
|
+
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
|
444
|
+
count += entry.isDirectory() ? countFiles(path.join(dirPath, entry.name)) : 1;
|
|
445
|
+
}
|
|
446
|
+
return count;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// ── UNINSTALL ──
|
|
450
|
+
|
|
451
|
+
function uninstall(targetDir, runtime) {
|
|
452
|
+
const label = getRuntimeLabel(runtime);
|
|
453
|
+
console.log(` ${yellow}Uninstalling UP from ${label} at ${targetDir}${reset}\n`);
|
|
454
|
+
|
|
455
|
+
if (!fs.existsSync(targetDir)) {
|
|
456
|
+
console.log(` ${dim}Directory does not exist. Nothing to uninstall.${reset}\n`);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
let removed = 0;
|
|
461
|
+
|
|
462
|
+
// Remove up/ directory
|
|
463
|
+
const upDir = path.join(targetDir, 'up');
|
|
464
|
+
if (fs.existsSync(upDir)) {
|
|
465
|
+
const count = countFiles(upDir);
|
|
466
|
+
rmDir(upDir);
|
|
467
|
+
console.log(` ${green}✓${reset} Removed up/ (${count} files)`);
|
|
468
|
+
removed += count;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Remove UP agents
|
|
472
|
+
const agentsDir = path.join(targetDir, 'agents');
|
|
473
|
+
if (fs.existsSync(agentsDir)) {
|
|
474
|
+
for (const file of fs.readdirSync(agentsDir)) {
|
|
475
|
+
if (file.startsWith('up-') && file.endsWith('.md')) {
|
|
476
|
+
fs.unlinkSync(path.join(agentsDir, file));
|
|
477
|
+
removed++;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (removed > 0) console.log(` ${green}✓${reset} Removed UP agents`);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Remove UP commands (runtime-specific structure)
|
|
484
|
+
if (runtime === 'opencode') {
|
|
485
|
+
const commandDir = path.join(targetDir, 'command');
|
|
486
|
+
if (fs.existsSync(commandDir)) {
|
|
487
|
+
let cmdCount = 0;
|
|
488
|
+
for (const file of fs.readdirSync(commandDir)) {
|
|
489
|
+
if (file.startsWith('up-') && file.endsWith('.md')) {
|
|
490
|
+
fs.unlinkSync(path.join(commandDir, file));
|
|
491
|
+
cmdCount++;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (cmdCount > 0) {
|
|
495
|
+
console.log(` ${green}✓${reset} Removed ${cmdCount} commands from command/`);
|
|
496
|
+
removed += cmdCount;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
} else {
|
|
500
|
+
// Claude & Gemini: nested commands/up/ or commands/up/*.toml
|
|
501
|
+
const commandsDir = path.join(targetDir, 'commands', 'up');
|
|
502
|
+
if (fs.existsSync(commandsDir)) {
|
|
503
|
+
const count = countFiles(commandsDir);
|
|
504
|
+
rmDir(commandsDir);
|
|
505
|
+
console.log(` ${green}✓${reset} Removed commands/up/ (${count} files)`);
|
|
506
|
+
removed += count;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (removed === 0) {
|
|
511
|
+
console.log(` ${dim}Nothing to remove — UP is not installed here.${reset}`);
|
|
512
|
+
} else {
|
|
513
|
+
console.log(`\n ${green}Done!${reset} Removed ${removed} files from ${label}.\n`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// ── INSTALL ──
|
|
518
|
+
|
|
519
|
+
function install(isGlobal, runtime) {
|
|
520
|
+
const targetDir = getTargetDir(runtime, isGlobal);
|
|
521
|
+
const pathPrefix = buildPathPrefix(targetDir, isGlobal, runtime);
|
|
522
|
+
const label = getRuntimeLabel(runtime);
|
|
523
|
+
const locationLabel = isGlobal ? targetDir.replace(os.homedir(), '~') : targetDir.replace(process.cwd(), '.');
|
|
524
|
+
const failures = [];
|
|
525
|
+
|
|
526
|
+
console.log(` Installing for ${cyan}${label}${reset} to ${cyan}${locationLabel}${reset}\n`);
|
|
527
|
+
|
|
528
|
+
// 1. Copy up/ (the entire package becomes config/up/)
|
|
529
|
+
const upSrc = packageRoot;
|
|
530
|
+
const upDest = path.join(targetDir, 'up');
|
|
531
|
+
copyDirWithReplace(upSrc, upDest, pathPrefix, runtime);
|
|
532
|
+
const upCount = countFiles(upDest);
|
|
533
|
+
if (upCount > 0) {
|
|
534
|
+
console.log(` ${green}✓${reset} Installed up/ (${upCount} files)`);
|
|
535
|
+
} else {
|
|
536
|
+
failures.push('up/');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// 2. Copy commands (runtime-specific structure)
|
|
540
|
+
const cmdsSrc = path.join(packageRoot, 'commands');
|
|
541
|
+
if (fs.existsSync(cmdsSrc)) {
|
|
542
|
+
if (runtime === 'opencode') {
|
|
543
|
+
// OpenCode: flat command/up-*.md
|
|
544
|
+
const commandDir = path.join(targetDir, 'command');
|
|
545
|
+
fs.mkdirSync(commandDir, { recursive: true });
|
|
546
|
+
copyFlattenedCommands(cmdsSrc, commandDir, 'up', pathPrefix, runtime);
|
|
547
|
+
const count = fs.readdirSync(commandDir).filter(f => f.startsWith('up-')).length;
|
|
548
|
+
if (count > 0) {
|
|
549
|
+
console.log(` ${green}✓${reset} Installed ${count} commands to command/`);
|
|
550
|
+
} else {
|
|
551
|
+
failures.push('commands');
|
|
552
|
+
}
|
|
553
|
+
} else {
|
|
554
|
+
// Claude & Gemini: nested commands/up/
|
|
555
|
+
const cmdsDest = path.join(targetDir, 'commands', 'up');
|
|
556
|
+
copyDirWithReplace(cmdsSrc, cmdsDest, pathPrefix, runtime, true);
|
|
557
|
+
const cmdCount = countFiles(cmdsDest);
|
|
558
|
+
if (cmdCount > 0) {
|
|
559
|
+
console.log(` ${green}✓${reset} Installed ${cmdCount} commands`);
|
|
560
|
+
} else {
|
|
561
|
+
failures.push('commands');
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// 3. Copy agents (with runtime conversion)
|
|
567
|
+
const agentsSrc = path.join(packageRoot, 'agents');
|
|
568
|
+
if (fs.existsSync(agentsSrc)) {
|
|
569
|
+
const agentsDest = path.join(targetDir, 'agents');
|
|
570
|
+
fs.mkdirSync(agentsDest, { recursive: true });
|
|
571
|
+
|
|
572
|
+
// Remove old UP agents
|
|
573
|
+
for (const file of fs.readdirSync(agentsDest)) {
|
|
574
|
+
if (file.startsWith('up-') && file.endsWith('.md')) {
|
|
575
|
+
fs.unlinkSync(path.join(agentsDest, file));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Copy UP agents with runtime conversion
|
|
580
|
+
let agentCount = 0;
|
|
581
|
+
for (const file of fs.readdirSync(agentsSrc)) {
|
|
582
|
+
if (file.endsWith('.md')) {
|
|
583
|
+
let content = fs.readFileSync(path.join(agentsSrc, file), 'utf8');
|
|
584
|
+
content = replacePaths(content, pathPrefix, runtime);
|
|
585
|
+
|
|
586
|
+
if (runtime === 'gemini') {
|
|
587
|
+
content = convertAgentToGemini(content);
|
|
588
|
+
} else if (runtime === 'opencode') {
|
|
589
|
+
content = convertAgentToOpencode(content);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
fs.writeFileSync(path.join(agentsDest, file), content);
|
|
593
|
+
agentCount++;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (agentCount > 0) {
|
|
597
|
+
console.log(` ${green}✓${reset} Installed ${agentCount} agents`);
|
|
598
|
+
} else {
|
|
599
|
+
failures.push('agents');
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// 4. Write VERSION file
|
|
604
|
+
const versionDest = path.join(upDest, 'VERSION');
|
|
605
|
+
fs.writeFileSync(versionDest, VERSION);
|
|
606
|
+
console.log(` ${green}✓${reset} Wrote VERSION (${VERSION})`);
|
|
607
|
+
|
|
608
|
+
// 5. Write package.json for CommonJS mode (prevents ESM conflicts)
|
|
609
|
+
if (runtime !== 'opencode') {
|
|
610
|
+
const pkgDest = path.join(targetDir, 'package.json');
|
|
611
|
+
if (!fs.existsSync(pkgDest)) {
|
|
612
|
+
fs.writeFileSync(pkgDest, '{"type":"commonjs"}\n');
|
|
613
|
+
console.log(` ${green}✓${reset} Wrote package.json (CommonJS mode)`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Summary
|
|
618
|
+
if (failures.length > 0) {
|
|
619
|
+
console.log(`\n ${red}Failed:${reset} ${failures.join(', ')}`);
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const command = runtime === 'opencode' ? '/up-ajuda' : '/up:ajuda';
|
|
624
|
+
console.log(`\n ${green}Done!${reset} Run ${cyan}${command}${reset} in ${label} to get started.\n`);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// ── INTERACTIVE MODE ──
|
|
628
|
+
|
|
629
|
+
function promptRuntime(callback) {
|
|
630
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
631
|
+
let answered = false;
|
|
632
|
+
rl.on('close', () => {
|
|
633
|
+
if (!answered) { answered = true; process.exit(0); }
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
console.log(` ${yellow}Which runtime(s)?${reset}\n`);
|
|
637
|
+
console.log(` ${cyan}1${reset}) Claude Code ${dim}(~/.claude)${reset}`);
|
|
638
|
+
console.log(` ${cyan}2${reset}) Gemini ${dim}(~/.gemini)${reset}`);
|
|
639
|
+
console.log(` ${cyan}3${reset}) OpenCode ${dim}(~/.config/opencode)${reset}`);
|
|
640
|
+
console.log(` ${cyan}4${reset}) All\n`);
|
|
641
|
+
|
|
642
|
+
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
643
|
+
answered = true;
|
|
644
|
+
rl.close();
|
|
645
|
+
const choice = answer.trim() || '1';
|
|
646
|
+
if (choice === '4') callback(['claude', 'gemini', 'opencode']);
|
|
647
|
+
else if (choice === '3') callback(['opencode']);
|
|
648
|
+
else if (choice === '2') callback(['gemini']);
|
|
649
|
+
else callback(['claude']);
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function promptLocation(runtimes) {
|
|
654
|
+
if (!process.stdin.isTTY) {
|
|
655
|
+
console.log(` ${yellow}Non-interactive terminal, defaulting to global install${reset}\n`);
|
|
656
|
+
for (const r of runtimes) install(true, r);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
661
|
+
let answered = false;
|
|
662
|
+
rl.on('close', () => {
|
|
663
|
+
if (!answered) { answered = true; process.exit(0); }
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
const globalPaths = runtimes.map(r => getGlobalDir(r).replace(os.homedir(), '~')).join(', ');
|
|
667
|
+
const localPaths = runtimes.map(r => `./${getDirName(r)}`).join(', ');
|
|
668
|
+
|
|
669
|
+
console.log(` ${yellow}Where to install?${reset}\n`);
|
|
670
|
+
console.log(` ${cyan}1${reset}) Global ${dim}(${globalPaths})${reset}`);
|
|
671
|
+
console.log(` ${cyan}2${reset}) Local ${dim}(${localPaths})${reset}\n`);
|
|
672
|
+
|
|
673
|
+
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
674
|
+
answered = true;
|
|
675
|
+
rl.close();
|
|
676
|
+
const isGlobal = answer.trim() !== '2';
|
|
677
|
+
for (const r of runtimes) install(isGlobal, r);
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// ── MAIN ──
|
|
682
|
+
|
|
683
|
+
if (hasGlobal && hasLocal) {
|
|
684
|
+
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|
|
685
|
+
process.exit(1);
|
|
686
|
+
} else if (hasUninstall) {
|
|
687
|
+
if (!hasGlobal && !hasLocal) {
|
|
688
|
+
console.error(` ${yellow}--uninstall requires --global or --local${reset}`);
|
|
689
|
+
process.exit(1);
|
|
690
|
+
}
|
|
691
|
+
const runtimes = selectedRuntimes.length > 0 ? selectedRuntimes : ['claude'];
|
|
692
|
+
for (const r of runtimes) uninstall(getTargetDir(r, hasGlobal), r);
|
|
693
|
+
} else if (selectedRuntimes.length > 0) {
|
|
694
|
+
if (!hasGlobal && !hasLocal) {
|
|
695
|
+
promptLocation(selectedRuntimes);
|
|
696
|
+
} else {
|
|
697
|
+
for (const r of selectedRuntimes) install(hasGlobal, r);
|
|
698
|
+
}
|
|
699
|
+
} else if (hasGlobal || hasLocal) {
|
|
700
|
+
install(hasGlobal, 'claude');
|
|
701
|
+
} else {
|
|
702
|
+
// Fully interactive
|
|
703
|
+
if (!process.stdin.isTTY) {
|
|
704
|
+
console.log(` ${yellow}Non-interactive terminal, defaulting to Claude Code global${reset}\n`);
|
|
705
|
+
install(true, 'claude');
|
|
706
|
+
} else {
|
|
707
|
+
promptRuntime((runtimes) => promptLocation(runtimes));
|
|
708
|
+
}
|
|
709
|
+
}
|