hostctl 0.1.47 → 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.
@@ -3886,11 +3886,168 @@ var LocalRuntime = class {
3886
3886
  }
3887
3887
  };
3888
3888
 
3889
+ // src/zip.ts
3890
+ import AdmZip from "adm-zip";
3891
+
3892
+ // src/hash.ts
3893
+ import { createHash } from "crypto";
3894
+
3895
+ // src/param-map.ts
3896
+ import { match as match2 } from "ts-pattern";
3897
+ var ParamMap = class _ParamMap {
3898
+ /**
3899
+ * Parses CLI arguments of the form key:value, only supporting top-level keys.
3900
+ * Only the first colon is treated as a key/value separator; all subsequent colons are part of the value.
3901
+ * This means nested keys are not supported, but values can contain colons (e.g., URLs, JSON, etc.).
3902
+ */
3903
+ static parse(cliArguments) {
3904
+ const root = new _ParamMap();
3905
+ A2(cliArguments).each((arg) => {
3906
+ const idx = arg.indexOf(":");
3907
+ if (idx === -1) {
3908
+ root.set([arg], "true");
3909
+ return;
3910
+ }
3911
+ const key = arg.slice(0, idx);
3912
+ const value = arg.slice(idx + 1);
3913
+ root.set([key], value);
3914
+ });
3915
+ return root;
3916
+ }
3917
+ map;
3918
+ constructor() {
3919
+ this.map = /* @__PURE__ */ new Map();
3920
+ }
3921
+ get(keyPath) {
3922
+ const keys3 = keyPath.split(":");
3923
+ let node = this;
3924
+ for (let i = 0; i < keys3.length; i++) {
3925
+ if (!(node instanceof _ParamMap)) return void 0;
3926
+ node = node.map.get(keys3[i]);
3927
+ if (node === void 0) return void 0;
3928
+ }
3929
+ if (node instanceof _ParamMap) {
3930
+ return node.toObject();
3931
+ }
3932
+ return this.stringToJsonObj(node);
3933
+ }
3934
+ set(keyPath, value) {
3935
+ const keyParts = Array.isArray(keyPath) ? keyPath : keyPath.split(":");
3936
+ let node = this;
3937
+ for (let i = 0; i < keyParts.length - 1; i++) {
3938
+ const key = keyParts[i];
3939
+ let nextNode = node.map.get(key);
3940
+ if (!(nextNode instanceof _ParamMap)) {
3941
+ nextNode = new _ParamMap();
3942
+ node.map.set(key, nextNode);
3943
+ }
3944
+ node = nextNode;
3945
+ }
3946
+ const lastKey = keyParts[keyParts.length - 1];
3947
+ node.map.set(lastKey, value);
3948
+ }
3949
+ toObject() {
3950
+ let obj = {};
3951
+ M(this.map).each(([k, v]) => {
3952
+ obj[k] = v instanceof _ParamMap ? v.toObject() : this.stringToJsonObj(v);
3953
+ });
3954
+ return obj;
3955
+ }
3956
+ stringToJsonObj(str) {
3957
+ if (typeof str !== "string") return str;
3958
+ const trimmed = str.trim();
3959
+ if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
3960
+ try {
3961
+ return JSON.parse(trimmed);
3962
+ } catch {
3963
+ }
3964
+ }
3965
+ return match2(str).when(matches(/,/), (s) => {
3966
+ return VP(s.split(",")).map((v) => v.trim()).compact([""]).map((v) => this.stringToJsonObj(v)).value;
3967
+ }).when(isNumeric, (s) => parseFloat(s)).when(
3968
+ (s) => s.trim() === "",
3969
+ () => ""
3970
+ ).otherwise(() => str);
3971
+ }
3972
+ };
3973
+
3974
+ // src/runtime.ts
3975
+ import * as z from "zod";
3976
+
3977
+ // src/version.ts
3978
+ var version = "0.1.49";
3979
+
3980
+ // src/app.ts
3981
+ import { retryUntilDefined } from "ts-retry";
3982
+ import { Mutex as Mutex3 } from "async-mutex";
3983
+ import { match as match5 } from "ts-pattern";
3984
+
3985
+ // src/core/remote/runAllRemote.ts
3986
+ var RunParamsSchema = z.object({
3987
+ taskFn: z.custom((value) => typeof value === "function", {
3988
+ message: "taskFn must be a function"
3989
+ }),
3990
+ params: z.any()
3991
+ });
3992
+ var RunResultSchema = z.record(z.string(), z.any());
3993
+ function serializeError(value) {
3994
+ if (value instanceof Error) {
3995
+ const err = value;
3996
+ return {
3997
+ error: err.message,
3998
+ name: err.name,
3999
+ code: err.code,
4000
+ stack: err.stack
4001
+ };
4002
+ }
4003
+ if (value && typeof value === "object") {
4004
+ return { error: JSON.stringify(value) };
4005
+ }
4006
+ return { error: String(value) };
4007
+ }
4008
+ async function run(context) {
4009
+ const { params, ssh } = context;
4010
+ const { taskFn, params: taskParams } = params;
4011
+ const remoteResults = await ssh([], async (remoteContext) => {
4012
+ const hostIdentifier = remoteContext.host?.alias || remoteContext.host?.shortName || "unknown_remote_host";
4013
+ remoteContext.debug(`[${hostIdentifier}] Connected via SSH.`);
4014
+ try {
4015
+ const result = await remoteContext.run(taskFn(taskParams));
4016
+ remoteContext.debug(`[${hostIdentifier}] Remote task result:`, JSON.stringify(result));
4017
+ if (result instanceof Error) {
4018
+ remoteContext.warn?.(
4019
+ `[${hostIdentifier}] Remote task returned an Error instance. Converting to serializable object.`
4020
+ );
4021
+ return serializeError(result);
4022
+ }
4023
+ return result;
4024
+ } catch (e) {
4025
+ remoteContext.debug(`[${hostIdentifier}] Failed to run remote task:`, e.message);
4026
+ return serializeError(e);
4027
+ }
4028
+ });
4029
+ const normalizedEntries = Object.entries(remoteResults ?? {}).map(([hostAlias, value]) => {
4030
+ if (value instanceof Error) {
4031
+ return [hostAlias, serializeError(value)];
4032
+ }
4033
+ return [hostAlias, value];
4034
+ });
4035
+ return Object.fromEntries(normalizedEntries);
4036
+ }
4037
+ var runAllRemote_default = task(run, {
4038
+ description: "run a task on all selected hosts",
4039
+ inputSchema: RunParamsSchema,
4040
+ outputSchema: RunResultSchema
4041
+ });
4042
+
4043
+ // src/commands/pkg/package-manager.ts
4044
+ import { promises as fs6 } from "fs";
4045
+
3889
4046
  // src/node-runtime.ts
3890
4047
  import os3 from "os";
3891
4048
  import axios2 from "axios";
3892
4049
  import * as cheerio from "cheerio";
3893
- import { match as match3 } from "ts-pattern";
4050
+ import { match as match4 } from "ts-pattern";
3894
4051
  import which from "which";
3895
4052
 
3896
4053
  // src/http.ts
@@ -3935,14 +4092,14 @@ async function downloadFile(url, dest) {
3935
4092
  import decompress from "decompress";
3936
4093
  import decompressTarGzPlugin from "decompress-targz";
3937
4094
  import decompressZipPlugin from "decompress-unzip";
3938
- import { match as match2, P as P2 } from "ts-pattern";
4095
+ import { match as match3, P as P2 } from "ts-pattern";
3939
4096
  import decompressTarPlugin from "decompress-tar";
3940
4097
  import { fileTypeFromBuffer } from "file-type";
3941
4098
  import { getStreamAsBuffer } from "get-stream";
3942
4099
  import xzdecompress from "xz-decompress";
3943
4100
  async function unarchive(inputPath, outputPath) {
3944
4101
  const filename = File.basename(inputPath);
3945
- return await match2(filename).with(P2.string.regex(/.tar.xz$/), () => decompressTarXz(inputPath, outputPath)).with(P2.string.regex(/.tar.gz$/), () => decompressTarGz(inputPath, outputPath)).with(P2.string.regex(/.zip$/), () => decompressZip(inputPath, outputPath)).otherwise(() => {
4102
+ return await match3(filename).with(P2.string.regex(/.tar.xz$/), () => decompressTarXz(inputPath, outputPath)).with(P2.string.regex(/.tar.gz$/), () => decompressTarGz(inputPath, outputPath)).with(P2.string.regex(/.zip$/), () => decompressZip(inputPath, outputPath)).otherwise(() => {
3946
4103
  throw new Error(`unable to decompress unknown file type: ${inputPath}`);
3947
4104
  });
3948
4105
  }
@@ -4057,8 +4214,8 @@ var NodeRuntime = class _NodeRuntime {
4057
4214
  }
4058
4215
  // returns the path to the downloaded zip file
4059
4216
  async downloadNodePackage(platform2, arch) {
4060
- const platformInFilename = match3(platform2).with("linux", () => "linux").with("win32", () => "win").with("darwin", () => "darwin").otherwise(() => "unknown-platform");
4061
- const archInFilename = match3(arch).with("x64", () => "x64").with("x86", () => "x86").with("arm", () => "armv7l").with("arm64", () => "arm64").otherwise(() => "unknown-arch");
4217
+ const platformInFilename = match4(platform2).with("linux", () => "linux").with("win32", () => "win").with("darwin", () => "darwin").otherwise(() => "unknown-platform");
4218
+ const archInFilename = match4(arch).with("x64", () => "x64").with("x86", () => "x86").with("arm", () => "armv7l").with("arm64", () => "arm64").otherwise(() => "unknown-arch");
4062
4219
  const packages = await this.listPackages();
4063
4220
  const url = A2(packages).find((url2) => url2.match(`node-v.*-${platformInFilename}-${archInFilename}`));
4064
4221
  if (V.isAbsent(url)) {
@@ -4122,162 +4279,7 @@ var NodeRuntime = class _NodeRuntime {
4122
4279
  }
4123
4280
  };
4124
4281
 
4125
- // src/zip.ts
4126
- import AdmZip from "adm-zip";
4127
-
4128
- // src/hash.ts
4129
- import { createHash } from "crypto";
4130
-
4131
- // src/param-map.ts
4132
- import { match as match4 } from "ts-pattern";
4133
- var ParamMap = class _ParamMap {
4134
- /**
4135
- * Parses CLI arguments of the form key:value, only supporting top-level keys.
4136
- * Only the first colon is treated as a key/value separator; all subsequent colons are part of the value.
4137
- * This means nested keys are not supported, but values can contain colons (e.g., URLs, JSON, etc.).
4138
- */
4139
- static parse(cliArguments) {
4140
- const root = new _ParamMap();
4141
- A2(cliArguments).each((arg) => {
4142
- const idx = arg.indexOf(":");
4143
- if (idx === -1) {
4144
- root.set([arg], "true");
4145
- return;
4146
- }
4147
- const key = arg.slice(0, idx);
4148
- const value = arg.slice(idx + 1);
4149
- root.set([key], value);
4150
- });
4151
- return root;
4152
- }
4153
- map;
4154
- constructor() {
4155
- this.map = /* @__PURE__ */ new Map();
4156
- }
4157
- get(keyPath) {
4158
- const keys3 = keyPath.split(":");
4159
- let node = this;
4160
- for (let i = 0; i < keys3.length; i++) {
4161
- if (!(node instanceof _ParamMap)) return void 0;
4162
- node = node.map.get(keys3[i]);
4163
- if (node === void 0) return void 0;
4164
- }
4165
- if (node instanceof _ParamMap) {
4166
- return node.toObject();
4167
- }
4168
- return this.stringToJsonObj(node);
4169
- }
4170
- set(keyPath, value) {
4171
- const keyParts = Array.isArray(keyPath) ? keyPath : keyPath.split(":");
4172
- let node = this;
4173
- for (let i = 0; i < keyParts.length - 1; i++) {
4174
- const key = keyParts[i];
4175
- let nextNode = node.map.get(key);
4176
- if (!(nextNode instanceof _ParamMap)) {
4177
- nextNode = new _ParamMap();
4178
- node.map.set(key, nextNode);
4179
- }
4180
- node = nextNode;
4181
- }
4182
- const lastKey = keyParts[keyParts.length - 1];
4183
- node.map.set(lastKey, value);
4184
- }
4185
- toObject() {
4186
- let obj = {};
4187
- M(this.map).each(([k, v]) => {
4188
- obj[k] = v instanceof _ParamMap ? v.toObject() : this.stringToJsonObj(v);
4189
- });
4190
- return obj;
4191
- }
4192
- stringToJsonObj(str) {
4193
- if (typeof str !== "string") return str;
4194
- const trimmed = str.trim();
4195
- if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
4196
- try {
4197
- return JSON.parse(trimmed);
4198
- } catch {
4199
- }
4200
- }
4201
- return match4(str).when(matches(/,/), (s) => {
4202
- return VP(s.split(",")).map((v) => v.trim()).compact([""]).map((v) => this.stringToJsonObj(v)).value;
4203
- }).when(isNumeric, (s) => parseFloat(s)).when(
4204
- (s) => s.trim() === "",
4205
- () => ""
4206
- ).otherwise(() => str);
4207
- }
4208
- };
4209
-
4210
- // src/runtime.ts
4211
- import * as z from "zod";
4212
-
4213
- // src/version.ts
4214
- var version = "0.1.47";
4215
-
4216
- // src/app.ts
4217
- import { retryUntilDefined } from "ts-retry";
4218
- import { Mutex as Mutex3 } from "async-mutex";
4219
- import { match as match5 } from "ts-pattern";
4220
-
4221
- // src/core/remote/runAllRemote.ts
4222
- var RunParamsSchema = z.object({
4223
- taskFn: z.custom((value) => typeof value === "function", {
4224
- message: "taskFn must be a function"
4225
- }),
4226
- params: z.any()
4227
- });
4228
- var RunResultSchema = z.record(z.string(), z.any());
4229
- function serializeError(value) {
4230
- if (value instanceof Error) {
4231
- const err = value;
4232
- return {
4233
- error: err.message,
4234
- name: err.name,
4235
- code: err.code,
4236
- stack: err.stack
4237
- };
4238
- }
4239
- if (value && typeof value === "object") {
4240
- return { error: JSON.stringify(value) };
4241
- }
4242
- return { error: String(value) };
4243
- }
4244
- async function run(context) {
4245
- const { params, ssh } = context;
4246
- const { taskFn, params: taskParams } = params;
4247
- const remoteResults = await ssh([], async (remoteContext) => {
4248
- const hostIdentifier = remoteContext.host?.alias || remoteContext.host?.shortName || "unknown_remote_host";
4249
- remoteContext.debug(`[${hostIdentifier}] Connected via SSH.`);
4250
- try {
4251
- const result = await remoteContext.run(taskFn(taskParams));
4252
- remoteContext.debug(`[${hostIdentifier}] Remote task result:`, JSON.stringify(result));
4253
- if (result instanceof Error) {
4254
- remoteContext.warn?.(
4255
- `[${hostIdentifier}] Remote task returned an Error instance. Converting to serializable object.`
4256
- );
4257
- return serializeError(result);
4258
- }
4259
- return result;
4260
- } catch (e) {
4261
- remoteContext.debug(`[${hostIdentifier}] Failed to run remote task:`, e.message);
4262
- return serializeError(e);
4263
- }
4264
- });
4265
- const normalizedEntries = Object.entries(remoteResults ?? {}).map(([hostAlias, value]) => {
4266
- if (value instanceof Error) {
4267
- return [hostAlias, serializeError(value)];
4268
- }
4269
- return [hostAlias, value];
4270
- });
4271
- return Object.fromEntries(normalizedEntries);
4272
- }
4273
- var runAllRemote_default = task(run, {
4274
- description: "run a task on all selected hosts",
4275
- inputSchema: RunParamsSchema,
4276
- outputSchema: RunResultSchema
4277
- });
4278
-
4279
4282
  // src/commands/pkg/package-manager.ts
4280
- import { promises as fs6 } from "fs";
4281
4283
  import filenamify from "filenamify";
4282
4284
  var PackageManager = class {
4283
4285
  constructor(app) {
@@ -4296,7 +4298,9 @@ var PackageManager = class {
4296
4298
  this.manifest = { packages: [], version: "1.0" };
4297
4299
  }
4298
4300
  } catch (error) {
4299
- 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
+ }
4300
4304
  this.manifest = { packages: [], version: "1.0" };
4301
4305
  }
4302
4306
  }
@@ -4455,8 +4459,8 @@ var PackageManager = class {
4455
4459
  const packageToRemove = this.findPackageToRemove(packageIdentifier);
4456
4460
  if (!packageToRemove) {
4457
4461
  return {
4458
- success: false,
4459
- 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.`
4460
4464
  };
4461
4465
  }
4462
4466
  await this.removePackageFiles(packageToRemove);
@@ -4488,7 +4492,9 @@ var PackageManager = class {
4488
4492
  prefix: packagesDir.toString()
4489
4493
  });
4490
4494
  } catch (error) {
4491
- console.log(`DEBUG: Error during npm uninstall: ${error}`);
4495
+ if (this.app.outputPlain()) {
4496
+ console.log(`DEBUG: Error during npm uninstall: ${error}`);
4497
+ }
4492
4498
  }
4493
4499
  }
4494
4500
  /**
@@ -4547,7 +4553,9 @@ var PackageManager = class {
4547
4553
  const installDir = packagesDir.join(filenamifiedSource);
4548
4554
  const existingPackage = this.findPackageBySource(normalizedSource);
4549
4555
  if (existingPackage) {
4550
- 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
+ }
4551
4559
  return {
4552
4560
  success: true,
4553
4561
  packageInfo: existingPackage,
@@ -4651,7 +4659,9 @@ var PackageManager = class {
4651
4659
  async findRealInstalledNpmPackagePath(packagesDir, source) {
4652
4660
  const nodeModulesPath = packagesDir.join("node_modules");
4653
4661
  if (!await nodeModulesPath.exists()) {
4654
- 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
+ }
4655
4665
  return { path: null, name: null };
4656
4666
  }
4657
4667
  const entries = await fs6.readdir(nodeModulesPath.toString());
@@ -4692,6 +4702,9 @@ var PackageManager = class {
4692
4702
  async handleDuplicateNames(finalPackageInfo) {
4693
4703
  const actualPackageName = finalPackageInfo.name;
4694
4704
  if (this.hasPackageWithName(actualPackageName)) {
4705
+ if (!this.app.outputPlain()) {
4706
+ return;
4707
+ }
4695
4708
  const existingPackages = this.getPackagesWithName(actualPackageName);
4696
4709
  console.warn(`\u26A0\uFE0F Warning: Package name '${actualPackageName}' already exists.`);
4697
4710
  console.warn(` Existing packages:`);
@@ -6023,15 +6036,6 @@ ${successfullyUsedIdentityPaths}`);
6023
6036
  p = p.parent();
6024
6037
  }
6025
6038
  }
6026
- async printRuntimeReport() {
6027
- const nodeRuntime = new NodeRuntime(this.tmpDir);
6028
- const reportObj = {
6029
- nodePath: await nodeRuntime.nodePath(),
6030
- npmPath: await nodeRuntime.npmPath()
6031
- // summary: report?.getReport(),
6032
- };
6033
- this.info(reportObj);
6034
- }
6035
6039
  async promptPasswordInteractively(message = "Enter your password") {
6036
6040
  if (this.providedPassword !== void 0) {
6037
6041
  return this.providedPassword;
@@ -6050,11 +6054,6 @@ ${successfullyUsedIdentityPaths}`);
6050
6054
  this.providedPassword = await promptPassword({ message });
6051
6055
  return this.providedPassword;
6052
6056
  }
6053
- async installRuntime() {
6054
- this.info(`creating node runtime with ${this.tmpDir.toString()}`);
6055
- const nodeRuntime = new NodeRuntime(this.tmpDir);
6056
- await nodeRuntime.installIfNeeded();
6057
- }
6058
6057
  /**
6059
6058
  * Executes a command on the selected hosts
6060
6059
  *
@@ -6318,7 +6317,7 @@ dist/
6318
6317
  .DS_Store
6319
6318
  .env
6320
6319
  `;
6321
- async function createPackage(packageName, options) {
6320
+ async function createPackage(packageName, options, output) {
6322
6321
  const resolvedName = expandTildePath(packageName);
6323
6322
  const packageDir = path5.isAbsolute(resolvedName) ? resolvedName : path5.join(process.cwd(), resolvedName);
6324
6323
  const packageSlug = path5.basename(resolvedName);
@@ -6341,115 +6340,134 @@ async function createPackage(packageName, options) {
6341
6340
  await fs9.writeFile(path5.join(packageDir, "README.md"), readmeTemplate(packageJsonName, registryPrefix, false));
6342
6341
  await fs9.writeFile(path5.join(packageDir, ".gitignore"), gitignoreTemplate);
6343
6342
  }
6344
- console.log(`Created new hostctl package '${resolvedName}' at ${packageDir}`);
6345
- console.log(`
6346
- Next steps:`);
6347
- console.log(`1. cd ${packageDir}`);
6348
- console.log(`2. npm install`);
6349
- let step = 3;
6343
+ const nextSteps = [];
6344
+ nextSteps.push(`cd ${packageDir}`);
6345
+ nextSteps.push("npm install");
6350
6346
  if (options.lang === "typescript") {
6351
- console.log(`${step}. Edit src/index.ts and src/tasks/hello.ts to implement your tasks`);
6352
- step += 1;
6353
- console.log(`${step}. npm run build`);
6354
- step += 1;
6347
+ nextSteps.push("Edit src/index.ts and src/tasks/hello.ts to implement your tasks");
6348
+ nextSteps.push("npm run build");
6355
6349
  } else {
6356
- console.log(`${step}. Edit src/index.js and src/tasks/hello.js to implement your tasks`);
6357
- step += 1;
6350
+ nextSteps.push("Edit src/index.js and src/tasks/hello.js to implement your tasks");
6358
6351
  }
6359
- console.log(`${step}. Test your package with: hostctl tasks .`);
6360
- step += 1;
6361
- console.log(`${step}. Run it with: hostctl run . ${registryPrefix}.hello`);
6362
- 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(`
6363
6362
  Note: The package includes 'hostctl' as a dependency for local development.`);
6364
- 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
+ };
6365
6372
  }
6366
6373
 
6367
6374
  // src/commands/pkg/install.ts
6368
- async function installPackage(source, app) {
6375
+ async function installPackage(source, app, output) {
6369
6376
  const packageManager = new PackageManager(app);
6370
6377
  const result = await packageManager.installPackage(source);
6371
- if (result.success) {
6372
- console.log(`Installed package '${result.packageInfo.name}' from ${source} to ${result.installPath}`);
6373
- } else {
6374
- console.error(`Failed to install package from ${source}: ${result.error}`);
6375
- 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
+ }
6376
6384
  }
6385
+ return { ...result, source };
6377
6386
  }
6378
6387
 
6379
6388
  // src/commands/pkg/list.ts
6380
6389
  import chalk5 from "chalk";
6381
6390
  async function listPackages(app) {
6382
6391
  const packageManager = new PackageManager(app);
6383
- try {
6384
- const packages = await packageManager.listPackages();
6385
- if (packages.length === 0) {
6386
- console.log("No packages installed.");
6387
- 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, []);
6388
6397
  }
6389
- const packagesByName = /* @__PURE__ */ new Map();
6390
- packages.forEach((pkg) => {
6391
- if (!packagesByName.has(pkg.name)) {
6392
- packagesByName.set(pkg.name, []);
6393
- }
6394
- packagesByName.get(pkg.name).push(pkg);
6395
- });
6396
- 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):
6397
6416
  `);
6398
- packages.forEach((pkg) => {
6399
- const packagesWithSameName = packagesByName.get(pkg.name);
6400
- const hasDuplicates = packagesWithSameName.length > 1;
6401
- let output = `\u2022 ${pkg.name}`;
6402
- if (pkg.version) {
6403
- output += ` (v${pkg.version})`;
6404
- }
6405
- if (pkg.description) {
6406
- output += ` - ${pkg.description}`;
6407
- }
6408
- if (hasDuplicates) {
6409
- output = chalk5.yellow(output);
6410
- output += chalk5.yellow(" \u26A0\uFE0F (duplicate name)");
6411
- }
6412
- if (pkg.source) {
6413
- if (pkg.source.startsWith("http") || pkg.source.startsWith("git@")) {
6414
- 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 += `
6415
6434
  \u{1F4E6} Source: ${pkg.source}`;
6416
- } else if (!pkg.source.startsWith(".") && !pkg.source.startsWith("/")) {
6417
- output += `
6435
+ } else if (!pkg.source.startsWith(".") && !pkg.source.startsWith("/")) {
6436
+ output += `
6418
6437
  \u{1F4E6} Source: https://www.npmjs.com/package/${pkg.source}`;
6419
- } else {
6420
- output += `
6438
+ } else {
6439
+ output += `
6421
6440
  \u{1F4E6} Source: ${pkg.source}`;
6422
- }
6423
6441
  }
6424
- console.log(output);
6425
- console.log(` \u2514\u2500 (run "hostctl tasks ${pkg.name}" to list tasks)`);
6426
- console.log("");
6427
- });
6428
- const duplicateNames = Array.from(packagesByName.entries()).filter(([_, pkgs]) => pkgs.length > 1).map(([name, _]) => name);
6429
- if (duplicateNames.length > 0) {
6430
- console.log(chalk5.yellow("\u26A0\uFE0F Warning: The following package names have duplicates:"));
6431
- duplicateNames.forEach((name) => {
6432
- console.log(chalk5.yellow(` - ${name}`));
6433
- });
6434
- console.log(chalk5.yellow(" Use the full source URL to run tasks from these packages."));
6435
- console.log("");
6436
6442
  }
6437
- } catch (error) {
6438
- console.error("Error listing packages:", error);
6439
- 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("");
6440
6454
  }
6441
6455
  }
6442
6456
 
6443
6457
  // src/commands/pkg/remove.ts
6444
- async function removePackage(packageIdentifier, app) {
6458
+ async function removePackage(packageIdentifier, app, output) {
6445
6459
  const packageManager = new PackageManager(app);
6446
6460
  const result = await packageManager.removePackage(packageIdentifier);
6447
- if (result.success && result.packageInfo) {
6448
- console.log(`Successfully removed package '${result.packageInfo.name}' (${result.packageInfo.directory})`);
6449
- } else {
6450
- console.error(`Failed to remove package: ${result.error}`);
6451
- 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
+ }
6452
6469
  }
6470
+ return result;
6453
6471
  }
6454
6472
 
6455
6473
  // src/cli.ts
@@ -6779,8 +6797,6 @@ var Cli = class {
6779
6797
  '-p, --params "<json object>"',
6780
6798
  "runtime parameters supplied as a string to be parsed as a json object representing params to supply to the script"
6781
6799
  ).option("-r, --remote", "run the script on remote hosts specified by tags via SSH orchestration").action(this.handleRun.bind(this));
6782
- const runtimeCmd = this.program.command("runtime").alias("rt").description("print out a report of the runtime environment").action(this.handleRuntime.bind(this));
6783
- runtimeCmd.command("install").alias("i").description("install a temporary runtime environment on the local host if needed").action(this.handleRuntimeInstall.bind(this));
6784
6800
  this.program.command("tasks").description("list tasks available in a package or path").argument("[package-or-path...]", "npm package spec, installed package name, or local path").option("--task <name>", "filter to a specific task name").option("--json", "output should be json formatted").action(this.handleTasksList.bind(this));
6785
6801
  }
6786
6802
  async handleInventory(options, cmd) {
@@ -6960,37 +6976,87 @@ var Cli = class {
6960
6976
  logError("hostctl error:", e);
6961
6977
  }
6962
6978
  }
6963
- async handleRuntime(options, cmd) {
6964
- const opts = cmd.optsWithGlobals();
6965
- await this.loadApp(opts);
6966
- await this.app.printRuntimeReport();
6967
- }
6968
- async handleRuntimeInstall(options, cmd) {
6969
- const opts = cmd.optsWithGlobals();
6970
- await this.loadApp(opts);
6971
- await this.app.installRuntime();
6972
- }
6973
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
+ }
6974
6993
  await createPackage(packageName, options);
6975
6994
  }
6976
6995
  async handlePkgInstall(source) {
6977
- await this.loadApp(this.program.opts());
6978
- try {
6979
- await installPackage(source, this.app);
6980
- } catch (error) {
6981
- console.error(
6982
- `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
+ )
6983
7012
  );
7013
+ }
7014
+ if (!result.success) {
6984
7015
  process4.exitCode = 1;
6985
7016
  }
6986
7017
  }
6987
7018
  async handlePkgList() {
6988
- await this.loadApp(this.program.opts());
6989
- 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);
6990
7037
  }
6991
7038
  async handlePkgRemove(packageIdentifier) {
6992
- await this.loadApp(this.program.opts());
6993
- 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
+ }
6994
7060
  }
6995
7061
  async handleTasksList(specParts, options, cmd) {
6996
7062
  const opts = cmd.optsWithGlobals();