@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.
Files changed (119) hide show
  1. package/.github/workflows/release.yml +44 -0
  2. package/.goreleaser.yml +44 -0
  3. package/AGENTS.md +60 -0
  4. package/CLAUDE.md +192 -0
  5. package/README.md +2 -0
  6. package/docs/architecture/awiki-command-v2.md +955 -0
  7. package/docs/architecture/awiki-skill-architecture.md +475 -0
  8. package/docs/architecture/awiki-v2-architecture.md +1063 -0
  9. package/docs/architecture//345/217/202/350/200/203/346/226/207/346/241/243/cli-init.md +1008 -0
  10. package/docs/architecture//345/217/202/350/200/203/346/226/207/346/241/243/output-format.md +407 -0
  11. package/docs/architecture//345/217/202/350/200/203/346/226/207/346/241/243/overall-init.md +741 -0
  12. package/docs/harness/review-spec.md +474 -0
  13. package/docs/installation.md +372 -0
  14. package/docs/plan/awiki-v2-implementation-plan.md +903 -0
  15. package/docs/plan/phase-0/adr-index.md +56 -0
  16. package/docs/plan/phase-0/audit-findings.md +251 -0
  17. package/docs/plan/phase-0/capability-mapping.md +108 -0
  18. package/docs/plan/phase-0/implementation-constraints.md +363 -0
  19. package/docs/publish.md +169 -0
  20. package/go.mod +29 -0
  21. package/go.sum +73 -0
  22. package/internal/anpsdk/registry.go +63 -0
  23. package/internal/authsdk/session.go +351 -0
  24. package/internal/buildinfo/buildinfo.go +34 -0
  25. package/internal/cli/app.go +136 -0
  26. package/internal/cli/app_test.go +88 -0
  27. package/internal/cli/debug.go +104 -0
  28. package/internal/cli/group.go +263 -0
  29. package/internal/cli/id.go +473 -0
  30. package/internal/cli/init.go +134 -0
  31. package/internal/cli/msg.go +228 -0
  32. package/internal/cli/page.go +267 -0
  33. package/internal/cli/root.go +499 -0
  34. package/internal/cli/runtime.go +232 -0
  35. package/internal/cli/upgrade.go +60 -0
  36. package/internal/cmdmeta/catalog.go +203 -0
  37. package/internal/cmdmeta/catalog_test.go +21 -0
  38. package/internal/config/config.go +399 -0
  39. package/internal/config/config_test.go +104 -0
  40. package/internal/config/write.go +37 -0
  41. package/internal/content/service.go +314 -0
  42. package/internal/content/service_test.go +165 -0
  43. package/internal/content/types.go +44 -0
  44. package/internal/docs/topics.go +110 -0
  45. package/internal/doctor/doctor.go +306 -0
  46. package/internal/identity/client.go +267 -0
  47. package/internal/identity/did.go +85 -0
  48. package/internal/identity/did_test.go +50 -0
  49. package/internal/identity/layout.go +206 -0
  50. package/internal/identity/legacy.go +378 -0
  51. package/internal/identity/public.go +70 -0
  52. package/internal/identity/public_test.go +73 -0
  53. package/internal/identity/readiness.go +74 -0
  54. package/internal/identity/service.go +826 -0
  55. package/internal/identity/store.go +385 -0
  56. package/internal/identity/store_test.go +180 -0
  57. package/internal/identity/types.go +204 -0
  58. package/internal/message/auth.go +167 -0
  59. package/internal/message/group_service.go +838 -0
  60. package/internal/message/group_wire.go +350 -0
  61. package/internal/message/group_wire_test.go +67 -0
  62. package/internal/message/helpers.go +61 -0
  63. package/internal/message/http_client.go +334 -0
  64. package/internal/message/proof.go +156 -0
  65. package/internal/message/proof_test.go +61 -0
  66. package/internal/message/service.go +696 -0
  67. package/internal/message/service_test.go +97 -0
  68. package/internal/message/types.go +155 -0
  69. package/internal/message/wire.go +100 -0
  70. package/internal/message/wire_test.go +49 -0
  71. package/internal/message/ws_proxy_client.go +151 -0
  72. package/internal/output/output.go +350 -0
  73. package/internal/output/output_test.go +48 -0
  74. package/internal/runtime/config.go +117 -0
  75. package/internal/runtime/config_test.go +46 -0
  76. package/internal/runtime/listener/files.go +65 -0
  77. package/internal/runtime/listener/manager.go +142 -0
  78. package/internal/runtime/listener/server.go +983 -0
  79. package/internal/runtime/listener/server_test.go +319 -0
  80. package/internal/runtime/listener/sysproc_unix.go +17 -0
  81. package/internal/runtime/listener/sysproc_windows.go +13 -0
  82. package/internal/runtime/listener/types.go +21 -0
  83. package/internal/runtime/listener/wsclient.go +299 -0
  84. package/internal/runtime/listener/wsclient_test.go +41 -0
  85. package/internal/store/dao.go +632 -0
  86. package/internal/store/dao_test.go +87 -0
  87. package/internal/store/helpers.go +197 -0
  88. package/internal/store/import.go +499 -0
  89. package/internal/store/import_test.go +103 -0
  90. package/internal/store/open.go +71 -0
  91. package/internal/store/query.go +151 -0
  92. package/internal/store/schema.go +277 -0
  93. package/internal/store/schema_test.go +56 -0
  94. package/internal/store/types.go +177 -0
  95. package/internal/update/update.go +368 -0
  96. package/package.json +17 -0
  97. package/scripts/install.js +171 -0
  98. package/scripts/release/release-prerelease.sh +86 -0
  99. package/scripts/release/tag-release.sh +66 -0
  100. package/scripts/release/withdraw-release.sh +78 -0
  101. package/scripts/run.js +69 -0
  102. package/skills/README.md +32 -0
  103. package/skills/awiki-bundle/SKILL.md +76 -0
  104. package/skills/awiki-debug/SKILL.md +80 -0
  105. package/skills/awiki-group/SKILL.md +111 -0
  106. package/skills/awiki-id/SKILL.md +123 -0
  107. package/skills/awiki-msg/SKILL.md +131 -0
  108. package/skills/awiki-page/SKILL.md +93 -0
  109. package/skills/awiki-people/SKILL.md +66 -0
  110. package/skills/awiki-runtime/SKILL.md +137 -0
  111. package/skills/awiki-shared/SKILL.md +124 -0
  112. package/skills/awiki-workflow-discovery/SKILL.md +93 -0
  113. package/skills/awiki-workflow-onboarding/SKILL.md +119 -0
  114. package/skills/manifests/skills.yaml +260 -0
  115. package/skills/templates/bundle-skill-template.md +42 -0
  116. package/skills/templates/debug-skill-template.md +44 -0
  117. package/skills/templates/domain-skill-template.md +56 -0
  118. package/skills/templates/shared-skill-template.md +46 -0
  119. package/skills/templates/workflow-skill-template.md +46 -0
@@ -0,0 +1,314 @@
1
+ package content
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "fmt"
7
+ "strings"
8
+ "time"
9
+
10
+ "github.com/agentconnect/awiki-cli/internal/authsdk"
11
+ appconfig "github.com/agentconnect/awiki-cli/internal/config"
12
+ "github.com/agentconnect/awiki-cli/internal/identity"
13
+ )
14
+
15
+ type identityServiceError = identity.ServiceError
16
+
17
+ type Service struct {
18
+ config *appconfig.Resolved
19
+ manager *identity.Manager
20
+ remote *identity.RemoteClient
21
+ }
22
+
23
+ func NewService(resolved *appconfig.Resolved) (*Service, error) {
24
+ remote, err := identity.NewRemoteClient(resolved)
25
+ if err != nil {
26
+ return nil, err
27
+ }
28
+ return &Service{
29
+ config: resolved,
30
+ manager: identity.NewManager(resolved.Paths),
31
+ remote: remote,
32
+ }, nil
33
+ }
34
+
35
+ func (s *Service) Config() *appconfig.Resolved {
36
+ return s.config
37
+ }
38
+
39
+ func (s *Service) CreatePage(ctx context.Context, params CreatePageParams) (*CommandResult, error) {
40
+ record, auth, err := s.requireAuth(ctx)
41
+ if err != nil {
42
+ return nil, err
43
+ }
44
+ slug := strings.TrimSpace(params.Slug)
45
+ title := strings.TrimSpace(params.Title)
46
+ if slug == "" {
47
+ return nil, ErrSlugRequired
48
+ }
49
+ if title == "" {
50
+ return nil, ErrTitleRequired
51
+ }
52
+ visibility, err := normalizeVisibility(params.Visibility, false)
53
+ if err != nil {
54
+ return nil, err
55
+ }
56
+ payload := map[string]any{
57
+ "slug": slug,
58
+ "title": title,
59
+ "body": params.Body,
60
+ }
61
+ if visibility != "" {
62
+ payload["visibility"] = visibility
63
+ }
64
+ var result map[string]any
65
+ if err := s.remote.AuthenticatedRPCCall(ctx, contentRPCEndpoint, "create", payload, auth, &result); err != nil {
66
+ return nil, err
67
+ }
68
+ return &CommandResult{
69
+ Data: map[string]any{
70
+ "action": "create_page",
71
+ "identity": identitySummaryFromRecord(record),
72
+ "page": result,
73
+ },
74
+ Summary: fmt.Sprintf("Created content page %s", slug),
75
+ }, nil
76
+ }
77
+
78
+ func (s *Service) ListPages(ctx context.Context) (*CommandResult, error) {
79
+ record, auth, err := s.requireAuth(ctx)
80
+ if err != nil {
81
+ return nil, err
82
+ }
83
+ var result map[string]any
84
+ if err := s.remote.AuthenticatedRPCCall(ctx, contentRPCEndpoint, "list", map[string]any{}, auth, &result); err != nil {
85
+ return nil, err
86
+ }
87
+ count := 0
88
+ switch typed := result["count"].(type) {
89
+ case float64:
90
+ count = int(typed)
91
+ case int:
92
+ count = typed
93
+ }
94
+ return &CommandResult{
95
+ Data: map[string]any{
96
+ "action": "list_pages",
97
+ "identity": identitySummaryFromRecord(record),
98
+ "pages": result["pages"],
99
+ "count": count,
100
+ },
101
+ Summary: fmt.Sprintf("Fetched %d content pages", count),
102
+ }, nil
103
+ }
104
+
105
+ func (s *Service) GetPage(ctx context.Context, slug string) (*CommandResult, error) {
106
+ record, auth, err := s.requireAuth(ctx)
107
+ if err != nil {
108
+ return nil, err
109
+ }
110
+ slug = strings.TrimSpace(slug)
111
+ if slug == "" {
112
+ return nil, ErrSlugRequired
113
+ }
114
+ var result map[string]any
115
+ if err := s.remote.AuthenticatedRPCCall(ctx, contentRPCEndpoint, "get", map[string]any{"slug": slug}, auth, &result); err != nil {
116
+ return nil, err
117
+ }
118
+ return &CommandResult{
119
+ Data: map[string]any{
120
+ "action": "get_page",
121
+ "identity": identitySummaryFromRecord(record),
122
+ "page": result,
123
+ },
124
+ Summary: fmt.Sprintf("Fetched content page %s", slug),
125
+ }, nil
126
+ }
127
+
128
+ func (s *Service) UpdatePage(ctx context.Context, params UpdatePageParams) (*CommandResult, error) {
129
+ record, auth, err := s.requireAuth(ctx)
130
+ if err != nil {
131
+ return nil, err
132
+ }
133
+ slug := strings.TrimSpace(params.Slug)
134
+ if slug == "" {
135
+ return nil, ErrSlugRequired
136
+ }
137
+ payload := map[string]any{"slug": slug}
138
+ changedFields := make([]string, 0, 3)
139
+ if title := strings.TrimSpace(params.Title); title != "" {
140
+ payload["title"] = title
141
+ changedFields = append(changedFields, "title")
142
+ }
143
+ if params.Body != nil {
144
+ payload["body"] = *params.Body
145
+ changedFields = append(changedFields, "body")
146
+ }
147
+ if params.Visibility != nil {
148
+ visibility, err := normalizeVisibility(*params.Visibility, true)
149
+ if err != nil {
150
+ return nil, err
151
+ }
152
+ payload["visibility"] = visibility
153
+ changedFields = append(changedFields, "visibility")
154
+ }
155
+ if len(changedFields) == 0 {
156
+ return nil, ErrNoUpdateFields
157
+ }
158
+ var result map[string]any
159
+ if err := s.remote.AuthenticatedRPCCall(ctx, contentRPCEndpoint, "update", payload, auth, &result); err != nil {
160
+ return nil, err
161
+ }
162
+ return &CommandResult{
163
+ Data: map[string]any{
164
+ "action": "update_page",
165
+ "identity": identitySummaryFromRecord(record),
166
+ "changed_fields": changedFields,
167
+ "page": result,
168
+ },
169
+ Summary: fmt.Sprintf("Updated content page %s", slug),
170
+ }, nil
171
+ }
172
+
173
+ func (s *Service) RenamePage(ctx context.Context, params RenamePageParams) (*CommandResult, error) {
174
+ record, auth, err := s.requireAuth(ctx)
175
+ if err != nil {
176
+ return nil, err
177
+ }
178
+ slug := strings.TrimSpace(params.Slug)
179
+ target := strings.TrimSpace(params.To)
180
+ if slug == "" || target == "" {
181
+ return nil, ErrSlugRequired
182
+ }
183
+ var result map[string]any
184
+ payload := map[string]any{"old_slug": slug, "new_slug": target}
185
+ if err := s.remote.AuthenticatedRPCCall(ctx, contentRPCEndpoint, "rename", payload, auth, &result); err != nil {
186
+ return nil, err
187
+ }
188
+ return &CommandResult{
189
+ Data: map[string]any{
190
+ "action": "rename_page",
191
+ "identity": identitySummaryFromRecord(record),
192
+ "from": slug,
193
+ "to": target,
194
+ "page": result,
195
+ },
196
+ Summary: fmt.Sprintf("Renamed content page %s to %s", slug, target),
197
+ }, nil
198
+ }
199
+
200
+ func (s *Service) DeletePage(ctx context.Context, slug string) (*CommandResult, error) {
201
+ record, auth, err := s.requireAuth(ctx)
202
+ if err != nil {
203
+ return nil, err
204
+ }
205
+ slug = strings.TrimSpace(slug)
206
+ if slug == "" {
207
+ return nil, ErrSlugRequired
208
+ }
209
+ var result map[string]any
210
+ if err := s.remote.AuthenticatedRPCCall(ctx, contentRPCEndpoint, "delete", map[string]any{"slug": slug}, auth, &result); err != nil {
211
+ return nil, err
212
+ }
213
+ return &CommandResult{
214
+ Data: map[string]any{
215
+ "action": "delete_page",
216
+ "identity": identitySummaryFromRecord(record),
217
+ "slug": slug,
218
+ "result": result,
219
+ },
220
+ Summary: fmt.Sprintf("Deleted content page %s", slug),
221
+ }, nil
222
+ }
223
+
224
+ func (s *Service) requireAuth(ctx context.Context) (*identity.StoredIdentity, *authsdk.Session, error) {
225
+ record, err := s.requireActiveIdentity()
226
+ if err != nil {
227
+ return nil, nil, err
228
+ }
229
+ session, err := s.authSession(record)
230
+ if err != nil {
231
+ return nil, nil, err
232
+ }
233
+ return record, session, nil
234
+ }
235
+
236
+ func (s *Service) requireActiveIdentity() (*identity.StoredIdentity, error) {
237
+ if strings.TrimSpace(s.config.ActiveIdentity) == "" {
238
+ current, err := s.manager.Current()
239
+ if err != nil {
240
+ if errors.Is(err, identity.ErrNoDefaultIdentity) {
241
+ return nil, fmt.Errorf("%w: no active identity is configured", identity.ErrIdentityNotFound)
242
+ }
243
+ return nil, err
244
+ }
245
+ s.config.ActiveIdentity = current.IdentityName
246
+ }
247
+ record, err := s.manager.Load(s.config.ActiveIdentity)
248
+ if err != nil {
249
+ return nil, err
250
+ }
251
+ return record, nil
252
+ }
253
+
254
+ func (s *Service) authSession(record *identity.StoredIdentity) (*authsdk.Session, error) {
255
+ if record == nil {
256
+ return nil, fmt.Errorf("%w: active identity is required", identity.ErrAuthRequired)
257
+ }
258
+ paths, err := s.manager.PathsForIdentity(record.IdentityName)
259
+ if err != nil {
260
+ return nil, err
261
+ }
262
+ session := authsdk.NewSession(
263
+ paths.DIDDocumentPath,
264
+ paths.Key1PrivatePath,
265
+ record.IdentityName,
266
+ record.DID,
267
+ record.JWTToken,
268
+ func(token string) error { return s.manager.UpdateJWT(record.IdentityName, token) },
269
+ )
270
+ session.SetBearer(s.config.UserServiceURL, record.JWTToken)
271
+ if strings.TrimSpace(record.JWTToken) == "" {
272
+ ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
273
+ defer cancel()
274
+ if _, err := session.EnsureJWT(ctx, s.remote.Client(), strings.TrimRight(s.config.UserServiceURL, "/")+didAuthRPCEndpoint); err != nil {
275
+ var httpErr *authsdk.HTTPError
276
+ if errors.As(err, &httpErr) {
277
+ return nil, &identityServiceError{StatusCode: httpErr.StatusCode, Message: httpErr.Message}
278
+ }
279
+ var rpcErr *authsdk.RPCError
280
+ if errors.As(err, &rpcErr) {
281
+ return nil, &identityServiceError{RPCCode: rpcErr.Code, Message: rpcErr.Message, Data: rpcErr.Data}
282
+ }
283
+ return nil, err
284
+ }
285
+ }
286
+ return session, nil
287
+ }
288
+
289
+ func normalizeVisibility(value string, emptyAllowed bool) (string, error) {
290
+ visibility := strings.ToLower(strings.TrimSpace(value))
291
+ if visibility == "" && emptyAllowed {
292
+ return "", nil
293
+ }
294
+ if visibility == "" {
295
+ return "public", nil
296
+ }
297
+ switch visibility {
298
+ case "public", "draft", "unlisted":
299
+ return visibility, nil
300
+ default:
301
+ return "", ErrVisibilityInvalid
302
+ }
303
+ }
304
+
305
+ func identitySummaryFromRecord(record *identity.StoredIdentity) map[string]any {
306
+ if record == nil {
307
+ return nil
308
+ }
309
+ return map[string]any{
310
+ "identity_name": record.IdentityName,
311
+ "did": record.DID,
312
+ "handle": record.Handle,
313
+ }
314
+ }
@@ -0,0 +1,165 @@
1
+ package content
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "net/http"
7
+ "net/http/httptest"
8
+ "path/filepath"
9
+ "testing"
10
+
11
+ appconfig "github.com/agentconnect/awiki-cli/internal/config"
12
+ "github.com/agentconnect/awiki-cli/internal/identity"
13
+ )
14
+
15
+ func TestCreatePageCallsContentRPC(t *testing.T) {
16
+ t.Parallel()
17
+
18
+ var (
19
+ gotMethod string
20
+ gotAuth string
21
+ gotParams map[string]any
22
+ )
23
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24
+ gotAuth = r.Header.Get("Authorization")
25
+ if r.URL.Path != contentRPCEndpoint {
26
+ t.Fatalf("r.URL.Path = %q, want %q", r.URL.Path, contentRPCEndpoint)
27
+ }
28
+ var payload map[string]any
29
+ if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
30
+ t.Fatalf("Decode() error = %v", err)
31
+ }
32
+ gotMethod, _ = payload["method"].(string)
33
+ gotParams, _ = payload["params"].(map[string]any)
34
+ w.Header().Set("Content-Type", "application/json")
35
+ _, _ = w.Write([]byte(`{"jsonrpc":"2.0","result":{"slug":"hello-world","title":"Hello","visibility":"draft"},"id":"req-1"}`))
36
+ }))
37
+ defer server.Close()
38
+
39
+ service := newTestService(t, server.URL, "token-123")
40
+ result, err := service.CreatePage(context.Background(), CreatePageParams{
41
+ Slug: "hello-world",
42
+ Title: "Hello",
43
+ Body: "# Hello",
44
+ Visibility: "draft",
45
+ })
46
+ if err != nil {
47
+ t.Fatalf("CreatePage() error = %v", err)
48
+ }
49
+ if gotMethod != "create" {
50
+ t.Fatalf("rpc method = %q, want create", gotMethod)
51
+ }
52
+ if gotAuth != "Bearer token-123" {
53
+ t.Fatalf("Authorization = %q, want Bearer token-123", gotAuth)
54
+ }
55
+ if got, _ := gotParams["visibility"].(string); got != "draft" {
56
+ t.Fatalf("params.visibility = %q, want draft", got)
57
+ }
58
+ page, _ := result.Data["page"].(map[string]any)
59
+ if got, _ := page["slug"].(string); got != "hello-world" {
60
+ t.Fatalf("page.slug = %q, want hello-world", got)
61
+ }
62
+ }
63
+
64
+ func TestUpdatePageRejectsEmptyMutation(t *testing.T) {
65
+ t.Parallel()
66
+
67
+ service := newTestService(t, "https://awiki.test", "token-123")
68
+ _, err := service.UpdatePage(context.Background(), UpdatePageParams{Slug: "hello-world"})
69
+ if err != ErrNoUpdateFields {
70
+ t.Fatalf("UpdatePage() error = %v, want %v", err, ErrNoUpdateFields)
71
+ }
72
+ }
73
+
74
+ func TestDeletePageMapsRPCError(t *testing.T) {
75
+ t.Parallel()
76
+
77
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
78
+ w.Header().Set("Content-Type", "application/json")
79
+ _, _ = w.Write([]byte(`{"jsonrpc":"2.0","error":{"code":-32004,"message":"slug already exists"},"id":"req-1"}`))
80
+ }))
81
+ defer server.Close()
82
+
83
+ service := newTestService(t, server.URL, "token-123")
84
+ _, err := service.DeletePage(context.Background(), "hello-world")
85
+ if err == nil {
86
+ t.Fatal("DeletePage() error = nil, want rpc error")
87
+ }
88
+ serviceErr, ok := err.(*ServiceError)
89
+ if !ok {
90
+ t.Fatalf("DeletePage() error = %T, want *ServiceError", err)
91
+ }
92
+ if serviceErr.RPCCode != -32004 {
93
+ t.Fatalf("serviceErr.RPCCode = %d, want -32004", serviceErr.RPCCode)
94
+ }
95
+ }
96
+
97
+ func TestNormalizeVisibility(t *testing.T) {
98
+ t.Parallel()
99
+
100
+ got, err := normalizeVisibility("", false)
101
+ if err != nil || got != "public" {
102
+ t.Fatalf("normalizeVisibility('', false) = (%q, %v), want (public, nil)", got, err)
103
+ }
104
+ got, err = normalizeVisibility("UNLISTED", false)
105
+ if err != nil || got != "unlisted" {
106
+ t.Fatalf("normalizeVisibility('UNLISTED', false) = (%q, %v), want (unlisted, nil)", got, err)
107
+ }
108
+ if _, err := normalizeVisibility("private", false); err != ErrVisibilityInvalid {
109
+ t.Fatalf("normalizeVisibility('private', false) error = %v, want %v", err, ErrVisibilityInvalid)
110
+ }
111
+ }
112
+
113
+ func newTestService(t *testing.T, userServiceURL string, jwtToken string) *Service {
114
+ t.Helper()
115
+
116
+ root := t.TempDir()
117
+ resolved := &appconfig.Resolved{
118
+ Paths: appconfig.Paths{
119
+ IdentityDir: filepath.Join(root, "identities"),
120
+ LegacyCredentialsDir: filepath.Join(root, "legacy"),
121
+ DataDir: filepath.Join(root, "data"),
122
+ StateDir: filepath.Join(root, "state"),
123
+ DatabaseFile: filepath.Join(root, "data", "awiki-cli.db"),
124
+ },
125
+ UserServiceURL: userServiceURL,
126
+ MessageServiceURL: userServiceURL,
127
+ DIDDomain: "awiki.ai",
128
+ ActiveIdentity: "alice",
129
+ }
130
+ manager := identity.NewManager(resolved.Paths)
131
+ createTestIdentity(t, manager, identity.SaveInput{
132
+ IdentityName: "alice",
133
+ DisplayName: "Alice",
134
+ Handle: "alice",
135
+ JWTToken: jwtToken,
136
+ })
137
+ service, err := NewService(resolved)
138
+ if err != nil {
139
+ t.Fatalf("NewService() error = %v", err)
140
+ }
141
+ return service
142
+ }
143
+
144
+ func createTestIdentity(t *testing.T, manager *identity.Manager, input identity.SaveInput) {
145
+ t.Helper()
146
+
147
+ generated, err := identity.GenerateIdentity(identity.GenerateOptions{
148
+ Hostname: "awiki.ai",
149
+ PathPrefix: []string{"user"},
150
+ ProofDomain: "awiki.ai",
151
+ })
152
+ if err != nil {
153
+ t.Fatalf("GenerateIdentity() error = %v", err)
154
+ }
155
+ input.DID = generated.DID
156
+ input.UniqueID = generated.UniqueID
157
+ input.DIDDocument = generated.DIDDocument
158
+ input.Key1PrivatePEM = generated.Key1PrivatePEM
159
+ input.Key1PublicPEM = generated.Key1PublicPEM
160
+ input.E2EESigningPrivatePEM = generated.E2EESigningPrivatePEM
161
+ input.E2EEAgreementPrivatePEM = generated.E2EEAgreementPrivatePEM
162
+ if _, err := manager.Save(input); err != nil {
163
+ t.Fatalf("Save() error = %v", err)
164
+ }
165
+ }
@@ -0,0 +1,44 @@
1
+ package content
2
+
3
+ import "errors"
4
+
5
+ const (
6
+ contentRPCEndpoint = "/content/rpc"
7
+ didAuthRPCEndpoint = "/user-service/did-auth/rpc"
8
+ )
9
+
10
+ var (
11
+ ErrSlugRequired = errors.New("slug is required")
12
+ ErrTitleRequired = errors.New("title is required")
13
+ ErrBodySourceConflict = errors.New("use either inline markdown or markdown file, not both")
14
+ ErrNoUpdateFields = errors.New("no update fields were provided")
15
+ ErrVisibilityInvalid = errors.New("visibility must be one of public, draft, or unlisted")
16
+ ErrAuthIdentityRequired = errors.New("active identity is required")
17
+ )
18
+
19
+ type ServiceError = identityServiceError
20
+
21
+ type CommandResult struct {
22
+ Data map[string]any
23
+ Summary string
24
+ Warnings []string
25
+ }
26
+
27
+ type CreatePageParams struct {
28
+ Slug string
29
+ Title string
30
+ Body string
31
+ Visibility string
32
+ }
33
+
34
+ type UpdatePageParams struct {
35
+ Slug string
36
+ Title string
37
+ Body *string
38
+ Visibility *string
39
+ }
40
+
41
+ type RenamePageParams struct {
42
+ Slug string
43
+ To string
44
+ }
@@ -0,0 +1,110 @@
1
+ package docs
2
+
3
+ import "strings"
4
+
5
+ type Topic struct {
6
+ Name string `json:"name"`
7
+ Summary string `json:"summary"`
8
+ References []string `json:"references"`
9
+ }
10
+
11
+ type Index struct {
12
+ topics map[string]Topic
13
+ list []Topic
14
+ }
15
+
16
+ func NewIndex() *Index {
17
+ list := []Topic{
18
+ {
19
+ Name: "overview",
20
+ Summary: "Project-level implementation overview and roadmap",
21
+ References: []string{
22
+ "docs/plan/awiki-v2-implementation-plan.md",
23
+ },
24
+ },
25
+ {
26
+ Name: "phase-0",
27
+ Summary: "Frozen implementation constraints and audit outputs",
28
+ References: []string{
29
+ "docs/plan/phase-0/implementation-constraints.md",
30
+ "docs/plan/phase-0/capability-mapping.md",
31
+ "docs/plan/phase-0/audit-findings.md",
32
+ "docs/plan/phase-0/adr-index.md",
33
+ },
34
+ },
35
+ {
36
+ Name: "architecture",
37
+ Summary: "Overall v2 architecture and command model",
38
+ References: []string{
39
+ "docs/architecture/awiki-v2-architecture.md",
40
+ "docs/architecture/awiki-command-v2.md",
41
+ "docs/architecture/awiki-skill-architecture.md",
42
+ },
43
+ },
44
+ {
45
+ Name: "skills",
46
+ Summary: "Skill topology, manifests, templates, and awiki skill entrypoints",
47
+ References: []string{
48
+ "docs/architecture/awiki-skill-architecture.md",
49
+ "skills/README.md",
50
+ "skills/manifests/skills.yaml",
51
+ },
52
+ },
53
+ {
54
+ Name: "output",
55
+ Summary: "Output contract, dry-run, schema, and exit code rules",
56
+ References: []string{
57
+ "docs/architecture/output-format.md",
58
+ },
59
+ },
60
+ {
61
+ Name: "review",
62
+ Summary: "Secondary review checklist and dependency map for PR review",
63
+ References: []string{
64
+ "docs/harness/review-spec.md",
65
+ "docs/plan/phase-0/implementation-constraints.md",
66
+ "docs/plan/phase-0/audit-findings.md",
67
+ },
68
+ },
69
+ {
70
+ Name: "storage",
71
+ Summary: "Identity layout and SQLite baseline references",
72
+ References: []string{
73
+ "docs/plan/phase-0/implementation-constraints.md",
74
+ "../awiki-agent-id-message/scripts/credential_layout.py",
75
+ "../awiki-agent-id-message/scripts/local_store.py",
76
+ "../awiki-agent-id-message/references/local-store-schema.md",
77
+ },
78
+ },
79
+ {
80
+ Name: "runtime",
81
+ Summary: "Runtime mode, listener, heartbeat, and migration references",
82
+ References: []string{
83
+ "docs/architecture/awiki-v2-architecture.md",
84
+ "../awiki-agent-id-message/scripts/setup_realtime.py",
85
+ "../awiki-agent-id-message/scripts/ws_listener.py",
86
+ "../awiki-agent-id-message/references/WEBSOCKET_LISTENER.md",
87
+ },
88
+ },
89
+ }
90
+ index := &Index{topics: make(map[string]Topic, len(list)), list: list}
91
+ for _, topic := range list {
92
+ index.topics[strings.ToLower(topic.Name)] = topic
93
+ }
94
+ return index
95
+ }
96
+
97
+ func (i *Index) All() []Topic {
98
+ if i == nil {
99
+ return nil
100
+ }
101
+ return append([]Topic(nil), i.list...)
102
+ }
103
+
104
+ func (i *Index) Lookup(name string) (Topic, bool) {
105
+ if i == nil {
106
+ return Topic{}, false
107
+ }
108
+ topic, ok := i.topics[strings.ToLower(strings.TrimSpace(name))]
109
+ return topic, ok
110
+ }