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.
- package/CHANGELOG.md +283 -0
- package/README.md +178 -119
- package/dist/cli.js +44725 -27155
- package/dist/commands.json +406 -734
- package/dist/composition/build-deps.d.ts +8 -0
- package/dist/composition/mcp.d.ts +20 -0
- package/dist/composition/run-registry-command.d.ts +43 -0
- package/dist/domain/tenant-id.d.ts +9 -0
- package/dist/domain/utilities/spo-tenant.d.ts +20 -0
- package/dist/index.js +2831 -1833
- package/dist/infra/auth.d.ts +36 -14
- package/dist/infra/browser-auth.d.ts +7 -2
- package/dist/infra/graph-client.d.ts +52 -16
- package/dist/presenter/graph-cursor.d.ts +2 -0
- package/dist/presenter/output.d.ts +2 -2
- package/dist/presenter/render-to-string.d.ts +47 -0
- package/dist/use-cases/commands/build-command.d.ts +19 -2
- package/dist/use-cases/commands/command-types.d.ts +17 -31
- package/dist/use-cases/commands/convert-calendar-event-attachment-to-markdown.d.ts +4 -0
- package/dist/use-cases/commands/{convert-drive-item-zip.d.ts → convert-drive-item-zip-to-markdown.d.ts} +5 -0
- package/dist/use-cases/commands/{convert-local-file.d.ts → convert-local-file-to-markdown.d.ts} +4 -0
- package/dist/use-cases/commands/convert-mail-attachment-to-markdown.d.ts +10 -2
- package/dist/use-cases/commands/{convert-mail-attachment-zip.d.ts → convert-mail-attachment-zip-to-markdown.d.ts} +5 -1
- package/dist/use-cases/commands/convert-mail-to-markdown.d.ts +35 -1
- package/dist/use-cases/commands/create-forward-draft.d.ts +5 -1
- package/dist/use-cases/commands/create-reply-draft.d.ts +9 -1
- package/dist/use-cases/commands/docs-render.d.ts +0 -1
- package/dist/use-cases/commands/download-drive-item-as-markdown.d.ts +7 -2
- package/dist/use-cases/commands/download-drive-item-as-pdf.d.ts +1 -0
- package/dist/use-cases/commands/download-drive-item-content.d.ts +1 -0
- package/dist/use-cases/commands/draft-comment-splicer.d.ts +36 -0
- package/dist/use-cases/commands/draft-dedup.d.ts +18 -0
- package/dist/use-cases/commands/draft-response.d.ts +4 -0
- package/dist/use-cases/commands/extract-drive-item-images.d.ts +1 -0
- package/dist/use-cases/commands/extract-local-file-images.d.ts +1 -1
- package/dist/use-cases/commands/fetch-raw-bytes.d.ts +13 -0
- package/dist/use-cases/commands/find-mail-drafts.d.ts +9 -0
- package/dist/use-cases/commands/get-mail-signature.d.ts +8 -0
- package/dist/use-cases/commands/get-user.d.ts +10 -0
- package/dist/use-cases/commands/inline-image-embedder.d.ts +2 -1
- package/dist/use-cases/commands/list-mail-folder-messages-delta.d.ts +10 -2
- package/dist/use-cases/commands/login-status.d.ts +9 -27
- package/dist/use-cases/commands/login.d.ts +21 -0
- package/dist/use-cases/commands/mail-message-select.d.ts +1 -0
- package/dist/use-cases/commands/mail-quote-stripper.d.ts +16 -15
- package/dist/use-cases/commands/markdown-dispatch.d.ts +2 -1
- package/dist/use-cases/commands/msg-to-markdown.d.ts +10 -1
- package/dist/use-cases/commands/odata-query.d.ts +15 -1
- package/dist/use-cases/commands/office-to-markdown.d.ts +1 -0
- package/dist/use-cases/commands/read-mail-attachment.d.ts +4 -0
- package/dist/use-cases/commands/reject-unknown-params.d.ts +16 -0
- package/dist/use-cases/commands/resolve-command.d.ts +23 -0
- package/dist/use-cases/commands/search-all-files.d.ts +8 -0
- package/dist/use-cases/commands/search-escape.d.ts +23 -0
- package/dist/use-cases/commands/search-onenote-pages.d.ts +1 -1
- package/dist/use-cases/commands/signature-extractor.d.ts +2 -0
- package/dist/use-cases/commands/tenant-option.d.ts +44 -0
- package/dist/use-cases/commands/update-mail-draft.d.ts +5 -0
- package/dist/use-cases/commands/zip-archive-to-markdown.d.ts +10 -5
- package/dist/use-cases/ports/filesystem.d.ts +1 -1
- package/docs/COMMANDS.md +38 -33
- package/docs/USAGE.md +65 -7
- 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 };
|
|
@@ -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
|
-
|
|
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
|
|
2
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
23
|
-
readonly
|
|
24
|
-
readonly
|
|
25
|
-
readonly
|
|
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
|
|
30
|
-
export {
|
|
31
|
-
export type {
|
|
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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,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
|
-
|
|
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>>;
|