htmlhost-cli 2.13.0 → 2.14.0

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
@@ -174,7 +174,7 @@ htmlhost agents --no-claude # don't touch CLAUDE.md
174
174
  The same guide is at https://htmlhost.co/agents.md.
175
175
 
176
176
  ### `htmlhost list`
177
- List all your sites with titles, sizes, TTL, and expiry. Multi-page sites are marked with 📄. Sites shared with you as a collaborator follow under **Shared with you**, with their owner: `clone`, `deploy` and `versions` work on them with your own sign-in; only the owner can delete them.
177
+ List all your sites with titles, sizes, TTL, and expiry. Multi-page sites are marked with 📄. Sites shared with you follow under **Shared with you**, with their owner and your role. Admins and Editors can `clone`, `deploy` and roll them back with their own sign-in; Contributors can `clone` and `deploy --draft`, then ask someone who can publish; Viewers only look. Only the owner can delete a site.
178
178
 
179
179
  ### `htmlhost open <slug>`
180
180
  Open a deployed site in the default browser.
@@ -227,7 +227,7 @@ Free publishing is limited. Subscribe to Pro to continue creating unlimited site
227
227
 
228
228
  ## Unpublished sites and drafts
229
229
 
230
- Unpublished websites remain private until publishing is explicitly confirmed. Collaborators can publish with confirmation; only owners can unpublish. The server also cancels deployments still running when someone unpublishes.
230
+ Unpublished websites remain private until publishing is explicitly confirmed. Admins and Editors on a shared site can publish with confirmation and unpublish; Contributors save drafts only. The server also cancels deployments still running when someone unpublishes.
231
231
 
232
232
  - `htmlhost deploy --draft` saves files without changing publication.
233
233
  - `htmlhost deploy --publish` explicitly makes an unpublished website public. The same flag applies to `rollback`.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "htmlhost-cli",
3
- "version": "2.13.0",
4
- "description": "Deploy HTML files from the terminal \u2014 htmlhost.co CLI",
3
+ "version": "2.14.0",
4
+ "description": "Deploy HTML files from the terminal — htmlhost.co CLI",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "htmlhost": "bin/htmlhost.mjs"
package/src/cli.mjs CHANGED
@@ -5,7 +5,7 @@ import { bold, dim, cyan, err } from "./ui.mjs";
5
5
  import { ApiError } from "./api.mjs";
6
6
  import { startUpdateCheck, applyUpdate } from "./update.mjs";
7
7
 
8
- const VERSION = "2.13.0";
8
+ const VERSION = "2.14.0";
9
9
 
10
10
  const HELP = `
11
11
  ${bold("htmlhost")} ${dim(`v${VERSION}`)} — deploy HTML from the terminal
@@ -17,10 +17,10 @@ const HELP = `
17
17
  ${cyan("htmlhost pull")} [slug] [opts] Pull remote changes to local
18
18
  ${cyan("htmlhost clone")} <slug> [dir] Clone a site to a local directory
19
19
  ${cyan("htmlhost agents")} [dir] Teach your AI coding tool htmlhost (AGENTS.md)
20
- ${cyan("htmlhost recipients")} [slug] [cmd] Manage form notification recipients (owner)
20
+ ${cyan("htmlhost recipients")} [slug] [cmd] Manage form notification recipients (owner, Admin)
21
21
  ${cyan("htmlhost list")} List your sites and ones shared with you
22
22
  ${cyan("htmlhost versions")} [slug] List a site's published versions
23
- ${cyan("htmlhost unpublish")} [slug] Take a site offline (owner only)
23
+ ${cyan("htmlhost unpublish")} [slug] Take a site offline (owner, Admin, Editor)
24
24
  ${cyan("htmlhost rollback")} [slug] [ver] Make an earlier version live again (Pro)
25
25
  ${cyan("htmlhost delete")} <slug> Delete a site (owner)
26
26
  ${cyan("htmlhost login")} Sign in (approve in your browser)
@@ -2,8 +2,8 @@ import { get } from "../api.mjs";
2
2
  import { dim, cyan, bold, formatBytes, yellow } from "../ui.mjs";
3
3
 
4
4
  /**
5
- * htmlhost list — show all sites: your own, then the ones shared with you
6
- * (you can clone, deploy and restore those, not delete them).
5
+ * htmlhost list — show all sites: your own, then the ones shared with you,
6
+ * with your role on each (what you can do follows it; only owners delete).
7
7
  */
8
8
  export async function list({ json = false } = {}) {
9
9
  const data = await get("/api/sites");
@@ -54,11 +54,14 @@ export async function list({ json = false } = {}) {
54
54
  if (shared.length) {
55
55
  const sharedSlugW = Math.max(14, ...shared.map((s) => s.slug.length)) + 2;
56
56
  console.log("");
57
- console.log(` ${bold("Shared with you")} ${dim("(clone, deploy and restore; only the owner can delete)")}`);
58
- console.log(` ${dim(pad("SLUG", sharedSlugW) + pad("TITLE", 30) + pad("STATUS", 12) + "OWNER")}`);
57
+ console.log(` ${bold("Shared with you")} ${dim("(what you can do follows your role; only the owner can delete)")}`);
58
+ console.log(` ${dim(pad("SLUG", sharedSlugW) + pad("TITLE", 30) + pad("STATUS", 12) + pad("ROLE", 13) + "OWNER")}`);
59
59
  for (const s of shared) {
60
60
  const title = truncate(s.title && s.title !== s.slug ? s.title : "", 28);
61
- console.log(` ${pad(cyan(s.slug), sharedSlugW)}${pad(dim(title), 30)}${pad(s.isPublished === false ? "Draft" : "Published", 12)}${dim(s.owner || "")}`);
61
+ console.log(` ${pad(cyan(s.slug), sharedSlugW)}${pad(dim(title), 30)}${pad(s.isPublished === false ? "Draft" : "Published", 12)}${pad(roleLabel(s.role), 13)}${dim(s.owner || "")}`);
62
+ }
63
+ if (shared.some((s) => s.canPublish === false && s.canEdit !== false)) {
64
+ console.log(` ${dim("Contributors save drafts (htmlhost deploy --draft) and ask an Owner, Admin or Editor to publish.")}`);
62
65
  }
63
66
  }
64
67
 
@@ -91,3 +94,8 @@ function relTime(iso) {
91
94
  if (days === 1) return "tomorrow";
92
95
  return `${days}d`;
93
96
  }
97
+
98
+ /** The server's role name, as people read it ("contributor" → "Contributor"). Older servers send "editor". */
99
+ function roleLabel(role) {
100
+ return typeof role === "string" && role ? role[0].toUpperCase() + role.slice(1) : "Editor";
101
+ }