pageship 0.5.0 → 0.6.1

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
@@ -14,7 +14,7 @@ pnpm add -g pageship
14
14
  bun add -g pageship
15
15
  ```
16
16
 
17
- Then run `pageship --version` to check that the shell finds it. To have an agent install the CLI and the Pageship skills and sign you in, give it the [setup guide](https://pageship.dev/setup-guide) instead.
17
+ Then run `pageship --version` to check that the shell finds it. Run `pageship update` later to update it with the same package manager. To have an agent install the CLI and the Pageship skills and sign you in, give it the [setup guide](https://pageship.dev/setup-guide) instead.
18
18
 
19
19
  ## Get started
20
20
 
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { execFile } from "node:child_process";
2
+ import { exec, execFile, spawn } from "node:child_process";
3
3
  import { access, cp, lstat, mkdir, open, readFile, readdir, readlink, realpath, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
4
4
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { createInterface } from "node:readline/promises";
@@ -675,30 +675,18 @@ const documentsRestore = (options) => (options.client ?? client).post({
675
675
  ...options.headers
676
676
  }
677
677
  });
678
- const documentsUpdateVisibility = (options) => (options.client ?? client).patch({
679
- url: "/api/v1/documents/{id}/visibility",
680
- ...options,
681
- headers: {
682
- "Content-Type": "application/json",
683
- ...options.headers
684
- }
685
- });
686
678
  const viewersList = (options) => (options.client ?? client).get({
687
679
  url: "/api/v1/documents/{id}/viewers",
688
680
  ...options
689
681
  });
690
- const viewersAdd = (options) => (options.client ?? client).post({
691
- url: "/api/v1/documents/{id}/viewers",
682
+ const viewersUpdateSharing = (options) => (options.client ?? client).patch({
683
+ url: "/api/v1/documents/{id}/sharing",
692
684
  ...options,
693
685
  headers: {
694
686
  "Content-Type": "application/json",
695
687
  ...options.headers
696
688
  }
697
689
  });
698
- const viewersRemove = (options) => (options.client ?? client).delete({
699
- url: "/api/v1/documents/{id}/viewers/{githubAccountId}",
700
- ...options
701
- });
702
690
  const githubGetUser = (options) => (options.client ?? client).get({
703
691
  url: "/api/v1/github/users/{login}",
704
692
  ...options
@@ -797,10 +785,12 @@ function validRevision(value, documentId) {
797
785
  var DocumentRequestError = class extends Error {
798
786
  status;
799
787
  httpStatus;
800
- constructor(status, message, httpStatus) {
788
+ account;
789
+ constructor(status, message, httpStatus, account) {
801
790
  super(message);
802
791
  this.status = status;
803
792
  this.httpStatus = httpStatus;
793
+ this.account = account;
804
794
  }
805
795
  };
806
796
  function failed(response, error) {
@@ -921,29 +911,6 @@ async function restoreDocumentRevision(baseUrl, id, input, options = {}) {
921
911
  if (record(data) && data.status === "idempotency_conflict") return { status: "idempotency_conflict" };
922
912
  throw new Error("Invalid document restoration response. Retry with the same idempotency key.");
923
913
  }
924
- async function updateDocumentVisibility(baseUrl, id, visibility, options = {}) {
925
- const { data: received, response, error } = await documentsUpdateVisibility({
926
- ...changeOptions(baseUrl, options),
927
- path: { id },
928
- body: { visibility }
929
- });
930
- if (!response?.ok) {
931
- if (response?.status === 400) throw new DocumentRequestError("invalid_visibility", "Choose private or public.", 400);
932
- if (response?.status === 401) throw new DocumentRequestError("signed_out", "Sign in to update this document.", 401);
933
- if (response?.status === 403) throw new DocumentRequestError("uninvited", "Access unavailable.", 403);
934
- if (response?.status === 404) throw new DocumentRequestError("unavailable", "Document unavailable.", 404);
935
- throw new Error("Could not update document visibility. Try again.", { cause: error });
936
- }
937
- const data = received;
938
- const updated = record(data) && data.status === "visibility_updated" ? normalizeDocument(data.document) : void 0;
939
- const removedViewers = record(data) && data.removedViewers !== void 0 ? data.removedViewers : 0;
940
- if (updated?.visibility === visibility && typeof removedViewers === "number" && Number.isSafeInteger(removedViewers) && removedViewers >= 0) return {
941
- status: "visibility_updated",
942
- document: updated,
943
- removedViewers
944
- };
945
- throw new Error("Invalid document visibility response.");
946
- }
947
914
  async function publishDocument(baseUrl, input, options = {}) {
948
915
  if (input.action) {
949
916
  if (!(await getDocumentLimits(baseUrl, options)).publicationActions.includes(input.action)) throw new DocumentRequestError("publication_action_unsupported", "This server does not support explicit publication actions. Ask the operator to update the server. No HTML was uploaded.", 400);
@@ -990,7 +957,21 @@ function isGitHubAccountId(value) {
990
957
  return typeof value === "string" && /^[1-9][0-9]{0,19}(?![\s\S])/u.test(value);
991
958
  }
992
959
  function validViewer(value) {
993
- return record(value) && isGitHubAccountId(value.githubAccountId) && isGitHubLogin(value.login) && (value.name === null || typeof value.name === "string") && typeof value.addedAt === "number" && Number.isSafeInteger(value.addedAt) && value.addedAt >= 0;
960
+ return record(value) && isGitHubAccountId(value.githubAccountId) && isGitHubLogin(value.login) && (value.name === null || typeof value.name === "string") && typeof value.addedAt === "number" && Number.isSafeInteger(value.addedAt) && value.addedAt >= 0 && typeof value.viewCount === "number" && Number.isSafeInteger(value.viewCount) && value.viewCount >= 0 && (value.lastViewedAt === null || typeof value.lastViewedAt === "number" && Number.isSafeInteger(value.lastViewedAt) && value.lastViewedAt >= 0);
961
+ }
962
+ function normalizeViewer(value) {
963
+ if (!record(value)) return;
964
+ const normalized = {
965
+ ...value,
966
+ ...value.viewCount === void 0 ? { viewCount: 0 } : {},
967
+ ...value.lastViewedAt === void 0 ? { lastViewedAt: null } : {}
968
+ };
969
+ return validViewer(normalized) ? normalized : void 0;
970
+ }
971
+ function normalizeViewers(value) {
972
+ if (!Array.isArray(value)) return;
973
+ const viewers = value.map(normalizeViewer);
974
+ return viewers.every((viewer) => viewer !== void 0) ? viewers : void 0;
994
975
  }
995
976
  function validUserMatch(value) {
996
977
  return record(value) && isGitHubAccountId(value.githubAccountId) && isGitHubLogin(value.login);
@@ -1014,13 +995,14 @@ const viewerRefusals = {
1014
995
  owner_account: "You already have access to your own document.",
1015
996
  github_sign_in_required: "GitHub refused your sign-in. Sign in to Pageship again.",
1016
997
  github_rate_limited: "GitHub is limiting requests. Try again in a minute.",
1017
- github_unavailable: "GitHub is unavailable. Try again."
998
+ github_unavailable: "GitHub is unavailable. Try again.",
999
+ invalid_sharing: "A change can't both add and remove the same account."
1018
1000
  };
1019
1001
  function isViewerRefusal(value) {
1020
1002
  return typeof value === "string" && Object.hasOwn(viewerRefusals, value);
1021
1003
  }
1022
1004
  function viewerFailed(response, error) {
1023
- if (response && record(error) && isViewerRefusal(error.status)) throw new DocumentRequestError(error.status, viewerRefusals[error.status], response.status);
1005
+ if (response && record(error) && isViewerRefusal(error.status)) throw new DocumentRequestError(error.status, validUserMatch(error.account) && typeof error.message === "string" ? error.message : viewerRefusals[error.status], response.status, validUserMatch(error.account) ? error.account : void 0);
1024
1006
  failed(response, error);
1025
1007
  }
1026
1008
  async function getDocumentViewers(baseUrl, id, options = {}) {
@@ -1029,35 +1011,45 @@ async function getDocumentViewers(baseUrl, id, options = {}) {
1029
1011
  path: { id }
1030
1012
  });
1031
1013
  if (!response?.ok) viewerFailed(response, error);
1032
- if (data?.status === "listed" && Array.isArray(data.viewers) && data.viewers.every(validViewer)) return data;
1014
+ const viewers = data?.status === "listed" ? normalizeViewers(data.viewers) : void 0;
1015
+ if (data && viewers) return {
1016
+ ...data,
1017
+ viewers
1018
+ };
1033
1019
  throw new Error("Invalid document viewer listing response.");
1034
1020
  }
1035
- /** Adds the account the owner confirmed. The server looks the login up again before adding it. */
1036
- async function addDocumentViewer(baseUrl, id, user, options = {}) {
1037
- const { data, response, error } = await viewersAdd({
1021
+ /**
1022
+ * The most accounts one sharing change adds and removes. The contract's DocumentSharingChange
1023
+ * refuses more; tests/document-contract.test.ts keeps the two equal.
1024
+ */
1025
+ const sharingLimits = {
1026
+ add: 20,
1027
+ remove: 50
1028
+ };
1029
+ /**
1030
+ * Changes visibility and viewers together or not at all. The server looks up each added account
1031
+ * that isn't a viewer yet, as addDocumentViewer does.
1032
+ */
1033
+ async function updateDocumentSharing(baseUrl, id, change, options = {}) {
1034
+ const { data, response, error } = await viewersUpdateSharing({
1038
1035
  ...changeOptions(baseUrl, options),
1039
1036
  path: { id },
1040
- body: {
1041
- githubAccountId: user.githubAccountId,
1042
- login: user.login
1043
- }
1037
+ body: change
1044
1038
  });
1045
1039
  if (!response?.ok) viewerFailed(response, error);
1046
- if (data?.status === "added" && validViewer(data.viewer) && data.viewer.githubAccountId === user.githubAccountId) return data;
1047
- throw new Error("Invalid document viewer response.");
1048
- }
1049
- /** Removing an account that isn't a viewer succeeds. */
1050
- async function removeDocumentViewer(baseUrl, id, githubAccountId, options = {}) {
1051
- const { data, response, error } = await viewersRemove({
1052
- ...changeOptions(baseUrl, options),
1053
- path: {
1054
- id,
1055
- githubAccountId
1056
- }
1057
- });
1058
- if (!response?.ok) viewerFailed(response, error);
1059
- if (data?.status === "removed") return { status: "removed" };
1060
- throw new Error("Invalid document viewer removal response.");
1040
+ const updated = data?.status === "sharing_updated" ? data : void 0;
1041
+ const document = updated && normalizeDocument(updated.document);
1042
+ const viewers = updated && normalizeViewers(updated.viewers);
1043
+ const added = updated && normalizeViewers(updated.added);
1044
+ const removed = updated && normalizeViewers(updated.removed);
1045
+ if (updated && document && viewers && added && removed && (change.visibility === void 0 || document.visibility === change.visibility)) return {
1046
+ ...updated,
1047
+ document,
1048
+ viewers,
1049
+ added,
1050
+ removed
1051
+ };
1052
+ throw new Error("Invalid document sharing response.");
1061
1053
  }
1062
1054
  async function getGitHubUser(baseUrl, login, options = {}) {
1063
1055
  const { data, response, error } = await githubGetUser({
@@ -1448,6 +1440,8 @@ const outcomes = {
1448
1440
  restored: "succeeded",
1449
1441
  viewers_updated: "succeeded",
1450
1442
  skills_installed: "succeeded",
1443
+ cli_updated: "succeeded",
1444
+ cli_current: "succeeded",
1451
1445
  authorization_pending: "pending",
1452
1446
  expired: "pending",
1453
1447
  signed_out: "pending",
@@ -1483,6 +1477,8 @@ const outcomes = {
1483
1477
  document_id_taken: "failed",
1484
1478
  publication_action_unsupported: "failed",
1485
1479
  skills_modified: "failed",
1480
+ install_unknown: "failed",
1481
+ update_failed: "failed",
1486
1482
  ambiguous_viewer: "failed",
1487
1483
  document_public: "failed",
1488
1484
  github_user_not_found: "failed",
@@ -1504,7 +1500,8 @@ const commandNames = [
1504
1500
  "visibility",
1505
1501
  "viewers",
1506
1502
  "restore",
1507
- "skills"
1503
+ "skills",
1504
+ "update"
1508
1505
  ];
1509
1506
  const isCommandName = (value) => commandNames.some((name) => name === value);
1510
1507
  /** Exit codes follow the outcome, except an interruption, which exits 130. */
@@ -1554,7 +1551,7 @@ function commandLine(command, args) {
1554
1551
  }
1555
1552
  //#endregion
1556
1553
  //#region package.json
1557
- var version = "0.5.0";
1554
+ var version = "0.6.1";
1558
1555
  //#endregion
1559
1556
  //#region src/publish.ts
1560
1557
  const execute = promisify(execFile);
@@ -2427,10 +2424,12 @@ function downloadDocument(options) {
2427
2424
  }
2428
2425
  function changeVisibility(options) {
2429
2426
  return asOwner(options.server, options.signal, async ({ origin }, request) => {
2430
- const result = await updateDocumentVisibility(origin, options.documentId, options.visibility, request);
2431
- const { document, removedViewers } = result;
2427
+ const { document, removed } = await updateDocumentSharing(origin, options.documentId, { visibility: options.visibility }, request);
2428
+ const removedViewers = removed.length;
2432
2429
  return {
2433
- ...result,
2430
+ status: "visibility_updated",
2431
+ document,
2432
+ removedViewers,
2434
2433
  message: document.visibility === "public" ? `${document.id} is public.${removedViewers ? ` Removed its ${viewerCount(removedViewers)}.` : ""} Anyone with the URL can read its current revision: ${document.url}` : `${document.id} is private. Only you and its ${document.sharedWith ? viewerCount(document.sharedWith) : "viewers"} can read it after signing in: ${document.url}`
2435
2434
  };
2436
2435
  });
@@ -2510,6 +2509,10 @@ const installNext = {
2510
2509
  command: "skills",
2511
2510
  args: ["install"]
2512
2511
  };
2512
+ const updateNext = {
2513
+ command: "update",
2514
+ args: []
2515
+ };
2513
2516
  /** The output of `node --version` from PATH, or undefined when it does not run. */
2514
2517
  const nodeOnPath = () => new Promise((resolve) => {
2515
2518
  execFile("node", ["--version"], {
@@ -2594,6 +2597,7 @@ async function replaceFolder(source, target) {
2594
2597
  }
2595
2598
  }
2596
2599
  const parseMajor = (version) => Number(/^v?(\d+)\./u.exec(version)?.[1] ?? NaN);
2600
+ /** Whether version `installed` is older than `cli`. An unreadable `installed` counts as older. */
2597
2601
  const older = (installed, cli) => {
2598
2602
  const parts = (version) => version.split(/[.+-]/u).slice(0, 3).map(Number);
2599
2603
  const [a, b] = [parts(installed), parts(cli)];
@@ -2617,7 +2621,7 @@ async function inspectCli(environment, signal) {
2617
2621
  return {
2618
2622
  cli: "older",
2619
2623
  latest,
2620
- message: `A newer Pageship CLI is published (${latest}; this is ${environment.version}). Update it with the package manager that installed it, then run ${commandLine(installNext.command, installNext.args)}.`
2624
+ message: `A newer Pageship CLI is published (${latest}; this is ${environment.version}). Run ${commandLine(updateNext.command, updateNext.args)} to update it, then run ${commandLine(installNext.command, installNext.args)}.`
2621
2625
  };
2622
2626
  }
2623
2627
  /** The recommended skills and Node that the CLI could not find. It installs none of them. */
@@ -2909,7 +2913,7 @@ function viewerList(documentId, viewers) {
2909
2913
  }
2910
2914
  /**
2911
2915
  * Lists a document's viewers, or changes them. Every removal is matched and every added username
2912
- * looked up before anything changes. Removals run first, then additions.
2916
+ * looked up first, then one sharing change applies them all or none.
2913
2917
  */
2914
2918
  function manageViewers(options) {
2915
2919
  const { documentId, add, remove } = options;
@@ -2927,13 +2931,11 @@ function manageViewers(options) {
2927
2931
  ...add.flatMap((value) => ["--add", value])
2928
2932
  ]
2929
2933
  };
2934
+ if (add.length > sharingLimits.add || remove.length > sharingLimits.remove) return {
2935
+ status: "usage_error",
2936
+ message: `Add up to ${sharingLimits.add} and remove up to ${sharingLimits.remove} viewers at a time. Nothing was changed.`
2937
+ };
2930
2938
  const progress = [];
2931
- const added = [];
2932
- const removed = [];
2933
- const changed = () => added.length || removed.length ? {
2934
- added,
2935
- removed
2936
- } : {};
2937
2939
  try {
2938
2940
  const { viewers } = await getDocumentViewers(origin, documentId, request);
2939
2941
  if (!add.length && !remove.length) return {
@@ -2965,38 +2967,39 @@ function manageViewers(options) {
2965
2967
  user,
2966
2968
  message: `${account(user)} is ${user.type === "organization" ? "an organization" : "not a personal account"}. Only a personal GitHub account can be a viewer. Nothing was changed.`
2967
2969
  };
2970
+ if (removals.some(({ githubAccountId }) => githubAccountId === user.githubAccountId)) return {
2971
+ status: "usage_error",
2972
+ message: `${account(user)} is both added and removed. Nothing was changed.`
2973
+ };
2968
2974
  progress.push(`Found ${account(user)}.`);
2969
2975
  if (!accounts.some(({ githubAccountId }) => githubAccountId === user.githubAccountId)) accounts.push(user);
2970
2976
  }
2971
- for (const viewer of removals) {
2972
- await removeDocumentViewer(origin, documentId, viewer.githubAccountId, request);
2973
- removed.push(viewer);
2974
- progress.push(`Removed ${account(viewer)}.`);
2975
- }
2976
- for (const user of accounts) {
2977
- const { viewer } = await addDocumentViewer(origin, documentId, user, request);
2978
- added.push(viewer);
2979
- progress.push(`Added ${account(viewer)}.`);
2980
- }
2981
- const current = await getDocumentViewers(origin, documentId, request);
2977
+ const { added, removed, ...shared } = await updateDocumentSharing(origin, documentId, {
2978
+ add: accounts.map(({ githubAccountId, login }) => ({
2979
+ githubAccountId,
2980
+ login
2981
+ })),
2982
+ remove: removals.map(({ githubAccountId }) => githubAccountId)
2983
+ }, request);
2982
2984
  return {
2983
2985
  status: "viewers_updated",
2984
2986
  documentId,
2985
2987
  added,
2986
2988
  removed,
2987
2989
  notViewers,
2988
- viewers: current.viewers,
2990
+ viewers: shared.viewers,
2989
2991
  message: [
2990
2992
  ...progress,
2993
+ ...removed.map((viewer) => `Removed ${account(viewer)}.`),
2994
+ ...added.map((viewer) => `Added ${account(viewer)}.`),
2991
2995
  ...notViewers.map((value) => `${value} is not a viewer of ${documentId}.`),
2992
- viewerList(documentId, current.viewers)
2996
+ viewerList(documentId, shared.viewers)
2993
2997
  ].join("\n")
2994
2998
  };
2995
2999
  } catch (error) {
2996
3000
  const prefix = (result) => ({
2997
3001
  ...result,
2998
3002
  documentId,
2999
- ...changed(),
3000
3003
  message: [...progress, result.message].join("\n")
3001
3004
  });
3002
3005
  if (isRefusal(error)) {
@@ -3013,20 +3016,182 @@ function manageViewers(options) {
3013
3016
  });
3014
3017
  return prefix(refusal(error, login));
3015
3018
  }
3016
- if (!added.length && !removed.length) throw error;
3017
3019
  return prefix(options.signal.aborted ? {
3018
3020
  status: "interrupted",
3019
3021
  message: `Operation interrupted. Run ${commandLine(next.command, next.args)} to finish.`,
3020
3022
  next
3021
3023
  } : {
3022
3024
  status: "request_failed",
3023
- message: `${error instanceof Error ? error.message : String(error)} Run ${commandLine(next.command, next.args)} to finish.`,
3025
+ message: `${error instanceof Error ? error.message : String(error)} Run ${commandLine(next.command, next.args)} to retry.`,
3024
3026
  next
3025
3027
  });
3026
3028
  }
3027
3029
  });
3028
3030
  }
3029
3031
  //#endregion
3032
+ //#region src/update.ts
3033
+ /**
3034
+ * The command that updates a global install with each manager. Keep in sync with the setup
3035
+ * guide. The package names an exact version where one is known: pnpm resolves `latest` to the
3036
+ * newest version older than its minimum release age, so a fresh release would not install.
3037
+ */
3038
+ const updateCommand$1 = (manager, spec) => ({
3039
+ npm: [
3040
+ "npm",
3041
+ "i",
3042
+ "-g",
3043
+ spec
3044
+ ],
3045
+ pnpm: [
3046
+ "pnpm",
3047
+ "add",
3048
+ "-g",
3049
+ spec
3050
+ ],
3051
+ bun: [
3052
+ "bun",
3053
+ "add",
3054
+ "-g",
3055
+ spec
3056
+ ],
3057
+ yarn: [
3058
+ "yarn",
3059
+ "global",
3060
+ "add",
3061
+ spec
3062
+ ],
3063
+ vp: [
3064
+ "vp",
3065
+ "install",
3066
+ "-g",
3067
+ spec
3068
+ ]
3069
+ })[manager];
3070
+ const npmRoot = (signal) => new Promise((resolve) => {
3071
+ exec("npm root -g", {
3072
+ timeout: 1e4,
3073
+ windowsHide: true,
3074
+ signal
3075
+ }, (error, stdout) => resolve(error ? void 0 : stdout.trim() || void 0));
3076
+ });
3077
+ /**
3078
+ * Run a command with the terminal attached, or, for `--json`, with its output collected so
3079
+ * standard output stays one JSON value.
3080
+ */
3081
+ const runCommand = (json) => (argv, signal) => new Promise((resolve, reject) => {
3082
+ const [command, ...args] = argv;
3083
+ if (!json) console.log(`Running ${argv.join(" ")}`);
3084
+ const stdio = json ? [
3085
+ "ignore",
3086
+ "pipe",
3087
+ "pipe"
3088
+ ] : "inherit";
3089
+ const child = process.platform === "win32" ? spawn(argv.join(" "), {
3090
+ stdio,
3091
+ shell: true,
3092
+ windowsHide: true
3093
+ }) : spawn(command, args, { stdio });
3094
+ const stop = () => child.kill("SIGTERM");
3095
+ signal.addEventListener("abort", stop, { once: true });
3096
+ const settle = () => signal.removeEventListener("abort", stop);
3097
+ let output = "";
3098
+ const collect = (chunk) => {
3099
+ output = (output + chunk.toString()).slice(-4e3);
3100
+ };
3101
+ child.stdout?.on("data", collect);
3102
+ child.stderr?.on("data", collect);
3103
+ child.once("error", (error) => {
3104
+ settle();
3105
+ reject(error);
3106
+ });
3107
+ child.once("close", (exitCode) => {
3108
+ settle();
3109
+ resolve({
3110
+ exitCode,
3111
+ output
3112
+ });
3113
+ });
3114
+ });
3115
+ /** A path with forward slashes and a trailing slash, compared without case on Windows. */
3116
+ function comparable(path, platform) {
3117
+ const slashed = `${path.replaceAll("\\", "/").replace(/\/+$/u, "")}/`;
3118
+ return platform === "win32" ? slashed.toLowerCase() : slashed;
3119
+ }
3120
+ /**
3121
+ * Which manager installed this CLI globally, from where its package is. Undefined for anything
3122
+ * else, such as a project dependency or an npx cache, which `update` must not replace.
3123
+ */
3124
+ async function detectManager(environment, signal) {
3125
+ const { platform, env } = environment;
3126
+ const root = comparable(environment.packageRoot, platform);
3127
+ const installed = (folders, layout) => folders.some((folder) => {
3128
+ if (!folder) return false;
3129
+ const base = comparable(folder, platform);
3130
+ const index = root.lastIndexOf(base);
3131
+ return index !== -1 && layout.test(root.slice(index + base.length));
3132
+ });
3133
+ if (installed(["/vite-plus/packages/"], /^pageship\/[^/]+\/(?:lib\/)?node_modules\/pageship\/$/u)) return "vp";
3134
+ if (installed(["/pnpm/global/", env.PNPM_HOME && join(env.PNPM_HOME, "global")], /^[^/]+\/(?:[^/]+\/)?node_modules\/\.pnpm\/pageship@[^/]+\/node_modules\/pageship\/$/u)) return "pnpm";
3135
+ if (installed(["/.bun/", env.BUN_INSTALL], /^install\/global\/node_modules\/pageship\/$/u) || installed([env.BUN_INSTALL_GLOBAL_DIR], /^node_modules\/pageship\/$/u)) return "bun";
3136
+ if (installed(["/yarn/global/", "/yarn/data/global/"], /^node_modules\/pageship\/$/u)) return "yarn";
3137
+ const npm = await environment.npmRoot(signal);
3138
+ const parent = root.slice(0, root.lastIndexOf("/", root.length - 2) + 1);
3139
+ if (npm !== void 0 && parent === comparable(npm, platform)) return "npm";
3140
+ }
3141
+ /**
3142
+ * Update this CLI to the version npm publishes as latest, with the package manager that
3143
+ * installed it. It changes nothing when this CLI is already the latest.
3144
+ */
3145
+ async function update(environment, signal) {
3146
+ const { version } = environment;
3147
+ const latest = await environment.publishedVersion(signal);
3148
+ if (latest !== void 0 && !older(version, latest)) return {
3149
+ status: "cli_current",
3150
+ message: `Pageship ${version} is the latest version.`,
3151
+ version,
3152
+ latest
3153
+ };
3154
+ const manager = await detectManager(environment, signal);
3155
+ signal.throwIfAborted();
3156
+ if (manager === void 0) return {
3157
+ status: "install_unknown",
3158
+ message: `This Pageship CLI, in ${environment.packageRoot}, was not installed globally by npm, pnpm, bun, Yarn or Vite+, so update cannot tell how to replace it. Update it the way you installed it.`,
3159
+ version,
3160
+ ...latest === void 0 ? {} : { latest },
3161
+ path: environment.packageRoot
3162
+ };
3163
+ const argv = updateCommand$1(manager, `pageship@${latest ?? "latest"}`);
3164
+ const run = argv.join(" ");
3165
+ const ran = await environment.run(argv, signal).catch((error) => {
3166
+ if (signal.aborted) throw error;
3167
+ return {
3168
+ exitCode: null,
3169
+ output: error instanceof Error ? error.message : String(error)
3170
+ };
3171
+ });
3172
+ signal.throwIfAborted();
3173
+ if (ran.exitCode !== 0) return {
3174
+ status: "update_failed",
3175
+ message: [ran.exitCode === null ? `Could not run ${run}.` : `${run} failed with exit code ${ran.exitCode}.`, ran.output.trim()].filter(Boolean).join("\n"),
3176
+ version,
3177
+ ...latest === void 0 ? {} : { latest },
3178
+ manager,
3179
+ run,
3180
+ exitCode: ran.exitCode,
3181
+ output: ran.output
3182
+ };
3183
+ const skills = await stat(join(environment.home, ".agents", "skills", "pageship")).then(() => true, () => false);
3184
+ return {
3185
+ status: "cli_updated",
3186
+ message: [latest === void 0 ? `Updated Pageship from ${version} with ${run}.` : `Updated Pageship from ${version} to ${latest} with ${run}.`, ...skills ? [`Run ${commandLine(installNext.command, installNext.args)} to update the Pageship skills to match.`] : []].join("\n"),
3187
+ version,
3188
+ ...latest === void 0 ? {} : { latest },
3189
+ manager,
3190
+ run,
3191
+ ...skills ? { next: installNext } : {}
3192
+ };
3193
+ }
3194
+ //#endregion
3030
3195
  //#region src/cli.ts
3031
3196
  const args = process.argv.slice(2);
3032
3197
  const separator = args.indexOf("--");
@@ -3093,6 +3258,13 @@ const skills = Command.make("skills").pipe(Command.withDescription("Manage the P
3093
3258
  force,
3094
3259
  signal
3095
3260
  }))).pipe(Command.withDescription("Install the Pageship skills in ~/.agents/skills, and link them in ~/.claude/skills when ~/.claude exists."))]));
3261
+ const updateCommand = Command.make("update", {}, () => authCommand("update", (signal) => update({
3262
+ ...skillsEnvironment,
3263
+ packageRoot: dirname(dirname(fileURLToPath(import.meta.url))),
3264
+ env: process.env,
3265
+ npmRoot,
3266
+ run: runCommand(json)
3267
+ }, signal))).pipe(Command.withDescription("Update this CLI to the latest version with the package manager that installed it: npm, pnpm, bun, Yarn or Vite+."));
3096
3268
  const login = Command.make("login", {
3097
3269
  complete: Flag.boolean("complete").pipe(Flag.withDefault(false), Flag.withDescription("Check a pending authorization once, without waiting.")),
3098
3270
  restart: Flag.boolean("restart").pipe(Flag.withDefault(false), Flag.withDescription("Start a new authorization code.")),
@@ -3253,7 +3425,7 @@ function authCommand(name, operation) {
3253
3425
  settle(name, result);
3254
3426
  if (json) return;
3255
3427
  if (thrown) {
3256
- console.error(`${name === "status" ? "Could not check status" : name === "skills" ? "Could not install the Pageship skills" : "Pageship request failed"}: ${result.message}`);
3428
+ console.error(`${name === "status" ? "Could not check status" : name === "skills" ? "Could not install the Pageship skills" : name === "update" ? "Could not update Pageship" : "Pageship request failed"}: ${result.message}`);
3257
3429
  return;
3258
3430
  }
3259
3431
  console.log(result.message);
@@ -3264,7 +3436,7 @@ function authCommand(name, operation) {
3264
3436
  }
3265
3437
  });
3266
3438
  }
3267
- const command = Command.make("pageship").pipe(Command.withDescription("Pageship publishes private HTML documents on the hosted Pageship service.\n\nEarly development: check whether this installation is ready with the status command. Authorize this installation with login, then publish one HTML file with publish. Manage published documents with list, history, download, visibility, viewers and restore. Install the Pageship skills for your agents with skills install."), Command.withSubcommands([
3439
+ const command = Command.make("pageship").pipe(Command.withDescription("Pageship publishes private HTML documents on the hosted Pageship service.\n\nEarly development: check whether this installation is ready with the status command. Authorize this installation with login, then publish one HTML file with publish. Manage published documents with list, history, download, visibility, viewers and restore. Install the Pageship skills for your agents with skills install, and update the CLI with update."), Command.withSubcommands([
3268
3440
  status,
3269
3441
  login,
3270
3442
  whoami,
@@ -3275,7 +3447,8 @@ const command = Command.make("pageship").pipe(Command.withDescription("Pageship
3275
3447
  visibility,
3276
3448
  viewers,
3277
3449
  restore,
3278
- skills
3450
+ skills,
3451
+ updateCommand
3279
3452
  ]), Command.withGlobalFlags([jsonFlag]));
3280
3453
  const effectiveArgs = args.every((arg, index) => arg === "--json" || arg.startsWith("--json=") && booleanValue(arg.slice(7)) !== void 0 || index > 0 && args[index - 1] === "--json" && booleanValue(arg) !== void 0) ? [...args, "--help"] : args;
3281
3454
  const program = Command.runWith(command, {
@@ -3,7 +3,7 @@ name: pageship
3
3
  description: Publish an HTML file to Pageship and manage what is published. Use when the user asks to publish, share or get a link to a document, update its published copy, change its visibility or viewers, list, inspect history, download or restore published documents, or sign in to Pageship. To write, edit, rename or move a document, load pageship-docs instead.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Pageship
@@ -33,6 +33,7 @@ With `--json`, the CLI prints one JSON object on one line. Parse it before you d
33
33
  - `next` is one follow-up command. `resolutions` holds named alternatives for a decision, each a follow-up command or a list of them.
34
34
  - Other fields depend on the status, such as `document`, `candidates`, `choices`, `user` or `idempotencyKey`.
35
35
  - `skills install` returns `skills_installed` or `skills_modified`, as described in [Install the Pageship skills](#install-the-pageship-skills).
36
+ - `update` returns `cli_updated`, `cli_current`, `install_unknown` or `update_failed`, as described in [Update the CLI](#update-the-cli).
36
37
 
37
38
  A follow-up has a `command` and an `args` array. Run it as `pageship --json <command> <args...>` with `args` unchanged. They already carry the server, target document, expected revision and retry key, so never rebuild them from memory.
38
39
 
@@ -50,7 +51,17 @@ pageship --json status
50
51
 
51
52
  Status only reads; it never signs in, claims an address or uploads. On `admitted`, sign-in and the workspace address are set, unless `next` is a `login`: that comes from an older server that does not report the address, and running it confirms the address. A `next` of `skills install` on `admitted` is about the skills, below. On `signed_out` or `address_required`, sign in as described below. On `uninvited`, report the `message`: the user must ask the server owner for an invitation, or sign in with another account using `login --restart`. On `unreachable`, `client_too_old`, `server_too_old`, `unsupported` or `configuration_unreadable`, report the `message`; signing in does not fix these. A ready status does not prove that a publish will succeed.
52
53
 
53
- Status also reports the Pageship skills in `skills`: `missing`, `current` or `older` than the CLI. They never block publishing, and a status that is otherwise ready still succeeds. When `skills` is `missing` or `older`, tell the user, and install them as below only if they agree. Never run a `skills install` from `next` without asking. When `cli` is `older`, a newer CLI is published: tell the user, and offer to update it with the package manager that installed it before installing the skills.
54
+ Status also reports the Pageship skills in `skills`: `missing`, `current` or `older` than the CLI. They never block publishing, and a status that is otherwise ready still succeeds. When `skills` is `missing` or `older`, tell the user, and install them as below only if they agree. Never run a `skills install` from `next` without asking. When `cli` is `older`, a newer CLI is published: tell the user, and offer to update it, as below, before installing the skills.
55
+
56
+ ## Update the CLI
57
+
58
+ With the user's agreement, update the CLI with the package manager that installed it:
59
+
60
+ ```sh
61
+ pageship --json update
62
+ ```
63
+
64
+ On `cli_updated`, the new version is installed. Its `next` is a `skills install`, present when the Pageship skills are installed, since they still match the old version: install them as below if the user agrees. On `cli_current`, the CLI is already the latest. On `install_unknown`, no supported package manager installed it globally, so nothing ran; tell the user to update it the way they installed it. On `update_failed`, report the `message`, which carries the package manager's error.
54
65
 
55
66
  ## Install the Pageship skills
56
67
 
@@ -12,7 +12,7 @@ Set `PAGESHIP_DOCUMENT` to the document's `id`, then add each named username wit
12
12
  pageship --json viewers "$PAGESHIP_DOCUMENT" --add octocat --add hubot
13
13
  ```
14
14
 
15
- The CLI looks each username up on GitHub before it changes anything. `added` holds each account it added, with `login`, `name` and `githubAccountId`, on `viewers_updated` and on a failure partway through. Whatever the status, show the user every account in `added` by `login` and `name`, and ask them to confirm each is the person they meant. A username can belong to someone else. If one is wrong, remove it as below. Then give the user the document's `url` to send: Pageship sends no invitations.
15
+ The CLI looks each username up on GitHub before it changes anything. On `viewers_updated`, `added` holds each account it added, with `login`, `name` and `githubAccountId`. Show the user every account in `added` by `login` and `name`, and ask them to confirm each is the person they meant. A username can belong to someone else. If one is wrong, remove it as below. Then give the user the document's `url` to send: Pageship sends no invitations.
16
16
 
17
17
  ## List and remove viewers
18
18
 
@@ -40,4 +40,4 @@ pageship --json viewers "$PAGESHIP_DOCUMENT" --remove octocat
40
40
  - `github_sign_in_required`: GitHub refused the sign-in Pageship uses to look up users. Ask the user to sign in to the Pageship dashboard again with GitHub, then run the returned `next`. A CLI `login` doesn't fix this.
41
41
  - `github_rate_limited` or `github_unavailable`: wait a minute, then run the returned `next`.
42
42
 
43
- A failure partway through keeps the changes already made, listed in `added` and `removed`. Its `next` repeats the command, which finishes the rest without adding or removing anyone twice.
43
+ The command adds and removes everyone together or no one. On any failure, nothing changed. Its `next` repeats the whole command. It takes up to 20 `--add` and 50 `--remove` values; split a longer list across several commands.
@@ -3,7 +3,7 @@ name: pageship-chart
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for charts in a Pageship HTML document, loaded by a pageship- kind skill before it draws a chart."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Chart
@@ -3,7 +3,7 @@ name: pageship-code-block
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for code in a Pageship HTML document, such as source code, commands, configuration or logs, loaded by a pageship- kind skill before the first code block."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Code block
@@ -3,7 +3,7 @@ name: pageship-decision-record
3
3
  description: HTML decision records, as the second step of pageship-docs. For any request to record, write, regenerate, edit or move a decision, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Decision record
@@ -3,7 +3,7 @@ name: pageship-diagram
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for diagrams in a Pageship HTML document, such as a process, a timeline, a system map or states, loaded by a pageship- kind skill before it draws a diagram."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Diagram
@@ -3,7 +3,7 @@ name: pageship-docs
3
3
  description: First step for any document people read, such as a report, plan, spec, explainer, review, decision record or slide deck, in HTML or Markdown. Load it before any other pageship- skill. Use when the user asks for one of these documents, asks to regenerate, redesign, edit, rename, move or copy an HTML source file, or asks to record whether they want HTML or Markdown.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Pageship documents
@@ -12,7 +12,7 @@ Run the linter, html-validate with the config and rules in this folder:
12
12
  npx --yes html-validate@11.17.0 --config <pageship-docs folder>/references/html-validate.json <file>
13
13
  ```
14
14
 
15
- It reports HTML errors, accessibility problems such as a missing `lang` or `alt` or a skipped heading level, and breaks of three rules: [one standalone file](file.md#one-standalone-file) and [trusted libraries only](file.md#trusted-libraries-only) in the file rules, and **Height** in [the design rules](design.md#base-rules-every-file-meets). Fix each problem it reports and run it again. Done when it reports nothing. When `npx` can't run, such as without Node or network, check the file against those three rules by reading it, and say in the hand-over that the linter didn't run.
15
+ It reports HTML errors, accessibility problems such as a missing `lang` or `alt` or a skipped heading level, and breaks of four rules: [one standalone file](file.md#one-standalone-file), [trusted libraries only](file.md#trusted-libraries-only) and [link previews](file.md#link-previews) in the file rules, and **Height** in [the design rules](design.md#base-rules-every-file-meets). Fix each problem it reports and run it again. Done when it reports nothing. When `npx` can't run, such as without Node or network, check the file against those four rules by reading it, and say in the hand-over that the linter didn't run.
16
16
 
17
17
  Then:
18
18
 
@@ -51,7 +51,7 @@ Then use every control by keyboard and confirm it works and shows focus. Done wh
51
51
 
52
52
  ## Claims
53
53
 
54
- Read the file sentence by sentence beside the source, captions, labels and tooltips included. Keep each claim the source states. A claim one step past it goes, or becomes an open question: a cause, when something starts or ends, a count, how far a rule reaches, or a new name for something the source names. A caption claims only what its figure draws. Done when you can point to the source's words behind every sentence.
54
+ Read the file sentence by sentence beside the source, captions, labels, tooltips and the link preview's description included. Keep each claim the source states. A claim one step past it goes, or becomes an open question: a cause, when something starts or ends, a count, how far a rule reaches, or a new name for something the source names. A caption claims only what its figure draws. Done when you can point to the source's words behind every sentence.
55
55
 
56
56
  ## The structure you added
57
57
 
@@ -32,6 +32,39 @@ Mermaid, only when a pattern skill calls for it:
32
32
 
33
33
  Hosted fonts from `https://fonts.googleapis.com/css2?...` through a `<link rel="stylesheet">`, which can't carry `integrity`.
34
34
 
35
+ ## Link previews
36
+
37
+ Chat apps and social sites show a card for a shared link, built from tags in the file's `<head>`. Every file carries them, after `<meta charset>`, the viewport and `<title>`:
38
+
39
+ ```html
40
+ <title>Q3 support report</title>
41
+ <meta
42
+ name="description"
43
+ content="Ticket volume rose 18% in Q3 while first-reply time held at 2 hours."
44
+ />
45
+ <meta property="og:type" content="article" />
46
+ <meta property="og:title" content="Q3 support report" />
47
+ <meta
48
+ property="og:description"
49
+ content="Ticket volume rose 18% in Q3 while first-reply time held at 2 hours."
50
+ />
51
+ <meta property="og:image" content="https://example.com/q3/tickets-by-week.png" />
52
+ <meta property="og:image:alt" content="Tickets by week, July to September" />
53
+ <meta property="og:image:width" content="1200" />
54
+ <meta property="og:image:height" content="630" />
55
+ <meta name="twitter:card" content="summary_large_image" />
56
+ <meta name="twitter:image:alt" content="Tickets by week, July to September" />
57
+ ```
58
+
59
+ - **Title.** `<title>` and `og:title` hold the same text: the document's title, as its `h1` gives it.
60
+ - **Description.** `description` and `og:description` hold the same one or two sentences, up to about 160 characters, that state the document's main point as its first lines do. It follows the [content rules](content.md) like any other text in the file.
61
+ - **Type.** `og:type` is `article`.
62
+ - **Image.** The card's image is the first of these that is a PNG, JPEG or GIF file at an `https://` URL: the image or chart that leads the page, then the first image in the page with alt text. Its `alt` becomes `og:image:alt` and `twitter:image:alt`. Its size in pixels, when you measured the image file itself, becomes `og:image:width` and `og:image:height`; a size the page displays it at is not that size, so without a measured size leave those two out. Sites can't show an inline SVG or a `data:` URL, so a page whose images and charts are only those leaves out every image tag.
63
+ - **Card.** `twitter:card` is `summary_large_image` with an image and `summary` without. X reads the title, description and image from the `og:` tags, so the only other `twitter:` tag is `twitter:image:alt`.
64
+ - **No address.** Leave out `og:url` and `<link rel="canonical">`: the file doesn't know where it will be published, and sites use the address they fetched.
65
+
66
+ An edit that changes the title, the main point or the image updates these tags to match.
67
+
35
68
  ## The Pageship sandbox
36
69
 
37
70
  A published file runs with scripts allowed and everything else blocked, so it works without these:
@@ -316,11 +316,105 @@ class NoViewportHeight extends Rule {
316
316
  }
317
317
  }
318
318
 
319
+ const entities = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " " };
320
+
321
+ /** Text with its character references decoded and its runs of white space as single spaces. */
322
+ const plainText = (text) =>
323
+ text
324
+ .replace(/&(?:#(\d+)|#x([\da-f]+)|(\w+));/giu, (match, decimal, hex, name) =>
325
+ decimal || hex
326
+ ? String.fromCodePoint(Number.parseInt(decimal ?? hex, decimal ? 10 : 16))
327
+ : (entities[name.toLowerCase()] ?? match),
328
+ )
329
+ .replace(/\s+/gu, " ")
330
+ .trim();
331
+
332
+ /** The tags a link preview reads. Others, such as one `theme-color` per colour scheme, may repeat. */
333
+ const previewTags = /^(?:description|og:.+|twitter:.+)$/u;
334
+
335
+ /** An `https://` URL whose path doesn't end in `.svg`, which link previews don't show. */
336
+ const previewImage = /^https:\/\/(?![^?#]*\.svg(?:[?#]|$))/iu;
337
+
338
+ class LinkPreview extends Rule {
339
+ documentation() {
340
+ return {
341
+ description:
342
+ "Give the file the link preview tags in the file rules (references/file.md): a description, og:type, og:title and og:description matching the title and description, twitter:card, and an og:image only at an https:// URL to an image that isn't SVG, with og:image:alt and twitter:image:alt.",
343
+ };
344
+ }
345
+
346
+ setup() {
347
+ this.on("dom:ready", ({ document }) => {
348
+ const head = document.querySelector("head");
349
+ if (!head) return;
350
+ /** Each `<meta>` content by its `name` or `property`, with the element that has it. */
351
+ const metas = new Map();
352
+ for (const element of document.querySelectorAll("meta")) {
353
+ const key = attributeValue(element, "property") ?? attributeValue(element, "name");
354
+ if (!previewTags.test(key ?? "")) continue;
355
+ if (metas.has(key)) {
356
+ this.report(element, `The file has a second ${key} tag: keep one`);
357
+ continue;
358
+ }
359
+ metas.set(key, { element, content: plainText(attributeValue(element, "content") ?? "") });
360
+ }
361
+ const content = (key) => metas.get(key)?.content ?? "";
362
+ const at = (key) => metas.get(key)?.element ?? head;
363
+
364
+ for (const key of ["description", "og:type", "og:title", "og:description", "twitter:card"]) {
365
+ if (!content(key)) {
366
+ this.report(
367
+ at(key),
368
+ `The file needs the ${key} tag with content, as the file rules give`,
369
+ );
370
+ }
371
+ }
372
+ const title = document.querySelector("title");
373
+ if (title && content("og:title") && content("og:title") !== plainText(title.textContent)) {
374
+ this.report(at("og:title"), "og:title must hold the same text as <title>");
375
+ }
376
+ if (content("og:description") && content("og:description") !== content("description")) {
377
+ this.report(at("og:description"), "og:description must hold the same text as description");
378
+ }
379
+
380
+ const image = content("og:image");
381
+ if (metas.has("og:image") && !image) {
382
+ this.report(
383
+ at("og:image"),
384
+ "og:image is empty: give the image's URL, or leave out the image tags",
385
+ );
386
+ }
387
+ if (image && !previewImage.test(image)) {
388
+ this.report(
389
+ at("og:image"),
390
+ `og:image "${image}" is not an https:// URL to an image a link preview shows, such as a PNG or JPEG file: use one, or leave out the image tags`,
391
+ );
392
+ }
393
+ for (const key of ["og:image:alt", "twitter:image:alt"]) {
394
+ if (image && !content(key)) {
395
+ this.report(at("og:image"), `og:image needs ${key}, the image's alt text`);
396
+ }
397
+ }
398
+ if (!image && (metas.has("og:image:alt") || metas.has("twitter:image:alt"))) {
399
+ this.report(head, "The file has image alt tags but no og:image: leave them out too");
400
+ }
401
+ const card = image ? "summary_large_image" : "summary";
402
+ if (content("twitter:card") && content("twitter:card") !== card) {
403
+ this.report(
404
+ at("twitter:card"),
405
+ `twitter:card must be "${card}" for a file ${image ? "with" : "without"} og:image`,
406
+ );
407
+ }
408
+ });
409
+ }
410
+ }
411
+
319
412
  export default {
320
413
  name: "pageship",
321
414
  rules: {
322
415
  "pageship/trusted-libraries": TrustedLibraries,
323
416
  "pageship/no-relative-paths": NoRelativePaths,
324
417
  "pageship/no-viewport-height": NoViewportHeight,
418
+ "pageship/link-preview": LinkPreview,
325
419
  },
326
420
  };
@@ -8,6 +8,7 @@
8
8
  "heading-level": ["error", { "allowMultipleH1": false }],
9
9
  "pageship/trusted-libraries": "error",
10
10
  "pageship/no-relative-paths": "error",
11
- "pageship/no-viewport-height": "error"
11
+ "pageship/no-viewport-height": "error",
12
+ "pageship/link-preview": "error"
12
13
  }
13
14
  }
@@ -3,7 +3,7 @@ name: pageship-explainer
3
3
  description: HTML explainers, as the second step of pageship-docs. For any request to explain how something works, or to write, regenerate, edit or move an explainer, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Explainer
@@ -3,7 +3,7 @@ name: pageship-interaction
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for controls in a Pageship HTML document, such as stepping through stages, tracing a path, tabs, filters, revealing detail and pointer states on charts and diagrams, loaded by a pageship- kind skill before it adds any control."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Interaction
@@ -3,7 +3,7 @@ name: pageship-key-figures
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for the few numbers and callouts a Pageship HTML document opens with, loaded by a pageship- kind skill before it writes key figures."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Key figures
@@ -3,7 +3,7 @@ name: pageship-media
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for images, video and audio in a Pageship HTML document, loaded by a pageship- kind skill before it shows media."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Media
@@ -3,7 +3,7 @@ name: pageship-motion
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for animation and transitions in a Pageship HTML document, such as a chart that grows into view, a sequence that plays through a diagram, or a marker moving along a path, loaded by a pageship- kind skill before it draws a chart, a graphic or a sequence, or before anything else on the page moves."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Motion
@@ -3,7 +3,7 @@ name: pageship-plan
3
3
  description: HTML plans, as the second step of pageship-docs. For any request to write, regenerate, edit or move a plan, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Plan
@@ -3,7 +3,7 @@ name: pageship-report
3
3
  description: HTML reports, as the second step of pageship-docs. For any request to write, regenerate, edit or move a report, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Report
@@ -3,7 +3,7 @@ name: pageship-review-report
3
3
  description: HTML review reports, as the second step of pageship-docs. For any request to review a change or a plan and write up the findings, or to write, regenerate, edit or move a review report, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Review report
@@ -3,7 +3,7 @@ name: pageship-slide-deck
3
3
  description: HTML slide decks, as the second step of pageship-docs. For any request to make slides or a presentation, or to regenerate, edit or move a deck, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Slide deck
@@ -3,7 +3,7 @@ name: pageship-spec
3
3
  description: HTML specs, as the second step of pageship-docs. For any request to write, regenerate, edit or move a spec, load pageship-docs first; load this skill first only when the user names it.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Spec
@@ -3,7 +3,7 @@ name: pageship-table
3
3
  description: "Later step of pageship-docs: for a request to write or edit a document, load pageship-docs first; load this skill first only when the user names it. Pattern for tables in a Pageship HTML document, loaded by a pageship- kind skill before it writes a table."
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.5.0"
6
+ version: "0.6.1"
7
7
  ---
8
8
 
9
9
  # Table
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pageship",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Pageship CLI for checking server API compatibility.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {