ask-marcel-office-cli 2.2.0 → 2.4.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 (35) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/README.md +45 -26
  3. package/dist/cli.js +2964 -2446
  4. package/dist/commands.json +254 -727
  5. package/dist/composition/run-registry-command.d.ts +12 -3
  6. package/dist/domain/utilities/base64.d.ts +11 -0
  7. package/dist/index.js +2121 -1737
  8. package/dist/infra/auth.d.ts +30 -4
  9. package/dist/infra/browser-auth.d.ts +21 -1
  10. package/dist/presenter/output-text.d.ts +1 -1
  11. package/dist/presenter/output.d.ts +2 -2
  12. package/dist/presenter/render-to-string.d.ts +42 -2
  13. package/dist/use-cases/commands/command-types.d.ts +1 -20
  14. package/dist/use-cases/commands/create-forward-draft.d.ts +1 -1
  15. package/dist/use-cases/commands/create-reply-draft.d.ts +1 -1
  16. package/dist/use-cases/commands/docs-render.d.ts +0 -1
  17. package/dist/use-cases/commands/docx-metadata.d.ts +32 -2
  18. package/dist/use-cases/commands/draft-comment-splicer.d.ts +8 -1
  19. package/dist/use-cases/commands/include-hidden-folders.d.ts +2 -0
  20. package/dist/use-cases/commands/list-mail-folder-messages-delta.d.ts +10 -2
  21. package/dist/use-cases/commands/list-shared-mailbox-child-folders.d.ts +4 -0
  22. package/dist/use-cases/commands/list-shared-mailbox-folders.d.ts +4 -0
  23. package/dist/use-cases/commands/login-status.d.ts +2 -0
  24. package/dist/use-cases/commands/login.d.ts +18 -8
  25. package/dist/use-cases/commands/next-page.d.ts +1 -0
  26. package/dist/use-cases/commands/ooxml-xml-walker.d.ts +31 -6
  27. package/dist/use-cases/commands/pptx-comments.d.ts +8 -5
  28. package/dist/use-cases/commands/reject-unknown-params.d.ts +16 -0
  29. package/dist/use-cases/commands/resolve-command.d.ts +7 -9
  30. package/dist/use-cases/commands/search-onenote-pages.d.ts +1 -1
  31. package/dist/use-cases/commands/token-tier-capability.d.ts +5 -0
  32. package/dist/use-cases/commands/update-mail-draft.d.ts +4 -0
  33. package/docs/COMMANDS.md +15 -13
  34. package/docs/USAGE.md +2 -2
  35. package/package.json +25 -9
@@ -39,7 +39,9 @@ type AuthManager = {
39
39
  * `logicalPermissions` allow-list. Falls through cache → re-capture
40
40
  * via headless Playwright. Used by the 3 historical-version commands.
41
41
  */
42
- getElevatedAccessToken: () => Promise<Result<AccessToken, AuthError>>;
42
+ getElevatedAccessToken: (options?: {
43
+ readonly awaitSignIn?: boolean;
44
+ }) => Promise<Result<AccessToken, AuthError>>;
43
45
  /**
44
46
  * Returns a Graph token issued by a PARTNER tenant's authority, for a user
45
47
  * who is a guest there. Without it, every call against that tenant's
@@ -54,7 +56,9 @@ type AuthManager = {
54
56
  * through cache → re-capture via headless Playwright. Used by the
55
57
  * `list-teams-chats-with-messages` family of commands.
56
58
  */
57
- getChatsvcaggAccessToken: () => Promise<Result<AccessToken, AuthError>>;
59
+ getChatsvcaggAccessToken: (options?: {
60
+ readonly ignoreCache?: boolean;
61
+ }) => Promise<Result<AccessToken, AuthError>>;
58
62
  /**
59
63
  * Returns the regional segment used to construct chatsvcagg substrate
60
64
  * URLs (`teams.microsoft.com/api/csa/<region>/api/...`). Captured at
@@ -71,7 +75,17 @@ type AuthManager = {
71
75
  * via headless Playwright. Used by `list-teams-chat-history` to walk
72
76
  * paginated chat-message history beyond the 200-message chatsvcagg cap.
73
77
  */
74
- getIc3AccessToken: () => Promise<Result<AccessToken, AuthError>>;
78
+ getIc3AccessToken: (options?: {
79
+ readonly ignoreCache?: boolean;
80
+ }) => Promise<Result<AccessToken, AuthError>>;
81
+ /**
82
+ * Redeem the shared refresh token for any COLD substrate tier, over HTTP, with
83
+ * no browser on any path. `login` calls this so a warm-cache sign-in leaves all
84
+ * four tiers usable; without it the two substrate tiers stay cold until some
85
+ * Teams-chat command pays for them, and `login` reports them missing while
86
+ * having done nothing about it.
87
+ */
88
+ warmSubstrateTokens?: () => Promise<void>;
75
89
  logout: () => Promise<Result<void, AuthError>>;
76
90
  /**
77
91
  * Inspect the elevated-capture outcome from the most recent
@@ -95,6 +109,16 @@ type AuthManager = {
95
109
  * Optional: only the real manager implements it; a minimal fake omits it
96
110
  * and callers treat that as unavailable. Never captures or refreshes.
97
111
  */
112
+ /**
113
+ * The cached BASIC token, decoded by `scopes-check` and never acquired: no
114
+ * refresh, no browser. The acquiring getter heals a dead session by opening
115
+ * a browser and, when the persistent profile is already signed in, wiping it
116
+ * so the grant re-fires (2026-09-02: a diagnostic did exactly that). A stale
117
+ * token comes back as-is so its expiry can be reported; no cache at all is
118
+ * `undefined`. Optional: a bring-your-own-token manager omits it and callers
119
+ * fall back to `getAccessToken`, which is then the caller's own function.
120
+ */
121
+ getCachedBasicToken?: () => Promise<AccessToken | undefined>;
98
122
  getCachedElevatedInfo?: () => Promise<CachedTierInfo>;
99
123
  /**
100
124
  * Same decode-only preflight as `getCachedElevatedInfo`, for the chatsvcagg /
@@ -120,7 +144,8 @@ type SecondaryTokenCommands = {
120
144
  readonly chatsvcagg: ReadonlyArray<string>;
121
145
  readonly ic3: ReadonlyArray<string>;
122
146
  };
123
- declare const createAuthManagerFromApi: (browserAuth: BrowserAuth, cachePath: string, browserProfileDir: string, logger: Logger, fs: FileSystem, recaptureSecondaryViaBrowser?: boolean, secondaryTokenCommands?: SecondaryTokenCommands, acquireBasicViaBrowser?: boolean) => AuthManager;
147
+ type FetchFn = (url: string, init?: RequestInit) => Promise<Response>;
148
+ declare const createAuthManagerFromApi: (browserAuth: BrowserAuth, cachePath: string, browserProfileDir: string, logger: Logger, fs: FileSystem, recaptureSecondaryViaBrowser?: boolean, secondaryTokenCommands?: SecondaryTokenCommands, acquireBasicViaBrowser?: boolean, recaptureElevatedViaBrowser?: boolean, fetchFn?: FetchFn) => AuthManager;
124
149
  /**
125
150
  * Probe the token cache for a fresh access token. Handed to the browser
126
151
  * capture so its poll loop can short-circuit the multi-minute dance
@@ -138,6 +163,7 @@ declare const createAuthManager: (deps: {
138
163
  recaptureSecondaryViaBrowser?: boolean;
139
164
  secondaryTokenCommands?: SecondaryTokenCommands;
140
165
  acquireBasicViaBrowser?: boolean;
166
+ recaptureElevatedViaBrowser?: boolean;
141
167
  }) => AuthManager;
142
168
  export { createAuthManager, createAuthManagerFromApi, createFreshCachedTokenProbe, stderrProgress };
143
169
  export type { AuthError, AuthManager, CachedTierInfo, ElevatedOutcome, SecondaryTokenCommands };
@@ -115,8 +115,16 @@ type BrowserAuth = {
115
115
  * cached elevated token expires and the Teams session is already in
116
116
  * the cache (no fresh sign-in needed). On `login`, use
117
117
  * `acquireBothTokens` instead so the user sees only one browser.
118
+ *
119
+ * `awaitSignIn` says a human asked for this and is watching, so the capture
120
+ * gets the interactive `pollDeadlineMs` rather than the short silent cap:
121
+ * long enough for a slow redirect chain, and long enough to fill in a
122
+ * sign-in form if one appears. Without it the short cap applies and the
123
+ * capture fails fast, which is what a background command needs.
118
124
  */
119
- acquireElevatedToken: () => Promise<ElevatedTokenResult>;
125
+ acquireElevatedToken: (options?: {
126
+ readonly awaitSignIn?: boolean;
127
+ }) => Promise<ElevatedTokenResult>;
120
128
  /**
121
129
  * Capture a chatsvcagg-audience bearer (Teams basic identity, but the
122
130
  * `chatsvcagg.teams.microsoft.com` resource instead of Graph) by
@@ -223,6 +231,12 @@ type BrowserAuthConfig = {
223
231
  readonly initialSettleMs?: number;
224
232
  readonly postReloginSettleMs?: number;
225
233
  readonly pollIntervalMs?: number;
234
+ /**
235
+ * How long to keep listening for the PREFERRED elevated identity after an
236
+ * acceptable-but-weaker one has already been captured. See
237
+ * `PREFERRED_ELEVATED_APP_ID`.
238
+ */
239
+ readonly elevatedPreferenceGraceMs?: number;
226
240
  readonly pollDeadlineMs?: number;
227
241
  readonly navigationTimeoutMs?: number;
228
242
  /**
@@ -234,6 +248,12 @@ type BrowserAuthConfig = {
234
248
  * the LLM tool-call window. With a tight cap, the flow either yields
235
249
  * a token quickly or fails with `auth_failed: elevated token capture
236
250
  * timed out — run `ask-marcel-office login` to refresh.`
251
+ *
252
+ * The cap applies only when nobody is waiting. A caller passing
253
+ * `awaitSignIn` (today just `login`) gets `pollDeadlineMs` instead, because
254
+ * a silent capture that merely runs SLOW would otherwise fail here and send
255
+ * `login` into the forced dance, whose cookie wipe costs the 90-day sign-in
256
+ * session and guarantees the next run prompts.
237
257
  */
238
258
  readonly elevatedRecaptureTimeoutMs?: number;
239
259
  /**
@@ -1,2 +1,2 @@
1
- declare const renderTextOutput: (data: unknown) => string;
1
+ declare const renderTextOutput: (data: unknown, tenantId?: string) => string;
2
2
  export { renderTextOutput };
@@ -1,7 +1,7 @@
1
1
  import type { Logger } from '../use-cases/ports/logger.js';
2
2
  import type { ErrorSource } from './error-hints.js';
3
- import type { OutputFormat } from './render-to-string.js';
4
- declare const render: (data: unknown, logger: Logger, format: OutputFormat) => void;
3
+ import type { OutputFormat, RenderContext } from './render-to-string.js';
4
+ declare const render: (data: unknown, logger: Logger, format: OutputFormat, context?: RenderContext) => void;
5
5
  declare const renderError: (message: string, format: OutputFormat, errorCode?: string, explicitSource?: ErrorSource, retryAfterSeconds?: number) => void;
6
6
  export { render, renderError };
7
7
  export type { OutputFormat };
@@ -1,10 +1,50 @@
1
1
  import type { ErrorSource } from './error-hints.js';
2
2
  type OutputFormat = 'text' | 'json';
3
+ /**
4
+ * Which front end the caller is on. It changes what a remedy can even BE: a
5
+ * terminal caller can redirect stdout to a file, an MCP client cannot (it has
6
+ * no shell), and the `--output-path` flag reaches MCP as an `outputPath` tool
7
+ * param rather than a flag.
8
+ */
9
+ type RenderSurface = 'cli' | 'mcp';
10
+ /**
11
+ * What the presenter needs in order to name a remedy the caller can actually
12
+ * use, and to render a follow-up command the caller can actually run. Built in
13
+ * composition (which owns the command manifest AND the invoked params); omitted
14
+ * by the renders that have no command behind them at all — the 548 KB
15
+ * `list-commands` manifest, a login summary, a logout status.
16
+ *
17
+ * 2026-07-23: the banner used to call `--output-path` a "universal remedy
18
+ * (works on every command)". It is not. Plain-JSON commands REFUSE the flag,
19
+ * and the two commands that trip the banner most often (`search-all-files`,
20
+ * `microsoft-search-query`) advertise only `--query` — so every remedy the
21
+ * banner named was a dead end there, and an agentic caller following it burned
22
+ * a call to find out.
23
+ */
24
+ type RenderContext = {
25
+ readonly commandName: string;
26
+ /** `meta.producesBytes` — the manifest flag that decides whether --output-path is accepted. */
27
+ readonly producesBytes: boolean;
28
+ readonly supportsSelect: boolean;
29
+ readonly supportsTop: boolean;
30
+ readonly surface: RenderSurface;
31
+ /**
32
+ * The `--tenant-id` the call was made with, when it carried one. Unlike every
33
+ * field above it comes from the INVOCATION, not the manifest: a Graph cursor
34
+ * carries no tenant, so the footer is the only place the next call can learn
35
+ * which identity signed this page.
36
+ */
37
+ readonly tenantId?: string;
38
+ };
3
39
  /**
4
40
  * Render a use-case success value to its final string, newline included.
5
41
  * `output.ts` writes this to stdout; `mcp.ts` returns it as tool content.
42
+ *
43
+ * `context` is what makes the oversized-response banner honest — pass it
44
+ * whenever a registry command produced the data. Omit it for the renders that
45
+ * have no command behind them; the banner then claims no flag-level remedy.
6
46
  */
7
- declare const renderToString: (data: unknown, format: OutputFormat) => string;
47
+ declare const renderToString: (data: unknown, format: OutputFormat, context?: RenderContext) => string;
8
48
  /**
9
49
  * Render an error to its final string, newline included. The `hint` / `source`
10
50
  * lookup is shared with the CLI so an MCP consumer sees the same curated
@@ -12,4 +52,4 @@ declare const renderToString: (data: unknown, format: OutputFormat) => string;
12
52
  */
13
53
  declare const renderErrorToString: (message: string, format: OutputFormat, errorCode?: string, explicitSource?: ErrorSource, retryAfterSeconds?: number) => string;
14
54
  export { renderErrorToString, renderToString };
15
- export type { OutputFormat };
55
+ export type { OutputFormat, RenderContext, RenderSurface };
@@ -5,10 +5,6 @@ type CommandSchema = z.ZodType;
5
5
  type CommandExecute = (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, import('../../infra/graph-client.js').GraphError>>;
6
6
  type CommandCategory = 'drive' | 'excel' | 'sharepoint' | 'tasks' | 'mail' | 'notes' | 'user' | 'calendar' | 'chats' | 'teams' | 'meta' | 'lifecycle';
7
7
  type CommandHttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';
8
- type CommandOptionAlias = {
9
- readonly name: string;
10
- readonly key: string;
11
- };
12
8
  /**
13
9
  * Structured type-hint for a CLI flag value. Surfaces in `help-json` so an
14
10
  * LLM can avoid the trial-and-error of "is this an ID or a name?" prose
@@ -38,13 +34,6 @@ type CommandOptionMeta = {
38
34
  * commands accept but do not demand.
39
35
  */
40
36
  readonly required: boolean;
41
- /**
42
- * Optional secondary spellings of the same flag. Both the canonical
43
- * `name` and every alias name are accepted on the command line; values
44
- * passed under an alias are normalized to the canonical `key` before
45
- * the schema runs. The canonical name is what `--help` shows first.
46
- */
47
- readonly aliases?: ReadonlyArray<CommandOptionAlias>;
48
37
  /**
49
38
  * Structured value-type hint for LLM consumers. Optional.
50
39
  */
@@ -80,14 +69,6 @@ type PaginationStrategy =
80
69
  type CommandMeta = {
81
70
  readonly summary: string;
82
71
  readonly category: CommandCategory;
83
- /**
84
- * Deprecated former command names kept working as commander-level aliases for
85
- * back-compat after a rename (e.g. `download-onedrive-file-content` →
86
- * `download-drive-item-content`). The canonical registry key is what `--help`
87
- * and the manifest list first; each alias here is also accepted on the CLI and
88
- * surfaced in the manifest so an LLM that learned the old name still resolves.
89
- */
90
- readonly commandAliases?: ReadonlyArray<string>;
91
72
  readonly graphMethod: CommandHttpMethod;
92
73
  readonly graphPathTemplate: string;
93
74
  readonly graphDocsUrl: string;
@@ -178,4 +159,4 @@ type Command = {
178
159
  */
179
160
  readonly executeLocal?: (fs: import('../ports/filesystem.js').FileSystem, params: Record<string, string>) => Promise<Result<unknown, import('../../infra/graph-client.js').GraphError>>;
180
161
  };
181
- export type { ArgumentHint, Command, CommandCategory, CommandExecute, CommandHttpMethod, CommandMeta, CommandOptionAlias, CommandOptionMeta, CommandPositionalArgumentMeta, CommandSchema, PaginationStrategy, };
162
+ export type { ArgumentHint, Command, CommandCategory, CommandExecute, CommandHttpMethod, CommandMeta, CommandOptionMeta, CommandPositionalArgumentMeta, CommandSchema, PaginationStrategy, };
@@ -4,7 +4,7 @@ declare const schema: z.ZodObject<{
4
4
  forwardMessageId: z.ZodString;
5
5
  toRecipients: z.ZodString;
6
6
  ccRecipients: z.ZodOptional<z.ZodString>;
7
- bodyContent: z.ZodString;
7
+ comment: z.ZodString;
8
8
  subject: z.ZodOptional<z.ZodString>;
9
9
  bodyContentType: z.ZodOptional<z.ZodEnum<{
10
10
  Text: "Text";
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import type { Command, CommandMeta } from './command-types.js';
3
3
  declare const schema: z.ZodObject<{
4
4
  replyToMessageId: z.ZodString;
5
- bodyContent: z.ZodString;
5
+ comment: z.ZodString;
6
6
  subject: z.ZodOptional<z.ZodString>;
7
7
  replyAll: z.ZodOptional<z.ZodEnum<{
8
8
  true: "true";
@@ -3,7 +3,6 @@ export type CommandManifestEntry = {
3
3
  readonly name: string;
4
4
  readonly summary: string;
5
5
  readonly category: CommandCategory;
6
- readonly commandAliases?: CommandMeta['commandAliases'];
7
6
  readonly graphMethod: CommandMeta['graphMethod'];
8
7
  readonly graphPathTemplate: string;
9
8
  readonly graphDocsUrl: string;
@@ -4,7 +4,9 @@ import type { CustomProp, ExternalRel } from './ooxml-metadata.js';
4
4
  /**
5
5
  * Pulls the side-channel content out of a .docx zip — every text-bearing
6
6
  * surface mammoth drops on the floor: core / app / custom doc properties,
7
- * people registry, external hyperlinks, comments, tracked changes (ins + del),
7
+ * people registry, external hyperlinks, comments, tracked changes (replacements,
8
+ * the insertions and deletions that pair with nothing, moves, and run /
9
+ * paragraph formatting changes),
8
10
  * hidden text (w:vanish), text-box / shape text (w:txbxContent), header/footer
9
11
  * body prose, field instructions (MERGEFIELD / HYPERLINK / DOCVARIABLE), bookmarks.
10
12
  *
@@ -36,6 +38,28 @@ type TrackedChange = {
36
38
  readonly date: string;
37
39
  readonly text: string;
38
40
  };
41
+ type Replacement = {
42
+ readonly deletionId: string;
43
+ readonly insertionId: string;
44
+ readonly author: string;
45
+ readonly date: string;
46
+ readonly before: string;
47
+ readonly after: string;
48
+ };
49
+ type Move = {
50
+ readonly name: string;
51
+ readonly author: string;
52
+ readonly date: string;
53
+ readonly text: string;
54
+ readonly halves: 'both' | 'from-only' | 'to-only';
55
+ };
56
+ type FormatChange = {
57
+ readonly scope: 'run' | 'paragraph';
58
+ readonly author: string;
59
+ readonly date: string;
60
+ readonly text: string;
61
+ readonly properties: ReadonlyArray<string>;
62
+ };
39
63
  type Field = {
40
64
  readonly source: string;
41
65
  readonly instruction: string;
@@ -57,6 +81,12 @@ type DocxMetadata = {
57
81
  readonly comments: ReadonlyArray<Comment>;
58
82
  readonly insertions: ReadonlyArray<TrackedChange>;
59
83
  readonly deletions: ReadonlyArray<TrackedChange>;
84
+ /** A deletion and the insertion beside it, reported as the one edit they are. */
85
+ readonly replacements: ReadonlyArray<Replacement>;
86
+ /** Text moved elsewhere, joined by the range name that brackets both halves. */
87
+ readonly moves: ReadonlyArray<Move>;
88
+ /** Run or paragraph properties changed under revision marking. */
89
+ readonly formatChanges: ReadonlyArray<FormatChange>;
60
90
  readonly hiddenText: ReadonlyArray<string>;
61
91
  readonly textBoxes: ReadonlyArray<string>;
62
92
  readonly headersFooters: ReadonlyArray<HeaderFooter>;
@@ -66,4 +96,4 @@ type DocxMetadata = {
66
96
  };
67
97
  declare const extractDocxMetadata: (bytes: Uint8Array) => Promise<Result<DocxMetadata, GraphError>>;
68
98
  export { extractDocxMetadata };
69
- export type { Bookmark, Comment, CustomProp, DocxMetadata, ExternalRel, Field, HeaderFooter, Person, TrackedChange };
99
+ export type { Bookmark, Comment, CustomProp, DocxMetadata, ExternalRel, Field, FormatChange, HeaderFooter, Move, Person, Replacement, TrackedChange };
@@ -21,9 +21,16 @@ declare const findBodyInsertStart: (html: string) => number;
21
21
  * silently dropping the real quoted history below.
22
22
  */
23
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;
24
31
  /** The refusal copy for `commentCarriesQuoteBoundary`, named for the flag that carried it. */
25
32
  declare const boundaryMarkerRefusal: (flagName: string) => string;
26
33
  declare const insertCommentAboveQuote: (html: string, commentHtml: string) => SpliceResult;
27
34
  declare const replaceCommentAboveQuote: (html: string, commentHtml: string) => SpliceResult;
28
35
  declare const replacePlainTextCommentAboveQuote: (text: string, comment: string) => PlainTextSpliceResult;
29
- export { boundaryMarkerRefusal, commentCarriesQuoteBoundary, escapeTextAsHtml, findBodyInsertStart, insertCommentAboveQuote, replaceCommentAboveQuote, replacePlainTextCommentAboveQuote, };
36
+ export { bodyCarriesQuote, boundaryMarkerRefusal, commentCarriesQuoteBoundary, escapeTextAsHtml, findBodyInsertStart, insertCommentAboveQuote, replaceCommentAboveQuote, replacePlainTextCommentAboveQuote, };
@@ -0,0 +1,2 @@
1
+ import type { CommandOptionMeta } from './command-types.js';
2
+ export declare const INCLUDE_HIDDEN_FOLDERS_OPTION: CommandOptionMeta;
@@ -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 };
@@ -0,0 +1,4 @@
1
+ import type { CommandMeta } from './command-types.js';
2
+ declare const execute: import("./command-types.js").CommandExecute, schema: import("./command-types.js").CommandSchema;
3
+ declare const meta: CommandMeta;
4
+ export { execute, meta, schema };
@@ -0,0 +1,4 @@
1
+ import type { CommandMeta } from './command-types.js';
2
+ declare const execute: import("./command-types.js").CommandExecute, schema: import("./command-types.js").CommandSchema;
3
+ declare const meta: CommandMeta;
4
+ export { execute, meta, schema };
@@ -1,6 +1,8 @@
1
1
  type LoginSummary = {
2
2
  status: 'authenticated';
3
3
  available: ReadonlyArray<string>;
4
+ unlocked: Readonly<Record<string, string>>;
5
+ missing: Readonly<Record<string, string>>;
4
6
  hint: string;
5
7
  };
6
8
  type LoginSummaryInput = {
@@ -13,15 +13,25 @@ declare const schema: z.ZodObject<{}, z.core.$strict>;
13
13
  * loop with no exit: the command that needs elevated says "run login", login
14
14
  * says "authenticated", the command fails identically, forever.
15
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).
16
+ * When elevated is confirmed missing we try `getElevatedAccessToken` FIRST. It
17
+ * drives a silent SSO against the persistent browser profile and leaves cookies
18
+ * untouched, so the tenant's 90-day ESTSAUTHPERSISTENT session survives and the
19
+ * user sees a window flash rather than a sign-in page. The `{ force: true }`
20
+ * dance calls `context.clearCookies()` to make the OAuth grant re-fire for the
21
+ * BASIC token, and that wipe deletes ESTSAUTHPERSISTENT — which is why routing
22
+ * every elevated expiry through it produced a credential prompt each time, and
23
+ * then destroyed the very session the prompt had just established.
21
24
  *
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
+ * The silent route is not always available (expired profile cookies, a failed
26
+ * launch, a blocked navigation), so any failure falls back to the forced dance:
27
+ * it can prompt, but it polls for five minutes and a human can finish it.
28
+ *
29
+ * Supersedes the 2026-07-16 decision to escalate straight to `{ force: true }`.
30
+ * That entry feared a hand-rolled re-capture would no-op against the browser
31
+ * adapter's `freshCachedToken` probe; the probe lives in `acquireBothTokens`,
32
+ * not in `acquireElevatedToken`, and elevated freshness is decided upstream by
33
+ * `freshElevatedToken`. Verified live 2026-08-30: silent capture in 17s with no
34
+ * prompt, and the same call failing against a profile a forced login had wiped.
25
35
  */
26
36
  declare const execute: (auth: AuthManager, options?: {
27
37
  force?: boolean;
@@ -1,6 +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
+ tenantId: z.ZodOptional<z.ZodString>;
4
5
  url: z.ZodString;
5
6
  }, z.core.$strip>;
6
7
  declare const execute: Command['execute'];
@@ -17,6 +17,7 @@
17
17
  type XmlObject = Record<string, unknown>;
18
18
  declare const parseXml: (xml: string | undefined) => unknown;
19
19
  declare const findAll: (root: unknown, tagName: string) => ReadonlyArray<XmlObject>;
20
+ declare const findAllLocal: (root: unknown, localName: string) => ReadonlyArray<XmlObject>;
20
21
  declare const textOf: (node: unknown) => string;
21
22
  declare const attrOf: (node: XmlObject, name: string) => string;
22
23
  /**
@@ -26,11 +27,35 @@ declare const attrOf: (node: XmlObject, name: string) => string;
26
27
  * flattens every match into a single string for "the visible text of this run".
27
28
  */
28
29
  declare const findAllTexts: (root: unknown, tagName: string) => ReadonlyArray<string>;
30
+ declare const collectText: (node: unknown, tagName: string) => string;
31
+ declare const collectTextLocal: (node: unknown, localName: string) => string;
29
32
  /**
30
- * Concatenate the text content of every element matching `tagName` inside `node`,
31
- * regardless of nesting depth. Used to flatten a `<w:p>` (or `<w:ins>` / `<w:comment>`)
32
- * down to its visible text by gathering every `<w:t>` (or `<w:delText>`) descendant.
33
+ * One element in the `preserveOrder` shape: its tag name, and the node itself
34
+ * (whose `[tag]` holds the ordered children and whose `:@` holds attributes).
33
35
  */
34
- declare const collectText: (node: unknown, tagName: string) => string;
35
- export { attrOf, collectText, findAll, findAllTexts, parseXml, textOf };
36
- export type { XmlObject };
36
+ type OrderedNode = {
37
+ readonly tag: string;
38
+ readonly node: XmlObject;
39
+ };
40
+ declare const parseXmlOrdered: (xml: string | undefined) => unknown;
41
+ /**
42
+ * Every sibling list in the tree, each in document order, elements only.
43
+ *
44
+ * Sibling lists rather than one flat stream: adjacency only means anything
45
+ * WITHIN a parent. A deletion ending one paragraph and an insertion opening the
46
+ * next are consecutive in a flat walk and unrelated in the document.
47
+ */
48
+ declare const orderedSiblingGroups: (root: unknown) => ReadonlyArray<ReadonlyArray<OrderedNode>>;
49
+ /**
50
+ * Every element in true document order, pre-order (an element, then what it
51
+ * contains). Unlike `orderedSiblingGroups`, which reports a whole sibling level
52
+ * before descending, this is the order the start tags appear in the file, which
53
+ * is what reading flat range markers requires: `<w:moveFromRangeStart>` opens a
54
+ * span that a later element sits inside without being its child.
55
+ */
56
+ declare const orderedElements: (root: unknown) => ReadonlyArray<OrderedNode>;
57
+ declare const orderedAttrOf: (node: XmlObject, name: string) => string;
58
+ /** The `collectText` of the ordered shape: flatten every `tagName` descendant to one string. */
59
+ declare const collectOrderedText: (node: XmlObject, tagName: string) => string;
60
+ export { attrOf, collectOrderedText, collectText, collectTextLocal, findAll, findAllLocal, findAllTexts, orderedAttrOf, orderedElements, orderedSiblingGroups, parseXml, parseXmlOrdered, textOf, };
61
+ export type { OrderedNode, XmlObject };
@@ -1,16 +1,19 @@
1
1
  import type { OoxmlZip } from '../../infra/ooxml-zip-adapter.js';
2
2
  /**
3
3
  * PowerPoint comments come in two formats: legacy (`ppt/commentAuthors.xml`
4
- * authors by integer id + `ppt/comments/comment*.xml` `<p:cm authorId dt>`
5
- * with `<p:text>` body) and modern (`ppt/authors.xml` authors by GUID +
6
- * `ppt/comments/*.xml` `<p188:cm authorId created>` with DrawingML `<a:t>`
7
- * body). Both are scanned; authors are resolved by id in either scheme.
8
- * Modern-comment support is best-effort against the p188 (2018/8) schema.
4
+ * authors by integer id + `ppt/comments/comment*.xml` `<cm authorId dt>` with a
5
+ * `<text>` body) and modern (`ppt/authors.xml` authors by GUID +
6
+ * `ppt/comments/*.xml` `<cm authorId created>` with a DrawingML `<t>` body).
7
+ * Elements are matched by local name, so the prefixes a writer happens to bind
8
+ * (`p:` / `p188:` / `a:` from PowerPoint, anything else from a third-party tool)
9
+ * never decide whether a comment is found. Both generations share the local name
10
+ * `cm`, so one pass reads them all; authors are resolved by id in either scheme.
9
11
  */
10
12
  type CommentAuthor = {
11
13
  readonly id: string;
12
14
  readonly name: string;
13
15
  readonly initials: string;
16
+ readonly email: string;
14
17
  };
15
18
  type PptxComment = {
16
19
  readonly author: string;
@@ -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 };
@@ -1,17 +1,15 @@
1
1
  import type { Result } from '../../domain/result.js';
2
2
  import type { Command } from './command-types.js';
3
3
  /**
4
- * Name -> command lookup that also honours `meta.commandAliases`.
4
+ * Name -> command lookup shared by every non-commander front end.
5
5
  *
6
- * The CLI gets alias resolution for free from commander (`commandDef.alias(...)`
7
- * in `cli.ts`), so nothing needed this until a second front end appeared. The
8
- * MCP gateway has no commander, and its three command-taking tools
9
- * (`get-command-docs`, `run-command`, `run-write-command`) each need the same
10
- * lookup — Rule of Three, so it lives here rather than inline.
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.
11
9
  *
12
- * Returns the CANONICAL name alongside the command so every downstream concern
13
- * (docs rendering, the `mutates` read/write gate, error messages) keys off one
14
- * spelling regardless of which one the caller used.
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.
15
13
  */
16
14
  export type ResolveCommandError = {
17
15
  readonly type: 'unknown_command';
@@ -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,5 @@
1
+ declare const TIER_CAPABILITY: Readonly<Record<string, string>>;
2
+ declare const OPTIONAL_TIERS: readonly ["elevated", "chatsvcagg", "ic3"];
3
+ type OptionalTier = (typeof OPTIONAL_TIERS)[number];
4
+ export { OPTIONAL_TIERS, TIER_CAPABILITY };
5
+ export type { OptionalTier };
@@ -5,6 +5,10 @@ declare const schema: z.ZodObject<{
5
5
  subject: z.ZodOptional<z.ZodString>;
6
6
  bodyContent: z.ZodOptional<z.ZodString>;
7
7
  comment: z.ZodOptional<z.ZodString>;
8
+ replaceQuotedHistory: z.ZodOptional<z.ZodEnum<{
9
+ true: "true";
10
+ false: "false";
11
+ }>>;
8
12
  bodyContentType: z.ZodOptional<z.ZodEnum<{
9
13
  Text: "Text";
10
14
  HTML: "HTML";