hoja-cli 0.21.4__py3-none-any.whl

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 (107) hide show
  1. VERSION +1 -0
  2. google_workspace/__init__.py +19 -0
  3. google_workspace/client.py +253 -0
  4. google_workspace/config.py +39 -0
  5. google_workspace/errors.py +19 -0
  6. google_workspace/mcp_server.py +100 -0
  7. google_workspace/oauth.py +131 -0
  8. google_workspace/principal.py +83 -0
  9. google_workspace/py.typed +0 -0
  10. google_workspace/tools.py +64 -0
  11. google_workspace/writes.py +44 -0
  12. hoja/__init__.py +29 -0
  13. hoja/__main__.py +6 -0
  14. hoja/auth.py +42 -0
  15. hoja/cli.py +161 -0
  16. hoja/cli_state.py +55 -0
  17. hoja/client.py +268 -0
  18. hoja/commands/__init__.py +4 -0
  19. hoja/commands/auth_cmd.py +271 -0
  20. hoja/commands/comment.py +149 -0
  21. hoja/commands/cycle.py +75 -0
  22. hoja/commands/init.py +108 -0
  23. hoja/commands/issue.py +398 -0
  24. hoja/commands/label.py +163 -0
  25. hoja/commands/memory.py +240 -0
  26. hoja/commands/project.py +56 -0
  27. hoja/commands/raw.py +50 -0
  28. hoja/commands/team.py +102 -0
  29. hoja/commands/update.py +162 -0
  30. hoja/config.py +362 -0
  31. hoja/errors.py +107 -0
  32. hoja/filters.py +150 -0
  33. hoja/graphql/__init__.py +5 -0
  34. hoja/graphql/fragments.py +138 -0
  35. hoja/graphql/mutations.py +160 -0
  36. hoja/graphql/queries.py +245 -0
  37. hoja/mcp/__init__.py +27 -0
  38. hoja/mcp/content.py +151 -0
  39. hoja/mcp/prompts.py +298 -0
  40. hoja/mcp/resources.py +213 -0
  41. hoja/mcp/server.py +189 -0
  42. hoja/memory_board.py +370 -0
  43. hoja/models/__init__.py +37 -0
  44. hoja/models/base.py +64 -0
  45. hoja/models/cycle.py +32 -0
  46. hoja/models/enums.py +62 -0
  47. hoja/models/issue.py +99 -0
  48. hoja/models/label.py +17 -0
  49. hoja/models/project.py +60 -0
  50. hoja/models/team.py +31 -0
  51. hoja/models/user.py +32 -0
  52. hoja/models/workflow.py +65 -0
  53. hoja/output.py +410 -0
  54. hoja/skill_content/commands.md +222 -0
  55. hoja/skill_content/examples.md +193 -0
  56. hoja/skill_content/filters.md +101 -0
  57. hoja/skill_content/output-formats.md +92 -0
  58. hoja/skill_content/overview.md +123 -0
  59. hoja/skill_content/workflows.md +174 -0
  60. hoja_cli-0.21.4.dist-info/METADATA +279 -0
  61. hoja_cli-0.21.4.dist-info/RECORD +107 -0
  62. hoja_cli-0.21.4.dist-info/WHEEL +4 -0
  63. hoja_cli-0.21.4.dist-info/entry_points.txt +4 -0
  64. hoja_cli-0.21.4.dist-info/licenses/LICENSE +21 -0
  65. hoja_server/README.md +135 -0
  66. hoja_server/__init__.py +11 -0
  67. hoja_server/abac.py +116 -0
  68. hoja_server/agent_chat.py +280 -0
  69. hoja_server/agent_conversations.py +91 -0
  70. hoja_server/agent_durable.py +332 -0
  71. hoja_server/agent_prompt.py +117 -0
  72. hoja_server/agent_threads.py +96 -0
  73. hoja_server/agent_uploads.py +60 -0
  74. hoja_server/app.py +2189 -0
  75. hoja_server/cadence.py +231 -0
  76. hoja_server/cli.py +191 -0
  77. hoja_server/cloudflare_realtime.py +110 -0
  78. hoja_server/comment_policy.py +70 -0
  79. hoja_server/connector_actions.py +119 -0
  80. hoja_server/connectors_api.py +375 -0
  81. hoja_server/db.py +1816 -0
  82. hoja_server/denials.py +18 -0
  83. hoja_server/deployment.py +28 -0
  84. hoja_server/email_adapter.py +94 -0
  85. hoja_server/google_login.py +217 -0
  86. hoja_server/google_oidc.py +342 -0
  87. hoja_server/identity_transfer.py +210 -0
  88. hoja_server/mcp_oauth.py +623 -0
  89. hoja_server/mcp_tools.py +579 -0
  90. hoja_server/meeting_policy.py +179 -0
  91. hoja_server/meetings.py +1332 -0
  92. hoja_server/meetings_api.py +909 -0
  93. hoja_server/model_providers.py +205 -0
  94. hoja_server/realtime.py +690 -0
  95. hoja_server/resolvers.py +1281 -0
  96. hoja_server/schema.graphql +477 -0
  97. hoja_server/schema_sdl.py +8 -0
  98. hoja_server/secrets_box.py +39 -0
  99. hoja_server/signup_config.py +292 -0
  100. hoja_server/signup_gate.py +393 -0
  101. hoja_server/signup_policy.py +97 -0
  102. hoja_server/signup_store.py +577 -0
  103. hoja_server/store.py +2179 -0
  104. hoja_server/sync.py +249 -0
  105. hoja_server/tenant_context.py +131 -0
  106. hoja_server/turnstile.py +81 -0
  107. hoja_server/writer.py +1262 -0
@@ -0,0 +1,477 @@
1
+ # Linear-compatible GraphQL SDL — the subset hoja actually uses.
2
+ # Field names are EXACT camelCase; hoja's Pydantic models alias to these.
3
+
4
+ scalar DateTime
5
+ scalar TimelessDate
6
+ scalar JSON
7
+
8
+ type Organization {
9
+ id: ID!
10
+ name: String!
11
+ urlKey: String!
12
+ }
13
+
14
+ type ApiKey {
15
+ id: ID!
16
+ label: String
17
+ access: String
18
+ hint: String
19
+ expiresAt: DateTime
20
+ lastUsedAt: DateTime
21
+ createdAt: DateTime
22
+ revokedAt: DateTime
23
+ }
24
+
25
+ type ApiKeyPayload {
26
+ success: Boolean!
27
+ apiKey: ApiKey
28
+ secret: String
29
+ }
30
+
31
+ type UserPreferencesPayload {
32
+ success: Boolean!
33
+ theme: String
34
+ fontSize: String
35
+ }
36
+
37
+
38
+ type NotificationPreferencesPayload {
39
+ success: Boolean!
40
+ issueCreated: Boolean!
41
+ issueUpdated: Boolean!
42
+ }
43
+
44
+ type WorkspaceMembership {
45
+ id: ID!
46
+ organizationId: ID!
47
+ name: String!
48
+ urlKey: String!
49
+ role: String!
50
+ }
51
+
52
+ type OrganizationPayload {
53
+ success: Boolean!
54
+ organization: Organization
55
+ }
56
+
57
+ type UserPayload {
58
+ success: Boolean!
59
+ user: User
60
+ apiKey: String
61
+ inviteCode: String
62
+ }
63
+
64
+ input InitiativeCreateInput { name: String! summary: String status: String priority: Int ownerId: String targetDate: TimelessDate projectIds: [String!] }
65
+ input InitiativeUpdateInput { name: String summary: String status: String priority: Int ownerId: String targetDate: TimelessDate projectIds: [String!] }
66
+
67
+ input WorkspaceUpdateInput {
68
+ name: String
69
+ urlKey: String
70
+ }
71
+
72
+ input WorkspaceCreateInput {
73
+ name: String!
74
+ urlKey: String!
75
+ }
76
+
77
+ input MemberInviteInput {
78
+ email: String!
79
+ name: String!
80
+ admin: Boolean = false
81
+ teamIds: [String!]
82
+ }
83
+
84
+ type Query {
85
+ viewer: User
86
+ myApiKeys: [ApiKey!]!
87
+ myPreferences: UserPreferencesPayload!
88
+ myNotificationPreferences: NotificationPreferencesPayload!
89
+ issueLabelSettings: [IssueLabel!]!
90
+ myWorkspaces: [WorkspaceMembership!]!
91
+ organizationMembers(first: Int = 100): UserConnection
92
+ pendingMembershipInvitations: [PendingMembershipInvitation!]!
93
+ workspace: Organization
94
+ teams(first: Int = 50, after: String, filter: TeamFilter): TeamConnection
95
+ team(id: String!): Team
96
+ issues(filter: IssueFilter, first: Int = 50, after: String, orderBy: PaginationOrderBy): IssueConnection
97
+ issue(id: String!): Issue
98
+ myIssueActivity(first: Int = 50, after: String): MyIssueActivityConnection
99
+ inboxNotifications(first: Int = 50, after: String, unreadOnly: Boolean = false, archived: Boolean = false): InboxNotificationConnection
100
+ inboxUnreadCount: Int!
101
+ projects(filter: ProjectFilter, first: Int = 50, after: String): ProjectConnection
102
+ initiatives(first: Int = 50, after: String, status: String): InitiativeConnection
103
+ initiative(id: String!): Initiative
104
+ projectUpdates(first: Int = 50, after: String, filter: ProjectUpdateFilter): ProjectUpdateConnection
105
+ project(id: String!): Project
106
+ workflowStates(filter: WorkflowStateFilter, first: Int = 100, after: String): WorkflowStateConnection
107
+ cycles(filter: CycleFilter, first: Int = 100, after: String): CycleConnection
108
+ cycle(id: String!): Cycle
109
+ teamCycleCadence(teamId: String!): TeamCycleCadence!
110
+ issueLabels(filter: IssueLabelFilter, first: Int = 100, after: String): IssueLabelConnection
111
+ searchIssues(term: String!, first: Int = 20): IssueConnection
112
+ rateLimitStatus: RateLimitPayload
113
+ }
114
+
115
+ type Mutation {
116
+ apiKeyCreate(label: String!, access: String = "read_write", expiresAt: DateTime): ApiKeyPayload
117
+ apiKeyRevoke(id: String!): Boolean!
118
+ userPreferenceSet(key: String!, value: String!): UserPreferencesPayload!
119
+ profileNameUpdate(name: String!): UserPayload
120
+ notificationPreferenceSet(kind: String!, enabled: Boolean!): NotificationPreferencesPayload!
121
+ workspaceCreate(input: WorkspaceCreateInput!): OrganizationPayload
122
+ teamCreate(input: TeamCreateInput!): TeamPayload
123
+ teamUpdate(id: String!, input: TeamUpdateInput!): TeamPayload
124
+ teamArchive(id: String!): DeletePayload
125
+ teamDelete(id: String!, deleteIssues: Boolean = false): DeletePayload
126
+ workspaceSwitch(organizationId: String!): OrganizationPayload
127
+ membershipInvitationCreate(userId: String!): UserPayload
128
+ membershipInvitationAccept(code: String!, inviteePassword: String!): OrganizationPayload
129
+ membershipInvitationRevoke(userId: String!): DeletePayload
130
+ memberDeactivate(userId: String!): UserPayload
131
+ memberReactivate(userId: String!): UserPayload
132
+ workspaceUpdate(input: WorkspaceUpdateInput!): OrganizationPayload
133
+ memberInvite(input: MemberInviteInput!): UserPayload
134
+ memberRoleUpdate(userId: String!, admin: Boolean!): UserPayload
135
+ teamMemberAdd(teamId: String!, userId: String!, role: String = "member"): UserPayload
136
+ teamMemberRemove(teamId: String!, userId: String!): UserPayload
137
+ issueCreate(input: IssueCreateInput!): IssuePayload
138
+ issueUpdate(id: String!, input: IssueUpdateInput!): IssuePayload
139
+ notificationMarkRead(id: String!): Boolean!
140
+ notificationArchive(id: String!): Boolean!
141
+ notificationMarkAllRead: Boolean!
142
+ issueDelete(id: String!): DeletePayload
143
+ issueArchive(id: String!): DeletePayload
144
+ issueSubscribe(id: String!): Boolean!
145
+ issueUnsubscribe(id: String!): Boolean!
146
+ commentCreate(input: CommentCreateInput!): CommentPayload
147
+ commentUpdate(id: String!, input: CommentUpdateInput!): CommentPayload
148
+ commentDelete(id: String!): DeletePayload
149
+ issueLabelCreate(input: IssueLabelCreateInput!): IssueLabelPayload
150
+ issueLabelDelete(id: String!): DeletePayload
151
+ issueLabelUpdate(id: String!, input: IssueLabelUpdateInput!): IssueLabelPayload
152
+ projectCreate(input: ProjectCreateInput!): ProjectPayload
153
+ projectUpdate(id: String!, input: ProjectUpdateInput!): ProjectPayload
154
+ projectUpdateCreate(input: ProjectUpdateCreateInput!): ProjectUpdatePayload
155
+ projectArchive(id: String!): DeletePayload
156
+ teamCycleCadenceEnable(teamId: String!, weekday: Int!): TeamCycleCadence!
157
+ cycleUpdate(id: String!, input: CycleUpdateInput!): CyclePayload
158
+ initiativeCreate(input: InitiativeCreateInput!): InitiativePayload
159
+ initiativeUpdate(id: String!, input: InitiativeUpdateInput!): InitiativePayload
160
+ initiativeArchive(id: String!): DeletePayload
161
+ attachmentCreate(input: AttachmentCreateInput!): AttachmentPayload
162
+ }
163
+
164
+ enum PaginationOrderBy { createdAt updatedAt }
165
+
166
+ type PageInfo {
167
+ hasNextPage: Boolean!
168
+ hasPreviousPage: Boolean!
169
+ startCursor: String
170
+ endCursor: String
171
+ }
172
+
173
+ type InboxNotification {
174
+ id: ID!
175
+ issue: Issue!
176
+ actor: User
177
+ kind: String!
178
+ createdAt: DateTime!
179
+ readAt: DateTime
180
+ archivedAt: DateTime
181
+ }
182
+ type InboxNotificationConnection { nodes: [InboxNotification!]! pageInfo: PageInfo! }
183
+
184
+ type User {
185
+ id: ID!
186
+ name: String
187
+ displayName: String
188
+ email: String
189
+ active: Boolean
190
+ isMe: Boolean
191
+ admin: Boolean
192
+ avatarUrl: String
193
+ url: String
194
+ timezone: String
195
+ statusEmoji: String
196
+ statusLabel: String
197
+ createdAt: DateTime
198
+ updatedAt: DateTime
199
+ }
200
+
201
+ type Team {
202
+ id: ID!
203
+ name: String!
204
+ key: String!
205
+ description: String
206
+ color: String
207
+ icon: String
208
+ visibility: String!
209
+ parentTeamId: ID
210
+ parent: Team
211
+ children(first: Int = 100): TeamConnection
212
+ timezone: String
213
+ createdAt: DateTime
214
+ updatedAt: DateTime
215
+ states(first: Int = 100): WorkflowStateConnection
216
+ members(first: Int = 100): UserConnection
217
+ cycles(first: Int = 50): CycleConnection
218
+ activeCycle: Cycle
219
+ autoAddStarted: Boolean
220
+ autoAddCompleted: Boolean
221
+ autoAddBacklog: Boolean
222
+ }
223
+
224
+ type WorkflowState {
225
+ id: ID!
226
+ name: String!
227
+ color: String
228
+ description: String
229
+ position: Float
230
+ type: String!
231
+ createdAt: DateTime
232
+ updatedAt: DateTime
233
+ team: Team
234
+ }
235
+
236
+ type IssueLabel {
237
+ id: ID!
238
+ name: String!
239
+ color: String
240
+ description: String
241
+ createdAt: DateTime
242
+ updatedAt: DateTime
243
+ }
244
+
245
+ type TeamCycleCadence { enabled: Boolean! weekday: Int timezone: String! policyVersion: Int! activeCycle: Cycle upcomingCycles: [Cycle!]! }
246
+
247
+ type Cycle {
248
+ id: ID!
249
+ name: String
250
+ description: String
251
+ number: Int
252
+ startsAt: DateTime
253
+ endsAt: DateTime
254
+ completedAt: DateTime
255
+ progress: Float
256
+ createdAt: DateTime
257
+ updatedAt: DateTime
258
+ team: Team
259
+ }
260
+
261
+ type Project {
262
+ id: ID!
263
+ name: String!
264
+ description: String
265
+ slugId: String
266
+ icon: String
267
+ color: String
268
+ state: String
269
+ progress: Float
270
+ startDate: TimelessDate
271
+ targetDate: TimelessDate
272
+ url: String
273
+ lead: User
274
+ creator: User
275
+ members(first: Int = 100): UserConnection
276
+ teams(first: Int = 100): TeamConnection
277
+ "Events on this project's issues (created, updated, comments, archived), newest first."
278
+ activity(first: Int = 50, after: String, eventTypes: [String!]): MyIssueActivityConnection
279
+ health: String
280
+ createdAt: DateTime
281
+ updatedAt: DateTime
282
+ }
283
+
284
+ input ProjectUpdateFilter { project: ProjectFilter }
285
+
286
+ type ProjectUpdate {
287
+ id: ID!
288
+ project: Project!
289
+ status: String
290
+ body: String!
291
+ createdAt: DateTime!
292
+ actor: User
293
+ progress: Float
294
+ health: String
295
+ }
296
+ type ProjectUpdatePayload { success: Boolean! projectUpdate: ProjectUpdate }
297
+
298
+ type Comment {
299
+ id: ID!
300
+ body: String!
301
+ url: String
302
+ user: User
303
+ createdAt: DateTime
304
+ updatedAt: DateTime
305
+ "When the author last changed the body; null if never edited."
306
+ editedAt: DateTime
307
+ "Whether the viewer may edit this comment (author only)."
308
+ canEdit: Boolean!
309
+ "Whether the viewer may delete this comment (author or workspace admin)."
310
+ canDelete: Boolean!
311
+ }
312
+
313
+ type Issue {
314
+ id: ID!
315
+ identifier: String!
316
+ title: String!
317
+ description: String
318
+ priority: Int
319
+ priorityLabel: String
320
+ estimate: Float
321
+ url: String
322
+ branchName: String
323
+ number: Int
324
+ createdAt: DateTime
325
+ updatedAt: DateTime
326
+ dueDate: DateTime
327
+ completedAt: DateTime
328
+ canceledAt: DateTime
329
+ startedAt: DateTime
330
+ stateChangedAt: DateTime
331
+ snoozedUntilAt: DateTime
332
+ state: WorkflowState
333
+ assignee: User
334
+ creator: User
335
+ team: Team
336
+ project: Project
337
+ cycle: Cycle
338
+ parent: Issue
339
+ children(first: Int = 50, after: String): IssueConnection
340
+ labels(first: Int = 50): IssueLabelConnection
341
+ subscribers(first: Int = 50): UserConnection
342
+ comments(first: Int = 50): CommentConnection
343
+ attachments(first: Int = 50): AttachmentConnection
344
+ activity(first: Int = 50, after: String): IssueActivityConnection
345
+ }
346
+
347
+ type UserConnection { nodes: [User!]! pageInfo: PageInfo! }
348
+ type TeamPayload { success: Boolean! team: Team }
349
+ type TeamConnection { nodes: [Team!]! pageInfo: PageInfo! }
350
+ type WorkflowStateConnection { nodes: [WorkflowState!]! pageInfo: PageInfo! }
351
+ type IssueConnection { nodes: [Issue!]! pageInfo: PageInfo! }
352
+ type IssueLabelConnection { nodes: [IssueLabel!]! pageInfo: PageInfo! }
353
+ type ProjectConnection { nodes: [Project!]! pageInfo: PageInfo! }
354
+ type InitiativeConnection { nodes: [Initiative!]! pageInfo: PageInfo! }
355
+ type Initiative { id: ID! name: String! summary: String status: String! priority: Int! owner: User creator: User targetDate: TimelessDate createdAt: DateTime! updatedAt: DateTime! projects: ProjectConnection! }
356
+ type InitiativePayload { success: Boolean! initiative: Initiative }
357
+ type ProjectUpdateConnection { nodes: [ProjectUpdate!]! pageInfo: PageInfo! }
358
+ type CycleConnection { nodes: [Cycle!]! pageInfo: PageInfo! }
359
+ type CommentConnection { nodes: [Comment!]! pageInfo: PageInfo! }
360
+ type IssueActivity { id: ID! eventType: String! actor: User changes: JSON createdAt: DateTime! }
361
+ type IssueActivityConnection { nodes: [IssueActivity!]! pageInfo: PageInfo! }
362
+ type MyIssueActivity { id: ID! issue: Issue! eventType: String! actor: User changes: JSON createdAt: DateTime! }
363
+ type MyIssueActivityConnection { nodes: [MyIssueActivity!]! pageInfo: PageInfo! }
364
+
365
+ type IssuePayload { success: Boolean! issue: Issue }
366
+ type CommentPayload { success: Boolean! comment: Comment }
367
+ type IssueLabelPayload { success: Boolean! issueLabel: IssueLabel }
368
+ type ProjectPayload { success: Boolean! project: Project }
369
+ type DeletePayload { success: Boolean! }
370
+ type PendingMembershipInvitation { userId: ID! name: String email: String expiresAt: DateTime }
371
+ type AttachmentPayload { success: Boolean! attachment: Attachment }
372
+ type AttachmentConnection { nodes: [Attachment!]! pageInfo: PageInfo! }
373
+ type Attachment {
374
+ id: ID!
375
+ url: String!
376
+ title: String!
377
+ subtitle: String
378
+ issue: Issue
379
+ createdAt: DateTime
380
+ }
381
+
382
+ type RateLimitPayload {
383
+ identifier: String
384
+ kind: String
385
+ limit: Int
386
+ remaining: Int
387
+ requestsPerHour: Int
388
+ resetsAt: DateTime
389
+ }
390
+
391
+ # ---- Filter inputs (comparators hoja emits) ----
392
+ input StringComparator {
393
+ eq: String
394
+ neq: String
395
+ in: [String!]
396
+ nin: [String!]
397
+ contains: String
398
+ containsIgnoreCase: String
399
+ eqIgnoreCase: String
400
+ }
401
+ input IDComparator { eq: ID in: [ID!] neq: ID }
402
+ input NumberComparator { eq: Float neq: Float gt: Float lt: Float gte: Float lte: Float in: [Float!] }
403
+ input DateComparator { eq: DateTime neq: DateTime gt: DateTime lt: DateTime gte: DateTime lte: DateTime }
404
+ input BooleanComparator { eq: Boolean neq: Boolean }
405
+
406
+ input TeamFilter { key: StringComparator id: IDComparator name: StringComparator }
407
+ input UserFilter { id: IDComparator email: StringComparator displayName: StringComparator name: StringComparator }
408
+ input WorkflowStateFilter { name: StringComparator type: StringComparator team: TeamFilter }
409
+ input CycleFilter { id: IDComparator name: StringComparator team: TeamFilter isActive: BooleanComparator isNext: BooleanComparator }
410
+ input ProjectRelFilter { id: IDComparator name: StringComparator }
411
+ input LabelRelFilter { name: StringComparator }
412
+
413
+ input IssueFilter {
414
+ identifier: StringComparator
415
+ team: TeamFilter
416
+ state: WorkflowStateFilter
417
+ assignee: UserFilter
418
+ creator: UserFilter
419
+ project: ProjectRelFilter
420
+ cycle: CycleFilter
421
+ parent: IssueParentFilter
422
+ labels: LabelRelFilter
423
+ priority: NumberComparator
424
+ number: NumberComparator
425
+ title: StringComparator
426
+ description: StringComparator
427
+ updatedAt: DateComparator
428
+ createdAt: DateComparator
429
+ dueDate: DateComparator
430
+ and: [IssueFilter!]
431
+ or: [IssueFilter!]
432
+ }
433
+
434
+ input ProjectFilter { id: IDComparator slugId: StringComparator name: StringComparator state: StringComparator team: TeamFilter }
435
+ input IssueParentFilter { id: IDComparator }
436
+ input IssueLabelFilter { team: TeamFilter name: StringComparator }
437
+
438
+ # ---- Mutation inputs ----
439
+ input TeamUpdateInput { name: String key: String description: String autoAddStarted: Boolean autoAddCompleted: Boolean autoAddBacklog: Boolean visibility: String parentTeamId: String }
440
+ input CycleUpdateInput { name: String description: String startsAt: DateTime endsAt: DateTime }
441
+ type CyclePayload { success: Boolean! cycle: Cycle }
442
+ input TeamCreateInput { name: String! key: String! description: String color: String icon: String visibility: String parentTeamId: String timezone: String memberIds: [String!] }
443
+ input IssueCreateInput {
444
+ title: String!
445
+ teamId: String!
446
+ description: String
447
+ priority: Int
448
+ stateId: String
449
+ assigneeId: String
450
+ projectId: String
451
+ cycleId: String
452
+ parentId: String
453
+ estimate: Float
454
+ dueDate: DateTime
455
+ labelIds: [String!]
456
+ }
457
+ input IssueUpdateInput {
458
+ title: String
459
+ description: String
460
+ priority: Int
461
+ stateId: String
462
+ assigneeId: String
463
+ projectId: String
464
+ cycleId: String
465
+ parentId: String
466
+ estimate: Float
467
+ dueDate: DateTime
468
+ labelIds: [String!]
469
+ }
470
+ input CommentCreateInput { issueId: String! body: String! }
471
+ input CommentUpdateInput { body: String }
472
+ input IssueLabelUpdateInput { name: String color: String description: String }
473
+ input IssueLabelCreateInput { name: String! teamId: String color: String description: String }
474
+ input ProjectCreateInput { name: String! description: String teamIds: [String!] leadId: String state: String color: String icon: String startDate: TimelessDate targetDate: TimelessDate }
475
+ input ProjectUpdateInput { name: String description: String leadId: String state: String color: String icon: String startDate: TimelessDate targetDate: TimelessDate teamIds: [String!] }
476
+ input ProjectUpdateCreateInput { projectId: String! body: String! status: String }
477
+ input AttachmentCreateInput { issueId: String! url: String! title: String! subtitle: String }
@@ -0,0 +1,8 @@
1
+ """Hoja GraphQL schema text, exposed to the agent so it can discover fields."""
2
+
3
+ from pathlib import Path
4
+
5
+ from hoja_server.agent_conversations import SDL as CONVERSATION_SDL
6
+ from hoja_server.model_providers import SDL as PROVIDER_SDL
7
+
8
+ SCHEMA_SDL = (Path(__file__).parent / "schema.graphql").read_text() + "\n" + PROVIDER_SDL + "\n" + CONVERSATION_SDL
@@ -0,0 +1,39 @@
1
+ """Encrypt provider credentials at rest with a server-held master key."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import hashlib
7
+ import os
8
+ import secrets
9
+
10
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
11
+
12
+ KEY_ENV = "HOJA_SECRET_KEY"
13
+ _NONCE_BYTES = 12
14
+
15
+
16
+ class SecretBoxError(RuntimeError):
17
+ pass
18
+
19
+
20
+ def _master_key() -> bytes:
21
+ raw = os.environ.get(KEY_ENV, "")
22
+ if not raw:
23
+ raise SecretBoxError(f"{KEY_ENV} must be set to store provider credentials.")
24
+ return hashlib.sha256(raw.encode()).digest()
25
+
26
+
27
+ def encrypt_secret(plaintext: str) -> str:
28
+ nonce = secrets.token_bytes(_NONCE_BYTES)
29
+ sealed = AESGCM(_master_key()).encrypt(nonce, plaintext.encode(), None)
30
+ return base64.b64encode(nonce + sealed).decode()
31
+
32
+
33
+ def decrypt_secret(token: str) -> str:
34
+ blob = base64.b64decode(token)
35
+ nonce, sealed = blob[:_NONCE_BYTES], blob[_NONCE_BYTES:]
36
+ try:
37
+ return AESGCM(_master_key()).decrypt(nonce, sealed, None).decode()
38
+ except Exception as exc:
39
+ raise SecretBoxError("Stored provider credential could not be decrypted.") from exc