wiggum-cli 0.6.0 → 0.7.2
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 +49 -28
- package/dist/commands/init.d.ts +18 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +68 -92
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/new.js +1 -1
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/run.js +1 -1
- package/dist/commands/run.js.map +1 -1
- package/dist/generator/config.d.ts +3 -3
- package/dist/generator/config.js +3 -3
- package/dist/generator/index.js +1 -1
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/writer.js +1 -1
- package/dist/generator/writer.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -1
- package/dist/repl/command-parser.d.ts +5 -0
- package/dist/repl/command-parser.d.ts.map +1 -1
- package/dist/repl/command-parser.js +5 -0
- package/dist/repl/command-parser.js.map +1 -1
- package/dist/repl/repl-loop.d.ts.map +1 -1
- package/dist/repl/repl-loop.js +64 -3
- package/dist/repl/repl-loop.js.map +1 -1
- package/dist/repl/session-state.d.ts +4 -2
- package/dist/repl/session-state.d.ts.map +1 -1
- package/dist/repl/session-state.js +2 -1
- package/dist/repl/session-state.js.map +1 -1
- package/dist/templates/root/README.md.tmpl +1 -1
- package/dist/templates/scripts/feature-loop.sh.tmpl +17 -17
- package/dist/templates/scripts/loop.sh.tmpl +7 -7
- package/dist/templates/scripts/ralph-monitor.sh.tmpl +5 -5
- package/dist/utils/config.d.ts +7 -7
- package/dist/utils/config.js +4 -4
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/update-check.d.ts +21 -0
- package/dist/utils/update-check.d.ts.map +1 -0
- package/dist/utils/update-check.js +149 -0
- package/dist/utils/update-check.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/init.ts +92 -108
- package/src/commands/new.ts +1 -1
- package/src/commands/run.ts +1 -1
- package/src/generator/config.ts +3 -3
- package/src/generator/index.ts +1 -1
- package/src/generator/writer.ts +1 -1
- package/src/index.ts +50 -0
- package/src/repl/command-parser.ts +5 -0
- package/src/repl/repl-loop.ts +73 -3
- package/src/repl/session-state.ts +7 -3
- package/src/templates/config/ralph.config.cjs.tmpl +38 -0
- package/src/templates/root/README.md.tmpl +1 -1
- package/src/templates/scripts/feature-loop.sh.tmpl +17 -17
- package/src/templates/scripts/loop.sh.tmpl +7 -7
- package/src/templates/scripts/ralph-monitor.sh.tmpl +5 -5
- package/src/utils/config.ts +9 -9
- package/src/utils/update-check.ts +182 -0
- /package/{src/templates/config/ralph.config.js.tmpl → dist/templates/config/ralph.config.cjs.tmpl} +0 -0
package/src/repl/repl-loop.ts
CHANGED
|
@@ -9,6 +9,8 @@ import { logger } from '../utils/logger.js';
|
|
|
9
9
|
import { simpson } from '../utils/colors.js';
|
|
10
10
|
import { runCommand } from '../commands/run.js';
|
|
11
11
|
import { monitorCommand } from '../commands/monitor.js';
|
|
12
|
+
import { runInitWorkflow } from '../commands/init.js';
|
|
13
|
+
import { hasConfig } from '../utils/config.js';
|
|
12
14
|
import type { SessionState } from './session-state.js';
|
|
13
15
|
import { updateSessionState } from './session-state.js';
|
|
14
16
|
import {
|
|
@@ -21,13 +23,64 @@ import {
|
|
|
21
23
|
const PROMPT = `${simpson.yellow('wiggum')}${simpson.brown('>')} `;
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
|
-
* Handler for the /
|
|
26
|
+
* Handler for the /init command
|
|
27
|
+
*/
|
|
28
|
+
async function handleInitCommand(
|
|
29
|
+
_args: string[],
|
|
30
|
+
state: SessionState,
|
|
31
|
+
rl: readline.Interface
|
|
32
|
+
): Promise<SessionState> {
|
|
33
|
+
// Check if already initialized
|
|
34
|
+
if (state.initialized && hasConfig(state.projectRoot)) {
|
|
35
|
+
logger.warn('Project is already initialized. Re-running init will update configuration.');
|
|
36
|
+
console.log('');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Pause REPL readline to avoid conflicts with subcommand's stdin usage
|
|
40
|
+
rl.pause();
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const result = await runInitWorkflow(state.projectRoot, {
|
|
44
|
+
yes: false, // Always interactive in REPL
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (result) {
|
|
48
|
+
// Update state with init result
|
|
49
|
+
return updateSessionState(state, {
|
|
50
|
+
provider: result.provider,
|
|
51
|
+
model: result.model,
|
|
52
|
+
scanResult: result.scanResult,
|
|
53
|
+
config: result.config,
|
|
54
|
+
initialized: true,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// User cancelled
|
|
59
|
+
return state;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
logger.error(`Init failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
|
+
return state;
|
|
63
|
+
} finally {
|
|
64
|
+
// Resume REPL readline after subcommand completes
|
|
65
|
+
rl.resume();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Handler for the /new command
|
|
71
|
+
* Always uses AI interview mode in REPL (falls back to template if no API key)
|
|
25
72
|
*/
|
|
26
73
|
async function handleNewCommand(
|
|
27
74
|
args: string[],
|
|
28
75
|
state: SessionState,
|
|
29
76
|
rl: readline.Interface
|
|
30
77
|
): Promise<SessionState> {
|
|
78
|
+
// Check if initialized
|
|
79
|
+
if (!state.initialized && !hasConfig(state.projectRoot)) {
|
|
80
|
+
logger.warn('Project not initialized. Run /init first.');
|
|
81
|
+
return state;
|
|
82
|
+
}
|
|
83
|
+
|
|
31
84
|
if (args.length === 0) {
|
|
32
85
|
logger.error('Feature name required. Usage: /new <feature-name>');
|
|
33
86
|
return state;
|
|
@@ -40,12 +93,14 @@ async function handleNewCommand(
|
|
|
40
93
|
|
|
41
94
|
try {
|
|
42
95
|
// Delegate to the existing new command behavior
|
|
96
|
+
// Always use AI mode in REPL (the command handles fallback to template if no API key)
|
|
43
97
|
const { newCommand } = await import('../commands/new.js');
|
|
44
98
|
await newCommand(featureName, {
|
|
45
99
|
yes: false,
|
|
46
100
|
scanResult: state.scanResult,
|
|
47
|
-
provider: state.provider,
|
|
101
|
+
provider: state.provider ?? undefined,
|
|
48
102
|
model: state.model,
|
|
103
|
+
ai: true, // Always use AI interview in REPL
|
|
49
104
|
});
|
|
50
105
|
} finally {
|
|
51
106
|
// Resume REPL readline after subcommand completes
|
|
@@ -63,6 +118,12 @@ async function handleRunCommand(
|
|
|
63
118
|
state: SessionState,
|
|
64
119
|
rl: readline.Interface
|
|
65
120
|
): Promise<SessionState> {
|
|
121
|
+
// Check if initialized
|
|
122
|
+
if (!state.initialized && !hasConfig(state.projectRoot)) {
|
|
123
|
+
logger.warn('Project not initialized. Run /init first.');
|
|
124
|
+
return state;
|
|
125
|
+
}
|
|
126
|
+
|
|
66
127
|
if (args.length === 0) {
|
|
67
128
|
logger.error('Feature name required. Usage: /run <feature-name>');
|
|
68
129
|
return state;
|
|
@@ -128,6 +189,9 @@ async function executeCommand(
|
|
|
128
189
|
rl: readline.Interface
|
|
129
190
|
): Promise<{ state: SessionState; shouldExit: boolean }> {
|
|
130
191
|
switch (commandName) {
|
|
192
|
+
case 'init':
|
|
193
|
+
return { state: await handleInitCommand(args, state, rl), shouldExit: false };
|
|
194
|
+
|
|
131
195
|
case 'new':
|
|
132
196
|
return { state: await handleNewCommand(args, state, rl), shouldExit: false };
|
|
133
197
|
|
|
@@ -218,7 +282,13 @@ export async function startRepl(initialState: SessionState): Promise<void> {
|
|
|
218
282
|
|
|
219
283
|
console.log('');
|
|
220
284
|
console.log(simpson.yellow('Wiggum Interactive Mode'));
|
|
221
|
-
|
|
285
|
+
|
|
286
|
+
// Show context-aware welcome message
|
|
287
|
+
if (!state.initialized && !hasConfig(state.projectRoot)) {
|
|
288
|
+
console.log(pc.dim('Not initialized. Run /init to set up this project.'));
|
|
289
|
+
} else {
|
|
290
|
+
console.log(pc.dim('Type /help for commands, /exit to quit'));
|
|
291
|
+
}
|
|
222
292
|
console.log('');
|
|
223
293
|
|
|
224
294
|
const rl = readline.createInterface({
|
|
@@ -16,7 +16,7 @@ export interface SessionState {
|
|
|
16
16
|
/** Loaded Ralph configuration */
|
|
17
17
|
config: RalphConfig | null;
|
|
18
18
|
/** AI provider being used */
|
|
19
|
-
provider: AIProvider;
|
|
19
|
+
provider: AIProvider | null;
|
|
20
20
|
/** Model to use for AI operations */
|
|
21
21
|
model: string;
|
|
22
22
|
/** Cached scan result from init */
|
|
@@ -25,6 +25,8 @@ export interface SessionState {
|
|
|
25
25
|
conversationMode: boolean;
|
|
26
26
|
/** Current conversation context (e.g., 'spec-generation') */
|
|
27
27
|
conversationContext?: string;
|
|
28
|
+
/** Whether /init has been run in this session */
|
|
29
|
+
initialized: boolean;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
/**
|
|
@@ -32,10 +34,11 @@ export interface SessionState {
|
|
|
32
34
|
*/
|
|
33
35
|
export function createSessionState(
|
|
34
36
|
projectRoot: string,
|
|
35
|
-
provider: AIProvider,
|
|
37
|
+
provider: AIProvider | null,
|
|
36
38
|
model: string,
|
|
37
39
|
scanResult?: ScanResult,
|
|
38
|
-
config?: RalphConfig | null
|
|
40
|
+
config?: RalphConfig | null,
|
|
41
|
+
initialized?: boolean
|
|
39
42
|
): SessionState {
|
|
40
43
|
return {
|
|
41
44
|
projectRoot,
|
|
@@ -45,6 +48,7 @@ export function createSessionState(
|
|
|
45
48
|
scanResult,
|
|
46
49
|
conversationMode: false,
|
|
47
50
|
conversationContext: undefined,
|
|
51
|
+
initialized: initialized ?? false,
|
|
48
52
|
};
|
|
49
53
|
}
|
|
50
54
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: '{{projectName}}',
|
|
3
|
+
stack: {
|
|
4
|
+
framework: {
|
|
5
|
+
name: '{{framework}}',
|
|
6
|
+
version: '{{frameworkVersion}}',
|
|
7
|
+
variant: '{{frameworkVariant}}',
|
|
8
|
+
},
|
|
9
|
+
packageManager: '{{packageManager}}',
|
|
10
|
+
testing: {
|
|
11
|
+
unit: '{{unitTest}}',
|
|
12
|
+
e2e: '{{e2eTest}}',
|
|
13
|
+
},
|
|
14
|
+
styling: '{{styling}}',
|
|
15
|
+
},
|
|
16
|
+
commands: {
|
|
17
|
+
dev: '{{devCommand}}',
|
|
18
|
+
build: '{{buildCommand}}',
|
|
19
|
+
test: '{{testCommand}}',
|
|
20
|
+
lint: '{{lintCommand}}',
|
|
21
|
+
typecheck: '{{typecheckCommand}}',
|
|
22
|
+
},
|
|
23
|
+
paths: {
|
|
24
|
+
root: '.ralph',
|
|
25
|
+
prompts: '.ralph/prompts',
|
|
26
|
+
guides: '.ralph/guides',
|
|
27
|
+
specs: '.ralph/specs',
|
|
28
|
+
scripts: '.ralph/scripts',
|
|
29
|
+
learnings: '.ralph/LEARNINGS.md',
|
|
30
|
+
agents: '.ralph/AGENTS.md',
|
|
31
|
+
},
|
|
32
|
+
loop: {
|
|
33
|
+
maxIterations: 10,
|
|
34
|
+
maxE2eAttempts: 5,
|
|
35
|
+
defaultModel: 'sonnet',
|
|
36
|
+
planningModel: 'opus',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -14,23 +14,23 @@ set -o pipefail
|
|
|
14
14
|
# Get script directory
|
|
15
15
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
16
|
|
|
17
|
-
# Load config from ralph.config.
|
|
18
|
-
if [ -f "$SCRIPT_DIR/../ralph.config.
|
|
19
|
-
RALPH_ROOT=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.
|
|
20
|
-
SPEC_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.
|
|
21
|
-
PROMPTS_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.
|
|
22
|
-
DEFAULT_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.
|
|
23
|
-
PLANNING_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.
|
|
24
|
-
DEFAULT_MAX_ITERATIONS=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.
|
|
25
|
-
DEFAULT_MAX_E2E=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.
|
|
26
|
-
elif [ -f "$SCRIPT_DIR/../../ralph.config.
|
|
27
|
-
RALPH_ROOT=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.
|
|
28
|
-
SPEC_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.
|
|
29
|
-
PROMPTS_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.
|
|
30
|
-
DEFAULT_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.
|
|
31
|
-
PLANNING_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.
|
|
32
|
-
DEFAULT_MAX_ITERATIONS=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.
|
|
33
|
-
DEFAULT_MAX_E2E=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.
|
|
17
|
+
# Load config from ralph.config.cjs if available
|
|
18
|
+
if [ -f "$SCRIPT_DIR/../ralph.config.cjs" ]; then
|
|
19
|
+
RALPH_ROOT=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').paths?.root || '.ralph')" 2>/dev/null || echo ".ralph")
|
|
20
|
+
SPEC_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').paths?.specs || '.ralph/specs')" 2>/dev/null || echo ".ralph/specs")
|
|
21
|
+
PROMPTS_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').paths?.prompts || '.ralph/prompts')" 2>/dev/null || echo ".ralph/prompts")
|
|
22
|
+
DEFAULT_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').loop?.defaultModel || 'sonnet')" 2>/dev/null || echo "sonnet")
|
|
23
|
+
PLANNING_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').loop?.planningModel || 'opus')" 2>/dev/null || echo "opus")
|
|
24
|
+
DEFAULT_MAX_ITERATIONS=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').loop?.maxIterations || 10)" 2>/dev/null || echo "10")
|
|
25
|
+
DEFAULT_MAX_E2E=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').loop?.maxE2eAttempts || 5)" 2>/dev/null || echo "5")
|
|
26
|
+
elif [ -f "$SCRIPT_DIR/../../ralph.config.cjs" ]; then
|
|
27
|
+
RALPH_ROOT=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').paths?.root || '.ralph')" 2>/dev/null || echo ".ralph")
|
|
28
|
+
SPEC_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').paths?.specs || '.ralph/specs')" 2>/dev/null || echo ".ralph/specs")
|
|
29
|
+
PROMPTS_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').paths?.prompts || '.ralph/prompts')" 2>/dev/null || echo ".ralph/prompts")
|
|
30
|
+
DEFAULT_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').loop?.defaultModel || 'sonnet')" 2>/dev/null || echo "sonnet")
|
|
31
|
+
PLANNING_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').loop?.planningModel || 'opus')" 2>/dev/null || echo "opus")
|
|
32
|
+
DEFAULT_MAX_ITERATIONS=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').loop?.maxIterations || 10)" 2>/dev/null || echo "10")
|
|
33
|
+
DEFAULT_MAX_E2E=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').loop?.maxE2eAttempts || 5)" 2>/dev/null || echo "5")
|
|
34
34
|
else
|
|
35
35
|
# Default paths
|
|
36
36
|
RALPH_ROOT=".ralph"
|
|
@@ -8,13 +8,13 @@ set -e
|
|
|
8
8
|
# Get script directory
|
|
9
9
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
10
|
|
|
11
|
-
# Load config from ralph.config.
|
|
12
|
-
if [ -f "$SCRIPT_DIR/../ralph.config.
|
|
13
|
-
PROMPTS_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.
|
|
14
|
-
DEFAULT_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.
|
|
15
|
-
elif [ -f "$SCRIPT_DIR/../../ralph.config.
|
|
16
|
-
PROMPTS_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.
|
|
17
|
-
DEFAULT_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.
|
|
11
|
+
# Load config from ralph.config.cjs if available
|
|
12
|
+
if [ -f "$SCRIPT_DIR/../ralph.config.cjs" ]; then
|
|
13
|
+
PROMPTS_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').paths?.prompts || '.ralph/prompts')" 2>/dev/null || echo ".ralph/prompts")
|
|
14
|
+
DEFAULT_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').loop?.defaultModel || 'sonnet')" 2>/dev/null || echo "sonnet")
|
|
15
|
+
elif [ -f "$SCRIPT_DIR/../../ralph.config.cjs" ]; then
|
|
16
|
+
PROMPTS_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').paths?.prompts || '.ralph/prompts')" 2>/dev/null || echo ".ralph/prompts")
|
|
17
|
+
DEFAULT_MODEL=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').loop?.defaultModel || 'sonnet')" 2>/dev/null || echo "sonnet")
|
|
18
18
|
else
|
|
19
19
|
PROMPTS_DIR=".ralph/prompts"
|
|
20
20
|
DEFAULT_MODEL="sonnet"
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
FEATURE="${1:?Usage: ./ralph-monitor.sh <feature-name>}"
|
|
7
7
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
8
|
|
|
9
|
-
# Load config from ralph.config.
|
|
10
|
-
if [ -f "$SCRIPT_DIR/../ralph.config.
|
|
11
|
-
SPEC_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.
|
|
12
|
-
elif [ -f "$SCRIPT_DIR/../../ralph.config.
|
|
13
|
-
SPEC_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.
|
|
9
|
+
# Load config from ralph.config.cjs if available
|
|
10
|
+
if [ -f "$SCRIPT_DIR/../ralph.config.cjs" ]; then
|
|
11
|
+
SPEC_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../ralph.config.cjs').paths?.specs || '.ralph/specs')" 2>/dev/null || echo ".ralph/specs")
|
|
12
|
+
elif [ -f "$SCRIPT_DIR/../../ralph.config.cjs" ]; then
|
|
13
|
+
SPEC_DIR=$(node -e "console.log(require('$SCRIPT_DIR/../../ralph.config.cjs').paths?.specs || '.ralph/specs')" 2>/dev/null || echo ".ralph/specs")
|
|
14
14
|
else
|
|
15
15
|
SPEC_DIR=".ralph/specs"
|
|
16
16
|
fi
|
package/src/utils/config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Configuration Utilities
|
|
3
|
-
* Load and parse ralph.config.
|
|
3
|
+
* Load and parse ralph.config.cjs files
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
@@ -9,7 +9,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
9
9
|
import { logger } from './logger.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Stack configuration in ralph.config.
|
|
12
|
+
* Stack configuration in ralph.config.cjs
|
|
13
13
|
*/
|
|
14
14
|
export interface StackConfig {
|
|
15
15
|
framework: {
|
|
@@ -26,7 +26,7 @@ export interface StackConfig {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Commands configuration in ralph.config.
|
|
29
|
+
* Commands configuration in ralph.config.cjs
|
|
30
30
|
*/
|
|
31
31
|
export interface CommandsConfig {
|
|
32
32
|
dev: string;
|
|
@@ -37,7 +37,7 @@ export interface CommandsConfig {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Paths configuration in ralph.config.
|
|
40
|
+
* Paths configuration in ralph.config.cjs
|
|
41
41
|
*/
|
|
42
42
|
export interface PathsConfig {
|
|
43
43
|
root: string;
|
|
@@ -50,7 +50,7 @@ export interface PathsConfig {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* Loop configuration in ralph.config.
|
|
53
|
+
* Loop configuration in ralph.config.cjs
|
|
54
54
|
*/
|
|
55
55
|
export interface LoopConfig {
|
|
56
56
|
maxIterations: number;
|
|
@@ -60,7 +60,7 @@ export interface LoopConfig {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Full ralph.config.
|
|
63
|
+
* Full ralph.config.cjs structure
|
|
64
64
|
*/
|
|
65
65
|
export interface RalphConfig {
|
|
66
66
|
name: string;
|
|
@@ -113,11 +113,11 @@ export const DEFAULT_CONFIG: RalphConfig = {
|
|
|
113
113
|
};
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
-
* Load ralph.config.
|
|
116
|
+
* Load ralph.config.cjs from a project directory
|
|
117
117
|
* Returns null if config file doesn't exist
|
|
118
118
|
*/
|
|
119
119
|
export async function loadConfig(projectRoot: string): Promise<RalphConfig | null> {
|
|
120
|
-
const configPath = join(projectRoot, 'ralph.config.
|
|
120
|
+
const configPath = join(projectRoot, 'ralph.config.cjs');
|
|
121
121
|
|
|
122
122
|
if (!existsSync(configPath)) {
|
|
123
123
|
return null;
|
|
@@ -179,7 +179,7 @@ export async function loadConfigWithDefaults(projectRoot: string): Promise<Ralph
|
|
|
179
179
|
* Check if a ralph config exists in the project
|
|
180
180
|
*/
|
|
181
181
|
export function hasConfig(projectRoot: string): boolean {
|
|
182
|
-
const configPath = join(projectRoot, 'ralph.config.
|
|
182
|
+
const configPath = join(projectRoot, 'ralph.config.cjs');
|
|
183
183
|
return existsSync(configPath);
|
|
184
184
|
}
|
|
185
185
|
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Check Utility
|
|
3
|
+
* Checks npm registry for newer versions and notifies user
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import pc from 'picocolors';
|
|
11
|
+
|
|
12
|
+
const PACKAGE_NAME = 'wiggum-cli';
|
|
13
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
14
|
+
const CACHE_FILE = path.join(os.homedir(), '.wiggum-update-check.json');
|
|
15
|
+
|
|
16
|
+
interface UpdateCheckCache {
|
|
17
|
+
lastCheck: number;
|
|
18
|
+
latestVersion: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface UpdateInfo {
|
|
22
|
+
currentVersion: string;
|
|
23
|
+
latestVersion: string;
|
|
24
|
+
updateAvailable: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get current package version
|
|
29
|
+
*/
|
|
30
|
+
function getCurrentVersion(): string {
|
|
31
|
+
try {
|
|
32
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
33
|
+
const __dirname = path.dirname(__filename);
|
|
34
|
+
// Go up from utils/ to package root
|
|
35
|
+
const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
|
|
36
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
37
|
+
return packageJson.version;
|
|
38
|
+
} catch {
|
|
39
|
+
return '0.0.0';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Read cache file
|
|
45
|
+
*/
|
|
46
|
+
function readCache(): UpdateCheckCache | null {
|
|
47
|
+
try {
|
|
48
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
49
|
+
return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// Ignore cache read errors
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Write cache file
|
|
59
|
+
*/
|
|
60
|
+
function writeCache(cache: UpdateCheckCache): void {
|
|
61
|
+
try {
|
|
62
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache), 'utf-8');
|
|
63
|
+
} catch {
|
|
64
|
+
// Ignore cache write errors
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Fetch latest version from npm registry
|
|
70
|
+
*/
|
|
71
|
+
async function fetchLatestVersion(): Promise<string | null> {
|
|
72
|
+
try {
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
const timeout = setTimeout(() => controller.abort(), 3000); // 3s timeout
|
|
75
|
+
|
|
76
|
+
const response = await fetch(
|
|
77
|
+
`https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
|
|
78
|
+
{ signal: controller.signal }
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
clearTimeout(timeout);
|
|
82
|
+
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const data = await response.json();
|
|
88
|
+
return data.version || null;
|
|
89
|
+
} catch {
|
|
90
|
+
// Network error or timeout - fail silently
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Compare semantic versions
|
|
97
|
+
* Returns true if v2 is newer than v1
|
|
98
|
+
*/
|
|
99
|
+
function isNewer(v1: string, v2: string): boolean {
|
|
100
|
+
const parts1 = v1.split('.').map(Number);
|
|
101
|
+
const parts2 = v2.split('.').map(Number);
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < 3; i++) {
|
|
104
|
+
const p1 = parts1[i] || 0;
|
|
105
|
+
const p2 = parts2[i] || 0;
|
|
106
|
+
if (p2 > p1) return true;
|
|
107
|
+
if (p2 < p1) return false;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check for updates (with caching)
|
|
114
|
+
* Returns update info if check was performed, null if skipped/failed
|
|
115
|
+
*/
|
|
116
|
+
export async function checkForUpdates(): Promise<UpdateInfo | null> {
|
|
117
|
+
const currentVersion = getCurrentVersion();
|
|
118
|
+
const cache = readCache();
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
|
|
121
|
+
// Use cached result if recent enough
|
|
122
|
+
if (cache && (now - cache.lastCheck) < CHECK_INTERVAL_MS && cache.latestVersion) {
|
|
123
|
+
return {
|
|
124
|
+
currentVersion,
|
|
125
|
+
latestVersion: cache.latestVersion,
|
|
126
|
+
updateAvailable: isNewer(currentVersion, cache.latestVersion),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Fetch latest version
|
|
131
|
+
const latestVersion = await fetchLatestVersion();
|
|
132
|
+
|
|
133
|
+
if (latestVersion) {
|
|
134
|
+
writeCache({ lastCheck: now, latestVersion });
|
|
135
|
+
return {
|
|
136
|
+
currentVersion,
|
|
137
|
+
latestVersion,
|
|
138
|
+
updateAvailable: isNewer(currentVersion, latestVersion),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Display update notification if available
|
|
147
|
+
* Call this at startup - it's non-blocking and fails silently
|
|
148
|
+
*/
|
|
149
|
+
export async function notifyIfUpdateAvailable(): Promise<void> {
|
|
150
|
+
try {
|
|
151
|
+
const info = await checkForUpdates();
|
|
152
|
+
|
|
153
|
+
if (info?.updateAvailable) {
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log(
|
|
156
|
+
pc.yellow('╭─────────────────────────────────────────────────────────╮')
|
|
157
|
+
);
|
|
158
|
+
console.log(
|
|
159
|
+
pc.yellow('│') +
|
|
160
|
+
pc.bold(' Update available: ') +
|
|
161
|
+
pc.dim(info.currentVersion) +
|
|
162
|
+
pc.bold(' → ') +
|
|
163
|
+
pc.green(info.latestVersion) +
|
|
164
|
+
' ' +
|
|
165
|
+
pc.yellow('│')
|
|
166
|
+
);
|
|
167
|
+
console.log(
|
|
168
|
+
pc.yellow('│') +
|
|
169
|
+
' Run ' +
|
|
170
|
+
pc.cyan('npm i -g wiggum-cli') +
|
|
171
|
+
' to update ' +
|
|
172
|
+
pc.yellow('│')
|
|
173
|
+
);
|
|
174
|
+
console.log(
|
|
175
|
+
pc.yellow('╰─────────────────────────────────────────────────────────╯')
|
|
176
|
+
);
|
|
177
|
+
console.log('');
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
// Fail silently - don't interrupt user workflow
|
|
181
|
+
}
|
|
182
|
+
}
|
/package/{src/templates/config/ralph.config.js.tmpl → dist/templates/config/ralph.config.cjs.tmpl}
RENAMED
|
File without changes
|