ask-marcel-office-cli 1.0.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +119 -394
  2. package/dist/cli.js +4183 -1772
  3. package/dist/commands.json +309 -104
  4. package/dist/domain/iso-datetime.d.ts +30 -0
  5. package/dist/index.js +3780 -1712
  6. package/dist/infra/auth.d.ts +30 -0
  7. package/dist/infra/browser-auth.d.ts +75 -10
  8. package/dist/infra/graph-client.d.ts +38 -0
  9. package/dist/infra/network-error.d.ts +9 -0
  10. package/dist/presenter/error-hints.d.ts +41 -0
  11. package/dist/presenter/output.d.ts +2 -1
  12. package/dist/use-cases/commands/build-command.d.ts +30 -8
  13. package/dist/use-cases/commands/command-types.d.ts +12 -1
  14. package/dist/use-cases/commands/convert-mail-to-markdown.d.ts +4 -0
  15. package/dist/use-cases/commands/docs-render.d.ts +4 -2
  16. package/dist/use-cases/commands/docs.d.ts +44 -1
  17. package/dist/use-cases/commands/{download-drive-item-version-as-markdown.d.ts → download-drive-item-version.d.ts} +5 -0
  18. package/dist/use-cases/commands/fetch-raw-bytes.d.ts +1 -1
  19. package/dist/use-cases/commands/find-chats-with-user.d.ts +10 -0
  20. package/dist/use-cases/commands/get-chat.d.ts +1 -5
  21. package/dist/use-cases/commands/get-excel-used-range.d.ts +7 -2
  22. package/dist/use-cases/commands/get-teams-chat-message.d.ts +9 -0
  23. package/dist/use-cases/commands/iso-datetime-schema.d.ts +21 -0
  24. package/dist/use-cases/commands/link-shape.d.ts +13 -0
  25. package/dist/use-cases/commands/list-calendar-view-delta.d.ts +2 -2
  26. package/dist/use-cases/commands/list-teams-chat-history.d.ts +16 -0
  27. package/dist/use-cases/commands/list-teams-chat-messages.d.ts +8 -0
  28. package/dist/use-cases/commands/list-teams-chats-with-messages.d.ts +9 -0
  29. package/dist/use-cases/commands/resolve-calendar-link.d.ts +8 -0
  30. package/dist/use-cases/commands/resolve-drive-share-link.d.ts +8 -0
  31. package/dist/use-cases/commands/resolve-mail-link.d.ts +8 -0
  32. package/dist/use-cases/commands/resolve-teams-link.d.ts +8 -0
  33. package/dist/use-cases/commands/version-id.d.ts +7 -6
  34. package/docs/COMMANDS.md +243 -0
  35. package/docs/USAGE.md +249 -0
  36. package/docs/commands.json +309 -104
  37. package/package.json +3 -1
  38. package/dist/use-cases/commands/download-drive-item-version-as-pdf.d.ts +0 -12
  39. package/dist/use-cases/commands/download-drive-item-version-content.d.ts +0 -12
@@ -31,6 +31,30 @@ type AuthManager = {
31
31
  * via headless Playwright. Used by the 3 historical-version commands.
32
32
  */
33
33
  getElevatedAccessToken: () => Promise<Result<AccessToken, AuthError>>;
34
+ /**
35
+ * Returns a chatsvcagg-audience token (same Teams web client identity
36
+ * as `getAccessToken`, but issued for the chatsvcagg resource). Falls
37
+ * through cache → re-capture via headless Playwright. Used by the
38
+ * `list-teams-chats-with-messages` family of commands.
39
+ */
40
+ getChatsvcaggAccessToken: () => Promise<Result<AccessToken, AuthError>>;
41
+ /**
42
+ * Returns the regional segment used to construct chatsvcagg substrate
43
+ * URLs (`teams.microsoft.com/api/csa/<region>/api/...`). Captured at
44
+ * login from the first such URL the chatsvcagg bearer rides on. Falls
45
+ * back to `DEFAULT_CHATSVCAGG_REGION` ('emea') when the cache is
46
+ * either absent or pre-2026-05-migration. Synchronous on cache; calls
47
+ * `getChatsvcaggAccessToken()` first if no cache exists so a region is
48
+ * available immediately after login.
49
+ */
50
+ getChatsvcaggRegion: () => Promise<string>;
51
+ /**
52
+ * Returns an IC3-audience bearer (Teams web client identity, aud
53
+ * `https://ic3.teams.office.com`). Falls through cache → re-capture
54
+ * via headless Playwright. Used by `list-teams-chat-history` to walk
55
+ * paginated chat-message history beyond the 200-message chatsvcagg cap.
56
+ */
57
+ getIc3AccessToken: () => Promise<Result<AccessToken, AuthError>>;
34
58
  logout: () => Promise<Result<void, AuthError>>;
35
59
  /**
36
60
  * Inspect the elevated-capture outcome from the most recent
@@ -39,6 +63,12 @@ type AuthManager = {
39
63
  * Login-fix round-1 Wave D.
40
64
  */
41
65
  getLastElevatedOutcome: () => ElevatedOutcome | null;
66
+ /**
67
+ * Inspect the chatsvcagg-capture outcome from the most recent
68
+ * `acquireViaBrowser` invocation. Same shape and lifetime semantics
69
+ * as `getLastElevatedOutcome`.
70
+ */
71
+ getLastChatsvcaggOutcome: () => ElevatedOutcome | null;
42
72
  };
43
73
  declare const createAuthManagerFromApi: (browserAuth: BrowserAuth, cachePath: string, browserProfileDir: string, logger: Logger, fs: FileSystem) => AuthManager;
44
74
  declare const createAuthManager: (deps: {
@@ -26,19 +26,62 @@ type ElevatedTokenResult = {
26
26
  readonly reason: ElevatedFailureReason;
27
27
  };
28
28
  /**
29
- * Combined outcome of capturing BOTH the Teams web client token AND the
30
- * elevated M365ChatClient token inside one browser session. Login-fix
31
- * round-2 introduced this to fix the user-visible "second browser asks
32
- * me to log in again" symptom on federated tenants (LVMH / Okta): the
33
- * old flow opened two separate browser sessions, and silent SSO for the
34
- * elevated identity wouldn't pick up the cookies just freshly written
35
- * by the first session. Capturing both in one context means cookies are
36
- * live in memory — no disk-commit-vs-read race, no separate sign-in
37
- * prompt for the elevated identity.
29
+ * chatsvcagg capture result. Carries the same three failure modes as
30
+ * `ElevatedTokenResult` PLUS the parsed regional segment from the URL
31
+ * the bearer rode on (`emea` / `amer` / `apac` / etc.) — the new
32
+ * substrate host (`teams.microsoft.com/api/csa/<region>/api/...`) routes
33
+ * per region, so callers must persist this alongside the token to
34
+ * construct future URLs. See `gotcha_chatsvcagg_substrate_moved` in
35
+ * the project memory for the 2026-05 migration that made this
36
+ * necessary.
37
+ */
38
+ type ChatsvcaggTokenResult = {
39
+ readonly ok: true;
40
+ readonly token: AccessToken;
41
+ readonly region: string;
42
+ } | {
43
+ readonly ok: false;
44
+ readonly reason: ElevatedFailureReason;
45
+ };
46
+ /**
47
+ * IC3 capture result. The "next-gen" Teams chat-message substrate at
48
+ * `teams.microsoft.com/api/chatsvc/<region>/v1/users/ME/conversations/{id}/messages`
49
+ * — the path Teams web actually uses for chat scrollback (the existing
50
+ * chatsvcagg substrate at `/api/csa/<region>/api/v1/chats/{id}/messages`
51
+ * is the chat-list aggregator and caps at the 200 most recent messages
52
+ * with no working pagination cursor). The IC3 path supports `syncState`
53
+ * + `startTime` pagination, unlocking arbitrary-depth chat history.
54
+ *
55
+ * Bearer audience: `https://ic3.teams.office.com`. Standard `Bearer`
56
+ * authorization (NOT the purple-teams-documented `skype_token X`
57
+ * header — those forks predate Teams' move to OAuth bearer on this
58
+ * surface). Empirically discovered 2026-05-21 by widening the
59
+ * Playwright capture listener.
60
+ */
61
+ type Ic3TokenResult = {
62
+ readonly ok: true;
63
+ readonly token: AccessToken;
64
+ readonly region: string;
65
+ } | {
66
+ readonly ok: false;
67
+ readonly reason: ElevatedFailureReason;
68
+ };
69
+ /**
70
+ * Combined outcome of capturing all four tokens (Teams basic / M365
71
+ * elevated / chatsvcagg substrate / IC3 substrate) inside one browser
72
+ * session. Login-fix round-2 introduced this for the basic+elevated
73
+ * pair; chatsvcagg was added next; IC3 is the most recent leg, capturing
74
+ * the bearer Teams web uses for unbounded chat-history reads.
75
+ *
76
+ * All four legs ride on the SAME `teams.microsoft.com` session, so
77
+ * adding capture legs is essentially free — MSAL multiplexes the
78
+ * audience-scoped bearers via silent acquisition.
38
79
  */
39
80
  type BothTokensResult = {
40
81
  readonly teams: BrowserTokenResult | null;
41
82
  readonly elevated: ElevatedTokenResult;
83
+ readonly chatsvcagg: ChatsvcaggTokenResult;
84
+ readonly ic3: Ic3TokenResult;
42
85
  };
43
86
  type BrowserAuth = {
44
87
  acquireToken: (scopes: string[], startUrl: string) => Promise<BrowserTokenResult | null>;
@@ -62,6 +105,28 @@ type BrowserAuth = {
62
105
  * `acquireBothTokens` instead so the user sees only one browser.
63
106
  */
64
107
  acquireElevatedToken: () => Promise<ElevatedTokenResult>;
108
+ /**
109
+ * Capture a chatsvcagg-audience bearer (Teams basic identity, but the
110
+ * `chatsvcagg.teams.microsoft.com` resource instead of Graph) by
111
+ * navigating headless to `teams.microsoft.com/v2/`. The persistent
112
+ * profile's SSO cookies do the auth silently. Used by
113
+ * `getChatsvcaggAccessToken()` when the cached chatsvcagg token has
114
+ * expired and the basic Teams session is still warm.
115
+ *
116
+ * Same failure-mode shape as `acquireElevatedToken` — launch hang,
117
+ * navigation failure, silent SSO timeout — so the auth-manager can
118
+ * reuse the same auto-heal logic.
119
+ */
120
+ acquireChatsvcaggToken: () => Promise<ChatsvcaggTokenResult>;
121
+ /**
122
+ * Capture an IC3-audience bearer (aud `https://ic3.teams.office.com`,
123
+ * same basic Teams appid). The bearer Teams web client uses to call
124
+ * `teams.microsoft.com/api/chatsvc/<region>/v1/users/ME/conversations/{id}/messages`
125
+ * — the chat-message substrate with proper `syncState` pagination,
126
+ * enabling reads beyond the 200-message chatsvcagg cap. Same failure
127
+ * modes as `acquireChatsvcaggToken`.
128
+ */
129
+ acquireIc3Token: () => Promise<Ic3TokenResult>;
65
130
  /**
66
131
  * Login-fix round-2: capture BOTH tokens inside ONE browser session.
67
132
  * After the Teams response listener intercepts the Teams token,
@@ -160,4 +225,4 @@ declare const createBrowserAuth: (deps: {
160
225
  fs?: FileSystem;
161
226
  }) => BrowserAuth;
162
227
  export { createBrowserAuth, createBrowserAuthFromApi, createPlaywrightApi };
163
- export type { BothTokensResult, BrowserAuth, BrowserAuthApi, BrowserAuthConfig, BrowserTokenResult, ChromiumLike, ContextLike, ElevatedFailureReason, ElevatedTokenResult, PageLike, PlaywrightLoader, RequestLike, ResponseLike, };
228
+ export type { BothTokensResult, BrowserAuth, BrowserAuthApi, BrowserAuthConfig, BrowserTokenResult, ChatsvcaggTokenResult, Ic3TokenResult, ChromiumLike, ContextLike, ElevatedFailureReason, ElevatedTokenResult, PageLike, PlaywrightLoader, RequestLike, ResponseLike, };
@@ -35,6 +35,36 @@ type GraphClient = {
35
35
  * elevated token).
36
36
  */
37
37
  getElevated: (path: string) => Promise<Result<unknown, GraphError>>;
38
+ /**
39
+ * JSON-GET against the Teams chat substrate (post-2026-05:
40
+ * `teams.microsoft.com/api/csa/<region>/api/v{N}/...` — see
41
+ * `gotcha_chatsvcagg_substrate_moved` in memory for the migration
42
+ * away from `chatsvcagg.teams.microsoft.com`). Signs the request
43
+ * with the chatsvcagg-audience bearer captured at login (same Teams
44
+ * web client identity as `get`, different audience), and injects the
45
+ * cached substrate region between the host and `path`. Used by
46
+ * commands that need to read chat message BODIES, which the basic
47
+ * Graph token cannot reach (`Chat.Read*` scopes are missing).
48
+ *
49
+ * `path` MUST start with `/api/v{N}/...` — the host + `/api/csa/<region>`
50
+ * prefix are added by this client.
51
+ */
52
+ teamsChat: (path: string) => Promise<Result<unknown, GraphError>>;
53
+ /**
54
+ * JSON-GET against the Teams IC3 chat-message substrate at
55
+ * `teams.microsoft.com/api/chatsvc/<region>/v1/...`. Same host as
56
+ * `teamsChat` but a DIFFERENT path prefix AND a different bearer
57
+ * audience (`https://ic3.teams.office.com` instead of
58
+ * `https://chatsvcagg.teams.microsoft.com`). The path supports
59
+ * `syncState` + `startTime` pagination — unlocking arbitrary-depth
60
+ * chat-history reads beyond the chatsvcagg 200-message cap (see
61
+ * `gotcha_chatsvcagg_substrate_moved` in memory). Used by
62
+ * `list-teams-chat-history`.
63
+ *
64
+ * `path` MUST start with `/v1/...` (e.g. `/v1/users/ME/conversations/{id}/messages?startTime=...`)
65
+ * — the host + `/api/chatsvc/<region>` prefix are added here.
66
+ */
67
+ teamsChatIc3: (path: string) => Promise<Result<unknown, GraphError>>;
38
68
  post: (path: string, body: unknown) => Promise<Result<unknown, GraphError>>;
39
69
  getBinary: (path: string) => Promise<Result<unknown, GraphError>>;
40
70
  /**
@@ -74,6 +104,14 @@ type TokenInfo = {
74
104
  readonly scopes: ReadonlyArray<string>;
75
105
  readonly audience: string | undefined;
76
106
  readonly expiresAt: string | undefined;
107
+ /**
108
+ * Seconds remaining until the cached token's `exp` claim — derived from
109
+ * `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
111
+ * an LLM decide pre-emptively to run `ask-marcel login` (re-auth typically
112
+ * worth doing under ~5 minutes) without parsing the ISO string itself.
113
+ */
114
+ readonly expiresInSeconds: number | undefined;
77
115
  };
78
116
  type FetchFn = (url: string, init?: RequestInit) => Promise<Response>;
79
117
  declare const createGraphClient: (auth: AuthManager, fetchFn?: FetchFn) => GraphClient;
@@ -0,0 +1,9 @@
1
+ export declare const REQUEST_TIMEOUT_MS = 60000;
2
+ export declare const BINARY_TRANSFER_TIMEOUT_MS: number;
3
+ export declare const REQUEST_TIMEOUT_LABEL = "60s";
4
+ export declare const BINARY_TRANSFER_TIMEOUT_LABEL = "5min";
5
+ export declare const networkErrorMessage: (e: unknown, label: string, timeoutLabel: string) => string;
6
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
7
+ export type TimeoutTier = 'json' | 'binary';
8
+ export declare const timeoutLabelFor: (tier: TimeoutTier) => string;
9
+ export declare const timeoutMsFor: (tier: TimeoutTier) => number;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Translate Graph / substrate / CLI / validation errors into actionable hints.
3
+ *
4
+ * Audit Hervé-session §2: bare `error: ErrorInvalidIdMalformed: Id is
5
+ * malformed.` had no remedy for the LLM — it had to guess where the bad ID
6
+ * came from. This module is the centralised "what should I do about this"
7
+ * lookup: pattern-match the error code (or, as a fallback, a substring of
8
+ * the message) and surface a one-line hint plus a `source` classifier so
9
+ * the LLM can branch on whether the failure is server-side, substrate-side,
10
+ * CLI-side, or a Zod validation rejection. Surfaced through the standard
11
+ * error envelope in both `--output json` (as `hint` / `source` fields) and
12
+ * `--output text` (as `hint:` / `source:` lines under the existing `error:`
13
+ * line).
14
+ *
15
+ * Audit Hervé-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)
23
+ *
24
+ * Rule precedence: specific code matchers run FIRST, then message-pattern
25
+ * fallbacks. The generic-validation rule sits LAST so it never overrides a
26
+ * code-based remedy.
27
+ *
28
+ * The table is intentionally small and biased toward HIGH-FREQUENCY errors
29
+ * an LLM actually hits. Adding more entries is cheap; the lookup is O(n)
30
+ * on a tiny n.
31
+ */
32
+ export type ErrorSource = 'graph' | 'substrate' | 'cli' | 'validation';
33
+ export type ErrorHint = {
34
+ readonly hint: string;
35
+ readonly source: ErrorSource;
36
+ };
37
+ /**
38
+ * First matching rule wins. Returns `undefined` when nothing in the table
39
+ * matches — caller renders the bare error (the historical shape).
40
+ */
41
+ export declare const findErrorHint: (message: string, code: string | undefined) => ErrorHint | undefined;
@@ -1,6 +1,7 @@
1
1
  import type { Logger } from '../use-cases/ports/logger.js';
2
+ import type { ErrorSource } from './error-hints.js';
2
3
  type OutputFormat = 'text' | 'json';
3
4
  declare const render: (data: unknown, logger: Logger, format: OutputFormat) => void;
4
- declare const renderError: (message: string, format: OutputFormat, errorCode?: string) => void;
5
+ declare const renderError: (message: string, format: OutputFormat, errorCode?: string, explicitSource?: ErrorSource) => void;
5
6
  export { render, renderError };
6
7
  export type { OutputFormat };
@@ -1,10 +1,23 @@
1
1
  import type { z } from 'zod';
2
2
  import type { Command } from './command-types.js';
3
3
  import { type ODataKey } from './odata-query.js';
4
+ /**
5
+ * Options accepted by every builder that knows about `$select` (i.e. every
6
+ * builder except `buildCommand` / `buildElevatedCommand`, which take no OData
7
+ * passthroughs at all). `defaultSelect`, when set and the user did NOT pass
8
+ * `--select`, is injected into the OData query string so default invocations
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`
11
+ * pattern with the builder layer so the 6 heaviest endpoints stop returning
12
+ * the full Graph resource by default.
13
+ */
14
+ type SelectDefaults = {
15
+ readonly defaultSelect?: string;
16
+ };
4
17
  declare const buildCommand: (pathFn: (params: Record<string, string>) => string, schema: z.ZodType) => Pick<Command, "schema" | "execute">;
5
18
  declare const buildElevatedCommand: (pathFn: (params: Record<string, string>) => string, schema: z.ZodType) => Pick<Command, "schema" | "execute">;
6
- declare const buildListCommand: <Shape extends z.ZodRawShape>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>) => Pick<Command, "schema" | "execute">;
7
- declare const buildElevatedListCommand: <Shape extends z.ZodRawShape>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>) => 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">;
20
+ 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">;
8
21
  /**
9
22
  * Single-resource GET that supports the OData `$select` and `$expand` query
10
23
  * parameters. Mirrors `buildListCommand` but exposes only the two flags that
@@ -13,14 +26,23 @@ declare const buildElevatedListCommand: <Shape extends z.ZodRawShape>(pathFn: (p
13
26
  * the fields it needs (e.g. `--select id,subject`) instead of swallowing a
14
27
  * 50 KB resource just to read a subject line.
15
28
  */
16
- declare const buildSelectableCommand: <Shape extends z.ZodRawShape>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>) => Pick<Command, "schema" | "execute">;
29
+ declare const buildSelectableCommand: <Shape extends z.ZodRawShape>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>, options?: SelectDefaults) => Pick<Command, "schema" | "execute">;
30
+ /**
31
+ * Elevated-token twin of `buildSelectableCommand`. Use for single-resource
32
+ * GETs on endpoints that require the M365ChatClient identity (e.g. `/chats/{id}`)
33
+ * AND benefit from `$select`/`$expand` projection. The basic `buildElevatedCommand`
34
+ * builder takes no OData passthroughs — use this when the endpoint honours
35
+ * field projection, so an LLM can avoid pulling the whole resource just to
36
+ * read a topic or chatType.
37
+ */
38
+ declare const buildElevatedSelectableCommand: <Shape extends z.ZodRawShape>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>, options?: SelectDefaults) => Pick<Command, "schema" | "execute">;
17
39
  /**
18
40
  * Collection GET that supports ONLY `$filter` and `$select` — for endpoints
19
41
  * Microsoft documents as rejecting the other OData passthroughs (`/teams/{id}/channels`
20
42
  * is the canonical case: Graph returns BadRequest on `$top`, `$skip`, `$orderby`,
21
43
  * `$expand`). Advertising the unsupported flags would be a usability lie.
22
44
  */
23
- declare const buildFilterSelectListCommand: <Shape extends z.ZodRawShape>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>) => Pick<Command, "schema" | "execute">;
45
+ declare const buildFilterSelectListCommand: <Shape extends z.ZodRawShape>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>, options?: SelectDefaults) => Pick<Command, "schema" | "execute">;
24
46
  /**
25
47
  * Collection GET on an endpoint that supports the usual OData passthroughs
26
48
  * EXCEPT `$skip` (e.g. `/me/drive/recent`, `/sites/{id}/lists`,
@@ -28,7 +50,7 @@ declare const buildFilterSelectListCommand: <Shape extends z.ZodRawShape>(pathFn
28
50
  * `invalidRequest: $skip is not supported on this API.`; the CLI mirrors
29
51
  * by dropping `--skip` from the advertised flag set.
30
52
  */
31
- declare const buildNoSkipListCommand: <Shape extends z.ZodRawShape>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>) => Pick<Command, "schema" | "execute">;
53
+ declare const buildNoSkipListCommand: <Shape extends z.ZodRawShape>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>, options?: SelectDefaults) => Pick<Command, "schema" | "execute">;
32
54
  /**
33
55
  * Collection GET that supports an EXPLICIT subset of OData passthroughs.
34
56
  * Use for endpoints where Graph silently drops some flags — passing
@@ -37,12 +59,12 @@ declare const buildNoSkipListCommand: <Shape extends z.ZodRawShape>(pathFn: (par
37
59
  * (`buildNoSkipListCommand`, `buildFilterSelectListCommand`) are
38
60
  * specializations; this is the generic escape hatch.
39
61
  */
40
- declare const buildPickODataListCommand: <Shape extends z.ZodRawShape, K extends ODataKey>(pathFn: (params: z.infer<z.ZodObject<Shape>>) => string, schema: z.ZodObject<Shape>, keys: ReadonlyArray<K>) => Pick<Command, "schema" | "execute">;
62
+ declare const buildPickODataListCommand: <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">;
41
63
  /**
42
64
  * Elevated-token twin of `buildPickODataListCommand`. Use for endpoints that
43
65
  * require the M365ChatClient identity (e.g. `/me/chats`, `/chats/{}/members`)
44
66
  * AND honour only a subset of OData passthroughs — the chats family rejects
45
67
  * `$orderby` / `$expand` with `BadRequest`, so the picker is the right tool.
46
68
  */
47
- 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>) => Pick<Command, "schema" | "execute">;
48
- export { buildCommand, buildElevatedCommand, buildElevatedListCommand, buildElevatedPickODataListCommand, buildFilterSelectListCommand, buildListCommand, buildNoSkipListCommand, buildPickODataListCommand, buildSelectableCommand, };
69
+ 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, };
@@ -3,7 +3,7 @@ import type { Result } from '../../domain/result.js';
3
3
  import type { GraphClient } from '../../infra/graph-client.js';
4
4
  type CommandSchema = z.ZodType;
5
5
  type CommandExecute = (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, import('../../infra/graph-client.js').GraphError>>;
6
- type CommandCategory = 'auth' | 'drive' | 'excel' | 'sharepoint' | 'tasks' | 'mail' | 'notes' | 'user' | 'calendar' | 'contacts' | 'chats' | 'teams' | 'meta' | 'lifecycle';
6
+ type CommandCategory = 'drive' | 'excel' | 'sharepoint' | 'tasks' | 'mail' | 'notes' | 'user' | 'calendar' | 'chats' | 'teams' | 'meta' | 'lifecycle';
7
7
  type CommandHttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';
8
8
  type CommandOptionAlias = {
9
9
  readonly name: string;
@@ -115,6 +115,17 @@ type CommandMeta = {
115
115
  * as a string literal. Audit round-8 Wave E2.
116
116
  */
117
117
  readonly producesBytes?: true;
118
+ /**
119
+ * Stability tier of the command. Omitted from manifest entries when the
120
+ * command is `'stable'` (the implicit default), surfaced only on
121
+ * `'experimental'` commands so an LLM can prefer stable siblings when they
122
+ * exist. `'experimental'` today means the command rides a Microsoft-internal
123
+ * substrate (chatsvcagg / IC3) that is not in the public Graph API and can
124
+ * break on a Teams web-client update — the docstring "Best-effort, may break
125
+ * on Microsoft client updates" warnings now have a structured pair.
126
+ * Audit Hervé-session §6.
127
+ */
128
+ readonly stability?: 'experimental';
118
129
  };
119
130
  type Command = {
120
131
  readonly schema: CommandSchema;
@@ -4,6 +4,10 @@ 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
6
  messageId: z.ZodString;
7
+ inlineImages: z.ZodOptional<z.ZodEnum<{
8
+ true: "true";
9
+ false: "false";
10
+ }>>;
7
11
  }, z.core.$strip>;
8
12
  declare const execute: (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, GraphError>>;
9
13
  declare const meta: CommandMeta;
@@ -1,4 +1,4 @@
1
- import type { CommandCategory, CommandMeta } from './command-types.js';
1
+ import type { CommandCategory, CommandMeta, PaginationStrategy } from './command-types.js';
2
2
  export type CommandManifestEntry = {
3
3
  readonly name: string;
4
4
  readonly summary: string;
@@ -16,8 +16,10 @@ export type CommandManifestEntry = {
16
16
  readonly scopesRequired?: CommandMeta['scopesRequired'];
17
17
  readonly needsElevatedToken?: CommandMeta['needsElevatedToken'];
18
18
  readonly producesBytes?: CommandMeta['producesBytes'];
19
+ readonly stability?: CommandMeta['stability'];
19
20
  };
20
- export declare const PAGINATION_HINT = "Paginated by Microsoft Graph. The CLI hoists `@odata.nextLink` out of `data` to the **top-level `nextLink`** field of the response envelope (and `@odata.deltaLink` to top-level `deltaLink` for delta commands). Pass that URL to `next-page --url <link>` and repeat until the field is absent. Do NOT look for `data[\"@odata.nextLink\"]` \u2014 the presenter strips it from `data` so the cursor is always at envelope level.";
21
+ export declare const paginationHintFor: (strategy: PaginationStrategy | undefined) => string;
22
+ export declare const PAGINATION_HINT = "Paginated by Microsoft Graph. The CLI hoists `@odata.nextLink` out of `data` to the **top-level `nextLink`** field of the response envelope. Pass that URL to `next-page --url <link>` and repeat until the field is absent. Do NOT look for `data[\"@odata.nextLink\"]` \u2014 the presenter strips it from `data` so the cursor is always at envelope level.";
21
23
  export type CommandManifest = {
22
24
  readonly package: string;
23
25
  readonly version: string;
@@ -1,10 +1,53 @@
1
1
  import type { Result } from '../../domain/result.js';
2
- import type { Command } from './command-types.js';
2
+ import type { CommandCategory, Command, CommandMeta } from './command-types.js';
3
3
  import type { CommandManifest } from './docs-render.js';
4
4
  export type DocsError = {
5
5
  type: 'unknown_command';
6
6
  readonly name: string;
7
7
  readonly available: ReadonlyArray<string>;
8
8
  };
9
+ /**
10
+ * Terse manifest entry — only the fields an LLM needs to *discover* a command
11
+ * (i.e. "does this CLI do X?"). Drops `options`, `example`, `graphPathTemplate`,
12
+ * `graphDocsUrl`, `responseShape`, `bodyTemplate`, `paginationStrategy`,
13
+ * `scopesRequired` — everything the LLM only needs once it's already decided
14
+ * to invoke. `stability` is kept (it's a discovery-time concern: LLMs prefer
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 Hervé-session §B/§6.
17
+ */
18
+ export type TerseManifestEntry = {
19
+ readonly name: string;
20
+ readonly summary: string;
21
+ readonly category: CommandCategory;
22
+ readonly stability?: CommandMeta['stability'];
23
+ };
24
+ export type TerseManifest = {
25
+ readonly package: string;
26
+ readonly version: string;
27
+ readonly generatedAt: string;
28
+ readonly commands: ReadonlyArray<TerseManifestEntry>;
29
+ };
30
+ export type ManifestFilterError = {
31
+ readonly type: 'unknown_category';
32
+ readonly category: string;
33
+ readonly available: ReadonlyArray<string>;
34
+ };
9
35
  export declare const buildManifest: (registry: Readonly<Record<string, Command>>, packageName: string, version: string, now?: () => Date) => CommandManifest;
36
+ /**
37
+ * Terse manifest — `{ name, summary, category }` per command. Roughly 95%
38
+ * smaller than the full manifest (no options/example/Graph endpoint per entry).
39
+ * Use `help-json --terse` to surface this to an LLM as the discovery view.
40
+ */
41
+ export declare const buildTerseManifest: (registry: Readonly<Record<string, Command>>, packageName: string, version: string, now?: () => Date) => TerseManifest;
42
+ /**
43
+ * Filter a `CommandManifest` (or terse variant) down to a single category.
44
+ * Returns `err({ type: 'unknown_category', ... })` if the requested category
45
+ * isn't a known one — the CLI surfaces this through the standard error
46
+ * envelope rather than silently returning an empty list.
47
+ */
48
+ export declare const filterManifestByCategory: <M extends {
49
+ readonly commands: ReadonlyArray<{
50
+ readonly category: CommandCategory;
51
+ }>;
52
+ }>(manifest: M, category: string) => Result<M, ManifestFilterError>;
10
53
  export declare const renderSingleCommand: (registry: Readonly<Record<string, Command>>, name: string) => Result<string, DocsError>;
@@ -6,6 +6,11 @@ declare const schema: z.ZodObject<{
6
6
  driveId: z.ZodString;
7
7
  itemId: z.ZodString;
8
8
  versionId: z.ZodString;
9
+ format: z.ZodOptional<z.ZodEnum<{
10
+ markdown: "markdown";
11
+ pdf: "pdf";
12
+ original: "original";
13
+ }>>;
9
14
  }, z.core.$strip>;
10
15
  declare const execute: (graph: GraphClient, params: Record<string, string>) => Promise<Result<unknown, GraphError>>;
11
16
  declare const meta: CommandMeta;
@@ -44,7 +44,7 @@ export declare const inlineBinary: (graph: GraphClient, contentPath: string, opt
44
44
  * reference-attachment edge cases) Graph silently falls back to returning
45
45
  * the raw source bytes — same envelope shape, but contentType is the
46
46
  * source MIME (or `application/octet-stream`). The audit (round-5 #2)
47
- * caught this happening on `download-drive-item-version-as-pdf` for v79
47
+ * caught this happening on `download-drive-item-version --format pdf` for v79
48
48
  * of a pptx: the response said `contentType: "application/octet-stream"`
49
49
  * with the exact source byte size, and an LLM that saved it as `.pdf`
50
50
  * would have had a corrupt file.
@@ -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
+ name: z.ZodString;
5
+ maxPages: z.ZodOptional<z.ZodString>;
6
+ pageSize: z.ZodOptional<z.ZodString>;
7
+ }, z.core.$strip>;
8
+ declare const execute: Command['execute'];
9
+ declare const meta: CommandMeta;
10
+ export { execute, meta, schema };
@@ -1,8 +1,4 @@
1
- import { z } from 'zod';
2
1
  import type { CommandMeta } from './command-types.js';
3
- declare const schema: z.ZodObject<{
4
- chatId: z.ZodString;
5
- }, z.core.$strip>;
6
- declare const execute: import("./command-types.js").CommandExecute;
2
+ declare const execute: import("./command-types.js").CommandExecute, schema: import("./command-types.js").CommandSchema;
7
3
  declare const meta: CommandMeta;
8
4
  export { execute, meta, schema };
@@ -1,10 +1,15 @@
1
1
  import { z } from 'zod';
2
- import type { CommandMeta } from './command-types.js';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
3
  declare const schema: z.ZodObject<{
4
4
  driveId: z.ZodString;
5
5
  itemId: z.ZodString;
6
6
  worksheetId: z.ZodString;
7
+ full: z.ZodOptional<z.ZodEnum<{
8
+ true: "true";
9
+ false: "false";
10
+ }>>;
11
+ maxCells: z.ZodOptional<z.ZodString>;
7
12
  }, z.core.$strip>;
8
- declare const execute: (graph: import("../../index.js").GraphClient, params: Record<string, string>) => Promise<import("../../index.js").Result<unknown, import("../../index.js").GraphError>>;
13
+ declare const execute: Command['execute'];
9
14
  declare const meta: CommandMeta;
10
15
  export { execute, meta, schema };
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ chatId: z.ZodString;
5
+ messageId: z.ZodString;
6
+ }, z.core.$strip>;
7
+ declare const execute: Command['execute'];
8
+ declare const meta: CommandMeta;
9
+ export { execute, meta, schema };
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Reusable Zod field for an ISO-8601 UTC datetime parameter that ALSO accepts
4
+ * the relative-date vocabulary defined in `src/domain/iso-datetime.ts`
5
+ * (`7d`, `1w`, `today`, `monday`, `start-of-month`, etc.). Calendar commands
6
+ * compose this into their schemas in place of `z.string().min(1)`; the
7
+ * URL-builder sees the already-resolved canonical ISO form.
8
+ *
9
+ * Audit Hervé-session §C: removes the manual ISO arithmetic an LLM had to do
10
+ * to ask "what changed this week" against `list-calendar-view`. Type
11
+ * inference is left to Zod so the calendar commands' shape signatures stay
12
+ * compatible with `buildListCommand`'s `ZodRawShape` constraint.
13
+ */
14
+ export declare const isoDateTimeField: z.ZodPipe<z.ZodString, z.ZodTransform<import("../../domain/iso-datetime.js").IsoDateTime, string>>;
15
+ /**
16
+ * Standard `--start-date-time` / `--end-date-time` description used by every
17
+ * calendar-view command. Mentions both the strict ISO form Graph expects and
18
+ * the relative vocabulary the CLI also accepts, so the LLM sees both options
19
+ * in `--help`.
20
+ */
21
+ export declare const RELATIVE_DATE_DESCRIPTION = "ISO 8601 UTC (e.g. `2026-04-01T00:00:00Z` or `2026-04-01`) OR a relative shape: `7d` / `1w` / `2h` / `30m` (past), `+7d` (future), `today` / `yesterday` / `tomorrow` / `now`, `monday`-`sunday` (most recent), `last-<weekday>` / `next-<weekday>`, `start-of-week|month|year`, `end-of-week|month|year`. Relative forms resolve at request time relative to the CLI process clock (UTC).";
@@ -0,0 +1,13 @@
1
+ type SiblingResolver = 'mail' | 'calendar' | 'teams' | 'drive-share';
2
+ /**
3
+ * Classify a Zod-validated URL string by which sibling resolver it
4
+ * belongs to. Returns `null` when the URL matches none of the known
5
+ * resolver families (caller falls through to its own "unknown URL"
6
+ * rejection message).
7
+ *
8
+ * Precondition: `raw` has been validated through `z.url()`. Calling
9
+ * with an unvalidated string is a programmer error — `new URL(raw)`
10
+ * will throw and the atelier rule forbids try/catch outside infra.
11
+ */
12
+ export declare const detectSiblingResolver: (raw: string) => SiblingResolver | null;
13
+ export type { SiblingResolver };
@@ -1,8 +1,8 @@
1
1
  import { z } from 'zod';
2
2
  import type { Command, CommandMeta } from './command-types.js';
3
3
  declare const schema: z.ZodObject<{
4
- startDateTime: z.ZodString;
5
- endDateTime: z.ZodString;
4
+ startDateTime: z.ZodPipe<z.ZodString, z.ZodTransform<import("../../domain/iso-datetime.js").IsoDateTime, string>>;
5
+ endDateTime: z.ZodPipe<z.ZodString, z.ZodTransform<import("../../domain/iso-datetime.js").IsoDateTime, string>>;
6
6
  top: z.ZodOptional<z.ZodString>;
7
7
  }, z.core.$strip>;
8
8
  declare const execute: Command['execute'];
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ chatId: z.ZodString;
5
+ syncState: z.ZodOptional<z.ZodURL>;
6
+ pageSize: z.ZodOptional<z.ZodString>;
7
+ maxPages: z.ZodOptional<z.ZodString>;
8
+ full: z.ZodOptional<z.ZodEnum<{
9
+ true: "true";
10
+ false: "false";
11
+ }>>;
12
+ maxContentChars: z.ZodOptional<z.ZodString>;
13
+ }, z.core.$strip>;
14
+ declare const execute: Command['execute'];
15
+ declare const meta: CommandMeta;
16
+ export { execute, meta, schema };
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ chatId: z.ZodString;
5
+ }, z.core.$strip>;
6
+ declare const execute: Command['execute'];
7
+ declare const meta: CommandMeta;
8
+ export { execute, meta, schema };
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ pageSize: z.ZodOptional<z.ZodString>;
5
+ continuationToken: z.ZodOptional<z.ZodString>;
6
+ }, z.core.$strip>;
7
+ declare const execute: Command['execute'];
8
+ declare const meta: CommandMeta;
9
+ export { execute, meta, schema };
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ import type { Command, CommandMeta } from './command-types.js';
3
+ declare const schema: z.ZodObject<{
4
+ url: z.ZodURL;
5
+ }, z.core.$strip>;
6
+ declare const execute: Command['execute'];
7
+ declare const meta: CommandMeta;
8
+ export { execute, meta, schema };