typeclaw 0.11.1 → 0.12.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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # TypeClaw
2
2
 
3
3
  <p align="center">
4
- <img src="./docs/public/typeey.png" alt="Typeey, the TypeClaw mascot — a plush bird with navy wings sitting in grass" width="240" />
4
+ <img src="./docs/public/typeclaw.png" alt="TypeClaw logo" width="240" />
5
5
  </p>
6
6
 
7
7
  > A TypeScript-native, Bun-powered, Docker-friendly general-purpose agent runtime.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typeclaw",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "homepage": "https://github.com/typeclaw/typeclaw#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/typeclaw/typeclaw/issues"
@@ -23,6 +23,7 @@ export const BUILTIN_COMMAND_NAMES = [
23
23
  'model',
24
24
  'doctor',
25
25
  'usage',
26
+ 'update',
26
27
  '_hostd',
27
28
  ] as const
28
29
 
package/src/cli/index.ts CHANGED
@@ -33,6 +33,7 @@ const main = defineCommand({
33
33
  model: () => import('./model').then((m) => m.modelCommand),
34
34
  doctor: () => import('./doctor').then((m) => m.doctorCommand),
35
35
  usage: () => import('./usage').then((m) => m.usageCommand),
36
+ update: () => import('./update').then((m) => m.updateCommand),
36
37
  _hostd: () => import('./hostd').then((m) => m.hostdCommand),
37
38
  },
38
39
  })
@@ -0,0 +1,82 @@
1
+ import { defineCommand } from 'citty'
2
+
3
+ import { formatCommand, planSelfUpdate, type UpdateManagerSelection } from '@/update'
4
+
5
+ import { c, errorLine, successLine } from './ui'
6
+
7
+ const MANAGERS = ['auto', 'bun', 'npm', 'pnpm', 'yarn'] as const
8
+
9
+ export const updateCommand = defineCommand({
10
+ meta: {
11
+ name: 'update',
12
+ description: 'update the globally installed typeclaw CLI',
13
+ },
14
+ args: {
15
+ manager: {
16
+ type: 'string',
17
+ description: 'package manager to use: auto, bun, npm, pnpm, or yarn',
18
+ default: 'auto',
19
+ },
20
+ 'dry-run': {
21
+ type: 'boolean',
22
+ description: 'print the update command without running it',
23
+ default: false,
24
+ },
25
+ },
26
+ async run({ args }) {
27
+ const manager = parseManager(args.manager)
28
+ if (manager === null) {
29
+ console.error(errorLine(`Invalid --manager=${args.manager}. Expected auto, bun, npm, pnpm, or yarn.`))
30
+ process.exit(2)
31
+ }
32
+
33
+ const plan = planSelfUpdate({ manager })
34
+ if (!plan.ok) {
35
+ console.error(errorLine(plan.reason))
36
+ process.exit(1)
37
+ }
38
+
39
+ const rendered = formatCommand(plan.command)
40
+ if (args['dry-run']) {
41
+ process.stdout.write(`${rendered}\n`)
42
+ return
43
+ }
44
+
45
+ process.stdout.write(`${c.cyan('Updating TypeClaw with:')} ${rendered}\n`)
46
+ const exitCode = await runUpdateCommand(plan.command)
47
+ if (exitCode !== 0) {
48
+ console.error(errorLine(`Update command exited with code ${exitCode}.`))
49
+ process.exit(exitCode)
50
+ }
51
+ process.stdout.write(`${successLine('TypeClaw update command completed.')}\n`)
52
+ process.stdout.write(`${c.dim('Restart running agent containers to pick up the new CLI runtime.')}\n`)
53
+ },
54
+ })
55
+
56
+ function parseManager(value: string | undefined): UpdateManagerSelection | null {
57
+ if (value === undefined) return 'auto'
58
+ return (MANAGERS as readonly string[]).includes(value) ? (value as UpdateManagerSelection) : null
59
+ }
60
+
61
+ async function runUpdateCommand(command: string[]): Promise<number> {
62
+ const bun = (globalThis as { Bun?: { spawn: typeof Bun.spawn } }).Bun
63
+ if (!bun) {
64
+ console.error(errorLine('bun runtime not available'))
65
+ return 1
66
+ }
67
+ try {
68
+ const proc = bun.spawn({
69
+ cmd: command,
70
+ stdin: 'inherit',
71
+ stdout: 'inherit',
72
+ stderr: 'inherit',
73
+ })
74
+ return await proc.exited
75
+ } catch (error) {
76
+ if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {
77
+ console.error(errorLine(`${command[0]} not found in $PATH.`))
78
+ return 127
79
+ }
80
+ throw error
81
+ }
82
+ }
@@ -0,0 +1,86 @@
1
+ import { join } from 'node:path'
2
+
3
+ export type UpdateManager = 'bun' | 'npm' | 'pnpm' | 'yarn'
4
+ export type UpdateManagerSelection = 'auto' | UpdateManager
5
+
6
+ export type SelfUpdatePlan =
7
+ | { ok: true; manager: UpdateManager; command: string[]; detectedFrom: string }
8
+ | { ok: false; reason: string }
9
+
10
+ export function resolveSelfPackageJsonPath(): string {
11
+ return join(import.meta.dir, '..', '..', 'package.json')
12
+ }
13
+
14
+ export function planSelfUpdate(options: { manager: UpdateManagerSelection; packageJsonPath?: string }): SelfUpdatePlan {
15
+ const packageJsonPath = options.packageJsonPath ?? resolveSelfPackageJsonPath()
16
+ const manager = options.manager === 'auto' ? detectInstallManager(packageJsonPath) : options.manager
17
+ if (manager === null) {
18
+ return {
19
+ ok: false,
20
+ reason:
21
+ 'Cannot auto-detect how TypeClaw was installed from this checkout. Re-run with --manager=bun, --manager=npm, --manager=pnpm, or --manager=yarn if you want to update a global install.',
22
+ }
23
+ }
24
+ return {
25
+ ok: true,
26
+ manager,
27
+ command: commandForManager(manager),
28
+ detectedFrom: packageJsonPath,
29
+ }
30
+ }
31
+
32
+ export function commandForManager(manager: UpdateManager): string[] {
33
+ switch (manager) {
34
+ case 'bun':
35
+ return ['bun', 'update', '-g', 'typeclaw', '--latest']
36
+ case 'npm':
37
+ return ['npm', 'install', '-g', 'typeclaw@latest']
38
+ case 'pnpm':
39
+ return ['pnpm', 'add', '-g', 'typeclaw@latest']
40
+ case 'yarn':
41
+ return ['yarn', 'global', 'upgrade', 'typeclaw', '--latest']
42
+ }
43
+ }
44
+
45
+ export function formatCommand(command: readonly string[]): string {
46
+ return command.map(shellQuote).join(' ')
47
+ }
48
+
49
+ function detectInstallManager(packageJsonPath: string): UpdateManager | null {
50
+ const parts = packageJsonPath.split(/[\\/]+/).filter(Boolean)
51
+ const packageJson = parts[parts.length - 1]
52
+ const packageName = parts[parts.length - 2]
53
+ const nodeModulesIdx = parts.lastIndexOf('node_modules')
54
+ if (packageJson !== 'package.json' || packageName !== 'typeclaw' || nodeModulesIdx === -1) return null
55
+
56
+ // Bun global: .bun/install/global/node_modules/typeclaw
57
+ const bunGlobalIdx = parts.lastIndexOf('.bun')
58
+ if (
59
+ bunGlobalIdx !== -1 &&
60
+ parts[bunGlobalIdx + 1] === 'install' &&
61
+ parts[bunGlobalIdx + 2] === 'global' &&
62
+ parts[bunGlobalIdx + 3] === 'node_modules'
63
+ ) {
64
+ return 'bun'
65
+ }
66
+
67
+ // pnpm shards globals under a numeric major-version segment, e.g.
68
+ // ~/Library/pnpm/global/5/node_modules or legacy ~/.pnpm-global/5/node_modules.
69
+ if (nodeModulesIdx >= 2 && /^\d+$/.test(parts[nodeModulesIdx - 1] ?? '')) {
70
+ const anchor = parts[nodeModulesIdx - 2]
71
+ if (anchor === 'pnpm-global' || anchor === '.pnpm-global') return 'pnpm'
72
+ if (anchor === 'global' && parts[nodeModulesIdx - 3] === 'pnpm') return 'pnpm'
73
+ }
74
+
75
+ if (nodeModulesIdx >= 2 && parts[nodeModulesIdx - 1] === 'global' && parts[nodeModulesIdx - 2] === 'yarn') {
76
+ return 'yarn'
77
+ }
78
+
79
+ if (parts[nodeModulesIdx - 1] === 'lib') return 'npm'
80
+ return null
81
+ }
82
+
83
+ function shellQuote(arg: string): string {
84
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(arg)) return arg
85
+ return `'${arg.replaceAll("'", "'\\''")}'`
86
+ }