@kimuson/claude-code-viewer 0.6.0-beta.1 → 0.6.0-beta.2
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 +9 -0
- package/dist/main.js +92 -40
- package/dist/main.js.map +3 -3
- package/dist/static/assets/{ProtectedRoute-CZEcJAz0.js → ProtectedRoute-kldRjzy3.js} +1 -1
- package/dist/static/assets/{eye-B778x9yq.js → eye-BEkfdnRO.js} +1 -1
- package/dist/static/assets/{index-oaJjtYYB.js → index-C9P7O98i.js} +1 -1
- package/dist/static/assets/{index-Bku-MhbL.js → index-CHZixXYx.js} +1 -1
- package/dist/static/assets/{index-BrH8eRF1.js → index-nTFAt0o5.js} +2 -2
- package/dist/static/assets/{label-NirMm8Yi.js → label-DigNu3m4.js} +1 -1
- package/dist/static/assets/{login-BP3UU3Ko.js → login-CotBrr4P.js} +1 -1
- package/dist/static/assets/{session-BRgfW03O.js → session-B4VFb0DB.js} +30 -30
- package/dist/static/assets/{session-C9vbFKQy.js → session-BfEVLScy.js} +1 -1
- package/dist/static/index.html +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -168,6 +168,15 @@ Claude Code Viewer can be configured using command-line options or environment v
|
|
|
168
168
|
- `CLAUDE_CODE_VIEWER_CC_EXECUTABLE_PATH` → `CCV_CC_EXECUTABLE_PATH`
|
|
169
169
|
- New environment variable added: `CCV_GLOBAL_CLAUDE_DIR` (previously the Claude directory path was hardcoded to `~/.claude`)
|
|
170
170
|
|
|
171
|
+
### API Authentication
|
|
172
|
+
|
|
173
|
+
When `--password` / `CCV_PASSWORD` is set, all `/api` routes (except auth-related endpoints) require authentication. You can authenticate in two ways:
|
|
174
|
+
|
|
175
|
+
1. **Session cookie**: Use the `/api/auth/login` endpoint to set the `ccv-session` cookie, then include it in subsequent requests.
|
|
176
|
+
2. **Authorization header**: Send `Authorization: Bearer <password>` on each request.
|
|
177
|
+
|
|
178
|
+
If no password is configured, API authentication is disabled.
|
|
179
|
+
|
|
171
180
|
### User Settings
|
|
172
181
|
|
|
173
182
|
Settings can be configured from the sidebar in Claude Code Viewer.
|
package/dist/main.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Effect as Effect65 } from "effect";
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "@kimuson/claude-code-viewer",
|
|
10
|
-
version: "0.6.0-beta.
|
|
10
|
+
version: "0.6.0-beta.2",
|
|
11
11
|
description: "A full-featured web-based Claude Code client that provides complete interactive functionality for managing Claude Code projects.",
|
|
12
12
|
type: "module",
|
|
13
13
|
license: "MIT",
|
|
@@ -39,7 +39,7 @@ var package_default = {
|
|
|
39
39
|
fix: "run-s 'fix:*'",
|
|
40
40
|
"fix:biome-format": "biome format --write .",
|
|
41
41
|
"fix:biome-lint": "biome check --write --unsafe .",
|
|
42
|
-
typecheck: "
|
|
42
|
+
typecheck: "tsgo --noEmit",
|
|
43
43
|
test: "vitest --run",
|
|
44
44
|
"test:watch": "vitest",
|
|
45
45
|
e2e: "./scripts/e2e/exec_e2e.sh",
|
|
@@ -76,6 +76,7 @@ var package_default = {
|
|
|
76
76
|
"@radix-ui/react-slot": "1.2.4",
|
|
77
77
|
"@radix-ui/react-tabs": "1.1.13",
|
|
78
78
|
"@radix-ui/react-tooltip": "1.2.8",
|
|
79
|
+
"@replit/ruspty": "^3.6.0",
|
|
79
80
|
"@tailwindcss/vite": "4.1.18",
|
|
80
81
|
"@tanstack/react-devtools": "0.9.2",
|
|
81
82
|
"@tanstack/react-query": "5.90.20",
|
|
@@ -107,7 +108,6 @@ var package_default = {
|
|
|
107
108
|
sonner: "2.0.7",
|
|
108
109
|
"tailwind-merge": "3.4.0",
|
|
109
110
|
ulid: "3.0.2",
|
|
110
|
-
"node-pty": "1.1.0-beta34",
|
|
111
111
|
ws: "8.18.3",
|
|
112
112
|
zod: "4.3.6"
|
|
113
113
|
},
|
|
@@ -127,6 +127,7 @@ var package_default = {
|
|
|
127
127
|
"@types/react-dom": "19.2.3",
|
|
128
128
|
"@types/react-syntax-highlighter": "15.5.13",
|
|
129
129
|
"@types/ws": "8.18.0",
|
|
130
|
+
"@typescript/native-preview": "7.0.0-dev.20260207.1",
|
|
130
131
|
"@vitejs/plugin-react-swc": "4.2.2",
|
|
131
132
|
dotenv: "17.2.3",
|
|
132
133
|
esbuild: "0.27.2",
|
|
@@ -8758,6 +8759,41 @@ var TasksController = class extends Context37.Tag("TasksController")() {
|
|
|
8758
8759
|
// src/server/core/terminal/TerminalService.ts
|
|
8759
8760
|
import { Context as Context38, Effect as Effect48, Layer as Layer40 } from "effect";
|
|
8760
8761
|
import { ulid as ulid5 } from "ulid";
|
|
8762
|
+
|
|
8763
|
+
// src/server/core/terminal/normalizePtyChunk.ts
|
|
8764
|
+
var normalizePtyChunk = (chunk) => {
|
|
8765
|
+
if (typeof chunk === "string") {
|
|
8766
|
+
return chunk;
|
|
8767
|
+
}
|
|
8768
|
+
if (chunk instanceof Uint8Array) {
|
|
8769
|
+
return Buffer.from(chunk).toString("utf8");
|
|
8770
|
+
}
|
|
8771
|
+
if (chunk instanceof ArrayBuffer) {
|
|
8772
|
+
return Buffer.from(chunk).toString("utf8");
|
|
8773
|
+
}
|
|
8774
|
+
return null;
|
|
8775
|
+
};
|
|
8776
|
+
|
|
8777
|
+
// src/server/core/terminal/rusptyAdapter.ts
|
|
8778
|
+
var createRusptySession = (ruspty, options) => {
|
|
8779
|
+
const pty = new ruspty.Pty(options);
|
|
8780
|
+
return {
|
|
8781
|
+
process: {
|
|
8782
|
+
write: (data) => {
|
|
8783
|
+
pty.write.write(data);
|
|
8784
|
+
},
|
|
8785
|
+
resize: (cols, rows) => {
|
|
8786
|
+
pty.resize({ cols, rows });
|
|
8787
|
+
},
|
|
8788
|
+
kill: () => {
|
|
8789
|
+
pty.close();
|
|
8790
|
+
}
|
|
8791
|
+
},
|
|
8792
|
+
read: pty.read
|
|
8793
|
+
};
|
|
8794
|
+
};
|
|
8795
|
+
|
|
8796
|
+
// src/server/core/terminal/TerminalService.ts
|
|
8761
8797
|
var DEFAULT_COLS = 80;
|
|
8762
8798
|
var DEFAULT_ROWS = 24;
|
|
8763
8799
|
var MAX_BUFFER_BYTES = 1024 * 1024 * 2;
|
|
@@ -8794,9 +8830,9 @@ var LayerImpl30 = Effect48.gen(function* () {
|
|
|
8794
8830
|
snapshotSince: () => Effect48.succeed(void 0)
|
|
8795
8831
|
};
|
|
8796
8832
|
};
|
|
8797
|
-
const
|
|
8798
|
-
try: () => import("
|
|
8799
|
-
catch: (error) => new Error(`Failed to load
|
|
8833
|
+
const ruspty = yield* Effect48.tryPromise({
|
|
8834
|
+
try: () => import("@replit/ruspty"),
|
|
8835
|
+
catch: (error) => new Error(`Failed to load @replit/ruspty: ${String(error)}`)
|
|
8800
8836
|
}).pipe(
|
|
8801
8837
|
Effect48.catchAll(
|
|
8802
8838
|
(error) => Effect48.sync(() => {
|
|
@@ -8805,10 +8841,9 @@ var LayerImpl30 = Effect48.gen(function* () {
|
|
|
8805
8841
|
})
|
|
8806
8842
|
)
|
|
8807
8843
|
);
|
|
8808
|
-
if (!
|
|
8809
|
-
return disabledService("
|
|
8844
|
+
if (!ruspty) {
|
|
8845
|
+
return disabledService("@replit/ruspty failed to load");
|
|
8810
8846
|
}
|
|
8811
|
-
const { spawn } = nodePty;
|
|
8812
8847
|
const trimBuffer = (session) => {
|
|
8813
8848
|
while (session.bufferBytes > MAX_BUFFER_BYTES && session.buffer.length > 0) {
|
|
8814
8849
|
const removed = session.buffer.shift();
|
|
@@ -8864,16 +8899,26 @@ var LayerImpl30 = Effect48.gen(function* () {
|
|
|
8864
8899
|
options.fallbackShell,
|
|
8865
8900
|
options.unrestrictedFlag
|
|
8866
8901
|
);
|
|
8867
|
-
const
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8902
|
+
const { process: ptyProcess, read } = createRusptySession(ruspty, {
|
|
8903
|
+
command: shell.command,
|
|
8904
|
+
args: shell.args,
|
|
8905
|
+
envs: options.env,
|
|
8906
|
+
dir: options.cwd,
|
|
8907
|
+
size: { cols: DEFAULT_COLS, rows: DEFAULT_ROWS },
|
|
8908
|
+
onExit: (_error, exitCode) => {
|
|
8909
|
+
const current = sessions.get(id);
|
|
8910
|
+
if (!current) return;
|
|
8911
|
+
current.exited = true;
|
|
8912
|
+
current.lastActivity = Date.now();
|
|
8913
|
+
broadcast(current, { type: "exit", code: exitCode });
|
|
8914
|
+
if (current.clients.size === 0) {
|
|
8915
|
+
destroySession(current.id);
|
|
8916
|
+
}
|
|
8917
|
+
}
|
|
8873
8918
|
});
|
|
8874
8919
|
const session = {
|
|
8875
8920
|
id,
|
|
8876
|
-
pty,
|
|
8921
|
+
pty: ptyProcess,
|
|
8877
8922
|
seq: 0,
|
|
8878
8923
|
buffer: [],
|
|
8879
8924
|
bufferBytes: 0,
|
|
@@ -8882,27 +8927,22 @@ var LayerImpl30 = Effect48.gen(function* () {
|
|
|
8882
8927
|
exited: false,
|
|
8883
8928
|
inputBuffer: ""
|
|
8884
8929
|
};
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
session.
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8930
|
+
read.on("data", (chunk) => {
|
|
8931
|
+
const data = normalizePtyChunk(chunk);
|
|
8932
|
+
if (data === null) return;
|
|
8933
|
+
const current = sessions.get(session.id);
|
|
8934
|
+
if (!current || current.exited) return;
|
|
8935
|
+
current.lastActivity = Date.now();
|
|
8936
|
+
current.seq += 1;
|
|
8937
|
+
current.buffer.push({ seq: current.seq, data });
|
|
8938
|
+
current.bufferBytes += Buffer.byteLength(data, "utf8");
|
|
8939
|
+
trimBuffer(current);
|
|
8940
|
+
broadcast(current, {
|
|
8893
8941
|
type: "output",
|
|
8894
|
-
seq:
|
|
8942
|
+
seq: current.seq,
|
|
8895
8943
|
data
|
|
8896
8944
|
});
|
|
8897
8945
|
});
|
|
8898
|
-
pty.onExit((event) => {
|
|
8899
|
-
session.exited = true;
|
|
8900
|
-
session.lastActivity = Date.now();
|
|
8901
|
-
broadcast(session, { type: "exit", code: event.exitCode });
|
|
8902
|
-
if (session.clients.size === 0) {
|
|
8903
|
-
destroySession(session.id);
|
|
8904
|
-
}
|
|
8905
|
-
});
|
|
8906
8946
|
sessions.set(id, session);
|
|
8907
8947
|
return session;
|
|
8908
8948
|
};
|
|
@@ -9139,7 +9179,15 @@ var generateSessionToken = (password) => {
|
|
|
9139
9179
|
if (!password) return "";
|
|
9140
9180
|
return Buffer.from(`ccv-session:${password}`).toString("base64");
|
|
9141
9181
|
};
|
|
9142
|
-
var
|
|
9182
|
+
var getBearerToken = (authorization) => {
|
|
9183
|
+
if (!authorization) return void 0;
|
|
9184
|
+
const [scheme, token] = authorization.split(" ");
|
|
9185
|
+
if (!scheme || !token) return void 0;
|
|
9186
|
+
if (scheme.toLowerCase() !== "bearer") return void 0;
|
|
9187
|
+
const trimmedToken = token.trim();
|
|
9188
|
+
return trimmedToken.length > 0 ? trimmedToken : void 0;
|
|
9189
|
+
};
|
|
9190
|
+
var createAuthRequiredMiddleware = (authEnabled, validSessionToken, authPassword) => {
|
|
9143
9191
|
return createMiddleware(async (c, next) => {
|
|
9144
9192
|
if (!c.req.path.startsWith("/api")) {
|
|
9145
9193
|
return next();
|
|
@@ -9148,7 +9196,10 @@ var createAuthRequiredMiddleware = (authEnabled, validSessionToken) => {
|
|
|
9148
9196
|
return next();
|
|
9149
9197
|
}
|
|
9150
9198
|
const sessionToken = getCookie(c, "ccv-session");
|
|
9151
|
-
|
|
9199
|
+
const bearerToken = getBearerToken(c.req.header("Authorization"));
|
|
9200
|
+
const cookieAuthorized = sessionToken === validSessionToken;
|
|
9201
|
+
const bearerAuthorized = authPassword !== void 0 && bearerToken === authPassword;
|
|
9202
|
+
if (!cookieAuthorized && !bearerAuthorized) {
|
|
9152
9203
|
return c.json({ error: "Unauthorized" }, 401);
|
|
9153
9204
|
}
|
|
9154
9205
|
await next();
|
|
@@ -9170,11 +9221,12 @@ var LayerImpl31 = Effect50.gen(function* () {
|
|
|
9170
9221
|
if (!c.req.path.startsWith("/api")) {
|
|
9171
9222
|
return next();
|
|
9172
9223
|
}
|
|
9173
|
-
const { authEnabled, validSessionToken } = await runPromise(getAuthState);
|
|
9174
|
-
return createAuthRequiredMiddleware(
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9224
|
+
const { authEnabled, validSessionToken, authPassword } = await runPromise(getAuthState);
|
|
9225
|
+
return createAuthRequiredMiddleware(
|
|
9226
|
+
authEnabled,
|
|
9227
|
+
validSessionToken,
|
|
9228
|
+
authPassword
|
|
9229
|
+
)(c, next);
|
|
9178
9230
|
}
|
|
9179
9231
|
);
|
|
9180
9232
|
return {
|