@awiki/cli 0.0.1-beta.2
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.
- package/.github/workflows/release.yml +44 -0
- package/.goreleaser.yml +44 -0
- package/AGENTS.md +60 -0
- package/CLAUDE.md +192 -0
- package/README.md +2 -0
- package/docs/architecture/awiki-command-v2.md +955 -0
- package/docs/architecture/awiki-skill-architecture.md +475 -0
- package/docs/architecture/awiki-v2-architecture.md +1063 -0
- package/docs/architecture//345/217/202/350/200/203/346/226/207/346/241/243/cli-init.md +1008 -0
- package/docs/architecture//345/217/202/350/200/203/346/226/207/346/241/243/output-format.md +407 -0
- package/docs/architecture//345/217/202/350/200/203/346/226/207/346/241/243/overall-init.md +741 -0
- package/docs/harness/review-spec.md +474 -0
- package/docs/installation.md +372 -0
- package/docs/plan/awiki-v2-implementation-plan.md +903 -0
- package/docs/plan/phase-0/adr-index.md +56 -0
- package/docs/plan/phase-0/audit-findings.md +251 -0
- package/docs/plan/phase-0/capability-mapping.md +108 -0
- package/docs/plan/phase-0/implementation-constraints.md +363 -0
- package/docs/publish.md +169 -0
- package/go.mod +29 -0
- package/go.sum +73 -0
- package/internal/anpsdk/registry.go +63 -0
- package/internal/authsdk/session.go +351 -0
- package/internal/buildinfo/buildinfo.go +34 -0
- package/internal/cli/app.go +136 -0
- package/internal/cli/app_test.go +88 -0
- package/internal/cli/debug.go +104 -0
- package/internal/cli/group.go +263 -0
- package/internal/cli/id.go +473 -0
- package/internal/cli/init.go +134 -0
- package/internal/cli/msg.go +228 -0
- package/internal/cli/page.go +267 -0
- package/internal/cli/root.go +499 -0
- package/internal/cli/runtime.go +232 -0
- package/internal/cli/upgrade.go +60 -0
- package/internal/cmdmeta/catalog.go +203 -0
- package/internal/cmdmeta/catalog_test.go +21 -0
- package/internal/config/config.go +399 -0
- package/internal/config/config_test.go +104 -0
- package/internal/config/write.go +37 -0
- package/internal/content/service.go +314 -0
- package/internal/content/service_test.go +165 -0
- package/internal/content/types.go +44 -0
- package/internal/docs/topics.go +110 -0
- package/internal/doctor/doctor.go +306 -0
- package/internal/identity/client.go +267 -0
- package/internal/identity/did.go +85 -0
- package/internal/identity/did_test.go +50 -0
- package/internal/identity/layout.go +206 -0
- package/internal/identity/legacy.go +378 -0
- package/internal/identity/public.go +70 -0
- package/internal/identity/public_test.go +73 -0
- package/internal/identity/readiness.go +74 -0
- package/internal/identity/service.go +826 -0
- package/internal/identity/store.go +385 -0
- package/internal/identity/store_test.go +180 -0
- package/internal/identity/types.go +204 -0
- package/internal/message/auth.go +167 -0
- package/internal/message/group_service.go +838 -0
- package/internal/message/group_wire.go +350 -0
- package/internal/message/group_wire_test.go +67 -0
- package/internal/message/helpers.go +61 -0
- package/internal/message/http_client.go +334 -0
- package/internal/message/proof.go +156 -0
- package/internal/message/proof_test.go +61 -0
- package/internal/message/service.go +696 -0
- package/internal/message/service_test.go +97 -0
- package/internal/message/types.go +155 -0
- package/internal/message/wire.go +100 -0
- package/internal/message/wire_test.go +49 -0
- package/internal/message/ws_proxy_client.go +151 -0
- package/internal/output/output.go +350 -0
- package/internal/output/output_test.go +48 -0
- package/internal/runtime/config.go +117 -0
- package/internal/runtime/config_test.go +46 -0
- package/internal/runtime/listener/files.go +65 -0
- package/internal/runtime/listener/manager.go +142 -0
- package/internal/runtime/listener/server.go +983 -0
- package/internal/runtime/listener/server_test.go +319 -0
- package/internal/runtime/listener/sysproc_unix.go +17 -0
- package/internal/runtime/listener/sysproc_windows.go +13 -0
- package/internal/runtime/listener/types.go +21 -0
- package/internal/runtime/listener/wsclient.go +299 -0
- package/internal/runtime/listener/wsclient_test.go +41 -0
- package/internal/store/dao.go +632 -0
- package/internal/store/dao_test.go +87 -0
- package/internal/store/helpers.go +197 -0
- package/internal/store/import.go +499 -0
- package/internal/store/import_test.go +103 -0
- package/internal/store/open.go +71 -0
- package/internal/store/query.go +151 -0
- package/internal/store/schema.go +277 -0
- package/internal/store/schema_test.go +56 -0
- package/internal/store/types.go +177 -0
- package/internal/update/update.go +368 -0
- package/package.json +17 -0
- package/scripts/install.js +171 -0
- package/scripts/release/release-prerelease.sh +86 -0
- package/scripts/release/tag-release.sh +66 -0
- package/scripts/release/withdraw-release.sh +78 -0
- package/scripts/run.js +69 -0
- package/skills/README.md +32 -0
- package/skills/awiki-bundle/SKILL.md +76 -0
- package/skills/awiki-debug/SKILL.md +80 -0
- package/skills/awiki-group/SKILL.md +111 -0
- package/skills/awiki-id/SKILL.md +123 -0
- package/skills/awiki-msg/SKILL.md +131 -0
- package/skills/awiki-page/SKILL.md +93 -0
- package/skills/awiki-people/SKILL.md +66 -0
- package/skills/awiki-runtime/SKILL.md +137 -0
- package/skills/awiki-shared/SKILL.md +124 -0
- package/skills/awiki-workflow-discovery/SKILL.md +93 -0
- package/skills/awiki-workflow-onboarding/SKILL.md +119 -0
- package/skills/manifests/skills.yaml +260 -0
- package/skills/templates/bundle-skill-template.md +42 -0
- package/skills/templates/debug-skill-template.md +44 -0
- package/skills/templates/domain-skill-template.md +56 -0
- package/skills/templates/shared-skill-template.md +46 -0
- package/skills/templates/workflow-skill-template.md +46 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
package cli
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"sort"
|
|
8
|
+
"strconv"
|
|
9
|
+
"strings"
|
|
10
|
+
|
|
11
|
+
"github.com/agentconnect/awiki-cli/internal/buildinfo"
|
|
12
|
+
"github.com/agentconnect/awiki-cli/internal/cmdmeta"
|
|
13
|
+
appconfig "github.com/agentconnect/awiki-cli/internal/config"
|
|
14
|
+
doccheck "github.com/agentconnect/awiki-cli/internal/doctor"
|
|
15
|
+
"github.com/agentconnect/awiki-cli/internal/output"
|
|
16
|
+
"github.com/agentconnect/awiki-cli/internal/store"
|
|
17
|
+
"github.com/agentconnect/awiki-cli/internal/update"
|
|
18
|
+
"github.com/spf13/cobra"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const rootLong = `awiki-cli — Agent-native identity and messaging CLI.
|
|
22
|
+
|
|
23
|
+
Phase 1 currently provides the pure-Go CLI shell, global output contract,
|
|
24
|
+
static schema introspection, built-in docs, and baseline environment checks.
|
|
25
|
+
|
|
26
|
+
Use "awiki-cli schema" to inspect the frozen command contract and
|
|
27
|
+
"awiki-cli doctor" to inspect paths, env compatibility, and migration hints.`
|
|
28
|
+
|
|
29
|
+
func newRootCommand(app *App) *cobra.Command {
|
|
30
|
+
rootCmd := &cobra.Command{
|
|
31
|
+
Use: "awiki-cli",
|
|
32
|
+
Short: "awiki CLI phase-1 shell",
|
|
33
|
+
Long: rootLong,
|
|
34
|
+
SilenceErrors: true,
|
|
35
|
+
SilenceUsage: true,
|
|
36
|
+
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
|
37
|
+
app.globals.FormatChanged = cmd.Flags().Changed("format")
|
|
38
|
+
app.globals.IdentityChanged = cmd.Flags().Changed("identity")
|
|
39
|
+
_, err := output.NormalizeFormat(app.globals.Format)
|
|
40
|
+
if err != nil {
|
|
41
|
+
return output.NewExitError("invalid_argument", 2, err.Error(), "Use --format json, pretty, ndjson, or table.")
|
|
42
|
+
}
|
|
43
|
+
return app.maybeCheckForUpdates(cmd)
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
rootCmd.PersistentFlags().StringVar(&app.globals.Format, "format", string(output.FormatJSON), "Output format: json | pretty | ndjson | table")
|
|
47
|
+
rootCmd.PersistentFlags().StringVar(&app.globals.JQ, "jq", "", "Apply a jq expression to the JSON envelope")
|
|
48
|
+
rootCmd.PersistentFlags().BoolVar(&app.globals.DryRun, "dry-run", false, "Render the execution plan without mutating state")
|
|
49
|
+
rootCmd.PersistentFlags().StringVar(&app.globals.Identity, "identity", "", "Select the active identity")
|
|
50
|
+
rootCmd.PersistentFlags().BoolVar(&app.globals.Verbose, "verbose", false, "Enable verbose output")
|
|
51
|
+
|
|
52
|
+
commandsByName := map[string]*cobra.Command{"": rootCmd}
|
|
53
|
+
specs := app.catalog.Specs()
|
|
54
|
+
sort.Slice(specs, func(i, j int) bool {
|
|
55
|
+
leftDepth := strings.Count(specs[i].Name, ".")
|
|
56
|
+
rightDepth := strings.Count(specs[j].Name, ".")
|
|
57
|
+
if leftDepth == rightDepth {
|
|
58
|
+
return specs[i].Name < specs[j].Name
|
|
59
|
+
}
|
|
60
|
+
return leftDepth < rightDepth
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
for _, spec := range specs {
|
|
64
|
+
command := app.commandFromSpec(spec)
|
|
65
|
+
parent := commandsByName[parentName(spec.Name)]
|
|
66
|
+
if parent == nil {
|
|
67
|
+
panic(fmt.Sprintf("missing parent command for %s", spec.Name))
|
|
68
|
+
}
|
|
69
|
+
parent.AddCommand(command)
|
|
70
|
+
commandsByName[strings.ToLower(spec.Name)] = command
|
|
71
|
+
}
|
|
72
|
+
if runtimeListener := commandsByName["runtime.listener"]; runtimeListener != nil {
|
|
73
|
+
runtimeListener.AddCommand(&cobra.Command{
|
|
74
|
+
Use: "run",
|
|
75
|
+
Short: "Run the websocket listener in the foreground",
|
|
76
|
+
Hidden: true,
|
|
77
|
+
RunE: app.runRuntimeListenerRun,
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
return rootCmd
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func parentName(name string) string {
|
|
84
|
+
trimmed := strings.ToLower(strings.TrimSpace(name))
|
|
85
|
+
index := strings.LastIndex(trimmed, ".")
|
|
86
|
+
if index < 0 {
|
|
87
|
+
return ""
|
|
88
|
+
}
|
|
89
|
+
return trimmed[:index]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func (a *App) commandFromSpec(spec cmdmeta.CommandSpec) *cobra.Command {
|
|
93
|
+
command := &cobra.Command{
|
|
94
|
+
Use: spec.Use,
|
|
95
|
+
Short: spec.Short,
|
|
96
|
+
Long: spec.Long,
|
|
97
|
+
Aliases: spec.Aliases,
|
|
98
|
+
Hidden: spec.Hidden,
|
|
99
|
+
}
|
|
100
|
+
for _, flag := range spec.Flags {
|
|
101
|
+
switch flag.Type {
|
|
102
|
+
case "string":
|
|
103
|
+
command.Flags().String(flag.Name, flag.Default, flag.Usage)
|
|
104
|
+
case "bool":
|
|
105
|
+
defaultValue := strings.EqualFold(flag.Default, "true")
|
|
106
|
+
command.Flags().Bool(flag.Name, defaultValue, flag.Usage)
|
|
107
|
+
case "int":
|
|
108
|
+
defaultValue := 0
|
|
109
|
+
if strings.TrimSpace(flag.Default) != "" {
|
|
110
|
+
if parsed, err := strconv.Atoi(flag.Default); err == nil {
|
|
111
|
+
defaultValue = parsed
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
command.Flags().Int(flag.Name, defaultValue, flag.Usage)
|
|
115
|
+
default:
|
|
116
|
+
command.Flags().String(flag.Name, flag.Default, flag.Usage)
|
|
117
|
+
}
|
|
118
|
+
if flag.Required {
|
|
119
|
+
_ = command.MarkFlagRequired(flag.Name)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if handler := a.handlerFor(spec); handler != nil {
|
|
123
|
+
command.RunE = handler
|
|
124
|
+
}
|
|
125
|
+
return command
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
func (a *App) handlerFor(spec cmdmeta.CommandSpec) func(*cobra.Command, []string) error {
|
|
129
|
+
switch spec.Handler {
|
|
130
|
+
case "init":
|
|
131
|
+
return a.runInit
|
|
132
|
+
case "status":
|
|
133
|
+
return a.runStatus
|
|
134
|
+
case "docs":
|
|
135
|
+
return a.runDocs
|
|
136
|
+
case "schema":
|
|
137
|
+
return a.runSchema
|
|
138
|
+
case "doctor":
|
|
139
|
+
return a.runDoctor
|
|
140
|
+
case "version":
|
|
141
|
+
return a.runVersion
|
|
142
|
+
case "upgrade":
|
|
143
|
+
return a.runUpgrade
|
|
144
|
+
case "config.show":
|
|
145
|
+
return a.runConfigShow
|
|
146
|
+
case "id.status":
|
|
147
|
+
return a.runIDStatus
|
|
148
|
+
case "id.create":
|
|
149
|
+
return a.runIDCreate
|
|
150
|
+
case "id.register":
|
|
151
|
+
return a.runIDRegister
|
|
152
|
+
case "id.bind":
|
|
153
|
+
return a.runIDBind
|
|
154
|
+
case "id.resolve":
|
|
155
|
+
return a.runIDResolve
|
|
156
|
+
case "id.recover":
|
|
157
|
+
return a.runIDRecover
|
|
158
|
+
case "id.list":
|
|
159
|
+
return a.runIDList
|
|
160
|
+
case "id.current":
|
|
161
|
+
return a.runIDCurrent
|
|
162
|
+
case "id.use":
|
|
163
|
+
return a.runIDUse
|
|
164
|
+
case "id.profile.get":
|
|
165
|
+
return a.runIDProfileGet
|
|
166
|
+
case "id.profile.set":
|
|
167
|
+
return a.runIDProfileSet
|
|
168
|
+
case "id.import-v1":
|
|
169
|
+
return a.runIDImportV1
|
|
170
|
+
case "msg.send":
|
|
171
|
+
return a.runMsgSend
|
|
172
|
+
case "msg.inbox":
|
|
173
|
+
return a.runMsgInbox
|
|
174
|
+
case "msg.history":
|
|
175
|
+
return a.runMsgHistory
|
|
176
|
+
case "msg.mark-read":
|
|
177
|
+
return a.runMsgMarkRead
|
|
178
|
+
case "group.create":
|
|
179
|
+
return a.runGroupCreate
|
|
180
|
+
case "group.show":
|
|
181
|
+
return a.runGroupShow
|
|
182
|
+
case "group.get":
|
|
183
|
+
return a.runGroupShow
|
|
184
|
+
case "group.join":
|
|
185
|
+
return a.runGroupJoin
|
|
186
|
+
case "group.add":
|
|
187
|
+
return a.runGroupAdd
|
|
188
|
+
case "group.kick":
|
|
189
|
+
return a.runGroupKick
|
|
190
|
+
case "group.remove":
|
|
191
|
+
return a.runGroupKick
|
|
192
|
+
case "group.leave":
|
|
193
|
+
return a.runGroupLeave
|
|
194
|
+
case "group.update":
|
|
195
|
+
return a.runGroupUpdate
|
|
196
|
+
case "group.members":
|
|
197
|
+
return a.runGroupMembers
|
|
198
|
+
case "group.messages":
|
|
199
|
+
return a.runGroupMessages
|
|
200
|
+
case "page.create":
|
|
201
|
+
return a.runPageCreate
|
|
202
|
+
case "page.list":
|
|
203
|
+
return a.runPageList
|
|
204
|
+
case "page.get":
|
|
205
|
+
return a.runPageGet
|
|
206
|
+
case "page.update":
|
|
207
|
+
return a.runPageUpdate
|
|
208
|
+
case "page.rename":
|
|
209
|
+
return a.runPageRename
|
|
210
|
+
case "page.delete":
|
|
211
|
+
return a.runPageDelete
|
|
212
|
+
case "runtime.status":
|
|
213
|
+
return a.runRuntimeStatus
|
|
214
|
+
case "runtime.setup":
|
|
215
|
+
return a.runRuntimeSetup
|
|
216
|
+
case "runtime.mode.get":
|
|
217
|
+
return a.runRuntimeModeGet
|
|
218
|
+
case "runtime.mode.set":
|
|
219
|
+
return a.runRuntimeModeSet
|
|
220
|
+
case "runtime.listener.status":
|
|
221
|
+
return a.runRuntimeListenerStatus
|
|
222
|
+
case "runtime.listener.install":
|
|
223
|
+
return a.runRuntimeListenerInstall
|
|
224
|
+
case "runtime.listener.start":
|
|
225
|
+
return a.runRuntimeListenerStart
|
|
226
|
+
case "runtime.listener.stop":
|
|
227
|
+
return a.runRuntimeListenerStop
|
|
228
|
+
case "runtime.listener.restart":
|
|
229
|
+
return a.runRuntimeListenerRestart
|
|
230
|
+
case "runtime.listener.uninstall":
|
|
231
|
+
return a.runRuntimeListenerUninstall
|
|
232
|
+
case "debug.db.query":
|
|
233
|
+
return a.runDebugDBQuery
|
|
234
|
+
case "debug.db.import-v1":
|
|
235
|
+
return a.runDebugDBImportV1
|
|
236
|
+
case "completion.bash":
|
|
237
|
+
return func(cmd *cobra.Command, args []string) error { return cmd.Root().GenBashCompletion(cmd.OutOrStdout()) }
|
|
238
|
+
case "completion.zsh":
|
|
239
|
+
return func(cmd *cobra.Command, args []string) error { return cmd.Root().GenZshCompletion(cmd.OutOrStdout()) }
|
|
240
|
+
case "completion.fish":
|
|
241
|
+
return func(cmd *cobra.Command, args []string) error {
|
|
242
|
+
return cmd.Root().GenFishCompletion(cmd.OutOrStdout(), true)
|
|
243
|
+
}
|
|
244
|
+
case "completion.powershell":
|
|
245
|
+
return func(cmd *cobra.Command, args []string) error {
|
|
246
|
+
return cmd.Root().GenPowerShellCompletionWithDesc(cmd.OutOrStdout())
|
|
247
|
+
}
|
|
248
|
+
case "stub":
|
|
249
|
+
return a.runStub
|
|
250
|
+
default:
|
|
251
|
+
return nil
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
func (a *App) runStatus(cmd *cobra.Command, args []string) error {
|
|
256
|
+
service, format, err := a.identityService()
|
|
257
|
+
if err != nil {
|
|
258
|
+
return output.NewExitError("internal_error", 1, err.Error(), "Check your local configuration and environment variables.")
|
|
259
|
+
}
|
|
260
|
+
resolved := service.Config()
|
|
261
|
+
result, err := service.Status()
|
|
262
|
+
if err != nil {
|
|
263
|
+
return a.identityExit(err, "Run `awiki-cli doctor` to inspect the local identity store.")
|
|
264
|
+
}
|
|
265
|
+
data := map[string]any{
|
|
266
|
+
"cli": map[string]any{
|
|
267
|
+
"phase": "phase1-shell",
|
|
268
|
+
"version": buildinfo.Current(),
|
|
269
|
+
},
|
|
270
|
+
"paths": resolved.Paths,
|
|
271
|
+
"state": result.Data,
|
|
272
|
+
"config": map[string]any{
|
|
273
|
+
"config_exists": resolved.ConfigExists,
|
|
274
|
+
"config_error": resolved.ConfigError,
|
|
275
|
+
"env_hits": resolved.EnvHits,
|
|
276
|
+
"sources": resolved.Sources,
|
|
277
|
+
},
|
|
278
|
+
}
|
|
279
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, result.Summary, result.Warnings, identityMetaFromResolved(resolved))
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
func (a *App) runDocs(cmd *cobra.Command, args []string) error {
|
|
283
|
+
resolved, err := a.resolveConfig()
|
|
284
|
+
if err != nil {
|
|
285
|
+
return output.NewExitError("internal_error", 1, err.Error(), "Check your local configuration and environment variables.")
|
|
286
|
+
}
|
|
287
|
+
format := normalizedFormat(resolved.OutputFormat)
|
|
288
|
+
if len(args) == 0 {
|
|
289
|
+
data := map[string]any{"topics": a.docs.All()}
|
|
290
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Available documentation topics", nil, identityMetaFromResolved(resolved))
|
|
291
|
+
}
|
|
292
|
+
if len(args) > 1 {
|
|
293
|
+
return output.NewExitError("invalid_argument", 2, "docs accepts at most one topic.", "Run `awiki-cli docs` without arguments to list topics.")
|
|
294
|
+
}
|
|
295
|
+
topic, ok := a.docs.Lookup(args[0])
|
|
296
|
+
if !ok {
|
|
297
|
+
return output.NewExitError("not_found", 5, fmt.Sprintf("Unknown docs topic %q", args[0]), "Run `awiki-cli docs` to list available topics.")
|
|
298
|
+
}
|
|
299
|
+
data := map[string]any{"topic": topic}
|
|
300
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, fmt.Sprintf("Documentation topic %s", topic.Name), nil, identityMetaFromResolved(resolved))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
func (a *App) runSchema(cmd *cobra.Command, args []string) error {
|
|
304
|
+
resolved, err := a.resolveConfig()
|
|
305
|
+
if err != nil {
|
|
306
|
+
return output.NewExitError("internal_error", 1, err.Error(), "Check your local configuration and environment variables.")
|
|
307
|
+
}
|
|
308
|
+
format := normalizedFormat(resolved.OutputFormat)
|
|
309
|
+
if len(args) == 0 {
|
|
310
|
+
data := map[string]any{
|
|
311
|
+
"commands": a.catalog.Specs(),
|
|
312
|
+
"phase": "phase1-shell",
|
|
313
|
+
}
|
|
314
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Static command contract", nil, identityMetaFromResolved(resolved))
|
|
315
|
+
}
|
|
316
|
+
lookupTarget := strings.Join(args, " ")
|
|
317
|
+
spec, ok := a.catalog.Lookup(lookupTarget)
|
|
318
|
+
if !ok {
|
|
319
|
+
return output.NewExitError("not_found", 5, fmt.Sprintf("Unknown command schema target %q", lookupTarget), "Use `awiki-cli schema` to list command contracts.")
|
|
320
|
+
}
|
|
321
|
+
data := map[string]any{
|
|
322
|
+
"command": spec,
|
|
323
|
+
"children": a.catalog.ChildrenOf(spec.Name),
|
|
324
|
+
}
|
|
325
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, fmt.Sprintf("Static contract for %s", spec.Name), nil, identityMetaFromResolved(resolved))
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
func (a *App) runDoctor(cmd *cobra.Command, args []string) error {
|
|
329
|
+
resolved, err := a.resolveConfig()
|
|
330
|
+
if err != nil {
|
|
331
|
+
return output.NewExitError("internal_error", 1, err.Error(), "Check your local configuration and environment variables.")
|
|
332
|
+
}
|
|
333
|
+
format := normalizedFormat(resolved.OutputFormat)
|
|
334
|
+
report := doccheck.Run(resolved)
|
|
335
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, report, report.Summary, nil, identityMetaFromResolved(resolved))
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
func (a *App) runVersion(cmd *cobra.Command, args []string) error {
|
|
339
|
+
resolved, err := a.resolveConfig()
|
|
340
|
+
if err != nil {
|
|
341
|
+
return output.NewExitError("internal_error", 1, err.Error(), "Check your local configuration and environment variables.")
|
|
342
|
+
}
|
|
343
|
+
format := normalizedFormat(resolved.OutputFormat)
|
|
344
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, buildinfo.Current(), "Build information", nil, identityMetaFromResolved(resolved))
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
func (a *App) runConfigShow(cmd *cobra.Command, args []string) error {
|
|
348
|
+
service, format, err := a.identityService()
|
|
349
|
+
if err != nil {
|
|
350
|
+
return output.NewExitError("internal_error", 1, err.Error(), "Check your local configuration and environment variables.")
|
|
351
|
+
}
|
|
352
|
+
resolved := service.Config()
|
|
353
|
+
current, _ := service.Manager().Current()
|
|
354
|
+
legacy, _ := service.Manager().ScanLegacy()
|
|
355
|
+
data := appconfig.Snapshot(resolved)
|
|
356
|
+
database := map[string]any{
|
|
357
|
+
"database_file": resolved.Paths.DatabaseFile,
|
|
358
|
+
"exists": fileExists(resolved.Paths.DatabaseFile),
|
|
359
|
+
}
|
|
360
|
+
if database["exists"] == true {
|
|
361
|
+
if db, err := store.OpenReadOnly(resolved.Paths.DatabaseFile); err == nil {
|
|
362
|
+
defer db.Close()
|
|
363
|
+
if version, err := store.CurrentSchemaVersion(db); err == nil {
|
|
364
|
+
database["schema_version"] = version
|
|
365
|
+
database["target_schema_version"] = store.SchemaVersion
|
|
366
|
+
} else {
|
|
367
|
+
database["schema_error"] = err.Error()
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
database["open_error"] = err.Error()
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
data["identity_store"] = map[string]any{
|
|
374
|
+
"identity_dir": resolved.Paths.IdentityDir,
|
|
375
|
+
"index_file": filepathJoin(resolved.Paths.IdentityDir, "index.json"),
|
|
376
|
+
"default_identity": current,
|
|
377
|
+
"legacy_scan": legacy,
|
|
378
|
+
}
|
|
379
|
+
data["database"] = database
|
|
380
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Resolved configuration", nil, identityMetaFromResolved(resolved))
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
func (a *App) runStub(cmd *cobra.Command, args []string) error {
|
|
384
|
+
spec, ok := a.catalog.Lookup(strings.TrimPrefix(cmd.CommandPath(), "awiki-cli "))
|
|
385
|
+
if !ok {
|
|
386
|
+
return output.NewExitError("internal_error", 1, "Command metadata is missing.", "Run `awiki-cli schema` to inspect the current catalog.")
|
|
387
|
+
}
|
|
388
|
+
hint := fmt.Sprintf("%s is planned for %s. Use `awiki-cli schema %s` to inspect the frozen contract.", cmd.CommandPath(), strings.ToUpper(spec.Phase), spec.Name)
|
|
389
|
+
return output.NewExitError("internal_error", 1, fmt.Sprintf("%s is not implemented yet.", cmd.CommandPath()), hint)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
func identityMetaFromResolved(resolved *appconfig.Resolved) *output.IdentityMeta {
|
|
393
|
+
if resolved == nil || strings.TrimSpace(resolved.ActiveIdentity) == "" {
|
|
394
|
+
return nil
|
|
395
|
+
}
|
|
396
|
+
return &output.IdentityMeta{Name: resolved.ActiveIdentity}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
func fileExists(path string) bool {
|
|
400
|
+
_, err := os.Stat(path)
|
|
401
|
+
return err == nil
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
func filepathJoin(parts ...string) string {
|
|
405
|
+
return filepath.Join(parts...)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
func normalizedFormat(raw string) output.Format {
|
|
409
|
+
format, err := output.NormalizeFormat(raw)
|
|
410
|
+
if err != nil {
|
|
411
|
+
return output.FormatJSON
|
|
412
|
+
}
|
|
413
|
+
return format
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// maybeCheckForUpdates performs a best-effort version policy check before
|
|
417
|
+
// running most commands. It is intentionally soft-fail: network / metadata
|
|
418
|
+
// errors do not break the CLI, but when we have a clear "below minSupported"
|
|
419
|
+
// signal we block remote-affecting commands.
|
|
420
|
+
func (a *App) maybeCheckForUpdates(cmd *cobra.Command) error {
|
|
421
|
+
// Some commands must always be available regardless of version policy.
|
|
422
|
+
if isUpdateExemptCommand(cmd) {
|
|
423
|
+
return nil
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Resolve config to get update-related knobs and cache paths.
|
|
427
|
+
resolved, err := a.resolveConfig()
|
|
428
|
+
if err != nil {
|
|
429
|
+
// Config errors are surfaced by individual commands; do not double-fail here.
|
|
430
|
+
return nil
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
decision, err := update.Check(resolved)
|
|
434
|
+
if err != nil {
|
|
435
|
+
// Best-effort: log to stderr in verbose mode, but never break the command.
|
|
436
|
+
if a.globals.Verbose {
|
|
437
|
+
fmt.Fprintf(os.Stderr, "[awiki-cli] update check failed: %v\n", err)
|
|
438
|
+
}
|
|
439
|
+
return nil
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if decision.Blocked {
|
|
443
|
+
summary := fmt.Sprintf(
|
|
444
|
+
"awiki-cli %s is no longer supported (minimum supported version is %s).",
|
|
445
|
+
decision.CurrentVersion,
|
|
446
|
+
decision.MinSupportedVersion,
|
|
447
|
+
)
|
|
448
|
+
hint := "Please upgrade awiki-cli before running this command. Run `awiki-cli upgrade` or `npm install -g @awiki/cli@latest`."
|
|
449
|
+
return output.NewExitError("version_unsupported", 3, summary, hint)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if decision.HasNewerVersion && !decision.StrictDisabled && !decision.DevBuild {
|
|
453
|
+
a.updateWarning = fmt.Sprintf(
|
|
454
|
+
"A newer awiki-cli version (%s) is available; you are running %s. Run `awiki-cli upgrade` for details.",
|
|
455
|
+
decision.LatestVersion,
|
|
456
|
+
decision.CurrentVersion,
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
return nil
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
func isUpdateExemptCommand(cmd *cobra.Command) bool {
|
|
463
|
+
if cmd == nil {
|
|
464
|
+
return false
|
|
465
|
+
}
|
|
466
|
+
path := strings.TrimSpace(cmd.CommandPath())
|
|
467
|
+
if path == "" {
|
|
468
|
+
return false
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Allow basic local inspection / help commands even when the binary is old.
|
|
472
|
+
exempt := []string{
|
|
473
|
+
"awiki-cli help",
|
|
474
|
+
"awiki-cli version",
|
|
475
|
+
"awiki-cli upgrade",
|
|
476
|
+
"awiki-cli init",
|
|
477
|
+
"awiki-cli docs",
|
|
478
|
+
"awiki-cli schema",
|
|
479
|
+
"awiki-cli config show",
|
|
480
|
+
"awiki-cli doctor",
|
|
481
|
+
"awiki-cli completion",
|
|
482
|
+
"awiki-cli completion bash",
|
|
483
|
+
"awiki-cli completion zsh",
|
|
484
|
+
"awiki-cli completion fish",
|
|
485
|
+
"awiki-cli completion powershell",
|
|
486
|
+
}
|
|
487
|
+
for _, allowed := range exempt {
|
|
488
|
+
if strings.EqualFold(path, allowed) {
|
|
489
|
+
return true
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Cobra may construct aliases / nested paths; treat any "help ..." as exempt.
|
|
494
|
+
if strings.HasPrefix(strings.ToLower(path), "awiki-cli help ") {
|
|
495
|
+
return true
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return false
|
|
499
|
+
}
|