@ottocode/sdk 0.1.235 → 0.1.236
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/package.json +1 -1
- package/src/config/src/manager.ts +55 -0
- package/src/config/src/paths.ts +27 -0
- package/src/core/src/utils/debug.ts +28 -1
- package/src/core/src/utils/logger.ts +116 -27
- package/src/index.ts +9 -1
- package/src/prompts/src/debug.ts +0 -7
- package/src/types/src/config.ts +2 -0
package/package.json
CHANGED
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
getGlobalConfigDir,
|
|
11
11
|
getGlobalConfigPath,
|
|
12
|
+
getGlobalDebugDir,
|
|
13
|
+
getGlobalDebugLogPath,
|
|
14
|
+
getGlobalDebugSessionsDir,
|
|
12
15
|
getLocalDataDir,
|
|
13
16
|
joinPath,
|
|
14
17
|
} from './paths.ts';
|
|
@@ -20,6 +23,14 @@ import {
|
|
|
20
23
|
|
|
21
24
|
export type Scope = 'global' | 'local';
|
|
22
25
|
|
|
26
|
+
export type DebugConfig = {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
scopes: string[];
|
|
29
|
+
logPath: string;
|
|
30
|
+
sessionsDir: string;
|
|
31
|
+
debugDir: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
23
34
|
export async function read(projectRoot?: string) {
|
|
24
35
|
const cfg = await loadConfig(projectRoot);
|
|
25
36
|
const auth = await getAllAuth(projectRoot);
|
|
@@ -111,6 +122,50 @@ export async function writeDefaults(
|
|
|
111
122
|
await Bun.write(globalPath, JSON.stringify(next, null, 2));
|
|
112
123
|
}
|
|
113
124
|
|
|
125
|
+
export async function readDebugConfig(
|
|
126
|
+
projectRoot?: string,
|
|
127
|
+
): Promise<DebugConfig> {
|
|
128
|
+
const cfg = await loadConfig(projectRoot);
|
|
129
|
+
return {
|
|
130
|
+
enabled: cfg.debugEnabled === true,
|
|
131
|
+
scopes: Array.isArray(cfg.debugScopes)
|
|
132
|
+
? cfg.debugScopes.filter(
|
|
133
|
+
(scope): scope is string =>
|
|
134
|
+
typeof scope === 'string' && scope.trim().length > 0,
|
|
135
|
+
)
|
|
136
|
+
: [],
|
|
137
|
+
logPath: getGlobalDebugLogPath(),
|
|
138
|
+
sessionsDir: getGlobalDebugSessionsDir(),
|
|
139
|
+
debugDir: getGlobalDebugDir(),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function writeDebugConfig(
|
|
144
|
+
updates: Partial<{
|
|
145
|
+
enabled: boolean;
|
|
146
|
+
scopes: string[];
|
|
147
|
+
}>,
|
|
148
|
+
) {
|
|
149
|
+
const globalPath = getGlobalConfigPath();
|
|
150
|
+
const existing = await readJsonFile(globalPath);
|
|
151
|
+
const next: Record<string, unknown> = { ...(existing ?? {}) };
|
|
152
|
+
|
|
153
|
+
if (updates.enabled !== undefined) {
|
|
154
|
+
next.debugEnabled = updates.enabled;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (updates.scopes !== undefined) {
|
|
158
|
+
next.debugScopes = updates.scopes;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const base = getGlobalConfigDir();
|
|
162
|
+
try {
|
|
163
|
+
const { promises: fs } = await import('node:fs');
|
|
164
|
+
await fs.mkdir(base, { recursive: true }).catch(() => {});
|
|
165
|
+
} catch {}
|
|
166
|
+
await Bun.write(globalPath, JSON.stringify(next, null, 2));
|
|
167
|
+
}
|
|
168
|
+
|
|
114
169
|
async function readJsonFile(
|
|
115
170
|
filePath: string,
|
|
116
171
|
): Promise<Record<string, unknown> | undefined> {
|
package/src/config/src/paths.ts
CHANGED
|
@@ -79,6 +79,33 @@ export function getGlobalCommandsDir(): string {
|
|
|
79
79
|
return joinPath(getGlobalConfigDir(), 'commands');
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
export function getGlobalDebugDir(): string {
|
|
83
|
+
return joinPath(getGlobalConfigDir(), 'debug');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getGlobalDebugLogPath(): string {
|
|
87
|
+
return joinPath(getGlobalDebugDir(), 'latest.log');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getGlobalDebugSessionsDir(): string {
|
|
91
|
+
return joinPath(getGlobalDebugDir(), 'sessions');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function getSessionDebugLogPath(sessionId: string): string {
|
|
95
|
+
return joinPath(getGlobalDebugSessionsDir(), `${sessionId}.log`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function getSessionDebugDetailsLogPath(sessionId: string): string {
|
|
99
|
+
return joinPath(getGlobalDebugSessionsDir(), `${sessionId}.details.log`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getSessionSystemPromptPath(sessionId: string): string {
|
|
103
|
+
return joinPath(
|
|
104
|
+
getGlobalDebugSessionsDir(),
|
|
105
|
+
`${sessionId}.system-prompt.txt`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
82
109
|
export function getLocalDataDir(projectRoot: string): string {
|
|
83
110
|
return joinPath(projectRoot, '.otto');
|
|
84
111
|
}
|
|
@@ -1,7 +1,34 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { getGlobalConfigPath } from '../../../config/src/paths.ts';
|
|
3
|
+
|
|
4
|
+
type DebugSettings = {
|
|
5
|
+
debugEnabled?: boolean;
|
|
6
|
+
debugScopes?: unknown;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function readDebugSettings(): DebugSettings {
|
|
10
|
+
try {
|
|
11
|
+
const raw = readFileSync(getGlobalConfigPath(), 'utf-8');
|
|
12
|
+
const parsed = JSON.parse(raw) as DebugSettings;
|
|
13
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
14
|
+
} catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
1
19
|
export function isDebugEnabled(): boolean {
|
|
2
|
-
return
|
|
20
|
+
return readDebugSettings().debugEnabled === true;
|
|
3
21
|
}
|
|
4
22
|
|
|
5
23
|
export function isTraceEnabled(): boolean {
|
|
6
24
|
return false;
|
|
7
25
|
}
|
|
26
|
+
|
|
27
|
+
export function getDebugScopes(): string[] {
|
|
28
|
+
const scopes = readDebugSettings().debugScopes;
|
|
29
|
+
if (!Array.isArray(scopes)) return [];
|
|
30
|
+
return scopes.filter(
|
|
31
|
+
(scope): scope is string =>
|
|
32
|
+
typeof scope === 'string' && scope.trim().length > 0,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
|
-
import { isDebugEnabled, isTraceEnabled } from './debug.ts';
|
|
3
|
+
import { isDebugEnabled, isTraceEnabled, getDebugScopes } from './debug.ts';
|
|
4
|
+
import {
|
|
5
|
+
getGlobalDebugLogPath,
|
|
6
|
+
getSessionDebugDetailsLogPath,
|
|
7
|
+
getSessionDebugLogPath,
|
|
8
|
+
} from '../../../config/src/paths.ts';
|
|
4
9
|
|
|
5
10
|
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
6
11
|
|
|
12
|
+
const ANSI_RESET = '\x1b[0m';
|
|
13
|
+
const ANSI_DIM = '\x1b[2m';
|
|
14
|
+
const ANSI_CYAN = '\x1b[36m';
|
|
15
|
+
const ANSI_BLUE = '\x1b[34m';
|
|
16
|
+
const ANSI_GREEN = '\x1b[32m';
|
|
17
|
+
const ANSI_YELLOW = '\x1b[33m';
|
|
18
|
+
const ANSI_RED = '\x1b[31m';
|
|
19
|
+
|
|
7
20
|
function safeHasMeta(
|
|
8
21
|
meta?: Record<string, unknown>,
|
|
9
22
|
): meta is Record<string, unknown> {
|
|
@@ -11,18 +24,90 @@ function safeHasMeta(
|
|
|
11
24
|
}
|
|
12
25
|
|
|
13
26
|
function getDebugLogFilePath(): string | undefined {
|
|
14
|
-
return undefined;
|
|
27
|
+
if (!isDebugEnabled()) return undefined;
|
|
28
|
+
return getGlobalDebugLogPath();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getSessionLogFilePath(
|
|
32
|
+
meta?: Record<string, unknown>,
|
|
33
|
+
): string | undefined {
|
|
34
|
+
if (!isDebugEnabled()) return undefined;
|
|
35
|
+
if (meta?.debugDetail === true) return undefined;
|
|
36
|
+
const sessionId = meta?.sessionId;
|
|
37
|
+
if (typeof sessionId !== 'string' || !sessionId.trim()) return undefined;
|
|
38
|
+
return getSessionDebugLogPath(sessionId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getSessionDetailsLogFilePath(
|
|
42
|
+
meta?: Record<string, unknown>,
|
|
43
|
+
): string | undefined {
|
|
44
|
+
if (!isDebugEnabled()) return undefined;
|
|
45
|
+
const sessionId = meta?.sessionId;
|
|
46
|
+
if (typeof sessionId !== 'string' || !sessionId.trim()) return undefined;
|
|
47
|
+
return getSessionDebugDetailsLogPath(sessionId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function shouldWriteDebugLog(message: string): boolean {
|
|
51
|
+
if (!isDebugEnabled()) return false;
|
|
52
|
+
const scopes = getDebugScopes();
|
|
53
|
+
if (!scopes.length) return true;
|
|
54
|
+
const match = message.match(/^\[([^\]]+)\]/);
|
|
55
|
+
if (!match?.[1]) return true;
|
|
56
|
+
return scopes.includes(match[1]);
|
|
15
57
|
}
|
|
16
58
|
|
|
17
59
|
function serializeLogMeta(meta?: Record<string, unknown>): string {
|
|
18
60
|
if (!safeHasMeta(meta)) return '';
|
|
19
61
|
try {
|
|
20
|
-
|
|
62
|
+
const sanitized = { ...meta };
|
|
63
|
+
delete sanitized.debugDetail;
|
|
64
|
+
return Object.keys(sanitized).length ? ` ${JSON.stringify(sanitized)}` : '';
|
|
21
65
|
} catch {
|
|
22
66
|
return ' [unserializable-meta]';
|
|
23
67
|
}
|
|
24
68
|
}
|
|
25
69
|
|
|
70
|
+
function colorizeLine(line: string, level: LogLevel): string {
|
|
71
|
+
const levelColor =
|
|
72
|
+
level === 'debug'
|
|
73
|
+
? ANSI_CYAN
|
|
74
|
+
: level === 'info'
|
|
75
|
+
? ANSI_BLUE
|
|
76
|
+
: level === 'warn'
|
|
77
|
+
? ANSI_YELLOW
|
|
78
|
+
: ANSI_RED;
|
|
79
|
+
const scopeMatch = line.match(
|
|
80
|
+
/\[(debug|info|warn|error|timing)\]\s+\[([^\]]+)\]/i,
|
|
81
|
+
);
|
|
82
|
+
if (!scopeMatch) {
|
|
83
|
+
return `${levelColor}${line}${ANSI_RESET}`;
|
|
84
|
+
}
|
|
85
|
+
const rest = line.slice(24);
|
|
86
|
+
return `${ANSI_DIM}${line.slice(0, 24)}${ANSI_RESET}${rest
|
|
87
|
+
.replace(scopeMatch[1], `${levelColor}${scopeMatch[1]}${ANSI_RESET}`)
|
|
88
|
+
.replace(
|
|
89
|
+
`[${scopeMatch[2]}]`,
|
|
90
|
+
`${ANSI_GREEN}[${scopeMatch[2]}]${ANSI_RESET}`,
|
|
91
|
+
)}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function printLine(
|
|
95
|
+
level: LogLevel,
|
|
96
|
+
line: string,
|
|
97
|
+
meta?: Record<string, unknown>,
|
|
98
|
+
) {
|
|
99
|
+
const colored = colorizeLine(line, level);
|
|
100
|
+
if (safeHasMeta(meta)) {
|
|
101
|
+
if (level === 'warn') console.warn(colored, meta);
|
|
102
|
+
else if (level === 'error') console.error(colored, meta);
|
|
103
|
+
else console.log(colored, meta);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (level === 'warn') console.warn(colored);
|
|
107
|
+
else if (level === 'error') console.error(colored);
|
|
108
|
+
else console.log(colored);
|
|
109
|
+
}
|
|
110
|
+
|
|
26
111
|
function writeLogLine(line: string, meta?: Record<string, unknown>) {
|
|
27
112
|
const suffix = serializeLogMeta(meta);
|
|
28
113
|
const fullLine = `${new Date().toISOString()} ${line}${suffix}`;
|
|
@@ -37,32 +122,44 @@ function writeLogLine(line: string, meta?: Record<string, unknown>) {
|
|
|
37
122
|
}
|
|
38
123
|
}
|
|
39
124
|
|
|
125
|
+
const sessionLogFile = getSessionLogFilePath(meta);
|
|
126
|
+
if (sessionLogFile) {
|
|
127
|
+
try {
|
|
128
|
+
mkdirSync(dirname(sessionLogFile), { recursive: true });
|
|
129
|
+
appendFileSync(sessionLogFile, `${fullLine}\n`, 'utf-8');
|
|
130
|
+
} catch {
|
|
131
|
+
// ignore file logging errors
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const sessionDetailsLogFile = getSessionDetailsLogFilePath(meta);
|
|
136
|
+
if (sessionDetailsLogFile) {
|
|
137
|
+
try {
|
|
138
|
+
mkdirSync(dirname(sessionDetailsLogFile), { recursive: true });
|
|
139
|
+
appendFileSync(sessionDetailsLogFile, `${fullLine}\n`, 'utf-8');
|
|
140
|
+
} catch {
|
|
141
|
+
// ignore file logging errors
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
40
145
|
return fullLine;
|
|
41
146
|
}
|
|
42
147
|
|
|
43
148
|
export function debug(message: string, meta?: Record<string, unknown>): void {
|
|
44
|
-
if (!
|
|
149
|
+
if (!shouldWriteDebugLog(message)) return;
|
|
45
150
|
try {
|
|
46
151
|
const line = writeLogLine(`[debug] ${message}`, meta);
|
|
47
|
-
|
|
48
|
-
console.log(line, meta);
|
|
49
|
-
} else {
|
|
50
|
-
console.log(line);
|
|
51
|
-
}
|
|
152
|
+
printLine('debug', line, meta);
|
|
52
153
|
} catch {
|
|
53
154
|
// ignore logging errors
|
|
54
155
|
}
|
|
55
156
|
}
|
|
56
157
|
|
|
57
158
|
export function info(message: string, meta?: Record<string, unknown>): void {
|
|
58
|
-
if (!
|
|
159
|
+
if (!shouldWriteDebugLog(message) && !isTraceEnabled()) return;
|
|
59
160
|
try {
|
|
60
161
|
const line = writeLogLine(`[info] ${message}`, meta);
|
|
61
|
-
|
|
62
|
-
console.log(line, meta);
|
|
63
|
-
} else {
|
|
64
|
-
console.log(line);
|
|
65
|
-
}
|
|
162
|
+
printLine('info', line, meta);
|
|
66
163
|
} catch {
|
|
67
164
|
// ignore logging errors
|
|
68
165
|
}
|
|
@@ -71,11 +168,7 @@ export function info(message: string, meta?: Record<string, unknown>): void {
|
|
|
71
168
|
export function warn(message: string, meta?: Record<string, unknown>): void {
|
|
72
169
|
try {
|
|
73
170
|
const line = writeLogLine(`[warn] ${message}`, meta);
|
|
74
|
-
|
|
75
|
-
console.warn(line, meta);
|
|
76
|
-
} else {
|
|
77
|
-
console.warn(line);
|
|
78
|
-
}
|
|
171
|
+
printLine('warn', line, meta);
|
|
79
172
|
} catch {
|
|
80
173
|
// ignore logging errors
|
|
81
174
|
}
|
|
@@ -127,10 +220,10 @@ export function error(
|
|
|
127
220
|
|
|
128
221
|
if (safeHasMeta(logMeta)) {
|
|
129
222
|
const line = writeLogLine(`[error] ${message}`, logMeta);
|
|
130
|
-
|
|
223
|
+
printLine('error', line, logMeta);
|
|
131
224
|
} else {
|
|
132
225
|
const line = writeLogLine(`[error] ${message}`);
|
|
133
|
-
|
|
226
|
+
printLine('error', line);
|
|
134
227
|
}
|
|
135
228
|
} catch (logErr) {
|
|
136
229
|
try {
|
|
@@ -177,11 +270,7 @@ export function time(label: string): Timer {
|
|
|
177
270
|
`[timing] ${label} ${duration.toFixed(1)}ms`,
|
|
178
271
|
meta,
|
|
179
272
|
);
|
|
180
|
-
|
|
181
|
-
console.log(base, meta);
|
|
182
|
-
} else {
|
|
183
|
-
console.log(base);
|
|
184
|
-
}
|
|
273
|
+
printLine('info', base, meta);
|
|
185
274
|
} catch {
|
|
186
275
|
// ignore timing log errors
|
|
187
276
|
}
|
package/src/index.ts
CHANGED
|
@@ -177,6 +177,12 @@ export {
|
|
|
177
177
|
getGlobalAgentsDir,
|
|
178
178
|
getGlobalToolsDir,
|
|
179
179
|
getGlobalCommandsDir,
|
|
180
|
+
getGlobalDebugDir,
|
|
181
|
+
getGlobalDebugLogPath,
|
|
182
|
+
getGlobalDebugSessionsDir,
|
|
183
|
+
getSessionDebugLogPath,
|
|
184
|
+
getSessionDebugDetailsLogPath,
|
|
185
|
+
getSessionSystemPromptPath,
|
|
180
186
|
getSecureAuthPath,
|
|
181
187
|
getSecureBaseDir,
|
|
182
188
|
getSecureOAuthDir,
|
|
@@ -187,12 +193,14 @@ export {
|
|
|
187
193
|
isAuthorized,
|
|
188
194
|
ensureEnv,
|
|
189
195
|
writeDefaults as setConfig,
|
|
196
|
+
readDebugConfig,
|
|
197
|
+
writeDebugConfig,
|
|
190
198
|
writeAuth,
|
|
191
199
|
removeAuth as removeConfig,
|
|
192
200
|
getOnboardingComplete,
|
|
193
201
|
setOnboardingComplete,
|
|
194
202
|
} from './config/src/manager.ts';
|
|
195
|
-
export type { Scope } from './config/src/manager.ts';
|
|
203
|
+
export type { Scope, DebugConfig } from './config/src/manager.ts';
|
|
196
204
|
|
|
197
205
|
// =======================
|
|
198
206
|
// Prompts (from internal prompts module)
|
package/src/prompts/src/debug.ts
CHANGED
|
@@ -3,13 +3,6 @@ export function isDebugEnabled(flag?: string): boolean {
|
|
|
3
3
|
return false;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
function nowMs(): number {
|
|
7
|
-
const perf = (globalThis as { performance?: { now?: () => number } })
|
|
8
|
-
.performance;
|
|
9
|
-
if (perf && typeof perf.now === 'function') return perf.now();
|
|
10
|
-
return Date.now();
|
|
11
|
-
}
|
|
12
|
-
|
|
13
6
|
type Timer = { end(meta?: Record<string, unknown>): void };
|
|
14
7
|
|
|
15
8
|
export function time(label: string): Timer {
|