kirohub 1.0.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 ADDED
@@ -0,0 +1,36 @@
1
+ # KiroHub CLI
2
+
3
+ Install and discover Kiro resources from the command line.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx kirohub search <query>
9
+ npx kirohub add <owner/resource>
10
+ npx kirohub list
11
+ ```
12
+
13
+ ## Examples
14
+
15
+ ```bash
16
+ npx kirohub search steering
17
+ npx kirohub add owner/my-steering
18
+ npx kirohub add kirodotdev/power-builder
19
+ ```
20
+
21
+ ## Publish (Maintainers)
22
+
23
+ ```bash
24
+ cd packages/cli
25
+ npm install
26
+ npm run build
27
+ node dist/cli.js --help
28
+ npm login
29
+ npm publish --access public
30
+ ```
31
+
32
+ Test the published CLI:
33
+
34
+ ```bash
35
+ npx kirohub --help
36
+ ```
package/dist/cli.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { addResource } from './commands/add.js';
4
+ import { listResources } from './commands/list.js';
5
+ import { searchResources } from './commands/search.js';
6
+ const program = new Command();
7
+ program
8
+ .name('kirohub')
9
+ .description('CLI for installing Kiro resources from KiroHub')
10
+ .version('1.0.0');
11
+ program
12
+ .command('add <resource>')
13
+ .description('Install a Kiro resource (e.g., owner/resource-name)')
14
+ .option('--no-telemetry', 'Disable telemetry')
15
+ .action(addResource);
16
+ program
17
+ .command('list')
18
+ .description('List installed resources')
19
+ .action(listResources);
20
+ program
21
+ .command('search <query>')
22
+ .description('Search available resources')
23
+ .option('-t, --type <type>', 'Filter by type')
24
+ .action(searchResources);
25
+ program.parse();
@@ -0,0 +1,79 @@
1
+ import { writeFileSync, mkdirSync } from 'fs';
2
+ import { join, dirname, basename } from 'path';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import { trackResourceInstall, trackCommand } from '../utils/telemetry.js';
6
+ const KIROHUB_API = 'https://kirohub.dev/resource-map.json';
7
+ export async function addResource(resourceSlug, options) {
8
+ const spinner = ora('Fetching resource...').start();
9
+ try {
10
+ // Fetch resource map
11
+ const response = await fetch(KIROHUB_API);
12
+ if (!response.ok) {
13
+ throw new Error(`Failed to fetch resource map: ${response.status} ${response.statusText}`);
14
+ }
15
+ const resources = await response.json();
16
+ // Find resource by slug
17
+ const resource = resources.find(r => r.slug === resourceSlug);
18
+ if (!resource) {
19
+ spinner.fail(`Resource "${resourceSlug}" not found`);
20
+ console.log(chalk.gray('Available resources:'));
21
+ resources.slice(0, 5).forEach(r => {
22
+ console.log(chalk.gray(` ${r.slug} (${r.type})`));
23
+ });
24
+ return;
25
+ }
26
+ spinner.text = `Installing ${resource.name}...`;
27
+ // Fetch file content
28
+ const fileResponse = await fetch(resource.githubRawUrl);
29
+ if (!fileResponse.ok) {
30
+ throw new Error(`Failed to fetch resource file: ${fileResponse.status} ${fileResponse.statusText}`);
31
+ }
32
+ const content = await fileResponse.text();
33
+ // Determine installation path
34
+ const installPath = getInstallPath(resource);
35
+ // Create directory if needed
36
+ mkdirSync(dirname(installPath), { recursive: true });
37
+ // Write file
38
+ writeFileSync(installPath, content);
39
+ // Track telemetry
40
+ if (!options.noTelemetry) {
41
+ trackResourceInstall(resource.slug, resource.type);
42
+ trackCommand('add');
43
+ }
44
+ spinner.succeed(`Installed ${chalk.green(resource.name)} to ${chalk.gray(installPath)}`);
45
+ }
46
+ catch (error) {
47
+ spinner.fail(`Failed to install resource: ${error.message}`);
48
+ }
49
+ }
50
+ function getInstallPath(resource) {
51
+ const cwd = process.cwd();
52
+ const safeName = sanitizeName(resource.name || resource.slug);
53
+ const safeSlug = sanitizeName(resource.slug);
54
+ const fileNameFromRepo = basename(resource.githubFile || '');
55
+ switch (resource.type.toLowerCase()) {
56
+ case 'steering':
57
+ return join(cwd, '.kiro', 'steering', fileNameFromRepo || `${safeName}.md`);
58
+ case 'hook':
59
+ return join(cwd, '.kiro', 'hooks', fileNameFromRepo || `${safeName}.kiro.hook`);
60
+ case 'power':
61
+ return join(cwd, '.kiro', 'powers', safeSlug || safeName, fileNameFromRepo || 'POWER.md');
62
+ case 'prompt':
63
+ return join(cwd, '.kiro', 'prompts', fileNameFromRepo || `${safeName}.md`);
64
+ case 'agent':
65
+ return join(cwd, '.kiro', 'agents', fileNameFromRepo || `${safeName}.json`);
66
+ case 'skill':
67
+ return join(cwd, '.kiro', 'skills', safeSlug || safeName, fileNameFromRepo || 'SKILL.md');
68
+ default:
69
+ return join(cwd, '.kiro', 'resources', safeName);
70
+ }
71
+ }
72
+ function sanitizeName(value) {
73
+ return value
74
+ .toLowerCase()
75
+ .trim()
76
+ .replace(/[^a-z0-9-_./]+/g, "-")
77
+ .replace(/-+/g, "-")
78
+ .replace(/^\/+|\/+$/g, "");
79
+ }
@@ -0,0 +1,37 @@
1
+ import { readdirSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import chalk from 'chalk';
4
+ import { trackCommand } from '../utils/telemetry.js';
5
+ export async function listResources() {
6
+ trackCommand('list');
7
+ const cwd = process.cwd();
8
+ const kiroDir = join(cwd, '.kiro');
9
+ if (!existsSync(kiroDir)) {
10
+ console.log(chalk.yellow('No .kiro directory found. Install resources first.'));
11
+ return;
12
+ }
13
+ console.log(chalk.bold('Installed Kiro resources:'));
14
+ console.log();
15
+ const categories = [
16
+ { name: 'Steering', dir: 'steering', ext: '.md' },
17
+ { name: 'Hooks', dir: 'hooks', ext: '.kiro.hook' },
18
+ { name: 'Powers', dir: 'powers', ext: null },
19
+ { name: 'Prompts', dir: 'prompts', ext: '.md' },
20
+ { name: 'Agents', dir: 'agents', ext: '.json' },
21
+ { name: 'Skills', dir: 'skills', ext: null }
22
+ ];
23
+ categories.forEach(category => {
24
+ const categoryDir = join(kiroDir, category.dir);
25
+ if (!existsSync(categoryDir))
26
+ return;
27
+ const items = readdirSync(categoryDir);
28
+ if (items.length === 0)
29
+ return;
30
+ console.log(chalk.green(`${category.name}:`));
31
+ items.forEach(item => {
32
+ const name = category.ext ? item.replace(category.ext, '') : item;
33
+ console.log(chalk.gray(` ${name}`));
34
+ });
35
+ console.log();
36
+ });
37
+ }
@@ -0,0 +1,39 @@
1
+ import chalk from 'chalk';
2
+ import { trackCommand } from '../utils/telemetry.js';
3
+ const KIROHUB_API = 'https://kirohub.dev/resource-map.json';
4
+ export async function searchResources(query, options) {
5
+ try {
6
+ trackCommand('search');
7
+ const response = await fetch(KIROHUB_API);
8
+ if (!response.ok) {
9
+ throw new Error(`Failed to fetch resource map: ${response.status} ${response.statusText}`);
10
+ }
11
+ const resources = await response.json();
12
+ // Filter by type if specified
13
+ let filtered = resources;
14
+ if (options.type) {
15
+ filtered = resources.filter(r => r.type.toLowerCase() === options.type.toLowerCase());
16
+ }
17
+ // Search by name/slug
18
+ const results = filtered.filter(r => r.name.toLowerCase().includes(query.toLowerCase()) ||
19
+ r.slug.toLowerCase().includes(query.toLowerCase()));
20
+ if (results.length === 0) {
21
+ console.log(chalk.yellow(`No resources found for "${query}"`));
22
+ return;
23
+ }
24
+ console.log(chalk.bold(`Found ${results.length} resources:`));
25
+ console.log();
26
+ results.slice(0, 10).forEach(resource => {
27
+ console.log(chalk.green(`${resource.slug}`));
28
+ console.log(chalk.gray(` Type: ${resource.type}`));
29
+ console.log(chalk.gray(` Install: npx kirohub add ${resource.slug}`));
30
+ console.log();
31
+ });
32
+ if (results.length > 10) {
33
+ console.log(chalk.gray(`... and ${results.length - 10} more`));
34
+ }
35
+ }
36
+ catch (error) {
37
+ console.error(chalk.red(`Search failed: ${error.message}`));
38
+ }
39
+ }
@@ -0,0 +1,52 @@
1
+ // Telemetry endpoint — update this after deploying the Lambda Function URL
2
+ // IMPORTANT: Replace this URL with the actual Lambda Function URL before publishing to npm
3
+ const TELEMETRY_ENDPOINT = 'https://kirohub.dev/api/telemetry';
4
+ const TELEMETRY_TIMEOUT = 1500;
5
+ import { createRequire } from 'module';
6
+ const require = createRequire(import.meta.url);
7
+ const { version: CLI_VERSION } = require('../../package.json');
8
+ function isTelemetryDisabled() {
9
+ return (process.env.DISABLE_TELEMETRY === '1' ||
10
+ process.env.KIROHUB_DISABLE_TELEMETRY === '1' ||
11
+ process.env.NO_TELEMETRY === '1');
12
+ }
13
+ export async function sendTelemetry(payload) {
14
+ if (isTelemetryDisabled())
15
+ return;
16
+ if (typeof fetch !== 'function')
17
+ return;
18
+ const controller = new AbortController();
19
+ const timeout = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT);
20
+ try {
21
+ await fetch(TELEMETRY_ENDPOINT, {
22
+ method: 'POST',
23
+ headers: { 'Content-Type': 'application/json' },
24
+ body: JSON.stringify(payload),
25
+ signal: controller.signal,
26
+ keepalive: true,
27
+ });
28
+ }
29
+ catch {
30
+ // Ignore telemetry failures
31
+ }
32
+ finally {
33
+ clearTimeout(timeout);
34
+ }
35
+ }
36
+ export async function trackResourceInstall(resourceSlug, resourceType) {
37
+ await sendTelemetry({
38
+ eventType: 'resource_install',
39
+ resourceSlug,
40
+ resourceType,
41
+ cliVersion: CLI_VERSION,
42
+ source: 'cli',
43
+ });
44
+ }
45
+ export async function trackCommand(command) {
46
+ await sendTelemetry({
47
+ eventType: 'command_usage',
48
+ command,
49
+ cliVersion: CLI_VERSION,
50
+ source: 'cli',
51
+ });
52
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "kirohub",
3
+ "version": "1.0.0",
4
+ "description": "CLI for installing Kiro resources from KiroHub",
5
+ "type": "module",
6
+ "main": "dist/cli.js",
7
+ "bin": {
8
+ "kirohub": "dist/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.json",
12
+ "dev": "tsx src/cli.ts",
13
+ "smoke": "node dist/cli.js --help",
14
+ "postinstall": "node postinstall.cjs",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "keywords": ["kiro", "ai", "cli", "resources", "steering", "hooks", "powers"],
18
+ "author": "Kiro Community",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "commander": "^12.0.0",
22
+ "chalk": "^5.3.0",
23
+ "ora": "^8.0.1"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22.0.0",
27
+ "tsx": "^4.0.0",
28
+ "typescript": "^5.8.2"
29
+ },
30
+ "files": [
31
+ "dist/**/*",
32
+ "postinstall.cjs",
33
+ "README.md"
34
+ ],
35
+ "engines": {
36
+ "node": ">=18"
37
+ }
38
+ }
@@ -0,0 +1,50 @@
1
+ // TODO: Update with actual Lambda Function URL after deployment
2
+ // IMPORTANT: Replace this URL with the actual Lambda Function URL before publishing to npm
3
+ const TELEMETRY_ENDPOINT = "https://kirohub.dev/api/telemetry";
4
+
5
+ function isTelemetryDisabled() {
6
+ return (
7
+ process.env.DISABLE_TELEMETRY === "1" ||
8
+ process.env.KIROHUB_DISABLE_TELEMETRY === "1" ||
9
+ process.env.NO_TELEMETRY === "1"
10
+ );
11
+ }
12
+
13
+ function detectInstallSource(userAgent) {
14
+ const value = (userAgent || "").toLowerCase();
15
+ if (value.includes("npx")) return "npx";
16
+ if (value.includes("npm")) return "npm";
17
+ if (value.includes("yarn")) return "yarn";
18
+ if (value.includes("pnpm")) return "pnpm";
19
+ return "unknown";
20
+ }
21
+
22
+ async function sendInstallTelemetry() {
23
+ if (isTelemetryDisabled()) return;
24
+ if (typeof fetch !== "function") return;
25
+
26
+ const payload = {
27
+ eventType: "cli_install",
28
+ cliVersion: process.env.npm_package_version || "unknown",
29
+ source: detectInstallSource(process.env.npm_config_user_agent),
30
+ };
31
+
32
+ const controller = new AbortController();
33
+ const timeout = setTimeout(() => controller.abort(), 1500);
34
+
35
+ try {
36
+ await fetch(TELEMETRY_ENDPOINT, {
37
+ method: "POST",
38
+ headers: { "Content-Type": "application/json" },
39
+ body: JSON.stringify(payload),
40
+ signal: controller.signal,
41
+ keepalive: true,
42
+ });
43
+ } catch {
44
+ // Ignore telemetry failures
45
+ } finally {
46
+ clearTimeout(timeout);
47
+ }
48
+ }
49
+
50
+ sendInstallTelemetry();