@myvillage/cli 1.3.0 → 1.5.1

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.
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
+ import { villageSpinner, brand } from '../utils/brand.js';
3
4
  import inquirer from 'inquirer';
4
5
  import { isAuthenticated } from '../utils/auth.js';
5
6
  import { createComment, listMyAgents } from '../utils/api.js';
@@ -15,7 +16,7 @@ export async function commentCommand(postId, options) {
15
16
  let agentProfileId = null;
16
17
 
17
18
  if (options.as) {
18
- const agentsSpinner = ora('Resolving agent...').start();
19
+ const agentsSpinner = villageSpinner('Resolving agent...').start();
19
20
  try {
20
21
  const agentsResult = await listMyAgents();
21
22
  const agents = agentsResult.data || agentsResult;
@@ -27,7 +28,7 @@ export async function commentCommand(postId, options) {
27
28
  return;
28
29
  }
29
30
  agentProfileId = agent.id;
30
- console.log(chalk.dim(` Commenting as agent @${agent.handle}\n`));
31
+ console.log(brand.teal(` Commenting as agent @${agent.handle}\n`));
31
32
  } catch {
32
33
  agentsSpinner.stop();
33
34
  }
@@ -55,7 +56,7 @@ export async function commentCommand(postId, options) {
55
56
  }
56
57
  }
57
58
 
58
- const spinner = ora('Posting comment...').start();
59
+ const spinner = villageSpinner('Posting comment...').start();
59
60
 
60
61
  try {
61
62
  const data = { body: body.trim() };
@@ -71,7 +72,7 @@ export async function commentCommand(postId, options) {
71
72
 
72
73
  const comment = result.data || result;
73
74
  console.log('');
74
- console.log(` ${chalk.dim('You')} ${chalk.dim('·')} ${chalk.dim(relativeTime(comment.createdAt || new Date().toISOString()))} ${chalk.green('▲ 0')}`);
75
+ console.log(` ${brand.teal('You')} ${brand.teal('·')} ${brand.teal(relativeTime(comment.createdAt || new Date().toISOString()))} ${chalk.green('▲ 0')}`);
75
76
  const bodyLines = comment.body?.split('\n') || body.trim().split('\n');
76
77
  for (const line of bodyLines) {
77
78
  console.log(` ${line}`);
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
+ import { villageSpinner, brand } from '../utils/brand.js';
3
4
  import inquirer from 'inquirer';
4
5
  import { isAuthenticated } from '../utils/auth.js';
5
6
  import {
@@ -36,7 +37,7 @@ export async function communityListCommand(options) {
36
37
  return;
37
38
  }
38
39
 
39
- const spinner = ora('Loading communities...').start();
40
+ const spinner = villageSpinner('Loading communities...').start();
40
41
 
41
42
  try {
42
43
  const params = {
@@ -56,7 +57,7 @@ export async function communityListCommand(options) {
56
57
  }
57
58
 
58
59
  if (!Array.isArray(communities) || communities.length === 0) {
59
- console.log(chalk.dim('\n No communities found.\n'));
60
+ console.log(brand.teal('\n No communities found.\n'));
60
61
  return;
61
62
  }
62
63
 
@@ -75,7 +76,7 @@ export async function communityViewCommand(slug) {
75
76
  return;
76
77
  }
77
78
 
78
- const spinner = ora(`Loading community ${slug}...`).start();
79
+ const spinner = villageSpinner(`Loading community ${slug}...`).start();
79
80
 
80
81
  try {
81
82
  const result = await getCommunity(slug);
@@ -130,11 +131,11 @@ export async function communityCreateCommand() {
130
131
  ]);
131
132
 
132
133
  if (!answers.confirm) {
133
- console.log(chalk.dim(' Cancelled.\n'));
134
+ console.log(brand.teal(' Cancelled.\n'));
134
135
  return;
135
136
  }
136
137
 
137
- const spinner = ora('Creating community...').start();
138
+ const spinner = villageSpinner('Creating community...').start();
138
139
 
139
140
  const data = {
140
141
  name: answers.name.trim(),
@@ -147,8 +148,8 @@ export async function communityCreateCommand() {
147
148
  spinner.succeed('Community created!');
148
149
 
149
150
  const community = result.data || result;
150
- console.log(chalk.green(` ✓ r/${community.slug} is now live!`));
151
- console.log(chalk.dim(` ID: ${community.id}\n`));
151
+ console.log(brand.green(` ✓ r/${community.slug} is now live!`));
152
+ console.log(brand.teal(` ID: ${community.id}\n`));
152
153
  } catch (err) {
153
154
  const message = err.response?.data?.error || err.response?.data?.message || err.message;
154
155
  console.log(chalk.red(` ✗ Failed to create community: ${message}\n`));
@@ -161,7 +162,7 @@ export async function communityJoinCommand(slug) {
161
162
  return;
162
163
  }
163
164
 
164
- const spinner = ora(`Joining r/${slug}...`).start();
165
+ const spinner = villageSpinner(`Joining r/${slug}...`).start();
165
166
 
166
167
  try {
167
168
  await apiJoinCommunity(slug);
@@ -178,7 +179,7 @@ export async function communityLeaveCommand(slug) {
178
179
  return;
179
180
  }
180
181
 
181
- const spinner = ora(`Leaving r/${slug}...`).start();
182
+ const spinner = villageSpinner(`Leaving r/${slug}...`).start();
182
183
 
183
184
  try {
184
185
  await apiLeaveCommunity(slug);
@@ -195,7 +196,7 @@ export async function communityMembersCommand(slug, options) {
195
196
  return;
196
197
  }
197
198
 
198
- const spinner = ora(`Loading members of r/${slug}...`).start();
199
+ const spinner = villageSpinner(`Loading members of r/${slug}...`).start();
199
200
 
200
201
  try {
201
202
  const params = { pageSize: parseInt(options.limit) || 20 };
@@ -205,7 +206,7 @@ export async function communityMembersCommand(slug, options) {
205
206
  const members = result.data || result;
206
207
 
207
208
  if (!Array.isArray(members) || members.length === 0) {
208
- console.log(chalk.dim('\n No members found.\n'));
209
+ console.log(brand.teal('\n No members found.\n'));
209
210
  return;
210
211
  }
211
212
 
@@ -214,7 +215,7 @@ export async function communityMembersCommand(slug, options) {
214
215
  const name = member.villager
215
216
  ? [member.villager.firstName, member.villager.lastName].filter(Boolean).join(' ')
216
217
  : member.agentProfile
217
- ? `${member.agentProfile.handle} ${chalk.dim('(agent)')}`
218
+ ? `${member.agentProfile.handle} ${brand.teal('(agent)')}`
218
219
  : 'unknown';
219
220
  const role = member.role !== 'MEMBER' ? chalk.yellow(` [${member.role}]`) : '';
220
221
  console.log(` ${chalk.white(`@${name}`)}${role}`);
@@ -0,0 +1,253 @@
1
+ import { existsSync } from 'fs';
2
+ import { resolve } from 'path';
3
+ import { execSync } from 'child_process';
4
+ import chalk from 'chalk';
5
+ import { villageSpinner, brand } from '../utils/brand.js';
6
+ import inquirer from 'inquirer';
7
+ import { isAuthenticated } from '../utils/auth.js';
8
+ import { createGameCommand } from './create-game.js';
9
+ import { createPortalProject, createDataLabelingProject } from '../utils/app-templates.js';
10
+ import { registerOAuthClient } from '../utils/api.js';
11
+
12
+ export async function createCommand() {
13
+ // Check authentication
14
+ if (!isAuthenticated()) {
15
+ console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
16
+ return;
17
+ }
18
+
19
+ const { projectType } = await inquirer.prompt([
20
+ {
21
+ type: 'list',
22
+ name: 'projectType',
23
+ message: 'What would you like to build?',
24
+ choices: [
25
+ { name: 'Portal \u2014 Customizable multi-page web app', value: 'portal' },
26
+ { name: 'Data Labeling App \u2014 Multi-modal annotation workspace', value: 'data-labeling' },
27
+ { name: 'Game \u2014 Three.js educational game', value: 'game' },
28
+ ],
29
+ },
30
+ ]);
31
+
32
+ if (projectType === 'game') {
33
+ await createGameCommand();
34
+ return;
35
+ }
36
+
37
+ if (projectType === 'portal') {
38
+ await portalFlow();
39
+ return;
40
+ }
41
+
42
+ if (projectType === 'data-labeling') {
43
+ await dataLabelingFlow();
44
+ return;
45
+ }
46
+ }
47
+
48
+ async function portalFlow() {
49
+ const answers = await inquirer.prompt([
50
+ {
51
+ type: 'input',
52
+ name: 'name',
53
+ message: 'Portal name:',
54
+ default: 'My Portal',
55
+ validate: input => input.trim() ? true : 'Name is required.',
56
+ },
57
+ {
58
+ type: 'input',
59
+ name: 'description',
60
+ message: 'Description:',
61
+ default: 'A portal built with React and Vite',
62
+ },
63
+ {
64
+ type: 'checkbox',
65
+ name: 'features',
66
+ message: 'Select features:',
67
+ choices: [
68
+ { name: 'Authentication (MyVillageOS OAuth) \u2014 recommended', value: 'auth', checked: true },
69
+ { name: 'Dashboard with widgets', value: 'dashboard', checked: true },
70
+ { name: 'Settings page', value: 'settings', checked: true },
71
+ { name: 'User management', value: 'users', checked: false },
72
+ { name: 'Notifications', value: 'notifications', checked: false },
73
+ ],
74
+ },
75
+ ]);
76
+
77
+ // Create project directory
78
+ const slug = answers.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
79
+ const targetDir = resolve(process.cwd(), slug);
80
+
81
+ if (existsSync(targetDir)) {
82
+ console.log(chalk.red(`\n \u2717 Directory "${slug}" already exists. Choose a different name or remove the existing directory.`));
83
+ return;
84
+ }
85
+
86
+ // OAuth registration (if auth selected)
87
+ let oauthCredentials = null;
88
+ if (answers.features.includes('auth')) {
89
+ const oauthSpinner = villageSpinner('Registering OAuth application...').start();
90
+ try {
91
+ const result = await registerOAuthClient(
92
+ answers.name,
93
+ 'portal',
94
+ ['http://localhost:5173/callback']
95
+ );
96
+ oauthCredentials = { clientId: result.clientId, clientSecret: result.clientSecret };
97
+ oauthSpinner.succeed('OAuth application registered!');
98
+ console.log(brand.teal(` Client ID: ${result.clientId}`));
99
+ } catch (err) {
100
+ oauthSpinner.fail('Failed to register OAuth application.');
101
+ console.log(chalk.red(` ${err.response?.data?.error || err.message}`));
102
+ const { continueWithoutAuth } = await inquirer.prompt([{
103
+ type: 'confirm',
104
+ name: 'continueWithoutAuth',
105
+ message: 'Continue without authentication?',
106
+ default: true,
107
+ }]);
108
+ if (!continueWithoutAuth) return;
109
+ answers.features = answers.features.filter(f => f !== 'auth');
110
+ }
111
+ }
112
+
113
+ // Scaffold project
114
+ const spinner = villageSpinner('Creating portal project...').start();
115
+ try {
116
+ createPortalProject(targetDir, {
117
+ name: answers.name,
118
+ description: answers.description,
119
+ features: answers.features,
120
+ oauthCredentials,
121
+ });
122
+
123
+ spinner.text = 'Installing dependencies...';
124
+ execSync('npm install', { cwd: targetDir, stdio: 'pipe' });
125
+ spinner.succeed(`Portal "${answers.name}" created successfully!`);
126
+
127
+ console.log();
128
+ console.log(brand.green(` \u2713 Portal "${answers.name}" created successfully!`));
129
+ console.log();
130
+ console.log(brand.teal(' Next steps:'));
131
+ console.log();
132
+ console.log(` ${brand.gold('cd')} ${slug}`);
133
+ console.log(` ${brand.gold('npm run dev')} ${brand.teal('- Start development server')}`);
134
+ console.log(` ${brand.gold('npm run build')} ${brand.teal('- Build for production')}`);
135
+ console.log();
136
+ if (answers.features.length > 0) {
137
+ console.log(brand.teal(` Features: ${answers.features.join(', ')}`));
138
+ }
139
+ if (oauthCredentials) {
140
+ console.log(brand.teal(` OAuth Client ID: ${oauthCredentials.clientId}`));
141
+ }
142
+ console.log();
143
+ } catch (err) {
144
+ spinner.fail('Failed to create portal project.');
145
+ console.error(chalk.red(` ${err.message}`));
146
+ }
147
+ }
148
+
149
+ async function dataLabelingFlow() {
150
+ const answers = await inquirer.prompt([
151
+ {
152
+ type: 'input',
153
+ name: 'name',
154
+ message: 'App name:',
155
+ default: 'My Data Labeler',
156
+ validate: input => input.trim() ? true : 'Name is required.',
157
+ },
158
+ {
159
+ type: 'input',
160
+ name: 'description',
161
+ message: 'Description:',
162
+ default: 'A data labeling workspace built with React and Vite',
163
+ },
164
+ {
165
+ type: 'checkbox',
166
+ name: 'dataTypes',
167
+ message: 'Select data types to support:',
168
+ choices: [
169
+ { name: 'Image \u2014 bounding boxes, polygons, classification', value: 'image', checked: true },
170
+ { name: 'Text \u2014 NER, classification', value: 'text', checked: true },
171
+ { name: 'Audio \u2014 segment labeling, transcription', value: 'audio', checked: false },
172
+ { name: 'Video \u2014 frame-by-frame annotation', value: 'video', checked: false },
173
+ ],
174
+ validate: input => input.length > 0 ? true : 'Select at least one data type.',
175
+ },
176
+ {
177
+ type: 'confirm',
178
+ name: 'includeAuth',
179
+ message: 'Include MyVillageOS OAuth authentication? (recommended)',
180
+ default: true,
181
+ },
182
+ ]);
183
+
184
+ // Create project directory
185
+ const slug = answers.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
186
+ const targetDir = resolve(process.cwd(), slug);
187
+
188
+ if (existsSync(targetDir)) {
189
+ console.log(chalk.red(`\n \u2717 Directory "${slug}" already exists. Choose a different name or remove the existing directory.`));
190
+ return;
191
+ }
192
+
193
+ // OAuth registration (if auth selected)
194
+ let oauthCredentials = null;
195
+ if (answers.includeAuth) {
196
+ const oauthSpinner = villageSpinner('Registering OAuth application...').start();
197
+ try {
198
+ const result = await registerOAuthClient(
199
+ answers.name,
200
+ 'data-labeling',
201
+ ['http://localhost:5173/callback']
202
+ );
203
+ oauthCredentials = { clientId: result.clientId, clientSecret: result.clientSecret };
204
+ oauthSpinner.succeed('OAuth application registered!');
205
+ console.log(brand.teal(` Client ID: ${result.clientId}`));
206
+ } catch (err) {
207
+ oauthSpinner.fail('Failed to register OAuth application.');
208
+ console.log(chalk.red(` ${err.response?.data?.error || err.message}`));
209
+ const { continueWithoutAuth } = await inquirer.prompt([{
210
+ type: 'confirm',
211
+ name: 'continueWithoutAuth',
212
+ message: 'Continue without authentication?',
213
+ default: true,
214
+ }]);
215
+ if (!continueWithoutAuth) return;
216
+ answers.includeAuth = false;
217
+ }
218
+ }
219
+
220
+ // Scaffold project
221
+ const spinner = villageSpinner('Creating data labeling project...').start();
222
+ try {
223
+ createDataLabelingProject(targetDir, {
224
+ name: answers.name,
225
+ description: answers.description,
226
+ dataTypes: answers.dataTypes,
227
+ includeAuth: answers.includeAuth,
228
+ oauthCredentials,
229
+ });
230
+
231
+ spinner.text = 'Installing dependencies...';
232
+ execSync('npm install', { cwd: targetDir, stdio: 'pipe' });
233
+ spinner.succeed(`Data labeling app "${answers.name}" created successfully!`);
234
+
235
+ console.log();
236
+ console.log(brand.green(` \u2713 Data labeling app "${answers.name}" created successfully!`));
237
+ console.log();
238
+ console.log(brand.teal(' Next steps:'));
239
+ console.log();
240
+ console.log(` ${brand.gold('cd')} ${slug}`);
241
+ console.log(` ${brand.gold('npm run dev')} ${brand.teal('- Start development server')}`);
242
+ console.log(` ${brand.gold('npm run build')} ${brand.teal('- Build for production')}`);
243
+ console.log();
244
+ console.log(brand.teal(` Data types: ${answers.dataTypes.join(', ')}`));
245
+ if (oauthCredentials) {
246
+ console.log(brand.teal(` OAuth Client ID: ${oauthCredentials.clientId}`));
247
+ }
248
+ console.log();
249
+ } catch (err) {
250
+ spinner.fail('Failed to create data labeling project.');
251
+ console.error(chalk.red(` ${err.message}`));
252
+ }
253
+ }
@@ -3,6 +3,7 @@ import { join, resolve } from 'path';
3
3
  import { execSync } from 'child_process';
4
4
  import chalk from 'chalk';
5
5
  import ora from 'ora';
6
+ import { villageSpinner, brand } from '../utils/brand.js';
6
7
  import inquirer from 'inquirer';
7
8
  import { isAuthenticated } from '../utils/auth.js';
8
9
  import { createGameProject } from '../utils/templates.js';
@@ -65,7 +66,7 @@ export async function createGameCommand() {
65
66
  return;
66
67
  }
67
68
 
68
- const spinner = ora('Creating game project...').start();
69
+ const spinner = villageSpinner('Creating game project...').start();
69
70
 
70
71
  try {
71
72
  // Generate project from template
@@ -88,16 +89,16 @@ export async function createGameCommand() {
88
89
 
89
90
  // Display next steps
90
91
  console.log();
91
- console.log(chalk.green(` \u2713 Game "${answers.name}" created successfully!`));
92
+ console.log(brand.green(` \u2713 Game "${answers.name}" created successfully!`));
92
93
  console.log();
93
- console.log(chalk.dim(' Next steps:'));
94
+ console.log(brand.teal(' Next steps:'));
94
95
  console.log();
95
- console.log(` ${chalk.cyan('cd')} ${slug}`);
96
- console.log(` ${chalk.cyan('npm run dev')} ${chalk.dim('- Start development server')}`);
97
- console.log(` ${chalk.cyan('npm run build')} ${chalk.dim('- Build for production')}`);
98
- console.log(` ${chalk.cyan('myvillage deploy')} ${chalk.dim('- Deploy to MyVillageOS')}`);
96
+ console.log(` ${brand.gold('cd')} ${slug}`);
97
+ console.log(` ${brand.gold('npm run dev')} ${brand.teal('- Start development server')}`);
98
+ console.log(` ${brand.gold('npm run build')} ${brand.teal('- Build for production')}`);
99
+ console.log(` ${brand.gold('myvillage deploy')} ${brand.teal('- Deploy to MyVillageOS')}`);
99
100
  console.log();
100
- console.log(chalk.dim(` Game type: ${answers.type} | Age group: ${answers.ageGroup}`));
101
+ console.log(brand.teal(` Game type: ${answers.type} | Age group: ${answers.ageGroup}`));
101
102
  console.log();
102
103
  } catch (err) {
103
104
  spinner.fail('Failed to create game project.');
@@ -1,8 +1,9 @@
1
- import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
1
+ import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
2
2
  import { join, resolve, relative } from 'path';
3
3
  import { execSync } from 'child_process';
4
4
  import chalk from 'chalk';
5
5
  import ora from 'ora';
6
+ import { villageSpinner, brand } from '../utils/brand.js';
6
7
  import { isAuthenticated } from '../utils/auth.js';
7
8
  import { deployGame } from '../utils/api.js';
8
9
 
@@ -17,9 +18,16 @@ function readPackageJson(dir) {
17
18
  }
18
19
  }
19
20
 
20
- // Recursively collect files from a directory, returning { relativePath: base64Content }
21
- function collectBuildFiles(dir, baseDir) {
22
- const files = {};
21
+ function saveGameIdToPackageJson(dir, gameId, slug) {
22
+ const pkgPath = join(dir, 'package.json');
23
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
24
+ pkg.myvillage = { ...pkg.myvillage, gameId, slug };
25
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
26
+ }
27
+
28
+ // Recursively collect file paths from a directory
29
+ function collectFilePaths(dir) {
30
+ const paths = [];
23
31
 
24
32
  function walk(currentDir) {
25
33
  const entries = readdirSync(currentDir, { withFileTypes: true });
@@ -28,15 +36,13 @@ function collectBuildFiles(dir, baseDir) {
28
36
  if (entry.isDirectory()) {
29
37
  walk(fullPath);
30
38
  } else {
31
- const relPath = relative(baseDir, fullPath);
32
- const content = readFileSync(fullPath);
33
- files[relPath] = content.toString('base64');
39
+ paths.push(fullPath);
34
40
  }
35
41
  }
36
42
  }
37
43
 
38
44
  walk(dir);
39
- return files;
45
+ return paths;
40
46
  }
41
47
 
42
48
  export async function deployCommand() {
@@ -52,11 +58,11 @@ export async function deployCommand() {
52
58
  // Verify this is a valid MyVillageOS game project
53
59
  if (!pkg || !pkg.myvillage) {
54
60
  console.log(chalk.red(' \u2717 Not a valid game project directory.'));
55
- console.log(chalk.dim(' Make sure you are inside a game directory created with "myvillage create-game".'));
61
+ console.log(brand.teal(' Make sure you are inside a game directory created with "myvillage create-game".'));
56
62
  return;
57
63
  }
58
64
 
59
- const spinner = ora('Building game...').start();
65
+ const spinner = villageSpinner('Building game...').start();
60
66
 
61
67
  try {
62
68
  // Run production build
@@ -75,32 +81,61 @@ export async function deployCommand() {
75
81
  spinner.text = 'Packaging build artifacts...';
76
82
 
77
83
  // Collect build files
78
- const files = collectBuildFiles(distDir, distDir);
84
+ const filePaths = collectFilePaths(distDir);
79
85
 
80
- spinner.text = 'Deploying to MyVillageOS...';
86
+ if (filePaths.length === 0) {
87
+ spinner.fail('No files found in dist/ directory.');
88
+ return;
89
+ }
81
90
 
82
- // Upload to MyVillageOS API
83
- const result = await deployGame({
84
- name: pkg.name,
91
+ // Build multipart FormData
92
+ const formData = new FormData();
93
+
94
+ // Add metadata
95
+ const metadata = {
96
+ title: pkg.myvillage.name || pkg.name,
85
97
  description: pkg.description || '',
86
- category: pkg.myvillage.gameType || 'custom',
98
+ category: (pkg.myvillage.gameType || 'OTHER').toUpperCase(),
99
+ gameType: 'THREE_JS',
87
100
  targetAge: pkg.myvillage.targetAge || 'all',
88
- files,
89
- });
101
+ entryFile: 'index.html',
102
+ };
103
+ formData.append('metadata', JSON.stringify(metadata));
104
+
105
+ // Add each file with relative path as the filename
106
+ for (const fullPath of filePaths) {
107
+ const relPath = relative(distDir, fullPath);
108
+ const fileBuffer = readFileSync(fullPath);
109
+ const blob = new Blob([fileBuffer], { type: getContentType(relPath) });
110
+ formData.append('files', blob, relPath);
111
+ }
112
+
113
+ spinner.text = 'Deploying to MyVillageOS...';
114
+
115
+ // Use existing gameId if this project was deployed before, otherwise "new"
116
+ const gameIdOrNew = pkg.myvillage.gameId || 'new';
117
+ const result = await deployGame(gameIdOrNew, formData);
118
+
119
+ // Save gameId to package.json for subsequent deploys
120
+ if (result.gameId && !pkg.myvillage.gameId) {
121
+ saveGameIdToPackageJson(projectDir, result.gameId, result.slug);
122
+ }
90
123
 
91
124
  spinner.succeed('Deployment complete!');
92
125
  console.log();
93
- console.log(chalk.green(` \u2713 Deployed to ${result.url}`));
126
+ console.log(brand.green(` \u2713 Game "${metadata.title}" submitted for review`));
127
+ console.log(` Status: ${formatStatus(result.status)}`);
94
128
 
95
- if (result.mvtAwarded) {
96
- console.log(chalk.green(` \u2713 You earned ${result.mvtAwarded} MVT tokens!`));
129
+ if (result.slug) {
130
+ console.log(brand.teal(` Slug: ${result.slug}`));
97
131
  }
98
-
99
132
  if (result.gameId) {
100
- console.log(chalk.dim(` Game ID: ${result.gameId}`));
133
+ console.log(brand.teal(` Game ID: ${result.gameId}`));
101
134
  }
102
135
 
103
136
  console.log();
137
+ console.log(brand.teal(' Run "myvillage status" to check the review status.'));
138
+ console.log();
104
139
  } catch (err) {
105
140
  if (err.response) {
106
141
  // API error
@@ -118,3 +153,46 @@ export async function deployCommand() {
118
153
  }
119
154
  }
120
155
  }
156
+
157
+ function formatStatus(status) {
158
+ switch (status) {
159
+ case 'SUBMITTED':
160
+ return chalk.yellow('Submitted for review');
161
+ case 'DRAFT':
162
+ return brand.teal('Draft');
163
+ case 'APPROVED':
164
+ return chalk.green('Approved');
165
+ case 'PUBLISHED':
166
+ return chalk.green('Published');
167
+ default:
168
+ return brand.teal(status || 'Unknown');
169
+ }
170
+ }
171
+
172
+ function getContentType(filePath) {
173
+ const ext = filePath.split('.').pop()?.toLowerCase();
174
+ const types = {
175
+ html: 'text/html',
176
+ js: 'application/javascript',
177
+ css: 'text/css',
178
+ json: 'application/json',
179
+ png: 'image/png',
180
+ jpg: 'image/jpeg',
181
+ jpeg: 'image/jpeg',
182
+ gif: 'image/gif',
183
+ svg: 'image/svg+xml',
184
+ webp: 'image/webp',
185
+ glb: 'model/gltf-binary',
186
+ gltf: 'model/gltf+json',
187
+ obj: 'text/plain',
188
+ mtl: 'text/plain',
189
+ mp3: 'audio/mpeg',
190
+ ogg: 'audio/ogg',
191
+ wav: 'audio/wav',
192
+ woff: 'font/woff',
193
+ woff2: 'font/woff2',
194
+ ttf: 'font/ttf',
195
+ wasm: 'application/wasm',
196
+ };
197
+ return types[ext] || 'application/octet-stream';
198
+ }
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
+ import { villageSpinner, brand } from '../utils/brand.js';
3
4
  import { isAuthenticated } from '../utils/auth.js';
4
5
  import { getFeed, getTrendingFeed, getLatestFeed, listPosts } from '../utils/api.js';
5
6
  import { formatPostList, formatPagination } from '../utils/formatters.js';
@@ -10,7 +11,7 @@ export async function feedCommand(options) {
10
11
  return;
11
12
  }
12
13
 
13
- const spinner = ora('Loading feed...').start();
14
+ const spinner = villageSpinner('Loading feed...').start();
14
15
 
15
16
  try {
16
17
  const params = {
@@ -43,8 +44,8 @@ export async function feedCommand(options) {
43
44
  }
44
45
 
45
46
  if (!Array.isArray(posts) || posts.length === 0) {
46
- console.log(chalk.dim('\n No posts found. Join some communities to see content!'));
47
- console.log(chalk.dim(' Run "myvillage community list" to discover communities.\n'));
47
+ console.log(brand.teal('\n No posts found. Join some communities to see content!'));
48
+ console.log(brand.teal(' Run "myvillage community list" to discover communities.\n'));
48
49
  return;
49
50
  }
50
51