ask-marcel-office-cli 1.5.2 → 2.1.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 (38) hide show
  1. package/CHANGELOG.md +133 -3
  2. package/README.md +23 -59
  3. package/dist/cli.js +1757 -1766
  4. package/dist/commands.json +437 -275
  5. package/dist/composition/build-deps.d.ts +7 -8
  6. package/dist/composition/cli.d.ts +5 -4
  7. package/dist/domain/iso-datetime.d.ts +1 -1
  8. package/dist/index.js +1606 -1653
  9. package/dist/infra/auth.d.ts +37 -21
  10. package/dist/infra/browser-auth.d.ts +13 -12
  11. package/dist/infra/graph-client.d.ts +28 -2
  12. package/dist/presenter/error-hints.d.ts +9 -9
  13. package/dist/use-cases/commands/build-command.d.ts +1 -1
  14. package/dist/use-cases/commands/command-types.d.ts +5 -5
  15. package/dist/use-cases/commands/convert-local-file.d.ts +4 -0
  16. package/dist/use-cases/commands/create-forward-draft.d.ts +12 -0
  17. package/dist/use-cases/commands/create-reply-draft.d.ts +10 -0
  18. package/dist/use-cases/commands/docs.d.ts +1 -1
  19. package/dist/use-cases/commands/get-schedule.d.ts +11 -0
  20. package/dist/use-cases/commands/graph-scopes.d.ts +1 -1
  21. package/dist/use-cases/commands/image-extraction.d.ts +2 -1
  22. package/dist/use-cases/commands/iso-datetime-schema.d.ts +1 -1
  23. package/dist/use-cases/commands/login-status.d.ts +31 -0
  24. package/dist/use-cases/commands/login.d.ts +3 -1
  25. package/dist/use-cases/commands/markdown-pipeline.d.ts +5 -5
  26. package/dist/use-cases/commands/option-descriptions.d.ts +1 -1
  27. package/dist/use-cases/commands/output-path.d.ts +4 -4
  28. package/dist/use-cases/commands/parse-recipients.d.ts +14 -0
  29. package/dist/use-cases/commands/zip-archive-to-markdown.d.ts +4 -2
  30. package/dist/use-cases/ports/filesystem.d.ts +1 -1
  31. package/docs/COMMANDS.md +16 -13
  32. package/docs/USAGE.md +23 -23
  33. package/package.json +3 -4
  34. package/dist/composition/env.d.ts +0 -2
  35. package/dist/infra/system-browser-auth.d.ts +0 -35
  36. package/dist/infra/system-browser-loader.d.ts +0 -3
  37. package/dist/infra/token-callback-server.d.ts +0 -31
  38. package/docs/commands.json +0 -7676
@@ -6,6 +6,7 @@ import type { BrowserAuth, ElevatedFailureReason } from './browser-auth.js';
6
6
  type AuthError = {
7
7
  type: 'auth_failed';
8
8
  message: string;
9
+ code?: string;
9
10
  } | {
10
11
  type: 'auth_cancelled';
11
12
  };
@@ -15,7 +16,7 @@ type AuthError = {
15
16
  * `{ elevated: 'captured' | 'failed', elevatedReason?: ... }` field on
16
17
  * the login response so an LLM consumer can predict whether the
17
18
  * elevated-dependent commands (chat metadata, historical-version
18
- * downloads) will work without invoking them. Login-fix round-1 Wave D.
19
+ * downloads) will work without invoking them.
19
20
  */
20
21
  type ElevatedOutcome = {
21
22
  captured: true;
@@ -24,7 +25,9 @@ type ElevatedOutcome = {
24
25
  reason: ElevatedFailureReason | 'unknown_error';
25
26
  };
26
27
  type AuthManager = {
27
- getAccessToken: () => Promise<Result<AccessToken, AuthError>>;
28
+ getAccessToken: (options?: {
29
+ force?: boolean;
30
+ }) => Promise<Result<AccessToken, AuthError>>;
28
31
  /**
29
32
  * Returns a Graph token issued for an app on Microsoft's ODSP
30
33
  * `logicalPermissions` allow-list. Falls through cache → re-capture
@@ -60,7 +63,6 @@ type AuthManager = {
60
63
  * Inspect the elevated-capture outcome from the most recent
61
64
  * `acquireViaBrowser` invocation. Returns null if no browser-acquired
62
65
  * session has happened in this process (cache hit / refresh-only).
63
- * Login-fix round-1 Wave D.
64
66
  */
65
67
  getLastElevatedOutcome: () => ElevatedOutcome | null;
66
68
  /**
@@ -69,22 +71,39 @@ type AuthManager = {
69
71
  * as `getLastElevatedOutcome`.
70
72
  */
71
73
  getLastChatsvcaggOutcome: () => ElevatedOutcome | null;
74
+ /**
75
+ * Decode-only preflight for whether the *persisted* elevated
76
+ * (M365ChatClient) token is present and still usable — the token the
77
+ * historical-version download / convert commands need. Unlike
78
+ * `getLastElevatedOutcome` (per-process, null in a fresh CLI invocation),
79
+ * this reads the on-disk cache, so a separate `deep-scan` run can tell
80
+ * "elevated available" from "run `login` first" without provoking a 403.
81
+ * Optional: only the real manager implements it; a minimal fake omits it
82
+ * and callers treat that as unavailable. Never captures or refreshes.
83
+ */
84
+ getCachedElevatedInfo?: () => Promise<{
85
+ available: boolean;
86
+ expiresInSeconds: number | undefined;
87
+ }>;
88
+ /**
89
+ * Same decode-only preflight as `getCachedElevatedInfo`, for the chatsvcagg /
90
+ * ic3 Teams-chat substrate tokens. `login`'s four-token status and
91
+ * `scopes-check` read these; a minimal fake omits them and callers treat that
92
+ * as unavailable.
93
+ */
94
+ getCachedChatsvcaggInfo?: () => Promise<{
95
+ available: boolean;
96
+ expiresInSeconds: number | undefined;
97
+ }>;
98
+ getCachedIc3Info?: () => Promise<{
99
+ available: boolean;
100
+ expiresInSeconds: number | undefined;
101
+ }>;
72
102
  };
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, recaptureSecondaryViaBrowser?: boolean) => AuthManager;
103
+ declare const createAuthManagerFromApi: (browserAuth: BrowserAuth, cachePath: string, browserProfileDir: string, logger: Logger, fs: FileSystem, recaptureSecondaryViaBrowser?: boolean) => AuthManager;
85
104
  /**
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
105
+ * Probe the token cache for a fresh access token. Handed to the browser
106
+ * capture so its poll loop can short-circuit the multi-minute dance
88
107
  * when a concurrent process refreshes first (AAD rotates SPA refresh tokens,
89
108
  * so the loser of the race cannot refresh and falls into the browser leg).
90
109
  * Exported for the composition test; pure read, never writes.
@@ -96,10 +115,7 @@ declare const createAuthManager: (deps: {
96
115
  logger: Logger;
97
116
  fs?: FileSystem;
98
117
  browserProfileDir?: string;
99
- systemBrowserAuth?: SystemBrowserAuthFn;
100
- usePlaywrightFallback?: boolean;
101
- skipSystemBrowser?: boolean;
102
118
  recaptureSecondaryViaBrowser?: boolean;
103
119
  }) => AuthManager;
104
120
  export { createAuthManager, createAuthManagerFromApi, createFreshCachedTokenProbe, stderrProgress };
105
- export type { AuthError, AuthManager, ElevatedOutcome, SystemBrowserAuthFn };
121
+ export type { AuthError, AuthManager, ElevatedOutcome };
@@ -13,7 +13,7 @@ type BrowserTokenResult = {
13
13
  * one retry after wiping the profile; `navigation_failed` is a network
14
14
  * issue, not worth retrying).
15
15
  *
16
- * Login-fix round-1: was previously `AccessToken | null`, which conflated
16
+ * was previously `AccessToken | null`, which conflated
17
17
  * "browser launch hung", "navigation broke", and "silent-SSO polling
18
18
  * timed out" into a single null and made the error message inaccurate.
19
19
  */
@@ -69,7 +69,7 @@ type Ic3TokenResult = {
69
69
  /**
70
70
  * Combined outcome of capturing all four tokens (Teams basic / M365
71
71
  * elevated / chatsvcagg substrate / IC3 substrate) inside one browser
72
- * session. Login-fix round-2 introduced this for the basic+elevated
72
+ * session. introduced this for the basic+elevated
73
73
  * pair; chatsvcagg was added next; IC3 is the most recent leg, capturing
74
74
  * the bearer Teams web uses for unbounded chat-history reads.
75
75
  *
@@ -84,7 +84,7 @@ type BothTokensResult = {
84
84
  readonly ic3: Ic3TokenResult;
85
85
  /**
86
86
  * Set when the Teams poll short-circuited because `freshCachedToken`
87
- * found a token written by a concurrent process (QA-010). The caller
87
+ * found a token written by a concurrent process. The caller
88
88
  * must NOT persist this result — the cache is already the source of
89
89
  * truth, and `refreshToken` is null here (persisting would clobber the
90
90
  * winner's rotated refresh token).
@@ -92,7 +92,6 @@ type BothTokensResult = {
92
92
  readonly fromCache?: true;
93
93
  };
94
94
  type BrowserAuth = {
95
- acquireToken: (scopes: string[], startUrl: string) => Promise<BrowserTokenResult | null>;
96
95
  /**
97
96
  * Capture an "elevated" Graph access token by navigating to a
98
97
  * different Microsoft web app whose first-party app identity is on
@@ -136,11 +135,11 @@ type BrowserAuth = {
136
135
  */
137
136
  acquireIc3Token: () => Promise<Ic3TokenResult>;
138
137
  /**
139
- * Login-fix round-2: capture BOTH tokens inside ONE browser session.
138
+ * capture BOTH tokens inside ONE browser session.
140
139
  * After the Teams response listener intercepts the Teams token,
141
140
  * navigate the SAME page to the elevated URL and harvest the
142
141
  * M365ChatClient bearer from outgoing request headers. Cookies are
143
- * live in memory, so federated SSO chains (e.g. Okta-fronted
142
+ * live in memory, so federated SSO chains (e.g. third-party-IdP-fronted
144
143
  * tenants) work without a second visible sign-in.
145
144
  *
146
145
  * Returns `teams: null` if no Teams token came back within the full
@@ -149,7 +148,9 @@ type BrowserAuth = {
149
148
  * failed inside the same session — caller decides whether to surface
150
149
  * the partial success.
151
150
  */
152
- acquireBothTokens: (scopes: string[], teamsUrl: string) => Promise<BothTokensResult>;
151
+ acquireBothTokens: (teamsUrl: string, options?: {
152
+ skipCacheProbe?: boolean;
153
+ }) => Promise<BothTokensResult>;
153
154
  close: () => Promise<void>;
154
155
  };
155
156
  type ResponseLike = {
@@ -199,7 +200,7 @@ type BrowserAuthConfig = {
199
200
  readonly fs: FileSystem;
200
201
  readonly trace?: TraceFn;
201
202
  /**
202
- * QA-010 (login hang): probe for a fresh token that landed in the cache
203
+ * (login hang): probe for a fresh token that landed in the cache
203
204
  * WHILE the browser capture is polling. AAD SPA refresh tokens rotate, so
204
205
  * when two processes race, the loser's refresh fails and it falls into the
205
206
  * full multi-minute browser dance — while the winner's fresh token sits in
@@ -210,7 +211,7 @@ type BrowserAuthConfig = {
210
211
  /**
211
212
  * User-visible progress sink (stderr in production). The browser capture
212
213
  * 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
+ * one-liners are what separates "waiting on the user" from "hung".
214
215
  */
215
216
  readonly onProgress?: (line: string) => void;
216
217
  readonly profileDir?: string;
@@ -222,19 +223,19 @@ type BrowserAuthConfig = {
222
223
  /**
223
224
  * Deadline for the SILENT elevated-token recapture flow (no user
224
225
  * interaction expected — persistent profile cookies do the SSO).
225
- * Defaults to 20s. The audit (v1.0.0 §1.1) flagged that reusing the
226
+ * Defaults to 20s. The audit () flagged that reusing the
226
227
  * 5-minute interactive `pollDeadlineMs` for this silent path made
227
228
  * `list-chats` etc. hang for minutes when cookies were stale, blowing
228
229
  * the LLM tool-call window. With a tight cap, the flow either yields
229
230
  * a token quickly or fails with `auth_failed: elevated token capture
230
- * timed out — run `ask-marcel login` to refresh.`
231
+ * timed out — run `ask-marcel-office login` to refresh.`
231
232
  */
232
233
  readonly elevatedRecaptureTimeoutMs?: number;
233
234
  /**
234
235
  * Hard deadline on `launchPersistentContext` + `newPage` for the
235
236
  * elevated capture path. Defaults to 15s. Distinct from
236
237
  * `elevatedRecaptureTimeoutMs` so the error message can name which
237
- * step hung — launch vs polling. Audit login-fix round-1: previously
238
+ * step hung — launch vs polling. Audit previously
238
239
  * unguarded, so a hung Playwright launch (corrupt persistent profile
239
240
  * with stale `Singleton*` locks, or a slow browser binary) would
240
241
  * block the whole command indefinitely.
@@ -109,11 +109,37 @@ type TokenInfo = {
109
109
  /**
110
110
  * Seconds remaining until the cached token's `exp` claim — derived from
111
111
  * `expiresAt - now`. Negative when the token has already expired. Absent
112
- * when the JWT did not carry an `exp` claim. Audit Jane-session §4: lets
113
- * an LLM decide pre-emptively to run `ask-marcel login` (re-auth typically
112
+ * when the JWT did not carry an `exp` claim. lets
113
+ * an LLM decide pre-emptively to run `ask-marcel-office login` (re-auth typically
114
114
  * worth doing under ~5 minutes) without parsing the ISO string itself.
115
115
  */
116
116
  readonly expiresInSeconds: number | undefined;
117
+ /**
118
+ * Whether the *persisted* elevated (M365ChatClient) token — the one the
119
+ * historical-version download / convert commands need — is present and still
120
+ * usable, plus its raw seconds-to-expiry (`undefined` when absent). `available`
121
+ * is `false` when the auth manager cannot introspect it. Lets `deep-scan`
122
+ * preflight elevated access in a fresh process instead of turning every
123
+ * version download into a `403`.
124
+ */
125
+ readonly elevated: {
126
+ readonly available: boolean;
127
+ readonly expiresInSeconds: number | undefined;
128
+ };
129
+ /**
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.
134
+ */
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
+ };
117
143
  };
118
144
  type FetchFn = (url: string, init?: RequestInit) => Promise<Response>;
119
145
  declare const createGraphClient: (auth: AuthManager, fetchFn?: FetchFn) => GraphClient;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Translate Graph / substrate / CLI / validation errors into actionable hints.
3
3
  *
4
- * Audit Jane-session §2: bare `error: ErrorInvalidIdMalformed: Id is
4
+ * 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,14 +12,14 @@
12
12
  * `--output text` (as `hint:` / `source:` lines under the existing `error:`
13
13
  * line).
14
14
  *
15
- * Audit Jane-session §2 follow-up: the four error-envelope variants are
16
- * - `graph` — public Microsoft Graph API at /v1.0/
17
- * - `substrate` — Microsoft-internal chat substrates (chatsvcagg / IC3).
18
- * Tagged at the infra layer with `substrateHttp{N}_{name}`.
19
- * - `cli` — CLI itself (Commander parser, CLI rewrites of Graph
20
- * errors via `cli_rewrite_*` and `cli_reject_*` codes)
21
- * - `validation` — Zod schema validation from use-cases (no `code` — pure
22
- * message-pattern fallback)
15
+ * the four error-envelope variants are
16
+ * - `graph` — public Microsoft Graph API at /v1.0/
17
+ * - `substrate` — Microsoft-internal chat substrates (chatsvcagg / IC3).
18
+ * Tagged at the infra layer with `substrateHttp{N}_{name}`.
19
+ * - `cli` — CLI itself (Commander parser, CLI rewrites of Graph
20
+ * errors via `cli_rewrite_*` and `cli_reject_*` codes)
21
+ * - `validation` — Zod schema validation from use-cases (no `code` — pure
22
+ * message-pattern fallback)
23
23
  *
24
24
  * Rule precedence: specific code matchers run FIRST, then message-pattern
25
25
  * fallbacks. The generic-validation rule sits LAST so it never overrides a
@@ -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 Jane-session §A: pairs the `list-mail-attachments`
10
+ * `--select` always wins. 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
  */
@@ -52,7 +52,7 @@ type CommandOptionMeta = {
52
52
  };
53
53
  /**
54
54
  * A positional argument (i.e. NOT a `--flag`). Used today only for the
55
- * `docs` lifecycle command (`ask-marcel docs <command>`) but kept as its
55
+ * `docs` lifecycle command (`ask-marcel-office docs <command>`) but kept as its
56
56
  * own field so the manifest never claims a positional is a flag. An LLM
57
57
  * consumer reading `help-json` can branch on the presence of
58
58
  * `positionalArguments` to know to skip the `--` prefix.
@@ -100,7 +100,7 @@ type CommandMeta = {
100
100
  readonly paginationStrategy?: PaginationStrategy;
101
101
  /**
102
102
  * Graph permission scopes the endpoint requires. The basic Teams web-client
103
- * token grants ~30 scopes (run `ask-marcel scopes-check` to see). Commands
103
+ * token grants ~30 scopes (run `ask-marcel-office scopes-check` to see). Commands
104
104
  * with unmet scopes return `403 Forbidden: Missing scope` at the wire. Use
105
105
  * this for pre-flight checks rather than failing on-the-wire. Optional —
106
106
  * populated only on commands where the audit confirmed a scope-failure
@@ -120,7 +120,7 @@ type CommandMeta = {
120
120
  * captured at login from `teams.microsoft.com`) — the Teams chat-content
121
121
  * commands. Like `needsElevatedToken`, an LLM should check this before
122
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).
123
+ * times out on these (the non-interactive silent-SSO limitation, ).
124
124
  */
125
125
  readonly needsSubstrateToken?: true;
126
126
  /**
@@ -128,7 +128,7 @@ type CommandMeta = {
128
128
  * or `{contentType, size, text}`) and is therefore a valid target for the
129
129
  * global `--output-path` flag. Used by the CLI composition to derive the
130
130
  * rejection-message whitelist from the manifest rather than hand-keeping it
131
- * as a string literal. Audit round-8 Wave E2.
131
+ * as a string literal. Audit .
132
132
  */
133
133
  readonly producesBytes?: true;
134
134
  /**
@@ -157,7 +157,7 @@ type CommandMeta = {
157
157
  * substrate (chatsvcagg / IC3) that is not in the public Graph API and can
158
158
  * break on a Teams web-client update — the docstring "Best-effort, may break
159
159
  * on Microsoft client updates" warnings now have a structured pair.
160
- * Audit Jane-session §6.
160
+ *
161
161
  */
162
162
  readonly stability?: 'experimental';
163
163
  };
@@ -29,6 +29,10 @@ declare const schema: z.ZodObject<{
29
29
  true: "true";
30
30
  false: "false";
31
31
  }>>;
32
+ includeImages: z.ZodOptional<z.ZodEnum<{
33
+ true: "true";
34
+ false: "false";
35
+ }>>;
32
36
  maxCells: z.ZodOptional<z.ZodString>;
33
37
  }, z.core.$strip>;
34
38
  declare const executeLocal: (fs: FileSystem, params: Record<string, string>) => Promise<Result<unknown, GraphError>>;
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ forwardMessageId: z.ZodString;
5
+ toRecipients: z.ZodString;
6
+ ccRecipients: z.ZodOptional<z.ZodString>;
7
+ bodyContent: z.ZodString;
8
+ subject: z.ZodOptional<z.ZodString>;
9
+ }, z.core.$strip>;
10
+ declare const execute: Command['execute'];
11
+ declare const meta: CommandMeta;
12
+ export { execute, meta, schema };
@@ -0,0 +1,10 @@
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ replyToMessageId: z.ZodString;
5
+ bodyContent: z.ZodString;
6
+ subject: z.ZodOptional<z.ZodString>;
7
+ }, z.core.$strip>;
8
+ declare const execute: Command['execute'];
9
+ declare const meta: CommandMeta;
10
+ export { execute, meta, schema };
@@ -13,7 +13,7 @@ export type DocsError = {
13
13
  * `scopesRequired` — everything the LLM only needs once it's already decided
14
14
  * to invoke. `stability` is kept (it's a discovery-time concern: LLMs prefer
15
15
  * stable siblings when they exist, so they need to see the tag at discovery
16
- * time, not after a second full-manifest fetch). Audit Jane-session §B/§6.
16
+ * time, not after a second full-manifest fetch). /§6.
17
17
  */
18
18
  export type TerseManifestEntry = {
19
19
  readonly name: string;
@@ -0,0 +1,11 @@
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ schedules: z.ZodString;
5
+ startDateTime: z.ZodPipe<z.ZodString, z.ZodTransform<import("../../domain/iso-datetime.js").IsoDateTime, string>>;
6
+ endDateTime: z.ZodPipe<z.ZodString, z.ZodTransform<import("../../domain/iso-datetime.js").IsoDateTime, string>>;
7
+ availabilityViewInterval: z.ZodOptional<z.ZodString>;
8
+ }, z.core.$strip>;
9
+ declare const execute: Command['execute'];
10
+ declare const meta: CommandMeta;
11
+ export { execute, meta, schema };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Central Microsoft Graph permission scope map (audit round-8 Wave C).
2
+ * Central Microsoft Graph permission scope map.
3
3
  *
4
4
  * Source of truth: Microsoft Graph permissions reference
5
5
  * (https://learn.microsoft.com/en-us/graph/permissions-reference). Each
@@ -1,10 +1,11 @@
1
1
  import type { Result } from '../../domain/result.js';
2
2
  import type { GraphError } from '../../infra/graph-client.js';
3
+ import type { MediaEnvelope } from './media-files.js';
3
4
  /**
4
5
  * Shared by extract-drive-item-images and extract-mail-attachment-images: pick the
5
6
  * extractor for the file's extension and run it, or return a 415 whose tail
6
7
  * (`fetchHint`) names the caller's raw-bytes route. Both commands fetch / decode the
7
8
  * bytes first, then hand them here, so the dispatch + media envelope live in one place.
8
9
  */
9
- declare const extractImagesFromBytes: (bytes: Uint8Array, name: string, fetchHint: string) => Promise<Result<unknown, GraphError>>;
10
+ declare const extractImagesFromBytes: (bytes: Uint8Array, name: string, fetchHint: string) => Promise<Result<MediaEnvelope, GraphError>>;
10
11
  export { extractImagesFromBytes };
@@ -6,7 +6,7 @@ import { z } from 'zod';
6
6
  * compose this into their schemas in place of `z.string().min(1)`; the
7
7
  * URL-builder sees the already-resolved canonical ISO form.
8
8
  *
9
- * Audit Jane-session §C: removes the manual ISO arithmetic an LLM had to do
9
+ * removes the manual ISO arithmetic an LLM had to do
10
10
  * to ask "what changed this week" against `list-calendar-view`. Type
11
11
  * inference is left to Zod so the calendar commands' shape signatures stay
12
12
  * compatible with `buildListCommand`'s `ZodRawShape` constraint.
@@ -0,0 +1,31 @@
1
+ type RefreshRoute = 'automatic' | 'interactive';
2
+ type TokenTier = {
3
+ readonly available: boolean;
4
+ readonly expiresInSeconds: number | undefined;
5
+ };
6
+ type TokenView = {
7
+ available: boolean;
8
+ expiresInSeconds?: number;
9
+ refresh: RefreshRoute;
10
+ reason?: string;
11
+ };
12
+ type LoginStatus = {
13
+ status: 'authenticated';
14
+ tokens: {
15
+ basic: TokenView;
16
+ elevated: TokenView;
17
+ chatsvcagg: TokenView;
18
+ ic3: TokenView;
19
+ };
20
+ hint: string;
21
+ };
22
+ type LoginStatusInput = {
23
+ readonly basicExpiresInSeconds: number | undefined;
24
+ readonly elevated: TokenTier;
25
+ readonly chatsvcagg: TokenTier;
26
+ readonly ic3: TokenTier;
27
+ readonly elevatedFailureReason?: string;
28
+ };
29
+ declare const buildLoginStatus: (input: LoginStatusInput) => LoginStatus;
30
+ export { buildLoginStatus };
31
+ export type { LoginStatus, LoginStatusInput };
@@ -2,5 +2,7 @@ import { z } from 'zod';
2
2
  import type { Result } from '../../domain/result.js';
3
3
  import type { AuthManager } from '../../infra/auth.js';
4
4
  declare const schema: z.ZodObject<{}, z.core.$strict>;
5
- declare const execute: (auth: AuthManager) => Promise<Result<string, import("../../infra/auth.js").AuthError>>;
5
+ declare const execute: (auth: AuthManager, options?: {
6
+ force?: boolean;
7
+ }) => Promise<Result<string, import("../../infra/auth.js").AuthError>>;
6
8
  export { execute, schema };
@@ -5,11 +5,11 @@ import { type InlineAttachment } from './inline-image-embedder.js';
5
5
  * Orchestrate the four steps that turn a Graph `?format=html` content
6
6
  * call into a markdown envelope:
7
7
  *
8
- * 1. getBinary(contentPath)
9
- * 2. if Graph returned a 302 downloadUrl, follow via fetchUrl (host
10
- * allow-list enforced inside fetchUrl, Hardening #3)
11
- * 3. embedInlineImages over any cid: refs (Hardening #1: image/* only)
12
- * 4. htmlToMarkdown via turndown
8
+ * 1. getBinary(contentPath)
9
+ * 2. if Graph returned a 302 downloadUrl, follow via fetchUrl (host
10
+ * allow-list enforced inside fetchUrl, Hardening #3)
11
+ * 3. embedInlineImages over any cid: refs (Hardening #1: image/* only)
12
+ * 4. htmlToMarkdown via turndown
13
13
  *
14
14
  * Returns `{ contentType: 'text/markdown', size, text }` on success.
15
15
  */
@@ -2,4 +2,4 @@
2
2
  * `--drive-id` description for every generic OneDrive / SharePoint drive-item
3
3
  * command. Always points the caller at how to obtain a drive id.
4
4
  */
5
- export declare const DRIVE_ID_DESCRIPTION = "Microsoft Graph drive ID. Use `ask-marcel list-drives` for the personal OneDrive, or `ask-marcel list-sharepoint-site-drives --site-id <id>` for a SharePoint document library.";
5
+ export declare const DRIVE_ID_DESCRIPTION = "Microsoft Graph drive ID. Use `ask-marcel-office list-drives` for the personal OneDrive, or `ask-marcel-office list-sharepoint-site-drives --site-id <id>` for a SharePoint document library.";
@@ -11,10 +11,10 @@ import type { FileSystem } from '../ports/filesystem.js';
11
11
  * Two recognized inline shapes — both produced by `inlineBinary` and
12
12
  * `office-to-markdown`:
13
13
  *
14
- * 1. `{ contentType, size, base64 }` — written via `fs.writeBytes`,
15
- * with `base64` replaced by `savedTo`.
16
- * 2. `{ contentType, size, text }` — written via `fs.writeText`,
17
- * with `text` replaced by `savedTo`.
14
+ * 1. `{ contentType, size, base64 }` — written via `fs.writeBytes`,
15
+ * with `base64` replaced by `savedTo`.
16
+ * 2. `{ contentType, size, text }` — written via `fs.writeText`,
17
+ * with `text` replaced by `savedTo`.
18
18
  *
19
19
  * Anything else (plain JSON gets, error envelopes, etc.) returns
20
20
  * `no_inlined_bytes` so the CLI can surface a clear error rather than
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Parse a comma-separated recipient string into Microsoft Graph address
3
+ * objects. Shared by the mail-draft write commands (create-mail-draft,
4
+ * update-mail-draft, create-forward-draft) so the split/trim/empty-drop
5
+ * behaviour stays identical across all three. Whitespace around each address
6
+ * is trimmed and empty segments (leading, trailing, or doubled commas) are
7
+ * dropped, so `"a@x.com, , b@x.com,"` yields exactly two recipients.
8
+ */
9
+ declare const parseRecipients: (csv: string) => Array<{
10
+ emailAddress: {
11
+ address: string;
12
+ };
13
+ }>;
14
+ export { parseRecipients };
@@ -1,5 +1,6 @@
1
1
  import type { Result } from '../../domain/result.js';
2
2
  import type { GraphError } from '../../infra/graph-client.js';
3
+ import type { MediaEnvelope } from './media-files.js';
3
4
  /**
4
5
  * Shared "unzip + convert every contained file" core behind
5
6
  * `convert-drive-item-zip` (a OneDrive / SharePoint .zip),
@@ -8,7 +9,7 @@ import type { GraphError } from '../../infra/graph-client.js';
8
9
  * `bytesToMarkdown` dispatch the markdown commands use; an entry the dispatch
9
10
  * can't convert (image, binary, nested archive, scanned PDF) is LISTED with a
10
11
  * note instead of failing the whole archive. Notes use the container-neutral
11
- * NESTED_HINTS (QA-007): entries live INSIDE the zip, so caller-specific
12
+ * NESTED_HINTS: entries live INSIDE the zip, so caller-specific
12
13
  * sibling-command pointers (`extract-drive-item-images`, …) cannot reach them.
13
14
  */
14
15
  declare const MAX_ENTRIES = 100;
@@ -18,6 +19,7 @@ type FileResult = {
18
19
  readonly size?: number;
19
20
  readonly text?: string;
20
21
  readonly note?: string;
22
+ readonly images?: MediaEnvelope['media'];
21
23
  };
22
24
  type ZipArchiveResult = {
23
25
  readonly count: number;
@@ -25,6 +27,6 @@ type ZipArchiveResult = {
25
27
  readonly totalEntries?: number;
26
28
  readonly files: ReadonlyArray<FileResult>;
27
29
  };
28
- declare const convertZipArchive: (bytes: Uint8Array, includeMetadata: boolean) => Promise<Result<ZipArchiveResult, GraphError>>;
30
+ declare const convertZipArchive: (bytes: Uint8Array, includeMetadata: boolean, includeImages?: boolean) => Promise<Result<ZipArchiveResult, GraphError>>;
29
31
  export { convertZipArchive, MAX_ENTRIES };
30
32
  export type { FileResult, ZipArchiveResult };
@@ -19,7 +19,7 @@ export type FileSystem = {
19
19
  readonly writeBytes: (path: string, bytes: Uint8Array) => Promise<Result<void, FileSystemError>>;
20
20
  /**
21
21
  * Restrict a file's permission bits (e.g. 0o600 on the token cache so
22
- * other local users cannot read cached secrets — QA-001).
22
+ * other local users cannot read cached secrets — ).
23
23
  */
24
24
  readonly chmod: (path: string, mode: number) => Promise<Result<void, FileSystemError>>;
25
25
  readonly deleteIfExists: (path: string) => Promise<Result<void, FileSystemError>>;