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
@@ -1,5 +1,6 @@
1
1
  import type { AccessToken } from '../domain/access-token.js';
2
2
  import type { Result } from '../domain/result.js';
3
+ import type { TenantId } from '../domain/tenant-id.js';
3
4
  import type { FileSystem } from '../use-cases/ports/filesystem.js';
4
5
  import type { Logger } from '../use-cases/ports/logger.js';
5
6
  import type { BrowserAuth, ElevatedFailureReason } from './browser-auth.js';
@@ -24,6 +25,11 @@ type ElevatedOutcome = {
24
25
  captured: false;
25
26
  reason: ElevatedFailureReason | 'unknown_error';
26
27
  };
28
+ type CachedTierInfo = {
29
+ readonly available: boolean;
30
+ readonly expiresInSeconds: number | undefined;
31
+ readonly scopes: ReadonlyArray<string>;
32
+ };
27
33
  type AuthManager = {
28
34
  getAccessToken: (options?: {
29
35
  force?: boolean;
@@ -34,6 +40,14 @@ type AuthManager = {
34
40
  * via headless Playwright. Used by the 3 historical-version commands.
35
41
  */
36
42
  getElevatedAccessToken: () => Promise<Result<AccessToken, AuthError>>;
43
+ /**
44
+ * Returns a Graph token issued by a PARTNER tenant's authority, for a user
45
+ * who is a guest there. Without it, every call against that tenant's
46
+ * SharePoint dies at `401 invalidAudienceUri` — home-tenant Graph cannot mint
47
+ * an SPO token for a foreign tenant. Cache -> headless redemption of the
48
+ * shared refresh token; never a browser.
49
+ */
50
+ getGuestAccessToken: (tenantId: TenantId) => Promise<Result<AccessToken, AuthError>>;
37
51
  /**
38
52
  * Returns a chatsvcagg-audience token (same Teams web client identity
39
53
  * as `getAccessToken`, but issued for the chatsvcagg resource). Falls
@@ -81,26 +95,32 @@ type AuthManager = {
81
95
  * Optional: only the real manager implements it; a minimal fake omits it
82
96
  * and callers treat that as unavailable. Never captures or refreshes.
83
97
  */
84
- getCachedElevatedInfo?: () => Promise<{
85
- available: boolean;
86
- expiresInSeconds: number | undefined;
87
- }>;
98
+ getCachedElevatedInfo?: () => Promise<CachedTierInfo>;
88
99
  /**
89
100
  * Same decode-only preflight as `getCachedElevatedInfo`, for the chatsvcagg /
90
101
  * ic3 Teams-chat substrate tokens. `login`'s four-token status and
91
102
  * `scopes-check` read these; a minimal fake omits them and callers treat that
92
103
  * as unavailable.
93
104
  */
94
- getCachedChatsvcaggInfo?: () => Promise<{
95
- available: boolean;
96
- expiresInSeconds: number | undefined;
97
- }>;
98
- getCachedIc3Info?: () => Promise<{
99
- available: boolean;
100
- expiresInSeconds: number | undefined;
101
- }>;
105
+ getCachedChatsvcaggInfo?: () => Promise<CachedTierInfo>;
106
+ getCachedIc3Info?: () => Promise<CachedTierInfo>;
107
+ };
108
+ /**
109
+ * Command names quoted in the secondary-token error messages, per token kind.
110
+ * The composition root derives these from the command registry
111
+ * (`needsElevatedToken` / `needsSubstrateToken` flags) and injects them; the
112
+ * defaults below are the corrected registry sets at the time of writing, so
113
+ * direct `createAuthManagerFromApi` callers still get accurate messages. The
114
+ * registry sets are pinned in meta.test.ts and the wiring in
115
+ * build-deps.test.ts, so drift surfaces there, not in a stale user message
116
+ * (the old hardcoded elevated list omitted `get-user`).
117
+ */
118
+ type SecondaryTokenCommands = {
119
+ readonly elevated: ReadonlyArray<string>;
120
+ readonly chatsvcagg: ReadonlyArray<string>;
121
+ readonly ic3: ReadonlyArray<string>;
102
122
  };
103
- declare const createAuthManagerFromApi: (browserAuth: BrowserAuth, cachePath: string, browserProfileDir: string, logger: Logger, fs: FileSystem, recaptureSecondaryViaBrowser?: boolean) => AuthManager;
123
+ declare const createAuthManagerFromApi: (browserAuth: BrowserAuth, cachePath: string, browserProfileDir: string, logger: Logger, fs: FileSystem, recaptureSecondaryViaBrowser?: boolean, secondaryTokenCommands?: SecondaryTokenCommands, acquireBasicViaBrowser?: boolean) => AuthManager;
104
124
  /**
105
125
  * Probe the token cache for a fresh access token. Handed to the browser
106
126
  * capture so its poll loop can short-circuit the multi-minute dance
@@ -116,6 +136,8 @@ declare const createAuthManager: (deps: {
116
136
  fs?: FileSystem;
117
137
  browserProfileDir?: string;
118
138
  recaptureSecondaryViaBrowser?: boolean;
139
+ secondaryTokenCommands?: SecondaryTokenCommands;
140
+ acquireBasicViaBrowser?: boolean;
119
141
  }) => AuthManager;
120
142
  export { createAuthManager, createAuthManagerFromApi, createFreshCachedTokenProbe, stderrProgress };
121
- export type { AuthError, AuthManager, ElevatedOutcome };
143
+ export type { AuthError, AuthManager, CachedTierInfo, ElevatedOutcome, SecondaryTokenCommands };
@@ -97,8 +97,13 @@ type BrowserAuth = {
97
97
  * different Microsoft web app whose first-party app identity is on
98
98
  * Microsoft's allow-list for ODSP `logicalPermissions` (the scope our
99
99
  * Teams web client token lacks for historical-version stream
100
- * content). Reuses the persistent profile cookies — no second
101
- * sign-in. Headless by default.
100
+ * content). Launches a HEADED browser (`launchContext(false)`) and
101
+ * relies, best-effort, on the persistent profile's cookies to complete
102
+ * sign-in silently. This is NOT guaranteed: on tenants that do not
103
+ * persist SSO cookies (or once they lapse) the navigation lands on a
104
+ * sign-in wall and the capture times out (verified headed AND headless
105
+ * 2026-07-19). That is why it stays headed and why the caller treats a
106
+ * miss as non-fatal and points the user at an interactive `login`.
102
107
  *
103
108
  * Returns a discriminated union so the caller can distinguish the three
104
109
  * failure modes (browser-launch hang, navigation failure, silent-SSO
@@ -1,5 +1,6 @@
1
1
  import type { Result } from '../domain/result.js';
2
2
  import type { AuthManager } from '../infra/auth.js';
3
+ import type { TenantId } from '../domain/tenant-id.js';
3
4
  type GraphError = {
4
5
  type: 'api_error';
5
6
  status: number;
@@ -36,6 +37,34 @@ type GraphClient = {
36
37
  * elevated token).
37
38
  */
38
39
  getElevated: (path: string) => Promise<Result<unknown, GraphError>>;
40
+ /**
41
+ * JSON-GET signed with a PARTNER tenant's guest token instead of the home
42
+ * token. Required for any path that touches a tenant the user is only a guest
43
+ * in: home-tenant Graph cannot mint a SharePoint token for a foreign tenant,
44
+ * so those calls die at `401 invalidAudienceUri` no matter which home tier
45
+ * signs them.
46
+ *
47
+ * Get the `tenantId` from `resolve-drive-share-link` (it discovers it from the
48
+ * sharing URL) or from the caller's `--tenant-id`.
49
+ */
50
+ getGuest: (path: string, tenantId: TenantId) => Promise<Result<unknown, GraphError>>;
51
+ /**
52
+ * Binary twin of `getGuest`: follows the Graph 302 to the partner tenant's CDN
53
+ * and returns the bytes. The `fetchUrl` allow-list already admits any
54
+ * `*.sharepoint.com` / `*.svc.ms` host, so a partner tenant's download URL
55
+ * needs no special casing.
56
+ */
57
+ getBinaryGuest: (path: string, tenantId: TenantId) => Promise<Result<unknown, GraphError>>;
58
+ /**
59
+ * Resolves a SharePoint host to the Entra tenant that owns it, via the tenant's
60
+ * public OIDC discovery document. Unauthenticated: it asks "who owns this
61
+ * host?", not "what may I read?".
62
+ *
63
+ * This is what makes a bare sharing URL enough to cross tenants — the URL
64
+ * carries the host, the host names the tenant, and the tenant is the one thing
65
+ * `driveId` + `itemId` do not tell you.
66
+ */
67
+ discoverTenantId: (spoHost: string) => Promise<Result<TenantId, GraphError>>;
39
68
  /**
40
69
  * JSON-GET against the Teams chat substrate (post-2026-05:
41
70
  * `teams.microsoft.com/api/csa/<region>/api/v{N}/...` — see
@@ -102,6 +131,23 @@ type GraphClient = {
102
131
  */
103
132
  getCachedTokenInfo: () => Promise<Result<TokenInfo, GraphError>>;
104
133
  };
134
+ /**
135
+ * Decode-only status for one non-basic token tier: availability, remaining runway,
136
+ * the scopes granted to that token (decoded from its `scp`), and how it refreshes
137
+ * (`automatic` = rides the shared refresh token; `interactive` = elevated, needs a login).
138
+ */
139
+ type TokenTierInfo = {
140
+ readonly available: boolean;
141
+ readonly expiresInSeconds: number | undefined;
142
+ readonly scopes: ReadonlyArray<string>;
143
+ readonly refresh: 'automatic' | 'interactive';
144
+ /**
145
+ * Present ONLY when `available` is `false`: a one-line, jargon-free reason the
146
+ * tier is absent + how to restore it. Stops the empty `scopes: []` on a missing
147
+ * token from reading as a bug. Omitted entirely when the token is available.
148
+ */
149
+ readonly reason?: string;
150
+ };
105
151
  type TokenInfo = {
106
152
  readonly scopes: ReadonlyArray<string>;
107
153
  readonly audience: string | undefined;
@@ -122,24 +168,14 @@ type TokenInfo = {
122
168
  * preflight elevated access in a fresh process instead of turning every
123
169
  * version download into a `403`.
124
170
  */
125
- readonly elevated: {
126
- readonly available: boolean;
127
- readonly expiresInSeconds: number | undefined;
128
- };
171
+ readonly elevated: TokenTierInfo;
129
172
  /**
130
- * The two Teams-chat substrate tokens (chatsvcagg / ic3), same decode-only
131
- * `{ available, expiresInSeconds }` shape as `elevated`. `login` reports all four
132
- * tiers so a warm session can see every token's runway; both self-heal from the
133
- * shared refresh token, so they are informational rather than a preflight gate.
173
+ * The two Teams-chat substrate tokens (chatsvcagg / ic3), same `TokenTierInfo`
174
+ * shape as `elevated`. Both self-heal from the shared refresh token (refresh:
175
+ * automatic), so they are informational rather than a preflight gate.
134
176
  */
135
- readonly chatsvcagg: {
136
- readonly available: boolean;
137
- readonly expiresInSeconds: number | undefined;
138
- };
139
- readonly ic3: {
140
- readonly available: boolean;
141
- readonly expiresInSeconds: number | undefined;
142
- };
177
+ readonly chatsvcagg: TokenTierInfo;
178
+ readonly ic3: TokenTierInfo;
143
179
  };
144
180
  type FetchFn = (url: string, init?: RequestInit) => Promise<Response>;
145
181
  declare const createGraphClient: (auth: AuthManager, fetchFn?: FetchFn) => GraphClient;
@@ -0,0 +1,2 @@
1
+ declare const canonicalizeGraphCursor: (link: string) => string;
2
+ export { canonicalizeGraphCursor };
@@ -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
- type OutputFormat = 'text' | 'json';
4
- declare const render: (data: unknown, logger: Logger, format: OutputFormat) => void;
3
+ import type { OutputFormat, SizeHintContext } from './render-to-string.js';
4
+ declare const render: (data: unknown, logger: Logger, format: OutputFormat, context?: SizeHintContext) => 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 };
@@ -0,0 +1,47 @@
1
+ import type { ErrorSource } from './error-hints.js';
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. Built in composition (which owns the command manifest); omitted by the
13
+ * renders that have no command behind them at all — the 548 KB `list-commands`
14
+ * manifest, a login summary, a logout status.
15
+ *
16
+ * 2026-07-23: the banner used to call `--output-path` a "universal remedy
17
+ * (works on every command)". It is not. Plain-JSON commands REFUSE the flag,
18
+ * and the two commands that trip the banner most often (`search-all-files`,
19
+ * `microsoft-search-query`) advertise only `--query` — so every remedy the
20
+ * banner named was a dead end there, and an agentic caller following it burned
21
+ * a call to find out.
22
+ */
23
+ type SizeHintContext = {
24
+ readonly commandName: string;
25
+ /** `meta.producesBytes` — the manifest flag that decides whether --output-path is accepted. */
26
+ readonly producesBytes: boolean;
27
+ readonly supportsSelect: boolean;
28
+ readonly supportsTop: boolean;
29
+ readonly surface: RenderSurface;
30
+ };
31
+ /**
32
+ * Render a use-case success value to its final string, newline included.
33
+ * `output.ts` writes this to stdout; `mcp.ts` returns it as tool content.
34
+ *
35
+ * `context` is what makes the oversized-response banner honest — pass it
36
+ * whenever a registry command produced the data. Omit it for the renders that
37
+ * have no command behind them; the banner then claims no flag-level remedy.
38
+ */
39
+ declare const renderToString: (data: unknown, format: OutputFormat, context?: SizeHintContext) => string;
40
+ /**
41
+ * Render an error to its final string, newline included. The `hint` / `source`
42
+ * lookup is shared with the CLI so an MCP consumer sees the same curated
43
+ * remedies (`error-hints.ts`) the terminal does.
44
+ */
45
+ declare const renderErrorToString: (message: string, format: OutputFormat, errorCode?: string, explicitSource?: ErrorSource, retryAfterSeconds?: number) => string;
46
+ export { renderErrorToString, renderToString };
47
+ export type { OutputFormat, RenderSurface, SizeHintContext };
@@ -14,9 +14,26 @@ import { type ODataKey } from './odata-query.js';
14
14
  type SelectDefaults = {
15
15
  readonly defaultSelect?: string;
16
16
  };
17
+ /**
18
+ * `defaultTop`, when set and the user did NOT pass `--top`, injects a `$top`
19
+ * into the OData query string. Its purpose is NOT payload-slimming (that is
20
+ * `defaultSelect`) but cursor correctness: a handful of `$skip`-offset-paged
21
+ * Graph endpoints (`/me/people` is the canonical one) only emit a
22
+ * self-advancing `@odata.nextLink` when `$top` is present. Without it Graph
23
+ * echoes back the SAME `$skip` on every page (`?$skip=0` → `?$skip=0` → …), so
24
+ * following the cursor loops forever on page 1. Forcing a default `$top` makes
25
+ * Graph increment `$skip` by the page size. Only meaningful on list builders
26
+ * whose OData shape carries `top`. User-supplied `--top` always wins.
27
+ */
28
+ type ListDefaults = SelectDefaults & {
29
+ readonly defaultTop?: string;
30
+ };
31
+ declare const withDefaultSelect: <T extends {
32
+ readonly select?: string;
33
+ }>(data: T, defaultSelect: string | undefined) => T;
17
34
  declare const buildCommand: (pathFn: (params: Record<string, string>) => string, schema: z.ZodType) => Pick<Command, "schema" | "execute">;
18
35
  declare const buildElevatedCommand: (pathFn: (params: Record<string, string>) => string, schema: z.ZodType) => Pick<Command, "schema" | "execute">;
19
- declare const buildListCommand: <Shape extends z.ZodRawShape>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>, options?: SelectDefaults) => Pick<Command, "schema" | "execute">;
36
+ declare const buildListCommand: <Shape extends z.ZodRawShape>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>, options?: ListDefaults) => Pick<Command, "schema" | "execute">;
20
37
  declare const buildElevatedListCommand: <Shape extends z.ZodRawShape>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>, options?: SelectDefaults) => Pick<Command, "schema" | "execute">;
21
38
  /**
22
39
  * Single-resource GET that supports the OData `$select` and `$expand` query
@@ -67,4 +84,4 @@ declare const buildPickODataListCommand: <Shape extends z.ZodRawShape, K extends
67
84
  * `$orderby` / `$expand` with `BadRequest`, so the picker is the right tool.
68
85
  */
69
86
  declare const buildElevatedPickODataListCommand: <Shape extends z.ZodRawShape, K extends ODataKey>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>, keys: ReadonlyArray<K>, options?: SelectDefaults) => Pick<Command, "schema" | "execute">;
70
- export { buildCommand, buildElevatedCommand, buildElevatedListCommand, buildElevatedPickODataListCommand, buildElevatedSelectableCommand, buildFilterSelectListCommand, buildListCommand, buildNoSkipListCommand, buildPickODataListCommand, buildSelectableCommand, };
87
+ export { buildCommand, buildElevatedCommand, buildElevatedListCommand, buildElevatedPickODataListCommand, buildElevatedSelectableCommand, buildFilterSelectListCommand, buildListCommand, buildNoSkipListCommand, buildPickODataListCommand, buildSelectableCommand, withDefaultSelect, };
@@ -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;
@@ -109,20 +90,25 @@ type CommandMeta = {
109
90
  readonly scopesRequired?: ReadonlyArray<string>;
110
91
  /**
111
92
  * `true` if the command needs the M365ChatClient elevated token (captured
112
- * at login from `m365.cloud.microsoft`, ODSP allow-list). Only 3 commands
113
- * today the historical-version downloads. An LLM should check this
114
- * field before invoking; if the elevated capture failed at login, these
115
- * commands will time out.
93
+ * at login from `m365.cloud.microsoft`, ODSP allow-list). The exact set is
94
+ * pinned in meta.test.ts, and the composition root derives the auth
95
+ * fail-fast message's command list from this flag a new elevated command
96
+ * MUST carry it or the remedy message will omit the command. An LLM should
97
+ * check this field before invoking; if the elevated capture failed at
98
+ * login, these commands fail fast with `secondary_token_unavailable`.
116
99
  */
117
100
  readonly needsElevatedToken?: true;
118
101
  /**
119
- * `true` if the command needs a Teams substrate token (chatsvcagg or ic3,
120
- * captured at login from `teams.microsoft.com`) the Teams chat-content
121
- * commands. Like `needsElevatedToken`, an LLM should check this before
122
- * invoking and warm up an interactive `login`; a headless or stale session
123
- * times out on these (the non-interactive silent-SSO limitation, ).
102
+ * Which Teams substrate token the command needs: `'chatsvcagg'`
103
+ * (teams.microsoft.com/api/csa) or `'ic3'` (chat history), both captured
104
+ * at login from `teams.microsoft.com`. Truthy = "needs a substrate token"
105
+ * (the historical boolean semantics); the service value additionally
106
+ * routes the command into the right auth fail-fast list, derived by the
107
+ * composition root. Like `needsElevatedToken`, an LLM should check this
108
+ * before invoking and warm up an interactive `login`; a headless or stale
109
+ * session times out on these (the non-interactive silent-SSO limitation).
124
110
  */
125
- readonly needsSubstrateToken?: true;
111
+ readonly needsSubstrateToken?: 'chatsvcagg' | 'ic3';
126
112
  /**
127
113
  * `true` if the command returns inlined bytes (`{contentType, size, base64}`
128
114
  * or `{contentType, size, text}`) and is therefore a valid target for the
@@ -167,10 +153,10 @@ type Command = {
167
153
  readonly meta: CommandMeta;
168
154
  /**
169
155
  * Present on the rare command whose input is the LOCAL filesystem instead of
170
- * Graph (`convert-local-file`). The CLI routes execution here, passing its
156
+ * Graph (`convert-local-file-to-markdown`). The CLI routes execution here, passing its
171
157
  * composition-selected FileSystem; `execute` stays as the registry-typed
172
158
  * fallback that redirects library consumers to this variant.
173
159
  */
174
160
  readonly executeLocal?: (fs: import('../ports/filesystem.js').FileSystem, params: Record<string, string>) => Promise<Result<unknown, import('../../infra/graph-client.js').GraphError>>;
175
161
  };
176
- 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, };
@@ -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;
@@ -14,12 +14,17 @@ import type { CommandMeta } from './command-types.js';
14
14
  * rather than failing the whole archive.
15
15
  */
16
16
  declare const schema: z.ZodObject<{
17
+ tenantId: z.ZodOptional<z.ZodString>;
17
18
  driveId: z.ZodString;
18
19
  itemId: z.ZodString;
19
20
  includeMetadata: z.ZodOptional<z.ZodEnum<{
20
21
  true: "true";
21
22
  false: "false";
22
23
  }>>;
24
+ keepQuoted: z.ZodOptional<z.ZodEnum<{
25
+ true: "true";
26
+ false: "false";
27
+ }>>;
23
28
  }, z.core.$strip>;
24
29
  declare const execute: (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, GraphError>>;
25
30
  declare const meta: CommandMeta;
@@ -29,6 +29,10 @@ declare const schema: z.ZodObject<{
29
29
  true: "true";
30
30
  false: "false";
31
31
  }>>;
32
+ keepQuoted: z.ZodOptional<z.ZodEnum<{
33
+ true: "true";
34
+ false: "false";
35
+ }>>;
32
36
  includeImages: z.ZodOptional<z.ZodEnum<{
33
37
  true: "true";
34
38
  false: "false";
@@ -9,9 +9,17 @@ 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
- declare const convertFetchedAttachment: (graph: GraphClient, a: Record<string, unknown>, includeMetadata: boolean) => Promise<Result<unknown, GraphError>> | Result<unknown, GraphError>;
14
- declare const convertAttachmentToMarkdown: (graph: GraphClient, attachmentPath: string, includeMetadata: boolean) => Promise<Result<unknown, GraphError>>;
17
+ type ConvertOptions = {
18
+ readonly includeMetadata: boolean;
19
+ readonly keepQuoted: boolean;
20
+ };
21
+ declare const convertFetchedAttachment: (graph: GraphClient, a: Record<string, unknown>, opts: ConvertOptions) => Promise<Result<unknown, GraphError>> | Result<unknown, GraphError>;
22
+ declare const convertAttachmentToMarkdown: (graph: GraphClient, attachmentPath: string, opts: ConvertOptions) => Promise<Result<unknown, GraphError>>;
15
23
  declare const execute: (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, GraphError>>;
16
24
  declare const meta: CommandMeta;
17
25
  export { convertAttachmentToMarkdown, convertFetchedAttachment, execute, meta, schema };
@@ -4,7 +4,7 @@ import type { GraphClient, GraphError } from '../../infra/graph-client.js';
4
4
  import type { CommandMeta } from './command-types.js';
5
5
  /**
6
6
  * Unzips a `.zip` Outlook mail attachment and converts every contained file in one
7
- * call — the mail-side mirror of `convert-drive-item-zip`. Without it, reading a
7
+ * call — the mail-side mirror of `convert-drive-item-zip-to-markdown`. Without it, reading a
8
8
  * zipped vendor deck meant: `get-mail-attachment --output-path x.zip` → manual
9
9
  * `unzip` (with `-O GBK` for Chinese names) → convert each file. This collapses all
10
10
  * of that into one command: it pulls the fileAttachment bytes, unzips them (legacy
@@ -18,6 +18,10 @@ declare const schema: z.ZodObject<{
18
18
  true: "true";
19
19
  false: "false";
20
20
  }>>;
21
+ keepQuoted: z.ZodOptional<z.ZodEnum<{
22
+ true: "true";
23
+ false: "false";
24
+ }>>;
21
25
  }, z.core.$strip>;
22
26
  declare const execute: (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, GraphError>>;
23
27
  declare const meta: CommandMeta;
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  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
+ import { type InlineAttachment } from './inline-image-embedder.js';
5
6
  declare const schema: z.ZodObject<{
6
7
  messageId: z.ZodString;
7
8
  inlineImages: z.ZodOptional<z.ZodEnum<{
@@ -13,6 +14,39 @@ declare const schema: z.ZodObject<{
13
14
  false: "false";
14
15
  }>>;
15
16
  }, z.core.$strip>;
17
+ declare const ATTACHMENT_METADATA_SELECT = "$select=id,name,contentType,size,isInline,microsoft.graph.fileAttachment/contentId";
18
+ declare const attachmentMetaSchema: z.ZodObject<{
19
+ id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
20
+ name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
21
+ contentType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
22
+ size: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
23
+ isInline: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
24
+ contentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
25
+ }, z.core.$strip>;
26
+ declare const attachmentsListSchema: z.ZodObject<{
27
+ value: z.ZodOptional<z.ZodArray<z.ZodObject<{
28
+ id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
29
+ name: z.ZodOptional<z.ZodNullable<z.ZodString>>;
30
+ contentType: z.ZodOptional<z.ZodNullable<z.ZodString>>;
31
+ size: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
32
+ isInline: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
33
+ contentId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
34
+ }, z.core.$strip>>>;
35
+ }, z.core.$strip>;
36
+ type AttachmentMeta = z.infer<typeof attachmentMetaSchema>;
37
+ declare const formatBytes: (n: number) => string;
38
+ type InlineImageCandidate = AttachmentMeta & {
39
+ readonly contentType: string;
40
+ readonly contentId: string;
41
+ };
42
+ declare const isInlineImage: (a: AttachmentMeta) => a is InlineImageCandidate;
43
+ type EmbedFetchResult = {
44
+ readonly meta: InlineImageCandidate;
45
+ readonly inline?: InlineAttachment;
46
+ readonly oversize: boolean;
47
+ };
48
+ declare const fetchInlineImageBytes: (graph: GraphClient, messageId: string, meta: InlineImageCandidate) => Promise<EmbedFetchResult>;
16
49
  declare const execute: (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, GraphError>>;
17
50
  declare const meta: CommandMeta;
18
- export { execute, meta, schema };
51
+ export { ATTACHMENT_METADATA_SELECT, attachmentsListSchema, execute, fetchInlineImageBytes, formatBytes, isInlineImage, meta, schema };
52
+ export type { AttachmentMeta, InlineImageCandidate };
@@ -4,8 +4,12 @@ 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
+ bodyContentType: z.ZodOptional<z.ZodEnum<{
10
+ Text: "Text";
11
+ HTML: "HTML";
12
+ }>>;
9
13
  }, z.core.$strip>;
10
14
  declare const execute: Command['execute'];
11
15
  declare const meta: CommandMeta;
@@ -2,8 +2,16 @@ 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
+ replyAll: z.ZodOptional<z.ZodEnum<{
8
+ true: "true";
9
+ false: "false";
10
+ }>>;
11
+ bodyContentType: z.ZodOptional<z.ZodEnum<{
12
+ Text: "Text";
13
+ HTML: "HTML";
14
+ }>>;
7
15
  }, z.core.$strip>;
8
16
  declare const execute: Command['execute'];
9
17
  declare const meta: CommandMeta;
@@ -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;
@@ -3,8 +3,6 @@ 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
- driveId: z.ZodString;
7
- itemId: z.ZodString;
8
6
  includeMetadata: z.ZodOptional<z.ZodEnum<{
9
7
  true: "true";
10
8
  false: "false";
@@ -13,7 +11,14 @@ declare const schema: z.ZodObject<{
13
11
  true: "true";
14
12
  false: "false";
15
13
  }>>;
14
+ keepQuoted: z.ZodOptional<z.ZodEnum<{
15
+ true: "true";
16
+ false: "false";
17
+ }>>;
16
18
  maxCells: z.ZodOptional<z.ZodString>;
19
+ tenantId: z.ZodOptional<z.ZodString>;
20
+ driveId: z.ZodString;
21
+ itemId: z.ZodString;
17
22
  }, z.core.$strip>;
18
23
  declare const execute: (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, GraphError>>;
19
24
  declare const meta: CommandMeta;
@@ -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>;
@@ -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>;