hostctl 0.1.48 → 0.1.49

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
@@ -138,6 +138,29 @@ hostctl pkg remove hostctl-hello
138
138
  Installed packages live under `~/.hostctl/packages` and can be run offline once cached.
139
139
  Use `pkg install` for npm registry names (scoped + versioned) or git URLs. Local directories should be run directly without installing (`hostctl run ./path/to/pkg task args`); this avoids polluting the manifest with workstation paths and keeps the install story aligned with reproducible npm/git sources.
140
140
 
141
+ Add `--json` to any `hostctl pkg` command for machine-readable output:
142
+
143
+ ```bash
144
+ hostctl pkg list --json
145
+ ```
146
+
147
+ ```json
148
+ {
149
+ "count": 1,
150
+ "duplicates": [],
151
+ "packages": [
152
+ {
153
+ "name": "hostctl-hello",
154
+ "version": "1.2.3",
155
+ "description": "Example tasks",
156
+ "directory": "hostctl-hello",
157
+ "source": "https://github.com/monopod/hostctl-example",
158
+ "path": "/home/you/.hostctl/packages/hostctl-hello"
159
+ }
160
+ ]
161
+ }
162
+ ```
163
+
141
164
  ## Designing tasks
142
165
 
143
166
  Define tasks with the `task` helper and a typed `TaskContext`:
@@ -3975,7 +3975,7 @@ var ParamMap = class _ParamMap {
3975
3975
  import * as z from "zod";
3976
3976
 
3977
3977
  // src/version.ts
3978
- var version = "0.1.48";
3978
+ var version = "0.1.49";
3979
3979
 
3980
3980
  // src/app.ts
3981
3981
  import { retryUntilDefined } from "ts-retry";
@@ -4298,7 +4298,9 @@ var PackageManager = class {
4298
4298
  this.manifest = { packages: [], version: "1.0" };
4299
4299
  }
4300
4300
  } catch (error) {
4301
- console.warn("Failed to load manifest, creating new one:", error);
4301
+ if (this.app.outputPlain()) {
4302
+ console.warn("Failed to load manifest, creating new one:", error);
4303
+ }
4302
4304
  this.manifest = { packages: [], version: "1.0" };
4303
4305
  }
4304
4306
  }
@@ -4457,8 +4459,8 @@ var PackageManager = class {
4457
4459
  const packageToRemove = this.findPackageToRemove(packageIdentifier);
4458
4460
  if (!packageToRemove) {
4459
4461
  return {
4460
- success: false,
4461
- error: `Package '${packageIdentifier}' not found. Use 'hostctl pkg list' to see installed packages.`
4462
+ success: true,
4463
+ warning: `Package '${packageIdentifier}' not found. Use 'hostctl pkg list' to see installed packages.`
4462
4464
  };
4463
4465
  }
4464
4466
  await this.removePackageFiles(packageToRemove);
@@ -4490,7 +4492,9 @@ var PackageManager = class {
4490
4492
  prefix: packagesDir.toString()
4491
4493
  });
4492
4494
  } catch (error) {
4493
- console.log(`DEBUG: Error during npm uninstall: ${error}`);
4495
+ if (this.app.outputPlain()) {
4496
+ console.log(`DEBUG: Error during npm uninstall: ${error}`);
4497
+ }
4494
4498
  }
4495
4499
  }
4496
4500
  /**
@@ -4549,7 +4553,9 @@ var PackageManager = class {
4549
4553
  const installDir = packagesDir.join(filenamifiedSource);
4550
4554
  const existingPackage = this.findPackageBySource(normalizedSource);
4551
4555
  if (existingPackage) {
4552
- console.log(`Package '${existingPackage.name}' is already installed from ${normalizedSource}`);
4556
+ if (this.app.outputPlain()) {
4557
+ console.log(`Package '${existingPackage.name}' is already installed from ${normalizedSource}`);
4558
+ }
4553
4559
  return {
4554
4560
  success: true,
4555
4561
  packageInfo: existingPackage,
@@ -4653,7 +4659,9 @@ var PackageManager = class {
4653
4659
  async findRealInstalledNpmPackagePath(packagesDir, source) {
4654
4660
  const nodeModulesPath = packagesDir.join("node_modules");
4655
4661
  if (!await nodeModulesPath.exists()) {
4656
- console.log(`DEBUG: node_modules does not exist at ${nodeModulesPath.toString()}`);
4662
+ if (this.app.outputPlain()) {
4663
+ console.log(`DEBUG: node_modules does not exist at ${nodeModulesPath.toString()}`);
4664
+ }
4657
4665
  return { path: null, name: null };
4658
4666
  }
4659
4667
  const entries = await fs6.readdir(nodeModulesPath.toString());
@@ -4694,6 +4702,9 @@ var PackageManager = class {
4694
4702
  async handleDuplicateNames(finalPackageInfo) {
4695
4703
  const actualPackageName = finalPackageInfo.name;
4696
4704
  if (this.hasPackageWithName(actualPackageName)) {
4705
+ if (!this.app.outputPlain()) {
4706
+ return;
4707
+ }
4697
4708
  const existingPackages = this.getPackagesWithName(actualPackageName);
4698
4709
  console.warn(`\u26A0\uFE0F Warning: Package name '${actualPackageName}' already exists.`);
4699
4710
  console.warn(` Existing packages:`);
@@ -6306,7 +6317,7 @@ dist/
6306
6317
  .DS_Store
6307
6318
  .env
6308
6319
  `;
6309
- async function createPackage(packageName, options) {
6320
+ async function createPackage(packageName, options, output) {
6310
6321
  const resolvedName = expandTildePath(packageName);
6311
6322
  const packageDir = path5.isAbsolute(resolvedName) ? resolvedName : path5.join(process.cwd(), resolvedName);
6312
6323
  const packageSlug = path5.basename(resolvedName);
@@ -6329,115 +6340,134 @@ async function createPackage(packageName, options) {
6329
6340
  await fs9.writeFile(path5.join(packageDir, "README.md"), readmeTemplate(packageJsonName, registryPrefix, false));
6330
6341
  await fs9.writeFile(path5.join(packageDir, ".gitignore"), gitignoreTemplate);
6331
6342
  }
6332
- console.log(`Created new hostctl package '${resolvedName}' at ${packageDir}`);
6333
- console.log(`
6334
- Next steps:`);
6335
- console.log(`1. cd ${packageDir}`);
6336
- console.log(`2. npm install`);
6337
- let step = 3;
6343
+ const nextSteps = [];
6344
+ nextSteps.push(`cd ${packageDir}`);
6345
+ nextSteps.push("npm install");
6338
6346
  if (options.lang === "typescript") {
6339
- console.log(`${step}. Edit src/index.ts and src/tasks/hello.ts to implement your tasks`);
6340
- step += 1;
6341
- console.log(`${step}. npm run build`);
6342
- step += 1;
6347
+ nextSteps.push("Edit src/index.ts and src/tasks/hello.ts to implement your tasks");
6348
+ nextSteps.push("npm run build");
6343
6349
  } else {
6344
- console.log(`${step}. Edit src/index.js and src/tasks/hello.js to implement your tasks`);
6345
- step += 1;
6350
+ nextSteps.push("Edit src/index.js and src/tasks/hello.js to implement your tasks");
6346
6351
  }
6347
- console.log(`${step}. Test your package with: hostctl tasks .`);
6348
- step += 1;
6349
- console.log(`${step}. Run it with: hostctl run . ${registryPrefix}.hello`);
6350
- console.log(`
6352
+ nextSteps.push("Test your package with: hostctl tasks .");
6353
+ nextSteps.push(`Run it with: hostctl run . ${registryPrefix}.hello`);
6354
+ if (!output?.quiet) {
6355
+ console.log(`Created new hostctl package '${resolvedName}' at ${packageDir}`);
6356
+ console.log(`
6357
+ Next steps:`);
6358
+ nextSteps.forEach((step, index) => {
6359
+ console.log(`${index + 1}. ${step}`);
6360
+ });
6361
+ console.log(`
6351
6362
  Note: The package includes 'hostctl' as a dependency for local development.`);
6352
- console.log(`For production, you may want to add it as a peer dependency instead.`);
6363
+ console.log(`For production, you may want to add it as a peer dependency instead.`);
6364
+ }
6365
+ return {
6366
+ packageDir,
6367
+ packageName: packageJsonName,
6368
+ registryPrefix,
6369
+ language: options.lang,
6370
+ nextSteps
6371
+ };
6353
6372
  }
6354
6373
 
6355
6374
  // src/commands/pkg/install.ts
6356
- async function installPackage(source, app) {
6375
+ async function installPackage(source, app, output) {
6357
6376
  const packageManager = new PackageManager(app);
6358
6377
  const result = await packageManager.installPackage(source);
6359
- if (result.success) {
6360
- console.log(`Installed package '${result.packageInfo.name}' from ${source} to ${result.installPath}`);
6361
- } else {
6362
- console.error(`Failed to install package from ${source}: ${result.error}`);
6363
- throw new Error(result.error);
6378
+ if (!output?.quiet) {
6379
+ if (result.success) {
6380
+ console.log(`Installed package '${result.packageInfo.name}' from ${source} to ${result.installPath}`);
6381
+ } else {
6382
+ console.error(`Failed to install package from ${source}: ${result.error}`);
6383
+ }
6364
6384
  }
6385
+ return { ...result, source };
6365
6386
  }
6366
6387
 
6367
6388
  // src/commands/pkg/list.ts
6368
6389
  import chalk5 from "chalk";
6369
6390
  async function listPackages(app) {
6370
6391
  const packageManager = new PackageManager(app);
6371
- try {
6372
- const packages = await packageManager.listPackages();
6373
- if (packages.length === 0) {
6374
- console.log("No packages installed.");
6375
- return;
6392
+ const packages = await packageManager.listPackages();
6393
+ const packagesByName = /* @__PURE__ */ new Map();
6394
+ packages.forEach((pkg) => {
6395
+ if (!packagesByName.has(pkg.name)) {
6396
+ packagesByName.set(pkg.name, []);
6376
6397
  }
6377
- const packagesByName = /* @__PURE__ */ new Map();
6378
- packages.forEach((pkg) => {
6379
- if (!packagesByName.has(pkg.name)) {
6380
- packagesByName.set(pkg.name, []);
6381
- }
6382
- packagesByName.get(pkg.name).push(pkg);
6383
- });
6384
- console.log(`Found ${packages.length} package(s):
6398
+ packagesByName.get(pkg.name).push(pkg);
6399
+ });
6400
+ const duplicateNames = Array.from(packagesByName.entries()).filter(([_, pkgs]) => pkgs.length > 1).map(([name]) => name);
6401
+ return { packages, duplicateNames };
6402
+ }
6403
+ function printPackageList(packages, duplicateNames) {
6404
+ if (packages.length === 0) {
6405
+ console.log("No packages installed.");
6406
+ return;
6407
+ }
6408
+ const packagesByName = /* @__PURE__ */ new Map();
6409
+ packages.forEach((pkg) => {
6410
+ if (!packagesByName.has(pkg.name)) {
6411
+ packagesByName.set(pkg.name, []);
6412
+ }
6413
+ packagesByName.get(pkg.name).push(pkg);
6414
+ });
6415
+ console.log(`Found ${packages.length} package(s):
6385
6416
  `);
6386
- packages.forEach((pkg) => {
6387
- const packagesWithSameName = packagesByName.get(pkg.name);
6388
- const hasDuplicates = packagesWithSameName.length > 1;
6389
- let output = `\u2022 ${pkg.name}`;
6390
- if (pkg.version) {
6391
- output += ` (v${pkg.version})`;
6392
- }
6393
- if (pkg.description) {
6394
- output += ` - ${pkg.description}`;
6395
- }
6396
- if (hasDuplicates) {
6397
- output = chalk5.yellow(output);
6398
- output += chalk5.yellow(" \u26A0\uFE0F (duplicate name)");
6399
- }
6400
- if (pkg.source) {
6401
- if (pkg.source.startsWith("http") || pkg.source.startsWith("git@")) {
6402
- output += `
6417
+ packages.forEach((pkg) => {
6418
+ const packagesWithSameName = packagesByName.get(pkg.name);
6419
+ const hasDuplicates = packagesWithSameName.length > 1;
6420
+ let output = `\u2022 ${pkg.name}`;
6421
+ if (pkg.version) {
6422
+ output += ` (v${pkg.version})`;
6423
+ }
6424
+ if (pkg.description) {
6425
+ output += ` - ${pkg.description}`;
6426
+ }
6427
+ if (hasDuplicates) {
6428
+ output = chalk5.yellow(output);
6429
+ output += chalk5.yellow(" \u26A0\uFE0F (duplicate name)");
6430
+ }
6431
+ if (pkg.source) {
6432
+ if (pkg.source.startsWith("http") || pkg.source.startsWith("git@")) {
6433
+ output += `
6403
6434
  \u{1F4E6} Source: ${pkg.source}`;
6404
- } else if (!pkg.source.startsWith(".") && !pkg.source.startsWith("/")) {
6405
- output += `
6435
+ } else if (!pkg.source.startsWith(".") && !pkg.source.startsWith("/")) {
6436
+ output += `
6406
6437
  \u{1F4E6} Source: https://www.npmjs.com/package/${pkg.source}`;
6407
- } else {
6408
- output += `
6438
+ } else {
6439
+ output += `
6409
6440
  \u{1F4E6} Source: ${pkg.source}`;
6410
- }
6411
6441
  }
6412
- console.log(output);
6413
- console.log(` \u2514\u2500 (run "hostctl tasks ${pkg.name}" to list tasks)`);
6414
- console.log("");
6415
- });
6416
- const duplicateNames = Array.from(packagesByName.entries()).filter(([_, pkgs]) => pkgs.length > 1).map(([name, _]) => name);
6417
- if (duplicateNames.length > 0) {
6418
- console.log(chalk5.yellow("\u26A0\uFE0F Warning: The following package names have duplicates:"));
6419
- duplicateNames.forEach((name) => {
6420
- console.log(chalk5.yellow(` - ${name}`));
6421
- });
6422
- console.log(chalk5.yellow(" Use the full source URL to run tasks from these packages."));
6423
- console.log("");
6424
6442
  }
6425
- } catch (error) {
6426
- console.error("Error listing packages:", error);
6427
- throw error;
6443
+ console.log(output);
6444
+ console.log(` \u2514\u2500 (run "hostctl tasks ${pkg.name}" to list tasks)`);
6445
+ console.log("");
6446
+ });
6447
+ if (duplicateNames.length > 0) {
6448
+ console.log(chalk5.yellow("\u26A0\uFE0F Warning: The following package names have duplicates:"));
6449
+ duplicateNames.forEach((name) => {
6450
+ console.log(chalk5.yellow(` - ${name}`));
6451
+ });
6452
+ console.log(chalk5.yellow(" Use the full source URL to run tasks from these packages."));
6453
+ console.log("");
6428
6454
  }
6429
6455
  }
6430
6456
 
6431
6457
  // src/commands/pkg/remove.ts
6432
- async function removePackage(packageIdentifier, app) {
6458
+ async function removePackage(packageIdentifier, app, output) {
6433
6459
  const packageManager = new PackageManager(app);
6434
6460
  const result = await packageManager.removePackage(packageIdentifier);
6435
- if (result.success && result.packageInfo) {
6436
- console.log(`Successfully removed package '${result.packageInfo.name}' (${result.packageInfo.directory})`);
6437
- } else {
6438
- console.error(`Failed to remove package: ${result.error}`);
6439
- return;
6461
+ if (!output?.quiet) {
6462
+ if (result.success && result.packageInfo) {
6463
+ console.log(`Successfully removed package '${result.packageInfo.name}' (${result.packageInfo.directory})`);
6464
+ } else if (result.success && result.warning) {
6465
+ console.warn(result.warning);
6466
+ } else {
6467
+ console.error(`Failed to remove package: ${result.error}`);
6468
+ }
6440
6469
  }
6470
+ return result;
6441
6471
  }
6442
6472
 
6443
6473
  // src/cli.ts
@@ -6947,26 +6977,86 @@ var Cli = class {
6947
6977
  }
6948
6978
  }
6949
6979
  async handlePkgCreate(packageName, options) {
6980
+ const opts = this.program.opts();
6981
+ if (opts.json) {
6982
+ try {
6983
+ const result = await createPackage(packageName, options, { quiet: true });
6984
+ console.log(JSON.stringify({ success: true, ...result }, null, 2));
6985
+ } catch (error) {
6986
+ console.log(
6987
+ JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }, null, 2)
6988
+ );
6989
+ process4.exitCode = 1;
6990
+ }
6991
+ return;
6992
+ }
6950
6993
  await createPackage(packageName, options);
6951
6994
  }
6952
6995
  async handlePkgInstall(source) {
6953
- await this.loadApp(this.program.opts());
6954
- try {
6955
- await installPackage(source, this.app);
6956
- } catch (error) {
6957
- console.error(
6958
- `Failed to install package from ${source}: ${error instanceof Error ? error.message : String(error)}`
6996
+ const opts = this.program.opts();
6997
+ await this.loadApp(opts);
6998
+ const result = await installPackage(source, this.app, { quiet: Boolean(opts.json) });
6999
+ if (opts.json) {
7000
+ console.log(
7001
+ JSON.stringify(
7002
+ {
7003
+ success: result.success,
7004
+ source: result.source,
7005
+ package: result.packageInfo,
7006
+ installPath: result.installPath,
7007
+ error: result.error
7008
+ },
7009
+ null,
7010
+ 2
7011
+ )
6959
7012
  );
7013
+ }
7014
+ if (!result.success) {
6960
7015
  process4.exitCode = 1;
6961
7016
  }
6962
7017
  }
6963
7018
  async handlePkgList() {
6964
- await this.loadApp(this.program.opts());
6965
- await listPackages(this.app);
7019
+ const opts = this.program.opts();
7020
+ await this.loadApp(opts);
7021
+ const result = await listPackages(this.app);
7022
+ if (opts.json) {
7023
+ console.log(
7024
+ JSON.stringify(
7025
+ {
7026
+ count: result.packages.length,
7027
+ duplicates: result.duplicateNames,
7028
+ packages: result.packages
7029
+ },
7030
+ null,
7031
+ 2
7032
+ )
7033
+ );
7034
+ return;
7035
+ }
7036
+ printPackageList(result.packages, result.duplicateNames);
6966
7037
  }
6967
7038
  async handlePkgRemove(packageIdentifier) {
6968
- await this.loadApp(this.program.opts());
6969
- await removePackage(packageIdentifier, this.app);
7039
+ const opts = this.program.opts();
7040
+ await this.loadApp(opts);
7041
+ const result = await removePackage(packageIdentifier, this.app, { quiet: Boolean(opts.json) });
7042
+ if (opts.json) {
7043
+ console.log(
7044
+ JSON.stringify(
7045
+ {
7046
+ success: result.success,
7047
+ identifier: packageIdentifier,
7048
+ package: result.packageInfo,
7049
+ error: result.error,
7050
+ warning: result.warning
7051
+ },
7052
+ null,
7053
+ 2
7054
+ )
7055
+ );
7056
+ }
7057
+ if (!result.success) {
7058
+ process4.exitCode = 1;
7059
+ }
6970
7060
  }
6971
7061
  async handleTasksList(specParts, options, cmd) {
6972
7062
  const opts = cmd.optsWithGlobals();