@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,228 @@
|
|
|
1
|
+
package cli
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"os"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"github.com/agentconnect/awiki-cli/internal/identity"
|
|
10
|
+
"github.com/agentconnect/awiki-cli/internal/message"
|
|
11
|
+
"github.com/agentconnect/awiki-cli/internal/output"
|
|
12
|
+
"github.com/spf13/cobra"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
func (a *App) messageService() (*message.Service, output.Format, error) {
|
|
16
|
+
resolved, err := a.resolveConfig()
|
|
17
|
+
if err != nil {
|
|
18
|
+
return nil, output.FormatJSON, err
|
|
19
|
+
}
|
|
20
|
+
format := normalizedFormat(resolved.OutputFormat)
|
|
21
|
+
service, err := message.NewService(resolved)
|
|
22
|
+
if err != nil {
|
|
23
|
+
return nil, format, err
|
|
24
|
+
}
|
|
25
|
+
return service, format, nil
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func (a *App) messageExit(err error, hint string) error {
|
|
29
|
+
if err == nil {
|
|
30
|
+
return nil
|
|
31
|
+
}
|
|
32
|
+
var serviceErr *message.ServiceError
|
|
33
|
+
if errors.As(err, &serviceErr) {
|
|
34
|
+
switch {
|
|
35
|
+
case serviceErr.StatusCode == 400 || serviceErr.RPCCode == -32602:
|
|
36
|
+
return output.NewExitError("invalid_argument", 2, err.Error(), hint)
|
|
37
|
+
case serviceErr.StatusCode == 401 || serviceErr.RPCCode == -32000:
|
|
38
|
+
return output.NewExitError("auth_required", 3, err.Error(), "Use an identity with a valid JWT or DID WBA auth material.")
|
|
39
|
+
case serviceErr.StatusCode == 404 || serviceErr.RPCCode == -32002:
|
|
40
|
+
return output.NewExitError("not_found", 5, err.Error(), hint)
|
|
41
|
+
case serviceErr.StatusCode == 409 || serviceErr.RPCCode == -32003 || serviceErr.RPCCode == -32004:
|
|
42
|
+
return output.NewExitError("conflict", 1, err.Error(), hint)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
switch {
|
|
46
|
+
case errors.Is(err, message.ErrTargetRequired), errors.Is(err, message.ErrGroupRequired), errors.Is(err, message.ErrMemberRequired), errors.Is(err, message.ErrTextRequired), errors.Is(err, message.ErrMessageNotFound):
|
|
47
|
+
return output.NewExitError("invalid_argument", 2, err.Error(), hint)
|
|
48
|
+
case errors.Is(err, identity.ErrUserRegistrationRequired):
|
|
49
|
+
return output.NewExitError("identity_required", 3, err.Error(), "Complete user setup with `awiki-cli id register --handle <handle> ...` or recover an existing handle before using msg commands.")
|
|
50
|
+
case errors.Is(err, message.ErrSecureNotSupported):
|
|
51
|
+
return output.NewExitError("unsupported_mode", 1, err.Error(), "Direct secure messaging is planned for Phase 5.")
|
|
52
|
+
case errors.Is(err, message.ErrTransportUnavailable):
|
|
53
|
+
return output.NewExitError("transport_unavailable", 1, err.Error(), "Start the websocket listener/daemon or switch runtime.mode back to http.")
|
|
54
|
+
default:
|
|
55
|
+
type rpcCoder interface{ Error() string }
|
|
56
|
+
var _ rpcCoder = err
|
|
57
|
+
return output.NewExitError("internal_error", 1, err.Error(), hint)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func (a *App) runMsgSend(cmd *cobra.Command, args []string) error {
|
|
62
|
+
to, _ := cmd.Flags().GetString("to")
|
|
63
|
+
group, _ := cmd.Flags().GetString("group")
|
|
64
|
+
text, _ := cmd.Flags().GetString("text")
|
|
65
|
+
textFile, _ := cmd.Flags().GetString("text-file")
|
|
66
|
+
messageType, _ := cmd.Flags().GetString("type")
|
|
67
|
+
secure, _ := cmd.Flags().GetString("secure")
|
|
68
|
+
if strings.TrimSpace(group) == "" && strings.TrimSpace(to) == "" {
|
|
69
|
+
return output.NewExitError("invalid_argument", 2, "msg send requires either --to or --group.", "Usage: awiki-cli msg send --to <handle|did> --text \"Hello\" or awiki-cli msg send --group <group_did> --text \"Hello group\"")
|
|
70
|
+
}
|
|
71
|
+
if strings.TrimSpace(group) != "" && strings.TrimSpace(to) != "" {
|
|
72
|
+
return output.NewExitError("invalid_argument", 2, "msg send accepts either --to or --group, but not both.", "Choose direct messaging with --to or group messaging with --group.")
|
|
73
|
+
}
|
|
74
|
+
if strings.TrimSpace(text) == "" && strings.TrimSpace(textFile) != "" {
|
|
75
|
+
raw, err := os.ReadFile(textFile)
|
|
76
|
+
if err != nil {
|
|
77
|
+
return output.NewExitError("invalid_argument", 2, err.Error(), "Make sure --text-file points to a readable file.")
|
|
78
|
+
}
|
|
79
|
+
text = string(raw)
|
|
80
|
+
}
|
|
81
|
+
if strings.TrimSpace(text) == "" {
|
|
82
|
+
return output.NewExitError("invalid_argument", 2, "msg send requires --text or --text-file.", "Provide the message body via --text or --text-file.")
|
|
83
|
+
}
|
|
84
|
+
service, format, err := a.messageService()
|
|
85
|
+
if err != nil {
|
|
86
|
+
return a.messageExit(err, "Run `awiki-cli doctor` to inspect configuration and identity state.")
|
|
87
|
+
}
|
|
88
|
+
request := message.SendRequest{
|
|
89
|
+
IdentityName: a.globals.Identity,
|
|
90
|
+
Target: to,
|
|
91
|
+
Group: group,
|
|
92
|
+
Text: text,
|
|
93
|
+
MessageType: messageType,
|
|
94
|
+
SecureMode: secure,
|
|
95
|
+
}
|
|
96
|
+
if a.globals.DryRun {
|
|
97
|
+
action := "direct.send"
|
|
98
|
+
target := map[string]any{"did": to, "kind": "direct"}
|
|
99
|
+
if strings.TrimSpace(group) != "" {
|
|
100
|
+
action = "group.send"
|
|
101
|
+
target = map[string]any{"did": group, "kind": "group"}
|
|
102
|
+
}
|
|
103
|
+
data := map[string]any{
|
|
104
|
+
"plan": map[string]any{
|
|
105
|
+
"action": action,
|
|
106
|
+
"identity": a.globals.Identity,
|
|
107
|
+
"target": target,
|
|
108
|
+
"message_type": defaultString(messageType, "text"),
|
|
109
|
+
"runtime_mode": service.Config().RuntimeMode,
|
|
110
|
+
"transport": service.Config().RuntimeMode,
|
|
111
|
+
"local_writes": []string{"messages"},
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Dry run: message send planned", nil, a.identityMeta())
|
|
115
|
+
}
|
|
116
|
+
result, err := service.Send(context.Background(), request)
|
|
117
|
+
if err != nil {
|
|
118
|
+
return a.messageExit(err, "Ensure the target exists, the active identity is valid, and runtime mode is configured correctly.")
|
|
119
|
+
}
|
|
120
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, result.Data, result.Summary, result.Warnings, a.identityMeta())
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func (a *App) runMsgInbox(cmd *cobra.Command, args []string) error {
|
|
124
|
+
scope, _ := cmd.Flags().GetString("scope")
|
|
125
|
+
with, _ := cmd.Flags().GetString("with")
|
|
126
|
+
group, _ := cmd.Flags().GetString("group")
|
|
127
|
+
unread, _ := cmd.Flags().GetBool("unread")
|
|
128
|
+
limit, _ := cmd.Flags().GetInt("limit")
|
|
129
|
+
markRead, _ := cmd.Flags().GetBool("mark-read")
|
|
130
|
+
service, format, err := a.messageService()
|
|
131
|
+
if err != nil {
|
|
132
|
+
return a.messageExit(err, "Run `awiki-cli doctor` to inspect configuration and identity state.")
|
|
133
|
+
}
|
|
134
|
+
request := message.InboxRequest{
|
|
135
|
+
IdentityName: a.globals.Identity,
|
|
136
|
+
Scope: scope,
|
|
137
|
+
With: with,
|
|
138
|
+
Group: group,
|
|
139
|
+
Limit: limit,
|
|
140
|
+
UnreadOnly: unread,
|
|
141
|
+
MarkRead: markRead,
|
|
142
|
+
}
|
|
143
|
+
if a.globals.DryRun {
|
|
144
|
+
data := map[string]any{"plan": map[string]any{
|
|
145
|
+
"action": "inbox.get",
|
|
146
|
+
"identity": a.globals.Identity,
|
|
147
|
+
"runtime_mode": service.Config().RuntimeMode,
|
|
148
|
+
"scope": scope,
|
|
149
|
+
"with": with,
|
|
150
|
+
"group": group,
|
|
151
|
+
"limit": limit,
|
|
152
|
+
"mark_read": markRead,
|
|
153
|
+
}}
|
|
154
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Dry run: inbox read planned", nil, a.identityMeta())
|
|
155
|
+
}
|
|
156
|
+
result, err := service.Inbox(context.Background(), request)
|
|
157
|
+
if err != nil {
|
|
158
|
+
return a.messageExit(err, "Make sure the active identity is valid and runtime mode is available.")
|
|
159
|
+
}
|
|
160
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, result.Data, result.Summary, result.Warnings, a.identityMeta())
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
func (a *App) runMsgHistory(cmd *cobra.Command, args []string) error {
|
|
164
|
+
with, _ := cmd.Flags().GetString("with")
|
|
165
|
+
limit, _ := cmd.Flags().GetInt("limit")
|
|
166
|
+
cursor, _ := cmd.Flags().GetString("cursor")
|
|
167
|
+
service, format, err := a.messageService()
|
|
168
|
+
if err != nil {
|
|
169
|
+
return a.messageExit(err, "Run `awiki-cli doctor` to inspect configuration and identity state.")
|
|
170
|
+
}
|
|
171
|
+
request := message.HistoryRequest{
|
|
172
|
+
IdentityName: a.globals.Identity,
|
|
173
|
+
With: with,
|
|
174
|
+
Limit: limit,
|
|
175
|
+
Cursor: cursor,
|
|
176
|
+
}
|
|
177
|
+
if a.globals.DryRun {
|
|
178
|
+
data := map[string]any{"plan": map[string]any{
|
|
179
|
+
"action": "direct.get_history",
|
|
180
|
+
"identity": a.globals.Identity,
|
|
181
|
+
"runtime_mode": service.Config().RuntimeMode,
|
|
182
|
+
"with": with,
|
|
183
|
+
"limit": limit,
|
|
184
|
+
"cursor": cursor,
|
|
185
|
+
}}
|
|
186
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Dry run: direct history read planned", nil, a.identityMeta())
|
|
187
|
+
}
|
|
188
|
+
result, err := service.History(context.Background(), request)
|
|
189
|
+
if err != nil {
|
|
190
|
+
return a.messageExit(err, "Make sure the peer exists and runtime mode is available.")
|
|
191
|
+
}
|
|
192
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, result.Data, result.Summary, result.Warnings, a.identityMeta())
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
func (a *App) runMsgMarkRead(cmd *cobra.Command, args []string) error {
|
|
196
|
+
if len(args) == 0 {
|
|
197
|
+
return output.NewExitError("invalid_argument", 2, "msg mark-read requires at least one message id.", "Usage: awiki-cli msg mark-read <MESSAGE_ID...>")
|
|
198
|
+
}
|
|
199
|
+
service, format, err := a.messageService()
|
|
200
|
+
if err != nil {
|
|
201
|
+
return a.messageExit(err, "Run `awiki-cli doctor` to inspect configuration and identity state.")
|
|
202
|
+
}
|
|
203
|
+
request := message.MarkReadRequest{
|
|
204
|
+
IdentityName: a.globals.Identity,
|
|
205
|
+
MessageIDs: args,
|
|
206
|
+
}
|
|
207
|
+
if a.globals.DryRun {
|
|
208
|
+
data := map[string]any{"plan": map[string]any{
|
|
209
|
+
"action": "inbox.mark_read",
|
|
210
|
+
"identity": a.globals.Identity,
|
|
211
|
+
"runtime_mode": service.Config().RuntimeMode,
|
|
212
|
+
"message_ids": args,
|
|
213
|
+
}}
|
|
214
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, data, "Dry run: mark-read planned", nil, a.identityMeta())
|
|
215
|
+
}
|
|
216
|
+
result, err := service.MarkRead(context.Background(), request)
|
|
217
|
+
if err != nil {
|
|
218
|
+
return a.messageExit(err, "Make sure the message ids are valid and runtime mode is available.")
|
|
219
|
+
}
|
|
220
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, result.Data, result.Summary, result.Warnings, a.identityMeta())
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
func defaultString(value string, fallback string) string {
|
|
224
|
+
if strings.TrimSpace(value) == "" {
|
|
225
|
+
return fallback
|
|
226
|
+
}
|
|
227
|
+
return value
|
|
228
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
package cli
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"os"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"github.com/agentconnect/awiki-cli/internal/content"
|
|
10
|
+
"github.com/agentconnect/awiki-cli/internal/identity"
|
|
11
|
+
"github.com/agentconnect/awiki-cli/internal/output"
|
|
12
|
+
"github.com/spf13/cobra"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
func (a *App) contentService() (*content.Service, output.Format, error) {
|
|
16
|
+
resolved, err := a.resolveConfig()
|
|
17
|
+
if err != nil {
|
|
18
|
+
return nil, output.FormatJSON, err
|
|
19
|
+
}
|
|
20
|
+
format := normalizedFormat(resolved.OutputFormat)
|
|
21
|
+
service, err := content.NewService(resolved)
|
|
22
|
+
if err != nil {
|
|
23
|
+
return nil, format, err
|
|
24
|
+
}
|
|
25
|
+
return service, format, nil
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func (a *App) contentExit(err error, hint string) error {
|
|
29
|
+
if err == nil {
|
|
30
|
+
return nil
|
|
31
|
+
}
|
|
32
|
+
var serviceErr *content.ServiceError
|
|
33
|
+
if errors.As(err, &serviceErr) {
|
|
34
|
+
switch {
|
|
35
|
+
case serviceErr.StatusCode == 400 || serviceErr.RPCCode == -32602:
|
|
36
|
+
return output.NewExitError("invalid_argument", 2, err.Error(), hint)
|
|
37
|
+
case serviceErr.StatusCode == 401 || serviceErr.RPCCode == -32000:
|
|
38
|
+
return output.NewExitError("auth_required", 3, err.Error(), "Use an identity with a valid JWT or DID WBA auth material.")
|
|
39
|
+
case serviceErr.StatusCode == 404 || serviceErr.RPCCode == -32002:
|
|
40
|
+
return output.NewExitError("not_found", 5, err.Error(), hint)
|
|
41
|
+
case serviceErr.StatusCode == 409 || serviceErr.RPCCode == -32003 || serviceErr.RPCCode == -32004:
|
|
42
|
+
return output.NewExitError("conflict", 1, err.Error(), hint)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
switch {
|
|
46
|
+
case errors.Is(err, content.ErrSlugRequired), errors.Is(err, content.ErrTitleRequired), errors.Is(err, content.ErrNoUpdateFields), errors.Is(err, content.ErrVisibilityInvalid):
|
|
47
|
+
return output.NewExitError("invalid_argument", 2, err.Error(), hint)
|
|
48
|
+
case errors.Is(err, identity.ErrIdentityNotFound), errors.Is(err, identity.ErrNoDefaultIdentity):
|
|
49
|
+
return output.NewExitError("not_found", 5, err.Error(), "Run `awiki-cli id list` to inspect available identities.")
|
|
50
|
+
case errors.Is(err, identity.ErrAuthRequired):
|
|
51
|
+
return output.NewExitError("auth_required", 3, err.Error(), "Use an identity with a valid JWT, or run `awiki-cli id register` / `awiki-cli id recover` first.")
|
|
52
|
+
default:
|
|
53
|
+
return output.NewExitError("internal_error", 1, err.Error(), hint)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func (a *App) runPageCreate(cmd *cobra.Command, args []string) error {
|
|
58
|
+
slug, _ := cmd.Flags().GetString("slug")
|
|
59
|
+
title, _ := cmd.Flags().GetString("title")
|
|
60
|
+
markdown, _ := cmd.Flags().GetString("markdown")
|
|
61
|
+
markdownFile, _ := cmd.Flags().GetString("markdown-file")
|
|
62
|
+
visibility, _ := cmd.Flags().GetString("visibility")
|
|
63
|
+
body, err := resolveMarkdownBody(markdown, cmd.Flags().Changed("markdown"), markdownFile, cmd.Flags().Changed("markdown-file"))
|
|
64
|
+
if err != nil {
|
|
65
|
+
return output.NewExitError("invalid_argument", 2, err.Error(), "Choose one content body source and make sure the file is readable.")
|
|
66
|
+
}
|
|
67
|
+
service, format, err := a.contentService()
|
|
68
|
+
if err != nil {
|
|
69
|
+
return a.contentExit(err, "Run `awiki-cli doctor` to inspect configuration and identity state.")
|
|
70
|
+
}
|
|
71
|
+
request := content.CreatePageParams{Slug: slug, Title: title, Body: body, Visibility: visibility}
|
|
72
|
+
if a.globals.DryRun {
|
|
73
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, map[string]any{"plan": map[string]any{
|
|
74
|
+
"action": "page.create",
|
|
75
|
+
"identity": a.globals.Identity,
|
|
76
|
+
"rpc_endpoint": "/content/rpc",
|
|
77
|
+
"rpc_method": "create",
|
|
78
|
+
"request": map[string]any{
|
|
79
|
+
"slug": strings.TrimSpace(slug),
|
|
80
|
+
"title": strings.TrimSpace(title),
|
|
81
|
+
"body_bytes": len(body),
|
|
82
|
+
"visibility": defaultString(visibility, "public"),
|
|
83
|
+
},
|
|
84
|
+
}}, "Dry run: page create planned", nil, a.identityMeta())
|
|
85
|
+
}
|
|
86
|
+
result, err := service.CreatePage(context.Background(), request)
|
|
87
|
+
if err != nil {
|
|
88
|
+
return a.contentExit(err, "Make sure the active identity has a handle and the page slug is valid.")
|
|
89
|
+
}
|
|
90
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, result.Data, result.Summary, result.Warnings, a.identityMeta())
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
func (a *App) runPageList(cmd *cobra.Command, args []string) error {
|
|
94
|
+
service, format, err := a.contentService()
|
|
95
|
+
if err != nil {
|
|
96
|
+
return a.contentExit(err, "Run `awiki-cli doctor` to inspect configuration and identity state.")
|
|
97
|
+
}
|
|
98
|
+
if a.globals.DryRun {
|
|
99
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, map[string]any{"plan": map[string]any{
|
|
100
|
+
"action": "page.list",
|
|
101
|
+
"identity": a.globals.Identity,
|
|
102
|
+
"rpc_endpoint": "/content/rpc",
|
|
103
|
+
"rpc_method": "list",
|
|
104
|
+
}}, "Dry run: page list planned", nil, a.identityMeta())
|
|
105
|
+
}
|
|
106
|
+
result, err := service.ListPages(context.Background())
|
|
107
|
+
if err != nil {
|
|
108
|
+
return a.contentExit(err, "Make sure the active identity has a handle and can access content pages.")
|
|
109
|
+
}
|
|
110
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, result.Data, result.Summary, result.Warnings, a.identityMeta())
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
func (a *App) runPageGet(cmd *cobra.Command, args []string) error {
|
|
114
|
+
slug, _ := cmd.Flags().GetString("slug")
|
|
115
|
+
service, format, err := a.contentService()
|
|
116
|
+
if err != nil {
|
|
117
|
+
return a.contentExit(err, "Run `awiki-cli doctor` to inspect configuration and identity state.")
|
|
118
|
+
}
|
|
119
|
+
if a.globals.DryRun {
|
|
120
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, map[string]any{"plan": map[string]any{
|
|
121
|
+
"action": "page.get",
|
|
122
|
+
"identity": a.globals.Identity,
|
|
123
|
+
"rpc_endpoint": "/content/rpc",
|
|
124
|
+
"rpc_method": "get",
|
|
125
|
+
"request": map[string]any{"slug": strings.TrimSpace(slug)},
|
|
126
|
+
}}, "Dry run: page get planned", nil, a.identityMeta())
|
|
127
|
+
}
|
|
128
|
+
result, err := service.GetPage(context.Background(), slug)
|
|
129
|
+
if err != nil {
|
|
130
|
+
return a.contentExit(err, "Make sure the page exists and the active identity can access it.")
|
|
131
|
+
}
|
|
132
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, result.Data, result.Summary, result.Warnings, a.identityMeta())
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
func (a *App) runPageUpdate(cmd *cobra.Command, args []string) error {
|
|
136
|
+
slug, _ := cmd.Flags().GetString("slug")
|
|
137
|
+
title, _ := cmd.Flags().GetString("title")
|
|
138
|
+
markdown, _ := cmd.Flags().GetString("markdown")
|
|
139
|
+
markdownFile, _ := cmd.Flags().GetString("markdown-file")
|
|
140
|
+
visibilityChanged := cmd.Flags().Changed("visibility")
|
|
141
|
+
visibility, _ := cmd.Flags().GetString("visibility")
|
|
142
|
+
body, err := resolveOptionalMarkdownBody(markdown, cmd.Flags().Changed("markdown"), markdownFile, cmd.Flags().Changed("markdown-file"))
|
|
143
|
+
if err != nil {
|
|
144
|
+
return output.NewExitError("invalid_argument", 2, err.Error(), "Choose one content body source and make sure the file is readable.")
|
|
145
|
+
}
|
|
146
|
+
service, format, err := a.contentService()
|
|
147
|
+
if err != nil {
|
|
148
|
+
return a.contentExit(err, "Run `awiki-cli doctor` to inspect configuration and identity state.")
|
|
149
|
+
}
|
|
150
|
+
request := content.UpdatePageParams{Slug: slug, Title: title, Body: body}
|
|
151
|
+
if visibilityChanged {
|
|
152
|
+
request.Visibility = &visibility
|
|
153
|
+
}
|
|
154
|
+
if a.globals.DryRun {
|
|
155
|
+
changedFields := make([]string, 0, 3)
|
|
156
|
+
if strings.TrimSpace(title) != "" {
|
|
157
|
+
changedFields = append(changedFields, "title")
|
|
158
|
+
}
|
|
159
|
+
if body != nil {
|
|
160
|
+
changedFields = append(changedFields, "body")
|
|
161
|
+
}
|
|
162
|
+
if visibilityChanged {
|
|
163
|
+
changedFields = append(changedFields, "visibility")
|
|
164
|
+
}
|
|
165
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, map[string]any{"plan": map[string]any{
|
|
166
|
+
"action": "page.update",
|
|
167
|
+
"identity": a.globals.Identity,
|
|
168
|
+
"rpc_endpoint": "/content/rpc",
|
|
169
|
+
"rpc_method": "update",
|
|
170
|
+
"changed_fields": changedFields,
|
|
171
|
+
"request": map[string]any{
|
|
172
|
+
"slug": strings.TrimSpace(slug),
|
|
173
|
+
"title": strings.TrimSpace(title),
|
|
174
|
+
"body_bytes": bodySize(body),
|
|
175
|
+
"visibility": visibility,
|
|
176
|
+
},
|
|
177
|
+
}}, "Dry run: page update planned", nil, a.identityMeta())
|
|
178
|
+
}
|
|
179
|
+
result, err := service.UpdatePage(context.Background(), request)
|
|
180
|
+
if err != nil {
|
|
181
|
+
return a.contentExit(err, "Make sure the page exists and the updated fields are valid.")
|
|
182
|
+
}
|
|
183
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, result.Data, result.Summary, result.Warnings, a.identityMeta())
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
func (a *App) runPageRename(cmd *cobra.Command, args []string) error {
|
|
187
|
+
slug, _ := cmd.Flags().GetString("slug")
|
|
188
|
+
target, _ := cmd.Flags().GetString("to")
|
|
189
|
+
service, format, err := a.contentService()
|
|
190
|
+
if err != nil {
|
|
191
|
+
return a.contentExit(err, "Run `awiki-cli doctor` to inspect configuration and identity state.")
|
|
192
|
+
}
|
|
193
|
+
request := content.RenamePageParams{Slug: slug, To: target}
|
|
194
|
+
if a.globals.DryRun {
|
|
195
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, map[string]any{"plan": map[string]any{
|
|
196
|
+
"action": "page.rename",
|
|
197
|
+
"identity": a.globals.Identity,
|
|
198
|
+
"rpc_endpoint": "/content/rpc",
|
|
199
|
+
"rpc_method": "rename",
|
|
200
|
+
"request": map[string]any{"old_slug": strings.TrimSpace(slug), "new_slug": strings.TrimSpace(target)},
|
|
201
|
+
}}, "Dry run: page rename planned", nil, a.identityMeta())
|
|
202
|
+
}
|
|
203
|
+
result, err := service.RenamePage(context.Background(), request)
|
|
204
|
+
if err != nil {
|
|
205
|
+
return a.contentExit(err, "Make sure the source page exists and the target slug is available.")
|
|
206
|
+
}
|
|
207
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, result.Data, result.Summary, result.Warnings, a.identityMeta())
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
func (a *App) runPageDelete(cmd *cobra.Command, args []string) error {
|
|
211
|
+
slug, _ := cmd.Flags().GetString("slug")
|
|
212
|
+
service, format, err := a.contentService()
|
|
213
|
+
if err != nil {
|
|
214
|
+
return a.contentExit(err, "Run `awiki-cli doctor` to inspect configuration and identity state.")
|
|
215
|
+
}
|
|
216
|
+
if a.globals.DryRun {
|
|
217
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, map[string]any{"plan": map[string]any{
|
|
218
|
+
"action": "page.delete",
|
|
219
|
+
"identity": a.globals.Identity,
|
|
220
|
+
"rpc_endpoint": "/content/rpc",
|
|
221
|
+
"rpc_method": "delete",
|
|
222
|
+
"request": map[string]any{"slug": strings.TrimSpace(slug)},
|
|
223
|
+
}}, "Dry run: page delete planned", nil, a.identityMeta())
|
|
224
|
+
}
|
|
225
|
+
result, err := service.DeletePage(context.Background(), slug)
|
|
226
|
+
if err != nil {
|
|
227
|
+
return a.contentExit(err, "Make sure the page exists and the active identity can delete it.")
|
|
228
|
+
}
|
|
229
|
+
return a.renderSuccess(cmd.CommandPath(), format, a.globals.JQ, result.Data, result.Summary, result.Warnings, a.identityMeta())
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
func resolveMarkdownBody(markdown string, markdownChanged bool, markdownFile string, markdownFileChanged bool) (string, error) {
|
|
233
|
+
body, err := resolveOptionalMarkdownBody(markdown, markdownChanged, markdownFile, markdownFileChanged)
|
|
234
|
+
if err != nil {
|
|
235
|
+
return "", err
|
|
236
|
+
}
|
|
237
|
+
if body == nil {
|
|
238
|
+
return "", nil
|
|
239
|
+
}
|
|
240
|
+
return *body, nil
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
func resolveOptionalMarkdownBody(markdown string, markdownChanged bool, markdownFile string, markdownFileChanged bool) (*string, error) {
|
|
244
|
+
if markdownChanged && markdownFileChanged {
|
|
245
|
+
return nil, content.ErrBodySourceConflict
|
|
246
|
+
}
|
|
247
|
+
if markdownFileChanged {
|
|
248
|
+
raw, err := os.ReadFile(markdownFile)
|
|
249
|
+
if err != nil {
|
|
250
|
+
return nil, err
|
|
251
|
+
}
|
|
252
|
+
text := string(raw)
|
|
253
|
+
return &text, nil
|
|
254
|
+
}
|
|
255
|
+
if !markdownChanged {
|
|
256
|
+
return nil, nil
|
|
257
|
+
}
|
|
258
|
+
text := markdown
|
|
259
|
+
return &text, nil
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
func bodySize(body *string) int {
|
|
263
|
+
if body == nil {
|
|
264
|
+
return 0
|
|
265
|
+
}
|
|
266
|
+
return len(*body)
|
|
267
|
+
}
|