Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ Most command modules follow this shape:
6. Call `api::<domain>::...`.
7. Print through `output::print_success`, `print_success_list`, or a table helper.

For paginated list commands, pass `&PaginationOpts` from `src/cli/mod.rs` and use `client.get_paged` in the API layer.
For paginated list commands, pass `&PaginationOpts` from `src/cli/mod.rs` and use `client.get_paged` in the API layer. It returns a `Page`; print it with `output::print_success_page` (and `output::print_more_hint` after a table) so `metadata.has_more`/`next_link` reach the caller.

For new command domains, update:

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Added

- List commands without `--all-pages` report whether the result is complete: JSON `metadata.has_more`, plus `metadata.next_link` (Graph's `@odata.nextLink`) when more pages exist. Previously a truncated page looked identical to a complete collection. With `--all-pages`, `has_more` is false. `data` is unchanged. Human and plain output print "More results available; use --all-pages" on stderr.

### Changed

- `Retry-After` is read in its HTTP-date form as well as seconds, honored on retried 5xx responses as well as 429, and capped at 300 seconds.
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Single-crate Rust binary. Key modules:

### Output Contract
All commands emit a JSON envelope: `{ "success": bool, "data": ..., "metadata": { "request_id", "timestamp", "duration_ms" } }`.
Paged list results add `metadata.has_more` and, when true, `metadata.next_link`; human/plain output prints a stderr hint instead.
When stdout is a TTY, defaults to human-readable table format. When piped, defaults to JSON.
Plain list columns include keys from every row, with blank cells for absent values. Human message lists include a Subject column; absent message subjects remain omitted in JSON.

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Every command, when piped or called programmatically, returns a **JSON envelope*
}
```

Paged list results add `metadata.has_more` (more pages exist) and, when true, `metadata.next_link` (Graph's `@odata.nextLink`). With `--all-pages`, `has_more` is false. Human and plain output note truncation on stderr.

On failure:

```json
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ When stdout is piped or called by an agent, the CLI defaults to JSON:
}
```

Paged list results add `metadata.has_more` and, when true, `metadata.next_link` (Graph's `@odata.nextLink`); `--all-pages` gives `has_more: false`.

Errors use the same envelope shape with `success: false` and a stable error code. See [command reference](command-reference.md#exit-codes) and `teams-agent-contract(7)`.

## Official Microsoft references
Expand Down
14 changes: 14 additions & 0 deletions docs/man/teams-agent-contract.7
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,20 @@ code 7 and a hint that it was performed; do not re-send it. List commands use
.B --page-size
and can follow all pages with
.B --all-pages.
.PP
Paged list results add
.B metadata.has_more
(true when Graph has further pages) and, only when true,
.B metadata.next_link
(Graph's
.B @odata.nextLink\c
). With
.B --all-pages
.B has_more
is false.
.B data
is unchanged. Human and plain output print
"More results available; use --all-pages" on stderr instead.
.SH AUTHENTICATION FOR AGENTS
For automation that posts normal Teams chat or channel messages, use a
delegated profile or a future Teams bot mode. Microsoft Graph app-only tokens
Expand Down
5 changes: 3 additions & 2 deletions src/api/apps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::error::Result;
use crate::models::app::{
CreateTabRequest, InstallAppRequest, TeamsAppInstallation, TeamsTab, UpdateTabRequest,
};
use crate::models::common::Page;

use super::client::{GraphClient, PaginationOpts};
use super::endpoints;
Expand All @@ -10,7 +11,7 @@ pub async fn list_team_apps(
client: &GraphClient,
team_id: &str,
pagination: &PaginationOpts,
) -> Result<Vec<TeamsAppInstallation>> {
) -> Result<Page<TeamsAppInstallation>> {
client
.get_paged(
&endpoints::team_installed_apps(team_id),
Expand Down Expand Up @@ -45,7 +46,7 @@ pub async fn list_tabs(
team_id: &str,
channel_id: &str,
pagination: &PaginationOpts,
) -> Result<Vec<TeamsTab>> {
) -> Result<Page<TeamsTab>> {
client
.get_paged(
&endpoints::channel_tabs(team_id, channel_id),
Expand Down
10 changes: 6 additions & 4 deletions src/api/channels.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::error::Result;
use crate::models::channel::{Channel, ChannelCreateRequest, ChannelUpdateRequest};
use crate::models::common::Page;
use crate::models::member::{AddMemberRequest, ConversationMember};

use super::client::{GraphClient, PaginationOpts};
Expand All @@ -9,15 +10,15 @@ pub async fn list_channels(
client: &GraphClient,
team_id: &str,
pagination: &PaginationOpts,
) -> Result<Vec<Channel>> {
) -> Result<Page<Channel>> {
list_channels_at(client, &endpoints::channels(team_id), pagination).await
}

async fn list_channels_at(
client: &GraphClient,
url: &str,
pagination: &PaginationOpts,
) -> Result<Vec<Channel>> {
) -> Result<Page<Channel>> {
client.get_paged_without_top(url, &[], pagination).await
}

Expand Down Expand Up @@ -57,7 +58,7 @@ pub async fn list_members(
team_id: &str,
channel_id: &str,
pagination: &PaginationOpts,
) -> Result<Vec<ConversationMember>> {
) -> Result<Page<ConversationMember>> {
client
.get_paged(
&endpoints::channel_members(team_id, channel_id),
Expand Down Expand Up @@ -145,7 +146,8 @@ mod tests {
},
)
.await
.unwrap();
.unwrap()
.items;

assert_eq!(channels.len(), 1);
assert_eq!(channels[0].display_name.as_deref(), Some("General"));
Expand Down
18 changes: 11 additions & 7 deletions src/api/chats.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::error::Result;
use crate::models::chat::{Chat, ChatCreateRequest, ChatUpdateRequest};
use crate::models::common::Page;
use crate::models::member::{AddMemberRequest, ConversationMember};

use super::client::{GraphClient, PaginationOpts};
Expand Down Expand Up @@ -31,7 +32,7 @@ pub async fn list_chats(
client: &GraphClient,
order: ChatOrder,
pagination: &PaginationOpts,
) -> Result<Vec<Chat>> {
) -> Result<Page<Chat>> {
list_chats_at(client, &endpoints::my_chats(), order, pagination).await
}

Expand All @@ -40,7 +41,7 @@ async fn list_chats_at(
url: &str,
order: ChatOrder,
pagination: &PaginationOpts,
) -> Result<Vec<Chat>> {
) -> Result<Page<Chat>> {
client.get_paged(url, order.query(), pagination).await
}

Expand All @@ -66,7 +67,7 @@ pub async fn list_members(
client: &GraphClient,
chat_id: &str,
pagination: &PaginationOpts,
) -> Result<Vec<ConversationMember>> {
) -> Result<Page<ConversationMember>> {
list_members_at(client, &endpoints::chat_members(chat_id), pagination).await
}

Expand All @@ -76,7 +77,7 @@ pub(crate) async fn list_members_at(
client: &GraphClient,
url: &str,
pagination: &PaginationOpts,
) -> Result<Vec<ConversationMember>> {
) -> Result<Page<ConversationMember>> {
client.get_paged_without_top(url, &[], pagination).await
}

Expand Down Expand Up @@ -173,7 +174,8 @@ mod tests {
},
)
.await
.unwrap();
.unwrap()
.items;
assert_eq!(chats.len(), 1);
}

Expand Down Expand Up @@ -201,7 +203,8 @@ mod tests {
},
)
.await
.unwrap();
.unwrap()
.items;
assert_eq!(chats.len(), 1);
}

Expand Down Expand Up @@ -233,7 +236,8 @@ mod tests {
},
)
.await
.unwrap();
.unwrap()
.items;

assert_eq!(members.len(), 1);
assert_eq!(members[0].display_name.as_deref(), Some("Alice"));
Expand Down
Loading
Loading