@telemetryos/cli 1.9.0 → 1.11.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.
- package/CHANGELOG.md +25 -0
- package/dist/commands/auth.js +8 -15
- package/dist/commands/init.js +131 -68
- package/dist/commands/publish.d.ts +22 -0
- package/dist/commands/publish.js +238 -0
- package/dist/index.js +2 -0
- package/dist/plugins/math-tools.d.ts +2 -0
- package/dist/plugins/math-tools.js +18 -0
- package/dist/services/api-client.d.ts +18 -0
- package/dist/services/api-client.js +70 -0
- package/dist/services/archiver.d.ts +4 -0
- package/dist/services/archiver.js +65 -0
- package/dist/services/build-poller.d.ts +10 -0
- package/dist/services/build-poller.js +63 -0
- package/dist/services/cli-config.d.ts +10 -0
- package/dist/services/cli-config.js +45 -0
- package/dist/services/generate-application.d.ts +2 -1
- package/dist/services/generate-application.js +31 -32
- package/dist/services/project-config.d.ts +24 -0
- package/dist/services/project-config.js +51 -0
- package/dist/services/run-server.js +29 -73
- package/dist/types/api.d.ts +44 -0
- package/dist/types/api.js +1 -0
- package/dist/types/applications.d.ts +44 -0
- package/dist/types/applications.js +1 -0
- package/dist/utils/ansi.d.ts +10 -0
- package/dist/utils/ansi.js +10 -0
- package/dist/utils/path-utils.d.ts +55 -0
- package/dist/utils/path-utils.js +99 -0
- package/package.json +4 -2
- package/templates/vite-react-typescript/CLAUDE.md +14 -6
- package/templates/vite-react-typescript/_claude/skills/tos-architecture/SKILL.md +4 -28
- package/templates/vite-react-typescript/_claude/skills/tos-multi-mode/SKILL.md +359 -0
- package/templates/vite-react-typescript/_claude/skills/tos-render-design/SKILL.md +304 -12
- package/templates/vite-react-typescript/_claude/skills/tos-render-kiosk-design/SKILL.md +384 -0
- package/templates/vite-react-typescript/_claude/skills/tos-render-signage-design/SKILL.md +515 -0
- package/templates/vite-react-typescript/_claude/skills/tos-render-ui-design/SKILL.md +325 -0
- package/templates/vite-react-typescript/_claude/skills/tos-requirements/SKILL.md +405 -125
- package/templates/vite-react-typescript/_claude/skills/tos-store-sync/SKILL.md +96 -5
- package/templates/vite-react-typescript/_claude/skills/tos-weather-api/SKILL.md +443 -269
- package/templates/vite-react-typescript/index.html +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @telemetryos/cli
|
|
2
2
|
|
|
3
|
+
## 1.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Added media select component, improved tos init tos publish and tos auth commands, add new store hook
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @telemetryos/development-application-host-ui@1.11.0
|
|
13
|
+
|
|
14
|
+
## 1.10.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- Added MQTT, publish command, and other improvements
|
|
19
|
+
- Added MQTT support to the SDK
|
|
20
|
+
- Added a command `tos publish` that sends the project to be built on the platform (requires user token, so wont work right away)
|
|
21
|
+
- Added mock data for the currency service
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- Updated dependencies
|
|
26
|
+
- @telemetryos/development-application-host-ui@1.10.0
|
|
27
|
+
|
|
3
28
|
## 1.9.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
|
-
import {
|
|
3
|
+
import { apiTokenSchema, loadCliConfig, saveCliConfig } from '../services/cli-config.js';
|
|
4
4
|
export const authCommand = new Command('auth')
|
|
5
5
|
.description('Authenticate with TelemetryOS by saving your API token')
|
|
6
6
|
.option('-t, --token <string>', 'API token (skip interactive prompt)')
|
|
@@ -12,22 +12,15 @@ function maskToken(token) {
|
|
|
12
12
|
return `${token.slice(0, 4)}...${token.slice(-4)}`;
|
|
13
13
|
}
|
|
14
14
|
function validateToken(input) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (!/^[a-f0-9-]+$/i.test(trimmed)) {
|
|
20
|
-
return 'Token must be a valid string';
|
|
21
|
-
}
|
|
22
|
-
if (trimmed.length < 24) {
|
|
23
|
-
return 'Token must be at least 24 characters';
|
|
24
|
-
}
|
|
25
|
-
return true;
|
|
15
|
+
const result = apiTokenSchema.safeParse(input.trim());
|
|
16
|
+
if (result.success)
|
|
17
|
+
return true;
|
|
18
|
+
return result.error.issues[0].message;
|
|
26
19
|
}
|
|
27
20
|
async function handleAuthCommand(options) {
|
|
28
21
|
let token = options.token;
|
|
29
|
-
const
|
|
30
|
-
const existingToken =
|
|
22
|
+
const existingCliConfig = await loadCliConfig();
|
|
23
|
+
const existingToken = existingCliConfig.apiToken;
|
|
31
24
|
if (existingToken && !options.force) {
|
|
32
25
|
const { confirm } = await inquirer.prompt([
|
|
33
26
|
{
|
|
@@ -62,6 +55,6 @@ async function handleAuthCommand(options) {
|
|
|
62
55
|
]);
|
|
63
56
|
token = answers.token.trim();
|
|
64
57
|
}
|
|
65
|
-
await
|
|
58
|
+
await saveCliConfig({ ...existingCliConfig, apiToken: token });
|
|
66
59
|
console.log('API token saved successfully!');
|
|
67
60
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,100 +1,163 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { generateApplication } from '../services/generate-application.js';
|
|
2
|
+
import { generateApplication, checkDirectoryConflicts, removeConflictingFiles, } from '../services/generate-application.js';
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import { resolveProjectPathAndName, validateProjectName } from '../utils/path-utils.js';
|
|
6
|
+
import { ansi } from '../utils/ansi.js';
|
|
7
|
+
async function promptConflictResolution() {
|
|
8
|
+
const { action } = await inquirer.prompt([
|
|
9
|
+
{
|
|
10
|
+
type: 'list',
|
|
11
|
+
name: 'action',
|
|
12
|
+
message: 'Target directory is not empty. Please choose how to proceed:',
|
|
13
|
+
choices: [
|
|
14
|
+
{ name: 'Cancel operation', value: 'cancel' },
|
|
15
|
+
{ name: 'Remove existing files and continue', value: 'remove' },
|
|
16
|
+
{ name: 'Ignore files and continue', value: 'ignore' },
|
|
17
|
+
],
|
|
18
|
+
default: 'cancel',
|
|
19
|
+
},
|
|
20
|
+
]);
|
|
21
|
+
return action;
|
|
22
|
+
}
|
|
5
23
|
export const initCommand = new Command('init')
|
|
6
24
|
.description('Initializes a new telemetryOS application')
|
|
25
|
+
.argument('[project-path]', 'Path to create the project (defaults to current directory)', '')
|
|
7
26
|
.option('-d, --description <string>', 'The description of the application', '')
|
|
8
27
|
.option('-a, --author <string>', 'The author of the application', '')
|
|
9
28
|
.option('-v, --version <string>', 'The version of the application', '0.1.0')
|
|
10
29
|
.option('-t, --template <string>', 'The template to use (vite-react-typescript)', '')
|
|
11
|
-
.
|
|
30
|
+
.option('-y, --yes', 'Skip all prompts and use defaults', false)
|
|
12
31
|
.action(handleInitCommand);
|
|
13
|
-
async function handleInitCommand(
|
|
14
|
-
|
|
32
|
+
async function handleInitCommand(projectPathArg, options) {
|
|
33
|
+
// Step 1: Resolve path and derive name
|
|
34
|
+
const cwd = process.cwd();
|
|
35
|
+
const inputPath = projectPathArg || '.';
|
|
36
|
+
const { resolvedPath, derivedName } = resolveProjectPathAndName(inputPath, cwd);
|
|
37
|
+
// Step 2: Validate the derived name BEFORE any destructive operations
|
|
38
|
+
const validation = validateProjectName(derivedName);
|
|
39
|
+
if (validation !== true) {
|
|
40
|
+
console.error(`\n${ansi.red}Error:${ansi.reset} Cannot initialize project - ${validation}`);
|
|
41
|
+
console.error(`\nDerived project name: ${ansi.yellow}${derivedName}${ansi.reset}`);
|
|
42
|
+
console.error(`From path: ${ansi.dim}${path.relative(cwd, resolvedPath)}${ansi.reset}`);
|
|
43
|
+
console.error(`\n${ansi.bold}Project names must:${ansi.reset}`);
|
|
44
|
+
console.error(' • Contain only lowercase letters, numbers, and hyphens');
|
|
45
|
+
console.error(' • Not start or end with a hyphen');
|
|
46
|
+
console.error(' • Not start with a dot (.) or underscore (_)');
|
|
47
|
+
console.error(' • Be between 1 and 214 characters');
|
|
48
|
+
console.error(' • Not be a reserved name (e.g., node_modules)');
|
|
49
|
+
console.error(`\n${ansi.yellow}Tip:${ansi.reset} Use a different directory name or specify a path`);
|
|
50
|
+
console.error(`Example: ${ansi.cyan}tos init my-app${ansi.reset}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
// Step 3: Check for directory conflicts BEFORE prompting for details
|
|
54
|
+
const conflicts = await checkDirectoryConflicts(resolvedPath);
|
|
55
|
+
if (conflicts.length > 0) {
|
|
56
|
+
console.log(`\n${ansi.yellow}⚠${ansi.reset} Target directory contains files that could conflict:\n`);
|
|
57
|
+
conflicts.slice(0, 10).forEach((file) => console.log(` ${ansi.dim}${file}${ansi.reset}`));
|
|
58
|
+
if (conflicts.length > 10) {
|
|
59
|
+
console.log(` ${ansi.dim}... and ${conflicts.length - 10} more${ansi.reset}`);
|
|
60
|
+
}
|
|
61
|
+
console.log();
|
|
62
|
+
const action = await promptConflictResolution();
|
|
63
|
+
if (action === 'cancel') {
|
|
64
|
+
console.log('Operation cancelled');
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
else if (action === 'remove') {
|
|
68
|
+
console.log('\nRemoving existing files...');
|
|
69
|
+
await removeConflictingFiles(resolvedPath, conflicts);
|
|
70
|
+
console.log(`${ansi.green}✓${ansi.reset} Existing files removed\n`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// action === 'ignore' - continue with merge
|
|
74
|
+
console.log('\nContinuing with existing files (will overwrite conflicts)...\n');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Step 4: Setup variables from options
|
|
78
|
+
let name = derivedName;
|
|
15
79
|
let description = options.description;
|
|
16
80
|
let author = options.author;
|
|
17
81
|
let version = options.version;
|
|
18
82
|
let template = options.template;
|
|
19
|
-
|
|
20
|
-
if (
|
|
83
|
+
// Step 5: Build prompt questions (skipped with --yes)
|
|
84
|
+
if (options.yes) {
|
|
85
|
+
if (!name) {
|
|
86
|
+
console.error('Project name is required when using --yes');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
if (!description)
|
|
90
|
+
description = 'A telemetryOS application';
|
|
91
|
+
if (!template)
|
|
92
|
+
template = 'vite-react-typescript';
|
|
93
|
+
if (!version)
|
|
94
|
+
version = '0.1.0';
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const questions = [];
|
|
98
|
+
// Always show the derived name with option to override
|
|
99
|
+
// This provides transparency and control
|
|
21
100
|
questions.push({
|
|
22
101
|
type: 'input',
|
|
23
102
|
name: 'name',
|
|
24
|
-
message: '
|
|
103
|
+
message: 'Project name:',
|
|
104
|
+
default: derivedName,
|
|
25
105
|
validate: (input) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return 'Application name cannot be empty';
|
|
106
|
+
const result = validateProjectName(input);
|
|
107
|
+
return result === true ? true : result;
|
|
29
108
|
},
|
|
30
109
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
110
|
+
if (!description)
|
|
111
|
+
questions.push({
|
|
112
|
+
type: 'input',
|
|
113
|
+
name: 'description',
|
|
114
|
+
message: 'What is the description of your application?',
|
|
115
|
+
default: 'A telemetryOS application',
|
|
116
|
+
});
|
|
117
|
+
if (!author)
|
|
118
|
+
questions.push({
|
|
119
|
+
type: 'input',
|
|
120
|
+
name: 'author',
|
|
121
|
+
message: 'Who is the author of your application?',
|
|
122
|
+
default: '',
|
|
123
|
+
});
|
|
124
|
+
if (!version)
|
|
125
|
+
questions.push({
|
|
126
|
+
type: 'input',
|
|
127
|
+
name: 'version',
|
|
128
|
+
message: 'What is the version of your application?',
|
|
129
|
+
default: '0.1.0',
|
|
130
|
+
validate: (input) => {
|
|
131
|
+
if (/^\d+\.\d+\.\d+(-.+)?$/.test(input))
|
|
132
|
+
return true;
|
|
133
|
+
return 'Version must be in semver format (e.g. 1.0.0)';
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
if (!template)
|
|
137
|
+
questions.push({
|
|
138
|
+
type: 'list',
|
|
139
|
+
name: 'template',
|
|
140
|
+
message: 'Which template would you like to use?',
|
|
141
|
+
choices: [{ name: 'Vite + React + TypeScript', value: 'vite-react-typescript' }],
|
|
142
|
+
});
|
|
143
|
+
// Step 6: Prompt user
|
|
65
144
|
const answers = await inquirer.prompt(questions);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
description = answers.description;
|
|
72
|
-
if (answers.author)
|
|
73
|
-
author = answers.author;
|
|
74
|
-
if (answers.template)
|
|
75
|
-
template = answers.template;
|
|
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;
|
|
76
150
|
}
|
|
77
|
-
|
|
151
|
+
// Step 7: Generate application
|
|
78
152
|
await generateApplication({
|
|
79
153
|
name,
|
|
80
154
|
description,
|
|
81
155
|
author,
|
|
82
156
|
version,
|
|
83
157
|
template,
|
|
84
|
-
projectPath,
|
|
158
|
+
projectPath: resolvedPath,
|
|
85
159
|
progressFn: (createdFilePath) => {
|
|
86
|
-
console.log(`.${path.sep}${path.relative(
|
|
87
|
-
},
|
|
88
|
-
confirmOverwrite: async () => {
|
|
89
|
-
const { proceed } = await inquirer.prompt([
|
|
90
|
-
{
|
|
91
|
-
type: 'confirm',
|
|
92
|
-
name: 'proceed',
|
|
93
|
-
message: 'Do you want to continue and overwrite these files?',
|
|
94
|
-
default: false,
|
|
95
|
-
},
|
|
96
|
-
]);
|
|
97
|
-
return proceed;
|
|
160
|
+
console.log(`.${path.sep}${path.relative(cwd, createdFilePath)}`);
|
|
98
161
|
},
|
|
99
162
|
});
|
|
100
163
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export declare const publishCommand: Command;
|
|
3
|
+
type PublishOptions = {
|
|
4
|
+
interactive?: boolean;
|
|
5
|
+
name?: string;
|
|
6
|
+
baseImage?: string;
|
|
7
|
+
buildScript?: string;
|
|
8
|
+
buildOutput?: string;
|
|
9
|
+
workingPath?: string;
|
|
10
|
+
};
|
|
11
|
+
type PublishCallbacks = {
|
|
12
|
+
onLog?: (line: string) => void;
|
|
13
|
+
onStateChange?: (state: string) => void;
|
|
14
|
+
onComplete?: (data: {
|
|
15
|
+
success: boolean;
|
|
16
|
+
buildIndex?: number;
|
|
17
|
+
duration?: string;
|
|
18
|
+
}) => void;
|
|
19
|
+
onError?: (error: Error) => void;
|
|
20
|
+
};
|
|
21
|
+
declare function handlePublishCommand(projectPath: string, options: PublishOptions, callbacks?: PublishCallbacks): Promise<void>;
|
|
22
|
+
export { handlePublishCommand, type PublishCallbacks, type PublishOptions };
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { ApiClient } from '../services/api-client.js';
|
|
5
|
+
import { loadProjectConfig } from '../services/project-config.js';
|
|
6
|
+
import { createArchive } from '../services/archiver.js';
|
|
7
|
+
import { pollBuild } from '../services/build-poller.js';
|
|
8
|
+
import { ansi } from '../utils/ansi.js';
|
|
9
|
+
// Default values for new application builds
|
|
10
|
+
const APP_BUILD_DEFAULTS = {
|
|
11
|
+
baseImage: 'node:20',
|
|
12
|
+
buildScript: 'npm install\nnpm run build',
|
|
13
|
+
buildOutput: '/dist',
|
|
14
|
+
workingPath: '/',
|
|
15
|
+
};
|
|
16
|
+
export const publishCommand = new Command('publish')
|
|
17
|
+
.description('Publish an application to TelemetryOS')
|
|
18
|
+
.argument('[project-path]', 'Path to the project directory. Defaults to current working directory', process.cwd())
|
|
19
|
+
.option('-i, --interactive', 'Interactively prompt for all build configuration')
|
|
20
|
+
.option('--name <name>', 'Application name (overrides config)')
|
|
21
|
+
.option('--base-image <image>', 'Docker base image for build')
|
|
22
|
+
.option('--build-script <script>', 'Build script commands')
|
|
23
|
+
.option('--build-output <path>', 'Build output directory')
|
|
24
|
+
.option('--working-path <path>', 'Working directory in container')
|
|
25
|
+
.action(handlePublishCommand);
|
|
26
|
+
function getSpecifiedBuildOptions(options) {
|
|
27
|
+
const specified = [];
|
|
28
|
+
if (options.baseImage !== undefined)
|
|
29
|
+
specified.push('--base-image');
|
|
30
|
+
if (options.buildScript !== undefined)
|
|
31
|
+
specified.push('--build-script');
|
|
32
|
+
if (options.buildOutput !== undefined)
|
|
33
|
+
specified.push('--build-output');
|
|
34
|
+
if (options.workingPath !== undefined)
|
|
35
|
+
specified.push('--working-path');
|
|
36
|
+
return specified;
|
|
37
|
+
}
|
|
38
|
+
async function handlePublishCommand(projectPath, options, callbacks) {
|
|
39
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
40
|
+
projectPath = path.resolve(process.cwd(), projectPath);
|
|
41
|
+
const logMessage = (callbacks === null || callbacks === void 0 ? void 0 : callbacks.onLog) || ((msg) => console.log(msg));
|
|
42
|
+
try {
|
|
43
|
+
// Step 1: Load project config
|
|
44
|
+
(_a = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onStateChange) === null || _a === void 0 ? void 0 : _a.call(callbacks, 'loading');
|
|
45
|
+
logMessage(`\n${ansi.cyan}Loading project configuration...${ansi.reset}`);
|
|
46
|
+
const config = await loadProjectConfig(projectPath);
|
|
47
|
+
// Step 2: Resolve build configuration
|
|
48
|
+
let buildConfig = {
|
|
49
|
+
name: options.name || config.name || '',
|
|
50
|
+
baseImage: options.baseImage,
|
|
51
|
+
buildScript: options.buildScript,
|
|
52
|
+
buildOutput: options.buildOutput,
|
|
53
|
+
workingPath: options.workingPath,
|
|
54
|
+
};
|
|
55
|
+
if (options.interactive) {
|
|
56
|
+
// Interactive mode: prompt for all fields
|
|
57
|
+
buildConfig = await promptBuildConfig(buildConfig);
|
|
58
|
+
}
|
|
59
|
+
else if (!buildConfig.name) {
|
|
60
|
+
// Non-interactive mode but no name: prompt just for name
|
|
61
|
+
const answers = await inquirer.prompt([
|
|
62
|
+
{
|
|
63
|
+
type: 'input',
|
|
64
|
+
name: 'name',
|
|
65
|
+
message: 'Application name:',
|
|
66
|
+
validate: (input) => (input.trim() ? true : 'Application name is required'),
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
buildConfig.name = answers.name.trim();
|
|
70
|
+
}
|
|
71
|
+
if (!buildConfig.name) {
|
|
72
|
+
const error = new Error('Application name is required');
|
|
73
|
+
logMessage(`${ansi.red}Error: Application name is required${ansi.reset}`);
|
|
74
|
+
(_b = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onError) === null || _b === void 0 ? void 0 : _b.call(callbacks, error);
|
|
75
|
+
if (!callbacks)
|
|
76
|
+
process.exit(1);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
logMessage(` Project: ${ansi.bold}${buildConfig.name}${ansi.reset}`);
|
|
80
|
+
if (config.version) {
|
|
81
|
+
logMessage(` Version: ${config.version}`);
|
|
82
|
+
}
|
|
83
|
+
// Step 3: Initialize API client
|
|
84
|
+
(_c = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onStateChange) === null || _c === void 0 ? void 0 : _c.call(callbacks, 'authenticating');
|
|
85
|
+
logMessage(`\n${ansi.cyan}Authenticating...${ansi.reset}`);
|
|
86
|
+
const apiClient = await ApiClient.create();
|
|
87
|
+
logMessage(` ${ansi.green}Authenticated${ansi.reset}`);
|
|
88
|
+
// Step 4: Create archive
|
|
89
|
+
(_d = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onStateChange) === null || _d === void 0 ? void 0 : _d.call(callbacks, 'archiving');
|
|
90
|
+
logMessage(`\n${ansi.cyan}Archiving project...${ansi.reset}`);
|
|
91
|
+
const { buffer, filename } = await createArchive(projectPath, (msg) => {
|
|
92
|
+
logMessage(` ${ansi.dim}${msg}${ansi.reset}`);
|
|
93
|
+
});
|
|
94
|
+
// Step 5: Find or create application
|
|
95
|
+
logMessage(`\n${ansi.cyan}Checking for existing application...${ansi.reset}`);
|
|
96
|
+
const appsRes = await apiClient.get('/application');
|
|
97
|
+
const apps = (await appsRes.json());
|
|
98
|
+
let app = apps.find((a) => a.title === buildConfig.name) || null;
|
|
99
|
+
if (app) {
|
|
100
|
+
logMessage(` Found existing application: ${ansi.dim}${app.id}${ansi.reset}`);
|
|
101
|
+
const specifiedOptions = getSpecifiedBuildOptions(options);
|
|
102
|
+
if (specifiedOptions.length > 0) {
|
|
103
|
+
logMessage(` ${ansi.yellow}Warning: ${specifiedOptions.join(', ')} ignored for existing application${ansi.reset}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
logMessage(` Creating new application...`);
|
|
108
|
+
const createRequest = {
|
|
109
|
+
kind: 'uploaded',
|
|
110
|
+
title: buildConfig.name,
|
|
111
|
+
baseImage: (_e = buildConfig.baseImage) !== null && _e !== void 0 ? _e : APP_BUILD_DEFAULTS.baseImage,
|
|
112
|
+
baseImageRegistryAuth: '',
|
|
113
|
+
buildWorkingPath: (_f = buildConfig.workingPath) !== null && _f !== void 0 ? _f : APP_BUILD_DEFAULTS.workingPath,
|
|
114
|
+
buildScript: (_g = buildConfig.buildScript) !== null && _g !== void 0 ? _g : APP_BUILD_DEFAULTS.buildScript,
|
|
115
|
+
buildOutputPath: (_h = buildConfig.buildOutput) !== null && _h !== void 0 ? _h : APP_BUILD_DEFAULTS.buildOutput,
|
|
116
|
+
};
|
|
117
|
+
const createRes = await apiClient.post('/application', { body: createRequest });
|
|
118
|
+
app = (await createRes.json());
|
|
119
|
+
logMessage(` ${ansi.green}Created application:${ansi.reset} ${ansi.dim}${app.id}${ansi.reset}`);
|
|
120
|
+
}
|
|
121
|
+
// Step 6: Upload archive
|
|
122
|
+
(_j = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onStateChange) === null || _j === void 0 ? void 0 : _j.call(callbacks, 'uploading');
|
|
123
|
+
logMessage(`\n${ansi.cyan}Uploading archive...${ansi.reset}`);
|
|
124
|
+
const formData = new FormData();
|
|
125
|
+
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
126
|
+
const blob = new Blob([arrayBuffer], { type: 'application/gzip' });
|
|
127
|
+
formData.append('file', blob, filename);
|
|
128
|
+
await apiClient.post(`/application/${app.id}/archive`, { body: formData });
|
|
129
|
+
logMessage(` ${ansi.green}Upload complete${ansi.reset}`);
|
|
130
|
+
// Step 7: Poll for build status and stream logs
|
|
131
|
+
(_k = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onStateChange) === null || _k === void 0 ? void 0 : _k.call(callbacks, 'building');
|
|
132
|
+
logMessage(`\n${ansi.cyan}Building...${ansi.reset}`);
|
|
133
|
+
logMessage(`${ansi.white}Build Logs${ansi.reset}`);
|
|
134
|
+
const build = await pollBuild(apiClient, app.id, {
|
|
135
|
+
onLog: (line) => {
|
|
136
|
+
logMessage(` ${ansi.dim}${line}${ansi.reset}`);
|
|
137
|
+
},
|
|
138
|
+
onStateChange: () => { },
|
|
139
|
+
onComplete: () => { },
|
|
140
|
+
onError: (error) => {
|
|
141
|
+
var _a;
|
|
142
|
+
logMessage(`${ansi.red}Polling error: ${error.message}${ansi.reset}`);
|
|
143
|
+
(_a = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onError) === null || _a === void 0 ? void 0 : _a.call(callbacks, error);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
// Step 8: Report result
|
|
147
|
+
if (build.state === 'success') {
|
|
148
|
+
(_l = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onStateChange) === null || _l === void 0 ? void 0 : _l.call(callbacks, 'success');
|
|
149
|
+
logMessage(`\n${ansi.green}${ansi.bold}Build successful!${ansi.reset}`);
|
|
150
|
+
logMessage(`\n Application: ${buildConfig.name}`);
|
|
151
|
+
logMessage(` Build: #${build.index}`);
|
|
152
|
+
let duration = '';
|
|
153
|
+
if (build.finishedAt && build.startedAt) {
|
|
154
|
+
duration = calculateDuration(build.startedAt, build.finishedAt);
|
|
155
|
+
logMessage(` Duration: ${duration}`);
|
|
156
|
+
}
|
|
157
|
+
logMessage('');
|
|
158
|
+
(_m = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onComplete) === null || _m === void 0 ? void 0 : _m.call(callbacks, {
|
|
159
|
+
success: true,
|
|
160
|
+
buildIndex: build.index,
|
|
161
|
+
duration,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
(_o = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onStateChange) === null || _o === void 0 ? void 0 : _o.call(callbacks, 'failure');
|
|
166
|
+
logMessage(`\n${ansi.red}${ansi.bold}Build failed${ansi.reset}`);
|
|
167
|
+
logMessage(`\n State: ${build.state}`);
|
|
168
|
+
logMessage('');
|
|
169
|
+
const error = new Error(`Build failed with state: ${build.state}`);
|
|
170
|
+
(_p = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onError) === null || _p === void 0 ? void 0 : _p.call(callbacks, error);
|
|
171
|
+
if (!callbacks) {
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
const err = error;
|
|
178
|
+
logMessage(`\n${ansi.red}Error: ${err.message}${ansi.reset}\n`);
|
|
179
|
+
(_q = callbacks === null || callbacks === void 0 ? void 0 : callbacks.onError) === null || _q === void 0 ? void 0 : _q.call(callbacks, err);
|
|
180
|
+
if (!callbacks) {
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Export for use by server
|
|
186
|
+
export { handlePublishCommand };
|
|
187
|
+
async function promptBuildConfig(defaults) {
|
|
188
|
+
var _a, _b, _c, _d;
|
|
189
|
+
const answers = await inquirer.prompt([
|
|
190
|
+
{
|
|
191
|
+
type: 'input',
|
|
192
|
+
name: 'name',
|
|
193
|
+
message: 'Application name:',
|
|
194
|
+
default: defaults.name || undefined,
|
|
195
|
+
validate: (input) => (input.trim() ? true : 'Application name is required'),
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
type: 'input',
|
|
199
|
+
name: 'baseImage',
|
|
200
|
+
message: 'Base image:',
|
|
201
|
+
default: (_a = defaults.baseImage) !== null && _a !== void 0 ? _a : APP_BUILD_DEFAULTS.baseImage,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
type: 'input',
|
|
205
|
+
name: 'buildScript',
|
|
206
|
+
message: 'Build script:',
|
|
207
|
+
default: (_b = defaults.buildScript) !== null && _b !== void 0 ? _b : APP_BUILD_DEFAULTS.buildScript,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
type: 'input',
|
|
211
|
+
name: 'buildOutput',
|
|
212
|
+
message: 'Build output path:',
|
|
213
|
+
default: (_c = defaults.buildOutput) !== null && _c !== void 0 ? _c : APP_BUILD_DEFAULTS.buildOutput,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
type: 'input',
|
|
217
|
+
name: 'workingPath',
|
|
218
|
+
message: 'Working path:',
|
|
219
|
+
default: (_d = defaults.workingPath) !== null && _d !== void 0 ? _d : APP_BUILD_DEFAULTS.workingPath,
|
|
220
|
+
},
|
|
221
|
+
]);
|
|
222
|
+
return {
|
|
223
|
+
name: answers.name.trim(),
|
|
224
|
+
baseImage: answers.baseImage.trim(),
|
|
225
|
+
buildScript: answers.buildScript.trim(),
|
|
226
|
+
buildOutput: answers.buildOutput.trim(),
|
|
227
|
+
workingPath: answers.workingPath.trim(),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function calculateDuration(start, end) {
|
|
231
|
+
const ms = new Date(end).getTime() - new Date(start).getTime();
|
|
232
|
+
const seconds = Math.floor(ms / 1000);
|
|
233
|
+
if (seconds < 60)
|
|
234
|
+
return `${seconds}s`;
|
|
235
|
+
const minutes = Math.floor(seconds / 60);
|
|
236
|
+
const remainingSeconds = seconds % 60;
|
|
237
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
238
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { authCommand } from './commands/auth.js';
|
|
3
3
|
import { initCommand } from './commands/init.js';
|
|
4
|
+
import { publishCommand } from './commands/publish.js';
|
|
4
5
|
import { rootCommand } from './commands/root.js';
|
|
5
6
|
import { serveCommand } from './commands/serve.js';
|
|
6
7
|
rootCommand.addCommand(authCommand);
|
|
7
8
|
rootCommand.addCommand(serveCommand);
|
|
8
9
|
rootCommand.addCommand(initCommand);
|
|
10
|
+
rootCommand.addCommand(publishCommand);
|
|
9
11
|
rootCommand.parse(process.argv);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin';
|
|
2
|
+
export const MathToolsPlugin = async (ctx) => {
|
|
3
|
+
return {
|
|
4
|
+
tool: {
|
|
5
|
+
multiply: tool({
|
|
6
|
+
description: 'Multiply two numbers',
|
|
7
|
+
args: {
|
|
8
|
+
a: tool.schema.number().describe('First number'),
|
|
9
|
+
b: tool.schema.number().describe('Second number'),
|
|
10
|
+
},
|
|
11
|
+
async execute(args, context) {
|
|
12
|
+
const result = args.a * args.b;
|
|
13
|
+
return result.toString();
|
|
14
|
+
},
|
|
15
|
+
}),
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type RequestOptions = {
|
|
2
|
+
body?: unknown;
|
|
3
|
+
headers?: Record<string, string>;
|
|
4
|
+
query?: Record<string, string | number | boolean>;
|
|
5
|
+
};
|
|
6
|
+
export declare class ApiClient {
|
|
7
|
+
private baseUrl;
|
|
8
|
+
private token;
|
|
9
|
+
constructor(baseUrl: string, token: string);
|
|
10
|
+
static create(): Promise<ApiClient>;
|
|
11
|
+
get(path: string, opts?: RequestOptions): Promise<Response>;
|
|
12
|
+
post(path: string, opts?: RequestOptions): Promise<Response>;
|
|
13
|
+
put(path: string, opts?: RequestOptions): Promise<Response>;
|
|
14
|
+
patch(path: string, opts?: RequestOptions): Promise<Response>;
|
|
15
|
+
delete(path: string, opts?: RequestOptions): Promise<Response>;
|
|
16
|
+
private request;
|
|
17
|
+
private buildUrl;
|
|
18
|
+
}
|