@globio/cli 0.1.7 → 0.1.9

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,12 +1,28 @@
1
1
  import * as p from '@clack/prompts';
2
- import chalk from 'chalk';
3
2
  import { config } from '../lib/config.js';
4
3
  import {
5
4
  manageRequest,
6
5
  type ManageOrg,
7
6
  type ManageProject,
8
7
  type ManageProjectKey,
8
+ type ManageProjectServices,
9
9
  } from '../lib/manage.js';
10
+ import {
11
+ footer,
12
+ getCliVersion,
13
+ gold,
14
+ green,
15
+ header,
16
+ inactive,
17
+ muted,
18
+ orange,
19
+ renderTable,
20
+ reset,
21
+ white,
22
+ failure,
23
+ } from '../lib/banner.js';
24
+
25
+ const version = getCliVersion();
10
26
 
11
27
  function slugify(value: string) {
12
28
  return value
@@ -44,30 +60,36 @@ export async function projectsList(options: { profile?: string } = {}) {
44
60
 
45
61
  const projects = await manageRequest<ManageProject[]>('/projects', { profileName });
46
62
  const activeProjectId = config.getProfile(profileName)?.active_project_id;
47
- const grouped = new Map<string, ManageProject[]>();
48
-
49
- for (const project of projects) {
50
- const list = grouped.get(project.org_name) ?? [];
51
- list.push(project);
52
- grouped.set(project.org_name, list);
53
- }
54
63
 
55
- console.log('');
56
64
  if (!projects.length) {
57
- console.log(chalk.gray('No projects found.'));
58
- console.log('');
65
+ console.log(header(version) + ' ' + muted('No projects found.') + '\n');
59
66
  return;
60
67
  }
61
68
 
62
- for (const [orgName, orgProjects] of grouped.entries()) {
63
- console.log(chalk.cyan(`org: ${orgName}`));
64
- for (const project of orgProjects) {
65
- const marker = project.id === activeProjectId ? chalk.green('●') : chalk.gray('○');
66
- const active = project.id === activeProjectId ? chalk.green(' (active)') : '';
67
- console.log(` ${marker} ${project.slug.padEnd(22)} ${chalk.gray(project.id)}${active}`);
68
- }
69
- console.log('');
70
- }
69
+ const rows = projects.map((project) => [
70
+ activeProjectId === project.id
71
+ ? gold(project.name) + reset + ' ' + orange('●') + reset
72
+ : white(project.name),
73
+ muted(project.id),
74
+ muted(project.org_name || project.org_id),
75
+ inactive(project.environment?.slice(0, 4) ?? 'dev'),
76
+ ]);
77
+
78
+ console.log(header(version));
79
+ console.log(
80
+ renderTable({
81
+ columns: [
82
+ { header: 'Project', width: 24 },
83
+ { header: 'ID', width: 26 },
84
+ { header: 'Org', width: 16 },
85
+ { header: 'Env', width: 6 },
86
+ ],
87
+ rows,
88
+ })
89
+ );
90
+ console.log(
91
+ footer('● active project · run globio projects use <id> to switch')
92
+ );
71
93
  }
72
94
 
73
95
  export async function projectsUse(projectId: string, options: { profile?: string } = {}) {
@@ -77,7 +99,7 @@ export async function projectsUse(projectId: string, options: { profile?: string
77
99
  const projects = await manageRequest<ManageProject[]>('/projects', { profileName });
78
100
  const project = projects.find((item) => item.id === projectId);
79
101
  if (!project) {
80
- console.log(chalk.red(`Project not found: ${projectId}`));
102
+ console.log(failure(`Project not found: ${projectId}`));
81
103
  process.exit(1);
82
104
  }
83
105
 
@@ -87,13 +109,12 @@ export async function projectsUse(projectId: string, options: { profile?: string
87
109
  config.setProfile(profileName, {
88
110
  active_project_id: project.id,
89
111
  active_project_name: project.name,
112
+ org_name: project.org_name,
90
113
  project_api_key: apiKey,
91
114
  });
92
115
  config.setActiveProfile(profileName);
93
116
 
94
- console.log(
95
- chalk.green('Active project set to: ') + chalk.cyan(`${project.name} (${project.id})`)
96
- );
117
+ console.log(green('Active project: ') + `${project.name} (${project.id})`);
97
118
  }
98
119
 
99
120
  export async function projectsCreate(options: { profile?: string } = {}) {
@@ -102,7 +123,7 @@ export async function projectsCreate(options: { profile?: string } = {}) {
102
123
 
103
124
  const orgs = await manageRequest<ManageOrg[]>('/orgs', { profileName });
104
125
  if (!orgs.length) {
105
- console.log(chalk.red('No organizations found. Create one in the console first.'));
126
+ console.log(failure('No organizations found. Create one in the console first.'));
106
127
  process.exit(1);
107
128
  }
108
129
 
@@ -168,14 +189,15 @@ export async function projectsCreate(options: { profile?: string } = {}) {
168
189
  config.setProfile(profileName, {
169
190
  active_project_id: result.project.id,
170
191
  active_project_name: result.project.name,
192
+ org_name: orgs.find((org) => org.id === orgId)?.name,
171
193
  project_api_key: result.keys.server,
172
194
  });
173
195
  config.setActiveProfile(profileName);
174
196
 
175
197
  console.log('');
176
- console.log(chalk.green('Project created successfully.'));
177
- console.log(chalk.cyan('Project: ') + `${result.project.name} (${result.project.id})`);
178
- console.log(chalk.cyan('Client key: ') + result.keys.client);
179
- console.log(chalk.cyan('Server key: ') + result.keys.server);
198
+ console.log(green('Project created successfully.'));
199
+ console.log(orange('Project: ') + reset + `${result.project.name} (${result.project.id})`);
200
+ console.log(orange('Client key: ') + reset + result.keys.client);
201
+ console.log(orange('Server key: ') + reset + result.keys.server);
180
202
  console.log('');
181
203
  }
@@ -1,30 +1,70 @@
1
- import chalk from 'chalk';
2
1
  import { config } from '../lib/config.js';
2
+ import { manageRequest, type ManageProjectServices } from '../lib/manage.js';
3
+ import {
4
+ footer,
5
+ getCliVersion,
6
+ green,
7
+ header,
8
+ inactive,
9
+ muted,
10
+ orange,
11
+ renderTable,
12
+ } from '../lib/banner.js';
3
13
 
4
- const ALL_SERVICES = [
5
- 'id',
6
- 'doc',
7
- 'vault',
8
- 'pulse',
9
- 'scope',
10
- 'sync',
11
- 'signal',
12
- 'mart',
13
- 'brain',
14
- 'code',
15
- ];
14
+ const version = getCliVersion();
15
+
16
+ const SERVICE_DESCRIPTIONS: Record<string, string> = {
17
+ id: 'Authentication and user management',
18
+ doc: 'Document database',
19
+ vault: 'File storage',
20
+ pulse: 'Feature flags and remote config',
21
+ scope: 'Analytics and event tracking',
22
+ sync: 'Real-time multiplayer rooms',
23
+ signal: 'Push notifications',
24
+ mart: 'Game economy and payments',
25
+ brain: 'AI agents and LLM routing',
26
+ code: 'Edge functions and GC Hooks',
27
+ };
16
28
 
17
29
  export async function servicesList(options: { profile?: string } = {}) {
18
- void options.profile;
19
- void config;
20
- console.log('');
21
- console.log(chalk.cyan('Available Globio services:'));
22
- ALL_SERVICES.forEach((service) => {
23
- console.log(' ' + chalk.white(service));
30
+ const profileName = options.profile ?? config.getActiveProfile() ?? 'default';
31
+ const profile = config.getProfile(profileName);
32
+ let serviceStatuses: ManageProjectServices = {};
33
+
34
+ if (profile?.active_project_id) {
35
+ try {
36
+ serviceStatuses = await manageRequest<ManageProjectServices>(
37
+ `/projects/${profile.active_project_id}/services`,
38
+ { profileName }
39
+ );
40
+ } catch {
41
+ serviceStatuses = {};
42
+ }
43
+ }
44
+
45
+ const rows = Object.entries(SERVICE_DESCRIPTIONS).map(([slug, desc]) => {
46
+ const enabled = serviceStatuses[slug] ?? null;
47
+ return [
48
+ orange(slug),
49
+ muted(desc),
50
+ enabled === true
51
+ ? green('enabled')
52
+ : enabled === false
53
+ ? inactive('disabled')
54
+ : inactive('—'),
55
+ ];
24
56
  });
25
- console.log('');
57
+
58
+ console.log(header(version));
26
59
  console.log(
27
- chalk.gray('Manage service access via console.globio.stanlink.online')
60
+ renderTable({
61
+ columns: [
62
+ { header: 'Service', width: 10 },
63
+ { header: 'Description', width: 42 },
64
+ { header: 'Status', width: 10 },
65
+ ],
66
+ rows,
67
+ })
28
68
  );
29
- console.log('');
69
+ console.log(footer('Manage services at console.globio.stanlink.online'));
30
70
  }
package/src/lib/api.ts CHANGED
@@ -50,3 +50,24 @@ export async function docSet(
50
50
  profile,
51
51
  });
52
52
  }
53
+
54
+ export async function createIndex(
55
+ collection: string,
56
+ field: string,
57
+ fieldType: 'string' | 'number' | 'boolean' = 'string',
58
+ profile?: string
59
+ ): Promise<void> {
60
+ void fieldType;
61
+ try {
62
+ await apiCall(`/doc/${collection}/indexes`, {
63
+ method: 'POST',
64
+ body: {
65
+ field_path: field,
66
+ index_type: 'asc',
67
+ },
68
+ profile,
69
+ });
70
+ } catch {
71
+ // Index may already exist. Ignore duplicate-style failures.
72
+ }
73
+ }
package/src/lib/banner.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from 'fs';
2
2
  import figlet from 'figlet';
3
3
  import gradientString from 'gradient-string';
4
+ export * from './table.js';
4
5
 
5
6
  const globioGradient = gradientString(
6
7
  '#e85d04',
@@ -39,12 +40,6 @@ export function printInfo(message: string) {
39
40
  console.log('\x1b[2m›\x1b[0m ' + message);
40
41
  }
41
42
 
42
- export const orange = (s: string) => '\x1b[38;2;244;140;6m' + s + '\x1b[0m';
43
-
44
- export const gold = (s: string) => '\x1b[38;2;255;208;0m' + s + '\x1b[0m';
45
-
46
- export const muted = (s: string) => '\x1b[2m' + s + '\x1b[0m';
47
-
48
43
  export function getCliVersion() {
49
44
  const file = readFileSync(new URL('../package.json', import.meta.url), 'utf8');
50
45
  return (JSON.parse(file) as { version: string }).version;
package/src/lib/config.ts CHANGED
@@ -11,6 +11,7 @@ export interface ProfileData {
11
11
  pat: string;
12
12
  account_email: string;
13
13
  account_name: string;
14
+ org_name?: string;
14
15
  active_project_id?: string;
15
16
  active_project_name?: string;
16
17
  project_api_key?: string;
@@ -88,6 +89,7 @@ export const config = {
88
89
  pat: data.pat ?? existing?.pat ?? '',
89
90
  account_email: data.account_email ?? existing?.account_email ?? '',
90
91
  account_name: data.account_name ?? existing?.account_name ?? '',
92
+ org_name: data.org_name ?? existing?.org_name,
91
93
  active_project_id: data.active_project_id ?? existing?.active_project_id,
92
94
  active_project_name: data.active_project_name ?? existing?.active_project_name,
93
95
  project_api_key: data.project_api_key ?? existing?.project_api_key,
package/src/lib/manage.ts CHANGED
@@ -36,6 +36,10 @@ export interface ManageProject {
36
36
  active: boolean;
37
37
  }
38
38
 
39
+ export interface ManageProjectServices {
40
+ [key: string]: boolean;
41
+ }
42
+
39
43
  export interface ManageProjectKey {
40
44
  id: string;
41
45
  name: string;
@@ -0,0 +1,97 @@
1
+ export interface Column {
2
+ header: string;
3
+ width: number;
4
+ color?: (val: string) => string;
5
+ }
6
+
7
+ export interface TableOptions {
8
+ columns: Column[];
9
+ rows: string[][];
10
+ }
11
+
12
+ const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
13
+
14
+ export const orange = (s: string) => '\x1b[38;2;244;140;6m' + s;
15
+ export const gold = (s: string) => '\x1b[38;2;255;208;0m' + s;
16
+ export const dim = (s: string) => '\x1b[2m' + s + '\x1b[0m';
17
+ export const white = (s: string) => '\x1b[97m' + s;
18
+ export const green = (s: string) => '\x1b[38;2;34;197;94m' + s;
19
+ export const muted = (s: string) => '\x1b[38;2;85;85;85m' + s;
20
+ export const inactive = (s: string) => '\x1b[38;2;68;68;68m' + s;
21
+ export const failure = (s: string) => '\x1b[38;2;232;93;4m' + s;
22
+ export const reset = '\x1b[0m';
23
+
24
+ function stripAnsi(value: string): string {
25
+ return value.replace(ANSI_PATTERN, '');
26
+ }
27
+
28
+ function fitCell(value: string, width: number): string {
29
+ const plain = stripAnsi(value);
30
+ if (plain.length <= width) {
31
+ return value + ' '.repeat(width - plain.length);
32
+ }
33
+
34
+ const truncated = plain.slice(0, width);
35
+ return truncated;
36
+ }
37
+
38
+ export function renderTable(options: TableOptions): string {
39
+ const { columns, rows } = options;
40
+ const lines: string[] = [];
41
+
42
+ lines.push(
43
+ ' ┌' + columns.map((c) => '─'.repeat(c.width + 2)).join('┬') + '┐'
44
+ );
45
+
46
+ lines.push(
47
+ ' │' +
48
+ columns
49
+ .map((c) => ' ' + dim(c.header.padEnd(c.width)) + ' │')
50
+ .join('')
51
+ );
52
+
53
+ lines.push(
54
+ ' ├' + columns.map((c) => '─'.repeat(c.width + 2)).join('┼') + '┤'
55
+ );
56
+
57
+ for (const row of rows) {
58
+ lines.push(
59
+ ' │' +
60
+ columns
61
+ .map((c, i) => {
62
+ const raw = row[i] ?? '';
63
+ const fitted = fitCell(raw, c.width);
64
+ const colored = c.color
65
+ ? fitCell(c.color(stripAnsi(raw)), c.width)
66
+ : fitted;
67
+ return ' ' + colored + ' ' + reset + '│';
68
+ })
69
+ .join('')
70
+ );
71
+ }
72
+
73
+ lines.push(
74
+ ' └' + columns.map((c) => '─'.repeat(c.width + 2)).join('┴') + '┘'
75
+ );
76
+
77
+ return lines.join('\n');
78
+ }
79
+
80
+ export function header(version: string, subtitle?: string): string {
81
+ const lines = [
82
+ '',
83
+ orange(' ⇒⇒') + reset + ' globio ' + dim(version),
84
+ dim(' ──────────────────────────────────────────'),
85
+ ];
86
+
87
+ if (subtitle) {
88
+ lines.push(' ' + subtitle);
89
+ }
90
+
91
+ lines.push('');
92
+ return lines.join('\n');
93
+ }
94
+
95
+ export function footer(text: string): string {
96
+ return '\n' + dim(' ' + text) + '\n';
97
+ }