@ngmthaq20/claude-office 0.0.0-stage → 1.0.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.
- package/README.md +36 -2
- package/dist/cli.js +297 -0
- package/dist/server/main.js +3294 -0
- package/dist/server/migrate.js +87 -0
- package/dist/server/migrations/0001_init.sql +2 -0
- package/dist/server/migrations/0002_rooms.sql +6 -0
- package/dist/server/migrations/0003_room_name_archive.sql +2 -0
- package/dist/server/migrations/0004_room_permission_mode.sql +1 -0
- package/dist/server/migrations/0005_room_title.sql +1 -0
- package/dist/web/assets/Cache-fEtFvAzf.js +1 -0
- package/dist/web/assets/CanvasPool-BZ0sA3Gt.js +23 -0
- package/dist/web/assets/Filter-OkrPcSWo.js +1 -0
- package/dist/web/assets/GCManagedHash-DAvwKUzM.js +221 -0
- package/dist/web/assets/GraphicsContext-ehS8RVPM.js +1 -0
- package/dist/web/assets/RenderTargetSystem-Ij2FYAOb.js +184 -0
- package/dist/web/assets/bootstrap-icons-BHUGvtHM.woff +0 -0
- package/dist/web/assets/bootstrap-icons-DJdhg9lH.woff2 +0 -0
- package/dist/web/assets/browserAll-D5MrI34_.js +14 -0
- package/dist/web/assets/canvasUtils-BlnLdLY1.js +1 -0
- package/dist/web/assets/getTexelRangeRects-CJhL1thj.js +195 -0
- package/dist/web/assets/getTextureBatchBindGroup-CckcnH94.js +1 -0
- package/dist/web/assets/index-BLLR2wOy.js +384 -0
- package/dist/web/assets/index-CT9RduF9.css +1 -0
- package/dist/web/assets/init-wyB2AFnc.js +83 -0
- package/dist/web/assets/roguelikeChar_transparent-DP2joJTf.png +0 -0
- package/dist/web/assets/roguelikeIndoor_transparent-ConUXEsz.png +0 -0
- package/dist/web/assets/webworkerAll-CO_3Ti5Q.js +1 -0
- package/dist/web/index.html +22 -0
- package/package.json +33 -3
package/README.md
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ngmthaq20/claude-office
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Claude Office in your browser. One package holds the server and the web app, so there is nothing to clone or build.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node 24 or newer
|
|
8
|
+
- Claude Code set up on your machine (the app uses your Claude login)
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npx @ngmthaq20/claude-office install
|
|
14
|
+
npx @ngmthaq20/claude-office open [PORT]
|
|
15
|
+
npx @ngmthaq20/claude-office upgrade
|
|
16
|
+
npx @ngmthaq20/claude-office uninstall [--yes]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `install` creates the data folder, sets up the database, and installs the package globally. After that you can run `claude-office open`.
|
|
20
|
+
- `open [PORT]` starts the server and opens `http://127.0.0.1:<PORT>` in your browser. The default port is 4317. Press Ctrl+C to stop. If the port is busy, the command shows an error and exits with code 1.
|
|
21
|
+
- `upgrade` installs the latest version globally, then runs that version's database migrations on your data folder. Restart any running `open` afterwards.
|
|
22
|
+
- `uninstall` asks before it deletes the data folder, then removes the global package. `--yes` skips the question. Without a terminal and without `--yes`, the data is kept.
|
|
23
|
+
- `help` shows the usage text.
|
|
24
|
+
|
|
25
|
+
## Data folder
|
|
26
|
+
|
|
27
|
+
Rooms and history are stored in `~/.@ngmthaq20-claude-office`. The server listens on `127.0.0.1` only.
|
|
28
|
+
|
|
29
|
+
## Publish
|
|
30
|
+
|
|
31
|
+
From the repo root (it builds everything first):
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
pnpm publish:cli
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Do not use `npm publish`. `pnpm publish` replaces the `workspace:*` dev dependencies with real versions.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/configs/cli.config.ts
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
var PACKAGE_NAME = "@ngmthaq20/claude-office";
|
|
9
|
+
var BIN_NAME = "claude-office";
|
|
10
|
+
var DEFAULT_PORT = 4317;
|
|
11
|
+
var HOST = "127.0.0.1";
|
|
12
|
+
var MIN_NODE_MAJOR = 24;
|
|
13
|
+
var READY_TIMEOUT_MS = 2e4;
|
|
14
|
+
var READY_POLL_MS = 200;
|
|
15
|
+
var DIST_DIR = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
var DATA_DIR = join(homedir(), `.${PACKAGE_NAME.replace("/", "-")}`);
|
|
17
|
+
var SERVER_MAIN_FILE = join(DIST_DIR, "server", "main.js");
|
|
18
|
+
var SERVER_MIGRATE_FILE = join(DIST_DIR, "server", "migrate.js");
|
|
19
|
+
var MIGRATIONS_DIR = join(DIST_DIR, "server", "migrations");
|
|
20
|
+
var WEB_DIST_DIR = join(DIST_DIR, "web");
|
|
21
|
+
var PACKAGE_VERSION = JSON.parse(readFileSync(join(DIST_DIR, "..", "package.json"), "utf8")).version;
|
|
22
|
+
|
|
23
|
+
// src/commands/install.command.ts
|
|
24
|
+
import { mkdirSync } from "node:fs";
|
|
25
|
+
|
|
26
|
+
// src/utils/npm.util.ts
|
|
27
|
+
import { spawn } from "node:child_process";
|
|
28
|
+
var SPAWN_SHELL = process.platform === "win32";
|
|
29
|
+
var NpmUtil = class {
|
|
30
|
+
/** Runs npm with fixed arguments and resolves with its exit code. */
|
|
31
|
+
static run = (args2) => new Promise((resolve) => {
|
|
32
|
+
const child = spawn("npm", args2, { stdio: "inherit", shell: SPAWN_SHELL });
|
|
33
|
+
child.once("error", () => resolve(1));
|
|
34
|
+
child.once("close", (code) => resolve(code ?? 1));
|
|
35
|
+
});
|
|
36
|
+
/** Returns the global node_modules folder, or null when npm cannot tell. */
|
|
37
|
+
static globalRoot = () => new Promise((resolve) => {
|
|
38
|
+
const child = spawn("npm", ["root", "-g"], {
|
|
39
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
40
|
+
shell: SPAWN_SHELL
|
|
41
|
+
});
|
|
42
|
+
let output = "";
|
|
43
|
+
child.stdout.on("data", (chunk) => output += chunk.toString());
|
|
44
|
+
child.once("error", () => resolve(null));
|
|
45
|
+
child.once("close", (code) => resolve(code === 0 && output.trim() ? output.trim() : null));
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// src/utils/server-process.util.ts
|
|
50
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
51
|
+
var ServerProcessUtil = class {
|
|
52
|
+
static start = (file, extraEnv = {}) => spawn2(process.execPath, [file], {
|
|
53
|
+
stdio: "inherit",
|
|
54
|
+
env: {
|
|
55
|
+
...process.env,
|
|
56
|
+
NODE_ENV: "production",
|
|
57
|
+
CLAUDE_OFFICE_DATA_DIR: DATA_DIR,
|
|
58
|
+
CLAUDE_OFFICE_WEB_DIST_DIR: WEB_DIST_DIR,
|
|
59
|
+
CLAUDE_OFFICE_MIGRATIONS_DIR: MIGRATIONS_DIR,
|
|
60
|
+
...extraEnv
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
static waitForExit = (child) => new Promise((resolve) => {
|
|
64
|
+
if (child.exitCode !== null) return resolve(child.exitCode);
|
|
65
|
+
child.once("exit", (code, signal) => resolve(code ?? (signal ? 1 : 0)));
|
|
66
|
+
});
|
|
67
|
+
/** Resolves true when the server answers, false on timeout or when the child exits first. */
|
|
68
|
+
static waitUntilReady = async (url, child) => {
|
|
69
|
+
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
70
|
+
while (Date.now() < deadline) {
|
|
71
|
+
if (child.exitCode !== null || child.signalCode !== null) return false;
|
|
72
|
+
try {
|
|
73
|
+
if ((await fetch(url)).ok) return true;
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, READY_POLL_MS));
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/commands/install.command.ts
|
|
83
|
+
var installCommand = async () => {
|
|
84
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
85
|
+
console.log(`Data folder ready: ${DATA_DIR}`);
|
|
86
|
+
const migrateCode = await ServerProcessUtil.waitForExit(
|
|
87
|
+
ServerProcessUtil.start(SERVER_MIGRATE_FILE)
|
|
88
|
+
);
|
|
89
|
+
if (migrateCode !== 0) {
|
|
90
|
+
console.error("Database setup failed.");
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
const npmCode = await NpmUtil.run(["install", "-g", `${PACKAGE_NAME}@${PACKAGE_VERSION}`]);
|
|
94
|
+
if (npmCode !== 0) {
|
|
95
|
+
console.error(`Global install failed (npm exit code ${npmCode}).`);
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
console.log(`Installed ${PACKAGE_NAME}@${PACKAGE_VERSION}. Next step: ${BIN_NAME} open`);
|
|
99
|
+
return 0;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// src/commands/open.command.ts
|
|
103
|
+
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
104
|
+
|
|
105
|
+
// src/utils/browser.util.ts
|
|
106
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
107
|
+
var BrowserUtil = class _BrowserUtil {
|
|
108
|
+
/** Returns false when the browser could not be started. */
|
|
109
|
+
static open = (url) => new Promise((resolve) => {
|
|
110
|
+
const [command2, args2] = _BrowserUtil.commandFor(url);
|
|
111
|
+
const child = spawn3(command2, args2, { stdio: "ignore", detached: true });
|
|
112
|
+
child.once("error", () => resolve(false));
|
|
113
|
+
child.once("spawn", () => {
|
|
114
|
+
child.unref();
|
|
115
|
+
resolve(true);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
static commandFor = (url) => {
|
|
119
|
+
if (process.platform === "darwin") return ["open", [url]];
|
|
120
|
+
if (process.platform === "win32") return ["cmd", ["/c", "start", '""', url]];
|
|
121
|
+
return ["xdg-open", [url]];
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// src/utils/port.util.ts
|
|
126
|
+
import { createServer } from "node:net";
|
|
127
|
+
var MIN_PORT = 1;
|
|
128
|
+
var MAX_PORT = 65535;
|
|
129
|
+
var PortUtil = class {
|
|
130
|
+
static parse = (value) => {
|
|
131
|
+
if (!/^\d+$/.test(value)) return null;
|
|
132
|
+
const port = Number(value);
|
|
133
|
+
return port >= MIN_PORT && port <= MAX_PORT ? port : null;
|
|
134
|
+
};
|
|
135
|
+
static isFree = (port) => new Promise((resolve) => {
|
|
136
|
+
const probe = createServer();
|
|
137
|
+
probe.once("error", () => resolve(false));
|
|
138
|
+
probe.listen(port, HOST, () => probe.close(() => resolve(true)));
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// src/commands/open.command.ts
|
|
143
|
+
var openCommand = async (args2) => {
|
|
144
|
+
const port = args2[0] === void 0 ? DEFAULT_PORT : PortUtil.parse(args2[0]);
|
|
145
|
+
if (port === null) {
|
|
146
|
+
console.error(`Invalid port "${args2[0]}". Use a whole number from 1 to 65535.`);
|
|
147
|
+
console.error(`Example: ${BIN_NAME} open 4317`);
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
if (!await PortUtil.isFree(port)) {
|
|
151
|
+
console.error(`Port ${port} is already in use. Pick another port: ${BIN_NAME} open <PORT>`);
|
|
152
|
+
return 1;
|
|
153
|
+
}
|
|
154
|
+
mkdirSync2(DATA_DIR, { recursive: true });
|
|
155
|
+
const url = `http://${HOST}:${port}`;
|
|
156
|
+
const child = ServerProcessUtil.start(SERVER_MAIN_FILE, { PORT: String(port) });
|
|
157
|
+
const exited = ServerProcessUtil.waitForExit(child);
|
|
158
|
+
let stopping = false;
|
|
159
|
+
const stop = () => {
|
|
160
|
+
stopping = true;
|
|
161
|
+
child.kill("SIGTERM");
|
|
162
|
+
};
|
|
163
|
+
process.once("SIGINT", stop);
|
|
164
|
+
process.once("SIGTERM", stop);
|
|
165
|
+
if (await ServerProcessUtil.waitUntilReady(url, child)) {
|
|
166
|
+
console.log(`Claude Office is running at ${url} (Ctrl+C to stop)`);
|
|
167
|
+
if (!await BrowserUtil.open(url)) {
|
|
168
|
+
console.warn(`Could not open the browser. Open this address yourself: ${url}`);
|
|
169
|
+
}
|
|
170
|
+
} else if (!stopping) {
|
|
171
|
+
console.error(`The server did not become ready at ${url}.`);
|
|
172
|
+
child.kill("SIGTERM");
|
|
173
|
+
await exited;
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
176
|
+
const code = await exited;
|
|
177
|
+
return stopping ? 0 : code;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// src/commands/uninstall.command.ts
|
|
181
|
+
import { rmSync } from "node:fs";
|
|
182
|
+
|
|
183
|
+
// src/utils/prompt.util.ts
|
|
184
|
+
import { createInterface } from "node:readline/promises";
|
|
185
|
+
var PromptUtil = class {
|
|
186
|
+
/** Asks a yes/no question. The answer is "no" unless the user types y or yes. */
|
|
187
|
+
static confirm = async (question) => {
|
|
188
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
189
|
+
try {
|
|
190
|
+
const answer = await rl.question(`${question} (y/N) `);
|
|
191
|
+
return ["y", "yes"].includes(answer.trim().toLowerCase());
|
|
192
|
+
} finally {
|
|
193
|
+
rl.close();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// src/commands/uninstall.command.ts
|
|
199
|
+
var shouldDeleteData = async (skipQuestion) => {
|
|
200
|
+
if (skipQuestion) return true;
|
|
201
|
+
if (!process.stdin.isTTY) {
|
|
202
|
+
console.log("No terminal to ask in. Keeping the data folder (use --yes to delete it).");
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return PromptUtil.confirm(`Delete data folder ${DATA_DIR}?`);
|
|
206
|
+
};
|
|
207
|
+
var uninstallCommand = async (args2) => {
|
|
208
|
+
const deleteData = await shouldDeleteData(args2.includes("--yes"));
|
|
209
|
+
if (deleteData) rmSync(DATA_DIR, { recursive: true, force: true });
|
|
210
|
+
const npmCode = await NpmUtil.run(["uninstall", "-g", PACKAGE_NAME]);
|
|
211
|
+
if (npmCode !== 0) {
|
|
212
|
+
console.error(`Global uninstall failed (npm exit code ${npmCode}).`);
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
console.log(`Removed: global package ${PACKAGE_NAME}`);
|
|
216
|
+
console.log(deleteData ? `Removed: ${DATA_DIR}` : `Kept: ${DATA_DIR}`);
|
|
217
|
+
return 0;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// src/commands/upgrade.command.ts
|
|
221
|
+
import { existsSync, mkdirSync as mkdirSync3, readFileSync as readFileSync2 } from "node:fs";
|
|
222
|
+
import { join as join2 } from "node:path";
|
|
223
|
+
var readVersion = (packageDir) => JSON.parse(readFileSync2(join2(packageDir, "package.json"), "utf8")).version;
|
|
224
|
+
var upgradeCommand = async () => {
|
|
225
|
+
const npmCode = await NpmUtil.run(["install", "-g", `${PACKAGE_NAME}@latest`]);
|
|
226
|
+
if (npmCode !== 0) {
|
|
227
|
+
console.error(`Global install failed (npm exit code ${npmCode}).`);
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
const globalRoot = await NpmUtil.globalRoot();
|
|
231
|
+
const packageDir = globalRoot === null ? null : join2(globalRoot, PACKAGE_NAME);
|
|
232
|
+
if (packageDir === null || !existsSync(join2(packageDir, "package.json"))) {
|
|
233
|
+
console.error(
|
|
234
|
+
`Could not find the installed ${PACKAGE_NAME}. Run "${BIN_NAME} open" to finish the database setup.`
|
|
235
|
+
);
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
238
|
+
const serverDir = join2(packageDir, "dist", "server");
|
|
239
|
+
mkdirSync3(DATA_DIR, { recursive: true });
|
|
240
|
+
const migrateCode = await ServerProcessUtil.waitForExit(
|
|
241
|
+
ServerProcessUtil.start(join2(serverDir, "migrate.js"), {
|
|
242
|
+
CLAUDE_OFFICE_MIGRATIONS_DIR: join2(serverDir, "migrations")
|
|
243
|
+
})
|
|
244
|
+
);
|
|
245
|
+
if (migrateCode !== 0) {
|
|
246
|
+
console.error(
|
|
247
|
+
`Database migration failed. Fix the error above, then run "${BIN_NAME} upgrade" again.`
|
|
248
|
+
);
|
|
249
|
+
return 1;
|
|
250
|
+
}
|
|
251
|
+
console.log(`Upgraded ${PACKAGE_NAME}: ${PACKAGE_VERSION} -> ${readVersion(packageDir)}`);
|
|
252
|
+
console.log(`Restart any running "${BIN_NAME} open" to use the new version.`);
|
|
253
|
+
return 0;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// src/utils/node-version.util.ts
|
|
257
|
+
var NodeVersionUtil = class {
|
|
258
|
+
static isSupported = () => Number(process.versions.node.split(".")[0]) >= MIN_NODE_MAJOR;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/cli.ts
|
|
262
|
+
var USAGE = `Usage: ${BIN_NAME} <command>
|
|
263
|
+
|
|
264
|
+
Commands:
|
|
265
|
+
install Create the data folder, set up the database, and install ${PACKAGE_NAME} globally
|
|
266
|
+
upgrade Install the latest ${PACKAGE_NAME} globally and run new database migrations
|
|
267
|
+
uninstall [--yes] Remove the global package (asks before deleting the data folder; --yes skips the question)
|
|
268
|
+
open [PORT] Start Claude Office and open it in the browser (default port ${DEFAULT_PORT})
|
|
269
|
+
help Show this message`;
|
|
270
|
+
var run = async (command2, args2) => {
|
|
271
|
+
switch (command2) {
|
|
272
|
+
case "install":
|
|
273
|
+
return installCommand();
|
|
274
|
+
case "upgrade":
|
|
275
|
+
return upgradeCommand();
|
|
276
|
+
case "uninstall":
|
|
277
|
+
return uninstallCommand(args2);
|
|
278
|
+
case "open":
|
|
279
|
+
return openCommand(args2);
|
|
280
|
+
case "help":
|
|
281
|
+
console.log(USAGE);
|
|
282
|
+
return 0;
|
|
283
|
+
default:
|
|
284
|
+
if (command2 !== void 0) console.error(`Unknown command "${command2}".
|
|
285
|
+
`);
|
|
286
|
+
console.error(USAGE);
|
|
287
|
+
return 1;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
if (!NodeVersionUtil.isSupported()) {
|
|
291
|
+
console.error(
|
|
292
|
+
`${PACKAGE_NAME} needs Node ${MIN_NODE_MAJOR} or newer. You have ${process.version}.`
|
|
293
|
+
);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
var [command, ...args] = process.argv.slice(2);
|
|
297
|
+
process.exit(await run(command, args));
|