@whatcha/cli 0.0.0-stage → 0.3.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.
Files changed (59) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +345 -2
  3. package/dist/auth/gotrue-client.js +64 -0
  4. package/dist/auth/gotrue-client.js.map +1 -0
  5. package/dist/auth/session.js +147 -0
  6. package/dist/auth/session.js.map +1 -0
  7. package/dist/auth/token-store.js +165 -0
  8. package/dist/auth/token-store.js.map +1 -0
  9. package/dist/cli.js +134 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/commands/diaries.js +52 -0
  12. package/dist/commands/diaries.js.map +1 -0
  13. package/dist/commands/login.js +292 -0
  14. package/dist/commands/login.js.map +1 -0
  15. package/dist/commands/profiles.js +287 -0
  16. package/dist/commands/profiles.js.map +1 -0
  17. package/dist/commands/publish.js +81 -0
  18. package/dist/commands/publish.js.map +1 -0
  19. package/dist/commands/read.js +77 -0
  20. package/dist/commands/read.js.map +1 -0
  21. package/dist/commands/shared-diary.js +255 -0
  22. package/dist/commands/shared-diary.js.map +1 -0
  23. package/dist/commands/whoami.js +50 -0
  24. package/dist/commands/whoami.js.map +1 -0
  25. package/dist/commands/wiki.js +386 -0
  26. package/dist/commands/wiki.js.map +1 -0
  27. package/dist/commands/write.js +106 -0
  28. package/dist/commands/write.js.map +1 -0
  29. package/dist/config.js +47 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/errors.js +46 -0
  32. package/dist/errors.js.map +1 -0
  33. package/dist/http-client.js +102 -0
  34. package/dist/http-client.js.map +1 -0
  35. package/dist/index.js +16 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/lib/append-entry.js +193 -0
  38. package/dist/lib/append-entry.js.map +1 -0
  39. package/dist/lib/diary-resolve.js +50 -0
  40. package/dist/lib/diary-resolve.js.map +1 -0
  41. package/dist/lib/entry-date.js +96 -0
  42. package/dist/lib/entry-date.js.map +1 -0
  43. package/dist/lib/entry-find.js +39 -0
  44. package/dist/lib/entry-find.js.map +1 -0
  45. package/dist/lib/image-upload.js +126 -0
  46. package/dist/lib/image-upload.js.map +1 -0
  47. package/dist/lib/jwt.js +25 -0
  48. package/dist/lib/jwt.js.map +1 -0
  49. package/dist/lib/output.js +29 -0
  50. package/dist/lib/output.js.map +1 -0
  51. package/dist/lib/personal-access-token.js +46 -0
  52. package/dist/lib/personal-access-token.js.map +1 -0
  53. package/dist/lib/profile-family.js +29 -0
  54. package/dist/lib/profile-family.js.map +1 -0
  55. package/dist/lib/publication.js +55 -0
  56. package/dist/lib/publication.js.map +1 -0
  57. package/dist/types.js +6 -0
  58. package/dist/types.js.map +1 -0
  59. package/package.json +46 -4
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PUBLIC_SQUARE_GROUP_ID = exports.MAX_UPLOAD_BYTES = void 0;
4
+ exports.sniffImageType = sniffImageType;
5
+ exports.readImageFile = readImageFile;
6
+ exports.uploadImage = uploadImage;
7
+ exports.wikiImageRef = wikiImageRef;
8
+ const promises_1 = require("node:fs/promises");
9
+ const node_path_1 = require("node:path");
10
+ const errors_1 = require("../errors");
11
+ /**
12
+ * Local image files to media assets, for `whatcha wiki write --image/--cover`
13
+ * and `whatcha wiki cover` (#1667). `readImageFile` also checks the file
14
+ * `whatcha profile-add --avatar` sends (#1699).
15
+ *
16
+ * The upload is the app's own three steps -- `POST /uploads` (into the
17
+ * page's group), PUT the bytes to the signed URL, `POST /uploads/:id/complete`
18
+ * -- so the server applies the same limits it applies to an entry's photos.
19
+ * The checks here are those same limits, made before anything is sent, so a
20
+ * wrong file is a clear message instead of a half-done upload:
21
+ *
22
+ * - at most 40 MB (the API's UPLOAD_LIMITS.maxFileBytes);
23
+ * - a JPEG, PNG, WebP, GIF or AVIF image -- the image types the API accepts,
24
+ * and a wiki page shows nothing else. The type is read from the file's
25
+ * first bytes, the way the server resolves it; the extension only fills in
26
+ * for an AVIF variant the magic-number check does not recognise.
27
+ */
28
+ exports.MAX_UPLOAD_BYTES = 40 * 1024 * 1024;
29
+ /** Public Square's group: where the public wiki's images are uploaded. */
30
+ exports.PUBLIC_SQUARE_GROUP_ID = "00000000-0000-0000-0000-0000000000aa";
31
+ const BY_EXTENSION = {
32
+ ".jpg": "image/jpeg",
33
+ ".jpeg": "image/jpeg",
34
+ ".png": "image/png",
35
+ ".webp": "image/webp",
36
+ ".gif": "image/gif",
37
+ ".avif": "image/avif",
38
+ };
39
+ function ascii(bytes, start, end) {
40
+ return String.fromCharCode(...bytes.subarray(start, end));
41
+ }
42
+ /** The image type the bytes are, or null. Mirrors the API's core/media/image-type.ts. */
43
+ function sniffImageType(bytes) {
44
+ if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)
45
+ return "image/jpeg";
46
+ if (bytes.length >= 8 && bytes[0] === 0x89 && ascii(bytes, 1, 4) === "PNG" && bytes[4] === 0x0d && bytes[5] === 0x0a) {
47
+ return "image/png";
48
+ }
49
+ if (bytes.length >= 6 && (ascii(bytes, 0, 6) === "GIF87a" || ascii(bytes, 0, 6) === "GIF89a"))
50
+ return "image/gif";
51
+ if (bytes.length >= 12 && ascii(bytes, 0, 4) === "RIFF" && ascii(bytes, 8, 12) === "WEBP")
52
+ return "image/webp";
53
+ if (bytes.length >= 16 && ascii(bytes, 4, 8) === "ftyp") {
54
+ const box = Math.min(((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0, bytes.length, 64);
55
+ const brands = [ascii(bytes, 8, 12)];
56
+ for (let i = 16; i + 4 <= box; i += 4)
57
+ brands.push(ascii(bytes, i, i + 4));
58
+ if (brands.includes("avif") || brands.includes("avis"))
59
+ return "image/avif";
60
+ }
61
+ return null;
62
+ }
63
+ function megabytes(n) {
64
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
65
+ }
66
+ /** Reads and checks one image file. Throws CliUsageError with the reason when it cannot be uploaded. */
67
+ async function readImageFile(path) {
68
+ let size;
69
+ try {
70
+ const info = await (0, promises_1.stat)(path);
71
+ if (!info.isFile())
72
+ throw new errors_1.CliUsageError(`${path} is not a file.`);
73
+ size = info.size;
74
+ }
75
+ catch (err) {
76
+ if (err instanceof errors_1.CliUsageError)
77
+ throw err;
78
+ throw new errors_1.CliUsageError(`Cannot read ${path}: ${err.code ?? err.message}`);
79
+ }
80
+ // Before reading it: a 2 GB video passed by mistake should not be loaded to find out.
81
+ if (size > exports.MAX_UPLOAD_BYTES) {
82
+ throw new errors_1.CliUsageError(`${path} is ${megabytes(size)}; an upload is at most ${exports.MAX_UPLOAD_BYTES / (1024 * 1024)} MB.`);
83
+ }
84
+ if (size === 0)
85
+ throw new errors_1.CliUsageError(`${path} is empty.`);
86
+ const handle = await (0, promises_1.open)(path, "r");
87
+ let bytes;
88
+ try {
89
+ bytes = new Uint8Array(await handle.readFile());
90
+ }
91
+ finally {
92
+ await handle.close();
93
+ }
94
+ if (bytes.length > exports.MAX_UPLOAD_BYTES) {
95
+ throw new errors_1.CliUsageError(`${path} is ${megabytes(bytes.length)}; an upload is at most ${exports.MAX_UPLOAD_BYTES / (1024 * 1024)} MB.`);
96
+ }
97
+ const byExtension = BY_EXTENSION[(0, node_path_1.extname)(path).toLowerCase()];
98
+ const sniffed = sniffImageType(bytes);
99
+ // An ISO-BMFF file (ftyp box) named .avif whose brands this check does not list.
100
+ const contentType = sniffed ?? (byExtension === "image/avif" && ascii(bytes, 4, 8) === "ftyp" ? byExtension : null);
101
+ if (!contentType) {
102
+ throw new errors_1.CliUsageError(`${path} is not a JPEG, PNG, WebP, GIF or AVIF image; nothing else can be shown.`);
103
+ }
104
+ return { path, fileName: (0, node_path_1.basename)(path), contentType, bytes };
105
+ }
106
+ /** `POST /uploads` -> PUT to the signed URL -> `POST /uploads/:id/complete`. The new media asset's id. */
107
+ async function uploadImage(client, groupId, image) {
108
+ const ticket = await client.post("/uploads", {
109
+ fileName: image.fileName,
110
+ contentType: image.contentType,
111
+ size: image.bytes.length,
112
+ groupId,
113
+ });
114
+ await client.putBytes(ticket.uploadUrl, ticket.headers ?? { "Content-Type": image.contentType }, image.bytes);
115
+ await client.post(`/uploads/${ticket.mediaAssetId}/complete`);
116
+ return ticket.mediaAssetId;
117
+ }
118
+ /**
119
+ * `![alt](media:<id>)`, the wiki's image reference. The alt text is the file
120
+ * name; a `]` or line break in it would end the reference early.
121
+ */
122
+ function wikiImageRef(alt, mediaAssetId) {
123
+ const clean = alt.replace(/[[\]\r\n]+/g, " ").replace(/\s+/g, " ").trim();
124
+ return `![${clean}](media:${mediaAssetId})`;
125
+ }
126
+ //# sourceMappingURL=image-upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-upload.js","sourceRoot":"","sources":["../../src/lib/image-upload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeJwtPayloadUnsafe = decodeJwtPayloadUnsafe;
4
+ /**
5
+ * Reads the payload of a JWT without verifying its signature -- fine here
6
+ * because we never trust this for authorization (the server does that on
7
+ * every request), only to show a human "signed in as ...". Used for the
8
+ * `--token`/`WHATCHA_TOKEN` escape hatch (issue #1428): `POST /auth/refresh`
9
+ * returns only `accessToken`/`expiresIn`/`refreshToken`, no user object, so
10
+ * this is the only way to get an email/id to display after that path.
11
+ */
12
+ function decodeJwtPayloadUnsafe(token) {
13
+ const parts = token.split(".");
14
+ if (parts.length !== 3)
15
+ return null;
16
+ try {
17
+ const json = Buffer.from(parts[1].replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8");
18
+ const parsed = JSON.parse(json);
19
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ //# sourceMappingURL=jwt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../src/lib/jwt.ts"],"names":[],"mappings":";;AAQA,wDAUC;AAlBD;;;;;;;GAOG;AACH,SAAgB,sBAAsB,CAAC,KAAa;IAClD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACpG,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,IAAI,CAAC;IACpG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printJson = printJson;
4
+ exports.printLine = printLine;
5
+ exports.printErrorLine = printErrorLine;
6
+ /**
7
+ * The `--json` contract (see README.md): every command prints exactly one
8
+ * JSON object to stdout on success, `{"ok": true, ...command-specific
9
+ * fields}`, and `{"ok": false, "error": {"code", "message"}}` on failure --
10
+ * always to stdout, even for an error, so a script doing `whatcha ... --json
11
+ * | jq` gets parseable JSON either way instead of having to also capture
12
+ * stderr. Human-mode output goes to stdout on success and stderr on
13
+ * failure, and is not meant to be parsed by anything.
14
+ *
15
+ * Never pass an access/refresh token or a password through this -- see
16
+ * README.md's "--json contract" section. Nothing in src/commands builds a
17
+ * JSON payload from a StoredSession or a LoginResult directly; they pick
18
+ * the fields by hand.
19
+ */
20
+ function printJson(value) {
21
+ process.stdout.write(JSON.stringify(value) + "\n");
22
+ }
23
+ function printLine(text) {
24
+ process.stdout.write(text + "\n");
25
+ }
26
+ function printErrorLine(text) {
27
+ process.stderr.write(text + "\n");
28
+ }
29
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":";;AAcA,8BAEC;AAED,8BAEC;AAED,wCAEC;AAxBD;;;;;;;;;;;;;GAaG;AACH,SAAgB,SAAS,CAAC,KAA8B;IACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,SAAgB,SAAS,CAAC,IAAY;IACpC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,SAAgB,cAAc,CAAC,IAAY;IACzC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WHERE_KEYS_ARE_ISSUED = exports.PERSONAL_ACCESS_TOKEN_PREFIX = void 0;
4
+ exports.looksLikePersonalAccessToken = looksLikePersonalAccessToken;
5
+ exports.isWellFormedPersonalAccessToken = isWellFormedPersonalAccessToken;
6
+ exports.translateKeyError = translateKeyError;
7
+ const errors_1 = require("../errors");
8
+ /**
9
+ * Personal access keys (#1469, server side in PR #1550; CLI side #1663).
10
+ *
11
+ * The server tells a key from a Supabase session JWT by this literal prefix
12
+ * alone (apps/api/src/modules/account/auth/access-tokens.ts,
13
+ * `looksLikeAccessToken`), and so does the CLI: a key is sent as-is as the
14
+ * Bearer credential, never refreshed, and never exchanged for anything.
15
+ */
16
+ exports.PERSONAL_ACCESS_TOKEN_PREFIX = "whatcha_pat_";
17
+ /** `whatcha_pat_<24 hex>_<64 hex>` -- the server's own WIRE_FORM. */
18
+ const WIRE_FORM = /^whatcha_pat_[0-9a-f]{24}_[0-9a-f]{64}$/;
19
+ function looksLikePersonalAccessToken(raw) {
20
+ return raw.trim().startsWith(exports.PERSONAL_ACCESS_TOKEN_PREFIX);
21
+ }
22
+ function isWellFormedPersonalAccessToken(raw) {
23
+ return WIRE_FORM.test(raw.trim());
24
+ }
25
+ exports.WHERE_KEYS_ARE_ISSUED = "Profile → Connected apps and keys";
26
+ /**
27
+ * A key's refusals, said in terms of the key.
28
+ *
29
+ * The API answers a revoked key exactly like one that never existed -- 401
30
+ * UNAUTHENTICATED, "Invalid access token." -- on purpose (revocation deletes
31
+ * the row; acceptAccessToken). For a session that means "sign in again"; for
32
+ * a key the only fix is a new key, so a script gets a code it can branch on
33
+ * instead of the generic one. Expiry (TOKEN_EXPIRED) likewise.
34
+ */
35
+ function translateKeyError(err) {
36
+ if (!(err instanceof errors_1.ApiError))
37
+ return err;
38
+ if (err.status === 401 && err.code === "TOKEN_EXPIRED") {
39
+ return new errors_1.ApiError(401, "KEY_EXPIRED", `This personal access key has expired. Issue a new one in ${exports.WHERE_KEYS_ARE_ISSUED}, then run \`whatcha login --key <key>\`.`);
40
+ }
41
+ if (err.status === 401) {
42
+ return new errors_1.ApiError(401, "KEY_REVOKED", `This personal access key was revoked or is not valid. Issue a new one in ${exports.WHERE_KEYS_ARE_ISSUED}, then run \`whatcha login --key <key>\`.`);
43
+ }
44
+ return err;
45
+ }
46
+ //# sourceMappingURL=personal-access-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"personal-access-token.js","sourceRoot":"","sources":["../../src/lib/personal-access-token.ts"],"names":[],"mappings":";;;AAeA,oEAEC;AAED,0EAEC;AAaD,8CAiBC;AAnDD,sCAAqC;AAErC;;;;;;;GAOG;AACU,QAAA,4BAA4B,GAAG,cAAc,CAAC;AAE3D,qEAAqE;AACrE,MAAM,SAAS,GAAG,yCAAyC,CAAC;AAE5D,SAAgB,4BAA4B,CAAC,GAAW;IACtD,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,oCAA4B,CAAC,CAAC;AAC7D,CAAC;AAED,SAAgB,+BAA+B,CAAC,GAAW;IACzD,OAAO,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;AACpC,CAAC;AAEY,QAAA,qBAAqB,GAAG,mCAAmC,CAAC;AAEzE;;;;;;;;GAQG;AACH,SAAgB,iBAAiB,CAAC,GAAY;IAC5C,IAAI,CAAC,CAAC,GAAG,YAAY,iBAAQ,CAAC;QAAE,OAAO,GAAG,CAAC;IAC3C,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;QACvD,OAAO,IAAI,iBAAQ,CACjB,GAAG,EACH,aAAa,EACb,4DAA4D,6BAAqB,2CAA2C,CAC7H,CAAC;IACJ,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QACvB,OAAO,IAAI,iBAAQ,CACjB,GAAG,EACH,aAAa,EACb,4EAA4E,6BAAqB,2CAA2C,CAC7I,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findProfile = findProfile;
4
+ exports.bearerClients = bearerClients;
5
+ exports.clientAsProfile = clientAsProfile;
6
+ const http_client_1 = require("../http-client");
7
+ /** A profile of this login by @handle (the `@` optional, case-insensitive) or id. */
8
+ function findProfile(profiles, target) {
9
+ const wanted = target.replace(/^@/, "").toLowerCase();
10
+ return profiles.find((p) => p.id === target || (p.username ?? "").toLowerCase() === wanted);
11
+ }
12
+ function bearerClients(baseUrl) {
13
+ return (accessToken) => new http_client_1.FetchWhatchaClient(baseUrl, async () => `Bearer ${accessToken}`);
14
+ }
15
+ /**
16
+ * A client that IS one of this login's sub-profiles, for the rest of one
17
+ * command -- the session `whatcha switch` would save, used and dropped
18
+ * instead. Some things only a profile can do for itself (set its own
19
+ * avatar, accept an invite into a group), and the saved session must not
20
+ * change under a command that was not asked to switch.
21
+ *
22
+ * The API reaches only sub-profiles of the caller's own family, never the
23
+ * account itself; its refusal is passed on as it came.
24
+ */
25
+ async function clientAsProfile(client, profileId, forToken) {
26
+ const issued = await client.post(`/me/profiles/${profileId}/session`, {});
27
+ return forToken(issued.accessToken);
28
+ }
29
+ //# sourceMappingURL=profile-family.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-family.js","sourceRoot":"","sources":["../../src/lib/profile-family.ts"],"names":[],"mappings":";;AA+BA,kCAGC;AAKD,sCAEC;AAYD,0CAOC;AA5DD,gDAAwE;AA8BxE,qFAAqF;AACrF,SAAgB,WAAW,CAAC,QAA6B,EAAE,MAAc;IACvE,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACtD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,CAAC;AAC9F,CAAC;AAKD,SAAgB,aAAa,CAAC,OAAe;IAC3C,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,IAAI,gCAAkB,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,UAAU,WAAW,EAAE,CAAC,CAAC;AAC/F,CAAC;AAED;;;;;;;;;GASG;AACI,KAAK,UAAU,eAAe,CACnC,MAAqB,EACrB,SAAiB,EACjB,QAAwB;IAExB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAiB,gBAAgB,SAAS,UAAU,EAAE,EAAE,CAAC,CAAC;IAC1F,OAAO,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.publicDiaryUrl = publicDiaryUrl;
4
+ exports.publicDiaryIds = publicDiaryIds;
5
+ /**
6
+ * Whether anybody, signed in or not, can read a diary -- and where (#1701).
7
+ *
8
+ * Publishing belongs to the group, not the diary: `PUT /groups/:id/publication`
9
+ * (owner only; the app's "Publish" card on a group's page) makes a shared
10
+ * group public, and with it every diary in it that the group's members can
11
+ * read. A diary kept to its creator (`visibility: "private"`) stays private
12
+ * inside a published group. That is the API's own test (PUBLIC_SCOPE in
13
+ * public-square-repository.ts), read here from what the app reads: the
14
+ * group's `isPublic` from `GET /groups`, the diary's `visibility` from
15
+ * `GET /diaries/:id`.
16
+ */
17
+ /** The web page a published diary is read at: the site's origin, which is the API's (`/api/v1` hangs off it). */
18
+ function publicDiaryUrl(apiBaseUrl, diaryId) {
19
+ return `${new URL(apiBaseUrl).origin}/diaries/${diaryId}`;
20
+ }
21
+ async function publishedGroupIds(client) {
22
+ const ids = new Set();
23
+ let cursor;
24
+ for (;;) {
25
+ const page = await client.getPage("/groups", { limit: "50", cursor });
26
+ for (const g of page.items)
27
+ if (g.isPublic)
28
+ ids.add(g.id);
29
+ if (!page.hasNext || !page.nextCursor)
30
+ break;
31
+ cursor = page.nextCursor;
32
+ }
33
+ return ids;
34
+ }
35
+ /**
36
+ * The ids of the diaries among `diaries` that are public. One `GET /groups`
37
+ * walk, plus one `GET /diaries/:id` per diary in a published group only --
38
+ * a shelf with nothing published costs a single extra call.
39
+ */
40
+ async function publicDiaryIds(client, diaries) {
41
+ const published = new Set();
42
+ const shared = diaries.filter((d) => d.groupType !== "personal");
43
+ if (shared.length === 0)
44
+ return published;
45
+ const groups = await publishedGroupIds(client);
46
+ for (const d of shared) {
47
+ if (!groups.has(d.groupId))
48
+ continue;
49
+ const detail = await client.get(`/diaries/${d.id}`);
50
+ if (detail.visibility === "group")
51
+ published.add(d.id);
52
+ }
53
+ return published;
54
+ }
55
+ //# sourceMappingURL=publication.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publication.js","sourceRoot":"","sources":["../../src/lib/publication.ts"],"names":[],"mappings":";;AAiBA,wCAEC;AAwBD,wCAWC;AAnDD;;;;;;;;;;;GAWG;AAEH,iHAAiH;AACjH,SAAgB,cAAc,CAAC,UAAkB,EAAE,OAAe;IAChE,OAAO,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,MAAM,YAAY,OAAO,EAAE,CAAC;AAC5D,CAAC;AAOD,KAAK,UAAU,iBAAiB,CAAC,MAAqB;IACpD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,IAAI,MAA0B,CAAC;IAC/B,SAAS,CAAC;QACR,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,OAAO,CAAgB,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACrF,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,CAAC,QAAQ;gBAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1D,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,MAAM;QAC7C,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;IAC3B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,cAAc,CAAC,MAAqB,EAAE,OAAgB;IAC1E,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC;IACjE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC/C,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC;YAAE,SAAS;QACrC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,CAAsC,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzF,IAAI,MAAM,CAAC,UAAU,KAAK,OAAO;YAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ // Wire shapes this CLI actually touches, trimmed from apps/api/openapi.yaml.
3
+ // Not the whole API -- just the fields the four commands read or send. Keep
4
+ // these in sync with openapi.yaml by hand; there is no codegen step here.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,6EAA6E;AAC7E,4EAA4E;AAC5E,0EAA0E"}
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "@whatcha/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
4
+ "description": "Official command-line client for what-are-you-do-ing (whatareyoudo.ing) -- login (password or personal access key), list diaries, write and read entries, switch profiles, read and write the Experience Wiki, with images.",
5
+ "license": "Apache-2.0",
6
+ "homepage": "https://whatareyoudo.ing/cli",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/what-are-you-do-ing/whatcha.git",
10
+ "directory": "apps/cli"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/what-are-you-do-ing/whatcha/issues"
14
+ },
15
+ "keywords": [
16
+ "whatcha",
17
+ "diary",
18
+ "journal",
19
+ "cli",
20
+ "whatareyoudo"
21
+ ],
22
+ "bin": {
23
+ "whatcha": "./dist/index.js"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "engines": {
31
+ "node": ">=22"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "scripts": {
37
+ "build": "node node_modules/typescript/bin/tsc",
38
+ "typecheck": "node node_modules/typescript/bin/tsc --noEmit",
39
+ "test": "vitest run",
40
+ "test:watch": "vitest",
41
+ "prepublishOnly": "npm run build"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.10.0",
45
+ "typescript": "^5.5.2",
46
+ "vitest": "^4.1.10"
47
+ }
48
+ }