@telemetryos/cli 1.11.0 → 1.13.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 (51) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/commands/claude-code.d.ts +2 -0
  3. package/dist/commands/claude-code.js +29 -0
  4. package/dist/commands/init.js +22 -9
  5. package/dist/index.js +2 -0
  6. package/dist/services/create-project.d.ts +13 -0
  7. package/dist/services/create-project.js +188 -0
  8. package/dist/services/project-config.d.ts +3 -0
  9. package/dist/services/project-config.js +3 -0
  10. package/dist/services/run-server.js +186 -60
  11. package/dist/utils/ansi.d.ts +1 -0
  12. package/dist/utils/ansi.js +1 -0
  13. package/dist/utils/template.d.ts +2 -0
  14. package/dist/utils/template.js +30 -0
  15. package/package.json +4 -4
  16. package/templates/{vite-react-typescript → claude-code}/CLAUDE.md +10 -3
  17. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-architecture/SKILL.md +138 -61
  18. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-debugging/SKILL.md +2 -2
  19. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-media-api/SKILL.md +97 -10
  20. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-multi-mode/SKILL.md +97 -4
  21. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-requirements/SKILL.md +70 -5
  22. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-store-sync/SKILL.md +4 -2
  23. package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-weather-api/SKILL.md +7 -6
  24. package/templates/claude-code/_claude/skills/tos-web-ui-design/SKILL.md +373 -0
  25. package/templates/vite-react-typescript/_gitignore +4 -2
  26. package/templates/vite-react-typescript/telemetry.config.json +2 -1
  27. package/templates/vite-react-typescript-web/_gitignore +32 -0
  28. package/templates/vite-react-typescript-web/assets/telemetryos-wordmark.svg +11 -0
  29. package/templates/vite-react-typescript-web/assets/tos-app.svg +12 -0
  30. package/templates/vite-react-typescript-web/index.html +15 -0
  31. package/templates/vite-react-typescript-web/package.json +24 -0
  32. package/templates/vite-react-typescript-web/src/App.tsx +25 -0
  33. package/templates/vite-react-typescript-web/src/hooks/store.ts +8 -0
  34. package/templates/vite-react-typescript-web/src/index.css +24 -0
  35. package/templates/vite-react-typescript-web/src/index.tsx +11 -0
  36. package/templates/vite-react-typescript-web/src/views/Render.css +67 -0
  37. package/templates/vite-react-typescript-web/src/views/Render.tsx +44 -0
  38. package/templates/vite-react-typescript-web/src/views/Settings.tsx +72 -0
  39. package/templates/vite-react-typescript-web/src/views/Web.css +105 -0
  40. package/templates/vite-react-typescript-web/src/views/Web.tsx +52 -0
  41. package/templates/vite-react-typescript-web/telemetry.config.json +16 -0
  42. package/templates/vite-react-typescript-web/tsconfig.json +19 -0
  43. package/templates/vite-react-typescript-web/vite.config.ts +18 -0
  44. package/templates/vite-react-typescript/_claude/skills/tos-render-design/SKILL.md +0 -624
  45. /package/templates/{vite-react-typescript → claude-code}/AGENTS.md +0 -0
  46. /package/templates/{vite-react-typescript → claude-code}/_claude/settings.local.json +0 -0
  47. /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-proxy-fetch/SKILL.md +0 -0
  48. /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-render-kiosk-design/SKILL.md +0 -0
  49. /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-render-signage-design/SKILL.md +0 -0
  50. /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-render-ui-design/SKILL.md +0 -0
  51. /package/templates/{vite-react-typescript → claude-code}/_claude/skills/tos-settings-ui/SKILL.md +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # @telemetryos/cli
2
2
 
3
+ ## 1.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ### New Features
8
+ - **Web mount point support** — Applications can now define a third UI surface (`/web`) alongside Render and Settings, enabling web-portal-style interfaces with full navigation control via a postMessage bridge.
9
+ - **`playlist.getDuration()`** — New async method returns the playlist-configured page duration in seconds.
10
+ - **Custom logo support** — Projects can specify a `logoPath` in `telemetry.config.json` for branding in the dev host.
11
+ - **`tos claude-code` command** — New CLI command to apply or update Claude Code skills and settings in existing projects.
12
+ - **Web project template** — New `vite-react-typescript-web` template scaffolds a project with all three mount points pre-configured.
13
+
14
+ ### Bug Fixes
15
+ - Fixed SDK client not properly validating bridge message responses.
16
+ - Fixed canvas not resizing when the sidebar toggles or window resizes after a manual drag-resize.
17
+ - Fixed `isLoading` usage and `useEffect` dependency arrays in Claude Code skill examples.
18
+ - Fixed stale active tab when the tabs array changes in the dev host.
19
+
20
+ ### Infrastructure
21
+ - Renamed `generate-application` to `create-project`; extracted shared template utilities.
22
+ - Added unit tests for Navigation, Currency, and Weather classes.
23
+
24
+ ### Patch Changes
25
+
26
+ - Updated dependencies
27
+ - @telemetryos/development-application-host-ui@1.13.0
28
+
29
+ ## 1.12.0
30
+
31
+ ### Minor Changes
32
+
33
+ - Revamp the dev host
34
+
35
+ ### Patch Changes
36
+
37
+ - Updated dependencies
38
+ - @telemetryos/development-application-host-ui@1.12.0
39
+
3
40
  ## 1.11.0
4
41
 
5
42
  ### Minor Changes
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const claudeCodeCommand: Command;
@@ -0,0 +1,29 @@
1
+ import { Command } from 'commander';
2
+ import path from 'path';
3
+ import { loadProjectConfig } from '../services/project-config.js';
4
+ import { copyDir, templatesDir } from '../utils/template.js';
5
+ import { ansi } from '../utils/ansi.js';
6
+ export const claudeCodeCommand = new Command('claude-code')
7
+ .description('Apply or update Claude Code skills and settings in a project')
8
+ .argument('[project-path]', 'Path to the telemetryOS project (defaults to current directory)', process.cwd())
9
+ .action(handleClaudeCodeCommand);
10
+ async function handleClaudeCodeCommand(projectPath) {
11
+ var _a, _b, _c;
12
+ const resolvedPath = path.resolve(projectPath);
13
+ let config;
14
+ try {
15
+ config = await loadProjectConfig(resolvedPath);
16
+ }
17
+ catch (error) {
18
+ console.error(`\n${ansi.red}Error:${ansi.reset} ${error instanceof Error ? error.message : error}`);
19
+ process.exit(1);
20
+ }
21
+ const name = (_a = config.name) !== null && _a !== void 0 ? _a : '';
22
+ const description = (_b = config.description) !== null && _b !== void 0 ? _b : '';
23
+ const version = (_c = config.version) !== null && _c !== void 0 ? _c : '';
24
+ console.log(`\nApplying Claude Code skills and settings...\n`);
25
+ await copyDir(path.join(templatesDir, 'claude-code'), resolvedPath, { name, version, description, author: '' }, (createdFilePath) => {
26
+ console.log(`.${path.sep}${path.relative(process.cwd(), createdFilePath)}`);
27
+ });
28
+ console.log(`\n${ansi.green}Done!${ansi.reset} Claude Code skills and settings have been applied.`);
29
+ }
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { generateApplication, checkDirectoryConflicts, removeConflictingFiles, } from '../services/generate-application.js';
2
+ import { createProject, checkDirectoryConflicts, removeConflictingFiles, } from '../services/create-project.js';
3
3
  import inquirer from 'inquirer';
4
4
  import path from 'path';
5
5
  import { resolveProjectPathAndName, validateProjectName } from '../utils/path-utils.js';
@@ -26,10 +26,11 @@ export const initCommand = new Command('init')
26
26
  .option('-d, --description <string>', 'The description of the application', '')
27
27
  .option('-a, --author <string>', 'The author of the application', '')
28
28
  .option('-v, --version <string>', 'The version of the application', '0.1.0')
29
- .option('-t, --template <string>', 'The template to use (vite-react-typescript)', '')
29
+ .option('-t, --template <string>', 'The template to use (vite-react-typescript, vite-react-typescript-web)', '')
30
30
  .option('-y, --yes', 'Skip all prompts and use defaults', false)
31
31
  .action(handleInitCommand);
32
32
  async function handleInitCommand(projectPathArg, options) {
33
+ var _a, _b, _c, _d, _e, _f;
33
34
  // Step 1: Resolve path and derive name
34
35
  const cwd = process.cwd();
35
36
  const inputPath = projectPathArg || '.';
@@ -80,6 +81,7 @@ async function handleInitCommand(projectPathArg, options) {
80
81
  let author = options.author;
81
82
  let version = options.version;
82
83
  let template = options.template;
84
+ let claudeCode = true;
83
85
  // Step 5: Build prompt questions (skipped with --yes)
84
86
  if (options.yes) {
85
87
  if (!name) {
@@ -138,23 +140,34 @@ async function handleInitCommand(projectPathArg, options) {
138
140
  type: 'list',
139
141
  name: 'template',
140
142
  message: 'Which template would you like to use?',
141
- choices: [{ name: 'Vite + React + TypeScript', value: 'vite-react-typescript' }],
143
+ choices: [
144
+ { name: 'Vite + React + TypeScript', value: 'vite-react-typescript' },
145
+ { name: 'Vite + React + TypeScript + Web', value: 'vite-react-typescript-web' },
146
+ ],
142
147
  });
148
+ questions.push({
149
+ type: 'confirm',
150
+ name: 'claudeCode',
151
+ message: 'Include Claude Code skills and settings?',
152
+ default: true,
153
+ });
143
154
  // Step 6: Prompt user
144
155
  const answers = await inquirer.prompt(questions);
145
- name = answers.name || name;
146
- version = answers.version || version;
147
- description = answers.description || description;
148
- author = answers.author || author;
149
- template = answers.template || template;
156
+ name = (_a = answers.name) !== null && _a !== void 0 ? _a : name;
157
+ version = (_b = answers.version) !== null && _b !== void 0 ? _b : version;
158
+ description = (_c = answers.description) !== null && _c !== void 0 ? _c : description;
159
+ author = (_d = answers.author) !== null && _d !== void 0 ? _d : author;
160
+ template = (_e = answers.template) !== null && _e !== void 0 ? _e : template;
161
+ claudeCode = (_f = answers.claudeCode) !== null && _f !== void 0 ? _f : claudeCode;
150
162
  }
151
163
  // Step 7: Generate application
152
- await generateApplication({
164
+ await createProject({
153
165
  name,
154
166
  description,
155
167
  author,
156
168
  version,
157
169
  template,
170
+ claudeCode,
158
171
  projectPath: resolvedPath,
159
172
  progressFn: (createdFilePath) => {
160
173
  console.log(`.${path.sep}${path.relative(cwd, createdFilePath)}`);
package/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { authCommand } from './commands/auth.js';
3
+ import { claudeCodeCommand } from './commands/claude-code.js';
3
4
  import { initCommand } from './commands/init.js';
4
5
  import { publishCommand } from './commands/publish.js';
5
6
  import { rootCommand } from './commands/root.js';
6
7
  import { serveCommand } from './commands/serve.js';
7
8
  rootCommand.addCommand(authCommand);
9
+ rootCommand.addCommand(claudeCodeCommand);
8
10
  rootCommand.addCommand(serveCommand);
9
11
  rootCommand.addCommand(initCommand);
10
12
  rootCommand.addCommand(publishCommand);
@@ -0,0 +1,13 @@
1
+ export type CreateProjectOptions = {
2
+ name: string;
3
+ description: string;
4
+ author: string;
5
+ version: string;
6
+ template: string;
7
+ claudeCode: boolean;
8
+ projectPath: string;
9
+ progressFn: (createdFilePath: string) => void;
10
+ };
11
+ export declare function createProject(options: CreateProjectOptions): Promise<void>;
12
+ export declare function checkDirectoryConflicts(projectPath: string): Promise<string[]>;
13
+ export declare function removeConflictingFiles(projectPath: string, conflicts: string[]): Promise<void>;
@@ -0,0 +1,188 @@
1
+ import { spawn, execSync } from 'child_process';
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import { ansi } from '../utils/ansi.js';
5
+ import { copyDir, templatesDir } from '../utils/template.js';
6
+ // Files that can exist in a directory without being considered conflicts
7
+ const safeExistingFiles = [
8
+ '.DS_Store',
9
+ '.git',
10
+ '.gitignore',
11
+ '.gitattributes',
12
+ '.idea',
13
+ '.vscode',
14
+ 'Thumbs.db',
15
+ 'LICENSE',
16
+ 'README.md',
17
+ ];
18
+ export async function createProject(options) {
19
+ const { name, description, author, version, template, claudeCode, projectPath, progressFn } = options;
20
+ await fs.mkdir(projectPath, { recursive: true });
21
+ // Initialize git repo early (before template dependencies that may have git hooks)
22
+ const gitInitialized = tryGitInit(projectPath);
23
+ if (gitInitialized) {
24
+ console.log('\nInitialized a git repository');
25
+ }
26
+ await copyDir(path.join(templatesDir, template), projectPath, {
27
+ name,
28
+ version,
29
+ description,
30
+ author,
31
+ }, progressFn);
32
+ // Optionally apply Claude Code overlay (skills and settings)
33
+ if (claudeCode) {
34
+ await copyDir(path.join(templatesDir, 'claude-code'), projectPath, {
35
+ name,
36
+ version,
37
+ description,
38
+ author,
39
+ }, progressFn);
40
+ }
41
+ // Install latest versions of @telemetryos/sdk and @telemetryos/cli
42
+ console.log('\nInstalling dependencies...');
43
+ await installPackages(projectPath);
44
+ // Create initial commit after all files are in place
45
+ if (gitInitialized) {
46
+ await tryGitCommit(projectPath);
47
+ }
48
+ printSuccessMessage(name, projectPath);
49
+ }
50
+ async function installPackages(projectPath) {
51
+ // Install SDK as a regular dependency
52
+ await new Promise((resolve, reject) => {
53
+ const sdkInstall = spawn('npm', ['install', '@telemetryos/sdk'], {
54
+ cwd: projectPath,
55
+ stdio: 'inherit',
56
+ shell: true,
57
+ });
58
+ sdkInstall.on('close', (code) => {
59
+ if (code === 0) {
60
+ resolve();
61
+ }
62
+ else {
63
+ reject(new Error(`npm install @telemetryos/sdk failed with code ${code}`));
64
+ }
65
+ });
66
+ sdkInstall.on('error', (error) => {
67
+ reject(error);
68
+ });
69
+ });
70
+ // Install CLI as a dev dependency
71
+ await new Promise((resolve, reject) => {
72
+ const cliInstall = spawn('npm', ['install', '-D', '@telemetryos/cli'], {
73
+ cwd: projectPath,
74
+ stdio: 'inherit',
75
+ shell: true,
76
+ });
77
+ cliInstall.on('close', (code) => {
78
+ if (code === 0) {
79
+ resolve();
80
+ }
81
+ else {
82
+ reject(new Error(`npm install -D @telemetryos/cli failed with code ${code}`));
83
+ }
84
+ });
85
+ cliInstall.on('error', (error) => {
86
+ reject(error);
87
+ });
88
+ });
89
+ }
90
+ function isInGitRepository(cwd) {
91
+ try {
92
+ execSync('git rev-parse --is-inside-work-tree', { cwd, stdio: 'ignore' });
93
+ return true;
94
+ }
95
+ catch {
96
+ return false;
97
+ }
98
+ }
99
+ function tryGitInit(projectPath) {
100
+ try {
101
+ execSync('git --version', { stdio: 'ignore' });
102
+ if (isInGitRepository(projectPath)) {
103
+ return false;
104
+ }
105
+ execSync('git init', { cwd: projectPath, stdio: 'ignore' });
106
+ return true;
107
+ }
108
+ catch {
109
+ return false;
110
+ }
111
+ }
112
+ async function tryGitCommit(projectPath) {
113
+ try {
114
+ execSync('git add -A', { cwd: projectPath, stdio: 'ignore' });
115
+ execSync('git commit -m "Initialize project using TelemetryOS CLI"', {
116
+ cwd: projectPath,
117
+ stdio: 'ignore',
118
+ });
119
+ console.log('Created initial commit');
120
+ return true;
121
+ }
122
+ catch {
123
+ // Commit failed (e.g., git user not configured)
124
+ // Remove .git directory to avoid half-initialized state
125
+ console.log('Git commit not created');
126
+ console.log('Removing .git directory...');
127
+ try {
128
+ await fs.rm(path.join(projectPath, '.git'), { recursive: true, force: true });
129
+ }
130
+ catch {
131
+ // Ignore cleanup errors
132
+ }
133
+ return false;
134
+ }
135
+ }
136
+ export async function checkDirectoryConflicts(projectPath) {
137
+ try {
138
+ const entries = await fs.readdir(projectPath);
139
+ return entries.filter((file) => !safeExistingFiles.includes(file));
140
+ }
141
+ catch (error) {
142
+ // Directory doesn't exist, no conflicts
143
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
144
+ return [];
145
+ }
146
+ throw error;
147
+ }
148
+ }
149
+ export async function removeConflictingFiles(projectPath, conflicts) {
150
+ for (const file of conflicts) {
151
+ const filePath = path.join(projectPath, file);
152
+ try {
153
+ const stat = await fs.stat(filePath);
154
+ if (stat.isDirectory()) {
155
+ await fs.rm(filePath, { recursive: true, force: true });
156
+ }
157
+ else {
158
+ await fs.unlink(filePath);
159
+ }
160
+ }
161
+ catch (error) {
162
+ // Ignore errors for files that may have already been removed
163
+ console.error(`Warning: Could not remove ${file}: ${error instanceof Error ? error.message : error}`);
164
+ }
165
+ }
166
+ }
167
+ function printSuccessMessage(name, projectPath) {
168
+ // Calculate relative path from cwd for cleaner display
169
+ const relativePath = path.relative(process.cwd(), projectPath);
170
+ const displayPath = relativePath || '.';
171
+ console.log(`
172
+ ${ansi.green}Success!${ansi.reset} Created ${ansi.bold}${name}${ansi.reset} at ${ansi.cyan}${displayPath}${ansi.reset}
173
+
174
+ Inside that directory, you can run:
175
+
176
+ ${ansi.cyan}npm run dev${ansi.reset}
177
+ Starts the development server
178
+
179
+ ${ansi.cyan}npm run build${ansi.reset}
180
+ Builds the app for production
181
+
182
+ You may begin with:
183
+
184
+ ${ansi.cyan}cd ${displayPath}${ansi.reset}
185
+ ${ansi.cyan}npm run dev${ansi.reset}
186
+
187
+ `);
188
+ }
@@ -2,6 +2,8 @@ import z from 'zod';
2
2
  export declare const projectConfigSchema: z.ZodObject<{
3
3
  name: z.ZodOptional<z.ZodString>;
4
4
  version: z.ZodOptional<z.ZodString>;
5
+ description: z.ZodOptional<z.ZodString>;
6
+ logoPath: z.ZodOptional<z.ZodString>;
5
7
  thumbnailPath: z.ZodOptional<z.ZodString>;
6
8
  mountPoints: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
7
9
  path: z.ZodString;
@@ -15,6 +17,7 @@ export declare const projectConfigSchema: z.ZodObject<{
15
17
  containers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
16
18
  image: z.ZodString;
17
19
  }, z.z.core.$strip>, z.ZodString]>>>;
20
+ useSpaRouting: z.ZodOptional<z.ZodBoolean>;
18
21
  devServer: z.ZodOptional<z.ZodObject<{
19
22
  runCommand: z.ZodOptional<z.ZodString>;
20
23
  url: z.ZodString;
@@ -16,11 +16,14 @@ const containerSchema = z.object({
16
16
  export const projectConfigSchema = z.object({
17
17
  name: z.string().optional(),
18
18
  version: z.string().optional(),
19
+ description: z.string().optional(),
20
+ logoPath: z.string().optional(),
19
21
  thumbnailPath: z.string().optional(),
20
22
  mountPoints: z.record(z.string(), z.union([mountPointSchema, z.string()])).optional(),
21
23
  backgroundWorkers: z.record(z.string(), z.union([backgroundWorkerSchema, z.string()])).optional(),
22
24
  serverWorkers: z.record(z.string(), z.union([serverWorkerSchema, z.string()])).optional(),
23
25
  containers: z.record(z.string(), z.union([containerSchema, z.string()])).optional(),
26
+ useSpaRouting: z.boolean().optional(),
24
27
  devServer: z
25
28
  .object({
26
29
  runCommand: z.string().optional(),