@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,473 @@
|
|
|
1
|
+
package cli
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"errors"
|
|
6
|
+
"strings"
|
|
7
|
+
|
|
8
|
+
"github.com/agentconnect/awiki-cli/internal/identity"
|
|
9
|
+
"github.com/agentconnect/awiki-cli/internal/output"
|
|
10
|
+
"github.com/spf13/cobra"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
func (a *App) identityService() (*identity.Service, output.Format, error) {
|
|
14
|
+
resolved, err := a.resolveConfig()
|
|
15
|
+
if err != nil {
|
|
16
|
+
return nil, output.FormatJSON, err
|
|
17
|
+
}
|
|
18
|
+
format := normalizedFormat(resolved.OutputFormat)
|
|
19
|
+
service, err := identity.NewService(resolved)
|
|
20
|
+
if err != nil {
|
|
21
|
+
return nil, format, err
|
|
22
|
+
}
|
|
23
|
+
return service, format, nil
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func (a *App) renderIdentityResult(cmd *cobra.Command, format output.Format, result *identity.CommandResult) error {
|
|
27
|
+
if result == nil {
|
|
28
|
+
return nil
|
|
29
|
+
}
|
|
30
|
+
meta := a.identityMeta()
|
|
31
|
+
if meta == nil {
|
|
32
|
+
meta = identityMetaFromData(result.Data)
|
|
33
|
+
}
|
|
34
|
+
return a.renderSuccess(
|
|
35
|
+
cmd.CommandPath(),
|
|
36
|
+
format,
|
|
37
|
+
a.globals.JQ,
|
|
38
|
+
identity.PublicData(result.Data),
|
|
39
|
+
result.Summary,
|
|
40
|
+
result.Warnings,
|
|
41
|
+
meta,
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func (a *App) identityExit(err error, fallbackHint string) error {
|
|
46
|
+
if err == nil {
|
|
47
|
+
return nil
|
|
48
|
+
}
|
|
49
|
+
var serviceErr *identity.ServiceError
|
|
50
|
+
if errors.As(err, &serviceErr) {
|
|
51
|
+
switch {
|
|
52
|
+
case serviceErr.StatusCode == 400:
|
|
53
|
+
return output.NewExitError("invalid_argument", 2, serviceErr.Error(), fallbackHint)
|
|
54
|
+
case serviceErr.StatusCode == 401:
|
|
55
|
+
return output.NewExitError("auth_required", 3, serviceErr.Error(), "Use an identity with a valid JWT, or run `awiki-cli id register` / `awiki-cli id recover` first.")
|
|
56
|
+
case serviceErr.StatusCode == 404:
|
|
57
|
+
return output.NewExitError("not_found", 5, serviceErr.Error(), fallbackHint)
|
|
58
|
+
case serviceErr.StatusCode == 409:
|
|
59
|
+
return output.NewExitError("conflict", 1, serviceErr.Error(), fallbackHint)
|
|
60
|
+
case serviceErr.RPCCode != 0:
|
|
61
|
+
code := "internal_error"
|
|
62
|
+
exitCode := 1
|
|
63
|
+
switch serviceErr.RPCCode {
|
|
64
|
+
case -32602:
|
|
65
|
+
code, exitCode = "invalid_argument", 2
|
|
66
|
+
case -32000:
|
|
67
|
+
code, exitCode = "auth_required", 3
|
|
68
|
+
case -32002:
|
|
69
|
+
code, exitCode = "not_found", 5
|
|
70
|
+
case -32003, -32004:
|
|
71
|
+
code = "conflict"
|
|
72
|
+
}
|
|
73
|
+
return output.NewExitError(code, exitCode, serviceErr.Error(), fallbackHint)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
switch {
|
|
77
|
+
case errors.Is(err, identity.ErrInvalidInput):
|
|
78
|
+
return output.NewExitError("invalid_argument", 2, err.Error(), fallbackHint)
|
|
79
|
+
case errors.Is(err, identity.ErrIdentityNotFound), errors.Is(err, identity.ErrLegacyNotFound), errors.Is(err, identity.ErrNoDefaultIdentity):
|
|
80
|
+
return output.NewExitError("not_found", 5, err.Error(), fallbackHint)
|
|
81
|
+
case errors.Is(err, identity.ErrIdentityConflict):
|
|
82
|
+
return output.NewExitError("conflict", 1, err.Error(), fallbackHint)
|
|
83
|
+
case errors.Is(err, identity.ErrAuthRequired):
|
|
84
|
+
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.")
|
|
85
|
+
default:
|
|
86
|
+
return output.NewExitError("internal_error", 1, err.Error(), fallbackHint)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
func (a *App) runIDStatus(cmd *cobra.Command, args []string) error {
|
|
91
|
+
service, format, err := a.identityService()
|
|
92
|
+
if err != nil {
|
|
93
|
+
return a.identityExit(err, "Run `awiki-cli doctor` to inspect the local identity store.")
|
|
94
|
+
}
|
|
95
|
+
result, err := service.Status()
|
|
96
|
+
if err != nil {
|
|
97
|
+
return a.identityExit(err, "Run `awiki-cli doctor` to inspect the local identity store.")
|
|
98
|
+
}
|
|
99
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func (a *App) runIDList(cmd *cobra.Command, args []string) error {
|
|
103
|
+
service, format, err := a.identityService()
|
|
104
|
+
if err != nil {
|
|
105
|
+
return a.identityExit(err, "Run `awiki-cli doctor` to inspect the local identity store.")
|
|
106
|
+
}
|
|
107
|
+
result, err := service.List()
|
|
108
|
+
if err != nil {
|
|
109
|
+
return a.identityExit(err, "Run `awiki-cli doctor` to inspect the local identity store.")
|
|
110
|
+
}
|
|
111
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
func (a *App) runIDCurrent(cmd *cobra.Command, args []string) error {
|
|
115
|
+
service, format, err := a.identityService()
|
|
116
|
+
if err != nil {
|
|
117
|
+
return a.identityExit(err, "Run `awiki-cli id list` to inspect available identities.")
|
|
118
|
+
}
|
|
119
|
+
result, err := service.Current()
|
|
120
|
+
if err != nil {
|
|
121
|
+
return a.identityExit(err, "Run `awiki-cli id list` to inspect available identities.")
|
|
122
|
+
}
|
|
123
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func (a *App) runIDUse(cmd *cobra.Command, args []string) error {
|
|
127
|
+
if len(args) != 1 {
|
|
128
|
+
return output.NewExitError("invalid_argument", 2, "id use requires exactly one identity name.", "Usage: awiki-cli id use <identity>")
|
|
129
|
+
}
|
|
130
|
+
service, format, err := a.identityService()
|
|
131
|
+
if err != nil {
|
|
132
|
+
return a.identityExit(err, "Run `awiki-cli id list` to inspect available identities.")
|
|
133
|
+
}
|
|
134
|
+
if a.globals.DryRun {
|
|
135
|
+
result := &identity.CommandResult{
|
|
136
|
+
Data: map[string]any{
|
|
137
|
+
"plan": map[string]any{
|
|
138
|
+
"action": "set_default_identity",
|
|
139
|
+
"identity_name": args[0],
|
|
140
|
+
"writes": []string{"index.json"},
|
|
141
|
+
"side_effect": true,
|
|
142
|
+
"previous_source": "identity_index",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
Summary: "Dry run: default identity switch planned",
|
|
146
|
+
}
|
|
147
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
148
|
+
}
|
|
149
|
+
result, err := service.Use(args[0])
|
|
150
|
+
if err != nil {
|
|
151
|
+
return a.identityExit(err, "Run `awiki-cli id list` to inspect available identities.")
|
|
152
|
+
}
|
|
153
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
func (a *App) runIDCreate(cmd *cobra.Command, args []string) error {
|
|
157
|
+
name, _ := cmd.Flags().GetString("name")
|
|
158
|
+
identityName, _ := cmd.Flags().GetString("identity")
|
|
159
|
+
if strings.TrimSpace(name) == "" {
|
|
160
|
+
return output.NewExitError("invalid_argument", 2, "id create requires --name.", "Usage: awiki-cli id create --name \"Alice\" [--identity alice]")
|
|
161
|
+
}
|
|
162
|
+
service, format, err := a.identityService()
|
|
163
|
+
if err != nil {
|
|
164
|
+
return a.identityExit(err, "Run `awiki-cli doctor` to inspect configuration and storage paths.")
|
|
165
|
+
}
|
|
166
|
+
if a.globals.DryRun {
|
|
167
|
+
existing, _ := service.Manager().List()
|
|
168
|
+
alias := identity.PreviewDefaultIdentityName(identityName, existing, name)
|
|
169
|
+
result := &identity.CommandResult{
|
|
170
|
+
Data: map[string]any{
|
|
171
|
+
"plan": map[string]any{
|
|
172
|
+
"action": "create_identity",
|
|
173
|
+
"identity_name": alias,
|
|
174
|
+
"display_name": name,
|
|
175
|
+
"writes": []string{
|
|
176
|
+
"index.json",
|
|
177
|
+
"identity.json",
|
|
178
|
+
"auth.json",
|
|
179
|
+
"did_document.json",
|
|
180
|
+
"key-1-private.pem",
|
|
181
|
+
"key-1-public.pem",
|
|
182
|
+
"e2ee-signing-private.pem",
|
|
183
|
+
"e2ee-agreement-private.pem",
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
Summary: "Dry run: local DID identity creation planned",
|
|
188
|
+
}
|
|
189
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
190
|
+
}
|
|
191
|
+
result, err := service.Create(name, identityName)
|
|
192
|
+
if err != nil {
|
|
193
|
+
return a.identityExit(err, "Use a different --identity value if the alias is already occupied.")
|
|
194
|
+
}
|
|
195
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
func (a *App) runIDRegister(cmd *cobra.Command, args []string) error {
|
|
199
|
+
handle, _ := cmd.Flags().GetString("handle")
|
|
200
|
+
phone, _ := cmd.Flags().GetString("phone")
|
|
201
|
+
email, _ := cmd.Flags().GetString("email")
|
|
202
|
+
otp, _ := cmd.Flags().GetString("otp")
|
|
203
|
+
inviteCode, _ := cmd.Flags().GetString("invite-code")
|
|
204
|
+
wait, _ := cmd.Flags().GetBool("wait")
|
|
205
|
+
service, format, err := a.identityService()
|
|
206
|
+
if err != nil {
|
|
207
|
+
return a.identityExit(err, "Run `awiki-cli doctor` to inspect configuration and storage paths.")
|
|
208
|
+
}
|
|
209
|
+
params := identity.RegisterParams{
|
|
210
|
+
IdentityName: a.globals.Identity,
|
|
211
|
+
Handle: handle,
|
|
212
|
+
Phone: phone,
|
|
213
|
+
Email: email,
|
|
214
|
+
OTP: otp,
|
|
215
|
+
InviteCode: inviteCode,
|
|
216
|
+
Wait: wait,
|
|
217
|
+
VerificationTimeout: identity.DefaultEmailVerificationSecs,
|
|
218
|
+
PollIntervalSeconds: identity.DefaultEmailPollIntervalSecs,
|
|
219
|
+
}
|
|
220
|
+
if a.globals.DryRun {
|
|
221
|
+
existing, _ := service.Manager().List()
|
|
222
|
+
alias := identity.PreviewNamedIdentity(a.globals.Identity, existing, handle)
|
|
223
|
+
action := "register_handle"
|
|
224
|
+
remoteCalls := []string{"did-auth.register"}
|
|
225
|
+
if phone != "" && strings.TrimSpace(otp) == "" {
|
|
226
|
+
action = "send_handle_otp"
|
|
227
|
+
remoteCalls = []string{"handle.send_otp"}
|
|
228
|
+
}
|
|
229
|
+
if email != "" && !wait {
|
|
230
|
+
action = "send_registration_email"
|
|
231
|
+
remoteCalls = []string{"POST /user-service/auth/email-send"}
|
|
232
|
+
}
|
|
233
|
+
if email != "" && wait {
|
|
234
|
+
remoteCalls = []string{"GET /user-service/auth/email-status", "POST /user-service/auth/email-send", "did-auth.register"}
|
|
235
|
+
}
|
|
236
|
+
result := &identity.CommandResult{
|
|
237
|
+
Data: map[string]any{
|
|
238
|
+
"plan": map[string]any{
|
|
239
|
+
"action": action,
|
|
240
|
+
"identity_name": alias,
|
|
241
|
+
"handle": handle,
|
|
242
|
+
"phone": phone,
|
|
243
|
+
"email": email,
|
|
244
|
+
"remote_calls": remoteCalls,
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
Summary: "Dry run: handle registration flow planned",
|
|
248
|
+
}
|
|
249
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
250
|
+
}
|
|
251
|
+
result, err := service.Register(context.Background(), params)
|
|
252
|
+
if err != nil {
|
|
253
|
+
return a.identityExit(err, "Ensure the handle, verification method, and local alias are valid.")
|
|
254
|
+
}
|
|
255
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
func (a *App) runIDBind(cmd *cobra.Command, args []string) error {
|
|
259
|
+
phone, _ := cmd.Flags().GetString("phone")
|
|
260
|
+
email, _ := cmd.Flags().GetString("email")
|
|
261
|
+
otp, _ := cmd.Flags().GetString("otp")
|
|
262
|
+
wait, _ := cmd.Flags().GetBool("wait")
|
|
263
|
+
service, format, err := a.identityService()
|
|
264
|
+
if err != nil {
|
|
265
|
+
return a.identityExit(err, "Run `awiki-cli id current` to confirm the active identity.")
|
|
266
|
+
}
|
|
267
|
+
params := identity.BindParams{
|
|
268
|
+
Phone: phone,
|
|
269
|
+
Email: email,
|
|
270
|
+
OTP: otp,
|
|
271
|
+
Wait: wait,
|
|
272
|
+
VerificationTimeout: identity.DefaultEmailVerificationSecs,
|
|
273
|
+
PollIntervalSeconds: identity.DefaultEmailPollIntervalSecs,
|
|
274
|
+
}
|
|
275
|
+
if a.globals.DryRun {
|
|
276
|
+
action := "bind_contact"
|
|
277
|
+
remoteCalls := []string{}
|
|
278
|
+
if phone != "" && strings.TrimSpace(otp) == "" {
|
|
279
|
+
action = "send_bind_phone_otp"
|
|
280
|
+
remoteCalls = []string{"POST /user-service/auth/phone-bind-send"}
|
|
281
|
+
} else if phone != "" {
|
|
282
|
+
action = "bind_phone"
|
|
283
|
+
remoteCalls = []string{"POST /user-service/auth/phone-bind-verify"}
|
|
284
|
+
} else if email != "" && !wait {
|
|
285
|
+
action = "send_bind_email"
|
|
286
|
+
remoteCalls = []string{"POST /user-service/auth/email-send"}
|
|
287
|
+
} else if email != "" {
|
|
288
|
+
action = "bind_email"
|
|
289
|
+
remoteCalls = []string{"GET /user-service/auth/email-status", "POST /user-service/auth/email-send"}
|
|
290
|
+
}
|
|
291
|
+
result := &identity.CommandResult{
|
|
292
|
+
Data: map[string]any{
|
|
293
|
+
"plan": map[string]any{
|
|
294
|
+
"action": action,
|
|
295
|
+
"phone": phone,
|
|
296
|
+
"email": email,
|
|
297
|
+
"remote_calls": remoteCalls,
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
Summary: "Dry run: contact binding flow planned",
|
|
301
|
+
}
|
|
302
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
303
|
+
}
|
|
304
|
+
result, err := service.Bind(context.Background(), params)
|
|
305
|
+
if err != nil {
|
|
306
|
+
return a.identityExit(err, "Use an identity that already has a valid JWT.")
|
|
307
|
+
}
|
|
308
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
func (a *App) runIDResolve(cmd *cobra.Command, args []string) error {
|
|
312
|
+
handle, _ := cmd.Flags().GetString("handle")
|
|
313
|
+
did, _ := cmd.Flags().GetString("did")
|
|
314
|
+
service, format, err := a.identityService()
|
|
315
|
+
if err != nil {
|
|
316
|
+
return a.identityExit(err, "Run `awiki-cli doctor` to inspect the configured service endpoints.")
|
|
317
|
+
}
|
|
318
|
+
result, err := service.Resolve(context.Background(), handle, did)
|
|
319
|
+
if err != nil {
|
|
320
|
+
return a.identityExit(err, "Provide either --handle or --did, and make sure the target exists.")
|
|
321
|
+
}
|
|
322
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
func (a *App) runIDRecover(cmd *cobra.Command, args []string) error {
|
|
326
|
+
handle, _ := cmd.Flags().GetString("handle")
|
|
327
|
+
phone, _ := cmd.Flags().GetString("phone")
|
|
328
|
+
otp, _ := cmd.Flags().GetString("otp")
|
|
329
|
+
service, format, err := a.identityService()
|
|
330
|
+
if err != nil {
|
|
331
|
+
return a.identityExit(err, "Run `awiki-cli doctor` to inspect the configured service endpoints.")
|
|
332
|
+
}
|
|
333
|
+
params := identity.RecoverParams{
|
|
334
|
+
IdentityName: a.globals.Identity,
|
|
335
|
+
Handle: handle,
|
|
336
|
+
Phone: phone,
|
|
337
|
+
OTP: otp,
|
|
338
|
+
}
|
|
339
|
+
if a.globals.DryRun {
|
|
340
|
+
existing, _ := service.Manager().List()
|
|
341
|
+
alias := identity.PreviewNamedIdentity(a.globals.Identity, existing, handle)
|
|
342
|
+
result := &identity.CommandResult{
|
|
343
|
+
Data: map[string]any{
|
|
344
|
+
"plan": map[string]any{
|
|
345
|
+
"action": "recover_handle",
|
|
346
|
+
"identity_name": alias,
|
|
347
|
+
"handle": handle,
|
|
348
|
+
"phone": phone,
|
|
349
|
+
"remote_calls": []string{"did-auth.recover_handle"},
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
Summary: "Dry run: handle recovery planned",
|
|
353
|
+
}
|
|
354
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
355
|
+
}
|
|
356
|
+
result, err := service.Recover(context.Background(), params)
|
|
357
|
+
if err != nil {
|
|
358
|
+
return a.identityExit(err, "Make sure the handle exists and the recovery OTP is valid.")
|
|
359
|
+
}
|
|
360
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
func (a *App) runIDProfileGet(cmd *cobra.Command, args []string) error {
|
|
364
|
+
self, _ := cmd.Flags().GetBool("self")
|
|
365
|
+
handle, _ := cmd.Flags().GetString("handle")
|
|
366
|
+
did, _ := cmd.Flags().GetString("did")
|
|
367
|
+
service, format, err := a.identityService()
|
|
368
|
+
if err != nil {
|
|
369
|
+
return a.identityExit(err, "Run `awiki-cli doctor` to inspect the configured service endpoints.")
|
|
370
|
+
}
|
|
371
|
+
result, err := service.GetProfile(context.Background(), self, handle, did)
|
|
372
|
+
if err != nil {
|
|
373
|
+
return a.identityExit(err, "Use `--self`, `--handle`, or `--did` to select one profile target.")
|
|
374
|
+
}
|
|
375
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
func (a *App) runIDProfileSet(cmd *cobra.Command, args []string) error {
|
|
379
|
+
displayName, _ := cmd.Flags().GetString("display-name")
|
|
380
|
+
bio, _ := cmd.Flags().GetString("bio")
|
|
381
|
+
tags, _ := cmd.Flags().GetString("tags")
|
|
382
|
+
markdown, _ := cmd.Flags().GetString("markdown")
|
|
383
|
+
markdownFile, _ := cmd.Flags().GetString("markdown-file")
|
|
384
|
+
if strings.TrimSpace(markdown) != "" && strings.TrimSpace(markdownFile) != "" {
|
|
385
|
+
return output.NewExitError("invalid_argument", 2, "Use either --markdown or --markdown-file, not both.", "Choose one profile body source.")
|
|
386
|
+
}
|
|
387
|
+
service, format, err := a.identityService()
|
|
388
|
+
if err != nil {
|
|
389
|
+
return a.identityExit(err, "Run `awiki-cli id current` to confirm the active identity.")
|
|
390
|
+
}
|
|
391
|
+
params := identity.UpdateProfileParams{
|
|
392
|
+
DisplayName: displayName,
|
|
393
|
+
Bio: bio,
|
|
394
|
+
TagsCSV: tags,
|
|
395
|
+
Markdown: markdown,
|
|
396
|
+
MarkdownFile: markdownFile,
|
|
397
|
+
}
|
|
398
|
+
if a.globals.DryRun {
|
|
399
|
+
result := &identity.CommandResult{
|
|
400
|
+
Data: map[string]any{
|
|
401
|
+
"plan": map[string]any{
|
|
402
|
+
"action": "update_profile",
|
|
403
|
+
"display_name": displayName,
|
|
404
|
+
"bio": bio,
|
|
405
|
+
"tags": tags,
|
|
406
|
+
"markdown": markdown,
|
|
407
|
+
"markdown_file": markdownFile,
|
|
408
|
+
"remote_calls": []string{"did.profile.update_me"},
|
|
409
|
+
"local_writes": []string{"identity.json", "index.json"},
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
Summary: "Dry run: profile update planned",
|
|
413
|
+
}
|
|
414
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
415
|
+
}
|
|
416
|
+
result, err := service.SetProfile(context.Background(), params)
|
|
417
|
+
if err != nil {
|
|
418
|
+
return a.identityExit(err, "Use an identity that already has a valid DID JWT.")
|
|
419
|
+
}
|
|
420
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
func (a *App) runIDImportV1(cmd *cobra.Command, args []string) error {
|
|
424
|
+
name, _ := cmd.Flags().GetString("name")
|
|
425
|
+
importAll, _ := cmd.Flags().GetBool("all")
|
|
426
|
+
service, format, err := a.identityService()
|
|
427
|
+
if err != nil {
|
|
428
|
+
return a.identityExit(err, "Run `awiki-cli doctor` to inspect the legacy credential paths.")
|
|
429
|
+
}
|
|
430
|
+
if a.globals.DryRun {
|
|
431
|
+
result := &identity.CommandResult{
|
|
432
|
+
Data: map[string]any{
|
|
433
|
+
"plan": map[string]any{
|
|
434
|
+
"action": "import_v1_identities",
|
|
435
|
+
"name": name,
|
|
436
|
+
"all": importAll,
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
Summary: "Dry run: v1 credential import planned",
|
|
440
|
+
}
|
|
441
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
442
|
+
}
|
|
443
|
+
result, err := service.ImportV1(name, importAll)
|
|
444
|
+
if err != nil {
|
|
445
|
+
return a.identityExit(err, "Run `awiki-cli doctor` to inspect the detected v1 credential layout.")
|
|
446
|
+
}
|
|
447
|
+
return a.renderIdentityResult(cmd, format, result)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
func identityMetaFromData(data map[string]any) *output.IdentityMeta {
|
|
451
|
+
if data == nil {
|
|
452
|
+
return nil
|
|
453
|
+
}
|
|
454
|
+
for _, key := range []string{"identity", "active_identity", "default_identity"} {
|
|
455
|
+
value, ok := data[key]
|
|
456
|
+
if !ok || value == nil {
|
|
457
|
+
continue
|
|
458
|
+
}
|
|
459
|
+
switch typed := value.(type) {
|
|
460
|
+
case *identity.IdentitySummary:
|
|
461
|
+
return &output.IdentityMeta{Name: typed.IdentityName, DID: typed.DID}
|
|
462
|
+
case identity.IdentitySummary:
|
|
463
|
+
return &output.IdentityMeta{Name: typed.IdentityName, DID: typed.DID}
|
|
464
|
+
case map[string]any:
|
|
465
|
+
name, _ := typed["identity_name"].(string)
|
|
466
|
+
did, _ := typed["did"].(string)
|
|
467
|
+
if name != "" || did != "" {
|
|
468
|
+
return &output.IdentityMeta{Name: name, DID: did}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return nil
|
|
473
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
package cli
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"strings"
|
|
7
|
+
|
|
8
|
+
appconfig "github.com/agentconnect/awiki-cli/internal/config"
|
|
9
|
+
"github.com/agentconnect/awiki-cli/internal/output"
|
|
10
|
+
"github.com/spf13/cobra"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
// runInit initializes the awiki-cli workdir and a minimal config.json.
|
|
14
|
+
//
|
|
15
|
+
// It is the primary entrypoint for users to discover and prepare the workdir:
|
|
16
|
+
// it uses the same root resolution as other commands (AWIKI_HOME override or
|
|
17
|
+
// the built-in default root) and ensures the directory layout and config.json
|
|
18
|
+
// are in place.
|
|
19
|
+
func (a *App) runInit(cmd *cobra.Command, args []string) error {
|
|
20
|
+
resolved, err := a.resolveConfig()
|
|
21
|
+
if err != nil {
|
|
22
|
+
return output.NewExitError(
|
|
23
|
+
"internal_error",
|
|
24
|
+
1,
|
|
25
|
+
err.Error(),
|
|
26
|
+
"Run `awiki-cli doctor` to inspect configuration and environment.",
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
format := normalizedFormat(resolved.OutputFormat)
|
|
31
|
+
rootSource := resolved.Sources["root_dir"]
|
|
32
|
+
|
|
33
|
+
dirs := []string{
|
|
34
|
+
resolved.Paths.RootDir,
|
|
35
|
+
filepath.Dir(resolved.Paths.ConfigFile),
|
|
36
|
+
resolved.Paths.DataDir,
|
|
37
|
+
resolved.Paths.StateDir,
|
|
38
|
+
resolved.Paths.CacheDir,
|
|
39
|
+
resolved.Paths.IdentityDir,
|
|
40
|
+
resolved.Paths.LogsDir,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if a.globals.DryRun {
|
|
44
|
+
data := map[string]any{
|
|
45
|
+
"plan": map[string]any{
|
|
46
|
+
"action": "init_workdir",
|
|
47
|
+
"root_dir": resolved.Paths.RootDir,
|
|
48
|
+
"root_source": rootSource,
|
|
49
|
+
"directories": dirs,
|
|
50
|
+
"config_file": resolved.Paths.ConfigFile,
|
|
51
|
+
"config_exists": resolved.ConfigExists,
|
|
52
|
+
"config_error": resolved.ConfigError,
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
return a.renderSuccess(
|
|
56
|
+
cmd.CommandPath(),
|
|
57
|
+
format,
|
|
58
|
+
a.globals.JQ,
|
|
59
|
+
data,
|
|
60
|
+
"Dry run: workdir initialization planned",
|
|
61
|
+
nil,
|
|
62
|
+
identityMetaFromResolved(resolved),
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Avoid silently overwriting a broken config file – require the user to
|
|
67
|
+
// fix or remove it first.
|
|
68
|
+
if resolved.ConfigError != "" {
|
|
69
|
+
return output.NewExitError(
|
|
70
|
+
"invalid_argument",
|
|
71
|
+
2,
|
|
72
|
+
"config.json exists but failed to parse; fix or remove it before running init.",
|
|
73
|
+
"Run `awiki-cli config show` to inspect the parse error, then correct the JSON syntax.",
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for _, dir := range dirs {
|
|
78
|
+
if strings.TrimSpace(dir) == "" {
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
if err := os.MkdirAll(dir, 0o700); err != nil {
|
|
82
|
+
return output.NewExitError(
|
|
83
|
+
"internal_error",
|
|
84
|
+
1,
|
|
85
|
+
err.Error(),
|
|
86
|
+
"Check directory permissions for the awiki-cli work directory.",
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// If there is no config.json yet, create a minimal one based on the
|
|
92
|
+
// currently resolved defaults so future reads remain consistent.
|
|
93
|
+
if !resolved.ConfigExists {
|
|
94
|
+
cfg := appconfig.FileConfig{}
|
|
95
|
+
cfg.Services.Domain = resolved.DIDDomain
|
|
96
|
+
cfg.Identity.Active = resolved.ActiveIdentity
|
|
97
|
+
cfg.Runtime.Mode = resolved.RuntimeMode
|
|
98
|
+
cfg.Output.Format = resolved.OutputFormat
|
|
99
|
+
noColor := resolved.NoColor
|
|
100
|
+
cfg.Output.NoColor = &noColor
|
|
101
|
+
cfg.Update.DisableStrictVersion = resolved.UpdateDisableStrictVersion
|
|
102
|
+
cfg.Update.MetadataCacheTTLSeconds = resolved.UpdateMetadataCacheTTLSeconds
|
|
103
|
+
|
|
104
|
+
if err := appconfig.WriteFileConfig(resolved.Paths.ConfigFile, cfg); err != nil {
|
|
105
|
+
return output.NewExitError(
|
|
106
|
+
"internal_error",
|
|
107
|
+
1,
|
|
108
|
+
err.Error(),
|
|
109
|
+
"Check write permissions for config.json under the awiki-cli work directory.",
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
resolved.ConfigExists = true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
result := map[string]any{
|
|
116
|
+
"workdir": map[string]any{
|
|
117
|
+
"root_dir": resolved.Paths.RootDir,
|
|
118
|
+
"root_source": rootSource,
|
|
119
|
+
"paths": resolved.Paths,
|
|
120
|
+
"config_file": resolved.Paths.ConfigFile,
|
|
121
|
+
"config_exists": resolved.ConfigExists,
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return a.renderSuccess(
|
|
126
|
+
cmd.CommandPath(),
|
|
127
|
+
format,
|
|
128
|
+
a.globals.JQ,
|
|
129
|
+
result,
|
|
130
|
+
"Workdir initialized",
|
|
131
|
+
nil,
|
|
132
|
+
identityMetaFromResolved(resolved),
|
|
133
|
+
)
|
|
134
|
+
}
|