jdi-cli 0.1.3 → 0.1.4

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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # JDI — Just Do It
2
2
 
3
+ ```
4
+ ██╗██████╗ ██╗
5
+ ██║██╔══██╗██║
6
+ ██║██║ ██║██║
7
+ ██ ██║██║ ██║██║
8
+ ╚█████╔╝██████╔╝██║
9
+ ╚════╝ ╚═════╝ ╚═╝
10
+
11
+ ◄══════════════════════════════════════════════|=|◉|=|/////|==
12
+ ◄══════════════════════════════════════════════|=|◉|=|/////|==
13
+ ◄══════════════════════════════════════════════|=|◉|=|/////|==
14
+
15
+ Cut through the chaos. Ship the work. [Just do it]
16
+ ```
17
+
3
18
  Lean workflow toolkit for solo dev + AI assistant. Adaptive loop, atomic commits, file-based state, fresh context per agent, wave-based parallelism. Per-project specialists that already know your stack.
4
19
 
5
20
  ## Why
package/bin/jdi.js CHANGED
@@ -116,14 +116,14 @@ function ensureScope(scope) {
116
116
  // Commands
117
117
  // =================================================================
118
118
 
119
- function cmdInstall({ positional, flags }) {
119
+ async function cmdInstall({ positional, flags }) {
120
120
  const runtime = positional[0];
121
121
  const scope = flags.scope || 'project';
122
122
 
123
123
  ensureRuntime(runtime);
124
124
  ensureScope(scope);
125
125
 
126
- ui.banner();
126
+ await ui.bannerAnimated();
127
127
 
128
128
  ui.header(`Instalando JDI para ${c.bold}${runtime}${c.reset}`);
129
129
  ui.info(`Diretorio: ${c.dim}${process.cwd()}${c.reset}`);
@@ -163,8 +163,8 @@ function cmdInstall({ positional, flags }) {
163
163
  }
164
164
  }
165
165
 
166
- function cmdBuild({ flags }) {
167
- ui.banner();
166
+ async function cmdBuild({ flags }) {
167
+ await ui.bannerAnimated();
168
168
 
169
169
  ui.header('Building JDI runtimes');
170
170
  ui.info(`Source: ${c.dim}${PKG_ROOT}/core/${c.reset}`);
@@ -188,8 +188,8 @@ function cmdBuild({ flags }) {
188
188
  }
189
189
  }
190
190
 
191
- function cmdUpdate({ flags }) {
192
- ui.banner();
191
+ async function cmdUpdate({ flags }) {
192
+ await ui.bannerAnimated();
193
193
 
194
194
  ui.header('JDI Update');
195
195
  ui.info(`Diretorio: ${c.dim}${process.cwd()}${c.reset}`);
@@ -213,8 +213,8 @@ function cmdUpdate({ flags }) {
213
213
  }
214
214
  }
215
215
 
216
- function cmdUninstall({ positional, flags }) {
217
- ui.banner();
216
+ async function cmdUninstall({ positional, flags }) {
217
+ await ui.bannerAnimated();
218
218
 
219
219
  ui.header('JDI Uninstall');
220
220
  ui.info(`Diretorio: ${c.dim}${process.cwd()}${c.reset}`);
@@ -245,8 +245,8 @@ function cmdUninstall({ positional, flags }) {
245
245
  }
246
246
  }
247
247
 
248
- function cmdInstallPlaywright({ flags }) {
249
- ui.banner();
248
+ async function cmdInstallPlaywright({ flags }) {
249
+ await ui.bannerAnimated();
250
250
 
251
251
  ui.header('JDI: Install Playwright + MCP');
252
252
  ui.info(`Directory: ${c.dim}${process.cwd()}${c.reset}`);
@@ -282,8 +282,8 @@ function cmdInstallPlaywright({ flags }) {
282
282
  }
283
283
  }
284
284
 
285
- function cmdInstallCaveman({ flags }) {
286
- ui.banner();
285
+ async function cmdInstallCaveman({ flags }) {
286
+ await ui.bannerAnimated();
287
287
 
288
288
  ui.header('JDI: Install Caveman plugin');
289
289
  ui.info(`Directory: ${c.dim}${process.cwd()}${c.reset}`);
@@ -317,8 +317,8 @@ function cmdInstallCaveman({ flags }) {
317
317
  }
318
318
  }
319
319
 
320
- function cmdDoctor({ flags }) {
321
- ui.banner();
320
+ async function cmdDoctor({ flags }) {
321
+ await ui.bannerAnimated();
322
322
 
323
323
  ui.header('JDI Doctor');
324
324
  ui.info(`Diretorio atual: ${c.dim}${process.cwd()}${c.reset}`);
@@ -332,8 +332,8 @@ function cmdDoctor({ flags }) {
332
332
  }
333
333
  }
334
334
 
335
- function cmdHelp() {
336
- ui.banner();
335
+ async function cmdHelp() {
336
+ await ui.bannerAnimated();
337
337
 
338
338
  console.log(`${c.bold}Uso:${c.reset}`);
339
339
  console.log(` ${c.cyan}npx jdi-cli <comando> [opcoes]${c.reset}`);
@@ -403,7 +403,7 @@ function cmdVersion() {
403
403
  // Main dispatcher
404
404
  // =================================================================
405
405
 
406
- function main() {
406
+ async function main() {
407
407
  const parsed = parseArgs(process.argv);
408
408
 
409
409
  if (parsed.flags && parsed.flags.noColor) {
@@ -417,34 +417,34 @@ function main() {
417
417
 
418
418
  switch (parsed.cmd) {
419
419
  case 'install':
420
- cmdInstall(parsed);
420
+ await cmdInstall(parsed);
421
421
  break;
422
422
  case 'update':
423
423
  case 'upgrade':
424
- cmdUpdate(parsed);
424
+ await cmdUpdate(parsed);
425
425
  break;
426
426
  case 'uninstall':
427
427
  case 'remove':
428
- cmdUninstall(parsed);
428
+ await cmdUninstall(parsed);
429
429
  break;
430
430
  case 'build':
431
- cmdBuild(parsed);
431
+ await cmdBuild(parsed);
432
432
  break;
433
433
  case 'install-playwright':
434
434
  case 'playwright':
435
- cmdInstallPlaywright(parsed);
435
+ await cmdInstallPlaywright(parsed);
436
436
  break;
437
437
  case 'install-caveman':
438
438
  case 'caveman':
439
- cmdInstallCaveman(parsed);
439
+ await cmdInstallCaveman(parsed);
440
440
  break;
441
441
  case 'doctor':
442
- cmdDoctor(parsed);
442
+ await cmdDoctor(parsed);
443
443
  break;
444
444
  case 'help':
445
445
  case '--help':
446
446
  case '-h':
447
- cmdHelp();
447
+ await cmdHelp();
448
448
  break;
449
449
  case '--version':
450
450
  case '-V':
@@ -457,4 +457,7 @@ function main() {
457
457
  }
458
458
  }
459
459
 
460
- main();
460
+ main().catch((err) => {
461
+ ui.fail(err && err.message ? err.message : String(err));
462
+ process.exit(1);
463
+ });
package/bin/lib/ui.js CHANGED
@@ -3,6 +3,12 @@
3
3
  // ANSI escape codes — sem deps externas
4
4
  const isTTY = process.stdout.isTTY;
5
5
  const supportsColor = isTTY && !process.env.NO_COLOR;
6
+ const supportsTrueColor =
7
+ supportsColor &&
8
+ (process.env.COLORTERM === 'truecolor' ||
9
+ process.env.COLORTERM === '24bit' ||
10
+ process.env.TERM_PROGRAM === 'vscode' ||
11
+ process.env.WT_SESSION); // Windows Terminal
6
12
 
7
13
  const c = {
8
14
  reset: supportsColor ? '\x1b[0m' : '',
@@ -25,22 +31,234 @@ const c = {
25
31
  bgGreen: supportsColor ? '\x1b[42m' : '',
26
32
  };
27
33
 
28
- // ASCII banner do JDI
29
- const BANNER = [
30
- '',
31
- `${c.cyan}${c.bold} ██╗██████╗ ██╗${c.reset}`,
32
- `${c.cyan}${c.bold} ██║██╔══██╗██║${c.reset}`,
33
- `${c.cyan}${c.bold} ██║██║ ██║██║${c.reset}`,
34
- `${c.cyan}${c.bold}██ ██║██║ ██║██║${c.reset}`,
35
- `${c.cyan}${c.bold}╚█████╔╝██████╔╝██║${c.reset}`,
36
- `${c.cyan}${c.bold} ╚════╝ ╚═════╝ ╚═╝${c.reset}`,
37
- '',
38
- `${c.dim} Just Do It ${c.reset}${c.gray}— workflow enxuto, multi-runtime${c.reset}`,
39
- '',
34
+ // Synthwave palette (truecolor with 256-color fallback)
35
+ function rgb(r, g, b, fallback256) {
36
+ if (!supportsColor) return '';
37
+ if (supportsTrueColor) return `\x1b[38;2;${r};${g};${b}m`;
38
+ return `\x1b[38;5;${fallback256}m`;
39
+ }
40
+
41
+ const synthwave = {
42
+ hotPink: rgb(255, 27, 141, 199),
43
+ neonYellow: rgb(244, 241, 66, 227),
44
+ cyanNeon: rgb(0, 255, 224, 51),
45
+ magenta: rgb(255, 0, 200, 201),
46
+ purple: rgb(185, 103, 255, 141),
47
+ };
48
+
49
+ // ASCII letters J D I in ANSI Shadow font (full glyphs, including J's hook)
50
+ const JDI_LETTERS = [
51
+ ' ██╗██████╗ ██╗',
52
+ ' ██║██╔══██╗██║',
53
+ ' ██║██║ ██║██║',
54
+ '██ ██║██║ ██║██║',
55
+ '╚█████╔╝██████╔╝██║',
56
+ ' ╚════╝ ╚═════╝ ╚═╝',
40
57
  ];
41
58
 
59
+ // 3 lightsabers stacked, right-anchored hilts, blades ignite leftward.
60
+ // Each saber: [blade plasma extending left]|=|◉|=|/////|==
61
+ // ^^^ ^ ^^^ ^^^^^ ^^
62
+ // emt btn grd grip pommel
63
+ const HILT_STR = '|=|◉|=|/////|=='; // composed hilt, right side
64
+ const HILT_LEN = 15; // visible length of HILT_STR
65
+ const BLADE_CHAR = '═';
66
+ const BLADE_MAX = 47; // blade extends this many chars left
67
+ const BLADE_TIP = '◄'; // tip when fully ignited
68
+ const SABER_INDENTS = [2, 2, 2]; // aligned indents — hilts in same column
69
+ // Jedi blade palette — blue, green, purple
70
+ const SABER_COLORS_TRUE = [
71
+ [46, 170, 251], // Obi-Wan / Luke OG blue
72
+ [55, 251, 58], // Yoda / Luke ROTJ green
73
+ [163, 72, 251], // Mace Windu purple
74
+ ];
75
+ const SABER_COLORS_256 = [33, 46, 129];
76
+ function saberColor(idx) {
77
+ if (!supportsColor) return '';
78
+ if (supportsTrueColor) {
79
+ const [r, g, b] = SABER_COLORS_TRUE[idx];
80
+ return `\x1b[38;2;${r};${g};${b}m`;
81
+ }
82
+ return `\x1b[38;5;${SABER_COLORS_256[idx]}m`;
83
+ }
84
+ const TAGLINE = 'Cut through the chaos. Ship the work. [Just do it]';
85
+
86
+ // Build one saber line, blade ignited from right (hilt) extending left to bladeChars.
87
+ // Layout (right-anchored): [spaces if blade incomplete][tip?][blade chars][hilt]
88
+ function buildSaberLine(saberIdx, bladeChars) {
89
+ const yellow = synthwave.neonYellow;
90
+ const red = supportsColor ? '\x1b[38;2;255;40;40m' : '';
91
+ const bladeCol = saberColor(saberIdx);
92
+ const r = c.reset;
93
+ const hiltColor = `${yellow}${c.bold}`;
94
+ const buttonColor = bladeChars > 0 ? `${red}${c.bold}` : hiltColor;
95
+
96
+ // Indent (stagger) + leading spaces to keep hilt at fixed right column
97
+ const indent = ' '.repeat(SABER_INDENTS[saberIdx]);
98
+ const leftPad = ' '.repeat(Math.max(0, BLADE_MAX - bladeChars));
99
+
100
+ let bladeSection = '';
101
+ if (bladeChars > 0) {
102
+ const tip = bladeChars >= BLADE_MAX ? `${bladeCol}${c.bold}${BLADE_TIP}${r}` : '';
103
+ const bladeBody = bladeChars >= BLADE_MAX ? bladeChars - 1 : bladeChars;
104
+ bladeSection = `${tip}${bladeCol}${c.bold}${BLADE_CHAR.repeat(bladeBody)}${r}`;
105
+ }
106
+
107
+ // HILT_STR has '◉' as 4th char (index 3). Recolor it red when blade active.
108
+ // Render hilt in 3 segments to keep button distinct.
109
+ const before = HILT_STR.slice(0, 3); // |=|
110
+ const button = HILT_STR.slice(3, 4); // ◉
111
+ const after = HILT_STR.slice(4); // |=|/////|==
112
+ const hilt =
113
+ `${hiltColor}${before}${r}` +
114
+ `${buttonColor}${button}${r}` +
115
+ `${hiltColor}${after}${r}`;
116
+
117
+ return `${indent}${leftPad}${bladeSection}${hilt}`;
118
+ }
119
+
120
+ function buildFrame({ showHilt, sabersBlade, showTagline }) {
121
+ const pink = synthwave.hotPink;
122
+ const purple = synthwave.purple;
123
+ const r = c.reset;
124
+
125
+ const lines = [''];
126
+
127
+ // 6 rows of JDI letters in hot pink
128
+ for (const row of JDI_LETTERS) {
129
+ lines.push(` ${pink}${c.bold}${row}${r}`);
130
+ }
131
+
132
+ lines.push('');
133
+
134
+ // 3 stacked sabers
135
+ if (!showHilt) {
136
+ lines.push('');
137
+ lines.push('');
138
+ lines.push('');
139
+ } else {
140
+ for (let i = 0; i < 3; i++) {
141
+ const blade = (sabersBlade && sabersBlade[i] != null) ? sabersBlade[i] : 0;
142
+ lines.push(buildSaberLine(i, blade));
143
+ }
144
+ }
145
+
146
+ lines.push('');
147
+
148
+ if (showTagline) {
149
+ lines.push(` ${purple}${c.italic}${TAGLINE}${r}`);
150
+ } else {
151
+ lines.push('');
152
+ }
153
+ lines.push('');
154
+
155
+ return lines;
156
+ }
157
+
158
+ function moveCursorUp(n) {
159
+ if (n > 0) process.stdout.write(`\x1b[${n}A`);
160
+ }
161
+ function clearLine() {
162
+ process.stdout.write('\x1b[2K');
163
+ }
164
+ function sleep(ms) {
165
+ return new Promise((resolve) => setTimeout(resolve, ms));
166
+ }
167
+
168
+ function writeFrame(lines, isFirst) {
169
+ if (!isFirst) {
170
+ moveCursorUp(lines.length);
171
+ }
172
+ for (const line of lines) {
173
+ clearLine();
174
+ process.stdout.write(`\r${line}\n`);
175
+ }
176
+ }
177
+
178
+ const wantsAnim =
179
+ isTTY &&
180
+ !process.env.NO_COLOR &&
181
+ !process.env.CI &&
182
+ process.env.JDI_ANIMATE !== 'off';
183
+
184
+ async function bannerAnimated() {
185
+ // Non-TTY / CI / NO_COLOR: print static
186
+ if (!wantsAnim) {
187
+ banner();
188
+ return;
189
+ }
190
+
191
+ // Cascade ignition: 3 sabers, each ignites with 80ms stagger.
192
+ // Saber 0 (blue) at t=0, saber 1 (green) at t=80ms, saber 2 (purple) at t=160ms.
193
+ // Each blade extends in 5 steps (~400ms per saber).
194
+ const stepChars = [0, 12, 24, 36, 44, BLADE_MAX];
195
+
196
+ function snapshot(t) {
197
+ const result = [];
198
+ for (let i = 0; i < 3; i++) {
199
+ const elapsed = t - i * 80;
200
+ if (elapsed <= 0) {
201
+ result.push(0);
202
+ continue;
203
+ }
204
+ const stepIdx = Math.min(stepChars.length - 1, Math.floor(elapsed / 80));
205
+ result.push(stepChars[stepIdx]);
206
+ }
207
+ return result;
208
+ }
209
+
210
+ const frames = [];
211
+ // t=0: letters only, no sabers
212
+ frames.push({ showHilt: false, sabersBlade: [0, 0, 0], showTagline: false, delay: 80 });
213
+ // t=80: hilts appear (no blade)
214
+ frames.push({ showHilt: true, sabersBlade: [0, 0, 0], showTagline: false, delay: 80 });
215
+ // Cascade ignition (~480ms)
216
+ for (let t = 80; t <= 560; t += 80) {
217
+ frames.push({ showHilt: true, sabersBlade: snapshot(t), showTagline: false, delay: 80 });
218
+ }
219
+ // Final: all blades full + tagline
220
+ frames.push({
221
+ showHilt: true,
222
+ sabersBlade: [BLADE_MAX, BLADE_MAX, BLADE_MAX],
223
+ showTagline: true,
224
+ delay: 0,
225
+ });
226
+
227
+ let isFirst = true;
228
+ for (const f of frames) {
229
+ const lines = buildFrame(f);
230
+ writeFrame(lines, isFirst);
231
+ isFirst = false;
232
+ if (f.delay > 0) await sleep(f.delay);
233
+ }
234
+
235
+ // Hold the banner for HOLD_MS so the user can see it before the command continues.
236
+ // Use a small spinner under the tagline to signal that the CLI is alive and not frozen.
237
+ const HOLD_MS = 5000;
238
+ const SPIN_INTERVAL = 100;
239
+ const SPIN_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
240
+ const purple = synthwave.purple;
241
+ const r = c.reset;
242
+ const start = Date.now();
243
+ let spinIdx = 0;
244
+ while (Date.now() - start < HOLD_MS) {
245
+ const frame = SPIN_FRAMES[spinIdx % SPIN_FRAMES.length];
246
+ process.stdout.write(`\r ${purple}${frame}${r} ${c.dim}igniting...${r} `);
247
+ spinIdx++;
248
+ await sleep(SPIN_INTERVAL);
249
+ }
250
+ // Clear the spinner line so command output starts cleanly
251
+ process.stdout.write('\r' + ' '.repeat(40) + '\r');
252
+ }
253
+
42
254
  function banner() {
43
- console.log(BANNER.join('\n'));
255
+ // Static one-shot — final state: all 3 sabers ignited + tagline
256
+ const lines = buildFrame({
257
+ showHilt: true,
258
+ sabersBlade: [BLADE_MAX, BLADE_MAX, BLADE_MAX],
259
+ showTagline: true,
260
+ });
261
+ console.log(lines.join('\n'));
44
262
  }
45
263
 
46
264
  // Box drawing
@@ -120,7 +338,7 @@ function spinner(msg) {
120
338
  if (!isTTY) {
121
339
  console.log(`${sym.arrow} ${msg}...`);
122
340
  return {
123
- stop: () => {},
341
+ stop: () => { },
124
342
  success: (s) => ok(s || msg),
125
343
  fail: (s) => fail(s || msg),
126
344
  };
@@ -179,7 +397,9 @@ function nextSteps(steps) {
179
397
  module.exports = {
180
398
  c,
181
399
  sym,
400
+ synthwave,
182
401
  banner,
402
+ bannerAnimated,
183
403
  box,
184
404
  step,
185
405
  ok,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jdi-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "JDI (Just Do It) — lean workflow toolkit for Claude Code, GitHub Copilot, Antigravity, and OpenCode. 10 commands (7 loop + ralph + adopt + meta), 6 core agents + N per-project specialists with file-glob routing. Optional Playwright MCP + Caveman plugin install.",
5
5
  "bin": {
6
6
  "jdi": "bin/jdi.js"