hostctl 0.1.45 → 0.1.48

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
@@ -93,12 +93,14 @@ npx hostctl run core.echo message:hello
93
93
  ```
94
94
  Local directories are executed directly and are not installable via `hostctl pkg install`.
95
95
  - **Remote orchestration**
96
+
96
97
  ```bash
97
98
  hostctl run -r -t ubuntu core.net.interfaces --json
98
99
  ```
99
100
 
100
101
  - `-r/--remote` targets hosts selected by tags via SSH.
101
102
  - `-t/--tag` is greedy; use `--` before positional args when needed.
103
+
102
104
  - **From npm or git**
103
105
  ```bash
104
106
  hostctl run hostctl-hello greet name:Phil
@@ -204,7 +206,6 @@ Key `TaskContext` capabilities (see [`docs/task-api.md`](docs/task-api.md) for d
204
206
  - **Version mismatch**: keep `package.json`, `src/version.ts`, and `jsr.json` in sync before releasing.
205
207
  - **SSH failures**: verify inventory entries (hostname, user, auth) and only pass `-r` when you intend remote execution.
206
208
  - **Secrets**: when inventories are encrypted, ensure `AGE_IDS` includes the private keys so `hostctl` can decrypt secrets.
207
- - **Environment drift**: run `hostctl runtime` to inspect prerequisites or `hostctl runtime install` to bootstrap them.
208
209
 
209
210
  ## Developer workflow
210
211
 
@@ -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.48";
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.45";
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) {
@@ -4628,15 +4630,24 @@ var PackageManager = class {
4628
4630
  }
4629
4631
  async ensurePackagesDirHasPackageJson(packagesDir) {
4630
4632
  const packageJsonPath = packagesDir.join("package.json");
4631
- if (!await packageJsonPath.exists()) {
4632
- const packageJson = {
4633
+ const hostctlOverride = process.env.HOSTCTL_PKG_HOSTCTL_OVERRIDE;
4634
+ const overrideValue = hostctlOverride ? hostctlOverride.startsWith("file:") ? hostctlOverride : `file:${Path.new(hostctlOverride).absolute().toString()}` : void 0;
4635
+ let packageJson;
4636
+ if (await packageJsonPath.exists()) {
4637
+ const raw = await fs6.readFile(packageJsonPath.toString(), "utf-8");
4638
+ packageJson = JSON.parse(raw);
4639
+ } else {
4640
+ packageJson = {
4633
4641
  name: "hostctl-packages",
4634
4642
  version: "1.0.0",
4635
4643
  description: "Hostctl package management directory",
4636
4644
  private: true
4637
4645
  };
4638
- await fs6.writeFile(packageJsonPath.toString(), JSON.stringify(packageJson, null, 2));
4639
4646
  }
4647
+ if (overrideValue) {
4648
+ packageJson.overrides = { ...packageJson.overrides ?? {}, hostctl: overrideValue };
4649
+ }
4650
+ await fs6.writeFile(packageJsonPath.toString(), JSON.stringify(packageJson, null, 2));
4640
4651
  }
4641
4652
  // Scan node_modules for the real installed package (by name or repo match)
4642
4653
  async findRealInstalledNpmPackagePath(packagesDir, source) {
@@ -6014,15 +6025,6 @@ ${successfullyUsedIdentityPaths}`);
6014
6025
  p = p.parent();
6015
6026
  }
6016
6027
  }
6017
- async printRuntimeReport() {
6018
- const nodeRuntime = new NodeRuntime(this.tmpDir);
6019
- const reportObj = {
6020
- nodePath: await nodeRuntime.nodePath(),
6021
- npmPath: await nodeRuntime.npmPath()
6022
- // summary: report?.getReport(),
6023
- };
6024
- this.info(reportObj);
6025
- }
6026
6028
  async promptPasswordInteractively(message = "Enter your password") {
6027
6029
  if (this.providedPassword !== void 0) {
6028
6030
  return this.providedPassword;
@@ -6041,11 +6043,6 @@ ${successfullyUsedIdentityPaths}`);
6041
6043
  this.providedPassword = await promptPassword({ message });
6042
6044
  return this.providedPassword;
6043
6045
  }
6044
- async installRuntime() {
6045
- this.info(`creating node runtime with ${this.tmpDir.toString()}`);
6046
- const nodeRuntime = new NodeRuntime(this.tmpDir);
6047
- await nodeRuntime.installIfNeeded();
6048
- }
6049
6046
  /**
6050
6047
  * Executes a command on the selected hosts
6051
6048
  *
@@ -6122,8 +6119,7 @@ var packageJsonTsTemplate = (packageName) => `{
6122
6119
  "author": "",
6123
6120
  "license": "MIT",
6124
6121
  "dependencies": {
6125
- "hostctl": "^0.1.42",
6126
- "zod": "^4.1.13"
6122
+ "hostctl": "^0.1.42"
6127
6123
  },
6128
6124
  "devDependencies": {
6129
6125
  "typescript": "^5.8.3",
@@ -6156,8 +6152,7 @@ var packageJsonJsTemplate = (packageName) => `{
6156
6152
  "author": "",
6157
6153
  "license": "MIT",
6158
6154
  "dependencies": {
6159
- "hostctl": "^0.1.42",
6160
- "zod": "^4.1.13"
6155
+ "hostctl": "^0.1.42"
6161
6156
  },
6162
6157
  "engines": {
6163
6158
  "node": ">=24"
@@ -6192,157 +6187,21 @@ function registryPrefixFromPackageName(packageName) {
6192
6187
  const normalized = withoutPrefix.replace(/[^a-zA-Z0-9]+/g, ".").replace(/^\.|\.$/g, "");
6193
6188
  return normalized || "example";
6194
6189
  }
6195
- var indexTsTemplate = (registryPrefix) => `import { createRegistry } from './registry.js';
6190
+ var indexTsTemplate = (registryPrefix) => `import { createRegistry } from 'hostctl';
6196
6191
  import hello from './tasks/hello.js';
6197
6192
 
6198
6193
  export { hello };
6199
6194
 
6200
6195
  export const registry = createRegistry().register('${registryPrefix}.hello', hello);
6201
6196
  `;
6202
- var indexJsTemplate = (registryPrefix) => `import { createRegistry } from './registry.js';
6197
+ var indexJsTemplate = (registryPrefix) => `import { createRegistry } from 'hostctl';
6203
6198
  import hello from './tasks/hello.js';
6204
6199
 
6205
6200
  export { hello };
6206
6201
 
6207
6202
  export const registry = createRegistry().register('${registryPrefix}.hello', hello);
6208
6203
  `;
6209
- var registryTsTemplate = `import type { TaskFn } from 'hostctl';
6210
-
6211
- export type TaskRegistry = {
6212
- register: (name: string, task: TaskFn) => TaskRegistry;
6213
- get: (name: string) => TaskFn | undefined;
6214
- tasks: () => Array<[string, TaskFn]>;
6215
- has: (name: string) => boolean;
6216
- names: () => string[];
6217
- size: () => number;
6218
- };
6219
-
6220
- function isTaskFnLike(candidate: unknown): candidate is TaskFn {
6221
- return (
6222
- typeof candidate === 'function' &&
6223
- !!candidate &&
6224
- 'task' in (candidate as Record<string, unknown>) &&
6225
- typeof (candidate as { task?: unknown }).task === 'object'
6226
- );
6227
- }
6228
-
6229
- export function createRegistry(): TaskRegistry {
6230
- const entries = new Map<string, TaskFn>();
6231
-
6232
- const registry: TaskRegistry = {
6233
- register(name: string, task: TaskFn) {
6234
- if (!name || typeof name !== 'string') {
6235
- throw new Error('Registry task name must be a non-empty string.');
6236
- }
6237
- if (!isTaskFnLike(task)) {
6238
- throw new Error(\`Registry task '\${name}' must be a TaskFn.\`);
6239
- }
6240
- if (entries.has(name)) {
6241
- throw new Error(\`Registry already has a task named '\${name}'.\`);
6242
- }
6243
- if (task.task) {
6244
- task.task.name = name;
6245
- }
6246
- entries.set(name, task);
6247
- return registry;
6248
- },
6249
- get(name: string) {
6250
- return entries.get(name);
6251
- },
6252
- tasks() {
6253
- return Array.from(entries.entries()).sort(([a], [b]) => a.localeCompare(b));
6254
- },
6255
- has(name: string) {
6256
- return entries.has(name);
6257
- },
6258
- names() {
6259
- return Array.from(entries.keys()).sort((a, b) => a.localeCompare(b));
6260
- },
6261
- size() {
6262
- return entries.size;
6263
- },
6264
- };
6265
-
6266
- return registry;
6267
- }
6268
- `;
6269
- var registryJsTemplate = `export function createRegistry() {
6270
- const entries = new Map();
6271
-
6272
- const registry = {
6273
- register(name, task) {
6274
- if (!name || typeof name !== 'string') {
6275
- throw new Error('Registry task name must be a non-empty string.');
6276
- }
6277
- if (!task || typeof task !== 'function' || !task.task) {
6278
- throw new Error(\`Registry task '\${name}' must be a TaskFn.\`);
6279
- }
6280
- if (entries.has(name)) {
6281
- throw new Error(\`Registry already has a task named '\${name}'.\`);
6282
- }
6283
- if (task.task) {
6284
- task.task.name = name;
6285
- }
6286
- entries.set(name, task);
6287
- return registry;
6288
- },
6289
- get(name) {
6290
- return entries.get(name);
6291
- },
6292
- tasks() {
6293
- return Array.from(entries.entries()).sort(([a], [b]) => a.localeCompare(b));
6294
- },
6295
- has(name) {
6296
- return entries.has(name);
6297
- },
6298
- names() {
6299
- return Array.from(entries.keys()).sort((a, b) => a.localeCompare(b));
6300
- },
6301
- size() {
6302
- return entries.size;
6303
- },
6304
- };
6305
-
6306
- return registry;
6307
- }
6308
- `;
6309
- var taskWrapperTsTemplate = `import { task as baseTask, type RunFn, type RunFnReturnValue, type TaskFn, type TaskOptions, type TaskParams } from 'hostctl';
6310
- import type { ZodTypeAny } from 'zod';
6311
-
6312
- export type TaskOptionsWithSchema = TaskOptions & {
6313
- inputSchema?: ZodTypeAny;
6314
- outputSchema?: ZodTypeAny;
6315
- };
6316
-
6317
- export function task<TParams extends TaskParams, TReturn extends RunFnReturnValue>(
6318
- runFn: RunFn<TParams, TReturn>,
6319
- options?: TaskOptionsWithSchema,
6320
- ): TaskFn<TParams, TReturn> {
6321
- const taskFn = baseTask(runFn, options as TaskOptions);
6322
- if (options?.inputSchema) {
6323
- (taskFn.task as any).inputSchema = options.inputSchema;
6324
- }
6325
- if (options?.outputSchema) {
6326
- (taskFn.task as any).outputSchema = options.outputSchema;
6327
- }
6328
- return taskFn;
6329
- }
6330
- `;
6331
- var taskWrapperJsTemplate = `import { task as baseTask } from 'hostctl';
6332
-
6333
- export function task(runFn, options) {
6334
- const taskFn = baseTask(runFn, options);
6335
- if (options?.inputSchema) {
6336
- taskFn.task.inputSchema = options.inputSchema;
6337
- }
6338
- if (options?.outputSchema) {
6339
- taskFn.task.outputSchema = options.outputSchema;
6340
- }
6341
- return taskFn;
6342
- }
6343
- `;
6344
- var sampleTaskTsTemplate = `import { task, type TaskContext } from '../task.js';
6345
- import { z } from 'zod';
6204
+ var sampleTaskTsTemplate = `import { task, type TaskContext, z } from 'hostctl';
6346
6205
 
6347
6206
  const HelloInputSchema = z.object({
6348
6207
  name: z.string().optional(),
@@ -6375,8 +6234,7 @@ export default task(run, {
6375
6234
  outputSchema: HelloOutputSchema,
6376
6235
  });
6377
6236
  `;
6378
- var sampleTaskJsTemplate = `import { task } from '../task.js';
6379
- import { z } from 'zod';
6237
+ var sampleTaskJsTemplate = `import { task, z } from 'hostctl';
6380
6238
 
6381
6239
  const HelloInputSchema = z.object({
6382
6240
  name: z.string().optional(),
@@ -6422,7 +6280,7 @@ hostctl run . ${registryPrefix}.hello name:Hostctl excited:true
6422
6280
  - Tasks live under \`src/\` and export a default \`task(...)\`.
6423
6281
  - \`src/index.(ts|js)\` re-exports tasks and publishes a registry for discovery.
6424
6282
  - Task names are unqualified; the registry assigns qualified names.
6425
- - Schema helpers come from \`zod\` and are attached to tasks for discovery.
6283
+ - Schema helpers come from \`hostctl\` (re-exported \`zod\`) and are attached to tasks for discovery.
6426
6284
 
6427
6285
  ## Publish
6428
6286
 
@@ -6460,8 +6318,6 @@ async function createPackage(packageName, options) {
6460
6318
  await fs9.writeFile(path5.join(packageDir, "tsconfig.json"), tsconfigTemplate);
6461
6319
  await fs9.mkdir(path5.join(packageDir, "src", "tasks"), { recursive: true });
6462
6320
  await fs9.writeFile(path5.join(packageDir, "src", "index.ts"), indexTsTemplate(registryPrefix));
6463
- await fs9.writeFile(path5.join(packageDir, "src", "registry.ts"), registryTsTemplate);
6464
- await fs9.writeFile(path5.join(packageDir, "src", "task.ts"), taskWrapperTsTemplate);
6465
6321
  await fs9.writeFile(path5.join(packageDir, "src", "tasks", "hello.ts"), sampleTaskTsTemplate);
6466
6322
  await fs9.writeFile(path5.join(packageDir, "README.md"), readmeTemplate(packageJsonName, registryPrefix, true));
6467
6323
  await fs9.writeFile(path5.join(packageDir, ".gitignore"), gitignoreTemplate);
@@ -6469,8 +6325,6 @@ async function createPackage(packageName, options) {
6469
6325
  await fs9.writeFile(path5.join(packageDir, "package.json"), packageJsonJsTemplate(packageJsonName));
6470
6326
  await fs9.mkdir(path5.join(packageDir, "src", "tasks"), { recursive: true });
6471
6327
  await fs9.writeFile(path5.join(packageDir, "src", "index.js"), indexJsTemplate(registryPrefix));
6472
- await fs9.writeFile(path5.join(packageDir, "src", "registry.js"), registryJsTemplate);
6473
- await fs9.writeFile(path5.join(packageDir, "src", "task.js"), taskWrapperJsTemplate);
6474
6328
  await fs9.writeFile(path5.join(packageDir, "src", "tasks", "hello.js"), sampleTaskJsTemplate);
6475
6329
  await fs9.writeFile(path5.join(packageDir, "README.md"), readmeTemplate(packageJsonName, registryPrefix, false));
6476
6330
  await fs9.writeFile(path5.join(packageDir, ".gitignore"), gitignoreTemplate);
@@ -6913,8 +6767,6 @@ var Cli = class {
6913
6767
  '-p, --params "<json object>"',
6914
6768
  "runtime parameters supplied as a string to be parsed as a json object representing params to supply to the script"
6915
6769
  ).option("-r, --remote", "run the script on remote hosts specified by tags via SSH orchestration").action(this.handleRun.bind(this));
6916
- const runtimeCmd = this.program.command("runtime").alias("rt").description("print out a report of the runtime environment").action(this.handleRuntime.bind(this));
6917
- runtimeCmd.command("install").alias("i").description("install a temporary runtime environment on the local host if needed").action(this.handleRuntimeInstall.bind(this));
6918
6770
  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));
6919
6771
  }
6920
6772
  async handleInventory(options, cmd) {
@@ -7094,16 +6946,6 @@ var Cli = class {
7094
6946
  logError("hostctl error:", e);
7095
6947
  }
7096
6948
  }
7097
- async handleRuntime(options, cmd) {
7098
- const opts = cmd.optsWithGlobals();
7099
- await this.loadApp(opts);
7100
- await this.app.printRuntimeReport();
7101
- }
7102
- async handleRuntimeInstall(options, cmd) {
7103
- const opts = cmd.optsWithGlobals();
7104
- await this.loadApp(opts);
7105
- await this.app.installRuntime();
7106
- }
7107
6949
  async handlePkgCreate(packageName, options) {
7108
6950
  await createPackage(packageName, options);
7109
6951
  }