@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,232 @@
|
|
|
1
|
+
package cli
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"fmt"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
appconfig "github.com/agentconnect/awiki-cli/internal/config"
|
|
10
|
+
"github.com/agentconnect/awiki-cli/internal/identity"
|
|
11
|
+
"github.com/agentconnect/awiki-cli/internal/output"
|
|
12
|
+
runtimecfg "github.com/agentconnect/awiki-cli/internal/runtime"
|
|
13
|
+
listenerrt "github.com/agentconnect/awiki-cli/internal/runtime/listener"
|
|
14
|
+
"github.com/agentconnect/awiki-cli/internal/store"
|
|
15
|
+
"github.com/spf13/cobra"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
func runtimeFormat(resolved *appconfig.Resolved) string {
|
|
19
|
+
if resolved == nil || strings.TrimSpace(resolved.OutputFormat) == "" {
|
|
20
|
+
return "json"
|
|
21
|
+
}
|
|
22
|
+
return resolved.OutputFormat
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func (a *App) runtimeExit(err error, hint string) error {
|
|
26
|
+
if err == nil {
|
|
27
|
+
return nil
|
|
28
|
+
}
|
|
29
|
+
switch {
|
|
30
|
+
case errors.Is(err, identity.ErrUserRegistrationRequired):
|
|
31
|
+
return output.NewExitError("identity_required", 3, err.Error(), "Complete user setup with `awiki-cli id register --handle <handle> ...` or recover an existing handle before starting realtime runtime.")
|
|
32
|
+
case strings.Contains(err.Error(), "must be websocket"), strings.Contains(err.Error(), "unsupported mode"), strings.Contains(err.Error(), "runtime mode"):
|
|
33
|
+
return output.NewExitError("invalid_argument", 2, err.Error(), hint)
|
|
34
|
+
default:
|
|
35
|
+
return output.NewExitError("internal_error", 1, err.Error(), hint)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func (a *App) runRuntimeStatus(cmd *cobra.Command, args []string) error {
|
|
40
|
+
resolved, err := a.resolveConfig()
|
|
41
|
+
if err != nil {
|
|
42
|
+
return a.runtimeExit(err, "Run `awiki-cli doctor` to inspect runtime configuration.")
|
|
43
|
+
}
|
|
44
|
+
format := normalizedFormat(runtimeFormat(resolved))
|
|
45
|
+
status, err := listenerrt.StatusFor(resolved)
|
|
46
|
+
if err != nil {
|
|
47
|
+
return a.runtimeExit(err, "Run `awiki-cli doctor` to inspect runtime configuration.")
|
|
48
|
+
}
|
|
49
|
+
data := map[string]any{
|
|
50
|
+
"runtime": runtimecfg.Resolve(resolved),
|
|
51
|
+
"listener": status,
|
|
52
|
+
}
|
|
53
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Runtime status loaded", status.Warnings, identityMetaFromResolved(resolved))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func (a *App) runRuntimeSetup(cmd *cobra.Command, args []string) error {
|
|
57
|
+
mode, _ := cmd.Flags().GetString("mode")
|
|
58
|
+
resolved, err := a.resolveConfig()
|
|
59
|
+
if err != nil {
|
|
60
|
+
return a.runtimeExit(err, "Run `awiki-cli doctor` to inspect runtime configuration.")
|
|
61
|
+
}
|
|
62
|
+
if strings.TrimSpace(mode) == "" {
|
|
63
|
+
mode = resolved.RuntimeMode
|
|
64
|
+
}
|
|
65
|
+
if mode != runtimecfg.ModeHTTP && mode != runtimecfg.ModeWebSocket {
|
|
66
|
+
return output.NewExitError("invalid_argument", 2, "runtime setup requires --mode http|websocket.", "Use runtime setup --mode websocket or runtime setup --mode http.")
|
|
67
|
+
}
|
|
68
|
+
format := normalizedFormat(runtimeFormat(resolved))
|
|
69
|
+
if a.globals.DryRun {
|
|
70
|
+
data := map[string]any{"plan": map[string]any{
|
|
71
|
+
"action": "runtime_setup",
|
|
72
|
+
"mode": mode,
|
|
73
|
+
"state_dir": resolved.Paths.StateDir,
|
|
74
|
+
"database_file": resolved.Paths.DatabaseFile,
|
|
75
|
+
"writes": []string{resolved.Paths.ConfigFile, resolved.Paths.DatabaseFile},
|
|
76
|
+
}}
|
|
77
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Dry run: runtime setup planned", nil, identityMetaFromResolved(resolved))
|
|
78
|
+
}
|
|
79
|
+
if err := appconfig.UpdateRuntimeSettings(resolved.Paths, mode, resolved.RuntimeSocketPath); err != nil {
|
|
80
|
+
return a.runtimeExit(err, "Check write permissions for config.json under the awiki-cli work directory.")
|
|
81
|
+
}
|
|
82
|
+
db, err := store.Open(resolved.Paths)
|
|
83
|
+
if err != nil {
|
|
84
|
+
return a.runtimeExit(err, "Check write permissions for the local sqlite database.")
|
|
85
|
+
}
|
|
86
|
+
defer db.Close()
|
|
87
|
+
if err := store.EnsureSchema(context.Background(), db); err != nil {
|
|
88
|
+
return a.runtimeExit(err, "Initialize the local sqlite schema before enabling runtime.")
|
|
89
|
+
}
|
|
90
|
+
data := map[string]any{
|
|
91
|
+
"action": "runtime_setup",
|
|
92
|
+
"mode": mode,
|
|
93
|
+
"paths": resolved.Paths,
|
|
94
|
+
}
|
|
95
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Runtime setup completed", nil, identityMetaFromResolved(resolved))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func (a *App) runRuntimeModeGet(cmd *cobra.Command, args []string) error {
|
|
99
|
+
resolved, err := a.resolveConfig()
|
|
100
|
+
if err != nil {
|
|
101
|
+
return a.runtimeExit(err, "Run `awiki-cli doctor` to inspect runtime configuration.")
|
|
102
|
+
}
|
|
103
|
+
format := normalizedFormat(runtimeFormat(resolved))
|
|
104
|
+
data := map[string]any{"runtime": runtimecfg.Resolve(resolved)}
|
|
105
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Runtime mode loaded", nil, identityMetaFromResolved(resolved))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func (a *App) runRuntimeModeSet(cmd *cobra.Command, args []string) error {
|
|
109
|
+
if len(args) != 1 {
|
|
110
|
+
return output.NewExitError("invalid_argument", 2, "runtime mode set requires exactly one mode.", "Usage: awiki-cli runtime mode set <http|websocket>")
|
|
111
|
+
}
|
|
112
|
+
mode := strings.ToLower(strings.TrimSpace(args[0]))
|
|
113
|
+
if mode != runtimecfg.ModeHTTP && mode != runtimecfg.ModeWebSocket {
|
|
114
|
+
return output.NewExitError("invalid_argument", 2, "unsupported runtime mode", "Use runtime mode set http or runtime mode set websocket.")
|
|
115
|
+
}
|
|
116
|
+
resolved, err := a.resolveConfig()
|
|
117
|
+
if err != nil {
|
|
118
|
+
return a.runtimeExit(err, "Run `awiki-cli doctor` to inspect runtime configuration.")
|
|
119
|
+
}
|
|
120
|
+
format := normalizedFormat(runtimeFormat(resolved))
|
|
121
|
+
if a.globals.DryRun {
|
|
122
|
+
data := map[string]any{"plan": map[string]any{
|
|
123
|
+
"action": "runtime_mode_set",
|
|
124
|
+
"mode": mode,
|
|
125
|
+
"config_file": resolved.Paths.ConfigFile,
|
|
126
|
+
}}
|
|
127
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Dry run: runtime mode change planned", nil, identityMetaFromResolved(resolved))
|
|
128
|
+
}
|
|
129
|
+
if err := appconfig.UpdateRuntimeSettings(resolved.Paths, mode, resolved.RuntimeSocketPath); err != nil {
|
|
130
|
+
return a.runtimeExit(err, "Check write permissions for config.json under the awiki-cli work directory.")
|
|
131
|
+
}
|
|
132
|
+
data := map[string]any{
|
|
133
|
+
"action": "runtime_mode_set",
|
|
134
|
+
"mode": mode,
|
|
135
|
+
}
|
|
136
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, fmt.Sprintf("Runtime mode set to %s", mode), nil, identityMetaFromResolved(resolved))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
func (a *App) runRuntimeListenerStatus(cmd *cobra.Command, args []string) error {
|
|
140
|
+
resolved, err := a.resolveConfig()
|
|
141
|
+
if err != nil {
|
|
142
|
+
return a.runtimeExit(err, "Run `awiki-cli doctor` to inspect runtime configuration.")
|
|
143
|
+
}
|
|
144
|
+
format := normalizedFormat(runtimeFormat(resolved))
|
|
145
|
+
status, err := listenerrt.StatusFor(resolved)
|
|
146
|
+
if err != nil {
|
|
147
|
+
return a.runtimeExit(err, "Run `awiki-cli doctor` to inspect runtime configuration.")
|
|
148
|
+
}
|
|
149
|
+
data := map[string]any{"listener": status}
|
|
150
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Listener status loaded", status.Warnings, identityMetaFromResolved(resolved))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
func (a *App) runRuntimeListenerStart(cmd *cobra.Command, args []string) error {
|
|
154
|
+
resolved, err := a.resolveConfig()
|
|
155
|
+
if err != nil {
|
|
156
|
+
return a.runtimeExit(err, "Run `awiki-cli doctor` to inspect runtime configuration.")
|
|
157
|
+
}
|
|
158
|
+
format := normalizedFormat(runtimeFormat(resolved))
|
|
159
|
+
if a.globals.DryRun {
|
|
160
|
+
data := map[string]any{"plan": map[string]any{
|
|
161
|
+
"action": "listener_start",
|
|
162
|
+
"mode": resolved.RuntimeMode,
|
|
163
|
+
"socket_path": resolved.RuntimeSocketPath,
|
|
164
|
+
}}
|
|
165
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Dry run: listener start planned", nil, identityMetaFromResolved(resolved))
|
|
166
|
+
}
|
|
167
|
+
status, err := listenerrt.Start(resolved)
|
|
168
|
+
if err != nil {
|
|
169
|
+
return a.runtimeExit(err, "Set runtime.mode to websocket before starting the listener.")
|
|
170
|
+
}
|
|
171
|
+
data := map[string]any{"listener": status}
|
|
172
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Listener started", status.Warnings, identityMetaFromResolved(resolved))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
func (a *App) runRuntimeListenerStop(cmd *cobra.Command, args []string) error {
|
|
176
|
+
resolved, err := a.resolveConfig()
|
|
177
|
+
if err != nil {
|
|
178
|
+
return a.runtimeExit(err, "Run `awiki-cli doctor` to inspect runtime configuration.")
|
|
179
|
+
}
|
|
180
|
+
format := normalizedFormat(runtimeFormat(resolved))
|
|
181
|
+
if a.globals.DryRun {
|
|
182
|
+
data := map[string]any{"plan": map[string]any{
|
|
183
|
+
"action": "listener_stop",
|
|
184
|
+
"pid": "from_pid_file",
|
|
185
|
+
}}
|
|
186
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Dry run: listener stop planned", nil, identityMetaFromResolved(resolved))
|
|
187
|
+
}
|
|
188
|
+
status, err := listenerrt.Stop(resolved)
|
|
189
|
+
if err != nil {
|
|
190
|
+
return a.runtimeExit(err, "Check the pid file and socket path under the awiki-cli tmp/runtime directory.")
|
|
191
|
+
}
|
|
192
|
+
data := map[string]any{"listener": status}
|
|
193
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Listener stopped", status.Warnings, identityMetaFromResolved(resolved))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
func (a *App) runRuntimeListenerRestart(cmd *cobra.Command, args []string) error {
|
|
197
|
+
resolved, err := a.resolveConfig()
|
|
198
|
+
if err != nil {
|
|
199
|
+
return a.runtimeExit(err, "Run `awiki-cli doctor` to inspect runtime configuration.")
|
|
200
|
+
}
|
|
201
|
+
format := normalizedFormat(runtimeFormat(resolved))
|
|
202
|
+
if a.globals.DryRun {
|
|
203
|
+
data := map[string]any{"plan": map[string]any{
|
|
204
|
+
"action": "listener_restart",
|
|
205
|
+
"mode": resolved.RuntimeMode,
|
|
206
|
+
"socket_path": resolved.RuntimeSocketPath,
|
|
207
|
+
}}
|
|
208
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Dry run: listener restart planned", nil, identityMetaFromResolved(resolved))
|
|
209
|
+
}
|
|
210
|
+
status, err := listenerrt.Restart(resolved)
|
|
211
|
+
if err != nil {
|
|
212
|
+
return a.runtimeExit(err, "Set runtime.mode to websocket before restarting the listener.")
|
|
213
|
+
}
|
|
214
|
+
data := map[string]any{"listener": status}
|
|
215
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Listener restarted", status.Warnings, identityMetaFromResolved(resolved))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
func (a *App) runRuntimeListenerInstall(cmd *cobra.Command, args []string) error {
|
|
219
|
+
return a.runRuntimeListenerStart(cmd, args)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
func (a *App) runRuntimeListenerUninstall(cmd *cobra.Command, args []string) error {
|
|
223
|
+
return a.runRuntimeListenerStop(cmd, args)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
func (a *App) runRuntimeListenerRun(cmd *cobra.Command, args []string) error {
|
|
227
|
+
resolved, err := a.resolveConfig()
|
|
228
|
+
if err != nil {
|
|
229
|
+
return err
|
|
230
|
+
}
|
|
231
|
+
return listenerrt.RunForeground(resolved)
|
|
232
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
package cli
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
|
|
6
|
+
"github.com/agentconnect/awiki-cli/internal/output"
|
|
7
|
+
"github.com/agentconnect/awiki-cli/internal/update"
|
|
8
|
+
"github.com/spf13/cobra"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func (a *App) runUpgrade(cmd *cobra.Command, args []string) error {
|
|
12
|
+
resolved, err := a.resolveConfig()
|
|
13
|
+
if err != nil {
|
|
14
|
+
return output.NewExitError("internal_error", 1, err.Error(), "Check your local configuration and environment variables.")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
decision, err := update.Check(resolved)
|
|
18
|
+
if err != nil {
|
|
19
|
+
return output.NewExitError(
|
|
20
|
+
"internal_error",
|
|
21
|
+
1,
|
|
22
|
+
err.Error(),
|
|
23
|
+
"Failed to check npm metadata for awiki-cli. Please ensure you have network access and try again.",
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
format := normalizedFormat(resolved.OutputFormat)
|
|
28
|
+
|
|
29
|
+
data := map[string]any{
|
|
30
|
+
"current_version": decision.CurrentVersion,
|
|
31
|
+
"latest_version": decision.LatestVersion,
|
|
32
|
+
"min_supported_version": decision.MinSupportedVersion,
|
|
33
|
+
"strict_disabled": decision.StrictDisabled,
|
|
34
|
+
"dev_build": decision.DevBuild,
|
|
35
|
+
"has_newer_version": decision.HasNewerVersion,
|
|
36
|
+
"blocked": decision.Blocked,
|
|
37
|
+
"upgrade_hint": "To upgrade awiki-cli, run: npm install -g @awiki/cli@latest",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
summary := "awiki-cli is up to date"
|
|
41
|
+
var warnings []string
|
|
42
|
+
|
|
43
|
+
if decision.Blocked {
|
|
44
|
+
summary = "awiki-cli version is below the minimum supported version"
|
|
45
|
+
warnings = append(warnings, fmt.Sprintf(
|
|
46
|
+
"awiki-cli %s is below the minimum supported version %s. Upgrading is required before using remote APIs.",
|
|
47
|
+
decision.CurrentVersion,
|
|
48
|
+
decision.MinSupportedVersion,
|
|
49
|
+
))
|
|
50
|
+
} else if decision.HasNewerVersion {
|
|
51
|
+
if decision.LatestVersion != "" {
|
|
52
|
+
summary = fmt.Sprintf("A newer awiki-cli version (%s) is available", decision.LatestVersion)
|
|
53
|
+
} else {
|
|
54
|
+
summary = "A newer awiki-cli version may be available"
|
|
55
|
+
}
|
|
56
|
+
warnings = append(warnings, "Upgrading is recommended to stay on a supported version.")
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, summary, warnings, identityMetaFromResolved(resolved))
|
|
60
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
package cmdmeta
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"sort"
|
|
6
|
+
"strings"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
type FlagSpec struct {
|
|
10
|
+
Name string `json:"name"`
|
|
11
|
+
Type string `json:"type"`
|
|
12
|
+
Usage string `json:"usage"`
|
|
13
|
+
Default string `json:"default,omitempty"`
|
|
14
|
+
Required bool `json:"required,omitempty"`
|
|
15
|
+
Choices []string `json:"choices,omitempty"`
|
|
16
|
+
Deprecated bool `json:"deprecated,omitempty"`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type CommandSpec struct {
|
|
20
|
+
Name string `json:"name"`
|
|
21
|
+
Use string `json:"use"`
|
|
22
|
+
Short string `json:"short"`
|
|
23
|
+
Long string `json:"long,omitempty"`
|
|
24
|
+
Aliases []string `json:"aliases,omitempty"`
|
|
25
|
+
Phase string `json:"phase"`
|
|
26
|
+
Hidden bool `json:"hidden,omitempty"`
|
|
27
|
+
Implemented bool `json:"implemented"`
|
|
28
|
+
Handler string `json:"handler,omitempty"`
|
|
29
|
+
SideEffect bool `json:"side_effect"`
|
|
30
|
+
Outputs []string `json:"outputs,omitempty"`
|
|
31
|
+
Flags []FlagSpec `json:"flags,omitempty"`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type Catalog struct {
|
|
35
|
+
specs []CommandSpec
|
|
36
|
+
index map[string]CommandSpec
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
func NewCatalog() *Catalog {
|
|
40
|
+
specs := defaultSpecs()
|
|
41
|
+
index := make(map[string]CommandSpec, len(specs))
|
|
42
|
+
for _, spec := range specs {
|
|
43
|
+
index[normalizeName(spec.Name)] = spec
|
|
44
|
+
}
|
|
45
|
+
return &Catalog{specs: specs, index: index}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func (c *Catalog) Specs() []CommandSpec {
|
|
49
|
+
if c == nil {
|
|
50
|
+
return nil
|
|
51
|
+
}
|
|
52
|
+
return append([]CommandSpec(nil), c.specs...)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func (c *Catalog) Lookup(raw string) (CommandSpec, bool) {
|
|
56
|
+
if c == nil {
|
|
57
|
+
return CommandSpec{}, false
|
|
58
|
+
}
|
|
59
|
+
name := normalizeName(raw)
|
|
60
|
+
spec, ok := c.index[name]
|
|
61
|
+
return spec, ok
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func (c *Catalog) MustLookup(raw string) CommandSpec {
|
|
65
|
+
spec, ok := c.Lookup(raw)
|
|
66
|
+
if !ok {
|
|
67
|
+
panic(fmt.Sprintf("command metadata not found: %s", raw))
|
|
68
|
+
}
|
|
69
|
+
return spec
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
func (c *Catalog) ChildrenOf(parent string) []CommandSpec {
|
|
73
|
+
if c == nil {
|
|
74
|
+
return nil
|
|
75
|
+
}
|
|
76
|
+
needle := normalizeName(parent)
|
|
77
|
+
children := make([]CommandSpec, 0)
|
|
78
|
+
for _, spec := range c.specs {
|
|
79
|
+
if parentName(spec.Name) == needle {
|
|
80
|
+
children = append(children, spec)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
sort.Slice(children, func(i, j int) bool { return children[i].Name < children[j].Name })
|
|
84
|
+
return children
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func normalizeName(raw string) string {
|
|
88
|
+
trimmed := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(raw), "awiki-cli"))
|
|
89
|
+
trimmed = strings.TrimSpace(trimmed)
|
|
90
|
+
trimmed = strings.ReplaceAll(trimmed, " ", ".")
|
|
91
|
+
trimmed = strings.Trim(trimmed, ".")
|
|
92
|
+
return strings.ToLower(trimmed)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func parentName(name string) string {
|
|
96
|
+
normalized := normalizeName(name)
|
|
97
|
+
last := strings.LastIndex(normalized, ".")
|
|
98
|
+
if last < 0 {
|
|
99
|
+
return ""
|
|
100
|
+
}
|
|
101
|
+
return normalized[:last]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func defaultSpecs() []CommandSpec {
|
|
105
|
+
return []CommandSpec{
|
|
106
|
+
{Name: "status", Use: "status", Short: "Show the current phase-1 CLI status", Phase: "phase1", Implemented: true, Handler: "status", Outputs: []string{"json", "pretty", "table"}},
|
|
107
|
+
{Name: "docs", Use: "docs [topic]", Short: "Show built-in documentation topics", Phase: "phase1", Implemented: true, Handler: "docs", Outputs: []string{"json", "pretty", "table"}},
|
|
108
|
+
{Name: "schema", Use: "schema [command]", Short: "Show the static command contract", Phase: "phase1", Implemented: true, Handler: "schema", Outputs: []string{"json", "pretty", "table"}},
|
|
109
|
+
{Name: "doctor", Use: "doctor", Short: "Run baseline environment and storage diagnostics", Phase: "phase1", Implemented: true, Handler: "doctor", Outputs: []string{"json", "pretty", "table"}},
|
|
110
|
+
{Name: "version", Use: "version", Short: "Show build information", Phase: "phase1", Implemented: true, Handler: "version", Outputs: []string{"json", "pretty", "table"}},
|
|
111
|
+
{Name: "upgrade", Use: "upgrade", Short: "Check for newer awiki-cli versions and show upgrade hints", Phase: "phase2", Implemented: true, Handler: "upgrade", Outputs: []string{"json", "pretty", "table"}},
|
|
112
|
+
{Name: "init", Use: "init", Short: "Initialize the awiki-cli workdir and config.json", Phase: "phase1", Implemented: true, Handler: "init", SideEffect: true, Outputs: []string{"json", "pretty", "table"}},
|
|
113
|
+
{Name: "completion", Use: "completion", Short: "Generate shell completion scripts", Phase: "phase1", Implemented: true},
|
|
114
|
+
{Name: "completion.bash", Use: "bash", Short: "Generate Bash completion", Phase: "phase1", Implemented: true, Handler: "completion.bash"},
|
|
115
|
+
{Name: "completion.zsh", Use: "zsh", Short: "Generate Zsh completion", Phase: "phase1", Implemented: true, Handler: "completion.zsh"},
|
|
116
|
+
{Name: "completion.fish", Use: "fish", Short: "Generate Fish completion", Phase: "phase1", Implemented: true, Handler: "completion.fish"},
|
|
117
|
+
{Name: "completion.powershell", Use: "powershell", Short: "Generate PowerShell completion", Phase: "phase1", Implemented: true, Handler: "completion.powershell"},
|
|
118
|
+
{Name: "config", Use: "config", Short: "Inspect resolved CLI configuration", Phase: "phase1", Implemented: true},
|
|
119
|
+
{Name: "config.show", Use: "show", Short: "Show resolved configuration values", Phase: "phase1", Implemented: true, Handler: "config.show", Outputs: []string{"json", "pretty", "table"}},
|
|
120
|
+
{Name: "id", Use: "id", Short: "Identity lifecycle commands", Phase: "phase1", Implemented: true},
|
|
121
|
+
{Name: "id.status", Use: "status", Short: "Show identity status", Phase: "phase2", Implemented: true, Handler: "id.status", Outputs: []string{"json", "pretty"}},
|
|
122
|
+
{Name: "id.create", Use: "create", Short: "Create local DID material for bootstrap or migration", Phase: "phase2", Hidden: true, Implemented: true, Handler: "id.create", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "name", Type: "string", Usage: "Identity display name", Required: true}, {Name: "identity", Type: "string", Usage: "Identity alias override"}}},
|
|
123
|
+
{Name: "id.register", Use: "register", Short: "Register a handle-backed user identity", Phase: "phase3", Implemented: true, Handler: "id.register", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "handle", Type: "string", Usage: "Handle local part", Required: true}, {Name: "phone", Type: "string", Usage: "Phone number for registration"}, {Name: "email", Type: "string", Usage: "Email address for registration"}, {Name: "otp", Type: "string", Usage: "Verification code"}, {Name: "invite-code", Type: "string", Usage: "Invite code if required"}, {Name: "wait", Type: "bool", Usage: "Wait for email verification before completing registration"}}},
|
|
124
|
+
{Name: "id.bind", Use: "bind", Short: "Bind phone or email to the current identity", Phase: "phase3", Implemented: true, Handler: "id.bind", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "phone", Type: "string", Usage: "Phone number to bind"}, {Name: "email", Type: "string", Usage: "Email address to bind"}, {Name: "otp", Type: "string", Usage: "Verification code"}, {Name: "wait", Type: "bool", Usage: "Wait for email verification before completing the bind"}}},
|
|
125
|
+
{Name: "id.resolve", Use: "resolve", Short: "Resolve a DID or handle", Phase: "phase3", Implemented: true, Handler: "id.resolve", Outputs: []string{"json", "pretty", "table"}, Flags: []FlagSpec{{Name: "handle", Type: "string", Usage: "Handle to resolve"}, {Name: "did", Type: "string", Usage: "DID to resolve"}}},
|
|
126
|
+
{Name: "id.recover", Use: "recover", Short: "Recover a handle with phone verification", Phase: "phase3", Implemented: true, Handler: "id.recover", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "handle", Type: "string", Usage: "Handle local part", Required: true}, {Name: "phone", Type: "string", Usage: "Recovery phone number", Required: true}, {Name: "otp", Type: "string", Usage: "Verification code", Required: true}}},
|
|
127
|
+
{Name: "id.list", Use: "list", Short: "List local identities", Phase: "phase2", Implemented: true, Handler: "id.list", Outputs: []string{"json", "pretty", "table"}},
|
|
128
|
+
{Name: "id.current", Use: "current", Short: "Show the default identity", Phase: "phase2", Implemented: true, Handler: "id.current", Outputs: []string{"json", "pretty", "table"}},
|
|
129
|
+
{Name: "id.use", Use: "use <identity>", Short: "Switch the default identity", Phase: "phase2", Implemented: true, Handler: "id.use", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
130
|
+
{Name: "id.profile", Use: "profile", Short: "Read or update DID profile data", Phase: "phase3", Implemented: true},
|
|
131
|
+
{Name: "id.profile.get", Use: "get", Short: "Get DID profile data", Phase: "phase3", Implemented: true, Handler: "id.profile.get", Outputs: []string{"json", "pretty", "table"}, Flags: []FlagSpec{{Name: "self", Type: "bool", Usage: "Read the active identity profile"}, {Name: "handle", Type: "string", Usage: "Read a profile by handle"}, {Name: "did", Type: "string", Usage: "Read a profile by DID"}}},
|
|
132
|
+
{Name: "id.profile.set", Use: "set", Short: "Update DID profile data", Phase: "phase3", Implemented: true, Handler: "id.profile.set", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "display-name", Type: "string", Usage: "Profile display name"}, {Name: "bio", Type: "string", Usage: "Profile bio"}, {Name: "tags", Type: "string", Usage: "Comma-separated tags"}, {Name: "markdown", Type: "string", Usage: "Inline markdown body"}, {Name: "markdown-file", Type: "string", Usage: "Markdown file path"}}},
|
|
133
|
+
{Name: "id.import-v1", Use: "import-v1", Short: "Import credentials from the v1 awiki-agent-id-message layout", Phase: "phase2", Implemented: true, Handler: "id.import-v1", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "name", Type: "string", Usage: "Import one legacy identity by name"}, {Name: "all", Type: "bool", Usage: "Import all detected legacy identities"}}},
|
|
134
|
+
{Name: "msg", Use: "msg", Short: "Messaging commands", Phase: "phase1", Implemented: true},
|
|
135
|
+
{Name: "msg.send", Use: "send", Short: "Send a direct or group message", Phase: "phase5", Implemented: true, Handler: "msg.send", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "to", Type: "string", Usage: "Direct message target"}, {Name: "group", Type: "string", Usage: "Group target"}, {Name: "text", Type: "string", Usage: "Inline message text"}, {Name: "text-file", Type: "string", Usage: "Message file path"}, {Name: "type", Type: "string", Usage: "Message type", Default: "text"}, {Name: "secure", Type: "string", Usage: "Secure mode", Default: "off", Choices: []string{"off", "on"}}}},
|
|
136
|
+
{Name: "msg.inbox", Use: "inbox", Short: "Read inbox messages", Phase: "phase5", Implemented: true, Handler: "msg.inbox", Outputs: []string{"json", "pretty", "table"}, Flags: []FlagSpec{{Name: "scope", Type: "string", Usage: "Message scope", Default: "all", Choices: []string{"all", "direct", "group"}}, {Name: "with", Type: "string", Usage: "Direct peer filter"}, {Name: "group", Type: "string", Usage: "Group filter"}, {Name: "unread", Type: "bool", Usage: "Only unread messages"}, {Name: "limit", Type: "int", Usage: "Maximum number of results", Default: "20"}, {Name: "mark-read", Type: "bool", Usage: "Mark returned messages as read"}}},
|
|
137
|
+
{Name: "msg.history", Use: "history", Short: "Read message history", Phase: "phase5", Implemented: true, Handler: "msg.history", Outputs: []string{"json", "pretty", "table"}, Flags: []FlagSpec{{Name: "with", Type: "string", Usage: "Direct peer DID or handle", Required: true}, {Name: "limit", Type: "int", Usage: "Maximum number of rows", Default: "50"}, {Name: "cursor", Type: "string", Usage: "Pagination cursor"}}},
|
|
138
|
+
{Name: "msg.mark-read", Use: "mark-read [MESSAGE_ID...]", Short: "Mark messages as read", Phase: "phase5", Implemented: true, Handler: "msg.mark-read", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
139
|
+
{Name: "msg.secure", Use: "secure", Short: "Secure direct messaging commands", Phase: "phase5", Implemented: false},
|
|
140
|
+
{Name: "msg.secure.status", Use: "status", Short: "Inspect secure messaging status", Phase: "phase5", Implemented: false, Handler: "stub", Outputs: []string{"json", "pretty", "table"}, Flags: []FlagSpec{{Name: "with", Type: "string", Usage: "Target peer DID or handle"}}},
|
|
141
|
+
{Name: "msg.secure.init", Use: "init", Short: "Initialize a secure session", Phase: "phase5", Implemented: false, Handler: "stub", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "with", Type: "string", Usage: "Target peer DID or handle", Required: true}}},
|
|
142
|
+
{Name: "msg.secure.repair", Use: "repair", Short: "Repair a secure session", Phase: "phase5", Implemented: false, Handler: "stub", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "with", Type: "string", Usage: "Target peer DID or handle", Required: true}}},
|
|
143
|
+
{Name: "msg.secure.failed", Use: "failed", Short: "List failed secure outbox items", Phase: "phase5", Implemented: false, Handler: "stub", Outputs: []string{"json", "pretty", "table"}},
|
|
144
|
+
{Name: "msg.secure.retry", Use: "retry <OUTBOX_ID>", Short: "Retry one failed secure outbox item", Phase: "phase5", Implemented: false, Handler: "stub", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
145
|
+
{Name: "msg.secure.drop", Use: "drop <OUTBOX_ID>", Short: "Drop one failed secure outbox item", Phase: "phase5", Implemented: false, Handler: "stub", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
146
|
+
{Name: "group", Use: "group", Short: "Group lifecycle commands", Phase: "phase1", Implemented: true},
|
|
147
|
+
{Name: "group.create", Use: "create", Short: "Create a new group", Phase: "phase5", Implemented: true, Handler: "group.create", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "name", Type: "string", Usage: "Group display name", Required: true}, {Name: "description", Type: "string", Usage: "Group description"}, {Name: "discoverability", Type: "string", Usage: "Discoverability mode", Default: "private"}, {Name: "admission-mode", Type: "string", Usage: "Admission mode", Default: "open-join"}, {Name: "slug", Type: "string", Usage: "Group slug"}, {Name: "goal", Type: "string", Usage: "Group goal"}, {Name: "rules", Type: "string", Usage: "Group rules"}, {Name: "message-prompt", Type: "string", Usage: "Default group prompt"}, {Name: "doc-url", Type: "string", Usage: "Group document URL"}, {Name: "attachments-allowed", Type: "bool", Usage: "Allow attachments"}, {Name: "max-members", Type: "string", Usage: "Maximum group members"}, {Name: "member-max-messages", Type: "int", Usage: "Per-member message limit"}, {Name: "member-max-total-chars", Type: "int", Usage: "Per-member total char limit"}}},
|
|
148
|
+
{Name: "group.get", Use: "get", Short: "Show group details", Aliases: []string{"show"}, Phase: "phase5", Implemented: true, Handler: "group.get", Outputs: []string{"json", "pretty", "table"}, Flags: []FlagSpec{{Name: "group", Type: "string", Usage: "Group DID", Required: true}}},
|
|
149
|
+
{Name: "group.join", Use: "join", Short: "Join an open group", Phase: "phase5", Implemented: true, Handler: "group.join", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "group", Type: "string", Usage: "Group DID", Required: true}, {Name: "reason", Type: "string", Usage: "Join reason"}}},
|
|
150
|
+
{Name: "group.add", Use: "add", Short: "Add a member to a group", Phase: "phase5", Implemented: true, Handler: "group.add", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "group", Type: "string", Usage: "Group DID", Required: true}, {Name: "member", Type: "string", Usage: "Member DID or handle", Required: true}, {Name: "role", Type: "string", Usage: "Member role", Default: "member"}}},
|
|
151
|
+
{Name: "group.remove", Use: "remove", Short: "Remove a member from a group", Aliases: []string{"kick"}, Phase: "phase5", Implemented: true, Handler: "group.remove", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "group", Type: "string", Usage: "Group DID", Required: true}, {Name: "member", Type: "string", Usage: "Member DID or handle", Required: true}, {Name: "reason", Type: "string", Usage: "Removal reason"}}},
|
|
152
|
+
{Name: "group.leave", Use: "leave", Short: "Leave a group", Phase: "phase5", Implemented: true, Handler: "group.leave", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "group", Type: "string", Usage: "Group DID", Required: true}}},
|
|
153
|
+
{Name: "group.update", Use: "update", Short: "Update group profile or policy", Phase: "phase5", Implemented: true, Handler: "group.update", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "group", Type: "string", Usage: "Group DID", Required: true}, {Name: "name", Type: "string", Usage: "New group display name"}, {Name: "description", Type: "string", Usage: "New group description"}, {Name: "discoverability", Type: "string", Usage: "Discoverability mode"}, {Name: "admission-mode", Type: "string", Usage: "Admission mode"}, {Name: "slug", Type: "string", Usage: "New group slug"}, {Name: "goal", Type: "string", Usage: "New group goal"}, {Name: "rules", Type: "string", Usage: "New group rules"}, {Name: "message-prompt", Type: "string", Usage: "New group prompt"}, {Name: "doc-url", Type: "string", Usage: "New group document URL"}, {Name: "attachments-allowed", Type: "bool", Usage: "Allow attachments"}, {Name: "max-members", Type: "string", Usage: "Maximum group members"}, {Name: "member-max-messages", Type: "int", Usage: "Per-member message limit"}, {Name: "member-max-total-chars", Type: "int", Usage: "Per-member total char limit"}}},
|
|
154
|
+
{Name: "group.members", Use: "members", Short: "List active group members", Phase: "phase5", Implemented: true, Handler: "group.members", Outputs: []string{"json", "pretty", "table"}, Flags: []FlagSpec{{Name: "group", Type: "string", Usage: "Group DID", Required: true}, {Name: "limit", Type: "int", Usage: "Maximum number of rows", Default: "100"}}},
|
|
155
|
+
{Name: "group.messages", Use: "messages", Short: "List group messages", Phase: "phase5", Implemented: true, Handler: "group.messages", Outputs: []string{"json", "pretty", "table"}, Flags: []FlagSpec{{Name: "group", Type: "string", Usage: "Group DID", Required: true}, {Name: "limit", Type: "int", Usage: "Maximum number of rows", Default: "50"}, {Name: "cursor", Type: "string", Usage: "Pagination cursor"}}},
|
|
156
|
+
{Name: "group.code", Use: "code", Short: "Inspect or manage group join codes", Phase: "phase5", Implemented: false},
|
|
157
|
+
{Name: "group.code.get", Use: "get", Short: "Show group join code status", Phase: "phase5", Implemented: false, Handler: "stub", Outputs: []string{"json", "pretty", "table"}, Flags: []FlagSpec{{Name: "group", Type: "string", Usage: "Group DID", Required: true}}},
|
|
158
|
+
{Name: "group.code.refresh", Use: "refresh", Short: "Rotate the current group join code", Phase: "phase5", Implemented: false, Handler: "stub", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "group", Type: "string", Usage: "Group DID", Required: true}}},
|
|
159
|
+
{Name: "group.code.enable", Use: "enable", Short: "Enable or disable group join codes", Phase: "phase5", Implemented: false, Handler: "stub", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "group", Type: "string", Usage: "Group DID", Required: true}, {Name: "enabled", Type: "bool", Usage: "Whether join codes are enabled", Required: true}}},
|
|
160
|
+
{Name: "runtime", Use: "runtime", Short: "Runtime mode, listener, and heartbeat commands", Phase: "phase1", Implemented: true},
|
|
161
|
+
{Name: "runtime.status", Use: "status", Short: "Show runtime status", Phase: "phase7", Implemented: true, Handler: "runtime.status", Outputs: []string{"json", "pretty", "table"}},
|
|
162
|
+
{Name: "runtime.setup", Use: "setup", Short: "Run runtime bootstrap and migration checks", Phase: "phase7", Implemented: true, Handler: "runtime.setup", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "mode", Type: "string", Usage: "Runtime mode", Choices: []string{"http", "websocket"}}}},
|
|
163
|
+
{Name: "runtime.mode", Use: "mode", Short: "Inspect or update runtime mode", Phase: "phase7", Implemented: false},
|
|
164
|
+
{Name: "runtime.mode.get", Use: "get", Short: "Get the current runtime mode", Phase: "phase7", Implemented: true, Handler: "runtime.mode.get", Outputs: []string{"json", "pretty", "table"}},
|
|
165
|
+
{Name: "runtime.mode.set", Use: "set <MODE>", Short: "Set the runtime mode", Phase: "phase7", Implemented: true, Handler: "runtime.mode.set", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
166
|
+
{Name: "runtime.listener", Use: "listener", Short: "Manage the realtime listener service", Phase: "phase7", Implemented: false},
|
|
167
|
+
{Name: "runtime.listener.status", Use: "status", Short: "Show listener status", Phase: "phase7", Implemented: true, Handler: "runtime.listener.status", Outputs: []string{"json", "pretty", "table"}},
|
|
168
|
+
{Name: "runtime.listener.install", Use: "install", Short: "Install the listener service", Phase: "phase7", Implemented: true, Handler: "runtime.listener.install", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
169
|
+
{Name: "runtime.listener.start", Use: "start", Short: "Start the listener service", Phase: "phase7", Implemented: true, Handler: "runtime.listener.start", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
170
|
+
{Name: "runtime.listener.stop", Use: "stop", Short: "Stop the listener service", Phase: "phase7", Implemented: true, Handler: "runtime.listener.stop", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
171
|
+
{Name: "runtime.listener.restart", Use: "restart", Short: "Restart the listener service", Phase: "phase7", Implemented: true, Handler: "runtime.listener.restart", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
172
|
+
{Name: "runtime.listener.uninstall", Use: "uninstall", Short: "Uninstall the listener service", Phase: "phase7", Implemented: true, Handler: "runtime.listener.uninstall", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
173
|
+
{Name: "runtime.heartbeat", Use: "heartbeat", Short: "Manage heartbeat tasks", Phase: "phase7", Implemented: false},
|
|
174
|
+
{Name: "runtime.heartbeat.status", Use: "status", Short: "Show heartbeat status", Phase: "phase7", Implemented: false, Handler: "stub", Outputs: []string{"json", "pretty", "table"}},
|
|
175
|
+
{Name: "runtime.heartbeat.install", Use: "install", Short: "Install heartbeat automation", Phase: "phase7", Implemented: false, Handler: "stub", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "every", Type: "string", Usage: "Heartbeat schedule", Default: "15m"}}},
|
|
176
|
+
{Name: "runtime.heartbeat.run-once", Use: "run-once", Short: "Run heartbeat once", Phase: "phase7", Implemented: false, Handler: "stub", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
177
|
+
{Name: "people", Use: "people", Short: "People, relationships, and contacts commands", Phase: "phase1", Implemented: true},
|
|
178
|
+
{Name: "people.search", Use: "search <QUERY>", Short: "Search users", Phase: "phase8", Implemented: false, Handler: "stub", Outputs: []string{"json", "pretty", "table"}},
|
|
179
|
+
{Name: "people.follow", Use: "follow <TARGET>", Short: "Follow a user", Phase: "phase8", Implemented: false, Handler: "stub", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
180
|
+
{Name: "people.unfollow", Use: "unfollow <TARGET>", Short: "Unfollow a user", Phase: "phase8", Implemented: false, Handler: "stub", SideEffect: true, Outputs: []string{"json", "pretty"}},
|
|
181
|
+
{Name: "people.status", Use: "status <TARGET>", Short: "Show relationship status", Phase: "phase8", Implemented: false, Handler: "stub", Outputs: []string{"json", "pretty", "table"}},
|
|
182
|
+
{Name: "people.followers", Use: "followers", Short: "List followers", Phase: "phase8", Implemented: false, Handler: "stub", Outputs: []string{"json", "pretty", "table"}},
|
|
183
|
+
{Name: "people.following", Use: "following", Short: "List following", Phase: "phase8", Implemented: false, Handler: "stub", Outputs: []string{"json", "pretty", "table"}},
|
|
184
|
+
{Name: "people.contacts", Use: "contacts", Short: "Manage local contacts", Phase: "phase8", Implemented: false},
|
|
185
|
+
{Name: "people.contacts.list", Use: "list", Short: "List local contacts", Phase: "phase8", Implemented: false, Handler: "stub", Outputs: []string{"json", "pretty", "table"}},
|
|
186
|
+
{Name: "people.contacts.save", Use: "save", Short: "Save a local contact", Phase: "phase8", Implemented: false, Handler: "stub", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "did", Type: "string", Usage: "Contact DID", Required: true}, {Name: "handle", Type: "string", Usage: "Contact handle"}, {Name: "reason", Type: "string", Usage: "Why the contact was saved"}}},
|
|
187
|
+
{Name: "page", Use: "page", Short: "Content page commands", Phase: "phase1", Implemented: true},
|
|
188
|
+
{Name: "page.create", Use: "create", Short: "Create a content page", Phase: "phase8", Implemented: true, Handler: "page.create", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "slug", Type: "string", Usage: "Page slug", Required: true}, {Name: "title", Type: "string", Usage: "Page title", Required: true}, {Name: "markdown", Type: "string", Usage: "Inline markdown body"}, {Name: "markdown-file", Type: "string", Usage: "Markdown file path"}, {Name: "visibility", Type: "string", Usage: "Page visibility", Default: "public", Choices: []string{"public", "draft", "unlisted"}}}},
|
|
189
|
+
{Name: "page.list", Use: "list", Short: "List content pages", Phase: "phase8", Implemented: true, Handler: "page.list", Outputs: []string{"json", "pretty", "table"}},
|
|
190
|
+
{Name: "page.get", Use: "get", Short: "Get one content page", Phase: "phase8", Implemented: true, Handler: "page.get", Outputs: []string{"json", "pretty", "table"}, Flags: []FlagSpec{{Name: "slug", Type: "string", Usage: "Page slug", Required: true}}},
|
|
191
|
+
{Name: "page.update", Use: "update", Short: "Update a content page", Phase: "phase8", Implemented: true, Handler: "page.update", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "slug", Type: "string", Usage: "Page slug", Required: true}, {Name: "title", Type: "string", Usage: "Page title"}, {Name: "markdown", Type: "string", Usage: "Inline markdown body"}, {Name: "markdown-file", Type: "string", Usage: "Markdown file path"}, {Name: "visibility", Type: "string", Usage: "Page visibility", Choices: []string{"public", "draft", "unlisted"}}}},
|
|
192
|
+
{Name: "page.rename", Use: "rename", Short: "Rename a content page slug", Phase: "phase8", Implemented: true, Handler: "page.rename", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "slug", Type: "string", Usage: "Current page slug", Required: true}, {Name: "to", Type: "string", Usage: "New slug", Required: true}}},
|
|
193
|
+
{Name: "page.delete", Use: "delete", Short: "Delete a content page", Phase: "phase8", Implemented: true, Handler: "page.delete", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "slug", Type: "string", Usage: "Page slug", Required: true}}},
|
|
194
|
+
{Name: "debug", Use: "debug", Short: "Debugging and raw inspection commands", Phase: "phase1", Implemented: true},
|
|
195
|
+
{Name: "debug.db", Use: "db", Short: "Database inspection helpers", Phase: "phase4", Implemented: true},
|
|
196
|
+
{Name: "debug.db.query", Use: "query <SQL>", Short: "Execute a local SQLite query", Phase: "phase4", Implemented: true, Handler: "debug.db.query", Outputs: []string{"json", "pretty", "table"}},
|
|
197
|
+
{Name: "debug.db.import-v1", Use: "import-v1", Short: "Import a legacy v1 local SQLite database", Phase: "phase4", Implemented: true, Handler: "debug.db.import-v1", SideEffect: true, Outputs: []string{"json", "pretty"}, Flags: []FlagSpec{{Name: "path", Type: "string", Usage: "Explicit legacy database path override"}}},
|
|
198
|
+
{Name: "debug.raw", Use: "raw", Short: "Raw RPC helpers", Phase: "phase1", Implemented: false},
|
|
199
|
+
{Name: "debug.raw.rpc", Use: "rpc", Short: "Call raw RPC endpoints", Phase: "phase7", Implemented: false, Handler: "stub", Outputs: []string{"json", "pretty"}},
|
|
200
|
+
{Name: "debug.schema-cache", Use: "schema-cache", Short: "Inspect generated schema metadata", Phase: "phase7", Implemented: false, Handler: "stub", Outputs: []string{"json", "pretty", "table"}},
|
|
201
|
+
{Name: "debug.logs", Use: "logs", Short: "Tail runtime logs", Phase: "phase7", Implemented: false, Handler: "stub", Outputs: []string{"ndjson", "pretty"}, Flags: []FlagSpec{{Name: "follow", Type: "bool", Usage: "Follow log output"}}},
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package cmdmeta
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
func TestCatalogPublishesCanonicalGroupCommands(t *testing.T) {
|
|
6
|
+
t.Parallel()
|
|
7
|
+
|
|
8
|
+
catalog := NewCatalog()
|
|
9
|
+
|
|
10
|
+
for _, name := range []string{"group get", "group add", "group remove", "group code get", "group code refresh", "group code enable"} {
|
|
11
|
+
if _, ok := catalog.Lookup(name); !ok {
|
|
12
|
+
t.Fatalf("Lookup(%q) = false, want true", name)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
for _, name := range []string{"group show", "group kick"} {
|
|
17
|
+
if _, ok := catalog.Lookup(name); ok {
|
|
18
|
+
t.Fatalf("Lookup(%q) = true, want false", name)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|