@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 +4 -0
- package/dist/cli.js +29 -33
- package/package.json +2 -2
- package/templates/svelte/AGENTS.md +2 -1
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.
|
|
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.
|
|
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":
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
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 {
|
|
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)
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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://
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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
|