@locusai/sdk 0.2.1

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 (105) hide show
  1. package/dist/agent/artifact-syncer.d.ts +17 -0
  2. package/dist/agent/artifact-syncer.d.ts.map +1 -0
  3. package/dist/agent/artifact-syncer.js +77 -0
  4. package/dist/agent/codebase-indexer-service.d.ts +18 -0
  5. package/dist/agent/codebase-indexer-service.d.ts.map +1 -0
  6. package/dist/agent/codebase-indexer-service.js +55 -0
  7. package/dist/agent/index.d.ts +6 -0
  8. package/dist/agent/index.d.ts.map +1 -0
  9. package/dist/agent/index.js +5 -0
  10. package/dist/agent/sprint-planner.d.ts +17 -0
  11. package/dist/agent/sprint-planner.d.ts.map +1 -0
  12. package/dist/agent/sprint-planner.js +62 -0
  13. package/dist/agent/task-executor.d.ts +24 -0
  14. package/dist/agent/task-executor.d.ts.map +1 -0
  15. package/dist/agent/task-executor.js +56 -0
  16. package/dist/agent/worker.d.ts +37 -0
  17. package/dist/agent/worker.d.ts.map +1 -0
  18. package/dist/agent/worker.js +232 -0
  19. package/dist/ai/anthropic-client.d.ts +33 -0
  20. package/dist/ai/anthropic-client.d.ts.map +1 -0
  21. package/dist/ai/anthropic-client.js +70 -0
  22. package/dist/ai/claude-runner.d.ts +7 -0
  23. package/dist/ai/claude-runner.d.ts.map +1 -0
  24. package/dist/ai/claude-runner.js +43 -0
  25. package/dist/ai/index.d.ts +3 -0
  26. package/dist/ai/index.d.ts.map +1 -0
  27. package/dist/ai/index.js +2 -0
  28. package/dist/core/config.d.ts +10 -0
  29. package/dist/core/config.d.ts.map +1 -0
  30. package/dist/core/config.js +15 -0
  31. package/dist/core/index.d.ts +4 -0
  32. package/dist/core/index.d.ts.map +1 -0
  33. package/dist/core/index.js +3 -0
  34. package/dist/core/indexer.d.ts +18 -0
  35. package/dist/core/indexer.d.ts.map +1 -0
  36. package/dist/core/indexer.js +73 -0
  37. package/dist/core/prompt-builder.d.ts +8 -0
  38. package/dist/core/prompt-builder.d.ts.map +1 -0
  39. package/dist/core/prompt-builder.js +87 -0
  40. package/dist/events.d.ts +20 -0
  41. package/dist/events.d.ts.map +1 -0
  42. package/dist/events.js +15 -0
  43. package/dist/index-node.d.ts +14 -0
  44. package/dist/index-node.d.ts.map +1 -0
  45. package/dist/index-node.js +18 -0
  46. package/dist/index.d.ts +34 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +101 -0
  49. package/dist/modules/auth.d.ts +14 -0
  50. package/dist/modules/auth.d.ts.map +1 -0
  51. package/dist/modules/auth.js +23 -0
  52. package/dist/modules/base.d.ts +8 -0
  53. package/dist/modules/base.d.ts.map +1 -0
  54. package/dist/modules/base.js +8 -0
  55. package/dist/modules/ci.d.ts +8 -0
  56. package/dist/modules/ci.d.ts.map +1 -0
  57. package/dist/modules/ci.js +7 -0
  58. package/dist/modules/docs.d.ts +14 -0
  59. package/dist/modules/docs.d.ts.map +1 -0
  60. package/dist/modules/docs.js +38 -0
  61. package/dist/modules/invitations.d.ts +10 -0
  62. package/dist/modules/invitations.d.ts.map +1 -0
  63. package/dist/modules/invitations.js +22 -0
  64. package/dist/modules/organizations.d.ts +24 -0
  65. package/dist/modules/organizations.d.ts.map +1 -0
  66. package/dist/modules/organizations.js +39 -0
  67. package/dist/modules/sprints.d.ts +13 -0
  68. package/dist/modules/sprints.d.ts.map +1 -0
  69. package/dist/modules/sprints.js +34 -0
  70. package/dist/modules/tasks.d.ts +24 -0
  71. package/dist/modules/tasks.d.ts.map +1 -0
  72. package/dist/modules/tasks.js +56 -0
  73. package/dist/modules/workspaces.d.ts +21 -0
  74. package/dist/modules/workspaces.d.ts.map +1 -0
  75. package/dist/modules/workspaces.js +49 -0
  76. package/dist/orchestrator.d.ts +90 -0
  77. package/dist/orchestrator.d.ts.map +1 -0
  78. package/dist/orchestrator.js +326 -0
  79. package/package.json +53 -0
  80. package/src/agent/artifact-syncer.ts +111 -0
  81. package/src/agent/codebase-indexer-service.ts +71 -0
  82. package/src/agent/index.ts +5 -0
  83. package/src/agent/sprint-planner.ts +78 -0
  84. package/src/agent/task-executor.ts +77 -0
  85. package/src/agent/worker.ts +299 -0
  86. package/src/ai/anthropic-client.ts +93 -0
  87. package/src/ai/claude-runner.ts +49 -0
  88. package/src/ai/index.ts +2 -0
  89. package/src/core/config.ts +21 -0
  90. package/src/core/index.ts +3 -0
  91. package/src/core/indexer.ts +91 -0
  92. package/src/core/prompt-builder.ts +100 -0
  93. package/src/events.ts +32 -0
  94. package/src/index-node.ts +20 -0
  95. package/src/index.ts +119 -0
  96. package/src/modules/auth.ts +48 -0
  97. package/src/modules/base.ts +9 -0
  98. package/src/modules/ci.ts +12 -0
  99. package/src/modules/docs.ts +84 -0
  100. package/src/modules/invitations.ts +45 -0
  101. package/src/modules/organizations.ts +90 -0
  102. package/src/modules/sprints.ts +69 -0
  103. package/src/modules/tasks.ts +110 -0
  104. package/src/modules/workspaces.ts +94 -0
  105. package/src/orchestrator.ts +430 -0
@@ -0,0 +1,100 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { Task } from "@locusai/shared";
3
+ import { getLocusPath } from "./config";
4
+ import { CodebaseIndex } from "./indexer";
5
+
6
+ export class PromptBuilder {
7
+ constructor(private projectPath: string) {}
8
+
9
+ async build(task: Task): Promise<string> {
10
+ let prompt = `# Task: ${task.title}\n\n`;
11
+
12
+ if (task.assigneeRole) {
13
+ prompt += `## Role\nYou are acting as a ${task.assigneeRole} engineer.\n\n`;
14
+ }
15
+
16
+ prompt += `## Description\n${task.description || "No description provided."}\n\n`;
17
+
18
+ // 1. Add CLAUDE.md context
19
+ const contextPath = getLocusPath(this.projectPath, "contextFile");
20
+ if (existsSync(contextPath)) {
21
+ try {
22
+ const context = readFileSync(contextPath, "utf-8");
23
+ prompt += `## Project Context (from CLAUDE.md)\n${context}\n\n`;
24
+ } catch (err) {
25
+ console.warn(`Warning: Could not read context file: ${err}`);
26
+ }
27
+ }
28
+
29
+ // 2. Add Codebase Index context
30
+ const indexPath = getLocusPath(this.projectPath, "indexFile");
31
+ if (existsSync(indexPath)) {
32
+ try {
33
+ const indexContent = readFileSync(indexPath, "utf-8");
34
+ const index = JSON.parse(indexContent) as CodebaseIndex;
35
+ prompt += this.formatIndex(index, task);
36
+ } catch (err) {
37
+ console.warn(`Warning: Could not read codebase index: ${err}`);
38
+ }
39
+ }
40
+
41
+ // 3. Add Documents
42
+ if (task.docs && task.docs.length > 0) {
43
+ prompt += `## Attached Documents\n`;
44
+ for (const doc of task.docs) {
45
+ prompt += `### ${doc.title}\n${doc.content || "(No content)"}\n\n`;
46
+ }
47
+ }
48
+
49
+ // 4. Add Checklist
50
+ if (task.acceptanceChecklist && task.acceptanceChecklist.length > 0) {
51
+ prompt += `## Acceptance Criteria\n`;
52
+ for (const item of task.acceptanceChecklist) {
53
+ prompt += `- ${item.done ? "[x]" : "[ ]"} ${item.text}\n`;
54
+ }
55
+ prompt += "\n";
56
+ }
57
+
58
+ // 5. Add Comments & Feedback
59
+ if (task.comments && task.comments.length > 0) {
60
+ prompt += `## Task History & Feedback\n`;
61
+ prompt += `Review the following comments for context or rejection feedback:\n\n`;
62
+ for (const comment of task.comments) {
63
+ const date = new Date(comment.createdAt).toLocaleString();
64
+ prompt += `### ${comment.author} (${date})\n${comment.text}\n\n`;
65
+ }
66
+ }
67
+
68
+ prompt += `## Instructions
69
+ 1. Complete this task.
70
+ 2. **Artifact Management**: If you create any high-level documentation (PRDs, technical drafts, architecture docs), you MUST save them in \`.locus/artifacts/\`. Do NOT create them in the root directory.
71
+ 3. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
72
+ 4. When finished successfully, output: <promise>COMPLETE</promise>\n`;
73
+ return prompt;
74
+ }
75
+
76
+ private formatIndex(index: CodebaseIndex, task: Task): string {
77
+ let section = `## Codebase Overview\nThis codebase has been indexed to help you navigate.\n\n`;
78
+
79
+ // Structural directories
80
+ const structuralDirs = Object.entries(index.responsibilities || {})
81
+ .filter(([path]) => !path.includes(".") || path.split("/").length <= 2)
82
+ .slice(0, 15);
83
+
84
+ if (structuralDirs.length > 0) {
85
+ section += `### Project Structure\n${structuralDirs.map(([p, d]) => `- \`${p}\`: ${d}`).join("\n")}\n\n`;
86
+ }
87
+
88
+ // Relevant symbols
89
+ const keywords = `${task.title} ${task.description}`.toLowerCase();
90
+ const symbols = Object.entries(index.symbols || {})
91
+ .filter(([symbol]) => keywords.includes(symbol.toLowerCase()))
92
+ .slice(0, 10);
93
+
94
+ if (symbols.length > 0) {
95
+ section += `### Potentially Relevant Symbols\n${symbols.map(([s, f]) => `- \`${s}\` is defined in: ${Array.isArray(f) ? f.join(", ") : f}`).join("\n")}\n\n`;
96
+ }
97
+
98
+ return section;
99
+ }
100
+ }
package/src/events.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { EventEmitter } from "events";
2
+
3
+ export enum LocusEvent {
4
+ TOKEN_EXPIRED = "TOKEN_EXPIRED",
5
+ AUTH_ERROR = "AUTH_ERROR",
6
+ REQUEST_ERROR = "REQUEST_ERROR",
7
+ }
8
+
9
+ export interface LocusConfig {
10
+ baseUrl: string;
11
+ token?: string | null;
12
+ timeout?: number;
13
+ }
14
+
15
+ export class LocusEmitter extends EventEmitter {
16
+ on(event: LocusEvent.TOKEN_EXPIRED, listener: () => void): this;
17
+ on(event: LocusEvent.AUTH_ERROR, listener: (error: Error) => void): this;
18
+ on(event: LocusEvent.REQUEST_ERROR, listener: (error: Error) => void): this;
19
+ on(
20
+ event: LocusEvent | string,
21
+ listener: ((...args: unknown[]) => void) | (() => void)
22
+ ): this {
23
+ return super.on(event, listener);
24
+ }
25
+
26
+ emit(event: LocusEvent.TOKEN_EXPIRED): boolean;
27
+ emit(event: LocusEvent.AUTH_ERROR, error: Error): boolean;
28
+ emit(event: LocusEvent.REQUEST_ERROR, error: Error): boolean;
29
+ emit(event: LocusEvent | string, ...args: unknown[]): boolean {
30
+ return super.emit(event, ...args);
31
+ }
32
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Node.js-only exports
3
+ * This is a separate entry point for Node.js/CLI only
4
+ * It should NOT be imported by browser applications
5
+ *
6
+ * These modules use Node.js APIs (fs, child_process, etc.)
7
+ * and will break in browser environments
8
+ */
9
+
10
+ // Node.js-only: Agent system
11
+ export * from "./agent";
12
+ // Node.js-only: AI clients
13
+ export * from "./ai";
14
+ // Node.js-only: Core utilities (uses fs)
15
+ export * from "./core";
16
+ // Re-export everything from main index (browser-safe)
17
+ export * from "./index";
18
+
19
+ // Node.js-only: Orchestrator
20
+ export { AgentOrchestrator, type OrchestratorConfig } from "./orchestrator";
package/src/index.ts ADDED
@@ -0,0 +1,119 @@
1
+ import axios, { AxiosInstance } from "axios";
2
+ import { LocusConfig, LocusEmitter, LocusEvent } from "./events";
3
+ import { AuthModule } from "./modules/auth";
4
+ import { CiModule } from "./modules/ci";
5
+ import { DocsModule } from "./modules/docs";
6
+ import { InvitationsModule } from "./modules/invitations";
7
+ import { OrganizationsModule } from "./modules/organizations";
8
+ import { SprintsModule } from "./modules/sprints";
9
+ import { TasksModule } from "./modules/tasks";
10
+ import { WorkspacesModule } from "./modules/workspaces";
11
+
12
+ // Browser-safe exports only
13
+ export * from "./events";
14
+ export * from "./modules/auth";
15
+ export * from "./modules/ci";
16
+ export * from "./modules/docs";
17
+ export * from "./modules/invitations";
18
+ export * from "./modules/organizations";
19
+ export * from "./modules/sprints";
20
+ export * from "./modules/tasks";
21
+ export * from "./modules/workspaces";
22
+
23
+ export class LocusClient {
24
+ private readonly api: AxiosInstance;
25
+ public readonly emitter: LocusEmitter;
26
+
27
+ public readonly auth: AuthModule;
28
+ public readonly tasks: TasksModule;
29
+ public readonly sprints: SprintsModule;
30
+ public readonly workspaces: WorkspacesModule;
31
+ public readonly organizations: OrganizationsModule;
32
+ public readonly invitations: InvitationsModule;
33
+ public readonly docs: DocsModule;
34
+ public readonly ci: CiModule;
35
+
36
+ constructor(config: LocusConfig) {
37
+ this.emitter = new LocusEmitter();
38
+
39
+ this.api = axios.create({
40
+ baseURL: config.baseUrl,
41
+ timeout: config.timeout || 10000,
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ ...(config.token ? { Authorization: `Bearer ${config.token}` } : {}),
45
+ },
46
+ });
47
+
48
+ this.setupInterceptors();
49
+
50
+ // Initialize modules
51
+ this.auth = new AuthModule(this.api, this.emitter);
52
+ this.tasks = new TasksModule(this.api, this.emitter);
53
+ this.sprints = new SprintsModule(this.api, this.emitter);
54
+ this.workspaces = new WorkspacesModule(this.api, this.emitter);
55
+ this.organizations = new OrganizationsModule(this.api, this.emitter);
56
+ this.invitations = new InvitationsModule(this.api, this.emitter);
57
+ this.docs = new DocsModule(this.api, this.emitter);
58
+ this.ci = new CiModule(this.api, this.emitter);
59
+ }
60
+
61
+ private setupInterceptors() {
62
+ this.api.interceptors.response.use(
63
+ (response) => {
64
+ if (
65
+ response.data &&
66
+ typeof response.data === "object" &&
67
+ "data" in response.data
68
+ ) {
69
+ response.data = response.data.data;
70
+ }
71
+ return response;
72
+ },
73
+ (error) => {
74
+ const status = error.response?.status;
75
+
76
+ // Extract error message from API response format: { error: { message: "..." } }
77
+ let message: string;
78
+
79
+ // Try to get message from API error response
80
+ if (
81
+ error.response?.data?.error?.message &&
82
+ typeof error.response.data.error.message === "string"
83
+ ) {
84
+ message = error.response.data.error.message;
85
+ } else if (
86
+ error.response?.data?.message &&
87
+ typeof error.response.data.message === "string"
88
+ ) {
89
+ message = error.response.data.message;
90
+ } else if (error.message && typeof error.message === "string") {
91
+ message = error.message;
92
+ } else {
93
+ message = "An error occurred";
94
+ }
95
+
96
+ // Create a new error with a meaningful message
97
+ const enhancedError = new Error(message);
98
+ enhancedError.name = `HTTP${status || "Error"}`;
99
+
100
+ if (status === 401) {
101
+ this.emitter.emit(LocusEvent.TOKEN_EXPIRED);
102
+ this.emitter.emit(LocusEvent.AUTH_ERROR, enhancedError);
103
+ } else {
104
+ this.emitter.emit(LocusEvent.REQUEST_ERROR, enhancedError);
105
+ }
106
+
107
+ return Promise.reject(enhancedError);
108
+ }
109
+ );
110
+ }
111
+
112
+ public setToken(token: string | null) {
113
+ if (token) {
114
+ this.api.defaults.headers.common.Authorization = `Bearer ${token}`;
115
+ } else {
116
+ delete this.api.defaults.headers.common.Authorization;
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,48 @@
1
+ import {
2
+ CompleteRegistration,
3
+ LoginResponse,
4
+ User,
5
+ VerifyOtp,
6
+ } from "@locusai/shared";
7
+ import { BaseModule } from "./base";
8
+
9
+ export class AuthModule extends BaseModule {
10
+ async getMe(): Promise<User> {
11
+ const { data } = await this.api.get<User>("/auth/me");
12
+ return data;
13
+ }
14
+
15
+ async requestRegisterOtp(email: string): Promise<{ success: boolean }> {
16
+ const { data } = await this.api.post<{ success: boolean }>(
17
+ "/auth/register-otp",
18
+ { email }
19
+ );
20
+ return data;
21
+ }
22
+
23
+ async requestLoginOtp(email: string): Promise<{ success: boolean }> {
24
+ const { data } = await this.api.post<{ success: boolean }>(
25
+ "/auth/login-otp",
26
+ { email }
27
+ );
28
+ return data;
29
+ }
30
+
31
+ async verifyLogin(body: VerifyOtp): Promise<LoginResponse> {
32
+ const { data } = await this.api.post<LoginResponse>(
33
+ "/auth/verify-login",
34
+ body
35
+ );
36
+ return data;
37
+ }
38
+
39
+ async completeRegistration(
40
+ body: CompleteRegistration
41
+ ): Promise<LoginResponse> {
42
+ const { data } = await this.api.post<LoginResponse>(
43
+ "/auth/complete-registration",
44
+ body
45
+ );
46
+ return data;
47
+ }
48
+ }
@@ -0,0 +1,9 @@
1
+ import { AxiosInstance } from "axios";
2
+ import { LocusEmitter } from "../events";
3
+
4
+ export abstract class BaseModule {
5
+ constructor(
6
+ protected readonly api: AxiosInstance,
7
+ protected readonly emitter: LocusEmitter
8
+ ) {}
9
+ }
@@ -0,0 +1,12 @@
1
+ import { ReportCiResult } from "@locusai/shared";
2
+ import { BaseModule } from "./base";
3
+
4
+ export class CiModule extends BaseModule {
5
+ async report(body: ReportCiResult): Promise<{ success: boolean }> {
6
+ const { data } = await this.api.post<{ success: boolean }>(
7
+ "/ci/report",
8
+ body
9
+ );
10
+ return data;
11
+ }
12
+ }
@@ -0,0 +1,84 @@
1
+ import {
2
+ CreateDoc,
3
+ CreateDocGroup,
4
+ Doc,
5
+ DocGroup,
6
+ DocGroupResponse,
7
+ DocGroupsResponse,
8
+ DocResponse,
9
+ DocsResponse,
10
+ UpdateDoc,
11
+ UpdateDocGroup,
12
+ } from "@locusai/shared";
13
+ import { BaseModule } from "./base";
14
+
15
+ export class DocsModule extends BaseModule {
16
+ async create(workspaceId: string, body: CreateDoc): Promise<Doc> {
17
+ const { data } = await this.api.post<DocResponse>(
18
+ `/workspaces/${workspaceId}/docs`,
19
+ body
20
+ );
21
+ return data.doc;
22
+ }
23
+
24
+ async list(workspaceId: string): Promise<Doc[]> {
25
+ const { data } = await this.api.get<DocsResponse>(
26
+ `/workspaces/${workspaceId}/docs`
27
+ );
28
+ return data.docs;
29
+ }
30
+
31
+ async getById(id: string, workspaceId: string): Promise<Doc> {
32
+ const { data } = await this.api.get<DocResponse>(
33
+ `/workspaces/${workspaceId}/docs/${id}`
34
+ );
35
+ return data.doc;
36
+ }
37
+
38
+ async update(id: string, workspaceId: string, body: UpdateDoc): Promise<Doc> {
39
+ const { data } = await this.api.put<DocResponse>(
40
+ `/workspaces/${workspaceId}/docs/${id}`,
41
+ body
42
+ );
43
+ return data.doc;
44
+ }
45
+
46
+ async delete(id: string, workspaceId: string): Promise<void> {
47
+ await this.api.delete(`/workspaces/${workspaceId}/docs/${id}`);
48
+ }
49
+
50
+ // Group Management
51
+ async listGroups(workspaceId: string): Promise<DocGroup[]> {
52
+ const { data } = await this.api.get<DocGroupsResponse>(
53
+ `/workspaces/${workspaceId}/doc-groups`
54
+ );
55
+ return data.groups;
56
+ }
57
+
58
+ async createGroup(
59
+ workspaceId: string,
60
+ body: CreateDocGroup
61
+ ): Promise<DocGroup> {
62
+ const { data } = await this.api.post<DocGroupResponse>(
63
+ `/workspaces/${workspaceId}/doc-groups`,
64
+ body
65
+ );
66
+ return data.group;
67
+ }
68
+
69
+ async updateGroup(
70
+ id: string,
71
+ workspaceId: string,
72
+ body: UpdateDocGroup
73
+ ): Promise<DocGroup> {
74
+ const { data } = await this.api.patch<DocGroupResponse>(
75
+ `/workspaces/${workspaceId}/doc-groups/${id}`,
76
+ body
77
+ );
78
+ return data.group;
79
+ }
80
+
81
+ async deleteGroup(id: string, workspaceId: string): Promise<void> {
82
+ await this.api.delete(`/workspaces/${workspaceId}/doc-groups/${id}`);
83
+ }
84
+ }
@@ -0,0 +1,45 @@
1
+ import {
2
+ AcceptInvitation,
3
+ AcceptInvitationResponse,
4
+ CreateInvitation,
5
+ Invitation,
6
+ InvitationResponse,
7
+ InvitationsResponse,
8
+ } from "@locusai/shared";
9
+ import { BaseModule } from "./base";
10
+
11
+ export class InvitationsModule extends BaseModule {
12
+ async create(orgId: string, body: CreateInvitation): Promise<Invitation> {
13
+ const { data } = await this.api.post<InvitationResponse>(
14
+ `/org/${orgId}/invitations`,
15
+ body
16
+ );
17
+ return data.invitation;
18
+ }
19
+
20
+ async list(orgId: string): Promise<Invitation[]> {
21
+ const { data } = await this.api.get<InvitationsResponse>(
22
+ `/org/${orgId}/invitations`
23
+ );
24
+ return data.invitations;
25
+ }
26
+
27
+ async verify(token: string): Promise<InvitationResponse> {
28
+ const { data } = await this.api.get<InvitationResponse>(
29
+ `/invitations/verify/${token}`
30
+ );
31
+ return data;
32
+ }
33
+
34
+ async accept(body: AcceptInvitation): Promise<AcceptInvitationResponse> {
35
+ const { data } = await this.api.post<AcceptInvitationResponse>(
36
+ "/invitations/accept",
37
+ body
38
+ );
39
+ return data;
40
+ }
41
+
42
+ async revoke(orgId: string, id: string): Promise<void> {
43
+ await this.api.delete(`/org/${orgId}/invitations/${id}`);
44
+ }
45
+ }
@@ -0,0 +1,90 @@
1
+ import {
2
+ AddMember,
3
+ MembershipResponse,
4
+ MembershipWithUser,
5
+ MembersResponse,
6
+ Organization,
7
+ OrganizationResponse,
8
+ OrganizationsResponse,
9
+ } from "@locusai/shared";
10
+ import { BaseModule } from "./base";
11
+
12
+ export interface ApiKey {
13
+ id: string;
14
+ organizationId: string;
15
+ name: string;
16
+ key: string;
17
+ active: boolean;
18
+ lastUsedAt: string | null;
19
+ createdAt: string;
20
+ updatedAt: string;
21
+ }
22
+
23
+ interface ApiKeysResponse {
24
+ apiKeys: ApiKey[];
25
+ }
26
+
27
+ interface ApiKeyResponse {
28
+ apiKey: ApiKey;
29
+ }
30
+
31
+ export class OrganizationsModule extends BaseModule {
32
+ async list(): Promise<Organization[]> {
33
+ const { data } =
34
+ await this.api.get<OrganizationsResponse>("/organizations");
35
+ return data.organizations;
36
+ }
37
+
38
+ async getById(id: string): Promise<Organization> {
39
+ const { data } = await this.api.get<OrganizationResponse>(
40
+ `/organizations/${id}`
41
+ );
42
+ return data.organization;
43
+ }
44
+
45
+ async listMembers(id: string): Promise<MembershipWithUser[]> {
46
+ const { data } = await this.api.get<MembersResponse>(
47
+ `/organizations/${id}/members`
48
+ );
49
+ return data.members;
50
+ }
51
+
52
+ async addMember(id: string, body: AddMember): Promise<MembershipWithUser> {
53
+ const { data } = await this.api.post<MembershipResponse>(
54
+ `/organizations/${id}/members`,
55
+ body
56
+ );
57
+ return data.membership;
58
+ }
59
+
60
+ async removeMember(orgId: string, userId: string): Promise<void> {
61
+ await this.api.delete(`/organizations/${orgId}/members/${userId}`);
62
+ }
63
+
64
+ async delete(orgId: string): Promise<void> {
65
+ await this.api.delete(`/organizations/${orgId}`);
66
+ }
67
+
68
+ // ============================================================================
69
+ // API Key Management
70
+ // ============================================================================
71
+
72
+ async listApiKeys(orgId: string): Promise<ApiKey[]> {
73
+ const { data } = await this.api.get<ApiKeysResponse>(
74
+ `/organizations/${orgId}/api-keys`
75
+ );
76
+ return data.apiKeys;
77
+ }
78
+
79
+ async createApiKey(orgId: string, name: string): Promise<ApiKey> {
80
+ const { data } = await this.api.post<ApiKeyResponse>(
81
+ `/organizations/${orgId}/api-keys`,
82
+ { name }
83
+ );
84
+ return data.apiKey;
85
+ }
86
+
87
+ async deleteApiKey(orgId: string, keyId: string): Promise<void> {
88
+ await this.api.delete(`/organizations/${orgId}/api-keys/${keyId}`);
89
+ }
90
+ }
@@ -0,0 +1,69 @@
1
+ import {
2
+ CreateSprint,
3
+ Sprint,
4
+ SprintResponse,
5
+ SprintsResponse,
6
+ UpdateSprint,
7
+ } from "@locusai/shared";
8
+ import { BaseModule } from "./base";
9
+
10
+ export class SprintsModule extends BaseModule {
11
+ async list(workspaceId: string): Promise<Sprint[]> {
12
+ const { data } = await this.api.get<SprintsResponse>(
13
+ `/workspaces/${workspaceId}/sprints`
14
+ );
15
+ return data.sprints;
16
+ }
17
+
18
+ async getActive(workspaceId: string): Promise<Sprint> {
19
+ const { data } = await this.api.get<SprintResponse>(
20
+ `/workspaces/${workspaceId}/sprints/active`
21
+ );
22
+ return data.sprint;
23
+ }
24
+
25
+ async getById(id: string, workspaceId: string): Promise<Sprint> {
26
+ const { data } = await this.api.get<SprintResponse>(
27
+ `/workspaces/${workspaceId}/sprints/${id}`
28
+ );
29
+ return data.sprint;
30
+ }
31
+
32
+ async create(workspaceId: string, body: CreateSprint): Promise<Sprint> {
33
+ const { data } = await this.api.post<SprintResponse>(
34
+ `/workspaces/${workspaceId}/sprints`,
35
+ body
36
+ );
37
+ return data.sprint;
38
+ }
39
+
40
+ async update(
41
+ id: string,
42
+ workspaceId: string,
43
+ body: UpdateSprint
44
+ ): Promise<Sprint> {
45
+ const { data } = await this.api.patch<SprintResponse>(
46
+ `/workspaces/${workspaceId}/sprints/${id}`,
47
+ body
48
+ );
49
+ return data.sprint;
50
+ }
51
+
52
+ async delete(id: string, workspaceId: string): Promise<void> {
53
+ await this.api.delete(`/workspaces/${workspaceId}/sprints/${id}`);
54
+ }
55
+
56
+ async start(id: string, workspaceId: string): Promise<Sprint> {
57
+ const { data } = await this.api.post<SprintResponse>(
58
+ `/workspaces/${workspaceId}/sprints/${id}/start`
59
+ );
60
+ return data.sprint;
61
+ }
62
+
63
+ async complete(id: string, workspaceId: string): Promise<Sprint> {
64
+ const { data } = await this.api.post<SprintResponse>(
65
+ `/workspaces/${workspaceId}/sprints/${id}/complete`
66
+ );
67
+ return data.sprint;
68
+ }
69
+ }