pageship 0.4.1 → 0.6.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 +1 -1
- package/dist/cli.mjs +486 -46
- package/dist/skills/pageship/SKILL.md +21 -5
- package/dist/skills/pageship/references/viewers.md +43 -0
- package/dist/skills/pageship/references/visibility.md +2 -2
- package/dist/skills/pageship-chart/SKILL.md +2 -2
- package/dist/skills/pageship-code-block/SKILL.md +2 -2
- package/dist/skills/pageship-decision-record/SKILL.md +1 -1
- package/dist/skills/pageship-diagram/SKILL.md +2 -2
- package/dist/skills/pageship-docs/SKILL.md +1 -1
- package/dist/skills/pageship-explainer/SKILL.md +1 -1
- package/dist/skills/pageship-interaction/SKILL.md +2 -2
- package/dist/skills/pageship-key-figures/SKILL.md +2 -2
- package/dist/skills/pageship-media/SKILL.md +2 -2
- package/dist/skills/pageship-motion/SKILL.md +2 -2
- package/dist/skills/pageship-plan/SKILL.md +1 -1
- package/dist/skills/pageship-report/SKILL.md +1 -1
- package/dist/skills/pageship-review-report/SKILL.md +1 -1
- package/dist/skills/pageship-slide-deck/SKILL.md +1 -1
- package/dist/skills/pageship-spec/SKILL.md +1 -1
- package/dist/skills/pageship-table/SKILL.md +2 -2
- package/package.json +1 -1
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,14 +675,22 @@ const documentsRestore = (options) => (options.client ?? client).post({
|
|
|
675
675
|
...options.headers
|
|
676
676
|
}
|
|
677
677
|
});
|
|
678
|
-
const
|
|
679
|
-
url: "/api/v1/documents/{id}/
|
|
678
|
+
const viewersList = (options) => (options.client ?? client).get({
|
|
679
|
+
url: "/api/v1/documents/{id}/viewers",
|
|
680
|
+
...options
|
|
681
|
+
});
|
|
682
|
+
const viewersUpdateSharing = (options) => (options.client ?? client).patch({
|
|
683
|
+
url: "/api/v1/documents/{id}/sharing",
|
|
680
684
|
...options,
|
|
681
685
|
headers: {
|
|
682
686
|
"Content-Type": "application/json",
|
|
683
687
|
...options.headers
|
|
684
688
|
}
|
|
685
689
|
});
|
|
690
|
+
const githubGetUser = (options) => (options.client ?? client).get({
|
|
691
|
+
url: "/api/v1/github/users/{login}",
|
|
692
|
+
...options
|
|
693
|
+
});
|
|
686
694
|
//#endregion
|
|
687
695
|
//#region ../../packages/api-client/src/generated/workspace-domain.gen.ts
|
|
688
696
|
/** Every workspace address and document URL is a subdomain of this domain. */
|
|
@@ -760,14 +768,16 @@ function validTitle(value) {
|
|
|
760
768
|
}
|
|
761
769
|
function normalizeDocument(value) {
|
|
762
770
|
if (!record(value) || typeof value.slug !== "string") return;
|
|
763
|
-
const normalized =
|
|
771
|
+
const normalized = {
|
|
764
772
|
...value,
|
|
765
|
-
title: value.slug
|
|
766
|
-
|
|
773
|
+
...value.title === void 0 ? { title: value.slug } : {},
|
|
774
|
+
...value.viewCount === void 0 ? { viewCount: 0 } : {},
|
|
775
|
+
...value.sharedWith === void 0 ? { sharedWith: 0 } : {}
|
|
776
|
+
};
|
|
767
777
|
return validDocument(normalized) ? normalized : void 0;
|
|
768
778
|
}
|
|
769
779
|
function validDocument(value) {
|
|
770
|
-
return record(value) && isDocumentId(value.id) && typeof value.slug === "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*(?![\s\S])/u.test(value.slug) && validTitle(value.title) && typeof value.sourcePath === "string" && value.sourcePath.length <= 1024 && /^(?!\/)(?!.*(?:^|\/)\.{1,2}(?:\/|$))(?!.*\/\/)[^\x00-\x1f\x7f\\:]*\.[hH][tT][mM][lL]?(?![\s\S])/u.test(value.sourcePath) && (value.repository === null || isRepository(value.repository)) && (value.visibility === "private" || value.visibility === "public") && isRevisionId(value.revisionId) && typeof value.byteLength === "number" && Number.isSafeInteger(value.byteLength) && value.byteLength > 0 && typeof value.createdAt === "number" && Number.isSafeInteger(value.createdAt) && value.createdAt >= 0 && typeof value.publishedAt === "number" && Number.isSafeInteger(value.publishedAt) && value.publishedAt >= 0 && typeof value.url === "string" && new RegExp(`^https://[a-z0-9-]+\\.${workspaceDomainPattern}/d/${value.id}-${value.slug}$`, "u").test(value.url);
|
|
780
|
+
return record(value) && isDocumentId(value.id) && typeof value.slug === "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*(?![\s\S])/u.test(value.slug) && validTitle(value.title) && typeof value.sourcePath === "string" && value.sourcePath.length <= 1024 && /^(?!\/)(?!.*(?:^|\/)\.{1,2}(?:\/|$))(?!.*\/\/)[^\x00-\x1f\x7f\\:]*\.[hH][tT][mM][lL]?(?![\s\S])/u.test(value.sourcePath) && (value.repository === null || isRepository(value.repository)) && (value.visibility === "private" || value.visibility === "public") && isRevisionId(value.revisionId) && typeof value.byteLength === "number" && Number.isSafeInteger(value.byteLength) && value.byteLength > 0 && typeof value.createdAt === "number" && Number.isSafeInteger(value.createdAt) && value.createdAt >= 0 && typeof value.publishedAt === "number" && Number.isSafeInteger(value.publishedAt) && value.publishedAt >= 0 && typeof value.viewCount === "number" && Number.isSafeInteger(value.viewCount) && value.viewCount >= 0 && typeof value.sharedWith === "number" && Number.isSafeInteger(value.sharedWith) && value.sharedWith >= 0 && typeof value.url === "string" && new RegExp(`^https://[a-z0-9-]+\\.${workspaceDomainPattern}/d/${value.id}-${value.slug}$`, "u").test(value.url);
|
|
771
781
|
}
|
|
772
782
|
function validRevision(value, documentId) {
|
|
773
783
|
return record(value) && isRevisionId(value.id) && typeof value.createdAt === "number" && Number.isSafeInteger(value.createdAt) && value.createdAt >= 0 && typeof value.byteLength === "number" && Number.isSafeInteger(value.byteLength) && value.byteLength > 0 && typeof value.isCurrent === "boolean" && (value.cliVersion === null || isCliVersion(value.cliVersion)) && typeof value.url === "string" && new RegExp(`^https://[a-z0-9-]+\\.${workspaceDomainPattern}/d/${documentId}-[a-z0-9]+(?:-[a-z0-9]+)*/revisions/${value.id}$`, "u").test(value.url);
|
|
@@ -775,10 +785,12 @@ function validRevision(value, documentId) {
|
|
|
775
785
|
var DocumentRequestError = class extends Error {
|
|
776
786
|
status;
|
|
777
787
|
httpStatus;
|
|
778
|
-
|
|
788
|
+
account;
|
|
789
|
+
constructor(status, message, httpStatus, account) {
|
|
779
790
|
super(message);
|
|
780
791
|
this.status = status;
|
|
781
792
|
this.httpStatus = httpStatus;
|
|
793
|
+
this.account = account;
|
|
782
794
|
}
|
|
783
795
|
};
|
|
784
796
|
function failed(response, error) {
|
|
@@ -899,26 +911,6 @@ async function restoreDocumentRevision(baseUrl, id, input, options = {}) {
|
|
|
899
911
|
if (record(data) && data.status === "idempotency_conflict") return { status: "idempotency_conflict" };
|
|
900
912
|
throw new Error("Invalid document restoration response. Retry with the same idempotency key.");
|
|
901
913
|
}
|
|
902
|
-
async function updateDocumentVisibility(baseUrl, id, visibility, options = {}) {
|
|
903
|
-
const { data: received, response, error } = await documentsUpdateVisibility({
|
|
904
|
-
...changeOptions(baseUrl, options),
|
|
905
|
-
path: { id },
|
|
906
|
-
body: { visibility }
|
|
907
|
-
});
|
|
908
|
-
if (!response?.ok) {
|
|
909
|
-
if (response?.status === 400) throw new DocumentRequestError("invalid_visibility", "Choose private or public.", 400);
|
|
910
|
-
if (response?.status === 401) throw new DocumentRequestError("signed_out", "Sign in to update this document.", 401);
|
|
911
|
-
if (response?.status === 403) throw new DocumentRequestError("uninvited", "Access unavailable.", 403);
|
|
912
|
-
if (response?.status === 404) throw new DocumentRequestError("unavailable", "Document unavailable.", 404);
|
|
913
|
-
throw new Error("Could not update document visibility. Try again.", { cause: error });
|
|
914
|
-
}
|
|
915
|
-
const data = received;
|
|
916
|
-
if (record(data) && data.status === "visibility_updated" && validDocument(data.document) && data.document.visibility === visibility) return {
|
|
917
|
-
status: "visibility_updated",
|
|
918
|
-
document: data.document
|
|
919
|
-
};
|
|
920
|
-
throw new Error("Invalid document visibility response.");
|
|
921
|
-
}
|
|
922
914
|
async function publishDocument(baseUrl, input, options = {}) {
|
|
923
915
|
if (input.action) {
|
|
924
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);
|
|
@@ -957,6 +949,96 @@ async function publishDocument(baseUrl, input, options = {}) {
|
|
|
957
949
|
if (record(data) && (data.status === "idempotency_conflict" || data.status === "address_required" || data.status === "unavailable" || data.status === "document_id_taken")) return { status: data.status };
|
|
958
950
|
throw new Error("Invalid publication response. Retry with the same idempotency key.");
|
|
959
951
|
}
|
|
952
|
+
/** Same GitHub username grammar as the Effect contract. */
|
|
953
|
+
function isGitHubLogin(value) {
|
|
954
|
+
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9-]{0,38}(?![\s\S])/u.test(value);
|
|
955
|
+
}
|
|
956
|
+
function isGitHubAccountId(value) {
|
|
957
|
+
return typeof value === "string" && /^[1-9][0-9]{0,19}(?![\s\S])/u.test(value);
|
|
958
|
+
}
|
|
959
|
+
function validViewer(value) {
|
|
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;
|
|
961
|
+
}
|
|
962
|
+
function validUserMatch(value) {
|
|
963
|
+
return record(value) && isGitHubAccountId(value.githubAccountId) && isGitHubLogin(value.login);
|
|
964
|
+
}
|
|
965
|
+
function validUserProfile(value) {
|
|
966
|
+
return record(value) && (value.name === null || typeof value.name === "string") && (value.type === "user" || value.type === "organization" || value.type === "other") && validUserMatch(value);
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* One message per viewer refusal. The body's status picks it, not the HTTP code: a refused
|
|
970
|
+
* GitHub token and a signed-out session are both 401.
|
|
971
|
+
*/
|
|
972
|
+
const viewerRefusals = {
|
|
973
|
+
signed_out: "Sign in to manage viewers.",
|
|
974
|
+
uninvited: "Access unavailable.",
|
|
975
|
+
forbidden: "A trusted browser origin is required.",
|
|
976
|
+
unavailable: "Document unavailable.",
|
|
977
|
+
document_public: "Make the document private before adding viewers.",
|
|
978
|
+
github_user_not_found: "GitHub has no user with that username.",
|
|
979
|
+
github_account_mismatch: "That username now belongs to a different GitHub account. Look it up again.",
|
|
980
|
+
not_personal_account: "Only a personal GitHub account can be a viewer.",
|
|
981
|
+
owner_account: "You already have access to your own document.",
|
|
982
|
+
github_sign_in_required: "GitHub refused your sign-in. Sign in to Pageship again.",
|
|
983
|
+
github_rate_limited: "GitHub is limiting requests. Try again in a minute.",
|
|
984
|
+
github_unavailable: "GitHub is unavailable. Try again.",
|
|
985
|
+
invalid_sharing: "A change can't both add and remove the same account."
|
|
986
|
+
};
|
|
987
|
+
function isViewerRefusal(value) {
|
|
988
|
+
return typeof value === "string" && Object.hasOwn(viewerRefusals, value);
|
|
989
|
+
}
|
|
990
|
+
function viewerFailed(response, error) {
|
|
991
|
+
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);
|
|
992
|
+
failed(response, error);
|
|
993
|
+
}
|
|
994
|
+
async function getDocumentViewers(baseUrl, id, options = {}) {
|
|
995
|
+
const { data, response, error } = await viewersList({
|
|
996
|
+
...requestOptions(baseUrl, options),
|
|
997
|
+
path: { id }
|
|
998
|
+
});
|
|
999
|
+
if (!response?.ok) viewerFailed(response, error);
|
|
1000
|
+
if (data?.status === "listed" && Array.isArray(data.viewers) && data.viewers.every(validViewer)) return data;
|
|
1001
|
+
throw new Error("Invalid document viewer listing response.");
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* The most accounts one sharing change adds and removes. The contract's DocumentSharingChange
|
|
1005
|
+
* refuses more; tests/document-contract.test.ts keeps the two equal.
|
|
1006
|
+
*/
|
|
1007
|
+
const sharingLimits = {
|
|
1008
|
+
add: 20,
|
|
1009
|
+
remove: 50
|
|
1010
|
+
};
|
|
1011
|
+
/**
|
|
1012
|
+
* Changes visibility and viewers together or not at all. The server looks up each added account
|
|
1013
|
+
* that isn't a viewer yet, as addDocumentViewer does.
|
|
1014
|
+
*/
|
|
1015
|
+
async function updateDocumentSharing(baseUrl, id, change, options = {}) {
|
|
1016
|
+
const { data, response, error } = await viewersUpdateSharing({
|
|
1017
|
+
...changeOptions(baseUrl, options),
|
|
1018
|
+
path: { id },
|
|
1019
|
+
body: change
|
|
1020
|
+
});
|
|
1021
|
+
if (!response?.ok) viewerFailed(response, error);
|
|
1022
|
+
const document = data?.status === "sharing_updated" ? normalizeDocument(data.document) : void 0;
|
|
1023
|
+
if (data && document && (change.visibility === void 0 || document.visibility === change.visibility) && [
|
|
1024
|
+
data.viewers,
|
|
1025
|
+
data.added,
|
|
1026
|
+
data.removed
|
|
1027
|
+
].every((list) => Array.isArray(list) && list.every(validViewer))) return {
|
|
1028
|
+
...data,
|
|
1029
|
+
document
|
|
1030
|
+
};
|
|
1031
|
+
throw new Error("Invalid document sharing response.");
|
|
1032
|
+
}
|
|
1033
|
+
async function getGitHubUser(baseUrl, login, options = {}) {
|
|
1034
|
+
const { data, response, error } = await githubGetUser({
|
|
1035
|
+
...requestOptions(baseUrl, options),
|
|
1036
|
+
path: { login }
|
|
1037
|
+
});
|
|
1038
|
+
if (!response?.ok) viewerFailed(response, error);
|
|
1039
|
+
if (data?.status === "found" && validUserProfile(data.user)) return data;
|
|
1040
|
+
throw new Error("Invalid GitHub user response.");
|
|
1041
|
+
}
|
|
960
1042
|
//#endregion
|
|
961
1043
|
//#region ../../packages/api-client/src/index.ts
|
|
962
1044
|
async function getWorkspace(baseUrl, options = {}) {
|
|
@@ -971,7 +1053,17 @@ async function getWorkspace(baseUrl, options = {}) {
|
|
|
971
1053
|
throwOnError: false
|
|
972
1054
|
});
|
|
973
1055
|
if (!response?.ok) throw new Error("Could not load your workspace.", { cause: error });
|
|
974
|
-
if (data?.status === "signed_out"
|
|
1056
|
+
if (data?.status === "signed_out") return { status: data.status };
|
|
1057
|
+
if (data?.status === "uninvited") {
|
|
1058
|
+
const user = data.user;
|
|
1059
|
+
return user && typeof user.name === "string" && (user.login === void 0 || typeof user.login === "string") ? {
|
|
1060
|
+
status: data.status,
|
|
1061
|
+
user: {
|
|
1062
|
+
name: user.name,
|
|
1063
|
+
...user.login ? { login: user.login } : {}
|
|
1064
|
+
}
|
|
1065
|
+
} : { status: data.status };
|
|
1066
|
+
}
|
|
975
1067
|
if (data?.status === "admitted" && typeof data.user?.id === "string" && data.user.id.length > 0 && typeof data.user?.name === "string" && (data.user.login === void 0 || typeof data.user.login === "string") && (data.user.githubAccountId === void 0 || typeof data.user.githubAccountId === "string" && /^[1-9]\d*$/u.test(data.user.githubAccountId)) && typeof data.workspace?.id === "string" && data.workspace.id.length > 0 && (data.address === void 0 || data.address === null || isWorkspaceAddress(data.address))) return data;
|
|
976
1068
|
throw new Error("Invalid workspace response.");
|
|
977
1069
|
}
|
|
@@ -1325,7 +1417,10 @@ const outcomes = {
|
|
|
1325
1417
|
downloaded: "succeeded",
|
|
1326
1418
|
visibility_updated: "succeeded",
|
|
1327
1419
|
restored: "succeeded",
|
|
1420
|
+
viewers_updated: "succeeded",
|
|
1328
1421
|
skills_installed: "succeeded",
|
|
1422
|
+
cli_updated: "succeeded",
|
|
1423
|
+
cli_current: "succeeded",
|
|
1329
1424
|
authorization_pending: "pending",
|
|
1330
1425
|
expired: "pending",
|
|
1331
1426
|
signed_out: "pending",
|
|
@@ -1336,6 +1431,7 @@ const outcomes = {
|
|
|
1336
1431
|
repository_mismatch: "pending",
|
|
1337
1432
|
marker_unavailable: "pending",
|
|
1338
1433
|
revision_conflict: "pending",
|
|
1434
|
+
github_sign_in_required: "pending",
|
|
1339
1435
|
cancelled: "pending",
|
|
1340
1436
|
usage_error: "failed",
|
|
1341
1437
|
internal_error: "failed",
|
|
@@ -1359,7 +1455,17 @@ const outcomes = {
|
|
|
1359
1455
|
idempotency_conflict: "failed",
|
|
1360
1456
|
document_id_taken: "failed",
|
|
1361
1457
|
publication_action_unsupported: "failed",
|
|
1362
|
-
skills_modified: "failed"
|
|
1458
|
+
skills_modified: "failed",
|
|
1459
|
+
install_unknown: "failed",
|
|
1460
|
+
update_failed: "failed",
|
|
1461
|
+
ambiguous_viewer: "failed",
|
|
1462
|
+
document_public: "failed",
|
|
1463
|
+
github_user_not_found: "failed",
|
|
1464
|
+
github_account_mismatch: "failed",
|
|
1465
|
+
not_personal_account: "failed",
|
|
1466
|
+
owner_account: "failed",
|
|
1467
|
+
github_rate_limited: "failed",
|
|
1468
|
+
github_unavailable: "failed"
|
|
1363
1469
|
};
|
|
1364
1470
|
const isStatus = (value) => Object.hasOwn(outcomes, value);
|
|
1365
1471
|
const commandNames = [
|
|
@@ -1371,8 +1477,10 @@ const commandNames = [
|
|
|
1371
1477
|
"history",
|
|
1372
1478
|
"download",
|
|
1373
1479
|
"visibility",
|
|
1480
|
+
"viewers",
|
|
1374
1481
|
"restore",
|
|
1375
|
-
"skills"
|
|
1482
|
+
"skills",
|
|
1483
|
+
"update"
|
|
1376
1484
|
];
|
|
1377
1485
|
const isCommandName = (value) => commandNames.some((name) => name === value);
|
|
1378
1486
|
/** Exit codes follow the outcome, except an interruption, which exits 130. */
|
|
@@ -1422,7 +1530,7 @@ function commandLine(command, args) {
|
|
|
1422
1530
|
}
|
|
1423
1531
|
//#endregion
|
|
1424
1532
|
//#region package.json
|
|
1425
|
-
var version = "0.
|
|
1533
|
+
var version = "0.6.0";
|
|
1426
1534
|
//#endregion
|
|
1427
1535
|
//#region src/publish.ts
|
|
1428
1536
|
const execute = promisify(execFile);
|
|
@@ -2142,6 +2250,25 @@ async function identify(serverValue, signal) {
|
|
|
2142
2250
|
}
|
|
2143
2251
|
//#endregion
|
|
2144
2252
|
//#region src/documents.ts
|
|
2253
|
+
/** `1 viewer`, `2 viewers`. */
|
|
2254
|
+
const viewerCount = (count) => `${count} viewer${count === 1 ? "" : "s"}`;
|
|
2255
|
+
/** The result for a request the server refused. `login` signs in again. */
|
|
2256
|
+
function refusal(error, login) {
|
|
2257
|
+
const status = error.status;
|
|
2258
|
+
if (status === "signed_out") return {
|
|
2259
|
+
status,
|
|
2260
|
+
message: `Your credential was revoked, expired or is invalid. Run ${commandLine(login.command, login.args)} to sign in again.`,
|
|
2261
|
+
next: login
|
|
2262
|
+
};
|
|
2263
|
+
if (status === "uninvited") return {
|
|
2264
|
+
status,
|
|
2265
|
+
message: "This account is not invited. Contact the server owner."
|
|
2266
|
+
};
|
|
2267
|
+
return {
|
|
2268
|
+
status,
|
|
2269
|
+
message: error.message
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2145
2272
|
/** Checks compatibility and the stored credential; the server enforces ownership and admission. */
|
|
2146
2273
|
async function asOwner(serverValue, signal, operation) {
|
|
2147
2274
|
const selection = selectServer(serverValue);
|
|
@@ -2174,14 +2301,11 @@ async function asOwner(serverValue, signal, operation) {
|
|
|
2174
2301
|
signal
|
|
2175
2302
|
});
|
|
2176
2303
|
} catch (error) {
|
|
2177
|
-
if (!(error
|
|
2178
|
-
return
|
|
2179
|
-
status: error.status,
|
|
2180
|
-
message: error.status === "signed_out" ? `Your credential was revoked, expired or is invalid. Run ${login} to sign in again.` : error.status === "uninvited" ? "This account is not invited. Contact the server owner." : error.message,
|
|
2181
|
-
...error.status === "signed_out" ? { next } : {}
|
|
2182
|
-
};
|
|
2304
|
+
if (!isRefusal(error)) throw error;
|
|
2305
|
+
return refusal(error, next);
|
|
2183
2306
|
}
|
|
2184
2307
|
}
|
|
2308
|
+
const isRefusal = (error) => error instanceof DocumentRequestError && isStatus(error.status);
|
|
2185
2309
|
function listDocuments(options) {
|
|
2186
2310
|
return asOwner(options.server, options.signal, async ({ origin, args }, request) => {
|
|
2187
2311
|
const page = await getDocuments(origin, {
|
|
@@ -2201,7 +2325,7 @@ function listDocuments(options) {
|
|
|
2201
2325
|
...next ? { next } : {},
|
|
2202
2326
|
message: [
|
|
2203
2327
|
page.documents.length ? "Documents:" : options.cursor ? "No more documents." : "No documents.",
|
|
2204
|
-
...page.documents.map((document) => `${document.id} ${document.visibility} ${document.title}\n` + (document.repository ? ` repository ${document.repository}\n` : "") + ` source ${document.sourcePath}\n current revision ${document.revisionId}\n ${document.url}`),
|
|
2328
|
+
...page.documents.map((document) => `${document.id} ${document.visibility} ${viewerCount(document.sharedWith)} ${document.title}\n` + (document.repository ? ` repository ${document.repository}\n` : "") + ` source ${document.sourcePath}\n current revision ${document.revisionId}\n ${document.url}`),
|
|
2205
2329
|
...next ? [`More documents: run ${commandLine(next.command, next.args)}`] : []
|
|
2206
2330
|
].join("\n")
|
|
2207
2331
|
};
|
|
@@ -2279,10 +2403,13 @@ function downloadDocument(options) {
|
|
|
2279
2403
|
}
|
|
2280
2404
|
function changeVisibility(options) {
|
|
2281
2405
|
return asOwner(options.server, options.signal, async ({ origin }, request) => {
|
|
2282
|
-
const
|
|
2406
|
+
const { document, removed } = await updateDocumentSharing(origin, options.documentId, { visibility: options.visibility }, request);
|
|
2407
|
+
const removedViewers = removed.length;
|
|
2283
2408
|
return {
|
|
2284
|
-
|
|
2285
|
-
|
|
2409
|
+
status: "visibility_updated",
|
|
2410
|
+
document,
|
|
2411
|
+
removedViewers,
|
|
2412
|
+
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}`
|
|
2286
2413
|
};
|
|
2287
2414
|
});
|
|
2288
2415
|
}
|
|
@@ -2361,6 +2488,10 @@ const installNext = {
|
|
|
2361
2488
|
command: "skills",
|
|
2362
2489
|
args: ["install"]
|
|
2363
2490
|
};
|
|
2491
|
+
const updateNext = {
|
|
2492
|
+
command: "update",
|
|
2493
|
+
args: []
|
|
2494
|
+
};
|
|
2364
2495
|
/** The output of `node --version` from PATH, or undefined when it does not run. */
|
|
2365
2496
|
const nodeOnPath = () => new Promise((resolve) => {
|
|
2366
2497
|
execFile("node", ["--version"], {
|
|
@@ -2445,6 +2576,7 @@ async function replaceFolder(source, target) {
|
|
|
2445
2576
|
}
|
|
2446
2577
|
}
|
|
2447
2578
|
const parseMajor = (version) => Number(/^v?(\d+)\./u.exec(version)?.[1] ?? NaN);
|
|
2579
|
+
/** Whether version `installed` is older than `cli`. An unreadable `installed` counts as older. */
|
|
2448
2580
|
const older = (installed, cli) => {
|
|
2449
2581
|
const parts = (version) => version.split(/[.+-]/u).slice(0, 3).map(Number);
|
|
2450
2582
|
const [a, b] = [parts(installed), parts(cli)];
|
|
@@ -2468,7 +2600,7 @@ async function inspectCli(environment, signal) {
|
|
|
2468
2600
|
return {
|
|
2469
2601
|
cli: "older",
|
|
2470
2602
|
latest,
|
|
2471
|
-
message: `A newer Pageship CLI is published (${latest}; this is ${environment.version}).
|
|
2603
|
+
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)}.`
|
|
2472
2604
|
};
|
|
2473
2605
|
}
|
|
2474
2606
|
/** The recommended skills and Node that the CLI could not find. It installs none of them. */
|
|
@@ -2752,6 +2884,293 @@ async function checkReadiness(serverValue, signal) {
|
|
|
2752
2884
|
};
|
|
2753
2885
|
}
|
|
2754
2886
|
//#endregion
|
|
2887
|
+
//#region src/viewers.ts
|
|
2888
|
+
/** `octocat (Mona Lisa), GitHub ID 583231` */
|
|
2889
|
+
const account = ({ login, name, githubAccountId }) => `${login}${name ? ` (${name})` : ""}, GitHub ID ${githubAccountId}`;
|
|
2890
|
+
function viewerList(documentId, viewers) {
|
|
2891
|
+
return viewers.length ? [`Viewers of ${documentId}:`, ...viewers.map((viewer) => ` ${account(viewer)}`)].join("\n") : `${documentId} has no viewers.`;
|
|
2892
|
+
}
|
|
2893
|
+
/**
|
|
2894
|
+
* Lists a document's viewers, or changes them. Every removal is matched and every added username
|
|
2895
|
+
* looked up first, then one sharing change applies them all or none.
|
|
2896
|
+
*/
|
|
2897
|
+
function manageViewers(options) {
|
|
2898
|
+
const { documentId, add, remove } = options;
|
|
2899
|
+
return asOwner(options.server, options.signal, async ({ origin, args }, request) => {
|
|
2900
|
+
const login = {
|
|
2901
|
+
command: "login",
|
|
2902
|
+
args
|
|
2903
|
+
};
|
|
2904
|
+
const next = {
|
|
2905
|
+
command: "viewers",
|
|
2906
|
+
args: [
|
|
2907
|
+
documentId,
|
|
2908
|
+
...args,
|
|
2909
|
+
...remove.flatMap((value) => ["--remove", value]),
|
|
2910
|
+
...add.flatMap((value) => ["--add", value])
|
|
2911
|
+
]
|
|
2912
|
+
};
|
|
2913
|
+
if (add.length > sharingLimits.add || remove.length > sharingLimits.remove) return {
|
|
2914
|
+
status: "usage_error",
|
|
2915
|
+
message: `Add up to ${sharingLimits.add} and remove up to ${sharingLimits.remove} viewers at a time. Nothing was changed.`
|
|
2916
|
+
};
|
|
2917
|
+
const progress = [];
|
|
2918
|
+
try {
|
|
2919
|
+
const { viewers } = await getDocumentViewers(origin, documentId, request);
|
|
2920
|
+
if (!add.length && !remove.length) return {
|
|
2921
|
+
status: "listed",
|
|
2922
|
+
documentId,
|
|
2923
|
+
viewers,
|
|
2924
|
+
message: viewerList(documentId, viewers)
|
|
2925
|
+
};
|
|
2926
|
+
const removals = [];
|
|
2927
|
+
const notViewers = [];
|
|
2928
|
+
for (const value of remove) {
|
|
2929
|
+
const matches = /^[0-9]+$/u.test(value) ? viewers.filter((viewer) => viewer.githubAccountId === value) : viewers.filter((viewer) => viewer.login.toLowerCase() === value.toLowerCase());
|
|
2930
|
+
if (matches.length > 1) return {
|
|
2931
|
+
status: "ambiguous_viewer",
|
|
2932
|
+
documentId,
|
|
2933
|
+
value,
|
|
2934
|
+
matches,
|
|
2935
|
+
message: [`${value} matches ${matches.length} viewers of ${documentId}. Nothing was changed. Remove one by its GitHub ID with --remove <id>:`, ...matches.map((viewer) => ` ${account(viewer)}`)].join("\n")
|
|
2936
|
+
};
|
|
2937
|
+
if (!matches[0]) notViewers.push(value);
|
|
2938
|
+
else if (!removals.includes(matches[0])) removals.push(matches[0]);
|
|
2939
|
+
}
|
|
2940
|
+
const accounts = [];
|
|
2941
|
+
for (const value of add) {
|
|
2942
|
+
const { user } = await getGitHubUser(origin, value, request);
|
|
2943
|
+
if (user.type !== "user") return {
|
|
2944
|
+
status: "not_personal_account",
|
|
2945
|
+
documentId,
|
|
2946
|
+
user,
|
|
2947
|
+
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.`
|
|
2948
|
+
};
|
|
2949
|
+
if (removals.some(({ githubAccountId }) => githubAccountId === user.githubAccountId)) return {
|
|
2950
|
+
status: "usage_error",
|
|
2951
|
+
message: `${account(user)} is both added and removed. Nothing was changed.`
|
|
2952
|
+
};
|
|
2953
|
+
progress.push(`Found ${account(user)}.`);
|
|
2954
|
+
if (!accounts.some(({ githubAccountId }) => githubAccountId === user.githubAccountId)) accounts.push(user);
|
|
2955
|
+
}
|
|
2956
|
+
const { added, removed, ...shared } = await updateDocumentSharing(origin, documentId, {
|
|
2957
|
+
add: accounts.map(({ githubAccountId, login }) => ({
|
|
2958
|
+
githubAccountId,
|
|
2959
|
+
login
|
|
2960
|
+
})),
|
|
2961
|
+
remove: removals.map(({ githubAccountId }) => githubAccountId)
|
|
2962
|
+
}, request);
|
|
2963
|
+
return {
|
|
2964
|
+
status: "viewers_updated",
|
|
2965
|
+
documentId,
|
|
2966
|
+
added,
|
|
2967
|
+
removed,
|
|
2968
|
+
notViewers,
|
|
2969
|
+
viewers: shared.viewers,
|
|
2970
|
+
message: [
|
|
2971
|
+
...progress,
|
|
2972
|
+
...removed.map((viewer) => `Removed ${account(viewer)}.`),
|
|
2973
|
+
...added.map((viewer) => `Added ${account(viewer)}.`),
|
|
2974
|
+
...notViewers.map((value) => `${value} is not a viewer of ${documentId}.`),
|
|
2975
|
+
viewerList(documentId, shared.viewers)
|
|
2976
|
+
].join("\n")
|
|
2977
|
+
};
|
|
2978
|
+
} catch (error) {
|
|
2979
|
+
const prefix = (result) => ({
|
|
2980
|
+
...result,
|
|
2981
|
+
documentId,
|
|
2982
|
+
message: [...progress, result.message].join("\n")
|
|
2983
|
+
});
|
|
2984
|
+
if (isRefusal(error)) {
|
|
2985
|
+
const { status } = error;
|
|
2986
|
+
if (status === "github_sign_in_required") return prefix({
|
|
2987
|
+
status,
|
|
2988
|
+
message: `GitHub refused the sign-in Pageship uses to look up GitHub users. Sign in to the Pageship dashboard again with GitHub, then run ${commandLine(next.command, next.args)}.`,
|
|
2989
|
+
next
|
|
2990
|
+
});
|
|
2991
|
+
if (status === "github_rate_limited" || status === "github_unavailable") return prefix({
|
|
2992
|
+
status,
|
|
2993
|
+
message: error.message,
|
|
2994
|
+
next
|
|
2995
|
+
});
|
|
2996
|
+
return prefix(refusal(error, login));
|
|
2997
|
+
}
|
|
2998
|
+
return prefix(options.signal.aborted ? {
|
|
2999
|
+
status: "interrupted",
|
|
3000
|
+
message: `Operation interrupted. Run ${commandLine(next.command, next.args)} to finish.`,
|
|
3001
|
+
next
|
|
3002
|
+
} : {
|
|
3003
|
+
status: "request_failed",
|
|
3004
|
+
message: `${error instanceof Error ? error.message : String(error)} Run ${commandLine(next.command, next.args)} to retry.`,
|
|
3005
|
+
next
|
|
3006
|
+
});
|
|
3007
|
+
}
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
//#endregion
|
|
3011
|
+
//#region src/update.ts
|
|
3012
|
+
/**
|
|
3013
|
+
* The command that updates a global install with each manager. Keep in sync with the setup
|
|
3014
|
+
* guide. The package names an exact version where one is known: pnpm resolves `latest` to the
|
|
3015
|
+
* newest version older than its minimum release age, so a fresh release would not install.
|
|
3016
|
+
*/
|
|
3017
|
+
const updateCommand$1 = (manager, spec) => ({
|
|
3018
|
+
npm: [
|
|
3019
|
+
"npm",
|
|
3020
|
+
"i",
|
|
3021
|
+
"-g",
|
|
3022
|
+
spec
|
|
3023
|
+
],
|
|
3024
|
+
pnpm: [
|
|
3025
|
+
"pnpm",
|
|
3026
|
+
"add",
|
|
3027
|
+
"-g",
|
|
3028
|
+
spec
|
|
3029
|
+
],
|
|
3030
|
+
bun: [
|
|
3031
|
+
"bun",
|
|
3032
|
+
"add",
|
|
3033
|
+
"-g",
|
|
3034
|
+
spec
|
|
3035
|
+
],
|
|
3036
|
+
yarn: [
|
|
3037
|
+
"yarn",
|
|
3038
|
+
"global",
|
|
3039
|
+
"add",
|
|
3040
|
+
spec
|
|
3041
|
+
],
|
|
3042
|
+
vp: [
|
|
3043
|
+
"vp",
|
|
3044
|
+
"install",
|
|
3045
|
+
"-g",
|
|
3046
|
+
spec
|
|
3047
|
+
]
|
|
3048
|
+
})[manager];
|
|
3049
|
+
const npmRoot = (signal) => new Promise((resolve) => {
|
|
3050
|
+
exec("npm root -g", {
|
|
3051
|
+
timeout: 1e4,
|
|
3052
|
+
windowsHide: true,
|
|
3053
|
+
signal
|
|
3054
|
+
}, (error, stdout) => resolve(error ? void 0 : stdout.trim() || void 0));
|
|
3055
|
+
});
|
|
3056
|
+
/**
|
|
3057
|
+
* Run a command with the terminal attached, or, for `--json`, with its output collected so
|
|
3058
|
+
* standard output stays one JSON value.
|
|
3059
|
+
*/
|
|
3060
|
+
const runCommand = (json) => (argv, signal) => new Promise((resolve, reject) => {
|
|
3061
|
+
const [command, ...args] = argv;
|
|
3062
|
+
if (!json) console.log(`Running ${argv.join(" ")}`);
|
|
3063
|
+
const stdio = json ? [
|
|
3064
|
+
"ignore",
|
|
3065
|
+
"pipe",
|
|
3066
|
+
"pipe"
|
|
3067
|
+
] : "inherit";
|
|
3068
|
+
const child = process.platform === "win32" ? spawn(argv.join(" "), {
|
|
3069
|
+
stdio,
|
|
3070
|
+
shell: true,
|
|
3071
|
+
windowsHide: true
|
|
3072
|
+
}) : spawn(command, args, { stdio });
|
|
3073
|
+
const stop = () => child.kill("SIGTERM");
|
|
3074
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
3075
|
+
const settle = () => signal.removeEventListener("abort", stop);
|
|
3076
|
+
let output = "";
|
|
3077
|
+
const collect = (chunk) => {
|
|
3078
|
+
output = (output + chunk.toString()).slice(-4e3);
|
|
3079
|
+
};
|
|
3080
|
+
child.stdout?.on("data", collect);
|
|
3081
|
+
child.stderr?.on("data", collect);
|
|
3082
|
+
child.once("error", (error) => {
|
|
3083
|
+
settle();
|
|
3084
|
+
reject(error);
|
|
3085
|
+
});
|
|
3086
|
+
child.once("close", (exitCode) => {
|
|
3087
|
+
settle();
|
|
3088
|
+
resolve({
|
|
3089
|
+
exitCode,
|
|
3090
|
+
output
|
|
3091
|
+
});
|
|
3092
|
+
});
|
|
3093
|
+
});
|
|
3094
|
+
/** A path with forward slashes and a trailing slash, compared without case on Windows. */
|
|
3095
|
+
function comparable(path, platform) {
|
|
3096
|
+
const slashed = `${path.replaceAll("\\", "/").replace(/\/+$/u, "")}/`;
|
|
3097
|
+
return platform === "win32" ? slashed.toLowerCase() : slashed;
|
|
3098
|
+
}
|
|
3099
|
+
/**
|
|
3100
|
+
* Which manager installed this CLI globally, from where its package is. Undefined for anything
|
|
3101
|
+
* else, such as a project dependency or an npx cache, which `update` must not replace.
|
|
3102
|
+
*/
|
|
3103
|
+
async function detectManager(environment, signal) {
|
|
3104
|
+
const { platform, env } = environment;
|
|
3105
|
+
const root = comparable(environment.packageRoot, platform);
|
|
3106
|
+
const installed = (folders, layout) => folders.some((folder) => {
|
|
3107
|
+
if (!folder) return false;
|
|
3108
|
+
const base = comparable(folder, platform);
|
|
3109
|
+
const index = root.lastIndexOf(base);
|
|
3110
|
+
return index !== -1 && layout.test(root.slice(index + base.length));
|
|
3111
|
+
});
|
|
3112
|
+
if (installed(["/vite-plus/packages/"], /^pageship\/[^/]+\/(?:lib\/)?node_modules\/pageship\/$/u)) return "vp";
|
|
3113
|
+
if (installed(["/pnpm/global/", env.PNPM_HOME && join(env.PNPM_HOME, "global")], /^[^/]+\/(?:[^/]+\/)?node_modules\/\.pnpm\/pageship@[^/]+\/node_modules\/pageship\/$/u)) return "pnpm";
|
|
3114
|
+
if (installed(["/.bun/", env.BUN_INSTALL], /^install\/global\/node_modules\/pageship\/$/u) || installed([env.BUN_INSTALL_GLOBAL_DIR], /^node_modules\/pageship\/$/u)) return "bun";
|
|
3115
|
+
if (installed(["/yarn/global/", "/yarn/data/global/"], /^node_modules\/pageship\/$/u)) return "yarn";
|
|
3116
|
+
const npm = await environment.npmRoot(signal);
|
|
3117
|
+
const parent = root.slice(0, root.lastIndexOf("/", root.length - 2) + 1);
|
|
3118
|
+
if (npm !== void 0 && parent === comparable(npm, platform)) return "npm";
|
|
3119
|
+
}
|
|
3120
|
+
/**
|
|
3121
|
+
* Update this CLI to the version npm publishes as latest, with the package manager that
|
|
3122
|
+
* installed it. It changes nothing when this CLI is already the latest.
|
|
3123
|
+
*/
|
|
3124
|
+
async function update(environment, signal) {
|
|
3125
|
+
const { version } = environment;
|
|
3126
|
+
const latest = await environment.publishedVersion(signal);
|
|
3127
|
+
if (latest !== void 0 && !older(version, latest)) return {
|
|
3128
|
+
status: "cli_current",
|
|
3129
|
+
message: `Pageship ${version} is the latest version.`,
|
|
3130
|
+
version,
|
|
3131
|
+
latest
|
|
3132
|
+
};
|
|
3133
|
+
const manager = await detectManager(environment, signal);
|
|
3134
|
+
signal.throwIfAborted();
|
|
3135
|
+
if (manager === void 0) return {
|
|
3136
|
+
status: "install_unknown",
|
|
3137
|
+
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.`,
|
|
3138
|
+
version,
|
|
3139
|
+
...latest === void 0 ? {} : { latest },
|
|
3140
|
+
path: environment.packageRoot
|
|
3141
|
+
};
|
|
3142
|
+
const argv = updateCommand$1(manager, `pageship@${latest ?? "latest"}`);
|
|
3143
|
+
const run = argv.join(" ");
|
|
3144
|
+
const ran = await environment.run(argv, signal).catch((error) => {
|
|
3145
|
+
if (signal.aborted) throw error;
|
|
3146
|
+
return {
|
|
3147
|
+
exitCode: null,
|
|
3148
|
+
output: error instanceof Error ? error.message : String(error)
|
|
3149
|
+
};
|
|
3150
|
+
});
|
|
3151
|
+
signal.throwIfAborted();
|
|
3152
|
+
if (ran.exitCode !== 0) return {
|
|
3153
|
+
status: "update_failed",
|
|
3154
|
+
message: [ran.exitCode === null ? `Could not run ${run}.` : `${run} failed with exit code ${ran.exitCode}.`, ran.output.trim()].filter(Boolean).join("\n"),
|
|
3155
|
+
version,
|
|
3156
|
+
...latest === void 0 ? {} : { latest },
|
|
3157
|
+
manager,
|
|
3158
|
+
run,
|
|
3159
|
+
exitCode: ran.exitCode,
|
|
3160
|
+
output: ran.output
|
|
3161
|
+
};
|
|
3162
|
+
const skills = await stat(join(environment.home, ".agents", "skills", "pageship")).then(() => true, () => false);
|
|
3163
|
+
return {
|
|
3164
|
+
status: "cli_updated",
|
|
3165
|
+
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"),
|
|
3166
|
+
version,
|
|
3167
|
+
...latest === void 0 ? {} : { latest },
|
|
3168
|
+
manager,
|
|
3169
|
+
run,
|
|
3170
|
+
...skills ? { next: installNext } : {}
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
//#endregion
|
|
2755
3174
|
//#region src/cli.ts
|
|
2756
3175
|
const args = process.argv.slice(2);
|
|
2757
3176
|
const separator = args.indexOf("--");
|
|
@@ -2818,6 +3237,13 @@ const skills = Command.make("skills").pipe(Command.withDescription("Manage the P
|
|
|
2818
3237
|
force,
|
|
2819
3238
|
signal
|
|
2820
3239
|
}))).pipe(Command.withDescription("Install the Pageship skills in ~/.agents/skills, and link them in ~/.claude/skills when ~/.claude exists."))]));
|
|
3240
|
+
const updateCommand = Command.make("update", {}, () => authCommand("update", (signal) => update({
|
|
3241
|
+
...skillsEnvironment,
|
|
3242
|
+
packageRoot: dirname(dirname(fileURLToPath(import.meta.url))),
|
|
3243
|
+
env: process.env,
|
|
3244
|
+
npmRoot,
|
|
3245
|
+
run: runCommand(json)
|
|
3246
|
+
}, signal))).pipe(Command.withDescription("Update this CLI to the latest version with the package manager that installed it: npm, pnpm, bun, Yarn or Vite+."));
|
|
2821
3247
|
const login = Command.make("login", {
|
|
2822
3248
|
complete: Flag.boolean("complete").pipe(Flag.withDefault(false), Flag.withDescription("Check a pending authorization once, without waiting.")),
|
|
2823
3249
|
restart: Flag.boolean("restart").pipe(Flag.withDefault(false), Flag.withDescription("Start a new authorization code.")),
|
|
@@ -2905,6 +3331,18 @@ const visibility = Command.make("visibility", {
|
|
|
2905
3331
|
visibility,
|
|
2906
3332
|
signal
|
|
2907
3333
|
}))).pipe(Command.withDescription("Make a document private or public."));
|
|
3334
|
+
const viewers = Command.make("viewers", {
|
|
3335
|
+
documentId: documentIdArgument,
|
|
3336
|
+
add: Flag.string("add").pipe(Flag.filter((value) => isGitHubLogin(value), () => "Use a GitHub username."), Flag.withDescription("Look up this GitHub username and add the account it names as a viewer. Repeat to add several."), Flag.atLeast(0)),
|
|
3337
|
+
remove: Flag.string("remove").pipe(Flag.filter((value) => isGitHubLogin(value), () => "Use a viewer's GitHub username or GitHub ID."), Flag.withDescription("Remove the viewer with this GitHub username or GitHub ID. Repeat to remove several."), Flag.atLeast(0)),
|
|
3338
|
+
server: serverFlag
|
|
3339
|
+
}, ({ documentId, add, remove, server }) => authCommand("viewers", (signal) => manageViewers({
|
|
3340
|
+
server: Option.getOrUndefined(server),
|
|
3341
|
+
documentId,
|
|
3342
|
+
add,
|
|
3343
|
+
remove,
|
|
3344
|
+
signal
|
|
3345
|
+
}))).pipe(Command.withDescription("List the GitHub users who can read a private document, or add and remove them."));
|
|
2908
3346
|
const restore = Command.make("restore", {
|
|
2909
3347
|
documentId: documentIdArgument,
|
|
2910
3348
|
server: serverFlag,
|
|
@@ -2966,7 +3404,7 @@ function authCommand(name, operation) {
|
|
|
2966
3404
|
settle(name, result);
|
|
2967
3405
|
if (json) return;
|
|
2968
3406
|
if (thrown) {
|
|
2969
|
-
console.error(`${name === "status" ? "Could not check status" : name === "skills" ? "Could not install the Pageship skills" : "Pageship request failed"}: ${result.message}`);
|
|
3407
|
+
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}`);
|
|
2970
3408
|
return;
|
|
2971
3409
|
}
|
|
2972
3410
|
console.log(result.message);
|
|
@@ -2977,7 +3415,7 @@ function authCommand(name, operation) {
|
|
|
2977
3415
|
}
|
|
2978
3416
|
});
|
|
2979
3417
|
}
|
|
2980
|
-
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 and restore. Install the Pageship skills for your agents with skills install."), Command.withSubcommands([
|
|
3418
|
+
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([
|
|
2981
3419
|
status,
|
|
2982
3420
|
login,
|
|
2983
3421
|
whoami,
|
|
@@ -2986,8 +3424,10 @@ const command = Command.make("pageship").pipe(Command.withDescription("Pageship
|
|
|
2986
3424
|
history,
|
|
2987
3425
|
download,
|
|
2988
3426
|
visibility,
|
|
3427
|
+
viewers,
|
|
2989
3428
|
restore,
|
|
2990
|
-
skills
|
|
3429
|
+
skills,
|
|
3430
|
+
updateCommand
|
|
2991
3431
|
]), Command.withGlobalFlags([jsonFlag]));
|
|
2992
3432
|
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;
|
|
2993
3433
|
const program = Command.runWith(command, {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pageship
|
|
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, 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.
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
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
|
|
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
|
|
|
@@ -97,7 +108,7 @@ After a successful upload the CLI adds the document marker to the end of the loc
|
|
|
97
108
|
On `published`, tell the user:
|
|
98
109
|
|
|
99
110
|
- the document URL, `document.url`;
|
|
100
|
-
- who can read it, from `document.visibility`: a private document opens only for its owner, signed in;
|
|
111
|
+
- who can read it, from `document.visibility`: a private document opens only for its owner and its viewers, signed in;
|
|
101
112
|
- whether this publish created a new document or revised an existing one, from `change`: `created` or `revised`.
|
|
102
113
|
|
|
103
114
|
Keep `idempotencyKey` only while you resolve an uncertain attempt.
|
|
@@ -124,7 +135,11 @@ This works for any document the user owns, even when the file moved or belongs t
|
|
|
124
135
|
|
|
125
136
|
## Share a document
|
|
126
137
|
|
|
127
|
-
A request to share a document, or to send someone its link, is a request to publish it. Publish it as above and report the publish. A new document is private.
|
|
138
|
+
A request to share a document, or to send someone its link, is a request to publish it. Publish it as above and report the publish. A new document is private.
|
|
139
|
+
|
|
140
|
+
When the user names the people to share it with, keep it private and add them as viewers, as in [viewers](references/viewers.md). Sharing with named people never makes a document public.
|
|
141
|
+
|
|
142
|
+
When the user names no one and the result is private, ask whether to make it public, and say in the question that anyone with the link can then read it. Mention that it can instead stay private and be shared with named GitHub users. When it is already public, because the user made it public earlier, say so and don't ask. Change visibility only after the user agrees, as in [visibility](references/visibility.md).
|
|
128
143
|
|
|
129
144
|
## Use another server
|
|
130
145
|
|
|
@@ -137,6 +152,7 @@ Load the reference for the task:
|
|
|
137
152
|
- [List and history](references/list-and-history.md): find a document's `id`, `visibility`, current `revisionId` and `url`, and inspect its revisions.
|
|
138
153
|
- [Download](references/download.md): read a document's HTML, current or from an earlier revision.
|
|
139
154
|
- [Visibility](references/visibility.md): make a document public or private.
|
|
155
|
+
- [Viewers](references/viewers.md): list, add or remove the GitHub users who can read a private document.
|
|
140
156
|
- [Restore](references/restore.md): make an earlier revision current again.
|
|
141
157
|
|
|
142
158
|
Deleting a document, recovering a deleted one, changing a document's display title and revoking a CLI installation happen in the dashboard, at https://app.pageship.dev for the hosted service. The CLI has no commands for them yet, so give the user the dashboard link.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Viewers
|
|
2
|
+
|
|
3
|
+
A viewer is a GitHub user who can read a private document after signing in with GitHub. The owner always can. Viewers see only the current revision, never the history.
|
|
4
|
+
|
|
5
|
+
## Share with named people
|
|
6
|
+
|
|
7
|
+
Add viewers only for people the user named in this conversation. When the user names a person without a GitHub username, ask for it; a GitHub username is the only way to name a viewer. Sharing with people keeps the document private: a request to share a document with someone is never a request to make it public. A public document can't have viewers, so if it is public, ask the user whether to make it private first, as in [visibility](visibility.md), and add no one until they agree.
|
|
8
|
+
|
|
9
|
+
Set `PAGESHIP_DOCUMENT` to the document's `id`, then add each named username with its own `--add`:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pageship --json viewers "$PAGESHIP_DOCUMENT" --add octocat --add hubot
|
|
13
|
+
```
|
|
14
|
+
|
|
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
|
+
|
|
17
|
+
## List and remove viewers
|
|
18
|
+
|
|
19
|
+
To see who can read the document, run:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pageship --json viewers "$PAGESHIP_DOCUMENT"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
On `listed`, `viewers` holds each viewer's `login`, `name` and `githubAccountId`. A viewer's `login` is the one they had when added or last signed in, so two viewers can show the same `login`.
|
|
26
|
+
|
|
27
|
+
Remove viewers only when the user asks. Pass a `login` or a `githubAccountId`, one `--remove` each. A value of only digits is always a `githubAccountId`, so remove a viewer whose `login` is all digits by its `githubAccountId`:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
pageship --json viewers "$PAGESHIP_DOCUMENT" --remove octocat
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`notViewers` names values that matched no viewer; nothing was needed for them. On `ambiguous_viewer`, the value matched more than one viewer and nothing changed. Show the user `matches` and ask which one, then remove it by its `githubAccountId`.
|
|
34
|
+
|
|
35
|
+
## Outcomes
|
|
36
|
+
|
|
37
|
+
- `github_user_not_found`, `not_personal_account` or `owner_account`: nothing changed for that username. Report the `message` and ask the user for the right GitHub username. Only a personal account can be a viewer, and the owner can always read their own document.
|
|
38
|
+
- `github_account_mismatch`: the username moved to another account between the lookup and the change. Report the `message` and ask the user to confirm the username before you run the same command again.
|
|
39
|
+
- `document_public`: the document is public. Ask whether to make it private before adding anyone.
|
|
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
|
+
- `github_rate_limited` or `github_unavailable`: wait a minute, then run the returned `next`.
|
|
42
|
+
|
|
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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Visibility
|
|
2
2
|
|
|
3
|
-
A private document opens only for its owner, signed in. Anyone with the link can read a public one, without signing in.
|
|
3
|
+
A private document opens only for its owner and its [viewers](viewers.md), signed in. Anyone with the link can read a public one, without signing in. Making a document public removes all its viewers.
|
|
4
4
|
|
|
5
5
|
Change visibility only when the user asked for a policy by name, such as "make it public", or agreed when you asked after sharing. Set `PAGESHIP_DOCUMENT` to the document's `id`, then run one of:
|
|
6
6
|
|
|
@@ -9,4 +9,4 @@ pageship --json visibility "$PAGESHIP_DOCUMENT" public
|
|
|
9
9
|
pageship --json visibility "$PAGESHIP_DOCUMENT" private
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
On `visibility_updated`, report the returned `document.visibility` and `document.url
|
|
12
|
+
On `visibility_updated`, report the returned `document.visibility` and `document.url`, and `removedViewers` when it is more than 0. Publishing a new revision keeps the document's visibility.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pageship-chart
|
|
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.
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Chart
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pageship-code-block
|
|
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.
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Decision record
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pageship-diagram
|
|
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.
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Pageship documents
|
|
@@ -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.
|
|
6
|
+
version: "0.6.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Explainer
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pageship-interaction
|
|
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.
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Interaction
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pageship-key-figures
|
|
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.
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Key figures
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pageship-media
|
|
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.
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Media
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pageship-motion
|
|
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.
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Spec
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pageship-table
|
|
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.
|
|
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.
|
|
6
|
+
version: "0.6.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Table
|