-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.go
More file actions
179 lines (161 loc) · 6.72 KB
/
main.go
File metadata and controls
179 lines (161 loc) · 6.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package main
import (
"context"
"os"
"strings"
"github.com/alecthomas/kong"
"github.com/sozercan/a365cli/internal/commands"
"github.com/sozercan/a365cli/internal/commands/admin"
"github.com/sozercan/a365cli/internal/commands/admin365"
"github.com/sozercan/a365cli/internal/commands/api"
"github.com/sozercan/a365cli/internal/commands/calendar"
"github.com/sozercan/a365cli/internal/commands/configcmd"
"github.com/sozercan/a365cli/internal/commands/copilot"
"github.com/sozercan/a365cli/internal/commands/dasearch"
"github.com/sozercan/a365cli/internal/commands/excel"
"github.com/sozercan/a365cli/internal/commands/knowledge"
"github.com/sozercan/a365cli/internal/commands/mail"
"github.com/sozercan/a365cli/internal/commands/me"
"github.com/sozercan/a365cli/internal/commands/nlweb"
"github.com/sozercan/a365cli/internal/commands/onedriveremote"
"github.com/sozercan/a365cli/internal/commands/planner"
"github.com/sozercan/a365cli/internal/commands/sharepoint"
"github.com/sozercan/a365cli/internal/commands/splists"
"github.com/sozercan/a365cli/internal/commands/teams"
"github.com/sozercan/a365cli/internal/commands/triggers"
"github.com/sozercan/a365cli/internal/commands/websearch"
"github.com/sozercan/a365cli/internal/commands/word"
"github.com/sozercan/a365cli/internal/config"
"github.com/sozercan/a365cli/internal/output"
"github.com/sozercan/a365cli/internal/version"
)
// CLI is the top-level command structure.
type CLI struct {
// Services
Teams teams.TeamsCmd `cmd:"" help:"Microsoft Teams"`
Mail mail.MailCmd `cmd:"" help:"Microsoft Mail" aliases:"email"`
Calendar calendar.CalendarCmd `cmd:"" help:"Microsoft Calendar" aliases:"cal"`
Planner planner.PlannerCmd `cmd:"" help:"Microsoft Planner"`
SharePoint sharepoint.SharePointCmd `cmd:"" help:"SharePoint files and sites" name:"sharepoint" aliases:"sp"`
SPLists splists.SPListsCmd `cmd:"" help:"SharePoint Lists" name:"sp-lists"`
Me me.MeCmd `cmd:"" help:"User profiles and org info"`
Copilot copilot.CopilotCmd `cmd:"" help:"Microsoft 365 Copilot"`
Word word.WordCmd `cmd:"" help:"Microsoft Word documents"`
Excel excel.ExcelCmd `cmd:"" help:"Microsoft Excel workbooks"`
Admin admin.AdminCmd `cmd:"" help:"M365 tenant administration"`
Admin365 admin365.Admin365Cmd `cmd:"" help:"Admin365 agent and Copilot settings" name:"admin365"`
Knowledge knowledge.KnowledgeCmd `cmd:"" help:"Federated knowledge sources"`
NLWeb nlweb.NLWebCmd `cmd:"" help:"NLWeb natural language search" name:"nlweb"`
OneDriveRemote onedriveremote.OneDriveRemoteCmd `cmd:"" help:"Personal OneDrive files" name:"onedrive-remote" aliases:"odr"`
WebSearch websearch.WebSearchCmd `cmd:"" help:"Web search" name:"websearch"`
DASearch dasearch.DASearchCmd `cmd:"" help:"Declarative Agent search" name:"dasearch"`
Triggers triggers.TriggersCmd `cmd:"" help:"Event triggers and automation"`
// Auth & utility
Auth commands.AuthCmd `cmd:"" help:"Authentication"`
Config configcmd.ConfigCmd `cmd:"" help:"Manage CLI configuration"`
Completion commands.CompletionCmd `cmd:"" help:"Generate shell completion script"`
// Dev tools (hidden from main help)
API api.APICmd `cmd:"" help:"API explorer for MCP servers (dev/debug)" hidden:""`
// Output format
Output string `help:"Output format: table, json, or tsv" enum:"table,json,tsv,," default:"" env:"A365_OUTPUT" short:"o"`
JSON bool `help:"Shorthand for --output=json" hidden:"" xor:"output"`
Plain bool `help:"Shorthand for --output=tsv" hidden:"" xor:"output"`
// Safety flags
Force bool `help:"Skip confirmation prompts"`
NoInput bool `help:"Never prompt; fail instead (CI mode)" name:"no-input"`
DryRun bool `help:"Preview write operations without executing" name:"dry-run"`
// Connection
Verbose bool `help:"Show MCP request/response for debugging" short:"v"`
ClientID string `help:"Entra app client ID" env:"A365_CLIENT_ID"`
TenantID string `help:"Entra tenant ID" env:"A365_TENANT_ID"`
// Version flag
Version kong.VersionFlag `help:"Show version" short:"V"`
}
func main() {
cli := CLI{}
kongCtx := kong.Parse(&cli,
kong.Name("a365"),
kong.Description("CLI for Microsoft 365 via agent365 MCP servers"),
kong.Vars{"version": version.Version + " (" + version.Commit + ")"},
kong.ConfigureHelp(kong.HelpOptions{
Compact: true,
}),
kong.Bind(&commands.Context{}),
)
// Load config file defaults (best-effort).
// CLI flags and env vars always take precedence.
fileCfg := config.LoadFileConfig()
if cli.ClientID == "" && fileCfg.ClientID != "" {
cli.ClientID = fileCfg.ClientID
}
if cli.ClientID == "" {
cli.ClientID = config.DefaultClientID
}
if cli.TenantID == "" && fileCfg.TenantID != "" {
cli.TenantID = fileCfg.TenantID
}
if cli.Output == "" && !cli.JSON && !cli.Plain && fileCfg.Output != "" {
switch fileCfg.Output {
case "json":
cli.Output = "json"
case "plain", "tsv":
cli.Output = "tsv"
case "table":
cli.Output = "table"
}
}
if os.Getenv("A365_ENDPOINT") == "" && fileCfg.Endpoint != "" {
if err := config.ValidateEndpointURL(fileCfg.Endpoint); err != nil {
output.PrintError("ignoring invalid configured endpoint %q: %v", fileCfg.Endpoint, err)
} else {
os.Setenv("A365_ENDPOINT", fileCfg.Endpoint)
}
}
// Build shared context
ctx := &commands.Context{
Ctx: context.Background(),
Output: output.NewFormatter(resolveOutputFormat(cli.Output, cli.JSON, cli.Plain)),
Verbose: cli.Verbose,
ClientID: cli.ClientID,
TenantID: cli.TenantID,
Force: cli.Force,
NoInput: cli.NoInput,
DryRun: cli.DryRun,
}
// For non-auth, non-completion, non-config commands, ensure authentication
cmd := kongCtx.Command()
if requiresAuth(cmd) {
if err := ctx.EnsureAuth(); err != nil {
output.PrintError("%v", err)
os.Exit(1)
}
}
// Run the selected command
err := kongCtx.Run(ctx)
if err != nil {
output.PrintError("%v", err)
os.Exit(1)
}
}
// resolveOutputFormat merges the new --output flag with the legacy --json/--plain booleans.
func resolveOutputFormat(outputFlag string, jsonFlag, plainFlag bool) string {
if outputFlag != "" {
return outputFlag
}
if jsonFlag {
return "json"
}
if plainFlag {
return "tsv"
}
return "table"
}
func requiresAuth(cmd string) bool {
return cmd != "auth login" &&
cmd != "auth status" &&
cmd != "auth logout" &&
cmd != "auth token" &&
cmd != "completion <shell>" &&
!strings.HasPrefix(cmd, "config ") &&
cmd != "api servers"
}