ask-marcel-office-cli 2.1.0 → 2.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 (63) hide show
  1. package/CHANGELOG.md +283 -0
  2. package/README.md +178 -119
  3. package/dist/cli.js +44725 -27155
  4. package/dist/commands.json +406 -734
  5. package/dist/composition/build-deps.d.ts +8 -0
  6. package/dist/composition/mcp.d.ts +20 -0
  7. package/dist/composition/run-registry-command.d.ts +43 -0
  8. package/dist/domain/tenant-id.d.ts +9 -0
  9. package/dist/domain/utilities/spo-tenant.d.ts +20 -0
  10. package/dist/index.js +2831 -1833
  11. package/dist/infra/auth.d.ts +36 -14
  12. package/dist/infra/browser-auth.d.ts +7 -2
  13. package/dist/infra/graph-client.d.ts +52 -16
  14. package/dist/presenter/graph-cursor.d.ts +2 -0
  15. package/dist/presenter/output.d.ts +2 -2
  16. package/dist/presenter/render-to-string.d.ts +47 -0
  17. package/dist/use-cases/commands/build-command.d.ts +19 -2
  18. package/dist/use-cases/commands/command-types.d.ts +17 -31
  19. package/dist/use-cases/commands/convert-calendar-event-attachment-to-markdown.d.ts +4 -0
  20. package/dist/use-cases/commands/{convert-drive-item-zip.d.ts → convert-drive-item-zip-to-markdown.d.ts} +5 -0
  21. package/dist/use-cases/commands/{convert-local-file.d.ts → convert-local-file-to-markdown.d.ts} +4 -0
  22. package/dist/use-cases/commands/convert-mail-attachment-to-markdown.d.ts +10 -2
  23. package/dist/use-cases/commands/{convert-mail-attachment-zip.d.ts → convert-mail-attachment-zip-to-markdown.d.ts} +5 -1
  24. package/dist/use-cases/commands/convert-mail-to-markdown.d.ts +35 -1
  25. package/dist/use-cases/commands/create-forward-draft.d.ts +5 -1
  26. package/dist/use-cases/commands/create-reply-draft.d.ts +9 -1
  27. package/dist/use-cases/commands/docs-render.d.ts +0 -1
  28. package/dist/use-cases/commands/download-drive-item-as-markdown.d.ts +7 -2
  29. package/dist/use-cases/commands/download-drive-item-as-pdf.d.ts +1 -0
  30. package/dist/use-cases/commands/download-drive-item-content.d.ts +1 -0
  31. package/dist/use-cases/commands/draft-comment-splicer.d.ts +36 -0
  32. package/dist/use-cases/commands/draft-dedup.d.ts +18 -0
  33. package/dist/use-cases/commands/draft-response.d.ts +4 -0
  34. package/dist/use-cases/commands/extract-drive-item-images.d.ts +1 -0
  35. package/dist/use-cases/commands/extract-local-file-images.d.ts +1 -1
  36. package/dist/use-cases/commands/fetch-raw-bytes.d.ts +13 -0
  37. package/dist/use-cases/commands/find-mail-drafts.d.ts +9 -0
  38. package/dist/use-cases/commands/get-mail-signature.d.ts +8 -0
  39. package/dist/use-cases/commands/get-user.d.ts +10 -0
  40. package/dist/use-cases/commands/inline-image-embedder.d.ts +2 -1
  41. package/dist/use-cases/commands/list-mail-folder-messages-delta.d.ts +10 -2
  42. package/dist/use-cases/commands/login-status.d.ts +9 -27
  43. package/dist/use-cases/commands/login.d.ts +21 -0
  44. package/dist/use-cases/commands/mail-message-select.d.ts +1 -0
  45. package/dist/use-cases/commands/mail-quote-stripper.d.ts +16 -15
  46. package/dist/use-cases/commands/markdown-dispatch.d.ts +2 -1
  47. package/dist/use-cases/commands/msg-to-markdown.d.ts +10 -1
  48. package/dist/use-cases/commands/odata-query.d.ts +15 -1
  49. package/dist/use-cases/commands/office-to-markdown.d.ts +1 -0
  50. package/dist/use-cases/commands/read-mail-attachment.d.ts +4 -0
  51. package/dist/use-cases/commands/reject-unknown-params.d.ts +16 -0
  52. package/dist/use-cases/commands/resolve-command.d.ts +23 -0
  53. package/dist/use-cases/commands/search-all-files.d.ts +8 -0
  54. package/dist/use-cases/commands/search-escape.d.ts +23 -0
  55. package/dist/use-cases/commands/search-onenote-pages.d.ts +1 -1
  56. package/dist/use-cases/commands/signature-extractor.d.ts +2 -0
  57. package/dist/use-cases/commands/tenant-option.d.ts +44 -0
  58. package/dist/use-cases/commands/update-mail-draft.d.ts +5 -0
  59. package/dist/use-cases/commands/zip-archive-to-markdown.d.ts +10 -5
  60. package/dist/use-cases/ports/filesystem.d.ts +1 -1
  61. package/docs/COMMANDS.md +38 -33
  62. package/docs/USAGE.md +65 -7
  63. package/package.json +2 -1
@@ -0,0 +1,36 @@
1
+ type SpliceResult = {
2
+ readonly html: string;
3
+ readonly boundaryFound: boolean;
4
+ };
5
+ type PlainTextSpliceResult = {
6
+ readonly text: string;
7
+ readonly boundaryFound: boolean;
8
+ };
9
+ /**
10
+ * Renders an author's plain text as HTML: markup they typed shows as characters
11
+ * rather than taking effect, and their newlines survive as breaks. No wrapper
12
+ * element, so the caller decides the block context.
13
+ */
14
+ declare const escapeTextAsHtml: (text: string) => string;
15
+ /** Index just past the `<body>` open tag, or 0 for a fragment that has none. */
16
+ declare const findBodyInsertStart: (html: string) => number;
17
+ /**
18
+ * True when the author's own markup carries a quote boundary marker, e.g. they
19
+ * pasted a reply chain into it. Such a comment must be refused: the splice would
20
+ * keep the marker verbatim, and the NEXT revision would cut the draft AT it,
21
+ * silently dropping the real quoted history below.
22
+ */
23
+ declare const commentCarriesQuoteBoundary: (commentHtml: string) => boolean;
24
+ /**
25
+ * True when a draft body still carries the quoted history Graph minted with it.
26
+ * Dispatches on the draft's own contentType: the HTML and plain-text boundary
27
+ * markers are different things entirely. Used to refuse a whole-body replace
28
+ * that would drop the quote (2026-07-23 bug report).
29
+ */
30
+ declare const bodyCarriesQuote: (contentType: string, content: string) => boolean;
31
+ /** The refusal copy for `commentCarriesQuoteBoundary`, named for the flag that carried it. */
32
+ declare const boundaryMarkerRefusal: (flagName: string) => string;
33
+ declare const insertCommentAboveQuote: (html: string, commentHtml: string) => SpliceResult;
34
+ declare const replaceCommentAboveQuote: (html: string, commentHtml: string) => SpliceResult;
35
+ declare const replacePlainTextCommentAboveQuote: (text: string, comment: string) => PlainTextSpliceResult;
36
+ export { bodyCarriesQuote, boundaryMarkerRefusal, commentCarriesQuoteBoundary, escapeTextAsHtml, findBodyInsertStart, insertCommentAboveQuote, replaceCommentAboveQuote, replacePlainTextCommentAboveQuote, };
@@ -0,0 +1,18 @@
1
+ declare const normalizeThreadSubject: (subject: string) => string;
2
+ type DraftRecipient = {
3
+ readonly emailAddress?: {
4
+ readonly address?: string;
5
+ };
6
+ };
7
+ type DraftForMatch = {
8
+ readonly subject?: string;
9
+ readonly toRecipients?: ReadonlyArray<DraftRecipient>;
10
+ readonly ccRecipients?: ReadonlyArray<DraftRecipient>;
11
+ };
12
+ type DraftMatchCriteria = {
13
+ readonly subject: string;
14
+ readonly recipients?: ReadonlyArray<string>;
15
+ };
16
+ declare const matchExistingDrafts: <T extends DraftForMatch>(drafts: ReadonlyArray<T>, criteria: DraftMatchCriteria) => ReadonlyArray<T>;
17
+ export { matchExistingDrafts, normalizeThreadSubject };
18
+ export type { DraftForMatch, DraftMatchCriteria };
@@ -0,0 +1,4 @@
1
+ import { type Result } from '../../domain/result.js';
2
+ import type { GraphError } from '../../infra/graph-client.js';
3
+ declare const slimDraftResult: (result: Result<unknown, GraphError>) => Result<unknown, GraphError>;
4
+ export { slimDraftResult };
@@ -3,6 +3,7 @@ import type { Result } from '../../domain/result.js';
3
3
  import type { GraphClient, GraphError } from '../../infra/graph-client.js';
4
4
  import type { CommandMeta } from './command-types.js';
5
5
  declare const schema: z.ZodObject<{
6
+ tenantId: z.ZodOptional<z.ZodString>;
6
7
  driveId: z.ZodString;
7
8
  itemId: z.ZodString;
8
9
  }, z.core.$strip>;
@@ -9,7 +9,7 @@ import type { CommandMeta } from './command-types.js';
9
9
  * (OOXML media parts / unpdf page walk). It completes two flows the drive
10
10
  * command cannot reach: a Graph-rendered PDF saved with the global
11
11
  * output-path flag (the legacy-.ppt route), and Office files unpacked from a
12
- * local archive. Like `convert-local-file`, it never touches Graph and is
12
+ * local archive. Like `convert-local-file-to-markdown`, it never touches Graph and is
13
13
  * executed via `executeLocal(fs, params)`.
14
14
  */
15
15
  declare const schema: z.ZodObject<{
@@ -1,4 +1,5 @@
1
1
  import type { Result } from '../../domain/result.js';
2
+ import type { TenantId } from '../../domain/tenant-id.js';
2
3
  import type { GraphClient, GraphError } from '../../infra/graph-client.js';
3
4
  /**
4
5
  * Helpers that consolidate the "Graph hands you a 302, follow the CDN
@@ -26,8 +27,20 @@ import type { GraphClient, GraphError } from '../../infra/graph-client.js';
26
27
  * redirects to a streamContent URL whose embedded tempauth is signed
27
28
  * by Teams web client identity and rejected by SharePoint with 403.
28
29
  */
30
+ /**
31
+ * `elevated` picks the ODSP-allow-listed home identity (M365ChatClient).
32
+ * `tenantId` picks a PARTNER tenant's guest identity — for a file in a tenant the
33
+ * user is only a guest in, which no home-tier token can read (Graph answers `401
34
+ * invalidAudienceUri`).
35
+ *
36
+ * They are mutually exclusive by nature: elevated is a home-tenant identity, so
37
+ * "elevated in a partner tenant" does not exist. `tenantId` wins if both arrive,
38
+ * because a partner-tenant file is unreadable on ANY home token — including the
39
+ * elevated one — so honouring `elevated` there would guarantee failure.
40
+ */
29
41
  export type FetchOptions = {
30
42
  readonly elevated?: boolean;
43
+ readonly tenantId?: TenantId;
31
44
  };
32
45
  export type InlineBinary = {
33
46
  readonly contentType: string;
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ subject: z.ZodString;
5
+ toRecipients: z.ZodOptional<z.ZodString>;
6
+ }, z.core.$strip>;
7
+ declare const execute: Command['execute'];
8
+ declare const meta: CommandMeta;
9
+ export { execute, meta, schema };
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ messageId: z.ZodOptional<z.ZodString>;
5
+ }, z.core.$strip>;
6
+ declare const execute: Command['execute'];
7
+ declare const meta: CommandMeta;
8
+ export { execute, meta, schema };
@@ -0,0 +1,10 @@
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ userId: z.ZodString;
5
+ select: z.ZodOptional<z.ZodString>;
6
+ expand: z.ZodOptional<z.ZodString>;
7
+ }, z.core.$strip>;
8
+ declare const execute: Command['execute'];
9
+ declare const meta: CommandMeta;
10
+ export { execute, meta, schema };
@@ -16,5 +16,6 @@ type InlineAttachment = {
16
16
  readonly contentBytes: string;
17
17
  };
18
18
  declare const embedInlineImages: (html: string, attachments: ReadonlyArray<InlineAttachment>) => string;
19
- export { embedInlineImages };
19
+ declare const replaceUnresolvedCidImages: (html: string, labelByContentId: ReadonlyMap<string, string>) => string;
20
+ export { embedInlineImages, replaceUnresolvedCidImages };
20
21
  export type { InlineAttachment };
@@ -1,4 +1,12 @@
1
- import type { CommandMeta } from './command-types.js';
2
- declare const execute: import("./command-types.js").CommandExecute, schema: import("./command-types.js").CommandSchema;
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ mailFolderId: z.ZodString;
5
+ filter: z.ZodOptional<z.ZodString>;
6
+ select: z.ZodOptional<z.ZodString>;
7
+ top: z.ZodOptional<z.ZodString>;
8
+ expand: z.ZodOptional<z.ZodString>;
9
+ }, z.core.$strip>;
10
+ declare const execute: Command['execute'];
3
11
  declare const meta: CommandMeta;
4
12
  export { execute, meta, schema };
@@ -1,31 +1,13 @@
1
- type RefreshRoute = 'automatic' | 'interactive';
2
- type TokenTier = {
3
- readonly available: boolean;
4
- readonly expiresInSeconds: number | undefined;
5
- };
6
- type TokenView = {
7
- available: boolean;
8
- expiresInSeconds?: number;
9
- refresh: RefreshRoute;
10
- reason?: string;
11
- };
12
- type LoginStatus = {
1
+ type LoginSummary = {
13
2
  status: 'authenticated';
14
- tokens: {
15
- basic: TokenView;
16
- elevated: TokenView;
17
- chatsvcagg: TokenView;
18
- ic3: TokenView;
19
- };
3
+ available: ReadonlyArray<string>;
20
4
  hint: string;
21
5
  };
22
- type LoginStatusInput = {
23
- readonly basicExpiresInSeconds: number | undefined;
24
- readonly elevated: TokenTier;
25
- readonly chatsvcagg: TokenTier;
26
- readonly ic3: TokenTier;
27
- readonly elevatedFailureReason?: string;
6
+ type LoginSummaryInput = {
7
+ readonly elevatedAvailable: boolean;
8
+ readonly chatsvcaggAvailable: boolean;
9
+ readonly ic3Available: boolean;
28
10
  };
29
- declare const buildLoginStatus: (input: LoginStatusInput) => LoginStatus;
30
- export { buildLoginStatus };
31
- export type { LoginStatus, LoginStatusInput };
11
+ declare const buildLoginSummary: (input: LoginSummaryInput) => LoginSummary;
12
+ export { buildLoginSummary };
13
+ export type { LoginSummary, LoginSummaryInput };
@@ -2,6 +2,27 @@ import { z } from 'zod';
2
2
  import type { Result } from '../../domain/result.js';
3
3
  import type { AuthManager } from '../../infra/auth.js';
4
4
  declare const schema: z.ZodObject<{}, z.core.$strict>;
5
+ /**
6
+ * Sign in, and guarantee the browser-only tier is actually there afterwards.
7
+ *
8
+ * Every other token tier can be renewed headlessly from the shared refresh
9
+ * token. The elevated (M365ChatClient) token cannot — it carries no refresh
10
+ * token of its own and exists only via the browser dance. So a plain `login`
11
+ * that found a valid cached basic token used to return on the cache rung and
12
+ * report `authenticated` with elevated still missing, which left the user in a
13
+ * loop with no exit: the command that needs elevated says "run login", login
14
+ * says "authenticated", the command fails identically, forever.
15
+ *
16
+ * When elevated is confirmed missing we escalate through the SAME
17
+ * `{ force: true }` rung the `--force` flag uses. That matters: the browser
18
+ * adapter has its own `freshCachedToken` probe that short-circuits the dance
19
+ * when a valid token is on disk, and only the forced path suppresses it — a
20
+ * hand-rolled re-capture here would silently no-op (LESSONS 2026-07-13).
21
+ *
22
+ * Supersedes the 2026-07-13 decision that `login` is a slim confirmation and
23
+ * `--force` the only re-capture mechanism: correct about the mechanism, but it
24
+ * left the signpost pointing at a command that could not do the job.
25
+ */
5
26
  declare const execute: (auth: AuthManager, options?: {
6
27
  force?: boolean;
7
28
  }) => Promise<Result<string, import("../../infra/auth.js").AuthError>>;
@@ -0,0 +1 @@
1
+ export declare const MAIL_MESSAGE_DEFAULT_SELECT = "id,subject,from,toRecipients,ccRecipients,receivedDateTime,hasAttachments,isRead,importance,bodyPreview,conversationId";
@@ -1,19 +1,15 @@
1
+ import { z } from 'zod';
2
+ import type { CommandOptionMeta } from './command-types.js';
1
3
  /**
2
- * Strips quoted reply chains / forwarded-message blocks from an Outlook or
3
- * Gmail HTML email body so long threads don't duplicate quoted content into the
4
- * model's context. Conservative: truncates the body at the EARLIEST well-known
5
- * reply/forward boundary marker and replaces the tail with a single visible
6
- * placeholder nothing is removed silently, and `--keep-quoted true` on
7
- * `convert-mail-to-markdown` restores the full body. Pure string transform.
8
- *
9
- * Only structural, vendor-specific markers are matched (never a bare
10
- * `<blockquote>`, which legitimate content uses too):
11
- * - Outlook desktop / OWA reply+forward header block: `<div id="divRplyFwdMsg">`
12
- * - Outlook "type above this line" boundary: `<div id="appendonsend">`
13
- * - Outlook mobile reference container: `<div id="mail-editor-reference-message-container">`
14
- * - Outlook classic separator: `<hr id="stopSpelling">`
15
- * - Gmail quote container: `<div class="gmail_quote">` / `<blockquote class="gmail_quote">`
4
+ * Index where the quoted history begins in an HTML body, or -1 when the body
5
+ * quotes nothing. Earliest of the structural markers merged with the widened
6
+ * confirmed-header index. Exported because the draft-comment splicer needs the
7
+ * same cut to place a reply ABOVE the quote without disturbing it — the stripper
8
+ * throws the tail away, the splicer keeps it, but both agree on where it starts.
16
9
  */
10
+ declare const findQuoteBoundary: (html: string) => number;
11
+ /** The plain-text counterpart of `findQuoteBoundary`, for `contentType === 'text'` bodies. */
12
+ declare const findPlainTextQuoteBoundary: (text: string) => number;
17
13
  declare const stripQuotedReplies: (html: string) => {
18
14
  readonly html: string;
19
15
  readonly stripped: boolean;
@@ -22,4 +18,9 @@ declare const stripQuotedPlainText: (text: string) => {
22
18
  readonly text: string;
23
19
  readonly stripped: boolean;
24
20
  };
25
- export { stripQuotedPlainText, stripQuotedReplies };
21
+ declare const keepQuotedSchemaField: z.ZodOptional<z.ZodEnum<{
22
+ true: "true";
23
+ false: "false";
24
+ }>>;
25
+ declare const keepQuotedOption: CommandOptionMeta;
26
+ export { findPlainTextQuoteBoundary, findQuoteBoundary, keepQuotedOption, keepQuotedSchemaField, stripQuotedPlainText, stripQuotedReplies };
@@ -11,13 +11,14 @@ type BytesToMarkdownOptions = {
11
11
  readonly includeMetadata?: boolean;
12
12
  readonly maxCells?: number;
13
13
  readonly inlineImages?: boolean;
14
+ readonly keepQuoted?: boolean;
14
15
  readonly depth?: number;
15
16
  };
16
17
  declare const NESTED_HINTS: ConversionHints;
17
18
  /**
18
19
  * The single extension→converter dispatch for every markdown command, operating on
19
20
  * bytes already in hand: download-drive-item-as-markdown fetches them, convert-mail
20
- * decodes the attachment, convert-drive-item-zip unpacks the entry. Loop/Fluid/
21
+ * decodes the attachment, convert-drive-item-zip-to-markdown unpacks the entry. Loop/Fluid/
21
22
  * Whiteboard (`?format=html`) need a Graph round-trip and are handled by the drive
22
23
  * caller BEFORE this — they never reach here. An Outlook `.msg` is rendered to markdown
23
24
  * (headers + body) with each of its own attachments recursed through this dispatch.
@@ -10,6 +10,14 @@ import type { ParsedMsg } from '../../infra/msg-reader-adapter.js';
10
10
  * user asked ("maybe same way as zip"). Unconvertible attachments (images, binaries)
11
11
  * are listed with the dispatch's note instead of failing the whole message.
12
12
  *
13
+ * The body goes through the same two passes `convert-mail-to-markdown` applies to a
14
+ * Graph mail body (they share the helpers): the quoted reply chain is stripped by
15
+ * default — `keepQuoted` restores it, and every command that can hand a `.msg` to
16
+ * this renderer exposes that as `--keep-quoted`, so the marker's remedy always
17
+ * works — and any `cid:` image becomes a readable placeholder. A `.msg` carries no
18
+ * contentId on its attachments, so embedding is impossible here and the placeholder
19
+ * falls back to the img alt text or the cid's filename prefix.
20
+ *
13
21
  * `recurse` is the attachment converter injected by `markdown-dispatch` (its own
14
22
  * `bytesToMarkdown`, with the recursion depth incremented). Injecting it — rather
15
23
  * than importing `bytesToMarkdown` here — keeps this use-case off the dispatch's
@@ -20,9 +28,10 @@ import type { ParsedMsg } from '../../infra/msg-reader-adapter.js';
20
28
  declare const MAX_MSG_DEPTH = 3;
21
29
  type MsgToMarkdownOptions = {
22
30
  readonly depth?: number;
31
+ readonly keepQuoted?: boolean;
23
32
  };
24
33
  type MsgAttachmentConverter = (bytes: Uint8Array, filename: string) => Promise<Result<unknown, GraphError>>;
25
- declare const renderMsg: (msg: ParsedMsg, depth: number, recurse: MsgAttachmentConverter) => Promise<string>;
34
+ declare const renderMsg: (msg: ParsedMsg, opts: MsgToMarkdownOptions, recurse: MsgAttachmentConverter) => Promise<string>;
26
35
  declare const msgToMarkdown: (bytes: Uint8Array, opts: MsgToMarkdownOptions, recurse: MsgAttachmentConverter) => Promise<Result<unknown, GraphError>>;
27
36
  export { MAX_MSG_DEPTH, msgToMarkdown, renderMsg };
28
37
  export type { MsgAttachmentConverter, MsgToMarkdownOptions };
@@ -9,6 +9,20 @@ declare const odataQuerySchema: z.ZodObject<{
9
9
  expand: z.ZodOptional<z.ZodString>;
10
10
  }, z.core.$strip>;
11
11
  type ODataQueryParams = z.infer<typeof odataQuerySchema>;
12
+ /**
13
+ * Embed a user-supplied value inside an OData single-quoted string literal
14
+ * (`$filter=mail eq '<here>'`, `contains(title,'<here>')`, `search(q='<here>')`).
15
+ * Two steps, in this order:
16
+ * 1. double any `'` — OData's string-literal escape, so the value cannot
17
+ * terminate the literal early;
18
+ * 2. percent-encode — graph-client concatenates command paths verbatim, so an
19
+ * un-encoded value corrupts the query string. `&`/`#` truncate it outright,
20
+ * and a raw `+` is decoded back to a SPACE server-side, which silently
21
+ * matches nothing (base64 conversationIds and plus-addressed emails both
22
+ * carry `+`). encodeURIComponent leaves `'` unreserved, so the doubled
23
+ * quote survives as the escape OData expects.
24
+ */
25
+ declare const odataStringLiteral: (value: string) => string;
12
26
  declare const appendOData: (path: string, params: ODataQueryParams) => string;
13
27
  declare const odataQueryOptions: ReadonlyArray<CommandOptionMeta>;
14
28
  /**
@@ -86,5 +100,5 @@ declare const selectOnlyShape: Pick<{
86
100
  expand: z.ZodOptional<z.ZodString>;
87
101
  }, "select">;
88
102
  declare const selectOnlyOptions: readonly CommandOptionMeta[];
89
- export { appendOData, filterSelectOptions, filterSelectSchema, noSkipOptions, noSkipShape, odataQueryOptions, odataQuerySchema, pickODataOptions, pickODataShape, selectExpandOptions, selectExpandSchema, selectOnlyOptions, selectOnlyShape, topOnlyOptions, topOnlyShape, };
103
+ export { appendOData, filterSelectOptions, filterSelectSchema, noSkipOptions, noSkipShape, odataQueryOptions, odataQuerySchema, odataStringLiteral, pickODataOptions, pickODataShape, selectExpandOptions, selectExpandSchema, selectOnlyOptions, selectOnlyShape, topOnlyOptions, topOnlyShape, };
90
104
  export type { FilterSelectParams, ODataKey, ODataQueryParams, SelectExpandParams };
@@ -5,6 +5,7 @@ type OfficeToMarkdownOptions = FetchOptions & {
5
5
  readonly includeMetadata?: boolean;
6
6
  readonly inlineImages?: boolean;
7
7
  readonly maxCells?: number;
8
+ readonly keepQuoted?: boolean;
8
9
  };
9
10
  declare const officeToMarkdown: (graph: GraphClient, contentPath: string, filename: string, opts?: OfficeToMarkdownOptions) => Promise<Result<unknown, GraphError>>;
10
11
  export { officeToMarkdown };
@@ -9,6 +9,10 @@ declare const schema: z.ZodObject<{
9
9
  true: "true";
10
10
  false: "false";
11
11
  }>>;
12
+ keepQuoted: z.ZodOptional<z.ZodEnum<{
13
+ true: "true";
14
+ false: "false";
15
+ }>>;
12
16
  }, z.core.$strip>;
13
17
  declare const execute: (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, GraphError>>;
14
18
  declare const meta: CommandMeta;
@@ -0,0 +1,16 @@
1
+ import type { Result } from '../../domain/result.js';
2
+ import type { GraphError } from '../../infra/graph-client.js';
3
+ import type { Command } from './command-types.js';
4
+ /**
5
+ * Returns the rejection for any param key the command does not declare, or
6
+ * `undefined` when every key is known. Exported for the registry wrapper.
7
+ */
8
+ declare const rejectUnknownParams: (schema: Command["schema"], params: Record<string, string>) => Result<never, GraphError> | undefined;
9
+ /**
10
+ * Wraps a command so `execute` / `executeLocal` refuse unknown params before
11
+ * running. Applied at registry assembly, which is the single choke point every
12
+ * surface passes through — including a library caller that never touches
13
+ * composition.
14
+ */
15
+ declare const withUnknownParamRejection: (command: Command) => Command;
16
+ export { rejectUnknownParams, withUnknownParamRejection };
@@ -0,0 +1,23 @@
1
+ import type { Result } from '../../domain/result.js';
2
+ import type { Command } from './command-types.js';
3
+ /**
4
+ * Name -> command lookup shared by every non-commander front end.
5
+ *
6
+ * The MCP gateway's three command-taking tools (`get-command-docs`,
7
+ * `run-command`, `run-write-command`) each need the same lookup — Rule of
8
+ * Three, so it lives here rather than inline.
9
+ *
10
+ * 2026-07-24: one name per command. Deprecated-name resolution
11
+ * (`meta.commandAliases`) was removed with the alias system; an old name gets
12
+ * the unknown-command rejection with the full list, same as any typo.
13
+ */
14
+ export type ResolveCommandError = {
15
+ readonly type: 'unknown_command';
16
+ readonly name: string;
17
+ readonly available: ReadonlyArray<string>;
18
+ };
19
+ export type ResolvedCommand = {
20
+ readonly name: string;
21
+ readonly command: Command;
22
+ };
23
+ export declare const resolveCommand: (registry: Readonly<Record<string, Command>>, name: string) => Result<ResolvedCommand, ResolveCommandError>;
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ query: z.ZodString;
5
+ }, z.core.$strip>;
6
+ declare const execute: Command['execute'];
7
+ declare const meta: CommandMeta;
8
+ export { execute, meta, schema };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Wire-safe builder for Graph `$search=` KQL clauses. graph-client
3
+ * concatenates command paths verbatim (`https://graph.microsoft.com/v1.0${path}`)
4
+ * with no percent-encoding pass, so a raw `&`, `#`, `%`, or `+` inside a user
5
+ * query corrupts the query string on the wire: a live ` & ` truncated the KQL
6
+ * mid-phrase (`$search="subject:"Contoso A2` reached Graph). Values are
7
+ * percent-encoded here, at the only layer that knows which part of the path
8
+ * is data.
9
+ *
10
+ * The sibling concern — a value inside a single-quoted OData literal
11
+ * (`$filter=x eq '…'`, `contains(…)`, `search(q='…')`) — is
12
+ * `odataStringLiteral` in odata-query.ts, which owns OData query construction.
13
+ */
14
+ /**
15
+ * Build a `$search=` KQL clause: escape embedded double quotes as `\"`
16
+ * (Graph's documented KQL escaping, so `subject:"multi word"` and whole
17
+ * `"phrase"` queries become phrase matches instead of a BadRequest), wrap the
18
+ * expression in the double quotes Graph requires, then percent-encode the
19
+ * value. Backslashes are NOT pre-escaped: raw quotes are the documented
20
+ * input contract and backslash has no other KQL meaning here.
21
+ */
22
+ declare const kqlSearchClause: (query: string) => string;
23
+ export { kqlSearchClause };
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import type { Command, CommandMeta } from './command-types.js';
3
3
  declare const schema: z.ZodObject<{
4
- titleSubstring: z.ZodString;
4
+ query: z.ZodString;
5
5
  select: z.ZodOptional<z.ZodString>;
6
6
  top: z.ZodOptional<z.ZodString>;
7
7
  skip: z.ZodOptional<z.ZodString>;
@@ -0,0 +1,2 @@
1
+ declare const extractSignatureBlock: (html: string) => string | undefined;
2
+ export { extractSignatureBlock };
@@ -0,0 +1,44 @@
1
+ import { z } from 'zod';
2
+ import type { Result } from '../../domain/result.js';
3
+ import type { TenantId } from '../../domain/tenant-id.js';
4
+ import type { GraphClient, GraphError } from '../../infra/graph-client.js';
5
+ import type { CommandOptionMeta } from './command-types.js';
6
+ /**
7
+ * `--tenant-id`: read a file that lives in a tenant you are only a GUEST in.
8
+ *
9
+ * Without it, such a read dies at `401 invalidAudienceUri` — home-tenant Graph
10
+ * cannot mint a SharePoint token for a foreign tenant, so no home-tier token can
11
+ * reach it. Unlike `resolve-drive-share-link`, these commands cannot recover on
12
+ * their own: they hold only a `driveId` and an `itemId`, and NEITHER carries a
13
+ * tenant. The sharing URL does, which is why `resolve-drive-share-link` returns
14
+ * the `tenantId` for this flag to consume.
15
+ *
16
+ * OPTIONAL, always. That is not a detail — `required: true` compiles straight to
17
+ * commander's `.requiredOption()`, so an optional flag is what keeps the
18
+ * `CommandMeta` contract untouched and every existing invocation working.
19
+ */
20
+ declare const tenantIdShape: {
21
+ tenantId: z.ZodOptional<z.ZodString>;
22
+ };
23
+ declare const tenantIdSchema: z.ZodObject<{
24
+ tenantId: z.ZodOptional<z.ZodString>;
25
+ }, z.core.$strip>;
26
+ declare const TENANT_ID_OPTION: CommandOptionMeta;
27
+ /**
28
+ * Route a JSON GET to the right identity: the home token normally, a partner
29
+ * tenant's guest token when `--tenant-id` was given.
30
+ *
31
+ * The guest tier is a RUNTIME choice, not a static one like `elevated` — the same
32
+ * command uses either, depending on this flag. That is why the builders route
33
+ * here instead of growing guest twins (which would take 10 builders to 14+, and
34
+ * would still not express "it depends on the argument").
35
+ */
36
+ declare const routeGet: (graph: GraphClient, path: string, rawTenantId: string | undefined) => Promise<Result<unknown, GraphError>>;
37
+ /**
38
+ * Validate a caller-supplied tenant id at the boundary. The value reaches an
39
+ * authority URL whose POST carries the refresh token, so it is branded rather
40
+ * than trusted (hard rule 12); a bad one must fail here with a readable message,
41
+ * not further in.
42
+ */
43
+ declare const brandTenantId: (raw: string) => Result<TenantId, GraphError>;
44
+ export { TENANT_ID_OPTION, brandTenantId, routeGet, tenantIdSchema, tenantIdShape };
@@ -4,6 +4,11 @@ declare const schema: z.ZodObject<{
4
4
  messageId: z.ZodString;
5
5
  subject: z.ZodOptional<z.ZodString>;
6
6
  bodyContent: z.ZodOptional<z.ZodString>;
7
+ comment: z.ZodOptional<z.ZodString>;
8
+ replaceQuotedHistory: z.ZodOptional<z.ZodEnum<{
9
+ true: "true";
10
+ false: "false";
11
+ }>>;
7
12
  bodyContentType: z.ZodOptional<z.ZodEnum<{
8
13
  Text: "Text";
9
14
  HTML: "HTML";
@@ -3,9 +3,9 @@ import type { GraphError } from '../../infra/graph-client.js';
3
3
  import type { MediaEnvelope } from './media-files.js';
4
4
  /**
5
5
  * Shared "unzip + convert every contained file" core behind
6
- * `convert-drive-item-zip` (a OneDrive / SharePoint .zip),
7
- * `convert-mail-attachment-zip` (an Outlook .zip attachment), and
8
- * `convert-local-file` (a .zip on disk). Each entry is run through the same
6
+ * `convert-drive-item-zip-to-markdown` (a OneDrive / SharePoint .zip),
7
+ * `convert-mail-attachment-zip-to-markdown` (an Outlook .zip attachment), and
8
+ * `convert-local-file-to-markdown` (a .zip on disk). Each entry is run through the same
9
9
  * `bytesToMarkdown` dispatch the markdown commands use; an entry the dispatch
10
10
  * can't convert (image, binary, nested archive, scanned PDF) is LISTED with a
11
11
  * note instead of failing the whole archive. Notes use the container-neutral
@@ -27,6 +27,11 @@ type ZipArchiveResult = {
27
27
  readonly totalEntries?: number;
28
28
  readonly files: ReadonlyArray<FileResult>;
29
29
  };
30
- declare const convertZipArchive: (bytes: Uint8Array, includeMetadata: boolean, includeImages?: boolean) => Promise<Result<ZipArchiveResult, GraphError>>;
30
+ type ZipArchiveOptions = {
31
+ readonly includeMetadata: boolean;
32
+ readonly includeImages?: boolean;
33
+ readonly keepQuoted?: boolean;
34
+ };
35
+ declare const convertZipArchive: (bytes: Uint8Array, opts: ZipArchiveOptions) => Promise<Result<ZipArchiveResult, GraphError>>;
31
36
  export { convertZipArchive, MAX_ENTRIES };
32
- export type { FileResult, ZipArchiveResult };
37
+ export type { FileResult, ZipArchiveOptions, ZipArchiveResult };
@@ -11,7 +11,7 @@ export type FileSystemError = {
11
11
  export type FileSystem = {
12
12
  readonly readJson: <T>(path: string) => Promise<Result<T, FileSystemError>>;
13
13
  /**
14
- * Read a file's raw bytes. Used by `convert-local-file` to feed a local
14
+ * Read a file's raw bytes. Used by `convert-local-file-to-markdown` to feed a local
15
15
  * document into the same conversion dispatch the Graph-backed commands use.
16
16
  */
17
17
  readonly readBytes: (path: string) => Promise<Result<Uint8Array, FileSystemError>>;