@nclamvn/vibecode-cli 1.0.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.
@@ -0,0 +1,115 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Session Management
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ import path from 'path';
6
+ import {
7
+ getSessionsPath,
8
+ getSessionPath,
9
+ loadState,
10
+ saveState
11
+ } from './workspace.js';
12
+ import { ensureDir, pathExists, writeMarkdown, readMarkdown } from '../utils/files.js';
13
+ import { generateSessionId } from '../utils/hash.js';
14
+ import { getIntakeTemplate, getBlueprintTemplate, getContractTemplate } from '../config/templates.js';
15
+
16
+ /**
17
+ * Create new session
18
+ */
19
+ export async function createSession(projectName) {
20
+ const sessionId = generateSessionId();
21
+ const sessionPath = getSessionPath(sessionId);
22
+
23
+ // Create session directory
24
+ await ensureDir(sessionPath);
25
+ await ensureDir(path.join(sessionPath, 'evidence'));
26
+
27
+ // Update state with current session
28
+ const stateData = await loadState();
29
+ stateData.current_session = sessionId;
30
+ await saveState(stateData);
31
+
32
+ return sessionId;
33
+ }
34
+
35
+ /**
36
+ * Get current session ID
37
+ */
38
+ export async function getCurrentSessionId() {
39
+ const stateData = await loadState();
40
+ return stateData.current_session;
41
+ }
42
+
43
+ /**
44
+ * Get current session path
45
+ */
46
+ export async function getCurrentSessionPath() {
47
+ const sessionId = await getCurrentSessionId();
48
+ if (!sessionId) return null;
49
+ return getSessionPath(sessionId);
50
+ }
51
+
52
+ /**
53
+ * Check if file exists in current session
54
+ */
55
+ export async function sessionFileExists(filename) {
56
+ const sessionPath = await getCurrentSessionPath();
57
+ if (!sessionPath) return false;
58
+ return await pathExists(path.join(sessionPath, filename));
59
+ }
60
+
61
+ /**
62
+ * Write file to current session
63
+ */
64
+ export async function writeSessionFile(filename, content) {
65
+ const sessionPath = await getCurrentSessionPath();
66
+ if (!sessionPath) throw new Error('No active session');
67
+ await writeMarkdown(path.join(sessionPath, filename), content);
68
+ }
69
+
70
+ /**
71
+ * Read file from current session
72
+ */
73
+ export async function readSessionFile(filename) {
74
+ const sessionPath = await getCurrentSessionPath();
75
+ if (!sessionPath) throw new Error('No active session');
76
+ return await readMarkdown(path.join(sessionPath, filename));
77
+ }
78
+
79
+ /**
80
+ * Get session files status
81
+ */
82
+ export async function getSessionFilesStatus() {
83
+ const sessionPath = await getCurrentSessionPath();
84
+ if (!sessionPath) return null;
85
+
86
+ return {
87
+ intake: await pathExists(path.join(sessionPath, 'intake.md')),
88
+ blueprint: await pathExists(path.join(sessionPath, 'blueprint.md')),
89
+ contract: await pathExists(path.join(sessionPath, 'contract.md'))
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Create intake file from description
95
+ */
96
+ export async function createIntake(projectName, description, sessionId) {
97
+ const template = getIntakeTemplate(projectName, description, sessionId);
98
+ await writeSessionFile('intake.md', template);
99
+ }
100
+
101
+ /**
102
+ * Create blueprint file
103
+ */
104
+ export async function createBlueprint(projectName, sessionId) {
105
+ const template = getBlueprintTemplate(projectName, sessionId);
106
+ await writeSessionFile('blueprint.md', template);
107
+ }
108
+
109
+ /**
110
+ * Create contract file
111
+ */
112
+ export async function createContract(projectName, sessionId) {
113
+ const template = getContractTemplate(projectName, sessionId);
114
+ await writeSessionFile('contract.md', template);
115
+ }
@@ -0,0 +1,95 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - State Machine
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ import { STATES, TRANSITIONS } from '../config/constants.js';
6
+ import { loadState, saveState, getLogsPath } from './workspace.js';
7
+ import { appendToFile } from '../utils/files.js';
8
+ import path from 'path';
9
+
10
+ /**
11
+ * Get current state
12
+ */
13
+ export async function getCurrentState() {
14
+ const stateData = await loadState();
15
+ return stateData.current_state;
16
+ }
17
+
18
+ /**
19
+ * Check if transition is valid
20
+ */
21
+ export function isValidTransition(fromState, toState) {
22
+ const allowedTransitions = TRANSITIONS[fromState] || [];
23
+ return allowedTransitions.includes(toState);
24
+ }
25
+
26
+ /**
27
+ * Get allowed transitions from current state
28
+ */
29
+ export function getAllowedTransitions(currentState) {
30
+ return TRANSITIONS[currentState] || [];
31
+ }
32
+
33
+ /**
34
+ * Transition to new state
35
+ */
36
+ export async function transitionTo(newState, action = 'manual_transition') {
37
+ const stateData = await loadState();
38
+ const currentState = stateData.current_state;
39
+
40
+ if (!isValidTransition(currentState, newState)) {
41
+ throw new Error(
42
+ `Invalid transition: ${currentState} → ${newState}. ` +
43
+ `Allowed: ${getAllowedTransitions(currentState).join(', ') || 'none'}`
44
+ );
45
+ }
46
+
47
+ // Update state
48
+ stateData.current_state = newState;
49
+ stateData.history.push({
50
+ state: newState,
51
+ timestamp: new Date().toISOString(),
52
+ action: action,
53
+ from: currentState
54
+ });
55
+
56
+ await saveState(stateData);
57
+
58
+ // Log to audit trail
59
+ await logAudit(`State transition: ${currentState} → ${newState} (${action})`);
60
+
61
+ return stateData;
62
+ }
63
+
64
+ /**
65
+ * Log to audit file
66
+ */
67
+ export async function logAudit(message) {
68
+ const timestamp = new Date().toISOString();
69
+ const logLine = `[${timestamp}] ${message}`;
70
+ const logPath = path.join(getLogsPath(), 'audit.log');
71
+
72
+ try {
73
+ await appendToFile(logPath, logLine);
74
+ } catch (error) {
75
+ // Silently fail if logs dir doesn't exist yet
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Get state history
81
+ */
82
+ export async function getStateHistory() {
83
+ const stateData = await loadState();
84
+ return stateData.history;
85
+ }
86
+
87
+ /**
88
+ * Check if state is at or past a certain point
89
+ */
90
+ export function isStateAtLeast(currentState, targetState) {
91
+ const stateOrder = Object.values(STATES);
92
+ const currentIndex = stateOrder.indexOf(currentState);
93
+ const targetIndex = stateOrder.indexOf(targetState);
94
+ return currentIndex >= targetIndex;
95
+ }
@@ -0,0 +1,152 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Workspace Management
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ import path from 'path';
6
+ import {
7
+ WORKSPACE_DIR,
8
+ CONFIG_FILE,
9
+ STATE_FILE,
10
+ SESSIONS_DIR,
11
+ LIBRARY_DIR,
12
+ LOGS_DIR,
13
+ STATES
14
+ } from '../config/constants.js';
15
+ import {
16
+ pathExists,
17
+ ensureDir,
18
+ readJson,
19
+ writeJson,
20
+ readYaml,
21
+ writeYaml,
22
+ getCurrentDirName
23
+ } from '../utils/files.js';
24
+
25
+ /**
26
+ * Get workspace root path
27
+ */
28
+ export function getWorkspacePath() {
29
+ return path.join(process.cwd(), WORKSPACE_DIR);
30
+ }
31
+
32
+ /**
33
+ * Check if workspace exists
34
+ */
35
+ export async function workspaceExists() {
36
+ return await pathExists(getWorkspacePath());
37
+ }
38
+
39
+ /**
40
+ * Get config file path
41
+ */
42
+ export function getConfigPath() {
43
+ return path.join(getWorkspacePath(), CONFIG_FILE);
44
+ }
45
+
46
+ /**
47
+ * Get state file path
48
+ */
49
+ export function getStatePath() {
50
+ return path.join(getWorkspacePath(), STATE_FILE);
51
+ }
52
+
53
+ /**
54
+ * Get sessions directory path
55
+ */
56
+ export function getSessionsPath() {
57
+ return path.join(getWorkspacePath(), SESSIONS_DIR);
58
+ }
59
+
60
+ /**
61
+ * Get specific session path
62
+ */
63
+ export function getSessionPath(sessionId) {
64
+ return path.join(getSessionsPath(), sessionId);
65
+ }
66
+
67
+ /**
68
+ * Get logs directory path
69
+ */
70
+ export function getLogsPath() {
71
+ return path.join(getWorkspacePath(), LOGS_DIR);
72
+ }
73
+
74
+ /**
75
+ * Create workspace structure
76
+ */
77
+ export async function createWorkspace() {
78
+ const workspacePath = getWorkspacePath();
79
+
80
+ // Create directories
81
+ await ensureDir(workspacePath);
82
+ await ensureDir(path.join(workspacePath, SESSIONS_DIR));
83
+ await ensureDir(path.join(workspacePath, LIBRARY_DIR));
84
+ await ensureDir(path.join(workspacePath, LIBRARY_DIR, 'prompts'));
85
+ await ensureDir(path.join(workspacePath, LOGS_DIR));
86
+
87
+ // Create config file
88
+ const config = {
89
+ version: '1.0',
90
+ created: new Date().toISOString(),
91
+ project: {
92
+ name: getCurrentDirName(),
93
+ type: 'auto'
94
+ },
95
+ settings: {
96
+ enforcement: 'strict',
97
+ auto_backup: true,
98
+ audit_log: true
99
+ },
100
+ provider: {
101
+ default: 'claude-code'
102
+ }
103
+ };
104
+ await writeYaml(getConfigPath(), config);
105
+
106
+ // Create state file
107
+ const state = {
108
+ version: '1.0',
109
+ current_state: STATES.INIT,
110
+ current_session: null,
111
+ spec_hash: null,
112
+ history: [
113
+ {
114
+ state: STATES.INIT,
115
+ timestamp: new Date().toISOString(),
116
+ action: 'workspace_initialized'
117
+ }
118
+ ]
119
+ };
120
+ await writeJson(getStatePath(), state);
121
+
122
+ return { config, state };
123
+ }
124
+
125
+ /**
126
+ * Load config
127
+ */
128
+ export async function loadConfig() {
129
+ return await readYaml(getConfigPath());
130
+ }
131
+
132
+ /**
133
+ * Load state
134
+ */
135
+ export async function loadState() {
136
+ return await readJson(getStatePath());
137
+ }
138
+
139
+ /**
140
+ * Save state
141
+ */
142
+ export async function saveState(state) {
143
+ await writeJson(getStatePath(), state);
144
+ }
145
+
146
+ /**
147
+ * Get project name from config
148
+ */
149
+ export async function getProjectName() {
150
+ const config = await loadConfig();
151
+ return config.project?.name || getCurrentDirName();
152
+ }
package/src/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Main Exports
3
+ // Spec Hash: 0fe43335f5a325e3279a079ce616c052
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ export { initCommand } from './commands/init.js';
7
+ export { startCommand } from './commands/start.js';
8
+ export { statusCommand } from './commands/status.js';
9
+ export { lockCommand } from './commands/lock.js';
10
+ export { doctorCommand } from './commands/doctor.js';
11
+
12
+ export { VERSION, SPEC_HASH, STATES } from './config/constants.js';
@@ -0,0 +1,36 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Branding & ASCII Art
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ import chalk from 'chalk';
6
+ import { VERSION } from '../config/constants.js';
7
+
8
+ export const LOGO = `
9
+ ██╗ ██╗██╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗
10
+ ██║ ██║██║██╔══██╗██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔════╝
11
+ ██║ ██║██║██████╔╝█████╗ ██║ ██║ ██║██║ ██║█████╗
12
+ ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██║ ██║██║ ██║██╔══╝
13
+ ╚████╔╝ ██║██████╔╝███████╗╚██████╗╚██████╔╝██████╔╝███████╗
14
+ ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
15
+ `;
16
+
17
+ export function printLogo() {
18
+ console.log(chalk.cyan(LOGO));
19
+ console.log(chalk.gray.italic(' "Build with Discipline"'));
20
+ console.log(chalk.gray(` Version ${VERSION}`));
21
+ console.log();
22
+ }
23
+
24
+ export function printWelcome() {
25
+ printLogo();
26
+ console.log(chalk.cyan('🏗️ Chào mừng! Tôi là Kiến trúc sư của bạn.'));
27
+ console.log();
28
+ console.log(chalk.white(' Tôi đã thiết kế hàng triệu sản phẩm số.'));
29
+ console.log(chalk.white(' Tôi sẽ dẫn dắt bạn qua từng bước.'));
30
+ console.log(chalk.white(' Bạn chỉ cần cho tôi biết MỤC TIÊU.'));
31
+ console.log();
32
+ }
33
+
34
+ export function printDivider() {
35
+ console.log(chalk.gray('─'.repeat(70)));
36
+ }
@@ -0,0 +1,96 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Output Formatting
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ import chalk from 'chalk';
6
+ import boxen from 'boxen';
7
+ import { PROGRESS_MAP, COLORS } from '../config/constants.js';
8
+
9
+ /**
10
+ * Print boxed message
11
+ */
12
+ export function printBox(content, options = {}) {
13
+ const defaultOptions = {
14
+ padding: 1,
15
+ borderStyle: 'round',
16
+ borderColor: 'cyan'
17
+ };
18
+ console.log(boxen(content, { ...defaultOptions, ...options }));
19
+ }
20
+
21
+ /**
22
+ * Print success message
23
+ */
24
+ export function printSuccess(message) {
25
+ console.log(chalk.green('✅ ' + message));
26
+ }
27
+
28
+ /**
29
+ * Print error message
30
+ */
31
+ export function printError(message) {
32
+ console.log(chalk.red('❌ ' + message));
33
+ }
34
+
35
+ /**
36
+ * Print warning message
37
+ */
38
+ export function printWarning(message) {
39
+ console.log(chalk.yellow('⚠️ ' + message));
40
+ }
41
+
42
+ /**
43
+ * Print info message
44
+ */
45
+ export function printInfo(message) {
46
+ console.log(chalk.blue('ℹ️ ' + message));
47
+ }
48
+
49
+ /**
50
+ * Print progress bar based on state
51
+ */
52
+ export function printProgress(state) {
53
+ const progress = PROGRESS_MAP[state] || PROGRESS_MAP.INIT;
54
+
55
+ const bar = ` ${progress.intake} INTAKE ${progress.blueprint} BLUEPRINT ${progress.contract} CONTRACT ${progress.build} BUILD ${progress.ship} SHIP`;
56
+
57
+ console.log(chalk.gray('┌─ Progress ─────────────────────────────────────────────────────────┐'));
58
+ console.log(chalk.white(bar));
59
+ console.log(chalk.gray('└────────────────────────────────────────────────────────────────────┘'));
60
+ }
61
+
62
+ /**
63
+ * Print status info
64
+ */
65
+ export function printStatus(projectName, state, sessionId, specHash = null) {
66
+ console.log();
67
+ console.log(chalk.cyan(`📍 Project: ${chalk.white(projectName)}`));
68
+ console.log(chalk.cyan(`📊 State: ${chalk.white(state)}`));
69
+ console.log(chalk.cyan(`📋 Session: ${chalk.gray(sessionId || 'none')}`));
70
+
71
+ if (specHash) {
72
+ console.log(chalk.cyan(`🔒 Spec Hash: ${chalk.green(specHash)}`));
73
+ } else {
74
+ console.log(chalk.cyan(`📄 Spec Hash: ${chalk.gray('(not locked)')}`));
75
+ }
76
+ console.log();
77
+ }
78
+
79
+ /**
80
+ * Print file tree
81
+ */
82
+ export function printFileTree(files) {
83
+ console.log(chalk.cyan('📁 Files:'));
84
+ files.forEach((file, index) => {
85
+ const prefix = index === files.length - 1 ? '└── ' : '├── ';
86
+ console.log(chalk.gray(` ${prefix}${file}`));
87
+ });
88
+ }
89
+
90
+ /**
91
+ * Print next step hint
92
+ */
93
+ export function printNextStep(message) {
94
+ console.log();
95
+ console.log(chalk.yellow(`💡 Next: ${message}`));
96
+ }
@@ -0,0 +1,120 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Interactive Prompts
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ import inquirer from 'inquirer';
6
+ import chalk from 'chalk';
7
+
8
+ /**
9
+ * Ask for project description
10
+ */
11
+ export async function askProjectDescription() {
12
+ const { description } = await inquirer.prompt([
13
+ {
14
+ type: 'editor',
15
+ name: 'description',
16
+ message: 'Mô tả ý tưởng dự án của bạn (mở editor):',
17
+ waitForUseInput: false
18
+ }
19
+ ]);
20
+
21
+ // If editor fails, fall back to input
22
+ if (!description || description.trim() === '') {
23
+ const { fallback } = await inquirer.prompt([
24
+ {
25
+ type: 'input',
26
+ name: 'fallback',
27
+ message: chalk.cyan('📝 Bạn muốn build gì hôm nay?\n') + chalk.gray(' (Mô tả bằng ngôn ngữ tự nhiên)\n\n >'),
28
+ }
29
+ ]);
30
+ return fallback;
31
+ }
32
+
33
+ return description;
34
+ }
35
+
36
+ /**
37
+ * Simple text input for description
38
+ */
39
+ export async function askSimpleDescription() {
40
+ console.log();
41
+ const { description } = await inquirer.prompt([
42
+ {
43
+ type: 'input',
44
+ name: 'description',
45
+ message: chalk.cyan('📝 Bạn muốn build gì hôm nay?'),
46
+ validate: (input) => {
47
+ if (input.trim().length < 10) {
48
+ return 'Vui lòng mô tả chi tiết hơn (ít nhất 10 ký tự)';
49
+ }
50
+ return true;
51
+ }
52
+ }
53
+ ]);
54
+ return description;
55
+ }
56
+
57
+ /**
58
+ * Confirm action
59
+ */
60
+ export async function confirmAction(message) {
61
+ const { confirm } = await inquirer.prompt([
62
+ {
63
+ type: 'confirm',
64
+ name: 'confirm',
65
+ message: message,
66
+ default: true
67
+ }
68
+ ]);
69
+ return confirm;
70
+ }
71
+
72
+ /**
73
+ * Select from options
74
+ */
75
+ export async function selectOption(message, choices) {
76
+ const { selection } = await inquirer.prompt([
77
+ {
78
+ type: 'list',
79
+ name: 'selection',
80
+ message: message,
81
+ choices: choices
82
+ }
83
+ ]);
84
+ return selection;
85
+ }
86
+
87
+ /**
88
+ * Ask what to do next based on state
89
+ */
90
+ export async function askNextAction(state) {
91
+ const actions = {
92
+ 'INIT': [
93
+ { name: 'Capture intake (mô tả dự án)', value: 'intake' },
94
+ { name: 'Exit', value: 'exit' }
95
+ ],
96
+ 'INTAKE_CAPTURED': [
97
+ { name: 'Create blueprint', value: 'blueprint' },
98
+ { name: 'View intake', value: 'view_intake' },
99
+ { name: 'Exit', value: 'exit' }
100
+ ],
101
+ 'BLUEPRINT_DRAFTED': [
102
+ { name: 'Create contract', value: 'contract' },
103
+ { name: 'Edit blueprint', value: 'edit_blueprint' },
104
+ { name: 'Exit', value: 'exit' }
105
+ ],
106
+ 'CONTRACT_DRAFTED': [
107
+ { name: 'Lock contract (vibecode lock)', value: 'lock' },
108
+ { name: 'Edit contract', value: 'edit_contract' },
109
+ { name: 'Exit', value: 'exit' }
110
+ ],
111
+ 'CONTRACT_LOCKED': [
112
+ { name: 'Ready for build! (Phase B)', value: 'ready' },
113
+ { name: 'View contract', value: 'view_contract' },
114
+ { name: 'Exit', value: 'exit' }
115
+ ]
116
+ };
117
+
118
+ const choices = actions[state] || actions['INIT'];
119
+ return await selectOption('What would you like to do?', choices);
120
+ }