@opentiny/tiny-robot-cli 0.5.2-alpha.15 → 0.5.2-alpha.16

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/README.md CHANGED
@@ -13,11 +13,12 @@ npx @opentiny/tiny-robot-cli add chat --yes
13
13
  ## Options
14
14
 
15
15
  - `-t, --template <name>`: template name; `basic` generates the Chat Basic project
16
+ - `--runtime-version <version>`: override the TinyRobot runtime version for local CLI development or diagnosis
16
17
  - `-h, --help`: show help
17
18
 
18
19
  `create` is an overall project scaffold. The `basic` template is aligned with `packages/chat-basic` and is copied into a new project.
19
20
 
20
- `add chat` is a local feature injection for an existing Vue project. It creates the isolated `src/tiny-robot-chat/` feature from `packages/cli/templates/chat`, adds the runtime dependencies (including `@vueuse/core`), and imports the feature CSS. It does not modify the host project's Vite configuration. Existing files with different contents are reported as conflicts and are never silently overwritten.
21
+ `add chat` is a local feature injection for an existing Vue project. It creates the isolated `src/tiny-robot-chat/` feature from `packages/cli/templates/chat`, adds the runtime dependencies (including `@vueuse/core@13.9.0`), and imports feature-scoped CSS that does not reset the host document. It does not modify the host project's Vite configuration. Existing files with different contents are reported as conflicts and are never silently overwritten.
21
22
 
22
23
  By default, `add chat` mounts `TinyRobotChat` into a standard `src/App.vue` when a safe template and script setup block are available, so the generated project includes the floating AI trigger. Use `--no-mount` to keep `App.vue` unchanged and print the mount snippet. Use `--dry-run` to inspect the plan without changing files and `--yes` to skip prompts:
23
24
 
@@ -35,6 +36,16 @@ The feature adds or preserves these dependencies. Compatible versions are kept,
35
36
  - `@opentiny/tiny-robot-svgs`
36
37
  - `@vueuse/core`
37
38
 
39
+ Both `create` and `add chat` derive TinyRobot runtime dependency versions from the CLI package version. Prerelease CLI versions use the same exact runtime version, while stable CLI versions use a caret range:
40
+
41
+ ```text
42
+ CLI 0.5.2-alpha.15 -> runtime 0.5.2-alpha.15
43
+ CLI 0.5.2-beta.3 -> runtime 0.5.2-beta.3
44
+ CLI 0.5.3 -> runtime ^0.5.3
45
+ ```
46
+
47
+ An existing dependency range is preserved when it accepts the target runtime version; for example, `^0.5.1` is kept for a stable `0.5.3` target. Use `--runtime-version` only when testing an unpublished CLI checkout or diagnosing version resolution. The override follows the same prerelease-exact and stable-caret rules.
48
+
38
49
  Copy the generated `.env.example` to `.env.local`, then configure the provider API URL and API key before starting the project. `add chat` does not create or modify `.env`.
39
50
 
40
51
  The Model Context MCP example uses `/modelcontextprotocol-mcp`. Add this proxy manually to the existing `vite.config.*` file under `server.proxy`, then restart Vite:
@@ -6,6 +6,7 @@ import path from 'node:path'
6
6
  import process from 'node:process'
7
7
  import semver from 'semver'
8
8
 
9
+ import { createRuntimeDependencies, resolveRuntimeVersion } from '../runtime-version.js'
9
10
  import {
10
11
  findProjectRoot,
11
12
  findSubPackageRoot,
@@ -19,15 +20,7 @@ import {
19
20
  mergeEnvContent,
20
21
  } from '../utils.js'
21
22
 
22
- const TARGET_VERSION = '0.5.2-alpha.15'
23
23
  const CHAT_ADD_FEATURE_DIR = 'src/tiny-robot-chat'
24
- const DEPENDENCIES = {
25
- '@opentiny/tiny-robot': TARGET_VERSION,
26
- '@opentiny/tiny-robot-chat': TARGET_VERSION,
27
- '@opentiny/tiny-robot-kit': TARGET_VERSION,
28
- '@opentiny/tiny-robot-svgs': TARGET_VERSION,
29
- '@vueuse/core': '13.1.0',
30
- }
31
24
  const PACKAGE_STYLE_IMPORTS = [
32
25
  "import '@opentiny/tiny-robot/dist/style.css'",
33
26
  "import '@opentiny/tiny-robot-chat/dist/style.css'",
@@ -189,7 +182,11 @@ function insertDependencyOrdered(dependencies, name, version) {
189
182
  return next
190
183
  }
191
184
 
192
- function ensureDependency(pkg, name, targetVersion) {
185
+ function ensureDependency(pkg, name, targetSpecifier) {
186
+ const targetMinimum = semver.minVersion(targetSpecifier)
187
+ invariant(targetMinimum, `${name} has an invalid target version specifier (${targetSpecifier})`)
188
+ const targetVersion = targetMinimum.version
189
+
193
190
  for (const section of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {
194
191
  const value = pkg[section]
195
192
  if (value !== undefined && (value === null || typeof value !== 'object' || Array.isArray(value))) {
@@ -210,8 +207,8 @@ function ensureDependency(pkg, name, targetVersion) {
210
207
  return { type: 'conflict', reason: 'dependencies must be a JSON object' }
211
208
  }
212
209
  pkg.dependencies ??= {}
213
- pkg.dependencies = insertDependencyOrdered(pkg.dependencies, name, targetVersion)
214
- return { type: 'added', to: targetVersion, section: 'dependencies' }
210
+ pkg.dependencies = insertDependencyOrdered(pkg.dependencies, name, targetSpecifier)
211
+ return { type: 'added', to: targetSpecifier, section: 'dependencies' }
215
212
  }
216
213
 
217
214
  const dependency = existing[0]
@@ -226,12 +223,13 @@ function ensureDependency(pkg, name, targetVersion) {
226
223
  }
227
224
  }
228
225
 
229
- const satisfies = semver.satisfies(targetVersion, dependency.version, { includePrerelease: true })
226
+ const satisfies = semver.satisfies(targetVersion, dependency.version)
230
227
 
231
228
  if (satisfies) return { type: 'skipped', section: dependency.section, version: dependency.version }
232
229
 
233
230
  const minimum = typeof dependency.version === 'string' ? semver.minVersion(dependency.version) : null
234
231
  if (
232
+ semver.prerelease(targetVersion) === null &&
235
233
  typeof dependency.version === 'string' &&
236
234
  ((semver.valid(dependency.version) && semver.gte(dependency.version, targetVersion)) ||
237
235
  (minimum && semver.gte(minimum, targetVersion)))
@@ -242,16 +240,16 @@ function ensureDependency(pkg, name, targetVersion) {
242
240
  if (dependency.section !== 'dependencies') {
243
241
  return {
244
242
  type: 'conflict',
245
- reason: `${name}@${dependency.version} in ${dependency.section} does not satisfy ${targetVersion}`,
243
+ reason: `${name}@${dependency.version} in ${dependency.section} does not satisfy ${targetSpecifier}`,
246
244
  }
247
245
  }
248
246
 
249
- pkg.dependencies[name] = targetVersion
247
+ pkg.dependencies[name] = targetSpecifier
250
248
  return {
251
249
  type: 'updated',
252
250
  section: dependency.section,
253
251
  from: dependency.version,
254
- to: targetVersion,
252
+ to: targetSpecifier,
255
253
  }
256
254
  }
257
255
 
@@ -412,10 +410,10 @@ function isSelected(selectedFiles, label) {
412
410
  return selectedFiles.includes(label)
413
411
  }
414
412
 
415
- function getDependencyPlan(pkg) {
413
+ function getDependencyPlan(pkg, dependencies) {
416
414
  const preview = JSON.parse(JSON.stringify(pkg))
417
415
 
418
- return Object.entries(DEPENDENCIES).map(([name, version]) => ({
416
+ return Object.entries(dependencies).map(([name, version]) => ({
419
417
  name,
420
418
  result: ensureDependency(preview, name, version),
421
419
  }))
@@ -493,7 +491,7 @@ function applyChanges(changes) {
493
491
  }
494
492
  }
495
493
 
496
- function validateSelection(selectedFiles, pkg, featureInspection, mountPlan, allowManualMount) {
494
+ function validateSelection(selectedFiles, pkg, featureInspection, mountPlan, allowManualMount, dependencies) {
497
495
  const featureFilesSelected = isSelected(selectedFiles, 'Chat feature files')
498
496
  const featureFilesMissing = featureInspection.some((file) => file.type === 'create')
499
497
 
@@ -511,7 +509,7 @@ function validateSelection(selectedFiles, pkg, featureInspection, mountPlan, all
511
509
 
512
510
  if (isSelected(selectedFiles, 'package.json')) return
513
511
 
514
- const dependencyChanges = getDependencyPlan(pkg).filter(({ result }) => result.type !== 'skipped')
512
+ const dependencyChanges = getDependencyPlan(pkg, dependencies).filter(({ result }) => result.type !== 'skipped')
515
513
  if (dependencyChanges.length > 0 && (featureFilesSelected || isSelected(selectedFiles, 'main entry style imports'))) {
516
514
  throw new Error(
517
515
  'Select package.json when adding the chat feature or its style imports so required dependencies can be checked.',
@@ -520,7 +518,7 @@ function validateSelection(selectedFiles, pkg, featureInspection, mountPlan, all
520
518
  }
521
519
 
522
520
  function prepareChanges(targetDir, selectedFiles, context) {
523
- const { featureInspection, mainFile, mountPlan, pkgPath, pkg, allowConflicts } = context
521
+ const { featureInspection, mainFile, mountPlan, pkgPath, pkg, allowConflicts, dependencies } = context
524
522
  const changes = []
525
523
  const results = { featureInspection, style: null, env: null, dependencies: [], mount: null, dependencyChanged: false }
526
524
 
@@ -564,7 +562,7 @@ function prepareChanges(targetDir, selectedFiles, context) {
564
562
  }
565
563
 
566
564
  if (isSelected(selectedFiles, 'package.json')) {
567
- for (const [name, version] of Object.entries(DEPENDENCIES)) {
565
+ for (const [name, version] of Object.entries(dependencies)) {
568
566
  const result = ensureDependency(pkg, name, version)
569
567
  if (result.type === 'conflict' && !allowConflicts) throw new Error(`${name}: ${result.reason}`)
570
568
  results.dependencies.push({ name, result })
@@ -637,7 +635,7 @@ async function addFeature(targetDir, type, options) {
637
635
  nonInteractive: options.nonInteractive,
638
636
  })
639
637
 
640
- validateSelection(selectedFiles, pkg, featureInspection, mountPlan, options.dryRun)
638
+ validateSelection(selectedFiles, pkg, featureInspection, mountPlan, options.dryRun, options.dependencies)
641
639
  const prepared = prepareChanges(targetDir, selectedFiles, {
642
640
  featureInspection,
643
641
  mainFile,
@@ -645,6 +643,7 @@ async function addFeature(targetDir, type, options) {
645
643
  pkgPath,
646
644
  pkg,
647
645
  allowConflicts: options.dryRun,
646
+ dependencies: options.dependencies,
648
647
  })
649
648
 
650
649
  if (options.dryRun) {
@@ -660,7 +659,9 @@ async function addFeature(targetDir, type, options) {
660
659
  ` ${formatPlanStatus(prepared.results.env?.type ?? 'unavailable')} .env.example (${prepared.results.env?.type ?? 'not selected'})`,
661
660
  )
662
661
  const dependencyPlan =
663
- prepared.results.dependencies.length > 0 ? prepared.results.dependencies : getDependencyPlan(pkg)
662
+ prepared.results.dependencies.length > 0
663
+ ? prepared.results.dependencies
664
+ : getDependencyPlan(pkg, options.dependencies)
664
665
  for (const { name, result } of dependencyPlan) {
665
666
  const status = result.type === 'added' || result.type === 'updated' ? '~' : result.type === 'conflict' ? '!' : '○'
666
667
  console.log(` ${status} package.json (${result.type}: ${name})`)
@@ -733,11 +734,17 @@ export function registerAddCommand(program) {
733
734
  .option('--dry-run', 'print the change plan without modifying files')
734
735
  .option('--mount', 'safely mount TinyRobotChat in src/App.vue (default)')
735
736
  .option('--no-mount', 'keep App.vue unchanged and print the mount snippet')
737
+ .option('--runtime-version <version>', 'override the TinyRobot runtime version')
736
738
  .action(async (type, options) => {
737
739
  try {
740
+ const runtime = resolveRuntimeVersion(options.runtimeVersion)
738
741
  const nonInteractive = Boolean(options.yes || options.dryRun || !process.stdout.isTTY)
739
742
  const targetDir = await resolveTargetPackage(process.cwd(), nonInteractive)
740
- await addFeature(targetDir, type, { ...options, nonInteractive })
743
+ await addFeature(targetDir, type, {
744
+ ...options,
745
+ dependencies: createRuntimeDependencies(runtime.specifier),
746
+ nonInteractive,
747
+ })
741
748
  } catch (error) {
742
749
  if (error instanceof Error && error.name === 'ExitPromptError') {
743
750
  console.error('\nOperation cancelled.')
@@ -749,4 +756,4 @@ export function registerAddCommand(program) {
749
756
  })
750
757
  }
751
758
 
752
- export { DEPENDENCIES, ensureDependency, ensureStyleImports, getChatFeatureFiles, planMount, resolveTargetPackage }
759
+ export { ensureDependency, ensureStyleImports, getChatFeatureFiles, planMount, resolveTargetPackage }
@@ -2,6 +2,7 @@ import { input, select } from '@inquirer/prompts'
2
2
  import path from 'node:path'
3
3
  import process from 'node:process'
4
4
 
5
+ import { resolveRuntimeVersion } from '../runtime-version.js'
5
6
  import {
6
7
  DEFAULT_PROJECT_NAME,
7
8
  DEFAULT_TEMPLATE,
@@ -119,12 +120,13 @@ function printCreateSuccess(projectName) {
119
120
  console.log()
120
121
  }
121
122
 
122
- async function createProject(initialProjectName, initialTemplateName, skipPrompt) {
123
+ async function createProject(initialProjectName, initialTemplateName, runtimeVersion, skipPrompt) {
124
+ const runtime = resolveRuntimeVersion(runtimeVersion)
123
125
  const options = await resolveCreateOptions(initialProjectName, initialTemplateName, skipPrompt)
124
126
 
125
127
  const { templateDir, targetDir } = validateCreateOptions(options)
126
128
 
127
- scaffoldProject(templateDir, targetDir, options.projectName)
129
+ scaffoldProject(templateDir, targetDir, options.projectName, runtime.specifier)
128
130
 
129
131
  printCreateSuccess(options.projectName)
130
132
  }
@@ -134,10 +136,11 @@ export function registerCreateCommand(program) {
134
136
  .command('create [project-name]')
135
137
  .description('Create a TinyRobot project from template')
136
138
  .option('-t, --template <name>', 'template name')
139
+ .option('--runtime-version <version>', 'override the TinyRobot runtime version')
137
140
  .action((projectName, options) => {
138
141
  const skipPrompt = !process.stdout.isTTY
139
142
 
140
- createProject(projectName ?? '', options.template ?? '', skipPrompt).catch((error) => {
143
+ createProject(projectName ?? '', options.template ?? '', options.runtimeVersion, skipPrompt).catch((error) => {
141
144
  if (error instanceof Error && error.name === 'ExitPromptError') {
142
145
  console.error('\nOperation cancelled.')
143
146
  process.exit(1)
@@ -0,0 +1,37 @@
1
+ import fs from 'node:fs'
2
+ import { fileURLToPath } from 'node:url'
3
+ import semver from 'semver'
4
+
5
+ const CLI_PACKAGE_FILE = fileURLToPath(new URL('../package.json', import.meta.url))
6
+
7
+ export function readCliVersion() {
8
+ const packageJson = JSON.parse(fs.readFileSync(CLI_PACKAGE_FILE, 'utf8'))
9
+ return packageJson.version
10
+ }
11
+
12
+ export function formatRuntimeSpecifier(version) {
13
+ const parsedVersion = typeof version === 'string' ? semver.parse(version) : null
14
+ const canonicalVersion = parsedVersion
15
+ ? `${parsedVersion.version}${parsedVersion.build.length > 0 ? `+${parsedVersion.build.join('.')}` : ''}`
16
+ : null
17
+ if (!parsedVersion || canonicalVersion !== version) {
18
+ throw new Error(`Runtime version must be a valid semantic version: ${String(version)}`)
19
+ }
20
+
21
+ return parsedVersion.prerelease.length > 0 ? version : `^${version}`
22
+ }
23
+
24
+ export function resolveRuntimeVersion(override) {
25
+ const version = override ?? readCliVersion()
26
+ return { version, specifier: formatRuntimeSpecifier(version) }
27
+ }
28
+
29
+ export function createRuntimeDependencies(specifier) {
30
+ return {
31
+ '@opentiny/tiny-robot': specifier,
32
+ '@opentiny/tiny-robot-chat': specifier,
33
+ '@opentiny/tiny-robot-kit': specifier,
34
+ '@opentiny/tiny-robot-svgs': specifier,
35
+ '@vueuse/core': '13.9.0',
36
+ }
37
+ }
package/bin/utils.js CHANGED
@@ -5,6 +5,7 @@ import pc from 'picocolors'
5
5
  import yaml from 'yaml'
6
6
 
7
7
  const TEMPLATE_PLACEHOLDER = '__PROJECT_NAME__'
8
+ const RUNTIME_VERSION_PLACEHOLDER = '__TINY_ROBOT_VERSION__'
8
9
 
9
10
  export const BUILTIN_TEMPLATES = ['basic']
10
11
  export const DEFAULT_TEMPLATE = 'basic'
@@ -130,13 +131,14 @@ function replaceTemplateVariables(targetDir, variables) {
130
131
  }
131
132
  }
132
133
 
133
- export function scaffoldProject(templateDir, targetDir, projectName) {
134
+ export function scaffoldProject(templateDir, targetDir, projectName, runtimeVersion) {
134
135
  copyTemplate(templateDir, targetDir)
135
136
 
136
137
  renameSpecialFiles(targetDir)
137
138
 
138
139
  replaceTemplateVariables(targetDir, {
139
140
  [TEMPLATE_PLACEHOLDER]: projectName,
141
+ [RUNTIME_VERSION_PLACEHOLDER]: runtimeVersion,
140
142
  })
141
143
  }
142
144
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentiny/tiny-robot-cli",
3
- "version": "0.5.2-alpha.15",
3
+ "version": "0.5.2-alpha.16",
4
4
  "description": "CLI to scaffold TinyRobot product projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,5 +34,5 @@
34
34
  "semver": "^7.8.1",
35
35
  "yaml": "^2.9.0"
36
36
  },
37
- "gitHead": "e6908488f65b8615cb9f40eacc8e216405a66d2c"
37
+ "gitHead": "1bad8116c3bf83194a44bcde3dbd970cb8aacdd2"
38
38
  }
@@ -9,9 +9,9 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
- "@opentiny/tiny-robot-chat": "0.5.2-alpha.15",
13
- "@opentiny/tiny-robot": "0.5.2-alpha.15",
14
- "@opentiny/tiny-robot-svgs": "0.5.2-alpha.15",
12
+ "@opentiny/tiny-robot-chat": "__TINY_ROBOT_VERSION__",
13
+ "@opentiny/tiny-robot": "__TINY_ROBOT_VERSION__",
14
+ "@opentiny/tiny-robot-svgs": "__TINY_ROBOT_VERSION__",
15
15
  "vue": "^3.5.40"
16
16
  },
17
17
  "devDependencies": {
@@ -35,7 +35,7 @@ DeepSeek 模型默认继承 `low`、`high`、`max` 三档思考强度,默认
35
35
  - `src/config/chat-runtime.ts`: MCP 示例和模型配置。
36
36
  - `src/components`: 窗口头部和输入区工具。
37
37
  - `src/composables/useWindow.ts`: floating、fullscreen 窗口状态。
38
- - `src/index.css`: 模板公共样式和 Surface 样式变量。
38
+ - `src/index.css`: 仅作用于 Chat 容器的组件样式和 Surface 样式变量,不重置宿主页面的全局样式。
39
39
 
40
40
  ## 替换数据
41
41
 
@@ -10,11 +10,11 @@
10
10
  "type-check": "vue-tsc -b"
11
11
  },
12
12
  "dependencies": {
13
- "@opentiny/tiny-robot": "0.5.2-alpha.15",
14
- "@opentiny/tiny-robot-chat": "0.5.2-alpha.15",
15
- "@opentiny/tiny-robot-kit": "0.5.2-alpha.15",
16
- "@opentiny/tiny-robot-svgs": "0.5.2-alpha.15",
17
- "@vueuse/core": "13.1.0",
13
+ "@opentiny/tiny-robot": "__TINY_ROBOT_VERSION__",
14
+ "@opentiny/tiny-robot-chat": "__TINY_ROBOT_VERSION__",
15
+ "@opentiny/tiny-robot-kit": "__TINY_ROBOT_VERSION__",
16
+ "@opentiny/tiny-robot-svgs": "__TINY_ROBOT_VERSION__",
17
+ "@vueuse/core": "13.9.0",
18
18
  "vue": "^3.5.40"
19
19
  },
20
20
  "devDependencies": {
@@ -1,4 +1,5 @@
1
- :root {
1
+ .chat-add-app,
2
+ .chat-add-window {
2
3
  font-family:
3
4
  Inter,
4
5
  -apple-system,
@@ -7,20 +8,12 @@
7
8
  sans-serif;
8
9
  color: #1f2329;
9
10
  }
10
- * {
11
+ .chat-add-app,
12
+ .chat-add-app *,
13
+ .chat-add-window,
14
+ .chat-add-window * {
11
15
  box-sizing: border-box;
12
16
  }
13
- html,
14
- body,
15
- #app {
16
- width: 100%;
17
- height: 100%;
18
- min-height: 0;
19
- margin: 0;
20
- }
21
- body {
22
- overflow: hidden;
23
- }
24
17
  .chat-add-app {
25
18
  position: relative;
26
19
  container-type: inline-size;