pagecord-cli 0.3.1 → 0.3.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 282d0f9f75b00c70f396dd00494c60b0e345d8af90689fa6cd6f217ea2288c12
4
- data.tar.gz: db5c120d320ed5c012c4576e7fce832879336e3b3e255ce0b494ff0ddb489e73
3
+ metadata.gz: 1e49c84bdd83870e36e7d68b218fd211dcae477a6b5b7afca0649785651f7bc2
4
+ data.tar.gz: c0f1f731273d36f8bc9626c5f5f7b3f596b34ca647187140b20afea957c88e42
5
5
  SHA512:
6
- metadata.gz: 1b8f2174e7ca61cdba14e6b1accdd617da6c71c1b747c379120c6ff13955d1de26b0256ea34fecb2908e128a19eab52785abd0d2ae2d868916ac129f16d0523a
7
- data.tar.gz: 872259af41ebbf97ece3a9eb859ebf4509f97568cd8027d8b5d6f813d40c5d96562ee7de51ead2e133922a64c26d2c3f0c7f7ba1896e765de969e9ee01d88f67
6
+ metadata.gz: 97a1e5baf19415337cf2a8d0a97bb888394f3550d2b6ce85f29e7039d1aae9ad58b30a5883f1ce63bcae691211c847eb5a84c6a074f4555cb4cf4f57e35fe812
7
+ data.tar.gz: 54bc9279534fe8ce1210cfa4fb3ce962b273c23ae60654d9e96065372b16bf7211e89868d16fb5cb6a63a07e75563259fe5d9d41acb9ffc1b490528712098c64
data/README.md CHANGED
@@ -79,6 +79,19 @@ and HTML otherwise. Other flags: `--title`, `--status draft|published`,
79
79
  `--slug`, `--published-at`, `--tags`, `--canonical-url`, `--locale` and
80
80
  `--hidden`/`--no-hidden`. `--json` prints the full response.
81
81
 
82
+ ## Agent skill
83
+
84
+ The gem includes a skill that teaches coding agents such as Claude Code to use
85
+ the CLI:
86
+
87
+ ```bash
88
+ pagecord skill # print it
89
+ pagecord skill install # install it for your agents
90
+ ```
91
+
92
+ `install` writes it to `~/.agents/skills/pagecord` and links it into
93
+ `~/.claude/skills`. Run it again after upgrading the gem.
94
+
82
95
  ## Choosing a blog
83
96
 
84
97
  With one blog configured, every command uses it. With several, set a default
@@ -197,9 +210,13 @@ Action Text attachments:
197
210
 
198
211
  ```markdown
199
212
  ![Alt text](photo.jpg)
213
+ ![Alt text](my photo.jpg "A caption")
200
214
  ![[photo.jpg]]
201
215
  ```
202
216
 
217
+ The alt text and an optional quoted caption go onto the image. Filenames can
218
+ contain spaces.
219
+
203
220
  Supported local image types are JPEG, PNG, GIF, and WebP. External image URLs
204
221
  and HTML `<img>` tags are left alone.
205
222
 
@@ -0,0 +1,127 @@
1
+ ---
2
+ name: pagecord
3
+ description: Manage a Pagecord blog with the `pagecord` CLI. Use when the user wants to list, read, write or edit posts, pages or drafts, publish a local Markdown or HTML file, restyle their blog (theme, font, layout, colours, custom CSS), or add custom head or footer code.
4
+ allowed-tools: Bash, Read, Write, Edit, WebFetch
5
+ ---
6
+
7
+ # Pagecord
8
+
9
+ Everything here runs through the `pagecord` command. `pagecord --help` lists
10
+ every command and flag; `pagecord version` says which version is installed.
11
+
12
+ ## Rules
13
+
14
+ 1. **Draft unless told to publish.** Pass `--status draft` to `post create` and
15
+ `post update`, and use `pagecord draft FILE` rather than `publish`, unless
16
+ the user asked to publish.
17
+ 2. **Posts and pages are addressed by token**, eight hex characters such as
18
+ `aaa33a9b`. `post list` shows them.
19
+ 3. **Content is HTML.** `post show` returns HTML; edit it as HTML and send it
20
+ back with `--content-file`. A file ending `.md` is sent as Markdown.
21
+ 4. **`custom-code update` replaces the whole field.** Save the current value
22
+ first and keep it as the backup.
23
+ 5. **Never create an API key.** If `pagecord blog list` prints nothing, ask the
24
+ user to run `pagecord login SUBDOMAIN` with a key from Settings > API.
25
+ 6. **Pass `--json` when you need to parse output.**
26
+
27
+ ## Quick reference
28
+
29
+ | To | Run |
30
+ |----|-----|
31
+ | List posts | `pagecord post list [--drafts\|--published] [--page N]` |
32
+ | Read a post | `pagecord post show TOKEN` |
33
+ | Edit a post | `pagecord post update TOKEN --content-file F [--title T] [--status draft]` |
34
+ | Write a post | `pagecord post create --title T --content-file F --status draft` |
35
+ | Delete a post | `pagecord post delete TOKEN [--permanent]` |
36
+ | Pages | `pagecord page list\|show\|create\|update\|delete`, as for posts |
37
+ | Publish a file | `pagecord publish FILE` or `pagecord draft FILE` |
38
+ | Theme and fonts | `pagecord appearance show\|update` |
39
+ | Custom CSS, head, footer | `pagecord custom-code show\|update` |
40
+ | Blogs | `pagecord blog list`, `pagecord blog use SUBDOMAIN` |
41
+
42
+ With several blogs configured, pass `--blog SUBDOMAIN` or set a default with
43
+ `pagecord blog use`. Lists return 15 at a time, newest first; `--page 2` gets
44
+ the next 15. The home page isn't covered by the CLI yet.
45
+
46
+ ## Editing posts
47
+
48
+ ```bash
49
+ pagecord post show TOKEN --json # fields plus the HTML content
50
+ pagecord post update TOKEN --content-file post.html --status draft
51
+ ```
52
+
53
+ `update` changes only the flags given. Other fields: `--slug`,
54
+ `--published-at`, `--tags "a, b"`, `--canonical-url`, `--locale`,
55
+ `--hidden`/`--no-hidden`. Delete moves a post to the bin unless you pass
56
+ `--permanent`, which can't be undone: confirm with the user first.
57
+
58
+ ## Publishing files
59
+
60
+ ```bash
61
+ pagecord publish hello.md # publish or update
62
+ pagecord draft notes/idea.md # save as a draft
63
+ ```
64
+
65
+ The first run writes a `pagecord_token` into the file's front matter, and later
66
+ runs update that same post. Front matter can set `title`, `slug`, `tags`,
67
+ `published_at`, `canonical_url`, `hidden` and `locale`. Local images written as
68
+ `![alt](photo.jpg "Caption")` are uploaded with their alt text and
69
+ optional caption.
70
+
71
+ ## Restyling a blog
72
+
73
+ Read https://help.pagecord.com/custom-css before the first CSS change. It
74
+ documents every class you can target and what Pagecord accepts. Don't work out
75
+ the structure by scraping a blog: comments load on demand and are missing from
76
+ the page source.
77
+
78
+ Look at the theme first, since your CSS has to work with it:
79
+
80
+ ```bash
81
+ pagecord appearance show
82
+ pagecord custom-code show --css > blog.css
83
+ cp blog.css blog.css.orig
84
+ ```
85
+
86
+ Edit `blog.css`, show the user a diff, then save it:
87
+
88
+ ```bash
89
+ pagecord custom-code update --css blog.css
90
+ ```
91
+
92
+ Undoing is `pagecord custom-code update --css blog.css.orig`; tell the user.
93
+
94
+ Pagecord rejects CSS that is over 16KB, nested (`.post { .title { } }`), or
95
+ `@import`s anything but HTTPS Google Fonts or Bunny Fonts. A rejected update
96
+ changes nothing. Custom properties, `@supports` and `@layer` are fine.
97
+
98
+ Prefer overriding the theme's custom properties, which work in light and dark
99
+ mode, over restyling elements one by one:
100
+
101
+ ```css
102
+ :root {
103
+ --theme-bg: #faf7f0;
104
+ --theme-text: #2b2b2b;
105
+ --theme-accent: #8c5a3c;
106
+ --font-body: Georgia, serif;
107
+ --font-size-base: 112.5%;
108
+ }
109
+ ```
110
+
111
+ Scope rules with the attributes on `<body>`: `data-page-type` is `index`,
112
+ `home-page`, `page` or `post`, and `data-slug` is the slug being viewed. Target
113
+ `.post-body`, never classes containing `lexxy`, which may be renamed.
114
+
115
+ Whole-theme changes belong in `appearance update`, not CSS:
116
+ `--theme`, `--font`, `--width`, `--layout`, the six `--custom-theme-*` colours
117
+ and `--show-branding true|false`. The Theme Garden in Settings > Appearance
118
+ applies curated designs in one click.
119
+
120
+ `--footer-html`, `--head-html` and `--body-html` work like `--css` and each
121
+ take a file path. `--enabled false` switches head and body code off without
122
+ deleting it.
123
+
124
+ ## Always verify
125
+
126
+ After any change, load the blog and check it. A successful update only means
127
+ the change was saved.
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "io/console"
4
4
  require "json"
5
+ require "fileutils"
5
6
  require "optparse"
6
7
 
7
8
  module PagecordCLI
@@ -21,6 +22,8 @@ module PagecordCLI
21
22
  "body-html" => "custom_body_html"
22
23
  }.freeze
23
24
 
25
+ SKILL_PATH = File.expand_path("SKILL.md", __dir__)
26
+
24
27
  attr_reader :argv, :config, :input, :output, :error, :options
25
28
 
26
29
  def initialize(argv, config: Config.new, input: $stdin, output: $stdout, error: $stderr)
@@ -48,6 +51,7 @@ module PagecordCLI
48
51
  when "custom-code" then custom_code
49
52
  when "post" then records("posts")
50
53
  when "page" then records("pages")
54
+ when "skill" then skill
51
55
  when "publish" then publish("published")
52
56
  when "draft" then publish("draft")
53
57
  else
@@ -309,6 +313,34 @@ module PagecordCLI
309
313
  params
310
314
  end
311
315
 
316
+ def skill
317
+ case argv.shift
318
+ when nil then output.print File.read(SKILL_PATH)
319
+ when "install" then install_skill
320
+ else return fail_with("Usage: pagecord skill [install]")
321
+ end
322
+ 0
323
+ end
324
+
325
+ def install_skill
326
+ dir = File.join(Dir.home, ".agents", "skills", "pagecord")
327
+ FileUtils.mkdir_p(dir)
328
+ FileUtils.cp(SKILL_PATH, dir)
329
+ File.write(File.join(dir, ".installed-version"), VERSION)
330
+ say "Installed the Pagecord skill to #{dir}"
331
+
332
+ link = File.join(Dir.home, ".claude", "skills", "pagecord")
333
+ return if File.symlink?(link) && File.readlink(link) == dir
334
+
335
+ if File.exist?(link) || File.symlink?(link)
336
+ error.puts "#{link} already exists, so Claude Code will keep using that. Remove it and run this again to use this skill."
337
+ else
338
+ FileUtils.mkdir_p(File.dirname(link))
339
+ File.symlink(dir, link)
340
+ say "Linked it into #{link}"
341
+ end
342
+ end
343
+
312
344
  def publish(status)
313
345
  overrides = {}
314
346
  parser do |opts|
@@ -423,6 +455,7 @@ module PagecordCLI
423
455
  output.puts <<~HELP
424
456
  Usage:
425
457
  pagecord version
458
+ pagecord skill [install]
426
459
  pagecord login SUBDOMAIN
427
460
  pagecord logout [SUBDOMAIN]
428
461
  pagecord blog list
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest"
4
+ require "cgi/escape"
5
+ require "uri"
4
6
 
5
7
  module PagecordCLI
6
8
  class ImageUploads
7
- MARKDOWN_IMAGE = /!\[([^\]]*)\]\(([^)]+)\)/
9
+ MARKDOWN_IMAGE = /!\[([^\]]*)\]\(([^)"]+?)(?:\s+"([^"]*)")?\)/
8
10
  OBSIDIAN_IMAGE = /!\[\[([^\]]+)\]\]/
9
11
  IMAGE_EXTENSIONS = /\.(jpe?g|png|gif|webp)\z/i
10
12
 
@@ -20,8 +22,9 @@ module PagecordCLI
20
22
 
21
23
  def process
22
24
  with_markdown_images = content.gsub(MARKDOWN_IMAGE) do |match|
23
- path = Regexp.last_match(2)
24
- local_path?(path) ? attachment_tag_for(path) : match
25
+ alt, path, caption = Regexp.last_match.captures
26
+ path = decode_path(path)
27
+ local_path?(path) ? attachment_tag_for(path, alt: alt, caption: caption) : match
25
28
  end
26
29
 
27
30
  with_markdown_images.gsub(OBSIDIAN_IMAGE) do |match|
@@ -32,9 +35,20 @@ module PagecordCLI
32
35
 
33
36
  private
34
37
 
35
- def attachment_tag_for(path)
38
+ def attachment_tag_for(path, alt: nil, caption: nil)
36
39
  sgid = cached_sgid(path) || upload(path)
37
- %(<action-text-attachment sgid="#{sgid}"></action-text-attachment>)
40
+ %(<action-text-attachment sgid="#{sgid}"#{attribute("alt", alt)}#{attribute("caption", caption)}></action-text-attachment>)
41
+ end
42
+
43
+ def attribute(name, value)
44
+ %( #{name}="#{CGI.escapeHTML(value)}") unless value.to_s.empty?
45
+ end
46
+
47
+ # A filename can contain a literal "%", as in 100%.jpg.
48
+ def decode_path(path)
49
+ URI.decode_uri_component(path)
50
+ rescue ArgumentError
51
+ path
38
52
  end
39
53
 
40
54
  def cached_sgid(path)
@@ -74,7 +88,8 @@ module PagecordCLI
74
88
  end
75
89
 
76
90
  def checksum(path)
77
- Digest::SHA256.file(path).hexdigest[0, 16]
91
+ @checksums ||= {}
92
+ @checksums[path] ||= Digest::SHA256.file(path).hexdigest[0, 16]
78
93
  end
79
94
 
80
95
  def filename(path)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PagecordCLI
4
- VERSION = "0.3.1"
4
+ VERSION = "0.3.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pagecord-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Olly Headey
@@ -20,6 +20,7 @@ files:
20
20
  - README.md
21
21
  - bin/pagecord
22
22
  - lib/pagecord_cli.rb
23
+ - lib/pagecord_cli/SKILL.md
23
24
  - lib/pagecord_cli/cli.rb
24
25
  - lib/pagecord_cli/client.rb
25
26
  - lib/pagecord_cli/config.rb