@telemetryos/cli 1.9.0 → 1.10.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/commands/auth.js +4 -4
  3. package/dist/commands/init.js +90 -42
  4. package/dist/commands/publish.d.ts +2 -0
  5. package/dist/commands/publish.js +208 -0
  6. package/dist/index.js +2 -0
  7. package/dist/plugins/math-tools.d.ts +2 -0
  8. package/dist/plugins/math-tools.js +18 -0
  9. package/dist/services/api-client.d.ts +18 -0
  10. package/dist/services/api-client.js +70 -0
  11. package/dist/services/archiver.d.ts +4 -0
  12. package/dist/services/archiver.js +65 -0
  13. package/dist/services/build-poller.d.ts +10 -0
  14. package/dist/services/build-poller.js +63 -0
  15. package/dist/services/cli-config.d.ts +6 -0
  16. package/dist/services/cli-config.js +23 -0
  17. package/dist/services/generate-application.d.ts +2 -1
  18. package/dist/services/generate-application.js +31 -32
  19. package/dist/services/project-config.d.ts +24 -0
  20. package/dist/services/project-config.js +51 -0
  21. package/dist/services/run-server.js +29 -73
  22. package/dist/types/api.d.ts +44 -0
  23. package/dist/types/api.js +1 -0
  24. package/dist/types/applications.d.ts +44 -0
  25. package/dist/types/applications.js +1 -0
  26. package/dist/utils/ansi.d.ts +10 -0
  27. package/dist/utils/ansi.js +10 -0
  28. package/dist/utils/path-utils.d.ts +55 -0
  29. package/dist/utils/path-utils.js +99 -0
  30. package/package.json +4 -2
  31. package/templates/vite-react-typescript/CLAUDE.md +6 -5
  32. package/templates/vite-react-typescript/_claude/skills/tos-render-design/SKILL.md +304 -12
  33. package/templates/vite-react-typescript/_claude/skills/tos-requirements/SKILL.md +367 -130
  34. package/templates/vite-react-typescript/_claude/skills/tos-weather-api/SKILL.md +443 -269
@@ -0,0 +1,63 @@
1
+ const POLL_INTERVAL = 1000; // 1 second, matching Studio UI
2
+ const TERMINAL_STATES = ['success', 'failure', 'failed', 'cancelled'];
3
+ export async function pollBuild(apiClient, applicationId, callbacks) {
4
+ let lastLogIndex = 0;
5
+ let lastState = '';
6
+ let lastBuildId = null;
7
+ const poll = async () => {
8
+ try {
9
+ const res = await apiClient.get(`/application/${applicationId}/build`);
10
+ const builds = (await res.json());
11
+ // Get the most recent build (highest index)
12
+ const build = builds.sort((a, b) => b.index - a.index)[0];
13
+ if (!build)
14
+ return null;
15
+ // Reset log tracking when build changes
16
+ if (build.id !== lastBuildId) {
17
+ lastBuildId = build.id;
18
+ lastLogIndex = 0;
19
+ }
20
+ // Notify on state change
21
+ if (build.state !== lastState) {
22
+ lastState = build.state;
23
+ callbacks.onStateChange(build.state);
24
+ }
25
+ // Stream new log lines (logs array grows incrementally)
26
+ if (build.logs && build.logs.length > lastLogIndex) {
27
+ const newLogs = build.logs.slice(lastLogIndex);
28
+ newLogs.forEach((line) => callbacks.onLog(line));
29
+ lastLogIndex = build.logs.length;
30
+ }
31
+ // Check if complete
32
+ if (TERMINAL_STATES.includes(build.state)) {
33
+ callbacks.onComplete(build);
34
+ return build;
35
+ }
36
+ return null;
37
+ }
38
+ catch (error) {
39
+ callbacks.onError(error);
40
+ throw error;
41
+ }
42
+ };
43
+ // Initial poll - build may already exist
44
+ let result = await poll();
45
+ if (result)
46
+ return result;
47
+ // Continue polling until terminal state
48
+ return new Promise((resolve, reject) => {
49
+ const interval = setInterval(async () => {
50
+ try {
51
+ result = await poll();
52
+ if (result) {
53
+ clearInterval(interval);
54
+ resolve(result);
55
+ }
56
+ }
57
+ catch (error) {
58
+ clearInterval(interval);
59
+ reject(error);
60
+ }
61
+ }, POLL_INTERVAL);
62
+ });
63
+ }
@@ -0,0 +1,6 @@
1
+ export type CliConfig = {
2
+ apiToken?: string;
3
+ apiUrl?: string;
4
+ };
5
+ export declare function loadCliConfig(): Promise<CliConfig | null>;
6
+ export declare function saveCliConfig(config: CliConfig): Promise<void>;
@@ -0,0 +1,23 @@
1
+ import { mkdir, readFile, writeFile } from 'fs/promises';
2
+ import { homedir } from 'os';
3
+ import path from 'path';
4
+ function getCliConfigDir() {
5
+ const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(homedir(), '.config');
6
+ return path.join(xdgConfig, 'telemetryos-cli');
7
+ }
8
+ function getCliConfigPath() {
9
+ return path.join(getCliConfigDir(), 'config.json');
10
+ }
11
+ export async function loadCliConfig() {
12
+ try {
13
+ const content = await readFile(getCliConfigPath(), 'utf-8');
14
+ return JSON.parse(content);
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ export async function saveCliConfig(config) {
21
+ await mkdir(getCliConfigDir(), { recursive: true });
22
+ await writeFile(getCliConfigPath(), JSON.stringify(config, null, 2));
23
+ }
@@ -6,6 +6,7 @@ export type GenerateApplicationOptions = {
6
6
  template: string;
7
7
  projectPath: string;
8
8
  progressFn: (createdFilePath: string) => void;
9
- confirmOverwrite?: () => Promise<boolean>;
10
9
  };
11
10
  export declare function generateApplication(options: GenerateApplicationOptions): Promise<void>;
11
+ export declare function checkDirectoryConflicts(projectPath: string): Promise<string[]>;
12
+ export declare function removeConflictingFiles(projectPath: string, conflicts: string[]): Promise<void>;
@@ -1,14 +1,10 @@
1
1
  import { spawn, execSync } from 'child_process';
2
2
  import fs from 'fs/promises';
3
3
  import path from 'path';
4
+ import { ansi } from '../utils/ansi.js';
4
5
  const ignoredTemplateFiles = ['.DS_Store', 'thumbs.db', 'node_modules', '.git', 'dist'];
5
6
  const templatesDir = path.join(import.meta.dirname, '../../templates');
6
7
  const dotfileNames = ['_gitignore', '_claude'];
7
- // ANSI color codes for terminal output
8
- const ansiGreen = '\u001b[32m';
9
- const ansiCyan = '\u001b[36m';
10
- const ansiBold = '\u001b[1m';
11
- const ansiReset = '\u001b[0m';
12
8
  // Files that can exist in a directory without being considered conflicts
13
9
  const safeExistingFiles = [
14
10
  '.DS_Store',
@@ -22,25 +18,7 @@ const safeExistingFiles = [
22
18
  'README.md',
23
19
  ];
24
20
  export async function generateApplication(options) {
25
- const { name, description, author, version, template, projectPath, progressFn, confirmOverwrite, } = options;
26
- // Check for directory conflicts before proceeding
27
- const conflicts = await checkDirectoryConflicts(projectPath);
28
- if (conflicts.length > 0) {
29
- console.log(`\nThe directory ${name} contains files that could conflict:\n`);
30
- conflicts.forEach((file) => console.log(` ${file}`));
31
- console.log();
32
- if (confirmOverwrite) {
33
- const proceed = await confirmOverwrite();
34
- if (!proceed) {
35
- console.log('Aborting installation');
36
- process.exit(1);
37
- }
38
- }
39
- else {
40
- console.log('Aborting installation');
41
- process.exit(1);
42
- }
43
- }
21
+ const { name, description, author, version, template, projectPath, progressFn } = options;
44
22
  await fs.mkdir(projectPath, { recursive: true });
45
23
  // Initialize git repo early (before template dependencies that may have git hooks)
46
24
  const gitInitialized = tryGitInit(projectPath);
@@ -68,7 +46,7 @@ async function installPackages(projectPath) {
68
46
  const sdkInstall = spawn('npm', ['install', '@telemetryos/sdk'], {
69
47
  cwd: projectPath,
70
48
  stdio: 'inherit',
71
- shell: true
49
+ shell: true,
72
50
  });
73
51
  sdkInstall.on('close', (code) => {
74
52
  if (code === 0) {
@@ -87,7 +65,7 @@ async function installPackages(projectPath) {
87
65
  const cliInstall = spawn('npm', ['install', '-D', '@telemetryos/cli'], {
88
66
  cwd: projectPath,
89
67
  stdio: 'inherit',
90
- shell: true
68
+ shell: true,
91
69
  });
92
70
  cliInstall.on('close', (code) => {
93
71
  if (code === 0) {
@@ -148,7 +126,7 @@ async function tryGitCommit(projectPath) {
148
126
  return false;
149
127
  }
150
128
  }
151
- async function checkDirectoryConflicts(projectPath) {
129
+ export async function checkDirectoryConflicts(projectPath) {
152
130
  try {
153
131
  const entries = await fs.readdir(projectPath);
154
132
  return entries.filter((file) => !safeExistingFiles.includes(file));
@@ -161,22 +139,43 @@ async function checkDirectoryConflicts(projectPath) {
161
139
  throw error;
162
140
  }
163
141
  }
142
+ export async function removeConflictingFiles(projectPath, conflicts) {
143
+ for (const file of conflicts) {
144
+ const filePath = path.join(projectPath, file);
145
+ try {
146
+ const stat = await fs.stat(filePath);
147
+ if (stat.isDirectory()) {
148
+ await fs.rm(filePath, { recursive: true, force: true });
149
+ }
150
+ else {
151
+ await fs.unlink(filePath);
152
+ }
153
+ }
154
+ catch (error) {
155
+ // Ignore errors for files that may have already been removed
156
+ console.error(`Warning: Could not remove ${file}: ${error instanceof Error ? error.message : error}`);
157
+ }
158
+ }
159
+ }
164
160
  function printSuccessMessage(name, projectPath) {
161
+ // Calculate relative path from cwd for cleaner display
162
+ const relativePath = path.relative(process.cwd(), projectPath);
163
+ const displayPath = relativePath || '.';
165
164
  console.log(`
166
- ${ansiGreen}Success!${ansiReset} Created ${ansiBold}${name}${ansiReset} at ${ansiCyan}${projectPath}${ansiReset}
165
+ ${ansi.green}Success!${ansi.reset} Created ${ansi.bold}${name}${ansi.reset} at ${ansi.cyan}${displayPath}${ansi.reset}
167
166
 
168
167
  Inside that directory, you can run:
169
168
 
170
- ${ansiCyan}npm run dev${ansiReset}
169
+ ${ansi.cyan}npm run dev${ansi.reset}
171
170
  Starts the development server
172
171
 
173
- ${ansiCyan}npm run build${ansiReset}
172
+ ${ansi.cyan}npm run build${ansi.reset}
174
173
  Builds the app for production
175
174
 
176
175
  You may begin with:
177
176
 
178
- ${ansiCyan}cd ${name}${ansiReset}
179
- ${ansiCyan}npm run dev${ansiReset}
177
+ ${ansi.cyan}cd ${displayPath}${ansi.reset}
178
+ ${ansi.cyan}npm run dev${ansi.reset}
180
179
 
181
180
  `);
182
181
  }
@@ -0,0 +1,24 @@
1
+ import z from 'zod';
2
+ export declare const projectConfigSchema: z.ZodObject<{
3
+ name: z.ZodOptional<z.ZodString>;
4
+ version: z.ZodOptional<z.ZodString>;
5
+ thumbnailPath: z.ZodOptional<z.ZodString>;
6
+ mountPoints: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
7
+ path: z.ZodString;
8
+ }, z.z.core.$strip>, z.ZodString]>>>;
9
+ backgroundWorkers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
10
+ path: z.ZodString;
11
+ }, z.z.core.$strip>, z.ZodString]>>>;
12
+ serverWorkers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
13
+ path: z.ZodString;
14
+ }, z.z.core.$strip>, z.ZodString]>>>;
15
+ containers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
16
+ image: z.ZodString;
17
+ }, z.z.core.$strip>, z.ZodString]>>>;
18
+ devServer: z.ZodOptional<z.ZodObject<{
19
+ runCommand: z.ZodOptional<z.ZodString>;
20
+ url: z.ZodString;
21
+ }, z.z.core.$strip>>;
22
+ }, z.z.core.$strip>;
23
+ export type ProjectConfig = z.infer<typeof projectConfigSchema>;
24
+ export declare function loadProjectConfig(projectPath: string): Promise<ProjectConfig>;
@@ -0,0 +1,51 @@
1
+ import { readFile } from 'fs/promises';
2
+ import path from 'path';
3
+ import z from 'zod';
4
+ const mountPointSchema = z.object({
5
+ path: z.string(),
6
+ });
7
+ const backgroundWorkerSchema = z.object({
8
+ path: z.string(),
9
+ });
10
+ const serverWorkerSchema = z.object({
11
+ path: z.string(),
12
+ });
13
+ const containerSchema = z.object({
14
+ image: z.string(),
15
+ });
16
+ export const projectConfigSchema = z.object({
17
+ name: z.string().optional(),
18
+ version: z.string().optional(),
19
+ thumbnailPath: z.string().optional(),
20
+ mountPoints: z.record(z.string(), z.union([mountPointSchema, z.string()])).optional(),
21
+ backgroundWorkers: z.record(z.string(), z.union([backgroundWorkerSchema, z.string()])).optional(),
22
+ serverWorkers: z.record(z.string(), z.union([serverWorkerSchema, z.string()])).optional(),
23
+ containers: z.record(z.string(), z.union([containerSchema, z.string()])).optional(),
24
+ devServer: z
25
+ .object({
26
+ runCommand: z.string().optional(),
27
+ url: z.string(),
28
+ })
29
+ .optional(),
30
+ });
31
+ export async function loadProjectConfig(projectPath) {
32
+ const configPath = path.join(projectPath, 'telemetry.config.json');
33
+ try {
34
+ const content = await readFile(configPath, 'utf-8');
35
+ const parsed = JSON.parse(content);
36
+ return projectConfigSchema.parse(parsed);
37
+ }
38
+ catch (error) {
39
+ if (error.code === 'ENOENT') {
40
+ throw new Error(`No telemetry.config.json found in ${projectPath}`);
41
+ }
42
+ if (error instanceof z.ZodError) {
43
+ const issues = error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`).join('\n');
44
+ throw new Error(`Invalid telemetry.config.json:\n${issues}`);
45
+ }
46
+ if (error instanceof SyntaxError) {
47
+ throw new Error(`Invalid JSON in telemetry.config.json: ${error.message}`);
48
+ }
49
+ throw error;
50
+ }
51
+ }
@@ -5,61 +5,24 @@ import http from 'http';
5
5
  import path from 'path';
6
6
  import readable from 'readline/promises';
7
7
  import serveHandler from 'serve-handler';
8
- import z from 'zod';
9
8
  import pkg from '../../package.json' with { type: 'json' };
10
- const ansiWhite = '\u001b[37m';
11
- const ansiYellow = '\u001b[33m';
12
- const ansiCyan = '\u001b[36m';
13
- const ansiBold = '\u001b[1m';
14
- const ansiReset = '\u001b[0m';
15
- const mountPointSchema = z.object({
16
- path: z.string(),
17
- });
18
- const backgroundWorkerSchema = z.object({
19
- path: z.string(),
20
- });
21
- const serverWorkerSchema = z.object({
22
- path: z.string(),
23
- });
24
- const containerSchema = z.object({
25
- image: z.string(),
26
- });
27
- const configSchema = z.object({
28
- name: z.string().optional(),
29
- version: z.string().optional(),
30
- thumbnailPath: z.string().optional(),
31
- mountPoints: z.record(z.string(), z.union([mountPointSchema, z.string()])).optional(),
32
- backgroundWorkers: z.record(z.string(), z.union([backgroundWorkerSchema, z.string()])).optional(),
33
- serverWorkers: z.record(z.string(), z.union([serverWorkerSchema, z.string()])).optional(),
34
- containers: z.record(z.string(), z.union([containerSchema, z.string()])).optional(),
35
- devServer: z
36
- .object({
37
- runCommand: z.string().optional(),
38
- url: z.string(),
39
- })
40
- .optional(),
41
- });
9
+ import { loadProjectConfig } from './project-config.js';
10
+ import { ansi } from '../utils/ansi.js';
42
11
  export async function runServer(projectPath, flags) {
43
12
  printSplashScreen();
44
13
  projectPath = path.resolve(process.cwd(), projectPath);
45
- const telemetryConfig = await loadConfigFile(projectPath);
46
- if (!telemetryConfig) {
47
- console.error('No telemetry configuration found. Are you in the right directory?');
48
- process.exit(1);
14
+ let projectConfig;
15
+ try {
16
+ projectConfig = await loadProjectConfig(projectPath);
49
17
  }
50
- const validationResult = configSchema.safeParse(telemetryConfig);
51
- if (!validationResult.success) {
52
- console.error('Invalid telemetry.config.json:');
53
- validationResult.error.issues.forEach((issue) => {
54
- console.error(` ${issue.path.join('.')}: ${issue.message}`);
55
- });
56
- console.error('\n');
18
+ catch (error) {
19
+ console.error(error.message);
57
20
  process.exit(1);
58
21
  }
59
- await serveDevelopmentApplicationHostUI(projectPath, flags.port, validationResult.data);
60
- await serveTelemetryApplication(projectPath, validationResult.data);
22
+ await serveDevelopmentApplicationHostUI(projectPath, flags.port, projectConfig);
23
+ await serveTelemetryApplication(projectPath, projectConfig);
61
24
  }
62
- async function serveDevelopmentApplicationHostUI(projectPath, port, telemetryConfig) {
25
+ async function serveDevelopmentApplicationHostUI(projectPath, port, projectConfig) {
63
26
  const hostUiPath = await import.meta.resolve('@telemetryos/development-application-host-ui/dist');
64
27
  const serveConfig = { public: fileURLToPath(hostUiPath) };
65
28
  const server = http.createServer();
@@ -68,19 +31,19 @@ async function serveDevelopmentApplicationHostUI(projectPath, port, telemetryCon
68
31
  const url = new URL(req.url, `http://${req.headers.origin}`);
69
32
  if (url.pathname === '/__tos-config__') {
70
33
  res.setHeader('Content-Type', 'application/json');
71
- res.end(JSON.stringify(telemetryConfig));
34
+ res.end(JSON.stringify(projectConfig));
72
35
  return;
73
36
  }
74
37
  if (url.pathname === '/__tos-thumbnail__') {
75
- if (!telemetryConfig.thumbnailPath) {
38
+ if (!projectConfig.thumbnailPath) {
76
39
  res.statusCode = 404;
77
40
  res.end('No thumbnail configured');
78
41
  return;
79
42
  }
80
- const thumbnailFullPath = path.join(projectPath, telemetryConfig.thumbnailPath);
43
+ const thumbnailFullPath = path.join(projectPath, projectConfig.thumbnailPath);
81
44
  try {
82
45
  const imageData = await readFile(thumbnailFullPath);
83
- const ext = path.extname(telemetryConfig.thumbnailPath).toLowerCase();
46
+ const ext = path.extname(projectConfig.thumbnailPath).toLowerCase();
84
47
  const mimeTypes = {
85
48
  '.jpg': 'image/jpeg',
86
49
  '.jpeg': 'image/jpeg',
@@ -168,17 +131,21 @@ async function serveDevelopmentApplicationHostUI(projectPath, port, telemetryCon
168
131
  printServerInfo(port);
169
132
  server.listen(port);
170
133
  }
171
- async function serveTelemetryApplication(rootPath, telemetryConfig) {
134
+ async function serveTelemetryApplication(rootPath, projectConfig) {
172
135
  var _a;
173
- if (!((_a = telemetryConfig === null || telemetryConfig === void 0 ? void 0 : telemetryConfig.devServer) === null || _a === void 0 ? void 0 : _a.runCommand)) {
136
+ if (!((_a = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.devServer) === null || _a === void 0 ? void 0 : _a.runCommand)) {
174
137
  console.log('No value in config at devServer.runCommand');
175
138
  return;
176
139
  }
177
- const runCommand = telemetryConfig.devServer.runCommand;
140
+ const runCommand = projectConfig.devServer.runCommand;
178
141
  const binPath = path.join(rootPath, 'node_modules', '.bin');
179
142
  const childProcess = spawn(runCommand, {
180
143
  shell: true,
181
- env: { ...process.env, FORCE_COLOR: '1', PATH: `${binPath}${path.delimiter}${process.env.PATH}` },
144
+ env: {
145
+ ...process.env,
146
+ FORCE_COLOR: '1',
147
+ PATH: `${binPath}${path.delimiter}${process.env.PATH}`,
148
+ },
182
149
  stdio: ['ignore', 'pipe', 'pipe'],
183
150
  cwd: rootPath,
184
151
  });
@@ -200,32 +167,21 @@ async function serveTelemetryApplication(rootPath, telemetryConfig) {
200
167
  childProcess.kill();
201
168
  });
202
169
  }
203
- async function loadConfigFile(rootPath) {
204
- const configFilePath = path.join(rootPath, 'telemetry.config.json');
205
- try {
206
- const fileContent = await readFile(configFilePath, 'utf-8');
207
- const config = JSON.parse(fileContent);
208
- return config;
209
- }
210
- catch {
211
- return null;
212
- }
213
- }
214
170
  function printSplashScreen() {
215
171
  console.log(`
216
172
 
217
- ${ansiWhite} █ █ █ ${ansiYellow}▄▀▀▀▄ ▄▀▀▀▄
218
- ${ansiWhite} █ █ █ ${ansiYellow}█ █ █
219
- ${ansiWhite}▀█▀ ▄▀▀▄ █ ▄▀▀▄ █▀▄▀▄ ▄▀▀▄ ▀█▀ █▄▀ █ █ ${ansiYellow}█ █ ▀▀▀▄
220
- ${ansiWhite} █ █▀▀▀ █ █▀▀▀ █ █ █ █▀▀▀ █ █ █ █ ${ansiYellow}█ █ █
221
- ${ansiWhite} ▀▄ ▀▄▄▀ █ ▀▄▄▀ █ █ █ ▀▄▄▀ ▀▄ █ █ ${ansiYellow}▀▄▄▄▀ ▀▄▄▄▀
222
- ${ansiWhite} ▄▀ ${ansiReset}
173
+ ${ansi.white} █ █ █ ${ansi.yellow}▄▀▀▀▄ ▄▀▀▀▄
174
+ ${ansi.white} █ █ █ ${ansi.yellow}█ █ █
175
+ ${ansi.white}▀█▀ ▄▀▀▄ █ ▄▀▀▄ █▀▄▀▄ ▄▀▀▄ ▀█▀ █▄▀ █ █ ${ansi.yellow}█ █ ▀▀▀▄
176
+ ${ansi.white} █ █▀▀▀ █ █▀▀▀ █ █ █ █▀▀▀ █ █ █ █ ${ansi.yellow}█ █ █
177
+ ${ansi.white} ▀▄ ▀▄▄▀ █ ▀▄▄▀ █ █ █ ▀▄▄▀ ▀▄ █ █ ${ansi.yellow}▀▄▄▄▀ ▀▄▄▄▀
178
+ ${ansi.white} ▄▀ ${ansi.reset}
223
179
  v${pkg.version}`);
224
180
  }
225
181
  function printServerInfo(port) {
226
182
  console.log(`
227
183
  ╔═══════════════════════════════════════════════════════════╗
228
- ║ ${ansiBold}Development environment running at: ${ansiCyan}http://localhost:${port}${ansiReset} ║
184
+ ║ ${ansi.bold}Development environment running at: ${ansi.cyan}http://localhost:${port}${ansi.reset} ║
229
185
  ╚═══════════════════════════════════════════════════════════╝
230
186
  `);
231
187
  }
@@ -0,0 +1,44 @@
1
+ export type ApplicationKind = 'git' | 'github' | 'uploaded' | null;
2
+ export type Application = {
3
+ id: string;
4
+ title: string;
5
+ description: string;
6
+ kind: ApplicationKind;
7
+ baseImage: string;
8
+ buildWorkingPath?: string;
9
+ buildScript?: string;
10
+ buildOutputPath?: string;
11
+ buildEnvironmentVariables?: Record<string, string>;
12
+ versions?: ApplicationVersion[];
13
+ createdAt?: string;
14
+ updatedAt?: string;
15
+ };
16
+ export type ApplicationVersion = {
17
+ name?: string;
18
+ version?: string;
19
+ applicationId: string;
20
+ buildId: string;
21
+ applicationSpecifier: string;
22
+ publishedAt: string;
23
+ };
24
+ export type ApplicationBuild = {
25
+ id: string;
26
+ applicationId: string;
27
+ index: number;
28
+ state: 'pending' | 'building' | 'success' | 'failure' | 'failed' | 'cancelled';
29
+ logs: string[];
30
+ scheduledAt?: string;
31
+ startedAt?: string;
32
+ finishedAt?: string;
33
+ };
34
+ export type CreateApplicationRequest = {
35
+ kind: 'uploaded';
36
+ title: string;
37
+ description?: string;
38
+ baseImage: string;
39
+ baseImageRegistryAuth: string;
40
+ buildWorkingPath: string;
41
+ buildScript: string;
42
+ buildOutputPath: string;
43
+ buildEnvironmentVariables?: Record<string, string>;
44
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ export type ApplicationKind = 'git' | 'github' | 'uploaded' | null;
2
+ export type Application = {
3
+ id: string;
4
+ title: string;
5
+ description: string;
6
+ kind: ApplicationKind;
7
+ baseImage: string;
8
+ buildWorkingPath?: string;
9
+ buildScript?: string;
10
+ buildOutputPath?: string;
11
+ buildEnvironmentVariables?: Record<string, string>;
12
+ versions?: ApplicationVersion[];
13
+ createdAt?: string;
14
+ updatedAt?: string;
15
+ };
16
+ export type ApplicationVersion = {
17
+ name?: string;
18
+ version?: string;
19
+ applicationId: string;
20
+ buildId: string;
21
+ applicationSpecifier: string;
22
+ publishedAt: string;
23
+ };
24
+ export type ApplicationBuild = {
25
+ id: string;
26
+ applicationId: string;
27
+ index: number;
28
+ state: 'pending' | 'building' | 'success' | 'failure' | 'failed' | 'cancelled';
29
+ logs: string[];
30
+ scheduledAt?: string;
31
+ startedAt?: string;
32
+ finishedAt?: string;
33
+ };
34
+ export type CreateApplicationRequest = {
35
+ kind: 'uploaded';
36
+ title: string;
37
+ description?: string;
38
+ baseImage: string;
39
+ baseImageRegistryAuth: string;
40
+ buildWorkingPath: string;
41
+ buildScript: string;
42
+ buildOutputPath: string;
43
+ buildEnvironmentVariables?: Record<string, string>;
44
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ export declare const ansi: {
2
+ readonly white: "\u001B[37m";
3
+ readonly yellow: "\u001B[33m";
4
+ readonly green: "\u001B[32m";
5
+ readonly cyan: "\u001B[36m";
6
+ readonly red: "\u001B[31m";
7
+ readonly bold: "\u001B[1m";
8
+ readonly dim: "\u001B[2m";
9
+ readonly reset: "\u001B[0m";
10
+ };
@@ -0,0 +1,10 @@
1
+ export const ansi = {
2
+ white: '\u001b[37m',
3
+ yellow: '\u001b[33m',
4
+ green: '\u001b[32m',
5
+ cyan: '\u001b[36m',
6
+ red: '\u001b[31m',
7
+ bold: '\u001b[1m',
8
+ dim: '\u001b[2m',
9
+ reset: '\u001b[0m',
10
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Converts a string to kebab-case
3
+ *
4
+ * @param str - The string to convert
5
+ * @returns The kebab-cased string
6
+ *
7
+ * @example
8
+ * toKebabCase('MyApp') // 'my-app'
9
+ * toKebabCase('my_app') // 'my-app'
10
+ * toKebabCase('My App!') // 'my-app'
11
+ * toKebabCase('my--app') // 'my-app'
12
+ */
13
+ export declare function toKebabCase(str: string): string;
14
+ /**
15
+ * Derives a project name from a given path
16
+ *
17
+ * @param projectPath - The path to derive the name from
18
+ * @param currentWorkingDirectory - The current working directory (defaults to process.cwd())
19
+ * @returns The derived project name in kebab-case
20
+ *
21
+ * @example
22
+ * deriveProjectName('my-project') // 'my-project'
23
+ * deriveProjectName('apps/MyApp') // 'my-app'
24
+ * deriveProjectName('./', '/Users/test/MyProject') // 'my-project'
25
+ * deriveProjectName('../parent') // 'parent'
26
+ * deriveProjectName('/absolute/path/to/app') // 'app'
27
+ */
28
+ export declare function deriveProjectName(projectPath: string, currentWorkingDirectory?: string): string;
29
+ /**
30
+ * Resolves a project path and derives the name
31
+ *
32
+ * @param projectPath - The path to resolve
33
+ * @param currentWorkingDirectory - The current working directory (defaults to process.cwd())
34
+ * @returns An object containing the resolved path and derived name
35
+ *
36
+ * @example
37
+ * resolveProjectPathAndName('apps/MyApp')
38
+ * // { resolvedPath: '/Users/user/cwd/apps/MyApp', derivedName: 'my-app' }
39
+ */
40
+ export declare function resolveProjectPathAndName(projectPath: string, currentWorkingDirectory?: string): {
41
+ resolvedPath: string;
42
+ derivedName: string;
43
+ };
44
+ /**
45
+ * Validates a project name according to npm package name requirements
46
+ *
47
+ * @param name - The project name to validate
48
+ * @returns true if valid, or an error message string if invalid
49
+ *
50
+ * @example
51
+ * validateProjectName('my-app') // true
52
+ * validateProjectName('') // 'Project name cannot be empty'
53
+ * validateProjectName('MyApp') // 'Project name must contain only lowercase letters, numbers, and hyphens'
54
+ */
55
+ export declare function validateProjectName(name: string): true | string;