@joshski/dust 0.1.101 → 0.1.102
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/dist/audits.js +33 -1
- package/dist/claude/spawn-claude-code.d.ts +12 -0
- package/dist/claude/types.d.ts +2 -0
- package/dist/dust.js +312 -77
- package/dist/env-config.d.ts +2 -0
- package/dist/filesystem-emulator.js +1 -1
- package/dist/logging/index.d.ts +10 -1
- package/dist/logging/match.d.ts +21 -1
- package/dist/logging.js +18 -3
- package/dist/proxy/claude-api-proxy.d.ts +34 -1
- package/dist/proxy/git-credential-proxy.d.ts +2 -0
- package/dist/proxy/helper-token.d.ts +73 -0
- package/package.json +1 -1
package/dist/env-config.d.ts
CHANGED
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
* - DEBUG: Pattern for stdout debug logging (comma-separated wildcards)
|
|
11
11
|
* - DUST_LOG_DIR: Override default log directory location
|
|
12
12
|
* - DUST_LOG_FILE: Inherited log file path for child processes
|
|
13
|
+
* - DUST_LOG_FORMAT: Output format ('json' for JSON Lines, 'text' for human-readable)
|
|
13
14
|
*/
|
|
14
15
|
export interface LoggingConfig {
|
|
15
16
|
debug: string | undefined;
|
|
16
17
|
logDir: string | undefined;
|
|
17
18
|
logFile: string | undefined;
|
|
19
|
+
logFormat: 'json' | 'text' | undefined;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* Dustbucket connection configuration.
|
|
@@ -48,7 +48,7 @@ function createFileSystemEmulator(tree = {}, flatFiles) {
|
|
|
48
48
|
creationTimes.set(path, nextCreationTime++);
|
|
49
49
|
}
|
|
50
50
|
return {
|
|
51
|
-
exists:
|
|
51
|
+
exists: paths.has.bind(paths),
|
|
52
52
|
isDirectory: (path) => paths.has(path) && !files.has(path),
|
|
53
53
|
readFile: async (path) => {
|
|
54
54
|
if (!files.has(path)) {
|
package/dist/logging/index.d.ts
CHANGED
|
@@ -46,8 +46,17 @@
|
|
|
46
46
|
* No external dependencies.
|
|
47
47
|
*/
|
|
48
48
|
import type { LoggingConfig } from '../env-config';
|
|
49
|
+
import { type LogEntry } from './match';
|
|
49
50
|
import { type LogSink } from './sink';
|
|
50
|
-
export type
|
|
51
|
+
export type { LogEntry };
|
|
52
|
+
/**
|
|
53
|
+
* Optional context object for structured logging.
|
|
54
|
+
* Fields are passed through to JSON output as-is.
|
|
55
|
+
*/
|
|
56
|
+
export interface LogContext {
|
|
57
|
+
[key: string]: unknown;
|
|
58
|
+
}
|
|
59
|
+
export type LogFn = (message: string, context?: LogContext) => void;
|
|
51
60
|
export interface LoggerOptions {
|
|
52
61
|
/**
|
|
53
62
|
* Per-logger file routing override.
|
package/dist/logging/match.d.ts
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
* Parses a DEBUG-style string (comma-separated, `*` wildcards)
|
|
5
5
|
* and tests logger names against it. No side effects.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* Structured log entry for JSON output.
|
|
9
|
+
* Required fields: ts, logger, level, msg
|
|
10
|
+
* Additional context fields are passed through as-is.
|
|
11
|
+
*/
|
|
12
|
+
export interface LogEntry {
|
|
13
|
+
ts: string;
|
|
14
|
+
logger: string;
|
|
15
|
+
level: 'info';
|
|
16
|
+
msg: string;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
7
19
|
/**
|
|
8
20
|
* Parse a DEBUG expression string into an array of RegExp matchers.
|
|
9
21
|
* Returns an empty array when the input is empty or undefined.
|
|
@@ -14,6 +26,14 @@ export declare function parsePatterns(debug: string | undefined): RegExp[];
|
|
|
14
26
|
*/
|
|
15
27
|
export declare function matchesAny(name: string, patterns: RegExp[]): boolean;
|
|
16
28
|
/**
|
|
17
|
-
* Format a log line with ISO timestamp and logger name.
|
|
29
|
+
* Format a log line with ISO timestamp and logger name (text format).
|
|
18
30
|
*/
|
|
19
31
|
export declare function formatLine(name: string, messages: unknown[]): string;
|
|
32
|
+
/**
|
|
33
|
+
* Format a log entry as a JSON line (JSON Lines format).
|
|
34
|
+
*/
|
|
35
|
+
export declare function formatJsonLine(entry: LogEntry): string;
|
|
36
|
+
/**
|
|
37
|
+
* Create a LogEntry from logger name, message, and optional context.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createLogEntry(name: string, message: string, context?: Record<string, unknown>): LogEntry;
|
package/dist/logging.js
CHANGED
|
@@ -20,6 +20,19 @@ function formatLine(name, messages) {
|
|
|
20
20
|
return `${new Date().toISOString()} [${name}] ${text}
|
|
21
21
|
`;
|
|
22
22
|
}
|
|
23
|
+
function formatJsonLine(entry) {
|
|
24
|
+
return JSON.stringify(entry) + `
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
function createLogEntry(name, message, context) {
|
|
28
|
+
return {
|
|
29
|
+
ts: new Date().toISOString(),
|
|
30
|
+
logger: name,
|
|
31
|
+
level: "info",
|
|
32
|
+
msg: message,
|
|
33
|
+
...context
|
|
34
|
+
};
|
|
35
|
+
}
|
|
23
36
|
|
|
24
37
|
// lib/logging/sink.ts
|
|
25
38
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
@@ -101,9 +114,10 @@ function createLoggingService(options) {
|
|
|
101
114
|
} else if (typeof loggerOptions?.file === "string") {
|
|
102
115
|
perLoggerSink = getOrCreateFileSink(loggerOptions.file);
|
|
103
116
|
}
|
|
104
|
-
|
|
117
|
+
const useJsonFormat = config.logFormat === "json";
|
|
118
|
+
return (message, context) => {
|
|
105
119
|
init();
|
|
106
|
-
const line = formatLine(name,
|
|
120
|
+
const line = useJsonFormat ? formatJsonLine(createLogEntry(name, message, context)) : formatLine(name, [message, ...context ? [context] : []]);
|
|
107
121
|
if (perLoggerSink !== undefined) {
|
|
108
122
|
if (perLoggerSink !== null) {
|
|
109
123
|
perLoggerSink.write(line);
|
|
@@ -126,7 +140,8 @@ var defaultService = createLoggingService({
|
|
|
126
140
|
config: {
|
|
127
141
|
debug: process.env.DEBUG,
|
|
128
142
|
logDir: process.env.DUST_LOG_DIR,
|
|
129
|
-
logFile: process.env.DUST_LOG_FILE
|
|
143
|
+
logFile: process.env.DUST_LOG_FILE,
|
|
144
|
+
logFormat: process.env.DUST_LOG_FORMAT === "json" ? "json" : process.env.DUST_LOG_FORMAT === "text" ? "text" : undefined
|
|
130
145
|
}
|
|
131
146
|
});
|
|
132
147
|
var enableFileLogs = defaultService.enableFileLogs.bind(defaultService);
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
* → Returns response to container
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
+
import { type HelperTokenState } from './helper-token';
|
|
24
25
|
export interface ClaudeApiProxyDependencies {
|
|
25
26
|
homedir: () => string;
|
|
26
27
|
readFileSync: (path: string, encoding: 'utf-8') => string;
|
|
@@ -99,6 +100,31 @@ export interface ErrorResponse {
|
|
|
99
100
|
* Build a 401 response for when no OAuth token is available.
|
|
100
101
|
*/
|
|
101
102
|
export declare function buildNoTokenResponse(): ErrorResponse;
|
|
103
|
+
/**
|
|
104
|
+
* Build a 401 response for when the helper token is invalid or expired.
|
|
105
|
+
*/
|
|
106
|
+
export declare function buildInvalidHelperTokenResponse(): ErrorResponse;
|
|
107
|
+
/**
|
|
108
|
+
* Extract the helper token from incoming request headers.
|
|
109
|
+
* Checks both Authorization header (Bearer token) and x-api-key header.
|
|
110
|
+
*/
|
|
111
|
+
export declare function extractHelperToken(headers: Record<string, string | string[] | undefined>): string | null;
|
|
112
|
+
/**
|
|
113
|
+
* Validate the incoming helper token against the issued token.
|
|
114
|
+
*/
|
|
115
|
+
export declare function validateHelperToken(incomingToken: string | null, state: HelperTokenState, now?: number): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Get the current helper token, rotating if needed.
|
|
118
|
+
* Returns the new state and the token string.
|
|
119
|
+
*/
|
|
120
|
+
export declare function getOrRefreshHelperToken(state: HelperTokenState, now?: number): {
|
|
121
|
+
state: HelperTokenState;
|
|
122
|
+
token: string;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Build a success response containing the helper token.
|
|
126
|
+
*/
|
|
127
|
+
export declare function buildTokenResponse(token: string): ProxyResponse;
|
|
102
128
|
/**
|
|
103
129
|
* Build a 502 response for when the upstream request fails.
|
|
104
130
|
*/
|
|
@@ -106,11 +132,18 @@ export declare function buildUpstreamErrorResponse(error: unknown): ErrorRespons
|
|
|
106
132
|
/**
|
|
107
133
|
* Handle a proxy request and return a platform-agnostic response.
|
|
108
134
|
* This is the pure core of the proxy logic, separated from HTTP plumbing.
|
|
135
|
+
*
|
|
136
|
+
* When helperTokenState is provided, incoming requests must include a valid
|
|
137
|
+
* helper token in the Authorization or x-api-key header.
|
|
109
138
|
*/
|
|
110
|
-
export declare function handleProxyRequest(request: ProxyRequest, dependencies: ClaudeApiProxyDependencies): Promise<ProxyResponse>;
|
|
139
|
+
export declare function handleProxyRequest(request: ProxyRequest, dependencies: ClaudeApiProxyDependencies, helperTokenState?: HelperTokenState, now?: number): Promise<ProxyResponse>;
|
|
111
140
|
/**
|
|
112
141
|
* Creates a Claude API proxy server.
|
|
113
142
|
* The server accepts HTTP requests and forwards them to the Anthropic API
|
|
114
143
|
* with the OAuth token injected.
|
|
144
|
+
*
|
|
145
|
+
* The server maintains helper token state and provides a `/token` endpoint
|
|
146
|
+
* that returns the current helper token. All other requests must include
|
|
147
|
+
* a valid helper token in the Authorization or x-api-key header.
|
|
115
148
|
*/
|
|
116
149
|
export declare function createClaudeApiProxyServer(dependencies?: ClaudeApiProxyDependencies): Promise<ClaudeApiProxyServer>;
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
import type { spawn as nodeSpawn } from 'node:child_process';
|
|
21
21
|
export interface GitCredentialProxyDependencies {
|
|
22
22
|
spawn: typeof nodeSpawn;
|
|
23
|
+
/** Real user HOME directory — used when the process HOME has been overridden */
|
|
24
|
+
userHome?: string;
|
|
23
25
|
}
|
|
24
26
|
export interface GitCredentials {
|
|
25
27
|
username: string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper Token Module
|
|
3
|
+
*
|
|
4
|
+
* Pure functional module for generating and validating short-TTL helper tokens.
|
|
5
|
+
* These tokens let containerized Claude Code authenticate with the host OAuth gateway.
|
|
6
|
+
*
|
|
7
|
+
* When running Claude Code in Docker containers, the real OAuth token should never
|
|
8
|
+
* enter the container environment. Instead, the container fetches a synthetic
|
|
9
|
+
* "helper token" from the host gateway. The gateway validates this helper token
|
|
10
|
+
* before injecting the real OAuth token upstream.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Time-to-live for helper tokens in milliseconds.
|
|
14
|
+
* Tokens expire after this duration and must be regenerated.
|
|
15
|
+
*/
|
|
16
|
+
export declare const HELPER_TOKEN_TTL_MS = 60000;
|
|
17
|
+
/**
|
|
18
|
+
* Represents a generated helper token with its issue time.
|
|
19
|
+
*/
|
|
20
|
+
export interface HelperToken {
|
|
21
|
+
token: string;
|
|
22
|
+
issuedAt: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* State object tracking the current helper token.
|
|
26
|
+
*/
|
|
27
|
+
export interface HelperTokenState {
|
|
28
|
+
current: HelperToken | null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generate a synthetic helper token that mimics the Claude API key format.
|
|
32
|
+
* The token is cryptographically random and follows the pattern: sk-ant-api03-...
|
|
33
|
+
*
|
|
34
|
+
* @param now - Current timestamp in milliseconds (defaults to Date.now())
|
|
35
|
+
* @returns A HelperToken containing the token string and issue time
|
|
36
|
+
*/
|
|
37
|
+
export declare function generateHelperToken(now?: number): HelperToken;
|
|
38
|
+
/**
|
|
39
|
+
* Check if a token matches the issued token and is within its TTL.
|
|
40
|
+
*
|
|
41
|
+
* @param token - The token string to validate
|
|
42
|
+
* @param issued - The HelperToken that was issued
|
|
43
|
+
* @param now - Current timestamp in milliseconds (defaults to Date.now())
|
|
44
|
+
* @param ttlMs - Time-to-live in milliseconds (defaults to HELPER_TOKEN_TTL_MS)
|
|
45
|
+
* @returns true if the token is valid, false otherwise
|
|
46
|
+
*/
|
|
47
|
+
export declare function isHelperTokenValid(token: string, issued: HelperToken, now?: number, ttlMs?: number): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Create a new helper token state object.
|
|
50
|
+
* The state starts with no current token.
|
|
51
|
+
*
|
|
52
|
+
* @returns A new HelperTokenState with null current token
|
|
53
|
+
*/
|
|
54
|
+
export declare function createHelperTokenState(): HelperTokenState;
|
|
55
|
+
/**
|
|
56
|
+
* Rotate the helper token state by generating a new token.
|
|
57
|
+
* Returns a new state object with the new token (immutable).
|
|
58
|
+
*
|
|
59
|
+
* @param state - The current helper token state
|
|
60
|
+
* @param now - Current timestamp in milliseconds (defaults to Date.now())
|
|
61
|
+
* @returns A new HelperTokenState with a fresh token
|
|
62
|
+
*/
|
|
63
|
+
export declare function rotateHelperToken(state: HelperTokenState, now?: number): HelperTokenState;
|
|
64
|
+
/**
|
|
65
|
+
* Check if the current helper token in state is valid.
|
|
66
|
+
* Returns false if there is no current token.
|
|
67
|
+
*
|
|
68
|
+
* @param state - The helper token state to check
|
|
69
|
+
* @param now - Current timestamp in milliseconds (defaults to Date.now())
|
|
70
|
+
* @param ttlMs - Time-to-live in milliseconds (defaults to HELPER_TOKEN_TTL_MS)
|
|
71
|
+
* @returns true if the current token exists and is within TTL
|
|
72
|
+
*/
|
|
73
|
+
export declare function isCurrentTokenValid(state: HelperTokenState, now?: number, ttlMs?: number): boolean;
|