ask-marcel-office-cli 1.4.0 → 1.5.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 (107) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +68 -24
  3. package/dist/cli.js +7631 -90932
  4. package/dist/commands.json +1230 -70
  5. package/dist/composition/build-deps.d.ts +13 -0
  6. package/dist/composition/cli.d.ts +8 -0
  7. package/dist/domain/iso-datetime.d.ts +1 -1
  8. package/dist/domain/utilities/archive-status.d.ts +18 -0
  9. package/dist/domain/utilities/site-url-classifier.d.ts +15 -0
  10. package/dist/index.js +10421 -93852
  11. package/dist/infra/auth.d.ts +26 -3
  12. package/dist/infra/browser-auth.d.ts +25 -0
  13. package/dist/infra/graph-client.d.ts +3 -1
  14. package/dist/infra/legacy-doc-extractor.d.ts +14 -0
  15. package/dist/infra/msg-reader-adapter.d.ts +83 -0
  16. package/dist/infra/network-error.d.ts +1 -1
  17. package/dist/infra/ooxml-media-extractor.d.ts +27 -0
  18. package/dist/infra/ooxml-zip-adapter.d.ts +28 -0
  19. package/dist/infra/pdf-image-extractor.d.ts +18 -0
  20. package/dist/infra/pdf-text-extractor.d.ts +41 -0
  21. package/dist/infra/png-encode.d.ts +15 -0
  22. package/dist/infra/sheetjs-adapter.d.ts +1 -1
  23. package/dist/infra/system-browser-auth.d.ts +35 -0
  24. package/dist/infra/system-browser-loader.d.ts +3 -0
  25. package/dist/infra/token-callback-server.d.ts +31 -0
  26. package/dist/infra/zip-reader.d.ts +32 -0
  27. package/dist/presenter/error-hints.d.ts +2 -2
  28. package/dist/presenter/output.d.ts +1 -1
  29. package/dist/use-cases/commands/build-command.d.ts +1 -1
  30. package/dist/use-cases/commands/command-types.d.ts +42 -1
  31. package/dist/use-cases/commands/convert-calendar-event-attachment-to-markdown.d.ts +15 -0
  32. package/dist/use-cases/commands/convert-calendar-event-attachment-to-pdf.d.ts +11 -0
  33. package/dist/use-cases/commands/convert-drive-item-zip.d.ts +26 -0
  34. package/dist/use-cases/commands/convert-local-file.d.ts +37 -0
  35. package/dist/use-cases/commands/convert-mail-attachment-to-markdown.d.ts +7 -1
  36. package/dist/use-cases/commands/convert-mail-attachment-to-pdf.d.ts +2 -1
  37. package/dist/use-cases/commands/convert-mail-attachment-zip.d.ts +24 -0
  38. package/dist/use-cases/commands/convert-mail-to-markdown.d.ts +4 -0
  39. package/dist/use-cases/commands/create-mail-draft.d.ts +22 -0
  40. package/dist/use-cases/commands/doc-to-markdown.d.ts +14 -0
  41. package/dist/use-cases/commands/docs-render.d.ts +3 -0
  42. package/dist/use-cases/commands/docs.d.ts +1 -1
  43. package/dist/use-cases/commands/docx-comment-anchors.d.ts +22 -0
  44. package/dist/use-cases/commands/docx-metadata-to-markdown.d.ts +3 -0
  45. package/dist/use-cases/commands/docx-metadata.d.ts +69 -0
  46. package/dist/use-cases/commands/docx-to-markdown.d.ts +8 -3
  47. package/dist/use-cases/commands/download-drive-item-as-markdown.d.ts +9 -0
  48. package/dist/use-cases/commands/download-drive-item-version.d.ts +4 -0
  49. package/dist/use-cases/commands/extract-drive-item-images.d.ts +11 -0
  50. package/dist/use-cases/commands/extract-local-file-images.d.ts +21 -0
  51. package/dist/use-cases/commands/extract-mail-attachment-images.d.ts +11 -0
  52. package/dist/use-cases/commands/extract-sharepoint-links-in-documents.d.ts +33 -0
  53. package/dist/use-cases/commands/extract-sharepoint-links-in-mail.d.ts +1 -8
  54. package/dist/use-cases/commands/fetch-raw-bytes.d.ts +1 -0
  55. package/dist/use-cases/commands/file-counts.d.ts +7 -0
  56. package/dist/use-cases/commands/filter-archived-sites.d.ts +17 -0
  57. package/dist/use-cases/commands/first-sentence.d.ts +12 -0
  58. package/dist/use-cases/commands/get-excel-chart-image.d.ts +20 -0
  59. package/dist/use-cases/commands/get-onenote-page-as-markdown.d.ts +8 -0
  60. package/dist/use-cases/commands/get-todo-task.d.ts +9 -2
  61. package/dist/use-cases/commands/image-extraction.d.ts +10 -0
  62. package/dist/use-cases/commands/iso-datetime-schema.d.ts +1 -1
  63. package/dist/use-cases/commands/list-accessible-drives.d.ts +14 -0
  64. package/dist/use-cases/commands/list-calendar-event-attachments.d.ts +14 -0
  65. package/dist/use-cases/commands/mail-quote-stripper.d.ts +25 -0
  66. package/dist/use-cases/commands/markdown-dispatch.d.ts +29 -0
  67. package/dist/use-cases/commands/markdown-pipeline.d.ts +6 -2
  68. package/dist/use-cases/commands/media-files.d.ts +20 -0
  69. package/dist/use-cases/commands/msg-to-markdown.d.ts +28 -0
  70. package/dist/use-cases/commands/odf-content-to-markdown.d.ts +5 -0
  71. package/dist/use-cases/commands/odf-metadata-to-markdown.d.ts +3 -0
  72. package/dist/use-cases/commands/odf-metadata.d.ts +26 -0
  73. package/dist/use-cases/commands/odf-to-markdown.d.ts +17 -0
  74. package/dist/use-cases/commands/office-extensions.d.ts +13 -0
  75. package/dist/use-cases/commands/office-to-markdown.d.ts +6 -2
  76. package/dist/use-cases/commands/onenote-metadata.d.ts +21 -0
  77. package/dist/use-cases/commands/onenote-resource-embedder.d.ts +3 -0
  78. package/dist/use-cases/commands/ooxml-metadata-to-markdown.d.ts +16 -0
  79. package/dist/use-cases/commands/ooxml-metadata.d.ts +23 -0
  80. package/dist/use-cases/commands/ooxml-xml-walker.d.ts +36 -0
  81. package/dist/use-cases/commands/option-descriptions.d.ts +5 -0
  82. package/dist/use-cases/commands/output-path.d.ts +23 -0
  83. package/dist/use-cases/commands/pdf-to-markdown.d.ts +19 -0
  84. package/dist/use-cases/commands/pptx-comments.d.ts +24 -0
  85. package/dist/use-cases/commands/pptx-metadata-to-markdown.d.ts +3 -0
  86. package/dist/use-cases/commands/pptx-metadata.d.ts +33 -0
  87. package/dist/use-cases/commands/pptx-slides.d.ts +17 -0
  88. package/dist/use-cases/commands/pptx-to-markdown.d.ts +7 -0
  89. package/dist/use-cases/commands/read-mail-attachment.d.ts +15 -0
  90. package/dist/use-cases/commands/search-all-accessible-sites.d.ts +12 -0
  91. package/dist/use-cases/commands/search-index-total.d.ts +3 -0
  92. package/dist/use-cases/commands/search-sharepoint-sites-by-name.d.ts +3 -2
  93. package/dist/use-cases/commands/sharepoint-link-extractor.d.ts +17 -12
  94. package/dist/use-cases/commands/text-passthrough.d.ts +15 -2
  95. package/dist/use-cases/commands/todo-parse-uri-rewrite.d.ts +7 -0
  96. package/dist/use-cases/commands/update-mail-draft.d.ts +22 -0
  97. package/dist/use-cases/commands/xlsx-comments.d.ts +29 -0
  98. package/dist/use-cases/commands/xlsx-metadata-to-markdown.d.ts +3 -0
  99. package/dist/use-cases/commands/xlsx-metadata.d.ts +39 -0
  100. package/dist/use-cases/commands/xlsx-to-markdown.d.ts +9 -2
  101. package/dist/use-cases/commands/zip-archive-to-markdown.d.ts +30 -0
  102. package/dist/use-cases/ports/filesystem.d.ts +10 -0
  103. package/docs/COMMANDS.md +34 -18
  104. package/docs/USAGE.md +25 -8
  105. package/docs/commands.json +1230 -70
  106. package/package.json +14 -8
  107. /package/dist/use-cases/commands/{download-onedrive-file-content.d.ts → download-drive-item-content.d.ts} +0 -0
@@ -70,12 +70,35 @@ type AuthManager = {
70
70
  */
71
71
  getLastChatsvcaggOutcome: () => ElevatedOutcome | null;
72
72
  };
73
- declare const createAuthManagerFromApi: (browserAuth: BrowserAuth, cachePath: string, browserProfileDir: string, logger: Logger, fs: FileSystem) => AuthManager;
73
+ type SystemBrowserAuthFn = () => Promise<Result<{
74
+ accessToken: AccessToken;
75
+ refreshToken: string | null;
76
+ elevatedAccessToken?: AccessToken | null;
77
+ chatsvcaggAccessToken?: AccessToken | null;
78
+ ic3AccessToken?: AccessToken | null;
79
+ chatsvcaggRegion?: string;
80
+ }, {
81
+ type: string;
82
+ message: string;
83
+ }>>;
84
+ declare const createAuthManagerFromApi: (browserAuth: BrowserAuth, cachePath: string, browserProfileDir: string, logger: Logger, fs: FileSystem, systemBrowserAuthFn?: SystemBrowserAuthFn, usePlaywrightFallback?: boolean, skipSystemBrowser?: boolean) => AuthManager;
85
+ /**
86
+ * QA-010: probe the token cache for a fresh access token. Handed to the
87
+ * browser capture so its poll loop can short-circuit the multi-minute dance
88
+ * when a concurrent process refreshes first (AAD rotates SPA refresh tokens,
89
+ * so the loser of the race cannot refresh and falls into the browser leg).
90
+ * Exported for the composition test; pure read, never writes.
91
+ */
92
+ declare const createFreshCachedTokenProbe: (fs: FileSystem, cachePath: string) => (() => Promise<string | null>);
93
+ declare const stderrProgress: (line: string) => void;
74
94
  declare const createAuthManager: (deps: {
75
95
  cachePath: string;
76
96
  logger: Logger;
77
97
  fs?: FileSystem;
78
98
  browserProfileDir?: string;
99
+ systemBrowserAuth?: SystemBrowserAuthFn;
100
+ usePlaywrightFallback?: boolean;
101
+ skipSystemBrowser?: boolean;
79
102
  }) => AuthManager;
80
- export { createAuthManager, createAuthManagerFromApi };
81
- export type { AuthError, AuthManager, ElevatedOutcome };
103
+ export { createAuthManager, createAuthManagerFromApi, createFreshCachedTokenProbe, stderrProgress };
104
+ export type { AuthError, AuthManager, ElevatedOutcome, SystemBrowserAuthFn };
@@ -82,6 +82,14 @@ type BothTokensResult = {
82
82
  readonly elevated: ElevatedTokenResult;
83
83
  readonly chatsvcagg: ChatsvcaggTokenResult;
84
84
  readonly ic3: Ic3TokenResult;
85
+ /**
86
+ * Set when the Teams poll short-circuited because `freshCachedToken`
87
+ * found a token written by a concurrent process (QA-010). The caller
88
+ * must NOT persist this result — the cache is already the source of
89
+ * truth, and `refreshToken` is null here (persisting would clobber the
90
+ * winner's rotated refresh token).
91
+ */
92
+ readonly fromCache?: true;
85
93
  };
86
94
  type BrowserAuth = {
87
95
  acquireToken: (scopes: string[], startUrl: string) => Promise<BrowserTokenResult | null>;
@@ -190,6 +198,21 @@ type BrowserAuthConfig = {
190
198
  readonly logger: Logger;
191
199
  readonly fs: FileSystem;
192
200
  readonly trace?: TraceFn;
201
+ /**
202
+ * QA-010 (login hang): probe for a fresh token that landed in the cache
203
+ * WHILE the browser capture is polling. AAD SPA refresh tokens rotate, so
204
+ * when two processes race, the loser's refresh fails and it falls into the
205
+ * full multi-minute browser dance — while the winner's fresh token sits in
206
+ * the cache the whole time. The Teams poll loop calls this every interval
207
+ * and short-circuits (closing the browser) the moment it returns a token.
208
+ */
209
+ readonly freshCachedToken?: () => Promise<string | null>;
210
+ /**
211
+ * User-visible progress sink (stderr in production). The browser capture
212
+ * can legitimately take minutes (federated SSO, interactive sign-in); these
213
+ * one-liners are what separates "waiting on the user" from "hung" (QA-010).
214
+ */
215
+ readonly onProgress?: (line: string) => void;
193
216
  readonly profileDir?: string;
194
217
  readonly initialSettleMs?: number;
195
218
  readonly postReloginSettleMs?: number;
@@ -223,6 +246,8 @@ declare const createBrowserAuthFromApi: (api: BrowserAuthApi, config: BrowserAut
223
246
  declare const createBrowserAuth: (deps: {
224
247
  logger: Logger;
225
248
  fs?: FileSystem;
249
+ freshCachedToken?: () => Promise<string | null>;
250
+ onProgress?: (line: string) => void;
226
251
  }) => BrowserAuth;
227
252
  export { createBrowserAuth, createBrowserAuthFromApi, createPlaywrightApi };
228
253
  export type { BothTokensResult, BrowserAuth, BrowserAuthApi, BrowserAuthConfig, BrowserTokenResult, ChatsvcaggTokenResult, Ic3TokenResult, ChromiumLike, ContextLike, ElevatedFailureReason, ElevatedTokenResult, PageLike, PlaywrightLoader, RequestLike, ResponseLike, };
@@ -5,6 +5,7 @@ type GraphError = {
5
5
  status: number;
6
6
  message: string;
7
7
  code?: string;
8
+ retryAfterSeconds?: number;
8
9
  } | {
9
10
  type: 'auth_failed';
10
11
  message: string;
@@ -66,6 +67,7 @@ type GraphClient = {
66
67
  */
67
68
  teamsChatIc3: (path: string) => Promise<Result<unknown, GraphError>>;
68
69
  post: (path: string, body: unknown) => Promise<Result<unknown, GraphError>>;
70
+ patch: (path: string, body: unknown) => Promise<Result<unknown, GraphError>>;
69
71
  getBinary: (path: string) => Promise<Result<unknown, GraphError>>;
70
72
  /**
71
73
  * Same shape as `getBinary` but signs the request with an "elevated"
@@ -107,7 +109,7 @@ type TokenInfo = {
107
109
  /**
108
110
  * Seconds remaining until the cached token's `exp` claim — derived from
109
111
  * `expiresAt - now`. Negative when the token has already expired. Absent
110
- * when the JWT did not carry an `exp` claim. Audit Hervé-session §4: lets
112
+ * when the JWT did not carry an `exp` claim. Audit Jane-session §4: lets
111
113
  * an LLM decide pre-emptively to run `ask-marcel login` (re-auth typically
112
114
  * worth doing under ~5 minutes) without parsing the ISO string itself.
113
115
  */
@@ -0,0 +1,14 @@
1
+ import type { Result } from '../domain/result.js';
2
+ import type { GraphError } from './graph-client.js';
3
+ /**
4
+ * Extract the text of a legacy Word .doc — the pre-2007 OLE binary "Compound
5
+ * File" format that mammoth (which only reads the OOXML .docx zip) cannot parse —
6
+ * via word-extractor (pure-JS, walks the WordDocument OLE stream; no native deps).
7
+ * Body text only: legacy .doc carries no structure this CLI surfaces (headings,
8
+ * tables, and styling are lost), so callers return it as text/plain.
9
+ *
10
+ * try/catch is permitted here per the infra-boundary rule: word-extractor throws
11
+ * on a non-.doc / corrupt OLE container, and we translate that into a Result.
12
+ */
13
+ declare const extractDocText: (bytes: Uint8Array) => Promise<Result<string, GraphError>>;
14
+ export { extractDocText };
@@ -0,0 +1,83 @@
1
+ import type { Result } from '../domain/result.js';
2
+ import type { GraphError } from './graph-client.js';
3
+ /**
4
+ * Parse an Outlook `.msg` file — the OLE Compound File (CFBF) container Outlook
5
+ * writes when you drag an email to disk, same binary family as legacy .doc/.xls —
6
+ * into a clean {@link ParsedMsg} via @kenjiuno/msgreader (pure-JS, no native deps,
7
+ * lazy-imported like word-extractor / unpdf so it stays out of the cold-start path).
8
+ *
9
+ * The adapter is the anti-corruption layer: msgreader returns a wide `FieldsData`
10
+ * (X500 sender addresses, RTF body, hundreds of MAPI props); {@link mapRawMsg}
11
+ * narrows it to the half-dozen fields the markdown renderer needs and resolves the
12
+ * sender SMTP / delivery-time fallbacks. `mapRawMsg` is pure and exported so every
13
+ * fallback branch is unit-tested with plain objects — the binary fixture only
14
+ * exercises one field combination.
15
+ *
16
+ * try/catch is permitted here per the infra-boundary rule: msgreader throws on a
17
+ * non-OLE / corrupt container, and a single unreadable attachment must not sink the
18
+ * whole parse, so attachment reads are individually guarded.
19
+ */
20
+ type MsgRecipientKind = 'to' | 'cc' | 'bcc' | 'unknown';
21
+ type MsgRecipient = {
22
+ readonly kind: MsgRecipientKind;
23
+ readonly name?: string;
24
+ readonly email?: string;
25
+ };
26
+ type MsgAttachment = {
27
+ readonly fileName?: string;
28
+ readonly content?: Uint8Array;
29
+ };
30
+ type ParsedMsg = {
31
+ readonly subject?: string;
32
+ readonly senderName?: string;
33
+ readonly senderEmail?: string;
34
+ readonly date?: string;
35
+ readonly body?: string;
36
+ readonly bodyHtml?: string;
37
+ readonly recipients: readonly MsgRecipient[];
38
+ readonly attachments: readonly MsgAttachment[];
39
+ };
40
+ type RawRecipient = {
41
+ readonly recipType?: string;
42
+ readonly name?: string;
43
+ readonly smtpAddress?: string;
44
+ readonly email?: string;
45
+ };
46
+ type RawAttachment = {
47
+ readonly fileName?: string;
48
+ };
49
+ type RawMsg = {
50
+ readonly subject?: string;
51
+ readonly senderName?: string;
52
+ readonly senderSmtpAddress?: string;
53
+ readonly senderEmail?: string;
54
+ readonly messageDeliveryTime?: string;
55
+ readonly clientSubmitTime?: string;
56
+ readonly body?: string;
57
+ readonly bodyHtml?: string;
58
+ readonly recipients?: readonly RawRecipient[];
59
+ readonly attachments?: readonly RawAttachment[];
60
+ };
61
+ declare const mapRawMsg: (raw: RawMsg, attachmentContents: readonly (Uint8Array | undefined)[]) => ParsedMsg;
62
+ type AttachmentReader = {
63
+ readonly getAttachment: (index: number) => {
64
+ readonly content?: unknown;
65
+ };
66
+ };
67
+ type MsgReaderInstance = AttachmentReader & {
68
+ readonly getFileData: () => unknown;
69
+ };
70
+ type MsgReaderCtor = new (input: ArrayBuffer | DataView) => MsgReaderInstance;
71
+ declare const readAttachmentContent: (reader: AttachmentReader, index: number) => Uint8Array | undefined;
72
+ /**
73
+ * Resolve the MsgReader class from the dynamic import's `default`, whichever
74
+ * interop shape it arrives in. msgreader is CJS with `exports.default = class`
75
+ * (+ `__esModule`): Bun's RUNTIME import hands us the class directly, but Bun's
76
+ * BUNDLER (node-mode `__toESM`) sets `default` to the whole exports object —
77
+ * so in `dist/cli.js` the class sits one level deeper at `default.default`
78
+ * ("Object is not a constructor" at runtime, invisible to source-run tests).
79
+ */
80
+ declare const resolveMsgReaderCtor: (defaultExport: unknown) => MsgReaderCtor;
81
+ declare const extractMsg: (bytes: Uint8Array) => Promise<Result<ParsedMsg, GraphError>>;
82
+ export { extractMsg, mapRawMsg, readAttachmentContent, resolveMsgReaderCtor };
83
+ export type { MsgAttachment, MsgRecipient, MsgRecipientKind, ParsedMsg };
@@ -3,7 +3,7 @@ export declare const BINARY_TRANSFER_TIMEOUT_MS: number;
3
3
  export declare const REQUEST_TIMEOUT_LABEL = "60s";
4
4
  export declare const BINARY_TRANSFER_TIMEOUT_LABEL = "5min";
5
5
  export declare const networkErrorMessage: (e: unknown, label: string, timeoutLabel: string) => string;
6
- export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
6
+ export type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
7
7
  export type TimeoutTier = 'json' | 'binary';
8
8
  export declare const timeoutLabelFor: (tier: TimeoutTier) => string;
9
9
  export declare const timeoutMsFor: (tier: TimeoutTier) => number;
@@ -0,0 +1,27 @@
1
+ import type { Result } from '../domain/result.js';
2
+ import type { GraphError } from './graph-client.js';
3
+ /**
4
+ * Pulls the image parts out of an OOXML package (.docx / .xlsx /
5
+ * .pptx and their macro-enabled / template variants). Images live as binary
6
+ * parts under `word/media/`, `xl/media/`, or `ppt/media/` — including
7
+ * original full-resolution / un-cropped originals and images on hidden
8
+ * slides that the rendered view never shows.
9
+ *
10
+ * Separate from `ooxml-zip-adapter` on purpose: that adapter pre-decodes
11
+ * every entry as a UTF-8 string (correct for XML, corrupting for binary),
12
+ * whereas media must come back as raw bytes. try/catch is permitted here
13
+ * (src/infra/**, atelier rule 17): a malformed-zip throw becomes a Result.err.
14
+ *
15
+ * Raster formats (png/jpg/gif/bmp/tiff/webp) a vision model can read directly,
16
+ * plus svg — the one vector format a model can use, because its XML source
17
+ * carries the diagram's own text labels (and modern Office embeds it as the
18
+ * high-fidelity original beside a png fallback). Legacy vector (emf/wmf) and
19
+ * audio/video are still excluded — neither a model can use.
20
+ */
21
+ type MediaPart = {
22
+ readonly path: string;
23
+ readonly bytes: Uint8Array;
24
+ };
25
+ declare const extractOoxmlMedia: (bytes: Uint8Array) => Promise<Result<ReadonlyArray<MediaPart>, GraphError>>;
26
+ export { extractOoxmlMedia };
27
+ export type { MediaPart };
@@ -0,0 +1,28 @@
1
+ import type { Result } from '../domain/result.js';
2
+ import type { GraphError } from './graph-client.js';
3
+ /**
4
+ * Thin wrapper around JSZip for opening already-fetched OOXML bytes
5
+ * (.docx / .xlsx / .pptx — all ZIP packages with a shared docProps/* core).
6
+ * Pure CPU on bytes already in memory — no IO — so this matches the
7
+ * mammoth-adapter pattern: a Result-returning factory, no port, no fake.
8
+ * Tests work end-to-end against real fixtures built with the `docx` /
9
+ * `xlsx` packages (or hand-rolled JSZip) via test-helpers/office-fixtures.ts.
10
+ *
11
+ * try/catch is permitted under src/infra/** (atelier hard rule 17): any
12
+ * malformed-zip throw from JSZip translates into a Result.err with a
13
+ * GraphError shape so callers stay on the Result rail.
14
+ *
15
+ * The returned reader is synchronous on purpose: callers in the use-case
16
+ * layer are pure XML walkers that don't model async/await internally.
17
+ * Every text entry is pre-decoded once at open time, then served from
18
+ * an in-memory map. `list()` exposes the entry paths so callers can
19
+ * enumerate numbered parts (`xl/comments1.xml`, `ppt/slides/slide*.xml`,
20
+ * every `*.rels`) that can't be hardcoded.
21
+ */
22
+ type OoxmlZip = {
23
+ readonly read: (path: string) => string | undefined;
24
+ readonly list: () => ReadonlyArray<string>;
25
+ };
26
+ declare const openOoxmlZip: (bytes: Uint8Array) => Promise<Result<OoxmlZip, GraphError>>;
27
+ export { openOoxmlZip };
28
+ export type { OoxmlZip };
@@ -0,0 +1,18 @@
1
+ import type { Result } from '../domain/result.js';
2
+ import type { GraphError } from './graph-client.js';
3
+ import type { MediaPart } from './ooxml-media-extractor.js';
4
+ /**
5
+ * Extract embedded raster images from a PDF via unpdf (a pure-JS pdfjs build — no
6
+ * native deps, runs under Bun). unpdf walks each page's painted image XObjects and
7
+ * returns *decoded* pixels, which we PNG-encode.
8
+ *
9
+ * Scope note: this is page-oriented — it sees images AS PAINTED on each page. It does
10
+ * NOT reach OCG/layer-hidden images, unpainted/orphan XObjects, or the full uncropped
11
+ * original behind a clipped image; capturing those needs an object-graph walk (a much
12
+ * heavier dependency). All PAGES are always walked (PDF has no "hidden page" concept).
13
+ *
14
+ * `try/catch` is permitted here per the infra-boundary rule: pdfjs throws on malformed
15
+ * input and we translate that into a Result rather than letting it escape.
16
+ */
17
+ declare const extractPdfImages: (bytes: Uint8Array) => Promise<Result<ReadonlyArray<MediaPart>, GraphError>>;
18
+ export { extractPdfImages };
@@ -0,0 +1,41 @@
1
+ import type { Result } from '../domain/result.js';
2
+ import type { GraphError } from './graph-client.js';
3
+ /**
4
+ * Extract a PDF's text layer via unpdf (a pure-JS pdfjs build — no native deps,
5
+ * runs under Bun). Returns the reading-order text of every page merged into one
6
+ * string. A born-digital PDF (exported from Word/LaTeX/Chrome/…) yields its text;
7
+ * a scanned / image-only PDF has no text layer, so the string comes back empty —
8
+ * callers treat that as "needs OCR / a vision model", never as success-with-text.
9
+ *
10
+ * Scope note: this is the embedded text layer, NOT OCR. It also does not preserve
11
+ * layout structure (headings, tables, columns flatten into reading order).
12
+ *
13
+ * `try/catch` is permitted here per the infra-boundary rule: pdfjs throws on
14
+ * malformed input and we translate that into a Result rather than letting it escape.
15
+ *
16
+ * `verbosity: 0` (pdfjs `VerbosityLevel.ERRORS`) silences pdfjs's TrueType
17
+ * font-hinting log spam — `Warning: TT: undefined function: N`, `Required "glyf"
18
+ * table is not found`, `Indexing all PDF objects` — which it emits via its own
19
+ * `console.warn` while parsing a PDF's embedded fonts. Font hinting is sub-pixel
20
+ * glyph rendering, completely irrelevant to text extraction (the text still comes
21
+ * out fine); the default level (WARNINGS) just floods the host app's logs once per
22
+ * quirky font. We lower it at the boundary because the noise originates inside
23
+ * pdfjs — the `no-console` rule keeps us from emitting or intercepting it ourselves.
24
+ */
25
+ /**
26
+ * Map a pdfjs throw to an actionable message. The special case is an encrypted
27
+ * PDF: pdfjs raises `PasswordException` ("No password given") — QA run-1 found a
28
+ * real tenant file surfacing that raw string with zero guidance. Password
29
+ * handling is genuinely unsupported (and Graph's `format=pdf` cannot unlock
30
+ * encrypted sources either), so say that honestly instead of leaking pdfjs
31
+ * internals. Pure and exported: the branch is unit-tested with shaped errors —
32
+ * no encrypted-PDF fixture exists in the toolchain.
33
+ */
34
+ declare const pdfErrorMessage: (e: unknown) => string;
35
+ type PdfText = {
36
+ readonly text: string;
37
+ readonly pageCount: number;
38
+ };
39
+ declare const extractPdfText: (bytes: Uint8Array) => Promise<Result<PdfText, GraphError>>;
40
+ export { extractPdfText, pdfErrorMessage };
41
+ export type { PdfText };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Minimal, dependency-free PNG encoder for raw 8-bit pixel buffers. unpdf hands
3
+ * back *decoded* pixels (1=grayscale, 3=RGB, 4=RGBA channels), so to write usable
4
+ * image files without a native dep (sharp/canvas) we wrap them in a PNG ourselves:
5
+ * signature + IHDR + a single zlib-compressed IDAT (filter-0 scanlines) + IEND.
6
+ */
7
+ type RawImage = {
8
+ readonly width: number;
9
+ readonly height: number;
10
+ readonly channels: number;
11
+ readonly data: Uint8Array;
12
+ };
13
+ declare const encodePng: (image: RawImage) => Uint8Array;
14
+ export { encodePng };
15
+ export type { RawImage };
@@ -4,6 +4,6 @@ type SheetCsv = {
4
4
  readonly name: string;
5
5
  readonly csv: string;
6
6
  };
7
- declare const readSheetsAsCsv: (bytes: Uint8Array) => Result<ReadonlyArray<SheetCsv>, GraphError>;
7
+ declare const readSheetsAsCsv: (bytes: Uint8Array) => Promise<Result<ReadonlyArray<SheetCsv>, GraphError>>;
8
8
  export { readSheetsAsCsv };
9
9
  export type { SheetCsv };
@@ -0,0 +1,35 @@
1
+ import type { AccessToken } from '../domain/access-token.js';
2
+ import type { Result } from '../domain/result.js';
3
+ import type { Logger } from '../use-cases/ports/logger.js';
4
+ type SystemBrowserAuthDeps = {
5
+ readonly logger: Logger;
6
+ readonly timeoutMs?: number;
7
+ readonly extensionTimeoutMs?: number;
8
+ };
9
+ type SystemBrowserTokenResult = {
10
+ readonly accessToken: AccessToken;
11
+ readonly refreshToken: string | null;
12
+ readonly elevatedAccessToken?: AccessToken | null;
13
+ readonly chatsvcaggAccessToken?: AccessToken | null;
14
+ readonly ic3AccessToken?: AccessToken | null;
15
+ readonly chatsvcaggRegion?: string;
16
+ };
17
+ type SystemBrowserAuthError = {
18
+ type: 'server_bind_failed';
19
+ message: string;
20
+ } | {
21
+ type: 'browser_open_failed';
22
+ message: string;
23
+ } | {
24
+ type: 'extension_timeout';
25
+ message: string;
26
+ } | {
27
+ type: 'invalid_token';
28
+ message: string;
29
+ } | {
30
+ type: 'cancelled';
31
+ message: string;
32
+ };
33
+ declare const authenticateViaSystemBrowser: (deps: SystemBrowserAuthDeps) => Promise<Result<SystemBrowserTokenResult, SystemBrowserAuthError>>;
34
+ export { authenticateViaSystemBrowser };
35
+ export type { SystemBrowserAuthDeps, SystemBrowserAuthError, SystemBrowserTokenResult };
@@ -0,0 +1,3 @@
1
+ import type { Logger } from '../use-cases/ports/logger.js';
2
+ import type { SystemBrowserAuthFn } from './auth.js';
3
+ export declare const defaultSystemBrowserAuth: (logger: Logger, skipSystemBrowser: boolean) => SystemBrowserAuthFn;
@@ -0,0 +1,31 @@
1
+ import type { Result } from '../domain/result.js';
2
+ import type { Logger } from '../use-cases/ports/logger.js';
3
+ type TokenCallbackPayload = {
4
+ readonly access_token: string;
5
+ readonly refresh_token?: string;
6
+ readonly elevated_access_token?: string;
7
+ readonly chatsvcagg_access_token?: string;
8
+ readonly ic3_access_token?: string;
9
+ readonly chatsvcagg_region?: string;
10
+ };
11
+ type TokenCallbackServer = {
12
+ readonly port: number;
13
+ readonly start: () => Promise<Result<TokenCallbackPayload, TokenCallbackError>>;
14
+ readonly stop: () => Promise<void>;
15
+ };
16
+ type TokenCallbackError = {
17
+ type: 'bind_failed';
18
+ message: string;
19
+ } | {
20
+ type: 'timeout';
21
+ message: string;
22
+ } | {
23
+ type: 'invalid_payload';
24
+ message: string;
25
+ } | {
26
+ type: 'server_closed';
27
+ message: string;
28
+ };
29
+ declare const createTokenCallbackServer: (logger: Logger, timeoutMs?: number) => TokenCallbackServer;
30
+ export { createTokenCallbackServer };
31
+ export type { TokenCallbackError, TokenCallbackPayload, TokenCallbackServer };
@@ -0,0 +1,32 @@
1
+ import type { Result } from '../domain/result.js';
2
+ import type { GraphError } from './graph-client.js';
3
+ /**
4
+ * Reads every file entry out of an arbitrary `.zip` archive as raw bytes,
5
+ * sorted by path, directories excluded. Generic counterpart to
6
+ * `ooxml-media-extractor` (which matches only media paths) and
7
+ * `ooxml-zip-adapter` (which decodes entries as UTF-8 strings) — used by the
8
+ * zip-conversion commands to fan each contained file out to the right
9
+ * converter. try/catch is permitted here (src/infra/**, atelier rule 17): a
10
+ * malformed-zip throw becomes a Result.err.
11
+ */
12
+ type ZipEntry = {
13
+ readonly path: string;
14
+ readonly bytes: Uint8Array;
15
+ };
16
+ /**
17
+ * Decode a legacy zip entry name — the raw filename bytes of an entry whose
18
+ * UTF-8 flag (general-purpose bit 11) is NOT set and which carries no Info-ZIP
19
+ * Unicode-path extra field. JSZip's default decoder assumes UTF-8 and mojibakes
20
+ * these (the `unzip -O GBK` case): Chinese vendor archives written by WinRAR /
21
+ * Windows Explorer store names in GBK. Try strict UTF-8 first (some archivers
22
+ * emit UTF-8 bytes without setting the flag), then fall back to GB18030 — a GBK
23
+ * superset whose 0x00–0x7F bytes are plain ASCII, so legacy ASCII/CP437 names
24
+ * survive unchanged while CJK byte sequences decode correctly.
25
+ *
26
+ * Only reached for non-UTF-8-flagged names (JSZip handles UTF-8 names itself),
27
+ * so a UTF-8 archive's path-handling is unchanged.
28
+ */
29
+ declare const decodeZipFileName: (bytes: Uint8Array) => string;
30
+ declare const openZipEntries: (bytes: Uint8Array) => Promise<Result<ReadonlyArray<ZipEntry>, GraphError>>;
31
+ export { decodeZipFileName, openZipEntries };
32
+ export type { ZipEntry };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Translate Graph / substrate / CLI / validation errors into actionable hints.
3
3
  *
4
- * Audit Hervé-session §2: bare `error: ErrorInvalidIdMalformed: Id is
4
+ * Audit Jane-session §2: bare `error: ErrorInvalidIdMalformed: Id is
5
5
  * malformed.` had no remedy for the LLM — it had to guess where the bad ID
6
6
  * came from. This module is the centralised "what should I do about this"
7
7
  * lookup: pattern-match the error code (or, as a fallback, a substring of
@@ -12,7 +12,7 @@
12
12
  * `--output text` (as `hint:` / `source:` lines under the existing `error:`
13
13
  * line).
14
14
  *
15
- * Audit Hervé-session §2 follow-up: the four error-envelope variants are
15
+ * Audit Jane-session §2 follow-up: the four error-envelope variants are
16
16
  * - `graph` — public Microsoft Graph API at /v1.0/
17
17
  * - `substrate` — Microsoft-internal chat substrates (chatsvcagg / IC3).
18
18
  * Tagged at the infra layer with `substrateHttp{N}_{name}`.
@@ -2,6 +2,6 @@ import type { Logger } from '../use-cases/ports/logger.js';
2
2
  import type { ErrorSource } from './error-hints.js';
3
3
  type OutputFormat = 'text' | 'json';
4
4
  declare const render: (data: unknown, logger: Logger, format: OutputFormat) => void;
5
- declare const renderError: (message: string, format: OutputFormat, errorCode?: string, explicitSource?: ErrorSource) => void;
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 };
@@ -7,7 +7,7 @@ import { type ODataKey } from './odata-query.js';
7
7
  * passthroughs at all). `defaultSelect`, when set and the user did NOT pass
8
8
  * `--select`, is injected into the OData query string so default invocations
9
9
  * return a slim projection instead of a 50 KB resource. User-supplied
10
- * `--select` always wins. Audit Hervé-session §A: pairs the `list-mail-attachments`
10
+ * `--select` always wins. Audit Jane-session §A: pairs the `list-mail-attachments`
11
11
  * pattern with the builder layer so the 6 heaviest endpoints stop returning
12
12
  * the full Graph resource by default.
13
13
  */
@@ -80,6 +80,14 @@ type PaginationStrategy =
80
80
  type CommandMeta = {
81
81
  readonly summary: string;
82
82
  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>;
83
91
  readonly graphMethod: CommandHttpMethod;
84
92
  readonly graphPathTemplate: string;
85
93
  readonly graphDocsUrl: string;
@@ -107,6 +115,14 @@ type CommandMeta = {
107
115
  * commands will time out.
108
116
  */
109
117
  readonly needsElevatedToken?: true;
118
+ /**
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, QA-011).
124
+ */
125
+ readonly needsSubstrateToken?: true;
110
126
  /**
111
127
  * `true` if the command returns inlined bytes (`{contentType, size, base64}`
112
128
  * or `{contentType, size, text}`) and is therefore a valid target for the
@@ -115,6 +131,24 @@ type CommandMeta = {
115
131
  * as a string literal. Audit round-8 Wave E2.
116
132
  */
117
133
  readonly producesBytes?: true;
134
+ /**
135
+ * `true` if the command returns a `media` array (`{ count, media: [{ path,
136
+ * contentType, sizeBytes, base64 }] }`) and is therefore a valid target for
137
+ * the global `--output-dir` flag, which writes each image to a directory.
138
+ * Parallel to `producesBytes` (single-file `--output-path`).
139
+ */
140
+ readonly producesMedia?: true;
141
+ /**
142
+ * `true` if the command performs a write (mutation) against Microsoft Graph
143
+ * rather than a read/search. Today only the two mail-draft commands
144
+ * (`create-mail-draft` = POST, `update-mail-draft` = PATCH) — both produce an
145
+ * UNSENT draft and cannot send. The CLI composition derives its read-only
146
+ * top-level `--help` narrative from this flag (mutating commands vs search
147
+ * POSTs) instead of hardcoding command names, and the manifest/`help-json`
148
+ * surface it so an LLM can tell at a glance which commands change state.
149
+ * Audit 2026-06-15 (F-03).
150
+ */
151
+ readonly mutates?: true;
118
152
  /**
119
153
  * Stability tier of the command. Omitted from manifest entries when the
120
154
  * command is `'stable'` (the implicit default), surfaced only on
@@ -123,7 +157,7 @@ type CommandMeta = {
123
157
  * substrate (chatsvcagg / IC3) that is not in the public Graph API and can
124
158
  * break on a Teams web-client update — the docstring "Best-effort, may break
125
159
  * on Microsoft client updates" warnings now have a structured pair.
126
- * Audit Hervé-session §6.
160
+ * Audit Jane-session §6.
127
161
  */
128
162
  readonly stability?: 'experimental';
129
163
  };
@@ -131,5 +165,12 @@ type Command = {
131
165
  readonly schema: CommandSchema;
132
166
  readonly execute: CommandExecute;
133
167
  readonly meta: CommandMeta;
168
+ /**
169
+ * 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
171
+ * composition-selected FileSystem; `execute` stays as the registry-typed
172
+ * fallback that redirects library consumers to this variant.
173
+ */
174
+ readonly executeLocal?: (fs: import('../ports/filesystem.js').FileSystem, params: Record<string, string>) => Promise<Result<unknown, import('../../infra/graph-client.js').GraphError>>;
134
175
  };
135
176
  export type { ArgumentHint, Command, CommandCategory, CommandExecute, CommandHttpMethod, CommandMeta, CommandOptionAlias, CommandOptionMeta, CommandPositionalArgumentMeta, CommandSchema, PaginationStrategy, };
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ import type { Result } from '../../domain/result.js';
3
+ import type { GraphClient, GraphError } from '../../infra/graph-client.js';
4
+ import type { CommandMeta } from './command-types.js';
5
+ declare const schema: z.ZodObject<{
6
+ eventId: z.ZodString;
7
+ attachmentId: z.ZodString;
8
+ includeMetadata: z.ZodOptional<z.ZodEnum<{
9
+ true: "true";
10
+ false: "false";
11
+ }>>;
12
+ }, z.core.$strip>;
13
+ declare const execute: (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, GraphError>>;
14
+ declare const meta: CommandMeta;
15
+ export { execute, meta, schema };
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+ import type { Result } from '../../domain/result.js';
3
+ import type { GraphClient, GraphError } from '../../infra/graph-client.js';
4
+ import type { CommandMeta } from './command-types.js';
5
+ declare const schema: z.ZodObject<{
6
+ eventId: z.ZodString;
7
+ attachmentId: z.ZodString;
8
+ }, z.core.$strip>;
9
+ declare const execute: (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, GraphError>>;
10
+ declare const meta: CommandMeta;
11
+ export { execute, meta, schema };
@@ -0,0 +1,26 @@
1
+ import { z } from 'zod';
2
+ import type { Result } from '../../domain/result.js';
3
+ import type { GraphClient, GraphError } from '../../infra/graph-client.js';
4
+ import type { CommandMeta } from './command-types.js';
5
+ /**
6
+ * Unzips a `.zip` from a OneDrive / SharePoint item and runs each contained
7
+ * file through the same local conversion pipelines the `*-as-markdown` commands
8
+ * use — so an agent reading "the project handover archive" doesn't have to shell
9
+ * out to `unzip` and convert each file separately. Office files (docx/xlsx/pptx/
10
+ * odt/ods/odp + variants) become markdown; legacy OLE .xls / .doc are extracted
11
+ * too (.ppt is noted, no pure-JS path); plain-text entries are decoded inline;
12
+ * PDFs have their text layer extracted; everything else (images, binaries, nested
13
+ * archives — plus scanned/image-only PDFs and legacy .ppt) is listed with a note
14
+ * rather than failing the whole archive.
15
+ */
16
+ declare const schema: z.ZodObject<{
17
+ driveId: z.ZodString;
18
+ itemId: z.ZodString;
19
+ includeMetadata: z.ZodOptional<z.ZodEnum<{
20
+ true: "true";
21
+ false: "false";
22
+ }>>;
23
+ }, z.core.$strip>;
24
+ declare const execute: (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, GraphError>>;
25
+ declare const meta: CommandMeta;
26
+ export { execute, meta, schema };