@wirecat/cli-core 0.545.0 → 0.546.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 (88) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/dist/calendar/busy.js +4 -1
  3. package/dist/calendar/cli/answers.d.ts +31 -0
  4. package/dist/calendar/cli/answers.js +8 -0
  5. package/dist/calendar/cli/index.d.ts +6 -0
  6. package/dist/calendar/cli/index.js +43 -4
  7. package/dist/calendar/cli/remind.js +2 -1
  8. package/dist/calendar/expand.d.ts +17 -0
  9. package/dist/calendar/expand.js +340 -0
  10. package/dist/calendar/feeds.d.ts +96 -0
  11. package/dist/calendar/feeds.js +324 -0
  12. package/dist/calendar/ics.d.ts +15 -4
  13. package/dist/calendar/ics.js +18 -7
  14. package/dist/calendar/index.d.ts +3 -1
  15. package/dist/calendar/index.js +2 -0
  16. package/dist/calendar/server-search.js +3 -1
  17. package/dist/calendar/writes.js +15 -4
  18. package/dist/cli/after-dashes.d.ts +16 -0
  19. package/dist/cli/after-dashes.js +41 -0
  20. package/dist/cli/bot/context.d.ts +1 -1
  21. package/dist/cli/bot/context.js +9 -1
  22. package/dist/cli/bot/messages.js +2 -0
  23. package/dist/cli/bot/setup.js +1 -0
  24. package/dist/cli/contract-templates.js +2 -2
  25. package/dist/cli/entry.d.ts +2 -0
  26. package/dist/cli/entry.js +3 -2
  27. package/dist/cli/index.d.ts +2 -1
  28. package/dist/cli/index.js +2 -1
  29. package/dist/cli/messenger/admin-commands.js +5 -1
  30. package/dist/cli/messenger/admin-contacts-command.js +5 -1
  31. package/dist/cli/messenger/admin-folders-command.js +5 -1
  32. package/dist/cli/messenger/complete-command.js +1 -0
  33. package/dist/cli/messenger/messages-command.js +2 -19
  34. package/dist/cli/messenger/messages-edit-command.js +1 -0
  35. package/dist/cli/messenger/messages-press-command.js +1 -0
  36. package/dist/cli/messenger/messages-send-command.js +1 -0
  37. package/dist/cli/messenger/observed.js +2 -2
  38. package/dist/cli/messenger/polls-command.js +1 -0
  39. package/dist/cli/messenger/private-people-command.js +5 -1
  40. package/dist/cli/messenger/stored.js +56 -3
  41. package/dist/cli/messenger/topics-command.js +1 -1
  42. package/dist/cli/program.js +17 -6
  43. package/dist/cli/schedule/schedule-command.js +1 -0
  44. package/dist/domain/message-link.d.ts +3 -0
  45. package/dist/domain/message-link.js +9 -14
  46. package/dist/domain/money-words.d.ts +11 -0
  47. package/dist/domain/money-words.js +38 -0
  48. package/dist/domain/providers.d.ts +2 -0
  49. package/dist/domain/providers.js +15 -3
  50. package/dist/domain/ref-arguments.d.ts +31 -0
  51. package/dist/domain/ref-arguments.js +79 -0
  52. package/dist/domain/references.d.ts +20 -2
  53. package/dist/domain/references.js +132 -47
  54. package/dist/errors/reasons.js +18 -0
  55. package/dist/index.d.ts +1 -1
  56. package/dist/index.js +1 -1
  57. package/dist/meetings/mcp/index.d.ts +15 -15
  58. package/dist/oauth/client-file.d.ts +14 -0
  59. package/dist/oauth/client-file.js +28 -0
  60. package/dist/oauth/index.d.ts +1 -0
  61. package/dist/oauth/index.js +1 -0
  62. package/dist/ports/messenger.d.ts +3 -2
  63. package/dist/resolve.d.ts +3 -1
  64. package/dist/resolve.js +8 -0
  65. package/dist/search/task-signature.d.ts +5 -1
  66. package/dist/search/task-signature.js +27 -9
  67. package/dist/services/agenda.d.ts +5 -0
  68. package/dist/services/agenda.js +3 -1
  69. package/dist/services/index.d.ts +5 -1
  70. package/dist/services/index.js +5 -1
  71. package/dist/services/message-resolution.d.ts +4 -1
  72. package/dist/services/message-resolution.js +19 -3
  73. package/dist/services/messages.js +15 -13
  74. package/dist/services/money-words.d.ts +3 -0
  75. package/dist/services/money-words.js +5 -0
  76. package/dist/services/questions.d.ts +7 -2
  77. package/dist/services/questions.js +13 -4
  78. package/dist/services/stored-project-gathering.d.ts +9 -0
  79. package/dist/services/stored-project-gathering.js +9 -4
  80. package/dist/services/task-categories.d.ts +2 -0
  81. package/dist/services/task-categories.js +19 -8
  82. package/dist/store/sqlite/evidence-sources.js +2 -2
  83. package/dist/store/sqlite/identities.js +1 -1
  84. package/dist/store/sqlite/knowledge.js +7 -2
  85. package/dist/store/sqlite/notes.js +4 -4
  86. package/dist/store/sqlite/things.d.ts +0 -1
  87. package/dist/store/sqlite/things.js +25 -24
  88. package/package.json +1 -1
@@ -0,0 +1,79 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { providerLabel, providerNamed } from "./providers.js";
3
+ import { looksLikeReference, parseReference } from "./references.js";
4
+ const TAKES = {
5
+ chat: "a chat: chat:<provider>/<account>/<chat>, its id or its title",
6
+ message: "a message: msg:<provider>/<account>/<chat>/<message>, or a chat and the message's id",
7
+ };
8
+ const OPENS = {
9
+ chat: "messages list",
10
+ message: "messages show",
11
+ contact: "contacts show",
12
+ };
13
+ const article = (word) => (/^[aeiou]/.test(word) ? "an" : "a");
14
+ const wrongKind = (text, kind, takes) => new CliError("validation_error", `${text} names ${article(kind)} ${kind}; this takes ${TAKES[takes]}`, {
15
+ reason: "ref.wrong-kind",
16
+ });
17
+ const wrongProvider = (text, provider, kind) => {
18
+ const command = providerNamed(provider)?.command;
19
+ const opens = OPENS[kind];
20
+ const how = command && opens ? `open it with ${command} <profile> ${opens} ${text}` : "open it with its own CLI";
21
+ const label = providerLabel(provider);
22
+ return new CliError("validation_error", `${text} is ${article(label.toLowerCase())} ${label} ${kind}; ${how}`, {
23
+ reason: "ref.wrong-provider",
24
+ });
25
+ };
26
+ /**
27
+ * The chat or message a reference names, when `text` is one of this provider's; `undefined` for text that is no
28
+ * reference, which stays an id or a title. Another kind or another messenger is refused, never read as a title.
29
+ */
30
+ export const chatReferenceOf = (text, provider, takes = "chat") => {
31
+ const trimmed = text.trim();
32
+ if (!looksLikeReference(trimmed))
33
+ return undefined;
34
+ const reference = parseReference(trimmed);
35
+ if (reference.type !== "chat" && reference.type !== "message")
36
+ throw wrongKind(trimmed, reference.type, takes);
37
+ if (reference.provider !== provider)
38
+ throw wrongProvider(trimmed, reference.provider, reference.type);
39
+ const found = {
40
+ text: trimmed,
41
+ provider: reference.provider,
42
+ account: reference.account,
43
+ chat: reference.chat,
44
+ ...(reference.type === "message" ? { message: reference.message } : {}),
45
+ };
46
+ return found;
47
+ };
48
+ /**
49
+ * The identity a `contact:` reference names, when it is one of this provider's; `undefined` for any other text. A
50
+ * `person:` reference is the store's person, which the callers that take one read before this.
51
+ */
52
+ export const contactReferenceOf = (text, provider) => {
53
+ const trimmed = text.trim();
54
+ if (!trimmed.startsWith("contact:"))
55
+ return undefined;
56
+ const reference = parseReference(trimmed);
57
+ if (reference.type !== "contact")
58
+ return undefined;
59
+ if (provider !== undefined && reference.provider !== provider)
60
+ throw wrongProvider(trimmed, reference.provider, reference.type);
61
+ return reference.id;
62
+ };
63
+ /** The refusal of another account's reference: switching to it silently would read or send as somebody else. */
64
+ export const wrongAccount = (reference, scope, profiles) => {
65
+ const here = scope.account === undefined
66
+ ? "the account of this profile is not known yet"
67
+ : `${scope.profile === undefined ? "this profile" : `profile ${scope.profile}`} is account ${scope.account}`;
68
+ const others = profiles === undefined
69
+ ? ""
70
+ : `. Profiles signed in to ${reference.account}: ${profiles.length > 0 ? profiles.join(", ") : "none here"}`;
71
+ return new CliError("validation_error", `${reference.text} belongs to account ${reference.account}; ${here}${others}`, { reason: "ref.wrong-account" });
72
+ };
73
+ /** Refuses a reference of another account than the scope's, naming the profiles that have it. */
74
+ export const ensureAccount = async (reference, scope, profilesOn) => {
75
+ if (scope.account !== undefined && scope.account === reference.account)
76
+ return;
77
+ const profiles = await profilesOn?.(reference.account).catch(() => undefined);
78
+ throw wrongAccount(reference, scope, profiles);
79
+ };
@@ -1,3 +1,4 @@
1
+ import { type MeetingReference } from "../meetings/index.js";
1
2
  /**
2
3
  * One typed form for every kind of thing a link, a tag or an argument can name. The prefix makes a
3
4
  * wrong kind fail loudly instead of matching another row. Parts that are ids of someone else's system
@@ -27,14 +28,31 @@ export type Reference = {
27
28
  type: "folder";
28
29
  id: string;
29
30
  path: string | null;
30
- };
31
+ } | MeetingReference;
31
32
  /**
32
33
  * Things named by their store id (a task also by its key or the task package's id). `entity` is the name
33
34
  * before organizations and projects: it still parses, so an old file's reference resolves as not found.
34
35
  */
35
- export declare const SIMPLE_REFERENCES: readonly ["note", "document", "person", "organization", "project", "task", "memory", "fact", "bot", "entity"];
36
+ export declare const SIMPLE_REFERENCES: readonly ["note", "document", "person", "organization", "project", "task", "memory", "fact", "bot", "event", "entity"];
36
37
  export type SimpleReference = (typeof SIMPLE_REFERENCES)[number];
38
+ export type RefKindName = Exclude<Reference["type"], "entity">;
39
+ export interface RefKindSpec<R extends Reference = Reference> {
40
+ /** The word before the first colon: `chat`, `msg`, `document`. */
41
+ prefix: string;
42
+ type: R["type"];
43
+ /** For error texts: `chat:<provider>/<account>/<chat>`. */
44
+ shape: string;
45
+ parse(rest: string, text: string): R;
46
+ format(reference: R): string;
47
+ }
48
+ /** Every kind of reference. A new kind is one row here; nothing else switches on the prefix. */
49
+ export declare const REF_KINDS: readonly RefKindSpec[];
37
50
  export declare const formatReference: (reference: Reference) => string;
38
51
  export declare const parseReference: (text: string) => Reference;
39
52
  /** The stored spelling of a reference, so two spellings of one thing compare equal. */
40
53
  export declare const canonicalReference: (text: string) => string;
54
+ /**
55
+ * Strict: the whole text is one reference of a known kind, with no space. A title such as "Note: plan" is not, so an
56
+ * argument that takes either a reference or a title can tell them apart. The shape is checked only by parsing it.
57
+ */
58
+ export declare const looksLikeReference: (text: string) => boolean;
@@ -1,4 +1,5 @@
1
1
  import { CliError, singleLine } from "@wirecat/cli-base";
2
+ import { formatMeetingReference, parseMeetingReference } from "../meetings/index.js";
2
3
  import { formatLocator, parseLocator } from "./locator.js";
3
4
  /**
4
5
  * Things named by their store id (a task also by its key or the task package's id). `entity` is the name
@@ -14,70 +15,154 @@ export const SIMPLE_REFERENCES = [
14
15
  "memory",
15
16
  "fact",
16
17
  "bot",
18
+ "event",
17
19
  "entity",
18
20
  ];
19
- const parts = (rest, count, reference, shape) => {
21
+ const malformed = (text, shape) => new CliError("validation_error", `"${singleLine(text)}" is not a reference — expected ${shape}`, {
22
+ reason: "ref.malformed",
23
+ });
24
+ const parts = (rest, count, text, shape) => {
20
25
  const split = rest.split("/");
21
26
  if (split.length !== count || split.some((part) => part === ""))
22
- throw new CliError("validation_error", `"${singleLine(reference)}" is not a reference — expected ${shape}`);
27
+ throw malformed(text, shape);
23
28
  return split.map(decodeURIComponent);
24
29
  };
25
- export const formatReference = (reference) => {
26
- switch (reference.type) {
27
- case "message":
28
- return formatLocator(reference);
29
- case "chat":
30
- return `chat:${[reference.provider, reference.account, reference.chat].map(encodeURIComponent).join("/")}`;
31
- case "contact":
32
- return `contact:${[reference.provider, reference.id].map(encodeURIComponent).join("/")}`;
33
- case "folder":
34
- return reference.path === null
35
- ? `folder:${encodeURIComponent(reference.id)}`
36
- : `folder:${encodeURIComponent(reference.id)}/${encodeURIComponent(reference.path)}`;
37
- default:
38
- return `${reference.type}:${reference.id}`;
39
- }
30
+ const encoded = (...values) => values.map(encodeURIComponent).join("/");
31
+ /** A subfolder spelled one way: no leading, trailing or doubled slash, and `.` for the folder itself. */
32
+ const folderPath = (path) => {
33
+ const clean = path
34
+ .split("/")
35
+ .filter((part) => part !== "" && part !== ".")
36
+ .join("/");
37
+ return clean === "" ? (path.trim() === "" ? "" : null) : clean;
40
38
  };
41
- export const parseReference = (text) => {
42
- const reference = text.trim();
43
- const colon = reference.indexOf(":");
44
- const prefix = colon < 0 ? "" : reference.slice(0, colon);
45
- const rest = reference.slice(colon + 1);
46
- switch (prefix) {
47
- case "msg":
48
- return { type: "message", ...parseLocator(reference) };
49
- case "chat": {
50
- const [provider, account, chat] = parts(rest, 3, reference, "chat:<provider>/<account>/<chat>");
39
+ const MEETING_SHAPE = "meeting:<account>/<meeting>[/<transcript>[/<cue>]] with safe integer ids";
40
+ const kind = (spec) => spec;
41
+ const simple = (prefix) => kind({
42
+ prefix,
43
+ type: prefix,
44
+ shape: `${prefix}:<id>`,
45
+ parse: (rest, text) => {
46
+ if (!rest.trim())
47
+ throw new CliError("validation_error", `"${singleLine(text)}" names no ${prefix}`, {
48
+ reason: "ref.malformed",
49
+ });
50
+ return { type: prefix, id: rest };
51
+ },
52
+ format: (reference) => `${reference.type}:${reference.id}`,
53
+ });
54
+ /** Every kind of reference. A new kind is one row here; nothing else switches on the prefix. */
55
+ export const REF_KINDS = [
56
+ kind({
57
+ prefix: "msg",
58
+ type: "message",
59
+ shape: "msg:<provider>/<account>/<chat>/<message>",
60
+ parse: (_, text) => {
61
+ try {
62
+ return { type: "message", ...parseLocator(text) };
63
+ }
64
+ catch {
65
+ throw malformed(text, "msg:<provider>/<account>/<chat>/<message>");
66
+ }
67
+ },
68
+ format: formatLocator,
69
+ }),
70
+ kind({
71
+ prefix: "chat",
72
+ type: "chat",
73
+ shape: "chat:<provider>/<account>/<chat>",
74
+ parse: (rest, text) => {
75
+ const [provider, account, chat] = parts(rest, 3, text, "chat:<provider>/<account>/<chat>");
51
76
  return { type: "chat", provider, account, chat };
52
- }
53
- case "contact": {
54
- const [provider, id] = parts(rest, 2, reference, "contact:<provider>/<id>");
77
+ },
78
+ format: ({ provider, account, chat }) => `chat:${encoded(provider, account, chat)}`,
79
+ }),
80
+ kind({
81
+ prefix: "contact",
82
+ type: "contact",
83
+ shape: "contact:<provider>/<id>",
84
+ parse: (rest, text) => {
85
+ const [provider, id] = parts(rest, 2, text, "contact:<provider>/<id>");
55
86
  return { type: "contact", provider, id };
56
- }
57
- case "folder": {
87
+ },
88
+ format: ({ provider, id }) => `contact:${encoded(provider, id)}`,
89
+ }),
90
+ kind({
91
+ prefix: "folder",
92
+ type: "folder",
93
+ shape: "folder:<id>[/<path>]",
94
+ parse: (rest, text) => {
58
95
  const slash = rest.indexOf("/");
59
96
  const id = decodeURIComponent(slash < 0 ? rest : rest.slice(0, slash));
60
97
  const path = slash < 0 ? null : folderPath(decodeURIComponent(rest.slice(slash + 1)));
61
98
  if (!id.trim() || path === "")
62
- throw new CliError("validation_error", `"${singleLine(reference)}" is not a reference — expected folder:<id>[/<path>]`);
99
+ throw malformed(text, "folder:<id>[/<path>]");
63
100
  return { type: "folder", id, path };
64
- }
65
- default:
66
- if (SIMPLE_REFERENCES.includes(prefix)) {
67
- if (!rest.trim())
68
- throw new CliError("validation_error", `"${singleLine(reference)}" names no ${prefix}`);
69
- return { type: prefix, id: rest };
101
+ },
102
+ format: ({ id, path }) => (path === null ? `folder:${encodeURIComponent(id)}` : `folder:${encoded(id, path)}`),
103
+ }),
104
+ kind({
105
+ prefix: "meeting",
106
+ type: "meeting",
107
+ shape: MEETING_SHAPE,
108
+ parse: (_, text) => {
109
+ try {
110
+ return parseMeetingReference(text);
70
111
  }
71
- throw new CliError("validation_error", `"${singleLine(reference)}" is not a reference — expected msg:, chat:, contact:, folder:, ${SIMPLE_REFERENCES.filter((one) => one !== "entity").join(":, ")}:`);
112
+ catch {
113
+ throw malformed(text, MEETING_SHAPE);
114
+ }
115
+ },
116
+ format: formatMeetingReference,
117
+ }),
118
+ ...SIMPLE_REFERENCES.map(simple),
119
+ ];
120
+ const byPrefix = new Map(REF_KINDS.map((spec) => [spec.prefix, spec]));
121
+ const byType = new Map(REF_KINDS.map((spec) => [spec.type, spec]));
122
+ /** The prefixes people are told about: `entity` only still parses. */
123
+ const offered = REF_KINDS.map(({ prefix }) => prefix).filter((prefix) => prefix !== "entity");
124
+ const distance = (a, b) => {
125
+ let row = Array.from({ length: b.length + 1 }, (_, at) => at);
126
+ for (const [i, x] of [...a].entries()) {
127
+ const next = [i + 1];
128
+ for (const [j, y] of [...b].entries())
129
+ next.push(Math.min(row[j + 1] + 1, next[j] + 1, row[j] + (x === y ? 0 : 1)));
130
+ row = next;
72
131
  }
132
+ return row[b.length];
73
133
  };
74
- /** A subfolder spelled one way: no leading, trailing or doubled slash, and `.` for the folder itself. */
75
- const folderPath = (path) => {
76
- const clean = path
77
- .split("/")
78
- .filter((part) => part !== "" && part !== ".")
79
- .join("/");
80
- return clean === "" ? (path.trim() === "" ? "" : null) : clean;
134
+ /** `doc` reads as `document`, not as `bot` one letter away: a typed start of a prefix wins over spelling. */
135
+ const nearest = (typed) => offered.find((prefix) => typed.length >= 2 && prefix.startsWith(typed)) ??
136
+ offered
137
+ .map((prefix) => ({ prefix, far: distance(typed, prefix) }))
138
+ .filter(({ far }) => far <= 2)
139
+ .sort((a, b) => a.far - b.far)[0]?.prefix;
140
+ const unknownKind = (text, prefix, rest) => {
141
+ const near = prefix === "" ? undefined : nearest(prefix);
142
+ const kinds = offered.map((one) => `${one}:`).join(", ");
143
+ const hint = near === undefined ? `; kinds: ${kinds}` : `; did you mean ${near}:${singleLine(rest)}? Kinds: ${kinds}`;
144
+ return new CliError("validation_error", `"${singleLine(text)}" is no kind of reference${hint}`, {
145
+ reason: "ref.unknown-kind",
146
+ });
147
+ };
148
+ export const formatReference = (reference) => byType.get(reference.type).format(reference);
149
+ export const parseReference = (text) => {
150
+ const reference = text.trim();
151
+ const colon = reference.indexOf(":");
152
+ const prefix = colon < 0 ? "" : reference.slice(0, colon);
153
+ const rest = reference.slice(colon + 1);
154
+ const spec = byPrefix.get(prefix);
155
+ if (!spec)
156
+ throw unknownKind(reference, prefix, rest);
157
+ return spec.parse(rest, reference);
81
158
  };
82
159
  /** The stored spelling of a reference, so two spellings of one thing compare equal. */
83
160
  export const canonicalReference = (text) => formatReference(parseReference(text));
161
+ /**
162
+ * Strict: the whole text is one reference of a known kind, with no space. A title such as "Note: plan" is not, so an
163
+ * argument that takes either a reference or a title can tell them apart. The shape is checked only by parsing it.
164
+ */
165
+ export const looksLikeReference = (text) => {
166
+ const match = /^([a-z]+):\S+$/.exec(text.trim());
167
+ return match !== null && byPrefix.has(match[1]);
168
+ };
@@ -29,6 +29,8 @@ const SIGN_IN = [
29
29
  ["oauth.revoke-failed", "outcome_unknown", "the provider did not confirm the revoke"],
30
30
  ["oauth.invalid", "authentication_error", "the saved sign-in cannot be read"],
31
31
  ["oauth.not-saved", "configuration_error", "the sign-in cannot be saved or read where it is kept"],
32
+ ["sign-in.client-file-open", "configuration_error", "other users of this computer can read the client file"],
33
+ ["sign-in.client-file-invalid", "configuration_error", "the file is not a Desktop app OAuth client JSON"],
32
34
  ];
33
35
  const OTHERS = [
34
36
  ["model.offline", "permission_error", "downloading a model needs the network; --offline was requested"],
@@ -78,6 +80,17 @@ const OTHERS = [
78
80
  validation("ics.too-many", "the file holds more events than one import sends"),
79
81
  validation("ics.too-large", "the file is larger than one import reads"),
80
82
  ["calendar.read-only", "permission_error", "the calendar takes no new events from this account"],
83
+ validation("feed.address-invalid", "a feed address is an http, https or webcal URL with no user or password in it"),
84
+ validation("feed.duplicate", "this profile follows a feed at this address already"),
85
+ validation("feed.not-a-feed", "the calendar is not a feed this profile follows"),
86
+ validation("feed.too-large", "the feed is larger than one sync reads"),
87
+ validation("feed.redirect-refused", "the feed sent the request on to an address that is not https"),
88
+ ["feed.no-address", "configuration_error", "the feed's address is no longer kept on this computer"],
89
+ ["feed.refused", "provider_error", "the feed's server refused the request"],
90
+ ["feed.gone", "not_found", "the feed's server has no feed at this address any more"],
91
+ ["feed.timeout", "timeout", "the feed's server did not answer in time"],
92
+ ["feed.unreachable", "network_error", "the feed's server could not be reached"],
93
+ validation("busy.feed", "a feed calendar has no busy times to ask; read its events with events list"),
81
94
  ["calendar.unsynced", "not_found", "the calendar account has no synced calendar in the store"],
82
95
  ["calendar.sync-incomplete", "not_found", "a synced calendar's sync never finished or is mid-round"],
83
96
  ["calendar.no-upcoming", "not_found", "no upcoming meeting with other attendees is stored for this account"],
@@ -133,6 +146,11 @@ const OTHERS = [
133
146
  validation("period.ambiguous", "the period can be read more than one way"),
134
147
  validation("proposal.wrong-state", "the proposal is not in a state that allows this"),
135
148
  validation("ranking.invalid", "the ranking is not valid"),
149
+ validation("ref.malformed", "the reference has a known kind but not its shape"),
150
+ validation("ref.unknown-kind", "the text names no kind of reference"),
151
+ validation("ref.wrong-kind", "the reference names another kind of thing than the argument takes"),
152
+ validation("ref.wrong-provider", "the reference belongs to another messenger; open it with that CLI"),
153
+ validation("ref.wrong-account", "the reference belongs to another account than this profile's"),
136
154
  validation("selection.changed", "the stored selection changed meanwhile"),
137
155
  validation("selection.invalid", "the selection is not valid"),
138
156
  validation("search.kind", "the saved search belongs to another kind of search: messages or meetings"),
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, type Kind, kindIn, k
12
12
  export type { RankingGraphEvidence, RankingGraphLink } from "./domain/rankings-graph.js";
13
13
  export type { RankingComponent, RankingInput, RankingMeasure, RankingOptions, RankingTarget, RankingWeights, ScorePreset, } from "./domain/rankings-options.js";
14
14
  export { rankingOptions } from "./domain/rankings-options.js";
15
- export { canonicalReference, formatReference, parseReference, type Reference } from "./domain/references.js";
15
+ export { canonicalReference, formatReference, looksLikeReference, parseReference, REF_KINDS, type Reference, type RefKindName, type RefKindSpec, } from "./domain/references.js";
16
16
  export { normalizeTag, TAG_TYPES, type TagType } from "./domain/tags.js";
17
17
  export { type AppErrors, DETAILS as CORE_DETAILS, failureContext, REASONS as CORE_REASONS } from "./errors/reasons.js";
18
18
  export { canonicalMeetingReference, formatMeetingReference, type MeetingReference, parseMeetingReference, } from "./meetings/index.js";
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ export { parseMarkdownSpans } from "./domain/markdown-spans.js";
7
7
  export { countedPoll } from "./domain/polls.js";
8
8
  export { FOLDER_PROVIDERS, isFolderProvider, isKind, KINDS, kindIn, kindOf, PROVIDERS, providerNamed, providers, UNKNOWN_PROVIDER, useProviders, } from "./domain/providers.js";
9
9
  export { rankingOptions } from "./domain/rankings-options.js";
10
- export { canonicalReference, formatReference, parseReference } from "./domain/references.js";
10
+ export { canonicalReference, formatReference, looksLikeReference, parseReference, REF_KINDS, } from "./domain/references.js";
11
11
  export { normalizeTag, TAG_TYPES } from "./domain/tags.js";
12
12
  export { DETAILS as CORE_DETAILS, failureContext, REASONS as CORE_REASONS } from "./errors/reasons.js";
13
13
  export { canonicalMeetingReference, formatMeetingReference, parseMeetingReference, } from "./meetings/index.js";
@@ -38,7 +38,6 @@ export declare const meetingTools: ({
38
38
  max_read_bytes?: undefined;
39
39
  cursor?: undefined;
40
40
  } | {
41
- limit?: undefined;
42
41
  event_id?: undefined;
43
42
  series_id?: undefined;
44
43
  page?: undefined;
@@ -54,11 +53,11 @@ export declare const meetingTools: ({
54
53
  format?: undefined;
55
54
  transcript_id?: undefined;
56
55
  query?: undefined;
56
+ limit?: undefined;
57
57
  scan_limit?: undefined;
58
58
  max_read_bytes?: undefined;
59
59
  cursor?: undefined;
60
60
  } | {
61
- limit?: undefined;
62
61
  event_id?: undefined;
63
62
  series_id?: undefined;
64
63
  page?: undefined;
@@ -76,11 +75,11 @@ export declare const meetingTools: ({
76
75
  format?: undefined;
77
76
  transcript_id?: undefined;
78
77
  query?: undefined;
78
+ limit?: undefined;
79
79
  scan_limit?: undefined;
80
80
  max_read_bytes?: undefined;
81
81
  cursor?: undefined;
82
82
  } | {
83
- limit?: undefined;
84
83
  event_id?: undefined;
85
84
  series_id?: undefined;
86
85
  page?: undefined;
@@ -102,6 +101,7 @@ export declare const meetingTools: ({
102
101
  minimum: number;
103
102
  };
104
103
  query?: undefined;
104
+ limit?: undefined;
105
105
  scan_limit?: undefined;
106
106
  max_read_bytes?: undefined;
107
107
  cursor?: undefined;
@@ -188,7 +188,6 @@ export declare const meetingTools: ({
188
188
  max_read_bytes?: undefined;
189
189
  cursor?: undefined;
190
190
  } | {
191
- limit?: undefined;
192
191
  event_id?: undefined;
193
192
  series_id?: undefined;
194
193
  page?: undefined;
@@ -204,11 +203,11 @@ export declare const meetingTools: ({
204
203
  format?: undefined;
205
204
  transcript_id?: undefined;
206
205
  query?: undefined;
206
+ limit?: undefined;
207
207
  scan_limit?: undefined;
208
208
  max_read_bytes?: undefined;
209
209
  cursor?: undefined;
210
210
  } | {
211
- limit?: undefined;
212
211
  event_id?: undefined;
213
212
  series_id?: undefined;
214
213
  page?: undefined;
@@ -226,11 +225,11 @@ export declare const meetingTools: ({
226
225
  format?: undefined;
227
226
  transcript_id?: undefined;
228
227
  query?: undefined;
228
+ limit?: undefined;
229
229
  scan_limit?: undefined;
230
230
  max_read_bytes?: undefined;
231
231
  cursor?: undefined;
232
232
  } | {
233
- limit?: undefined;
234
233
  event_id?: undefined;
235
234
  series_id?: undefined;
236
235
  page?: undefined;
@@ -252,6 +251,7 @@ export declare const meetingTools: ({
252
251
  minimum: number;
253
252
  };
254
253
  query?: undefined;
254
+ limit?: undefined;
255
255
  scan_limit?: undefined;
256
256
  max_read_bytes?: undefined;
257
257
  cursor?: undefined;
@@ -338,7 +338,6 @@ export declare const meetingTools: ({
338
338
  max_read_bytes?: undefined;
339
339
  cursor?: undefined;
340
340
  } | {
341
- limit?: undefined;
342
341
  event_id?: undefined;
343
342
  series_id?: undefined;
344
343
  page?: undefined;
@@ -354,11 +353,11 @@ export declare const meetingTools: ({
354
353
  format?: undefined;
355
354
  transcript_id?: undefined;
356
355
  query?: undefined;
356
+ limit?: undefined;
357
357
  scan_limit?: undefined;
358
358
  max_read_bytes?: undefined;
359
359
  cursor?: undefined;
360
360
  } | {
361
- limit?: undefined;
362
361
  event_id?: undefined;
363
362
  series_id?: undefined;
364
363
  page?: undefined;
@@ -376,11 +375,11 @@ export declare const meetingTools: ({
376
375
  format?: undefined;
377
376
  transcript_id?: undefined;
378
377
  query?: undefined;
378
+ limit?: undefined;
379
379
  scan_limit?: undefined;
380
380
  max_read_bytes?: undefined;
381
381
  cursor?: undefined;
382
382
  } | {
383
- limit?: undefined;
384
383
  event_id?: undefined;
385
384
  series_id?: undefined;
386
385
  page?: undefined;
@@ -402,6 +401,7 @@ export declare const meetingTools: ({
402
401
  minimum: number;
403
402
  };
404
403
  query?: undefined;
404
+ limit?: undefined;
405
405
  scan_limit?: undefined;
406
406
  max_read_bytes?: undefined;
407
407
  cursor?: undefined;
@@ -488,7 +488,6 @@ export declare const meetingTools: ({
488
488
  max_read_bytes?: undefined;
489
489
  cursor?: undefined;
490
490
  } | {
491
- limit?: undefined;
492
491
  event_id?: undefined;
493
492
  series_id?: undefined;
494
493
  page?: undefined;
@@ -504,11 +503,11 @@ export declare const meetingTools: ({
504
503
  format?: undefined;
505
504
  transcript_id?: undefined;
506
505
  query?: undefined;
506
+ limit?: undefined;
507
507
  scan_limit?: undefined;
508
508
  max_read_bytes?: undefined;
509
509
  cursor?: undefined;
510
510
  } | {
511
- limit?: undefined;
512
511
  event_id?: undefined;
513
512
  series_id?: undefined;
514
513
  page?: undefined;
@@ -526,11 +525,11 @@ export declare const meetingTools: ({
526
525
  format?: undefined;
527
526
  transcript_id?: undefined;
528
527
  query?: undefined;
528
+ limit?: undefined;
529
529
  scan_limit?: undefined;
530
530
  max_read_bytes?: undefined;
531
531
  cursor?: undefined;
532
532
  } | {
533
- limit?: undefined;
534
533
  event_id?: undefined;
535
534
  series_id?: undefined;
536
535
  page?: undefined;
@@ -552,6 +551,7 @@ export declare const meetingTools: ({
552
551
  minimum: number;
553
552
  };
554
553
  query?: undefined;
554
+ limit?: undefined;
555
555
  scan_limit?: undefined;
556
556
  max_read_bytes?: undefined;
557
557
  cursor?: undefined;
@@ -638,7 +638,6 @@ export declare const meetingTools: ({
638
638
  max_read_bytes?: undefined;
639
639
  cursor?: undefined;
640
640
  } | {
641
- limit?: undefined;
642
641
  event_id?: undefined;
643
642
  series_id?: undefined;
644
643
  page?: undefined;
@@ -654,11 +653,11 @@ export declare const meetingTools: ({
654
653
  format?: undefined;
655
654
  transcript_id?: undefined;
656
655
  query?: undefined;
656
+ limit?: undefined;
657
657
  scan_limit?: undefined;
658
658
  max_read_bytes?: undefined;
659
659
  cursor?: undefined;
660
660
  } | {
661
- limit?: undefined;
662
661
  event_id?: undefined;
663
662
  series_id?: undefined;
664
663
  page?: undefined;
@@ -676,11 +675,11 @@ export declare const meetingTools: ({
676
675
  format?: undefined;
677
676
  transcript_id?: undefined;
678
677
  query?: undefined;
678
+ limit?: undefined;
679
679
  scan_limit?: undefined;
680
680
  max_read_bytes?: undefined;
681
681
  cursor?: undefined;
682
682
  } | {
683
- limit?: undefined;
684
683
  event_id?: undefined;
685
684
  series_id?: undefined;
686
685
  page?: undefined;
@@ -702,6 +701,7 @@ export declare const meetingTools: ({
702
701
  minimum: number;
703
702
  };
704
703
  query?: undefined;
704
+ limit?: undefined;
705
705
  scan_limit?: undefined;
706
706
  max_read_bytes?: undefined;
707
707
  cursor?: undefined;
@@ -0,0 +1,14 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import type { OAuthClient } from "./provider.js";
3
+ export interface ClientFileOptions {
4
+ platform?: NodeJS.Platform;
5
+ /** Skip the owner-only check, for a file the package ships. Default: the file must be private to its owner. */
6
+ shipped?: boolean;
7
+ /** Thrown when the file cannot be read or has the wrong shape. */
8
+ invalid?: CliError;
9
+ }
10
+ /**
11
+ * Reads a Google Desktop OAuth client file (`{ "installed": { client_id, client_secret? } }`, the JSON Google's console
12
+ * downloads). Refuses a file other users can read, and never puts the file's contents in an error.
13
+ */
14
+ export declare const readClientFile: (location: URL | string, options?: ClientFileOptions) => OAuthClient;
@@ -0,0 +1,28 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { CliError } from "@wirecat/cli-base";
3
+ import { privateFiles } from "../cli/messenger/private-files.js";
4
+ // No schema library: the keeper-process test bundles this module without a node_modules next to it.
5
+ /**
6
+ * Reads a Google Desktop OAuth client file (`{ "installed": { client_id, client_secret? } }`, the JSON Google's console
7
+ * downloads). Refuses a file other users can read, and never puts the file's contents in an error.
8
+ */
9
+ export const readClientFile = (location, options = {}) => {
10
+ if (!options.shipped && typeof location === "string") {
11
+ const mode = privateFiles({ files: [location], dirs: [] }, options.platform);
12
+ if (mode.checked && !mode.ok)
13
+ throw new CliError("configuration_error", `the client file can be read by other users of this computer; run chmod 600 ${location} and try again`, { reason: "sign-in.client-file-open" });
14
+ }
15
+ try {
16
+ const { installed } = JSON.parse(readFileSync(location, "utf8"));
17
+ const { client_id: clientId, client_secret: clientSecret } = installed;
18
+ if (typeof clientId !== "string" || !clientId)
19
+ throw new TypeError("client_id");
20
+ if (clientSecret !== undefined && typeof clientSecret !== "string")
21
+ throw new TypeError("client_secret");
22
+ return { clientId, ...(clientSecret ? { clientSecret } : {}) };
23
+ }
24
+ catch {
25
+ throw (options.invalid ??
26
+ new CliError("configuration_error", "could not read a Desktop app OAuth client JSON from the client file; its contents were not logged", { reason: "sign-in.client-file-invalid" }));
27
+ }
28
+ };
@@ -1,4 +1,5 @@
1
1
  export { type Authorization, type AuthorizeOptions, authorize } from "./authorize.js";
2
+ export { type ClientFileOptions, readClientFile } from "./client-file.js";
2
3
  export { type SignInOptions, type TokenKeeper, type TokenKeeperOptions, type TokensOptions, tokenKeeper, } from "./keeper.js";
3
4
  export type { ExchangeContext, OAuthClient, OAuthProvider, RequestContext, TokenSet } from "./provider.js";
4
5
  export { requestTokens, revokeToken, TOKEN_LOG_ALLOW, TOKEN_LOG_EVENT } from "./requests.js";
@@ -1,4 +1,5 @@
1
1
  export { authorize } from "./authorize.js";
2
+ export { readClientFile } from "./client-file.js";
2
3
  export { tokenKeeper, } from "./keeper.js";
3
4
  export { requestTokens, revokeToken, TOKEN_LOG_ALLOW, TOKEN_LOG_EVENT } from "./requests.js";
4
5
  export { credentialStorage, driveSessionCodec, oauthAccount, standardCodec, zoomTokenCodec, } from "./storage.js";