commit a279032cf4344296ce2941d83aae46dbba83296c Author: Juergen Kunz Date: Tue May 5 12:03:46 2026 +0000 Add CLI package diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4336e0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +node_modules/ +dist/ +dist_*/ +dist_ts/ +coverage/ +.nyc_output/ +.nogit/ +.playwright-mcp/ +*.log +.DS_Store +.env +.env.* +!.env.example diff --git a/cli.js b/cli.js new file mode 100644 index 0000000..9201db4 --- /dev/null +++ b/cli.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +import './dist_ts/cli.js'; diff --git a/package.json b/package.json new file mode 100644 index 0000000..e5fa178 --- /dev/null +++ b/package.json @@ -0,0 +1,45 @@ +{ + "name": "@smarthome.exchange/cli", + "version": "0.1.0", + "private": false, + "description": "Command line interface for smarthome.exchange.", + "bin": { + "shx": "./cli.js" + }, + "exports": { + ".": "./dist_ts/index.js" + }, + "type": "module", + "author": "Task Venture Capital GmbH", + "license": "MIT", + "scripts": { + "test": "tstest test/ --verbose --logfile --timeout 60", + "build": "tsbuild tsfolders --allowimplicitany", + "buildDocs": "tsdoc" + }, + "dependencies": { + "@smarthome.exchange/api": "workspace:*", + "@smarthome.exchange/hub": "workspace:*", + "@smarthome.exchange/interfaces": "workspace:*", + "@smarthome.exchange/sdk": "workspace:*" + }, + "devDependencies": { + "@git.zone/tsbuild": "^4.4.0", + "@git.zone/tsdoc": "^2.0.3", + "@git.zone/tsrun": "^2.0.3", + "@git.zone/tstest": "^3.6.3", + "@types/node": "^25.6.0" + }, + "files": [ + "ts/**/*", + "dist/**/*", + "dist_*/**/*", + "dist_ts/**/*", + "cli.js", + "readme.md" + ], + "browserslist": [ + "last 1 chrome versions" + ], + "packageManager": "pnpm@10.28.2" +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..b85be55 --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +# @smarthome.exchange/cli + +The `shx` command line interface for initializing homes, agents, and automations. diff --git a/test/test.node.ts b/test/test.node.ts new file mode 100644 index 0000000..926e385 --- /dev/null +++ b/test/test.node.ts @@ -0,0 +1,8 @@ +import { expect, tap } from '@git.zone/tstest/tapbundle'; +import { normalizeFileStem } from '../ts/index.js'; + +tap.test('normalizes generated file names', async () => { + expect(normalizeFileStem('Plant Sitter!')).toEqual('plant-sitter'); +}); + +export default tap.start(); diff --git a/ts/cli.ts b/ts/cli.ts new file mode 100644 index 0000000..086f244 --- /dev/null +++ b/ts/cli.ts @@ -0,0 +1,91 @@ +import * as plugins from './plugins.js'; + +export const normalizeFileStem = (valueArg: string) => { + return valueArg + .trim() + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') || 'new-item'; +}; + +export const getHelpText = () => `smarthome.exchange CLI + +Usage: + shx help + shx init [name] + shx open + shx agent new + shx automation new +`; + +export const runCli = async (argvArg = process.argv.slice(2)) => { + const [command, subcommand, nameArg] = argvArg; + + if (!command || command === 'help' || command === '--help' || command === '-h') { + console.log(getHelpText()); + return; + } + + if (command === 'init') { + await initHome(subcommand || 'birch-lane'); + return; + } + + if (command === 'open') { + console.log('Open http://localhost:8080 after starting @smarthome.exchange/hub.'); + return; + } + + if (command === 'agent' && subcommand === 'new') { + await createAgent(nameArg || 'custom-agent'); + return; + } + + if (command === 'automation' && subcommand === 'new') { + await createAutomation(nameArg || 'new-automation'); + return; + } + + throw new Error(`Unknown command: ${argvArg.join(' ')}`); +}; + +export const initHome = async (nameArg: string) => { + const homeName = normalizeFileStem(nameArg); + await plugins.fs.mkdir('.shx/agents', { recursive: true }); + await plugins.fs.mkdir('.shx/automations', { recursive: true }); + await plugins.fs.mkdir('.shx/dashboards', { recursive: true }); + await plugins.fs.writeFile( + '.shx/config.json', + `${JSON.stringify({ name: homeName, createdAt: new Date().toISOString() }, null, 2)}\n` + ); + console.log(`Initialized .shx home: ${homeName}`); +}; + +export const createAgent = async (nameArg: string) => { + const fileStem = normalizeFileStem(nameArg); + const targetPath = plugins.path.join('.shx', 'agents', `${fileStem}.ts`); + await plugins.fs.mkdir(plugins.path.dirname(targetPath), { recursive: true }); + await plugins.fs.writeFile( + targetPath, + `import type { data } from '@smarthome.exchange/interfaces';\n\nexport const agent: data.IAgentDefinition = {\n id: '${fileStem}',\n name: '${nameArg}',\n role: 'Custom household agent',\n glyph: 'custom',\n color: '#60a5fa',\n mode: 'ask',\n model: 'local:small',\n scopes: ['*.read'],\n systemPrompt: 'Describe the agent behavior here.',\n enabled: true,\n};\n` + ); + console.log(`Created ${targetPath}`); +}; + +export const createAutomation = async (nameArg: string) => { + const fileStem = normalizeFileStem(nameArg); + const targetPath = plugins.path.join('.shx', 'automations', `${fileStem}.ts`); + await plugins.fs.mkdir(plugins.path.dirname(targetPath), { recursive: true }); + await plugins.fs.writeFile( + targetPath, + `import { on, devices } from '@smarthome.exchange/sdk';\n\non({ id: 'manual:${fileStem}', kind: 'manual', expression: '${fileStem}' }, async () => {\n await devices.read('climate.office');\n});\n` + ); + console.log(`Created ${targetPath}`); +}; + +if (process.argv[1]?.endsWith('/cli.js') || process.argv[1]?.endsWith('\\cli.js')) { + runCli().catch((errorArg) => { + console.error(errorArg.message); + process.exit(1); + }); +} diff --git a/ts/index.ts b/ts/index.ts new file mode 100644 index 0000000..c01c2dc --- /dev/null +++ b/ts/index.ts @@ -0,0 +1 @@ +export * from './cli.js'; diff --git a/ts/plugins.ts b/ts/plugins.ts new file mode 100644 index 0000000..47dea1f --- /dev/null +++ b/ts/plugins.ts @@ -0,0 +1,13 @@ +// Native scope +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; + +export { fs, path }; + +// Project scope +import * as shxApi from '@smarthome.exchange/api'; +import * as shxHub from '@smarthome.exchange/hub'; +import * as shxInterfaces from '@smarthome.exchange/interfaces'; +import * as shxSdk from '@smarthome.exchange/sdk'; + +export { shxApi, shxHub, shxInterfaces, shxSdk }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7862634 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "verbatimModuleSyntax": true, + "types": ["node"] + }, + "exclude": [ + "dist_*/**/*.d.ts" + ] +}