@chatroomcp/chatroom 0.2.1 → 0.2.3

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 (108) hide show
  1. package/README.md +5 -3
  2. package/dist/app/application.d.ts +0 -1
  3. package/dist/app/application.js +2 -0
  4. package/dist/app/event-bus.d.ts +0 -2
  5. package/dist/app/event-bus.js +0 -1
  6. package/dist/app/external-access-registry.d.ts +0 -1
  7. package/dist/app/external-access-registry.js +0 -1
  8. package/dist/app/lifecycle.d.ts +0 -1
  9. package/dist/auth/auth-service.js +0 -4
  10. package/dist/auth/ingress-policy.d.ts +0 -1
  11. package/dist/auth/ingress-policy.js +0 -1
  12. package/dist/cli/run-cli.js +0 -1
  13. package/dist/config/types.d.ts +0 -1
  14. package/dist/core/auth/repository.d.ts +0 -23
  15. package/dist/core/auth/repository.js +0 -6
  16. package/dist/core/errors/chatroom-error.d.ts +0 -15
  17. package/dist/core/errors/chatroom-error.js +0 -15
  18. package/dist/core/operations/bounded-value.d.ts +0 -1
  19. package/dist/core/operations/bounded-value.js +26 -10
  20. package/dist/core/operations/redactor.js +0 -1
  21. package/dist/core/operations/repository.d.ts +0 -1
  22. package/dist/core/operations/types.d.ts +0 -4
  23. package/dist/core/operations/types.js +0 -2
  24. package/dist/infrastructure/database/app-database.js +4 -97
  25. package/dist/infrastructure/database/migrations.d.ts +2 -0
  26. package/dist/infrastructure/database/migrations.js +44 -0
  27. package/dist/infrastructure/database/operation-repository.js +3 -9
  28. package/dist/infrastructure/database/schema.d.ts +2 -0
  29. package/dist/infrastructure/database/schema.js +85 -0
  30. package/dist/infrastructure/http/http-server.js +0 -1
  31. package/dist/mcp/server/create-mcp-server.js +3 -3
  32. package/dist/mcp/server/plugin-mcp-registrar.d.ts +0 -2
  33. package/dist/mcp/server/plugin-mcp-registrar.js +0 -4
  34. package/dist/mcp/server/tool-support.d.ts +0 -61
  35. package/dist/mcp/server/tool-support.js +0 -54
  36. package/dist/native/linux/chatroom-computer-helper +0 -0
  37. package/dist/native/macos/ChatRoomComputerHelper.app/Contents/Info.plist +1 -1
  38. package/dist/native/macos/ChatRoomComputerHelper.app/Contents/MacOS/chatroom-computer-helper +0 -0
  39. package/dist/native/windows/chatroom-computer-helper.exe +0 -0
  40. package/dist/operations/operation-log.d.ts +0 -3
  41. package/dist/operations/operation-log.js +0 -15
  42. package/dist/plugins/cloud/api-client.js +1 -0
  43. package/dist/plugins/cloud/controller.d.ts +2 -0
  44. package/dist/plugins/cloud/controller.js +28 -5
  45. package/dist/plugins/cloud/tunnel-client.js +0 -4
  46. package/dist/plugins/git/git-service.d.ts +33 -0
  47. package/dist/plugins/git/git-service.js +414 -0
  48. package/dist/plugins/git/plugin.d.ts +4 -0
  49. package/dist/plugins/git/plugin.js +12 -0
  50. package/dist/plugins/git/types.d.ts +32 -0
  51. package/dist/plugins/process/backend.d.ts +0 -1
  52. package/dist/plugins/process/head-tail-buffer.d.ts +0 -1
  53. package/dist/plugins/process/infrastructure/pipe-process-backend.js +0 -1
  54. package/dist/plugins/process/infrastructure/pty-process-backend.js +1 -4
  55. package/dist/plugins/process/process-supervisor.js +10 -23
  56. package/dist/plugins/process/types.d.ts +0 -5
  57. package/dist/plugins/types.d.ts +0 -1
  58. package/dist/plugins/web/api-types.d.ts +3 -7
  59. package/dist/plugins/web/http/api-router.d.ts +0 -1
  60. package/dist/plugins/web/http/api-router.js +2 -4
  61. package/dist/plugins/web/http/git-api-router.d.ts +3 -0
  62. package/dist/plugins/web/http/git-api-router.js +98 -0
  63. package/dist/plugins/web/http/workspace-api-router.js +51 -37
  64. package/dist/plugins/web/plugin.js +4 -6
  65. package/dist/plugins/web/runtime.d.ts +5 -27
  66. package/dist/plugins/web/runtime.js +3 -42
  67. package/dist/plugins/workspace/mcp.js +20 -135
  68. package/dist/plugins/workspace/metadata.d.ts +6 -0
  69. package/dist/plugins/workspace/metadata.js +115 -0
  70. package/dist/plugins/workspace/plugin.d.ts +2 -8
  71. package/dist/plugins/workspace/plugin.js +8 -20
  72. package/dist/plugins/workspace/types.d.ts +23 -0
  73. package/dist/plugins/workspace/{infrastructure/workspace-fs.d.ts → workspace-fs.d.ts} +4 -14
  74. package/dist/plugins/workspace/workspace-fs.js +200 -0
  75. package/dist/plugins/workspace/workspace-service.d.ts +11 -42
  76. package/dist/plugins/workspace/workspace-service.js +103 -302
  77. package/dist/presentation/http/http-utils.d.ts +0 -1
  78. package/dist/presentation/http/oauth-router.d.ts +0 -1
  79. package/dist/presentation/http/oauth-router.js +0 -1
  80. package/dist/web/assets/index-CEzdY5mz.js +27 -0
  81. package/dist/web/assets/index-D_mpjwCG.css +1 -0
  82. package/dist/web/index.html +2 -2
  83. package/package.json +4 -3
  84. package/dist/plugins/workspace/domain/filesystem.d.ts +0 -25
  85. package/dist/plugins/workspace/domain/git.d.ts +0 -7
  86. package/dist/plugins/workspace/domain/repository.d.ts +0 -16
  87. package/dist/plugins/workspace/domain/repository.js +0 -1
  88. package/dist/plugins/workspace/domain/review.d.ts +0 -28
  89. package/dist/plugins/workspace/domain/review.js +0 -1
  90. package/dist/plugins/workspace/domain/workspace.d.ts +0 -23
  91. package/dist/plugins/workspace/domain/workspace.js +0 -1
  92. package/dist/plugins/workspace/git/git-command-runner.d.ts +0 -6
  93. package/dist/plugins/workspace/git/git-command-runner.js +0 -15
  94. package/dist/plugins/workspace/git/git-service.d.ts +0 -14
  95. package/dist/plugins/workspace/git/git-service.js +0 -107
  96. package/dist/plugins/workspace/git/git-snapshot-service.d.ts +0 -33
  97. package/dist/plugins/workspace/git/git-snapshot-service.js +0 -352
  98. package/dist/plugins/workspace/infrastructure/workspace-fs.js +0 -340
  99. package/dist/plugins/workspace/infrastructure/workspace-repository.d.ts +0 -12
  100. package/dist/plugins/workspace/infrastructure/workspace-repository.js +0 -51
  101. package/dist/plugins/workspace/workspace-helpers.d.ts +0 -9
  102. package/dist/plugins/workspace/workspace-helpers.js +0 -67
  103. package/dist/plugins/workspace/worktree-review-service.d.ts +0 -16
  104. package/dist/plugins/workspace/worktree-review-service.js +0 -51
  105. package/dist/web/assets/index-B4vO7wVS.css +0 -1
  106. package/dist/web/assets/index-BU7LCgKB.js +0 -26
  107. /package/dist/plugins/{workspace/domain/filesystem.js → git/types.js} +0 -0
  108. /package/dist/plugins/workspace/{domain/git.js → types.js} +0 -0
@@ -1,34 +1,22 @@
1
- import { CommandRunner } from "../../core/runtime/command-runner.js";
2
1
  import { createServiceToken } from "../types.js";
3
- import { GitCommandRunner } from "./git/git-command-runner.js";
4
- import { GitService } from "./git/git-service.js";
5
- import { GitSnapshotService } from "./git/git-snapshot-service.js";
6
- import { WorkspaceRepository } from "./infrastructure/workspace-repository.js";
7
2
  import { registerWorkspaceTools } from "./mcp.js";
8
- import { WorkspaceService as WorkspaceRuntime } from "./workspace-service.js";
9
- import { WorktreeReviewService } from "./worktree-review-service.js";
10
- export const WorkspaceService = createServiceToken("workspace");
3
+ import { WorkspaceService } from "./workspace-service.js";
4
+ export const WorkspaceServiceToken = createServiceToken("workspace");
11
5
  export function createWorkspacePlugin() {
12
- let value = null;
6
+ let service = null;
13
7
  return {
14
8
  id: "workspace",
15
9
  async activate(context) {
16
- const gitRunner = new GitCommandRunner(new CommandRunner());
17
- const git = new GitService(gitRunner);
18
- const snapshots = new GitSnapshotService(gitRunner, git);
19
- const review = new WorktreeReviewService(git, snapshots);
20
- const workspaces = await WorkspaceRuntime.create(new WorkspaceRepository(context.database), git, review, context.config.allowedRoots, context.config.dataDir);
21
- await workspaces.discoverGitWorkspaces();
22
- value = { workspaces, git };
23
- context.services.provide(WorkspaceService, value);
10
+ service = await WorkspaceService.create(context.config.allowedRoots);
11
+ context.services.provide(WorkspaceServiceToken, service);
24
12
  },
25
13
  registerMcp(mcp) {
26
- if (!value)
14
+ if (!service)
27
15
  throw new Error("Workspace plugin is not active");
28
- registerWorkspaceTools(mcp, value.workspaces);
16
+ registerWorkspaceTools(mcp, service);
29
17
  },
30
18
  deactivate() {
31
- value = null;
19
+ service = null;
32
20
  },
33
21
  };
34
22
  }
@@ -0,0 +1,23 @@
1
+ export interface WorkspaceEntry {
2
+ root: string;
3
+ name: string;
4
+ summary: string | null;
5
+ }
6
+ export interface WorkspaceSkill {
7
+ name: string;
8
+ description: string;
9
+ path: string;
10
+ }
11
+ export interface WorkspaceInfo {
12
+ root: string;
13
+ name: string;
14
+ summary: string | null;
15
+ presetPrompt: string | null;
16
+ instructions: string | null;
17
+ skills: WorkspaceSkill[];
18
+ }
19
+ export interface WorkspaceFile {
20
+ path: string;
21
+ type: "file" | "directory" | "symlink";
22
+ size: number;
23
+ }
@@ -1,7 +1,6 @@
1
- import type { ChangeSet, FileInfo, PatchReplacement, SearchMatch } from "../domain/filesystem.js";
1
+ import type { WorkspaceFile } from "./types.js";
2
2
  export declare class WorkspaceFs {
3
3
  readonly root: string;
4
- private readonly realRoot;
5
4
  private constructor();
6
5
  static create(root: string): Promise<WorkspaceFs>;
7
6
  read(relativePath: string, options?: {
@@ -18,21 +17,12 @@ export declare class WorkspaceFs {
18
17
  bytes: number;
19
18
  truncated: boolean;
20
19
  }>;
21
- write(relativePath: string, content: string): Promise<{
22
- path: string;
23
- bytes: number;
24
- }>;
20
+ write(relativePath: string, content: string): Promise<WorkspaceFile>;
25
21
  list(relativePath?: string, options?: {
26
22
  recursive?: boolean;
27
23
  maxEntries?: number;
28
- }): Promise<FileInfo[]>;
29
- search(query: string, options?: {
30
- path?: string;
31
- maxResults?: number;
32
- }): Promise<SearchMatch[]>;
33
- patch(replacements: PatchReplacement[]): Promise<ChangeSet>;
34
- private normalizeRelative;
24
+ }): Promise<WorkspaceFile[]>;
35
25
  private lexical;
36
- private resolveReadable;
37
26
  private resolveWritable;
27
+ private resolveReadable;
38
28
  }
@@ -0,0 +1,200 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { lstat, mkdir, open, readdir, realpath, rename, rm, stat, writeFile, } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { ChatRoomError } from "../../core/errors/chatroom-error.js";
5
+ export class WorkspaceFs {
6
+ root;
7
+ constructor(root) {
8
+ this.root = root;
9
+ }
10
+ static async create(root) {
11
+ const canonical = await realpath(path.resolve(root)).catch(() => {
12
+ throw new ChatRoomError("NOT_FOUND", `Workspace root does not exist: ${root}`);
13
+ });
14
+ const info = await stat(canonical);
15
+ if (!info.isDirectory())
16
+ throw new ChatRoomError("INVALID_INPUT", `Workspace root is not a directory: ${root}`);
17
+ return new WorkspaceFs(canonical);
18
+ }
19
+ async read(relativePath, options = {}) {
20
+ const result = await this.readBytes(relativePath, options);
21
+ return {
22
+ content: result.data.toString("utf8"),
23
+ bytes: result.bytes,
24
+ truncated: result.truncated,
25
+ };
26
+ }
27
+ async readBytes(relativePath, options = {}) {
28
+ const target = await this.resolveReadable(relativePath);
29
+ const info = await stat(target);
30
+ if (!info.isFile())
31
+ throw new ChatRoomError("INVALID_INPUT", `Not a file: ${relativePath}`);
32
+ const maxBytes = options.maxBytes ?? 1024 * 1024;
33
+ const handle = await open(target, "r");
34
+ try {
35
+ const readSize = Math.min(info.size, maxBytes);
36
+ const buffer = Buffer.alloc(readSize);
37
+ const result = await handle.read(buffer, 0, readSize, 0);
38
+ return {
39
+ data: buffer.subarray(0, result.bytesRead),
40
+ bytes: info.size,
41
+ truncated: info.size > maxBytes,
42
+ };
43
+ }
44
+ finally {
45
+ await handle.close();
46
+ }
47
+ }
48
+ async write(relativePath, content) {
49
+ if (typeof content !== "string")
50
+ throw new ChatRoomError("INVALID_INPUT", "File content must be a string");
51
+ const normalized = normalizeRelative(relativePath);
52
+ if (normalized === ".")
53
+ throw new ChatRoomError("INVALID_INPUT", "File path is required");
54
+ const target = await this.resolveWritable(normalized);
55
+ let mode = 0o644;
56
+ try {
57
+ const info = await lstat(target);
58
+ if (!info.isFile())
59
+ throw new ChatRoomError("INVALID_INPUT", `Not a file: ${relativePath}`);
60
+ mode = info.mode & 0o777;
61
+ }
62
+ catch (error) {
63
+ if (error instanceof ChatRoomError)
64
+ throw error;
65
+ if (error.code !== "ENOENT")
66
+ throw error;
67
+ }
68
+ const temporary = `${target}.chatroom-${randomUUID()}.tmp`;
69
+ try {
70
+ await writeFile(temporary, content, {
71
+ encoding: "utf8",
72
+ flag: "wx",
73
+ mode,
74
+ });
75
+ await rename(temporary, target);
76
+ }
77
+ finally {
78
+ await rm(temporary, { force: true }).catch(() => undefined);
79
+ }
80
+ return {
81
+ path: normalized,
82
+ type: "file",
83
+ size: Buffer.byteLength(content, "utf8"),
84
+ };
85
+ }
86
+ async list(relativePath = ".", options = {}) {
87
+ const start = await this.resolveReadable(relativePath);
88
+ if (!(await stat(start)).isDirectory())
89
+ throw new ChatRoomError("INVALID_INPUT", `Not a directory: ${relativePath}`);
90
+ const maxEntries = Math.min(options.maxEntries ?? 1000, 10000);
91
+ const output = [];
92
+ const walk = async (directory) => {
93
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
94
+ if (output.length >= maxEntries)
95
+ return;
96
+ const absolute = path.join(directory, entry.name);
97
+ const info = await lstat(absolute);
98
+ const type = info.isSymbolicLink()
99
+ ? "symlink"
100
+ : info.isDirectory()
101
+ ? "directory"
102
+ : "file";
103
+ output.push({
104
+ path: path.relative(this.root, absolute).split(path.sep).join("/"),
105
+ type,
106
+ size: info.size,
107
+ });
108
+ if (options.recursive && entry.isDirectory() && !entry.isSymbolicLink())
109
+ await walk(absolute);
110
+ }
111
+ };
112
+ await walk(start);
113
+ return output;
114
+ }
115
+ lexical(relativePath) {
116
+ const normalized = normalizeRelative(relativePath);
117
+ const absolute = path.resolve(this.root, ...normalized.split("/"));
118
+ if (!inside(this.root, absolute))
119
+ throw new ChatRoomError("FORBIDDEN", "Path escapes workspace");
120
+ return absolute;
121
+ }
122
+ async resolveWritable(relativePath) {
123
+ const target = this.lexical(relativePath);
124
+ const relative = path.relative(this.root, target);
125
+ const parts = relative.split(path.sep).filter(Boolean);
126
+ let current = this.root;
127
+ for (let index = 0; index < parts.length - 1; index++) {
128
+ current = path.join(current, parts[index]);
129
+ let info;
130
+ try {
131
+ info = await lstat(current);
132
+ }
133
+ catch (error) {
134
+ if (error.code !== "ENOENT")
135
+ throw error;
136
+ try {
137
+ await mkdir(current, { recursive: false, mode: 0o700 });
138
+ }
139
+ catch (mkdirError) {
140
+ if (mkdirError.code !== "EEXIST")
141
+ throw mkdirError;
142
+ }
143
+ info = await lstat(current);
144
+ }
145
+ if (info.isSymbolicLink())
146
+ throw new ChatRoomError("FORBIDDEN", `Writes through symlinked directories are not allowed: ${relativePath}`);
147
+ if (!info.isDirectory())
148
+ throw new ChatRoomError("INVALID_INPUT", `Parent is not a directory: ${parts[index]}`);
149
+ const canonical = await realpath(current);
150
+ if (!inside(this.root, canonical))
151
+ throw new ChatRoomError("FORBIDDEN", "Write path escapes workspace");
152
+ }
153
+ try {
154
+ const targetInfo = await lstat(target);
155
+ if (targetInfo.isSymbolicLink())
156
+ throw new ChatRoomError("FORBIDDEN", `Writes through symlinks are not allowed: ${relativePath}`);
157
+ if (!targetInfo.isFile())
158
+ throw new ChatRoomError("INVALID_INPUT", `Not a file: ${relativePath}`);
159
+ }
160
+ catch (error) {
161
+ if (error instanceof ChatRoomError)
162
+ throw error;
163
+ if (error.code !== "ENOENT")
164
+ throw error;
165
+ }
166
+ return target;
167
+ }
168
+ async resolveReadable(relativePath) {
169
+ const lexical = this.lexical(relativePath);
170
+ const canonical = await realpath(lexical).catch((error) => {
171
+ if (error.code === "ENOENT")
172
+ throw new ChatRoomError("NOT_FOUND", `Path does not exist: ${relativePath}`);
173
+ throw error;
174
+ });
175
+ if (!inside(this.root, canonical))
176
+ throw new ChatRoomError("FORBIDDEN", "Symlink escapes workspace");
177
+ return canonical;
178
+ }
179
+ }
180
+ function normalizeRelative(input) {
181
+ if (typeof input !== "string" || input.includes("\0"))
182
+ throw new ChatRoomError("INVALID_INPUT", "Path must be a valid string");
183
+ if (path.isAbsolute(input) ||
184
+ path.win32.isAbsolute(input) ||
185
+ /^\\\\/.test(input))
186
+ throw new ChatRoomError("FORBIDDEN", "Absolute paths are not allowed");
187
+ const parts = input
188
+ .split(/[\\/]+/)
189
+ .filter((part) => part !== "" && part !== ".");
190
+ if (parts.some((part) => part === ".."))
191
+ throw new ChatRoomError("FORBIDDEN", "Path traversal is not allowed");
192
+ return parts.join("/") || ".";
193
+ }
194
+ function inside(root, candidate) {
195
+ const relative = path.relative(root, candidate);
196
+ return (relative === "" ||
197
+ (!relative.startsWith(`..${path.sep}`) &&
198
+ relative !== ".." &&
199
+ !path.isAbsolute(relative)));
200
+ }
@@ -1,46 +1,15 @@
1
- import type { Workspace, WorkspaceMode } from "./domain/workspace.js";
2
- import type { WorkspaceStateRepository } from "./domain/repository.js";
3
- import { WorkspaceFs } from "./infrastructure/workspace-fs.js";
4
- import type { GitService } from "./git/git-service.js";
5
- import type { WorktreeReviewService } from "./worktree-review-service.js";
6
- interface OpenWorkspaceRequest {
7
- path: string;
8
- mode?: WorkspaceMode;
9
- }
1
+ import type { WorkspaceEntry, WorkspaceInfo } from "./types.js";
2
+ import { WorkspaceFs } from "./workspace-fs.js";
10
3
  export declare class WorkspaceService {
11
- private readonly repository;
12
- private readonly git;
13
- private readonly review;
14
4
  private readonly allowedRoots;
15
- private readonly dataDir;
16
- private readonly pending;
17
- private readonly filesystems;
18
- private readonly workspaces;
19
5
  private constructor();
20
- static create(repository: WorkspaceStateRepository, git: GitService, review: WorktreeReviewService, allowedRoots: string[], dataDir: string): Promise<WorkspaceService>;
21
- open(request: OpenWorkspaceRequest): Promise<Workspace>;
22
- get(workspaceId: string): Workspace;
23
- list(): Workspace[];
24
- discoverGitWorkspaces(): Promise<Workspace[]>;
25
- private discoverGitWorkspace;
26
- markUsed(workspaceId: string): Workspace;
27
- fs(workspaceId: string): Promise<WorkspaceFs>;
28
- refresh(workspaceId: string): Promise<Workspace>;
29
- remove(workspaceId: string, force?: boolean): Promise<Workspace>;
30
- previewWorktreeApply(workspaceId: string): Promise<import("./domain/review.js").WorktreeApplyPreview>;
31
- previewWorktreeFileDiff(workspaceId: string, filePath: string): Promise<import("./domain/review.js").WorktreeFileDiff>;
32
- applyWorktree(workspaceId: string, paths?: string[]): Promise<{
33
- workspaceId: string;
34
- bytes: number;
35
- paths: string[];
36
- conflicts: string[];
37
- }>;
38
- private openLocked;
39
- private createWorkspace;
40
- private restorePersistedWorkspaces;
41
- private isRecordValid;
42
- private reconcileManagedWorktrees;
43
- private removeManagedContainer;
44
- private validateSourceRoot;
6
+ static create(allowedRoots: string[]): Promise<WorkspaceService>;
7
+ roots(): string[];
8
+ list(): Promise<WorkspaceEntry[]>;
9
+ resolve(input: string): Promise<string>;
10
+ createProject(parentInput: string, nameInput: string): Promise<WorkspaceEntry>;
11
+ info(input: string): Promise<WorkspaceInfo>;
12
+ fs(input: string): Promise<WorkspaceFs>;
13
+ private isWorkspaceRoot;
14
+ private resolveAllowedRoot;
45
15
  }
46
- export {};