@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.
- package/LICENSE +202 -0
- package/README.md +345 -2
- package/dist/auth/gotrue-client.js +64 -0
- package/dist/auth/gotrue-client.js.map +1 -0
- package/dist/auth/session.js +147 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/auth/token-store.js +165 -0
- package/dist/auth/token-store.js.map +1 -0
- package/dist/cli.js +134 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/diaries.js +52 -0
- package/dist/commands/diaries.js.map +1 -0
- package/dist/commands/login.js +292 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/profiles.js +287 -0
- package/dist/commands/profiles.js.map +1 -0
- package/dist/commands/publish.js +81 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/read.js +77 -0
- package/dist/commands/read.js.map +1 -0
- package/dist/commands/shared-diary.js +255 -0
- package/dist/commands/shared-diary.js.map +1 -0
- package/dist/commands/whoami.js +50 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/commands/wiki.js +386 -0
- package/dist/commands/wiki.js.map +1 -0
- package/dist/commands/write.js +106 -0
- package/dist/commands/write.js.map +1 -0
- package/dist/config.js +47 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.js +46 -0
- package/dist/errors.js.map +1 -0
- package/dist/http-client.js +102 -0
- package/dist/http-client.js.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/append-entry.js +193 -0
- package/dist/lib/append-entry.js.map +1 -0
- package/dist/lib/diary-resolve.js +50 -0
- package/dist/lib/diary-resolve.js.map +1 -0
- package/dist/lib/entry-date.js +96 -0
- package/dist/lib/entry-date.js.map +1 -0
- package/dist/lib/entry-find.js +39 -0
- package/dist/lib/entry-find.js.map +1 -0
- package/dist/lib/image-upload.js +126 -0
- package/dist/lib/image-upload.js.map +1 -0
- package/dist/lib/jwt.js +25 -0
- package/dist/lib/jwt.js.map +1 -0
- package/dist/lib/output.js +29 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/personal-access-token.js +46 -0
- package/dist/lib/personal-access-token.js.map +1 -0
- package/dist/lib/profile-family.js +29 -0
- package/dist/lib/profile-family.js.map +1 -0
- package/dist/lib/publication.js +55 -0
- package/dist/lib/publication.js.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- 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
|
+
* ``, 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 ``;
|
|
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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"}
|
package/dist/lib/jwt.js
ADDED
|
@@ -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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
}
|