@hitslop/cli 0.1.2 → 0.1.4

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
@@ -15,6 +15,10 @@ The supported v1 scaffold uses Svelte 5 and includes portable
15
15
  source-free runtime package; `slop register` adds an immutable local template;
16
16
  `slop publish` captures and signs one artifact for the catalog.
17
17
 
18
+ Interactive init asks for a required author name and optional URL. For
19
+ non-interactive use, pass `--yes --author-name "Your Name"` and optionally
20
+ `--author-url https://example.com`.
21
+
18
22
  Documentation: [Authoring](https://github.com/hitslop/hitslop/blob/main/docs/authoring.md) ·
19
23
  [Package format](https://github.com/hitslop/hitslop/blob/main/docs/package-format.md)
20
24
 
package/dist/cli.js CHANGED
@@ -21,7 +21,7 @@ import { manifestSchemaURL, parseManifest } from "@hitslop/schema";
21
21
  // package.json
22
22
  var package_default = {
23
23
  name: "@hitslop/cli",
24
- version: "0.1.2",
24
+ version: "0.1.4",
25
25
  description: "Create, develop, build, register, and publish hitSlop apps.",
26
26
  license: "MIT",
27
27
  repository: { type: "git", url: "git+https://github.com/hitslop/hitslop.git", directory: "packages/cli" },
@@ -41,7 +41,7 @@ var package_default = {
41
41
  },
42
42
  dependencies: {
43
43
  "@crustjs/core": "^0.0.19",
44
- "@hitslop/schema": "^0.1.2",
44
+ "@hitslop/schema": "^0.1.3",
45
45
  "@noble/ed25519": "^3.2.0",
46
46
  "fast-png": "^8.0.0",
47
47
  fflate: "^0.8.3",
@@ -338,7 +338,7 @@ async function validateSkin(root, manifest) {
338
338
  if (png.channels !== 4)
339
339
  throw new Error("The window skin must be an RGBA PNG image.");
340
340
  }
341
- async function scaffold(destination, options = {}) {
341
+ async function scaffold(destination, options) {
342
342
  const template = options.template ?? "svelte-counter";
343
343
  if (!new Set(["svelte", "svelte-counter"]).has(template))
344
344
  throw new Error(`Unknown authoring template: ${template}`);
@@ -360,18 +360,20 @@ async function scaffold(destination, options = {}) {
360
360
  register: "slop register",
361
361
  publish: "slop publish"
362
362
  },
363
- dependencies: { "@hitslop/runtime": `^${package_default.version}`, "@hitslop/svelte": `^${package_default.version}`, "bits-ui": "^2.19.0", svelte: "^5.0.0", zod: "^4.5.2" },
363
+ dependencies: { "@hitslop/runtime": "^0.1.2", "@hitslop/svelte": "^0.1.2", "bits-ui": "^2.19.0", svelte: "^5.0.0", zod: "^4.5.2" },
364
364
  devDependencies: { "@hitslop/cli": `^${package_default.version}`, "@sveltejs/vite-plugin-svelte": "^7.0.0", "@vanilla-extract/css": "^1.17.4", "@vanilla-extract/vite-plugin": "^5.1.1", vite: "^8.0.0" }
365
365
  }, null, 2) + `
366
366
  `);
367
- await writeFile2(join3(destination, "manifest.json"), JSON.stringify({
367
+ const manifest = parseManifest({
368
368
  $schema: manifestSchemaURL,
369
369
  slug,
370
+ author: options.author,
370
371
  title,
371
372
  description: options.description || "A small, lovable hitSlop app.",
372
373
  categories: options.categories?.length ? options.categories : ["utilities"],
373
374
  presentation: { width: 560, height: 420 }
374
- }, null, 2) + `
375
+ });
376
+ await writeFile2(join3(destination, "manifest.json"), JSON.stringify(manifest, null, 2) + `
375
377
  `);
376
378
  const appPath = join3(destination, "src/App.svelte");
377
379
  await writeFile2(appPath, (await readFile3(appPath, "utf8")).replace("__SLOP_TITLE_LITERAL__", JSON.stringify(title)));
@@ -606,7 +608,7 @@ import { getPublicKeyAsync, keygenAsync, signAsync } from "@noble/ed25519";
606
608
  import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
607
609
  import { chmod, mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
608
610
  import { join as join5, resolve as resolve3 } from "path";
609
- import { homedir, userInfo } from "os";
611
+ import { homedir } from "os";
610
612
  var service = "app.hitslop.cli";
611
613
  var account = "publisher";
612
614
  var fallback = join5(homedir(), ".hitslop", "identity.json");
@@ -637,14 +639,12 @@ async function storeIdentity(identity) {
637
639
  }
638
640
  }
639
641
  async function validated(identity) {
640
- if (!identity.displayName.trim() || identity.displayName.length > 80)
641
- throw new Error("Publisher name must contain 1\u201380 characters.");
642
642
  const derivedPublic = await getPublicKeyAsync(decode(identity.privateKey));
643
643
  if (encode(derivedPublic) !== identity.publicKey)
644
644
  throw new Error("Identity public and private keys do not match.");
645
645
  if (sha256(derivedPublic).slice(0, 32) !== identity.keyId)
646
646
  throw new Error("Identity key ID does not match its public key.");
647
- return { ...identity, displayName: identity.displayName.trim() };
647
+ return { privateKey: identity.privateKey, publicKey: identity.publicKey, keyId: identity.keyId };
648
648
  }
649
649
  async function getIdentity() {
650
650
  const stored = await readStored();
@@ -652,16 +652,10 @@ async function getIdentity() {
652
652
  return validated(stored);
653
653
  const pair = await keygenAsync();
654
654
  const publicKey = pair.publicKey ?? await getPublicKeyAsync(pair.secretKey);
655
- const identity = await validated({ privateKey: encode(pair.secretKey), publicKey: encode(publicKey), keyId: sha256(publicKey).slice(0, 32), displayName: userInfo().username });
655
+ const identity = await validated({ privateKey: encode(pair.secretKey), publicKey: encode(publicKey), keyId: sha256(publicKey).slice(0, 32) });
656
656
  await storeIdentity(identity);
657
657
  return identity;
658
658
  }
659
- async function setIdentityName(displayName) {
660
- const identity = await getIdentity();
661
- const next = await validated({ ...identity, displayName });
662
- await storeIdentity(next);
663
- return next;
664
- }
665
659
  async function exportIdentity(path, passphrase) {
666
660
  if (passphrase.length < 10)
667
661
  throw new Error("Identity export passphrases must contain at least 10 characters.");
@@ -1135,11 +1129,10 @@ async function publishSlop(root, flags) {
1135
1129
  const packed = await packSlop(built.directory);
1136
1130
  const identity = await getIdentity();
1137
1131
  const envelope = {
1138
- format: "hitslop-publish/2",
1132
+ format: "hitslop-publish/3",
1139
1133
  requestId: crypto.randomUUID(),
1140
1134
  publisherKeyId: identity.keyId,
1141
1135
  publicKey: identity.publicKey,
1142
- displayName: identity.displayName,
1143
1136
  artifactSha256: packed.sha256,
1144
1137
  artifactBytes: packed.bytes.byteLength,
1145
1138
  timestamp: Date.now()
@@ -1179,9 +1172,14 @@ async function screenshotDocument(path, options, native = runNative) {
1179
1172
  var titleFor = (directory) => directory.split("/").filter(Boolean).at(-1)?.split(/[-_ ]+/).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ") || "My Slop";
1180
1173
  var parseCategories = (values) => values.map((value) => SlopCategorySchema.parse(value.trim().toLowerCase()));
1181
1174
  async function initMetadata(directory, flags) {
1175
+ const authorName = flags["author-name"]?.trim();
1176
+ const authorURL = flags["author-url"]?.trim();
1182
1177
  const defaults = { title: flags.title || titleFor(directory), description: flags.description || "A small, lovable hitSlop app.", categories: parseCategories(flags.category?.length ? flags.category : ["utilities"]) };
1183
- if (flags.yes || !process.stdin.isTTY)
1184
- return defaults;
1178
+ if (flags.yes || !process.stdin.isTTY) {
1179
+ if (!authorName)
1180
+ throw new Error("--author-name is required with --yes or in a non-interactive terminal.");
1181
+ return { ...defaults, author: { name: authorName, ...authorURL ? { url: authorURL } : {} } };
1182
+ }
1185
1183
  const prompt = createInterface({ input: process.stdin, output: process.stdout });
1186
1184
  try {
1187
1185
  const title = (await prompt.question(`Title (${defaults.title}): `)).trim() || defaults.title;
@@ -1190,7 +1188,11 @@ async function initMetadata(directory, flags) {
1190
1188
  const categories = rawCategories ? parseCategories(rawCategories.split(",").filter(Boolean)) : defaults.categories;
1191
1189
  if (categories.length < 1 || categories.length > 2)
1192
1190
  throw new Error("Choose one or two categories.");
1193
- return { title, description, categories };
1191
+ const name = authorName || (await prompt.question("Author name: ")).trim();
1192
+ if (!name)
1193
+ throw new Error("Author name is required.");
1194
+ const url = authorURL || (await prompt.question("Author URL (optional): ")).trim();
1195
+ return { title, description, categories, author: { name, ...url ? { url } : {} } };
1194
1196
  } finally {
1195
1197
  prompt.close();
1196
1198
  }
@@ -1210,6 +1212,8 @@ app = app.command("init", (command) => command.meta({ description: "Create a Sve
1210
1212
  template: { type: "string", description: "Svelte authoring template (svelte or svelte-counter)." },
1211
1213
  title: { type: "string", description: "Manifest title." },
1212
1214
  description: { type: "string", description: "Manifest description." },
1215
+ "author-name": { type: "string", description: "Required manifest author name." },
1216
+ "author-url": { type: "string", description: "Optional manifest author URL." },
1213
1217
  category: { type: "string", multiple: true, description: "Manifest category; pass once or twice." },
1214
1218
  yes: { type: "boolean", description: "Accept manifest defaults without prompting." }
1215
1219
  }).run(async ({ args, flags }) => {
@@ -1235,7 +1239,7 @@ app = app.command("register", (command) => command.meta({ description: "Build an
1235
1239
  const result = await installTemplate(args.path, { force: flags.force ?? false, ...flags.preview ? { preview: flags.preview } : {}, ...flags.icon ? { icon: flags.icon } : {}, confirmOverwrite: confirmReplacement });
1236
1240
  console.log(`${result.replaced ? "Updated" : "Installed"} ${result.manifest.title} at ${result.directory}`);
1237
1241
  }));
1238
- app = app.command("publish", (command) => command.args([{ name: "path", type: "path", default: "." }]).flags({ registry: { type: "string", description: "Catalog publish endpoint. Defaults to https://api.hitslop.com/api/publish. Set HITSLOP_REGISTRY_URL=http://localhost:3000/api/publish for a local catalog." }, preview: { type: "path" }, icon: { type: "path", description: "Use this 512x512 PNG as the catalog and Finder icon." } }).run(async ({ args, flags }) => {
1242
+ app = app.command("publish", (command) => command.args([{ name: "path", type: "path", default: "." }]).flags({ registry: { type: "string", description: "Catalog publish endpoint. Defaults to https://api.hitslop.com/api/publish. Set HITSLOP_REGISTRY_URL=http://127.0.0.1:5002/api/publish for the local Firebase emulators." }, preview: { type: "path" }, icon: { type: "path", description: "Use this 512x512 PNG as the catalog and Finder icon." } }).run(async ({ args, flags }) => {
1239
1243
  console.log(await publishSlop(args.path, { ...flags.registry ? { registry: flags.registry } : {}, ...flags.preview ? { preview: flags.preview } : {}, ...flags.icon ? { icon: flags.icon } : {} }));
1240
1244
  }));
1241
1245
  app = app.command("export", (command) => command.meta({ description: "Export a built .slop document as a full-height PNG or PDF." }).args([{ name: "path", type: "path", default: "." }]).flags({
@@ -1303,15 +1307,7 @@ async function runIdentity(arguments_) {
1303
1307
  const action = arguments_[1] ?? "show";
1304
1308
  if (action === "show") {
1305
1309
  const identity = await getIdentity();
1306
- console.log(`${identity.keyId} ${identity.displayName}`);
1307
- return true;
1308
- }
1309
- if (action === "set-name") {
1310
- const name = arguments_.slice(2).join(" ").trim();
1311
- if (!name)
1312
- throw new Error("Usage: slop identity set-name <name>");
1313
- const identity = await setIdentityName(name);
1314
- console.log(`${identity.keyId} ${identity.displayName}`);
1310
+ console.log(identity.keyId);
1315
1311
  return true;
1316
1312
  }
1317
1313
  if (action === "export") {
@@ -1331,7 +1327,7 @@ async function runIdentity(arguments_) {
1331
1327
  if (!path)
1332
1328
  throw new Error("Usage: slop identity import <file> [--force]");
1333
1329
  const identity = await importIdentity(path, await secret("Import passphrase: "), arguments_.includes("--force"));
1334
- console.log(`${identity.keyId} ${identity.displayName}`);
1330
+ console.log(identity.keyId);
1335
1331
  return true;
1336
1332
  }
1337
1333
  throw new Error(`Unknown identity command: ${action}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hitslop/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Create, develop, build, register, and publish hitSlop apps.",
5
5
  "license": "MIT",
6
6
  "repository": { "type": "git", "url": "git+https://github.com/hitslop/hitslop.git", "directory": "packages/cli" },
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@crustjs/core": "^0.0.19",
23
- "@hitslop/schema": "^0.1.2",
23
+ "@hitslop/schema": "^0.1.3",
24
24
  "@noble/ed25519": "^3.2.0",
25
25
  "fast-png": "^8.0.0",
26
26
  "fflate": "^0.8.3",
@@ -1,6 +1,7 @@
1
1
  # hitSlop authoring
2
2
 
3
- Read `manifest.json` first. Use the local `hitslop-authoring` skill for package,
3
+ Read `manifest.json` first. Keep its required author name and optional HTTP(S)
4
+ author URL accurate. Use the local `hitslop-authoring` skill for package,
4
5
  storage, capture, install, and publish work. Use `hitslop-design` when creating
5
6
  or revising the interface. Treat the host window as the outer object boundary
6
7
  and size it for realistic default content. Use Bits UI (`bits-ui`) for interactive