@evref-bl/dev-nexus 0.1.0-alpha.0

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 (100) hide show
  1. package/README.md +677 -0
  2. package/dist/browserOpener.d.ts +9 -0
  3. package/dist/browserOpener.js +47 -0
  4. package/dist/cli.d.ts +18 -0
  5. package/dist/cli.js +2374 -0
  6. package/dist/gitWorktreeService.d.ts +57 -0
  7. package/dist/gitWorktreeService.js +157 -0
  8. package/dist/index.d.ts +47 -0
  9. package/dist/index.js +47 -0
  10. package/dist/nexusAgentMcpConfig.d.ts +30 -0
  11. package/dist/nexusAgentMcpConfig.js +228 -0
  12. package/dist/nexusAutomation.d.ts +103 -0
  13. package/dist/nexusAutomation.js +390 -0
  14. package/dist/nexusAutomationAgentLaunch.d.ts +148 -0
  15. package/dist/nexusAutomationAgentLaunch.js +855 -0
  16. package/dist/nexusAutomationAgentProfile.d.ts +39 -0
  17. package/dist/nexusAutomationAgentProfile.js +103 -0
  18. package/dist/nexusAutomationAgentSurface.d.ts +62 -0
  19. package/dist/nexusAutomationAgentSurface.js +90 -0
  20. package/dist/nexusAutomationCommandExecutor.d.ts +29 -0
  21. package/dist/nexusAutomationCommandExecutor.js +251 -0
  22. package/dist/nexusAutomationConfig.d.ts +114 -0
  23. package/dist/nexusAutomationConfig.js +547 -0
  24. package/dist/nexusAutomationEnqueue.d.ts +37 -0
  25. package/dist/nexusAutomationEnqueue.js +128 -0
  26. package/dist/nexusAutomationRunOnce.d.ts +91 -0
  27. package/dist/nexusAutomationRunOnce.js +586 -0
  28. package/dist/nexusAutomationScheduler.d.ts +50 -0
  29. package/dist/nexusAutomationScheduler.js +196 -0
  30. package/dist/nexusAutomationStatus.d.ts +55 -0
  31. package/dist/nexusAutomationStatus.js +462 -0
  32. package/dist/nexusAutomationTarget.d.ts +19 -0
  33. package/dist/nexusAutomationTarget.js +33 -0
  34. package/dist/nexusAutomationTargetCycle.d.ts +90 -0
  35. package/dist/nexusAutomationTargetCycle.js +282 -0
  36. package/dist/nexusAutomationTargetReport.d.ts +136 -0
  37. package/dist/nexusAutomationTargetReport.js +504 -0
  38. package/dist/nexusAutomationWorktreeSetup.d.ts +89 -0
  39. package/dist/nexusAutomationWorktreeSetup.js +661 -0
  40. package/dist/nexusCoordination.d.ts +198 -0
  41. package/dist/nexusCoordination.js +1018 -0
  42. package/dist/nexusExtension.d.ts +31 -0
  43. package/dist/nexusExtension.js +1 -0
  44. package/dist/nexusHomeConfig.d.ts +38 -0
  45. package/dist/nexusHomeConfig.js +133 -0
  46. package/dist/nexusMcpServer.d.ts +31 -0
  47. package/dist/nexusMcpServer.js +1036 -0
  48. package/dist/nexusPluginCapabilities.d.ts +197 -0
  49. package/dist/nexusPluginCapabilities.js +201 -0
  50. package/dist/nexusProjectConfig.d.ts +95 -0
  51. package/dist/nexusProjectConfig.js +880 -0
  52. package/dist/nexusProjectHomeService.d.ts +121 -0
  53. package/dist/nexusProjectHomeService.js +171 -0
  54. package/dist/nexusProjectLifecycle.d.ts +62 -0
  55. package/dist/nexusProjectLifecycle.js +205 -0
  56. package/dist/nexusProjectOperations.d.ts +101 -0
  57. package/dist/nexusProjectOperations.js +296 -0
  58. package/dist/nexusProjectRegistry.d.ts +42 -0
  59. package/dist/nexusProjectRegistry.js +91 -0
  60. package/dist/nexusProjectScaffold.d.ts +25 -0
  61. package/dist/nexusProjectScaffold.js +61 -0
  62. package/dist/nexusProjectTemplate.d.ts +34 -0
  63. package/dist/nexusProjectTemplate.js +354 -0
  64. package/dist/nexusSkills.d.ts +134 -0
  65. package/dist/nexusSkills.js +647 -0
  66. package/dist/nexusWorkerContextBundle.d.ts +142 -0
  67. package/dist/nexusWorkerContextBundle.js +375 -0
  68. package/dist/processSupervisor.d.ts +89 -0
  69. package/dist/processSupervisor.js +440 -0
  70. package/dist/vibeKanbanApi.d.ts +11 -0
  71. package/dist/vibeKanbanApi.js +14 -0
  72. package/dist/vibeKanbanAuth.d.ts +25 -0
  73. package/dist/vibeKanbanAuth.js +101 -0
  74. package/dist/vibeKanbanBoardAdapter.d.ts +36 -0
  75. package/dist/vibeKanbanBoardAdapter.js +196 -0
  76. package/dist/vibeKanbanMcpConfig.d.ts +36 -0
  77. package/dist/vibeKanbanMcpConfig.js +191 -0
  78. package/dist/vibeKanbanProjectAdapter.d.ts +39 -0
  79. package/dist/vibeKanbanProjectAdapter.js +113 -0
  80. package/dist/vibeKanbanWorkspaceSetup.d.ts +1 -0
  81. package/dist/vibeKanbanWorkspaceSetup.js +96 -0
  82. package/dist/workItemService.d.ts +60 -0
  83. package/dist/workItemService.js +163 -0
  84. package/dist/workTrackingGitHubProvider.d.ts +71 -0
  85. package/dist/workTrackingGitHubProvider.js +663 -0
  86. package/dist/workTrackingGitLabProvider.d.ts +62 -0
  87. package/dist/workTrackingGitLabProvider.js +523 -0
  88. package/dist/workTrackingJiraProvider.d.ts +67 -0
  89. package/dist/workTrackingJiraProvider.js +652 -0
  90. package/dist/workTrackingLocalProvider.d.ts +49 -0
  91. package/dist/workTrackingLocalProvider.js +463 -0
  92. package/dist/workTrackingProviderService.d.ts +21 -0
  93. package/dist/workTrackingProviderService.js +117 -0
  94. package/dist/workTrackingTypes.d.ts +202 -0
  95. package/dist/workTrackingTypes.js +1 -0
  96. package/dist/workTrackingVibeProvider.d.ts +35 -0
  97. package/dist/workTrackingVibeProvider.js +119 -0
  98. package/dist/worktreeExecutionMetadata.d.ts +76 -0
  99. package/dist/worktreeExecutionMetadata.js +239 -0
  100. package/package.json +37 -0
@@ -0,0 +1,196 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { vibeKanbanApiBaseUrl, vibeKanbanPinnedVersion, } from "./vibeKanbanApi.js";
3
+ export class VibeKanbanBoardAdapterError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "VibeKanbanBoardAdapterError";
7
+ }
8
+ }
9
+ function assertRecord(value, pathName) {
10
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
11
+ throw new VibeKanbanBoardAdapterError(`${pathName} must be an object`);
12
+ }
13
+ return value;
14
+ }
15
+ function requiredString(record, key, pathName) {
16
+ const value = record[key];
17
+ if (typeof value !== "string" || value.trim().length === 0) {
18
+ throw new VibeKanbanBoardAdapterError(`${pathName}.${key} must be a non-empty string`);
19
+ }
20
+ return value;
21
+ }
22
+ function parseApiSuccess(value) {
23
+ const response = assertRecord(value, "response");
24
+ if (response.success !== true) {
25
+ const message = typeof response.message === "string"
26
+ ? response.message
27
+ : typeof response.error === "string"
28
+ ? response.error
29
+ : "Vibe Kanban request failed";
30
+ throw new VibeKanbanBoardAdapterError(message);
31
+ }
32
+ return response.data;
33
+ }
34
+ function parseOrganization(value) {
35
+ const record = assertRecord(value, "organization");
36
+ return {
37
+ ...record,
38
+ id: requiredString(record, "id", "organization"),
39
+ };
40
+ }
41
+ function parseBoard(value) {
42
+ const record = assertRecord(value, "project");
43
+ return {
44
+ ...record,
45
+ id: requiredString(record, "id", "project"),
46
+ name: requiredString(record, "name", "project"),
47
+ };
48
+ }
49
+ async function readJsonResponse(response) {
50
+ const text = await response.text();
51
+ if (text.trim().length === 0) {
52
+ return {};
53
+ }
54
+ return JSON.parse(text.replace(/^\uFEFF/, ""));
55
+ }
56
+ async function requestJson(fetchImpl, url, init) {
57
+ const response = await fetchImpl(url, init);
58
+ const body = await readJsonResponse(response);
59
+ if (!response.ok) {
60
+ const detail = body && typeof body === "object"
61
+ ? `: ${JSON.stringify(body)}`
62
+ : "";
63
+ throw new VibeKanbanBoardAdapterError(`Vibe Kanban request failed with HTTP ${response.status}${detail}`);
64
+ }
65
+ return body;
66
+ }
67
+ function remoteRequestHeaders(accessToken) {
68
+ return {
69
+ authorization: `Bearer ${accessToken}`,
70
+ "content-type": "application/json",
71
+ "x-client-version": vibeKanbanPinnedVersion,
72
+ "x-client-type": "frontend",
73
+ };
74
+ }
75
+ function normalizeBaseUrl(value) {
76
+ return value.replace(/\/+$/, "");
77
+ }
78
+ async function getVibeKanbanInfo(options, fetchImpl) {
79
+ const url = new URL("/api/info", vibeKanbanApiBaseUrl(options));
80
+ const raw = await requestJson(fetchImpl, url.toString());
81
+ const data = assertRecord(parseApiSuccess(raw), "info");
82
+ return {
83
+ sharedApiBase: typeof data.shared_api_base === "string" && data.shared_api_base.trim()
84
+ ? normalizeBaseUrl(data.shared_api_base)
85
+ : "https://api.vibekanban.com",
86
+ raw,
87
+ };
88
+ }
89
+ async function getVibeKanbanAccessToken(options, fetchImpl) {
90
+ const url = new URL("/api/auth/token", vibeKanbanApiBaseUrl(options));
91
+ const raw = await requestJson(fetchImpl, url.toString());
92
+ const data = assertRecord(parseApiSuccess(raw), "auth token");
93
+ return requiredString(data, "access_token", "auth token");
94
+ }
95
+ async function listOrganizations(sharedApiBase, accessToken, fetchImpl) {
96
+ const raw = await requestJson(fetchImpl, `${sharedApiBase}/v1/organizations`, {
97
+ method: "GET",
98
+ headers: remoteRequestHeaders(accessToken),
99
+ });
100
+ const data = assertRecord(raw, "organizations response");
101
+ const organizations = data.organizations;
102
+ if (!Array.isArray(organizations)) {
103
+ throw new VibeKanbanBoardAdapterError("organizations response.organizations must be an array");
104
+ }
105
+ return {
106
+ organizations: organizations.map(parseOrganization),
107
+ raw,
108
+ };
109
+ }
110
+ async function listBoards(sharedApiBase, accessToken, organizationId, fetchImpl) {
111
+ const url = new URL(`${sharedApiBase}/v1/fallback/projects`);
112
+ url.searchParams.set("organization_id", organizationId);
113
+ const raw = await requestJson(fetchImpl, url.toString(), {
114
+ method: "GET",
115
+ headers: remoteRequestHeaders(accessToken),
116
+ });
117
+ const data = assertRecord(raw, "projects response");
118
+ const projects = data.projects;
119
+ if (!Array.isArray(projects)) {
120
+ throw new VibeKanbanBoardAdapterError("projects response.projects must be an array");
121
+ }
122
+ return {
123
+ boards: projects.map(parseBoard),
124
+ raw,
125
+ };
126
+ }
127
+ async function createBoard(sharedApiBase, accessToken, organizationId, name, color, uuid, fetchImpl) {
128
+ const board = {
129
+ id: uuid(),
130
+ organization_id: organizationId,
131
+ name,
132
+ color,
133
+ };
134
+ const raw = await requestJson(fetchImpl, `${sharedApiBase}/v1/projects`, {
135
+ method: "POST",
136
+ headers: remoteRequestHeaders(accessToken),
137
+ body: JSON.stringify(board),
138
+ });
139
+ return {
140
+ board,
141
+ raw,
142
+ };
143
+ }
144
+ function selectOrganization(organizations, organizationId) {
145
+ if (organizationId) {
146
+ const matched = organizations.find((organization) => organization.id === organizationId);
147
+ if (!matched) {
148
+ throw new VibeKanbanBoardAdapterError(`Vibe Kanban organization was not found: ${organizationId}`);
149
+ }
150
+ return matched;
151
+ }
152
+ const personal = organizations.find((organization) => organization.is_personal === true);
153
+ const selected = personal ?? organizations[0];
154
+ if (!selected) {
155
+ throw new VibeKanbanBoardAdapterError("No Vibe Kanban organizations are available");
156
+ }
157
+ return selected;
158
+ }
159
+ export async function ensureVibeKanbanBoard(options) {
160
+ if (options.name.trim().length === 0) {
161
+ throw new VibeKanbanBoardAdapterError("name must be non-empty");
162
+ }
163
+ const fetchImpl = options.fetch ?? fetch;
164
+ const info = await getVibeKanbanInfo(options, fetchImpl);
165
+ const accessToken = await getVibeKanbanAccessToken(options, fetchImpl);
166
+ const organizations = await listOrganizations(info.sharedApiBase, accessToken, fetchImpl);
167
+ const organization = selectOrganization(organizations.organizations, options.organizationId);
168
+ const projects = await listBoards(info.sharedApiBase, accessToken, organization.id, fetchImpl);
169
+ const existing = projects.boards.find((board) => board.name === options.name);
170
+ if (existing) {
171
+ return {
172
+ boardId: existing.id,
173
+ board: existing,
174
+ organization,
175
+ created: false,
176
+ raw: {
177
+ info: info.raw,
178
+ organizations: organizations.raw,
179
+ projects: projects.raw,
180
+ },
181
+ };
182
+ }
183
+ const created = await createBoard(info.sharedApiBase, accessToken, organization.id, options.name, options.color ?? "210 90% 54%", options.uuid ?? randomUUID, fetchImpl);
184
+ return {
185
+ boardId: created.board.id,
186
+ board: created.board,
187
+ organization,
188
+ created: true,
189
+ raw: {
190
+ info: info.raw,
191
+ organizations: organizations.raw,
192
+ projects: projects.raw,
193
+ create: created.raw,
194
+ },
195
+ };
196
+ }
@@ -0,0 +1,36 @@
1
+ import { type VibeKanbanApiOptions } from "./vibeKanbanApi.js";
2
+ export declare const vibeKanbanExecutors: readonly ["CLAUDE_CODE", "AMP", "GEMINI", "CODEX", "OPENCODE", "CURSOR_AGENT", "QWEN_CODE", "COPILOT", "DROID"];
3
+ export type VibeKanbanExecutor = (typeof vibeKanbanExecutors)[number];
4
+ export interface VibeKanbanMcpServerConfig {
5
+ type?: string;
6
+ command?: string;
7
+ args?: string[];
8
+ env?: Record<string, string>;
9
+ url?: string;
10
+ headers?: Record<string, string>;
11
+ [key: string]: unknown;
12
+ }
13
+ export interface VibeKanbanMcpConfig {
14
+ servers: Record<string, VibeKanbanMcpServerConfig>;
15
+ serversPath?: string[];
16
+ configPath?: string;
17
+ }
18
+ export interface VibeKanbanMcpConfigResponse {
19
+ mcpConfig: VibeKanbanMcpConfig;
20
+ raw: unknown;
21
+ }
22
+ export declare class VibeKanbanMcpConfigError extends Error {
23
+ constructor(message: string);
24
+ }
25
+ export declare function normalizeVibeKanbanExecutor(executor: string): VibeKanbanExecutor;
26
+ export declare function validateMcpServerConfig(value: unknown, pathName: string): VibeKanbanMcpServerConfig;
27
+ export declare function validateMcpServers(value: unknown): Record<string, VibeKanbanMcpServerConfig>;
28
+ export declare function normalizeExistingMcpServers(value: unknown): Record<string, VibeKanbanMcpServerConfig>;
29
+ export declare function getVibeKanbanMcpConfig(options: VibeKanbanApiOptions & {
30
+ executor: string;
31
+ }): Promise<VibeKanbanMcpConfigResponse>;
32
+ export declare function updateVibeKanbanMcpConfig(options: VibeKanbanApiOptions & {
33
+ executor: string;
34
+ servers: Record<string, VibeKanbanMcpServerConfig>;
35
+ }): Promise<unknown>;
36
+ export declare function mergeMcpServerConfig(servers: Record<string, VibeKanbanMcpServerConfig>, serverName: string, server: VibeKanbanMcpServerConfig): Record<string, VibeKanbanMcpServerConfig>;
@@ -0,0 +1,191 @@
1
+ import { vibeKanbanApiBaseUrl } from "./vibeKanbanApi.js";
2
+ export const vibeKanbanExecutors = [
3
+ "CLAUDE_CODE",
4
+ "AMP",
5
+ "GEMINI",
6
+ "CODEX",
7
+ "OPENCODE",
8
+ "CURSOR_AGENT",
9
+ "QWEN_CODE",
10
+ "COPILOT",
11
+ "DROID",
12
+ ];
13
+ export class VibeKanbanMcpConfigError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "VibeKanbanMcpConfigError";
17
+ }
18
+ }
19
+ function assertRecord(value, pathName) {
20
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
21
+ throw new VibeKanbanMcpConfigError(`${pathName} must be an object`);
22
+ }
23
+ return value;
24
+ }
25
+ function optionalStringArray(value, pathName) {
26
+ if (value === undefined) {
27
+ return undefined;
28
+ }
29
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
30
+ throw new VibeKanbanMcpConfigError(`${pathName} must be an array of strings`);
31
+ }
32
+ return value;
33
+ }
34
+ export function normalizeVibeKanbanExecutor(executor) {
35
+ const normalized = executor.trim().replaceAll("-", "_").toUpperCase();
36
+ if (normalized === "CURSOR") {
37
+ return "CURSOR_AGENT";
38
+ }
39
+ if (vibeKanbanExecutors.includes(normalized)) {
40
+ return normalized;
41
+ }
42
+ throw new VibeKanbanMcpConfigError(`Unsupported Vibe Kanban executor: ${executor}`);
43
+ }
44
+ export function validateMcpServerConfig(value, pathName) {
45
+ const record = assertRecord(value, pathName);
46
+ const commandValue = record.command;
47
+ const urlValue = record.url;
48
+ const hasCommand = typeof commandValue === "string" && commandValue.trim().length > 0;
49
+ const hasUrl = typeof urlValue === "string" && urlValue.trim().length > 0;
50
+ if (!hasCommand && !hasUrl) {
51
+ throw new VibeKanbanMcpConfigError(`${pathName}.command or ${pathName}.url must be a non-empty string`);
52
+ }
53
+ if (commandValue !== undefined && !hasCommand) {
54
+ throw new VibeKanbanMcpConfigError(`${pathName}.command must be a non-empty string`);
55
+ }
56
+ if (urlValue !== undefined && !hasUrl) {
57
+ throw new VibeKanbanMcpConfigError(`${pathName}.url must be a non-empty string`);
58
+ }
59
+ const argsValue = record.args;
60
+ if (argsValue !== undefined &&
61
+ (!Array.isArray(argsValue) || argsValue.some((arg) => typeof arg !== "string"))) {
62
+ throw new VibeKanbanMcpConfigError(`${pathName}.args must be an array of strings`);
63
+ }
64
+ const args = argsValue ?? (hasCommand ? [] : undefined);
65
+ const envValue = record.env;
66
+ let env;
67
+ if (envValue !== undefined) {
68
+ const envRecord = assertRecord(envValue, `${pathName}.env`);
69
+ env = {};
70
+ for (const [key, valueEntry] of Object.entries(envRecord)) {
71
+ if (typeof valueEntry !== "string") {
72
+ throw new VibeKanbanMcpConfigError(`${pathName}.env.${key} must be a string`);
73
+ }
74
+ env[key] = valueEntry;
75
+ }
76
+ }
77
+ const headersValue = record.headers;
78
+ let headers;
79
+ if (headersValue !== undefined) {
80
+ const headersRecord = assertRecord(headersValue, `${pathName}.headers`);
81
+ headers = {};
82
+ for (const [key, valueEntry] of Object.entries(headersRecord)) {
83
+ if (typeof valueEntry !== "string") {
84
+ throw new VibeKanbanMcpConfigError(`${pathName}.headers.${key} must be a string`);
85
+ }
86
+ headers[key] = valueEntry;
87
+ }
88
+ }
89
+ return {
90
+ ...record,
91
+ ...(hasCommand ? { command: commandValue } : {}),
92
+ ...(args ? { args } : {}),
93
+ ...(env ? { env } : {}),
94
+ ...(hasUrl ? { url: urlValue } : {}),
95
+ ...(headers ? { headers } : {}),
96
+ };
97
+ }
98
+ export function validateMcpServers(value) {
99
+ const record = assertRecord(value, "servers");
100
+ const servers = {};
101
+ for (const [name, serverConfig] of Object.entries(record)) {
102
+ if (!name.trim()) {
103
+ throw new VibeKanbanMcpConfigError("MCP server names must be non-empty");
104
+ }
105
+ servers[name] = validateMcpServerConfig(serverConfig, `servers.${name}`);
106
+ }
107
+ return servers;
108
+ }
109
+ export function normalizeExistingMcpServers(value) {
110
+ const record = assertRecord(value, "servers");
111
+ const servers = {};
112
+ for (const [name, serverConfig] of Object.entries(record)) {
113
+ if (!name.trim()) {
114
+ throw new VibeKanbanMcpConfigError("MCP server names must be non-empty");
115
+ }
116
+ const serverRecord = assertRecord(serverConfig, `servers.${name}`);
117
+ const command = serverRecord.command;
118
+ const args = serverRecord.args;
119
+ const hasValidCommand = typeof command === "string" && command.trim().length > 0;
120
+ const hasMissingArgs = args === undefined;
121
+ servers[name] =
122
+ hasValidCommand && hasMissingArgs
123
+ ? {
124
+ ...serverRecord,
125
+ args: [],
126
+ }
127
+ : serverRecord;
128
+ }
129
+ return servers;
130
+ }
131
+ function parseMcpConfigResponse(value) {
132
+ const response = assertRecord(value, "response");
133
+ if (response.success !== true) {
134
+ throw new VibeKanbanMcpConfigError("Vibe Kanban MCP config request failed");
135
+ }
136
+ const data = assertRecord(response.data, "response.data");
137
+ const mcpConfig = assertRecord(data.mcp_config, "response.data.mcp_config");
138
+ const configPath = typeof data.config_path === "string" ? data.config_path : undefined;
139
+ return {
140
+ raw: value,
141
+ mcpConfig: {
142
+ servers: normalizeExistingMcpServers(mcpConfig.servers ?? {}),
143
+ serversPath: optionalStringArray(mcpConfig.servers_path, "response.data.mcp_config.servers_path"),
144
+ ...(configPath ? { configPath } : {}),
145
+ },
146
+ };
147
+ }
148
+ async function readJsonResponse(response) {
149
+ const text = await response.text();
150
+ if (text.trim().length === 0) {
151
+ return {};
152
+ }
153
+ return JSON.parse(text.replace(/^\uFEFF/, ""));
154
+ }
155
+ async function requestJson(fetchImpl, url, init) {
156
+ const response = await fetchImpl(url, init);
157
+ const body = await readJsonResponse(response);
158
+ if (!response.ok) {
159
+ throw new VibeKanbanMcpConfigError(`Vibe Kanban request failed with HTTP ${response.status}`);
160
+ }
161
+ return body;
162
+ }
163
+ export async function getVibeKanbanMcpConfig(options) {
164
+ const executor = normalizeVibeKanbanExecutor(options.executor);
165
+ const url = new URL("/api/mcp-config", vibeKanbanApiBaseUrl(options));
166
+ url.searchParams.set("executor", executor);
167
+ return parseMcpConfigResponse(await requestJson(options.fetch ?? fetch, url.toString()));
168
+ }
169
+ export async function updateVibeKanbanMcpConfig(options) {
170
+ const executor = normalizeVibeKanbanExecutor(options.executor);
171
+ const url = new URL("/api/mcp-config", vibeKanbanApiBaseUrl(options));
172
+ url.searchParams.set("executor", executor);
173
+ return requestJson(options.fetch ?? fetch, url.toString(), {
174
+ method: "POST",
175
+ headers: {
176
+ "content-type": "application/json",
177
+ },
178
+ body: JSON.stringify({
179
+ servers: normalizeExistingMcpServers(options.servers),
180
+ }),
181
+ });
182
+ }
183
+ export function mergeMcpServerConfig(servers, serverName, server) {
184
+ if (serverName.trim().length === 0) {
185
+ throw new VibeKanbanMcpConfigError("serverName must be non-empty");
186
+ }
187
+ return normalizeExistingMcpServers({
188
+ ...servers,
189
+ [serverName]: validateMcpServerConfig(server, `servers.${serverName}`),
190
+ });
191
+ }
@@ -0,0 +1,39 @@
1
+ import { type VibeKanbanApiOptions } from "./vibeKanbanApi.js";
2
+ export interface VibeKanbanProject {
3
+ id: string;
4
+ path?: string;
5
+ name?: string;
6
+ display_name?: string;
7
+ setup_script?: string | null;
8
+ cleanup_script?: string | null;
9
+ dev_server_script?: string | null;
10
+ [key: string]: unknown;
11
+ }
12
+ export interface RegisterVibeKanbanProjectOptions extends VibeKanbanApiOptions {
13
+ projectRoot: string;
14
+ name: string;
15
+ }
16
+ export interface RegisterVibeKanbanProjectResult {
17
+ projectId: string;
18
+ project: VibeKanbanProject;
19
+ raw: unknown;
20
+ }
21
+ export interface UpdateVibeKanbanProjectOptions extends VibeKanbanApiOptions {
22
+ projectId: string;
23
+ setupScript?: string | null;
24
+ }
25
+ export interface UpdateVibeKanbanProjectResult {
26
+ projectId: string;
27
+ project: VibeKanbanProject;
28
+ raw: unknown;
29
+ }
30
+ export interface ListVibeKanbanProjectsResult {
31
+ projects: VibeKanbanProject[];
32
+ raw: unknown;
33
+ }
34
+ export declare class VibeKanbanProjectAdapterError extends Error {
35
+ constructor(message: string);
36
+ }
37
+ export declare function listVibeKanbanProjects(options: VibeKanbanApiOptions): Promise<ListVibeKanbanProjectsResult>;
38
+ export declare function registerVibeKanbanProject(options: RegisterVibeKanbanProjectOptions): Promise<RegisterVibeKanbanProjectResult>;
39
+ export declare function updateVibeKanbanProject(options: UpdateVibeKanbanProjectOptions): Promise<UpdateVibeKanbanProjectResult>;
@@ -0,0 +1,113 @@
1
+ import { vibeKanbanApiBaseUrl } from "./vibeKanbanApi.js";
2
+ export class VibeKanbanProjectAdapterError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "VibeKanbanProjectAdapterError";
6
+ }
7
+ }
8
+ function assertRecord(value, pathName) {
9
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
10
+ throw new VibeKanbanProjectAdapterError(`${pathName} must be an object`);
11
+ }
12
+ return value;
13
+ }
14
+ function parseApiSuccess(value) {
15
+ const response = assertRecord(value, "response");
16
+ if (response.success !== true) {
17
+ const message = typeof response.message === "string"
18
+ ? response.message
19
+ : typeof response.error === "string"
20
+ ? response.error
21
+ : "Vibe Kanban project request failed";
22
+ throw new VibeKanbanProjectAdapterError(message);
23
+ }
24
+ return response.data;
25
+ }
26
+ function parseProject(value) {
27
+ const record = assertRecord(value, "project");
28
+ if (typeof record.id !== "string" || record.id.trim().length === 0) {
29
+ throw new VibeKanbanProjectAdapterError("project.id must be a non-empty string");
30
+ }
31
+ return record;
32
+ }
33
+ async function readJsonResponse(response) {
34
+ const text = await response.text();
35
+ if (text.trim().length === 0) {
36
+ return {};
37
+ }
38
+ return JSON.parse(text.replace(/^\uFEFF/, ""));
39
+ }
40
+ async function requestJson(fetchImpl, url, init) {
41
+ const response = await fetchImpl(url, init);
42
+ const body = await readJsonResponse(response);
43
+ if (!response.ok) {
44
+ const detail = body && typeof body === "object"
45
+ ? `: ${JSON.stringify(body)}`
46
+ : "";
47
+ throw new VibeKanbanProjectAdapterError(`Vibe Kanban project request failed with HTTP ${response.status}${detail}`);
48
+ }
49
+ return body;
50
+ }
51
+ export async function listVibeKanbanProjects(options) {
52
+ const url = new URL("/api/repos", vibeKanbanApiBaseUrl(options));
53
+ const raw = await requestJson(options.fetch ?? fetch, url.toString());
54
+ const data = parseApiSuccess(raw);
55
+ if (!Array.isArray(data)) {
56
+ throw new VibeKanbanProjectAdapterError("response.data must be an array");
57
+ }
58
+ return {
59
+ projects: data.map(parseProject),
60
+ raw,
61
+ };
62
+ }
63
+ export async function registerVibeKanbanProject(options) {
64
+ if (options.projectRoot.trim().length === 0) {
65
+ throw new VibeKanbanProjectAdapterError("projectRoot must be non-empty");
66
+ }
67
+ if (options.name.trim().length === 0) {
68
+ throw new VibeKanbanProjectAdapterError("name must be non-empty");
69
+ }
70
+ const url = new URL("/api/repos", vibeKanbanApiBaseUrl(options));
71
+ const raw = await requestJson(options.fetch ?? fetch, url.toString(), {
72
+ method: "POST",
73
+ headers: {
74
+ "content-type": "application/json",
75
+ },
76
+ body: JSON.stringify({
77
+ path: options.projectRoot,
78
+ display_name: options.name,
79
+ }),
80
+ });
81
+ const project = parseProject(parseApiSuccess(raw));
82
+ return {
83
+ projectId: project.id,
84
+ project,
85
+ raw,
86
+ };
87
+ }
88
+ export async function updateVibeKanbanProject(options) {
89
+ if (options.projectId.trim().length === 0) {
90
+ throw new VibeKanbanProjectAdapterError("projectId must be non-empty");
91
+ }
92
+ const payload = {};
93
+ if (options.setupScript !== undefined) {
94
+ payload.setup_script =
95
+ options.setupScript && options.setupScript.trim().length > 0
96
+ ? options.setupScript
97
+ : null;
98
+ }
99
+ const url = new URL(`/api/repos/${encodeURIComponent(options.projectId)}`, vibeKanbanApiBaseUrl(options));
100
+ const raw = await requestJson(options.fetch ?? fetch, url.toString(), {
101
+ method: "PUT",
102
+ headers: {
103
+ "content-type": "application/json",
104
+ },
105
+ body: JSON.stringify(payload),
106
+ });
107
+ const project = parseProject(parseApiSuccess(raw));
108
+ return {
109
+ projectId: project.id,
110
+ project,
111
+ raw,
112
+ };
113
+ }
@@ -0,0 +1 @@
1
+ export declare function buildVibeKanbanWorkspaceSetupScript(managedRoot: string, sourceRoot: string, platform?: NodeJS.Platform): string;
@@ -0,0 +1,96 @@
1
+ import path from "node:path";
2
+ import process from "node:process";
3
+ function powershellSingleQuoted(value) {
4
+ return `'${value.replaceAll("'", "''")}'`;
5
+ }
6
+ function buildWindowsVibeKanbanWorkspaceSetupScript(managedRoot, sourceRoot) {
7
+ return [
8
+ "$ErrorActionPreference = 'Stop'",
9
+ `$managedRoot = ${powershellSingleQuoted(path.resolve(managedRoot))}`,
10
+ `$sourceRoot = ${powershellSingleQuoted(path.resolve(sourceRoot))}`,
11
+ "$workspaceRoot = (Get-Location).Path",
12
+ "",
13
+ "function Add-GitInfoExclude([string] $entry) {",
14
+ " $excludePath = git rev-parse --git-path info/exclude",
15
+ " if (-not (Test-Path -LiteralPath $excludePath)) {",
16
+ " New-Item -ItemType File -Path $excludePath -Force | Out-Null",
17
+ " }",
18
+ " $existing = Get-Content -LiteralPath $excludePath -ErrorAction SilentlyContinue",
19
+ " if ($existing -notcontains $entry) {",
20
+ " Add-Content -LiteralPath $excludePath -Value $entry",
21
+ " }",
22
+ "}",
23
+ "",
24
+ "$agentsSource = Join-Path $managedRoot 'AGENTS.md'",
25
+ "$agentsTarget = Join-Path $workspaceRoot 'AGENTS.md'",
26
+ "if ((Test-Path -LiteralPath $agentsSource) -and -not (Test-Path -LiteralPath $agentsTarget)) {",
27
+ " Copy-Item -LiteralPath $agentsSource -Destination $agentsTarget -Force",
28
+ " Add-GitInfoExclude 'AGENTS.md'",
29
+ "}",
30
+ "",
31
+ "$codexSource = Join-Path $managedRoot '.codex'",
32
+ "$codexTarget = Join-Path $workspaceRoot '.codex'",
33
+ "if ((Test-Path -LiteralPath $codexSource) -and -not (Test-Path -LiteralPath (Join-Path $codexTarget 'config.toml'))) {",
34
+ " New-Item -ItemType Directory -Path $codexTarget -Force | Out-Null",
35
+ " Copy-Item -Path (Join-Path $codexSource '*') -Destination $codexTarget -Recurse -Force",
36
+ " Add-GitInfoExclude '.codex/'",
37
+ "}",
38
+ "",
39
+ "$sourceNodeModules = Join-Path $sourceRoot 'node_modules'",
40
+ "$workspaceNodeModules = Join-Path $workspaceRoot 'node_modules'",
41
+ "if ((Test-Path -LiteralPath $sourceNodeModules) -and -not (Test-Path -LiteralPath $workspaceNodeModules)) {",
42
+ " New-Item -ItemType Junction -Path $workspaceNodeModules -Target $sourceNodeModules | Out-Null",
43
+ " Add-GitInfoExclude 'node_modules/'",
44
+ "} elseif (-not (Test-Path -LiteralPath $sourceNodeModules)) {",
45
+ " Write-Host \"Source checkout has no node_modules at $sourceNodeModules; skipping dependency junction.\"",
46
+ "}",
47
+ "",
48
+ "Write-Host 'Vibe workspace setup complete for DevNexus-managed project.'",
49
+ ].join("\n");
50
+ }
51
+ function shellSingleQuoted(value) {
52
+ return `'${value.replaceAll("'", "'\\''")}'`;
53
+ }
54
+ function buildPosixVibeKanbanWorkspaceSetupScript(managedRoot, sourceRoot) {
55
+ return [
56
+ "set -eu",
57
+ `managed_root=${shellSingleQuoted(path.resolve(managedRoot))}`,
58
+ `source_root=${shellSingleQuoted(path.resolve(sourceRoot))}`,
59
+ "workspace_root=$(pwd)",
60
+ "",
61
+ "add_git_info_exclude() {",
62
+ " entry=$1",
63
+ " exclude_path=$(git rev-parse --git-path info/exclude)",
64
+ " mkdir -p \"$(dirname \"$exclude_path\")\"",
65
+ " touch \"$exclude_path\"",
66
+ " if ! grep -Fxq \"$entry\" \"$exclude_path\"; then",
67
+ " printf '%s\\n' \"$entry\" >> \"$exclude_path\"",
68
+ " fi",
69
+ "}",
70
+ "",
71
+ "if [ -f \"$managed_root/AGENTS.md\" ] && [ ! -e \"$workspace_root/AGENTS.md\" ]; then",
72
+ " cp \"$managed_root/AGENTS.md\" \"$workspace_root/AGENTS.md\"",
73
+ " add_git_info_exclude 'AGENTS.md'",
74
+ "fi",
75
+ "",
76
+ "if [ -d \"$managed_root/.codex\" ] && [ ! -f \"$workspace_root/.codex/config.toml\" ]; then",
77
+ " mkdir -p \"$workspace_root/.codex\"",
78
+ " cp -R \"$managed_root/.codex/.\" \"$workspace_root/.codex/\"",
79
+ " add_git_info_exclude '.codex/'",
80
+ "fi",
81
+ "",
82
+ "if [ -d \"$source_root/node_modules\" ] && [ ! -e \"$workspace_root/node_modules\" ]; then",
83
+ " ln -s \"$source_root/node_modules\" \"$workspace_root/node_modules\"",
84
+ " add_git_info_exclude 'node_modules/'",
85
+ "elif [ ! -d \"$source_root/node_modules\" ]; then",
86
+ " printf '%s\\n' \"Source checkout has no node_modules at $source_root/node_modules; skipping dependency link.\"",
87
+ "fi",
88
+ "",
89
+ "printf '%s\\n' 'Vibe workspace setup complete for DevNexus-managed project.'",
90
+ ].join("\n");
91
+ }
92
+ export function buildVibeKanbanWorkspaceSetupScript(managedRoot, sourceRoot, platform = process.platform) {
93
+ return platform === "win32"
94
+ ? buildWindowsVibeKanbanWorkspaceSetupScript(managedRoot, sourceRoot)
95
+ : buildPosixVibeKanbanWorkspaceSetupScript(managedRoot, sourceRoot);
96
+ }