@whatcha/cli 0.0.0-stage → 0.1.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 +184 -2
- package/dist/auth/gotrue-client.js +64 -0
- package/dist/auth/gotrue-client.js.map +1 -0
- package/dist/auth/session.js +97 -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 +103 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/diaries.js +41 -0
- package/dist/commands/diaries.js.map +1 -0
- package/dist/commands/login.js +217 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/profiles.js +176 -0
- package/dist/commands/profiles.js.map +1 -0
- package/dist/commands/read.js +77 -0
- package/dist/commands/read.js.map +1 -0
- package/dist/commands/write.js +75 -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 +86 -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 +141 -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 +76 -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/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/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +46 -4
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeEntry = writeEntry;
|
|
4
|
+
const errors_1 = require("../errors");
|
|
5
|
+
function existingEntryIdFrom(err) {
|
|
6
|
+
if (err.status !== 409 || err.code !== "VERSION_CONFLICT")
|
|
7
|
+
return null;
|
|
8
|
+
const details = err.details;
|
|
9
|
+
return typeof details?.existingEntryId === "string" ? details.existingEntryId : null;
|
|
10
|
+
}
|
|
11
|
+
function headerOf(entry) {
|
|
12
|
+
return {
|
|
13
|
+
title: entry.title,
|
|
14
|
+
mood: entry.mood,
|
|
15
|
+
location: entry.location,
|
|
16
|
+
tags: entry.tags,
|
|
17
|
+
coverMediaAssetId: entry.coverMediaAssetId,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* What the stored body should read as after appending `text`, mirroring the
|
|
22
|
+
* API's own `appendedBody` (apps/api/src/modules/diaries/entries/
|
|
23
|
+
* entries-content.ts, merged to master in #1424): existing text, trimmed,
|
|
24
|
+
* then a blank line, then the new text -- or just the new text if there was
|
|
25
|
+
* none yet.
|
|
26
|
+
*/
|
|
27
|
+
function expectedAppendedBody(existingBody, text) {
|
|
28
|
+
const trimmed = existingBody.trimEnd();
|
|
29
|
+
return trimmed ? `${trimmed}\n\n${text}` : text;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The one thing this whole command exists to get right: add `text` to
|
|
33
|
+
* `diaryId`'s entry for `occurredAt`'s day without ever touching what
|
|
34
|
+
* somebody else already wrote there.
|
|
35
|
+
*
|
|
36
|
+
* Three paths, in order:
|
|
37
|
+
*
|
|
38
|
+
* 1. No entry exists yet for that day -- `POST /entries` creates it with
|
|
39
|
+
* exactly `text` as its body. Nothing to append to, nothing at risk.
|
|
40
|
+
* 2. An entry exists (`POST` answers 409 with `details.existingEntryId`).
|
|
41
|
+
* Read it (`GET /entries/{id}`) to get its current header fields --
|
|
42
|
+
* title/mood/location/tags/cover are full-replace-on-save same as the
|
|
43
|
+
* body (entries-content.ts's `contentToItems`), so sending `content`
|
|
44
|
+
* without them would silently wipe whatever the other person set, even
|
|
45
|
+
* though `appendBody` protects the body itself. Then `PATCH` with
|
|
46
|
+
* `appendBody: true` and only the *new* text as `content.body` -- the
|
|
47
|
+
* server appends it to whatever is currently stored, in the same
|
|
48
|
+
* transaction that checks `expectedHeadCommitId`, so this is safe even
|
|
49
|
+
* against a write that lands between our GET and our PATCH.
|
|
50
|
+
* 3. The PATCH in (2) answers 422 VALIDATION_ERROR citing `appendBody` as
|
|
51
|
+
* an unrecognized key (ApiError#isUnrecognizedAppendBodyFlag) -- the
|
|
52
|
+
* server does not have the #1422 fix. Build the appended body ourselves
|
|
53
|
+
* from the text we just read in (2) and PATCH again without the flag,
|
|
54
|
+
* still carrying `expectedHeadCommitId` so a concurrent write is caught
|
|
55
|
+
* as a conflict rather than silently overwritten. This is the read
|
|
56
|
+
* that "fall back to read-then-append" refers to; note it is the *same*
|
|
57
|
+
* read as (2), not a second one -- appendBody is tried first precisely
|
|
58
|
+
* so the common case does not pay for a read it does not need.
|
|
59
|
+
*
|
|
60
|
+
* Either way, nothing is ever PATCHed as a plain replacement of a body this
|
|
61
|
+
* function did not just read.
|
|
62
|
+
*/
|
|
63
|
+
async function writeEntry(client, diaryId, occurredAt, text) {
|
|
64
|
+
try {
|
|
65
|
+
const created = await client.post("/entries", {
|
|
66
|
+
diaryId,
|
|
67
|
+
occurredAt: occurredAt.toISOString(),
|
|
68
|
+
content: { body: text },
|
|
69
|
+
});
|
|
70
|
+
return { entryId: created.id, headCommitId: created.headCommitId, mode: "created", verified: null };
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (!(err instanceof errors_1.ApiError))
|
|
74
|
+
throw err;
|
|
75
|
+
const existingEntryId = existingEntryIdFrom(err);
|
|
76
|
+
if (!existingEntryId)
|
|
77
|
+
throw err;
|
|
78
|
+
return appendToExisting(client, existingEntryId, text);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function appendToExisting(client, entryId, text) {
|
|
82
|
+
const existing = await client.get(`/entries/${entryId}`);
|
|
83
|
+
const header = headerOf(existing);
|
|
84
|
+
try {
|
|
85
|
+
const saved = await client.patch(`/entries/${entryId}`, {
|
|
86
|
+
content: { body: text, ...header },
|
|
87
|
+
appendBody: true,
|
|
88
|
+
expectedHeadCommitId: existing.headCommitId,
|
|
89
|
+
});
|
|
90
|
+
const verified = await verifyAppend(client, entryId, existing.body, text);
|
|
91
|
+
return { entryId, headCommitId: saved.headCommitId, mode: "appended", verified };
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (!(err instanceof errors_1.ApiError))
|
|
95
|
+
throw err;
|
|
96
|
+
if (err.status === 409)
|
|
97
|
+
throw concurrentWriteError(err);
|
|
98
|
+
if (!err.isUnrecognizedAppendBodyFlag())
|
|
99
|
+
throw err;
|
|
100
|
+
// Fallback: this server does not know appendBody. Merge client-side
|
|
101
|
+
// from the body we already read, and send the merged body as a plain
|
|
102
|
+
// replacement -- which is safe precisely because we just read what it
|
|
103
|
+
// is replacing.
|
|
104
|
+
const mergedBody = expectedAppendedBody(existing.body, text);
|
|
105
|
+
const saved = await client
|
|
106
|
+
.patch(`/entries/${entryId}`, {
|
|
107
|
+
content: { body: mergedBody, ...header },
|
|
108
|
+
expectedHeadCommitId: existing.headCommitId,
|
|
109
|
+
})
|
|
110
|
+
.catch((fallbackErr) => {
|
|
111
|
+
if (fallbackErr instanceof errors_1.ApiError && fallbackErr.status === 409)
|
|
112
|
+
throw concurrentWriteError(fallbackErr);
|
|
113
|
+
throw fallbackErr;
|
|
114
|
+
});
|
|
115
|
+
const verified = await verifyAppend(client, entryId, existing.body, text);
|
|
116
|
+
return { entryId, headCommitId: saved.headCommitId, mode: "appended-fallback", verified };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function concurrentWriteError(err) {
|
|
120
|
+
return new errors_1.CliUsageError("Someone else saved to this entry while whatcha was writing to it. Nothing was overwritten -- " +
|
|
121
|
+
`run \`whatcha write\` again to retry. (${err.message})`);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Defense in depth against the exact failure #1422 was about, for the one
|
|
125
|
+
* case none of the above can rule out: a server old enough that it neither
|
|
126
|
+
* knows `appendBody` NOR rejects the unrecognized key (STRICT_REQUEST_BODIES
|
|
127
|
+
* off, which is the API's own default -- apps/api/src/config/index.ts). Such
|
|
128
|
+
* a server answers 200 having silently replaced the body instead of
|
|
129
|
+
* appending, and there is no error to catch.
|
|
130
|
+
*
|
|
131
|
+
* So: read the entry back and check the stored body actually starts with
|
|
132
|
+
* what was there before. This cannot repair a loss it finds -- the PATCH
|
|
133
|
+
* already committed -- but it turns a silent data loss into a loud one, and
|
|
134
|
+
* `whatcha write --json` surfaces `verified: false` so an agent calling
|
|
135
|
+
* this does not mistake a corrupted write for a clean one.
|
|
136
|
+
*/
|
|
137
|
+
async function verifyAppend(client, entryId, bodyBeforeWrite, text) {
|
|
138
|
+
const after = await client.get(`/entries/${entryId}`);
|
|
139
|
+
return after.body === expectedAppendedBody(bodyBeforeWrite, text);
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=append-entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"append-entry.js","sourceRoot":"","sources":["../../src/lib/append-entry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveDiary = resolveDiary;
|
|
4
|
+
exports.listAllDiaries = listAllDiaries;
|
|
5
|
+
const errors_1 = require("../errors");
|
|
6
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
7
|
+
/**
|
|
8
|
+
* `<diary>` on the command line -- a diary id, or a name to look up against
|
|
9
|
+
* `GET /diaries` (list, not search: the API also offers `?q=` substring
|
|
10
|
+
* search, but that is for the app's search box, not for turning a name a
|
|
11
|
+
* person typed back into exactly the diary they meant, so this matches by
|
|
12
|
+
* exact name, case-insensitively, and refuses to guess when more than one
|
|
13
|
+
* diary shares a name).
|
|
14
|
+
*/
|
|
15
|
+
async function resolveDiary(client, nameOrId) {
|
|
16
|
+
if (UUID_RE.test(nameOrId)) {
|
|
17
|
+
try {
|
|
18
|
+
return await client.get(`/diaries/${nameOrId}`);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
if (err instanceof errors_1.ApiError && err.status === 404) {
|
|
22
|
+
throw new errors_1.CliUsageError(`No diary with id ${nameOrId} (or it is not visible to you).`);
|
|
23
|
+
}
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const all = await listAllDiaries(client);
|
|
28
|
+
const matches = all.filter((d) => d.name.toLowerCase() === nameOrId.toLowerCase());
|
|
29
|
+
if (matches.length === 1)
|
|
30
|
+
return matches[0];
|
|
31
|
+
if (matches.length > 1) {
|
|
32
|
+
const ids = matches.map((d) => ` ${d.id} ${d.name} (${d.groupName})`).join("\n");
|
|
33
|
+
throw new errors_1.CliUsageError(`"${nameOrId}" matches more than one diary -- use the id instead:\n${ids}`);
|
|
34
|
+
}
|
|
35
|
+
const known = all.map((d) => d.name).join(", ") || "(none)";
|
|
36
|
+
throw new errors_1.CliUsageError(`No diary named "${nameOrId}". Diaries you can see: ${known}`);
|
|
37
|
+
}
|
|
38
|
+
async function listAllDiaries(client) {
|
|
39
|
+
const all = [];
|
|
40
|
+
let cursor;
|
|
41
|
+
for (;;) {
|
|
42
|
+
const page = await client.getPage("/diaries", { limit: "50", cursor });
|
|
43
|
+
all.push(...page.items);
|
|
44
|
+
if (!page.hasNext || !page.nextCursor)
|
|
45
|
+
break;
|
|
46
|
+
cursor = page.nextCursor;
|
|
47
|
+
}
|
|
48
|
+
return all;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=diary-resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diary-resolve.js","sourceRoot":"","sources":["../../src/lib/diary-resolve.ts"],"names":[],"mappings":";;AAcA,oCAqBC;AAED,wCAUC;AA/CD,sCAAoD;AAIpD,MAAM,OAAO,GAAG,iEAAiE,CAAC;AAElF;;;;;;;GAOG;AACI,KAAK,UAAU,YAAY,CAAC,MAAqB,EAAE,QAAgB;IACxE,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,CAAC,GAAG,CAAQ,YAAY,QAAQ,EAAE,CAAC,CAAC;QACzD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,iBAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAClD,MAAM,IAAI,sBAAa,CAAC,oBAAoB,QAAQ,iCAAiC,CAAC,CAAC;YACzF,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IACnF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnF,MAAM,IAAI,sBAAa,CAAC,IAAI,QAAQ,yDAAyD,GAAG,EAAE,CAAC,CAAC;IACtG,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC;IAC5D,MAAM,IAAI,sBAAa,CAAC,mBAAmB,QAAQ,2BAA2B,KAAK,EAAE,CAAC,CAAC;AACzF,CAAC;AAEM,KAAK,UAAU,cAAc,CAAC,MAAqB;IACxD,MAAM,GAAG,GAAY,EAAE,CAAC;IACxB,IAAI,MAA0B,CAAC;IAC/B,SAAS,CAAC;QACR,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,OAAO,CAAQ,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9E,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertValidDate = assertValidDate;
|
|
4
|
+
exports.assertValidTime = assertValidTime;
|
|
5
|
+
exports.dateKeyInTimeZone = dateKeyInTimeZone;
|
|
6
|
+
exports.timeKeyInTimeZone = timeKeyInTimeZone;
|
|
7
|
+
exports.zonedTimeToUtc = zonedTimeToUtc;
|
|
8
|
+
exports.resolveOccurredAt = resolveOccurredAt;
|
|
9
|
+
const errors_1 = require("../errors");
|
|
10
|
+
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
11
|
+
const TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
12
|
+
function assertValidDate(date) {
|
|
13
|
+
if (!DATE_RE.test(date))
|
|
14
|
+
throw new errors_1.CliUsageError(`--date must be YYYY-MM-DD, got "${date}".`);
|
|
15
|
+
// DATE_RE only checks the shape; Date.UTC quietly rolls "2026-02-30" into
|
|
16
|
+
// March, so check the round-trip too rather than writing to the wrong day.
|
|
17
|
+
const [y, m, d] = date.split("-").map(Number);
|
|
18
|
+
const roundTrip = new Date(Date.UTC(y, m - 1, d));
|
|
19
|
+
if (roundTrip.getUTCFullYear() !== y || roundTrip.getUTCMonth() !== m - 1 || roundTrip.getUTCDate() !== d) {
|
|
20
|
+
throw new errors_1.CliUsageError(`--date "${date}" is not a real calendar date.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function assertValidTime(time) {
|
|
24
|
+
if (!TIME_RE.test(time))
|
|
25
|
+
throw new errors_1.CliUsageError(`--time must be HH:MM (24h), got "${time}".`);
|
|
26
|
+
}
|
|
27
|
+
/** "YYYY-MM-DD" for `date`, as read in `timeZone` -- what day the diary's own §11.4 uniqueness check would see. */
|
|
28
|
+
function dateKeyInTimeZone(date, timeZone) {
|
|
29
|
+
// en-CA formats as YYYY-MM-DD, which is also the wire format --
|
|
30
|
+
// avoids hand-assembling the string from getters that don't accept a tz.
|
|
31
|
+
return new Intl.DateTimeFormat("en-CA", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" }).format(date);
|
|
32
|
+
}
|
|
33
|
+
/** "HH:MM" for `date`, as read in `timeZone`. Used to default --time to "now" without a second copy of the format string. */
|
|
34
|
+
function timeKeyInTimeZone(date, timeZone) {
|
|
35
|
+
return new Intl.DateTimeFormat("en-GB", {
|
|
36
|
+
timeZone,
|
|
37
|
+
hour: "2-digit",
|
|
38
|
+
minute: "2-digit",
|
|
39
|
+
hourCycle: "h23",
|
|
40
|
+
}).format(date);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The inverse of the two functions above: the UTC instant that reads as
|
|
44
|
+
* `dateStr` `timeStr` when displayed in `timeZone`.
|
|
45
|
+
*
|
|
46
|
+
* There is no built-in for this (Intl only goes UTC -> zoned, never back),
|
|
47
|
+
* so this uses the standard trick: read `dateStr timeStr` as if it were
|
|
48
|
+
* already UTC, ask what that UTC instant looks like in `timeZone`, and shift
|
|
49
|
+
* by the difference. Good to the minute, which is all `--time` offers;
|
|
50
|
+
* degrades right at a DST transition (an hour that either repeats or does
|
|
51
|
+
* not exist), which is an acceptable rough edge for a diary entry's
|
|
52
|
+
* timestamp and not worth a timezone-database dependency for.
|
|
53
|
+
*/
|
|
54
|
+
function zonedTimeToUtc(dateStr, timeStr, timeZone) {
|
|
55
|
+
const asIfUtc = new Date(`${dateStr}T${timeStr}:00.000Z`);
|
|
56
|
+
const readInZone = new Date(asIfUtc.toLocaleString("en-US", { timeZone }));
|
|
57
|
+
const diff = asIfUtc.getTime() - readInZone.getTime();
|
|
58
|
+
return new Date(asIfUtc.getTime() + diff);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolves `--date`/`--time` into the `occurredAt` instant to send the API,
|
|
62
|
+
* defaulting both to "now" in the diary's own timezone -- not the machine's,
|
|
63
|
+
* since §11.4's one-entry-per-day check is computed in the diary's timezone
|
|
64
|
+
* (entries-create-service.ts) and a mismatch is exactly the kind of thing
|
|
65
|
+
* that would land a `--date 2026-09-25` entry on the 24th or 26th instead.
|
|
66
|
+
*/
|
|
67
|
+
function resolveOccurredAt(opts, timeZone, now = new Date()) {
|
|
68
|
+
if (opts.date)
|
|
69
|
+
assertValidDate(opts.date);
|
|
70
|
+
if (opts.time)
|
|
71
|
+
assertValidTime(opts.time);
|
|
72
|
+
const date = opts.date ?? dateKeyInTimeZone(now, timeZone);
|
|
73
|
+
const time = opts.time ?? timeKeyInTimeZone(now, timeZone);
|
|
74
|
+
return zonedTimeToUtc(date, time, timeZone);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=entry-date.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-date.js","sourceRoot":"","sources":["../../src/lib/entry-date.ts"],"names":[],"mappings":";;AAKA,0CASC;AAED,0CAEC;AAGD,8CAMC;AAGD,8CAOC;AAcD,wCAKC;AASD,8CAUC;AA3ED,sCAA0C;AAE1C,MAAM,OAAO,GAAG,qBAAqB,CAAC;AACtC,MAAM,OAAO,GAAG,6BAA6B,CAAC;AAE9C,SAAgB,eAAe,CAAC,IAAY;IAC1C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,sBAAa,CAAC,mCAAmC,IAAI,IAAI,CAAC,CAAC;IAC9F,0EAA0E;IAC1E,2EAA2E;IAC3E,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAClD,IAAI,SAAS,CAAC,cAAc,EAAE,KAAK,CAAC,IAAI,SAAS,CAAC,WAAW,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;QAC1G,MAAM,IAAI,sBAAa,CAAC,WAAW,IAAI,gCAAgC,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC;AAED,SAAgB,eAAe,CAAC,IAAY;IAC1C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,sBAAa,CAAC,oCAAoC,IAAI,IAAI,CAAC,CAAC;AACjG,CAAC;AAED,mHAAmH;AACnH,SAAgB,iBAAiB,CAAC,IAAU,EAAE,QAAgB;IAC5D,gEAAgE;IAChE,yEAAyE;IACzE,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAC7G,IAAI,CACL,CAAC;AACJ,CAAC;AAED,6HAA6H;AAC7H,SAAgB,iBAAiB,CAAC,IAAU,EAAE,QAAgB;IAC5D,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;QACtC,QAAQ;QACR,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,SAAS;QACjB,SAAS,EAAE,KAAK;KACjB,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,cAAc,CAAC,OAAe,EAAE,OAAe,EAAE,QAAgB;IAC/E,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,GAAG,OAAO,IAAI,OAAO,UAAU,CAAC,CAAC;IAC1D,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC3E,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC;IACtD,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAC/B,IAAsC,EACtC,QAAgB,EAChB,MAAY,IAAI,IAAI,EAAE;IAEtB,IAAI,IAAI,CAAC,IAAI;QAAE,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,IAAI,CAAC,IAAI;QAAE,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC3D,OAAO,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findEntryOnDate = findEntryOnDate;
|
|
4
|
+
const entry_date_1 = require("./entry-date");
|
|
5
|
+
const PAGE_LIMIT = 50;
|
|
6
|
+
/** Ten pages is 500 entries -- generous for "find the one entry on this diary-day" without paging forever through years of history for a typo'd date. */
|
|
7
|
+
const MAX_PAGES = 10;
|
|
8
|
+
/**
|
|
9
|
+
* `GET /entries` has no `?date=`, only cursor pagination newest-first
|
|
10
|
+
* (entries-query-service.ts's `listEntriesPage`, `orderBy: occurred_at
|
|
11
|
+
* desc`). Since there is at most one entry per diary-day (§11.4), walking
|
|
12
|
+
* that list until an entry's day (in the diary's own timezone) matches or
|
|
13
|
+
* we pass it is exact, not a heuristic -- it just is not O(1). Returns null
|
|
14
|
+
* if the diary has no entry on that day, or if it is farther back than
|
|
15
|
+
* MAX_PAGES cover.
|
|
16
|
+
*/
|
|
17
|
+
async function findEntryOnDate(client, diaryId, timeZone, dateKey) {
|
|
18
|
+
let cursor;
|
|
19
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
20
|
+
const result = await client.getPage("/entries", {
|
|
21
|
+
diaryId,
|
|
22
|
+
limit: String(PAGE_LIMIT),
|
|
23
|
+
cursor,
|
|
24
|
+
});
|
|
25
|
+
for (const entry of result.items) {
|
|
26
|
+
const entryDateKey = (0, entry_date_1.dateKeyInTimeZone)(new Date(entry.occurredAt), timeZone);
|
|
27
|
+
if (entryDateKey === dateKey)
|
|
28
|
+
return entry;
|
|
29
|
+
// Newest-first: once we're past the target day, it is not coming back.
|
|
30
|
+
if (entryDateKey < dateKey)
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (!result.hasNext || !result.nextCursor)
|
|
34
|
+
return null;
|
|
35
|
+
cursor = result.nextCursor;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=entry-find.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-find.js","sourceRoot":"","sources":["../../src/lib/entry-find.ts"],"names":[],"mappings":";;AAiBA,0CAuBC;AAtCD,6CAAiD;AAEjD,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,yJAAyJ;AACzJ,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB;;;;;;;;GAQG;AACI,KAAK,UAAU,eAAe,CACnC,MAAqB,EACrB,OAAe,EACf,QAAgB,EAChB,OAAe;IAEf,IAAI,MAA0B,CAAC;IAC/B,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAgB,UAAU,EAAE;YAC7D,OAAO;YACP,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC;YACzB,MAAM;SACP,CAAC,CAAC;QACH,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjC,MAAM,YAAY,GAAG,IAAA,8BAAiB,EAAC,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAC;YAC7E,IAAI,YAAY,KAAK,OAAO;gBAAE,OAAO,KAAK,CAAC;YAC3C,uEAAuE;YACvE,IAAI,YAAY,GAAG,OAAO;gBAAE,OAAO,IAAI,CAAC;QAC1C,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QACvD,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC;IAC7B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
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"}
|
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.1.0",
|
|
4
|
+
"description": "Official command-line client for what-are-you-do-ing (whatareyoudo.ing) -- login, list diaries, write and read entries.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://whatareyoudo.ing/cli",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://gitea.lovekapibarasan.org/LoveKapibarasan/what-are-you-do-ing.git",
|
|
10
|
+
"directory": "apps/cli"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://gitea.lovekapibarasan.org/LoveKapibarasan/what-are-you-do-ing/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
|
+
}
|