codebot-ai 1.6.0 → 1.8.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.
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ /**
3
+ * Capability-Based Tool Permissions for CodeBot v1.8.0
4
+ *
5
+ * Fine-grained, per-tool resource restrictions.
6
+ * Configured via .codebot/policy.json → tools.capabilities.
7
+ *
8
+ * Example:
9
+ * {
10
+ * "execute": {
11
+ * "shell_commands": ["npm", "node", "git", "tsc"],
12
+ * "max_output_kb": 500
13
+ * },
14
+ * "write_file": {
15
+ * "fs_write": ["./src/**", "./tests/**"]
16
+ * }
17
+ * }
18
+ */
19
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ var desc = Object.getOwnPropertyDescriptor(m, k);
22
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
23
+ desc = { enumerable: true, get: function() { return m[k]; } };
24
+ }
25
+ Object.defineProperty(o, k2, desc);
26
+ }) : (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ o[k2] = m[k];
29
+ }));
30
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
31
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32
+ }) : function(o, v) {
33
+ o["default"] = v;
34
+ });
35
+ var __importStar = (this && this.__importStar) || (function () {
36
+ var ownKeys = function(o) {
37
+ ownKeys = Object.getOwnPropertyNames || function (o) {
38
+ var ar = [];
39
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
40
+ return ar;
41
+ };
42
+ return ownKeys(o);
43
+ };
44
+ return function (mod) {
45
+ if (mod && mod.__esModule) return mod;
46
+ var result = {};
47
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
48
+ __setModuleDefault(result, mod);
49
+ return result;
50
+ };
51
+ })();
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.CapabilityChecker = void 0;
54
+ const path = __importStar(require("path"));
55
+ // ── Capability Checker ──
56
+ class CapabilityChecker {
57
+ capabilities;
58
+ projectRoot;
59
+ constructor(capabilities, projectRoot) {
60
+ this.capabilities = capabilities;
61
+ this.projectRoot = projectRoot;
62
+ }
63
+ /** Get capabilities for a tool. undefined = no restrictions. */
64
+ getToolCapabilities(toolName) {
65
+ return this.capabilities[toolName];
66
+ }
67
+ /** Check if a specific capability is allowed. */
68
+ checkCapability(toolName, capabilityType, value) {
69
+ const caps = this.capabilities[toolName];
70
+ if (!caps)
71
+ return { allowed: true }; // No caps defined = unrestricted
72
+ switch (capabilityType) {
73
+ case 'fs_read':
74
+ return this.checkGlobs(caps.fs_read, value, 'fs_read', toolName);
75
+ case 'fs_write':
76
+ return this.checkGlobs(caps.fs_write, value, 'fs_write', toolName);
77
+ case 'net_access':
78
+ return this.checkDomain(caps.net_access, value, toolName);
79
+ case 'shell_commands':
80
+ return this.checkCommandPrefix(caps.shell_commands, value, toolName);
81
+ case 'max_output_kb':
82
+ return this.checkOutputSize(caps.max_output_kb, value, toolName);
83
+ default:
84
+ return { allowed: true };
85
+ }
86
+ }
87
+ /** Check if a path matches any of the allowed glob patterns. */
88
+ checkGlobs(patterns, filePath, capName, toolName) {
89
+ if (!patterns || patterns.length === 0)
90
+ return { allowed: true };
91
+ const resolved = path.resolve(filePath);
92
+ const relative = path.relative(this.projectRoot, resolved);
93
+ // Don't restrict paths outside the project (those are handled by security.ts)
94
+ if (relative.startsWith('..'))
95
+ return { allowed: true };
96
+ for (const pattern of patterns) {
97
+ if (this.matchGlob(relative, pattern)) {
98
+ return { allowed: true };
99
+ }
100
+ }
101
+ return {
102
+ allowed: false,
103
+ reason: `Tool "${toolName}" ${capName} capability blocks "${relative}" (allowed: ${patterns.join(', ')})`,
104
+ };
105
+ }
106
+ /** Check if a domain is in the allowed list. */
107
+ checkDomain(allowed, domain, toolName) {
108
+ if (allowed === undefined)
109
+ return { allowed: true }; // undefined = unrestricted
110
+ if (allowed.length === 0) {
111
+ // Empty array = no network access allowed
112
+ return {
113
+ allowed: false,
114
+ reason: `Tool "${toolName}" has no allowed network domains`,
115
+ };
116
+ }
117
+ if (allowed.includes('*'))
118
+ return { allowed: true };
119
+ const normalizedDomain = domain.toLowerCase();
120
+ for (const d of allowed) {
121
+ const nd = d.toLowerCase();
122
+ if (normalizedDomain === nd)
123
+ return { allowed: true };
124
+ if (normalizedDomain.endsWith('.' + nd))
125
+ return { allowed: true };
126
+ }
127
+ return {
128
+ allowed: false,
129
+ reason: `Tool "${toolName}" cannot access domain "${domain}" (allowed: ${allowed.join(', ')})`,
130
+ };
131
+ }
132
+ /** Check if a command starts with an allowed prefix. */
133
+ checkCommandPrefix(allowed, command, toolName) {
134
+ if (!allowed || allowed.length === 0)
135
+ return { allowed: true };
136
+ const cmd = command.trim();
137
+ for (const prefix of allowed) {
138
+ if (cmd === prefix || cmd.startsWith(prefix + ' ')) {
139
+ return { allowed: true };
140
+ }
141
+ }
142
+ // Extract first word for the error message
143
+ const firstWord = cmd.split(/\s+/)[0] || cmd.substring(0, 30);
144
+ return {
145
+ allowed: false,
146
+ reason: `Tool "${toolName}" cannot run "${firstWord}" (allowed commands: ${allowed.join(', ')})`,
147
+ };
148
+ }
149
+ /** Check if output size is within the cap. */
150
+ checkOutputSize(maxKb, actualKb, toolName) {
151
+ if (maxKb === undefined || maxKb <= 0)
152
+ return { allowed: true };
153
+ if (actualKb <= maxKb)
154
+ return { allowed: true };
155
+ return {
156
+ allowed: false,
157
+ reason: `Tool "${toolName}" output ${actualKb}KB exceeds cap of ${maxKb}KB`,
158
+ };
159
+ }
160
+ /** Simple glob matching (** = any depth, * = one segment). */
161
+ matchGlob(relativePath, pattern) {
162
+ const cleanPattern = pattern.replace(/^\.\//, '');
163
+ const cleanPath = relativePath.replace(/^\.\//, '');
164
+ // Exact match
165
+ if (cleanPath === cleanPattern)
166
+ return true;
167
+ // Prefix match (directory)
168
+ if (cleanPath.startsWith(cleanPattern + '/'))
169
+ return true;
170
+ if (cleanPath.startsWith(cleanPattern + path.sep))
171
+ return true;
172
+ // Glob expansion
173
+ if (pattern.includes('*')) {
174
+ const regex = new RegExp('^' +
175
+ cleanPattern
176
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
177
+ .replace(/\*\*/g, '<<GLOBSTAR>>')
178
+ .replace(/\*/g, '[^/]*')
179
+ .replace(/<<GLOBSTAR>>/g, '.*') +
180
+ '$');
181
+ return regex.test(cleanPath);
182
+ }
183
+ return false;
184
+ }
185
+ }
186
+ exports.CapabilityChecker = CapabilityChecker;
187
+ //# sourceMappingURL=capabilities.js.map
package/dist/cli.js CHANGED
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.main = main;
37
37
  const readline = __importStar(require("readline"));
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
38
40
  const agent_1 = require("./agent");
39
41
  const openai_1 = require("./providers/openai");
40
42
  const anthropic_1 = require("./providers/anthropic");
@@ -44,9 +46,11 @@ const setup_1 = require("./setup");
44
46
  const banner_1 = require("./banner");
45
47
  const tools_1 = require("./tools");
46
48
  const scheduler_1 = require("./scheduler");
47
- const VERSION = '1.6.0';
48
- // Session-wide token tracking
49
- let sessionTokens = { input: 0, output: 0, total: 0 };
49
+ const audit_1 = require("./audit");
50
+ const policy_1 = require("./policy");
51
+ const sandbox_1 = require("./sandbox");
52
+ const replay_1 = require("./replay");
53
+ const VERSION = '1.8.0';
50
54
  const C = {
51
55
  reset: '\x1b[0m',
52
56
  bold: '\x1b[1m',
@@ -86,17 +90,158 @@ async function main() {
86
90
  await (0, setup_1.runSetup)();
87
91
  return;
88
92
  }
93
+ // ── v1.7.0: New standalone commands ──
94
+ // --init-policy: Generate default policy file
95
+ if (args['init-policy']) {
96
+ const policyPath = path.join(process.cwd(), '.codebot', 'policy.json');
97
+ const policyDir = path.dirname(policyPath);
98
+ if (!fs.existsSync(policyDir))
99
+ fs.mkdirSync(policyDir, { recursive: true });
100
+ if (fs.existsSync(policyPath)) {
101
+ console.log(c(`Policy file already exists at ${policyPath}`, 'yellow'));
102
+ console.log(c('Delete it first if you want to regenerate.', 'dim'));
103
+ }
104
+ else {
105
+ fs.writeFileSync(policyPath, (0, policy_1.generateDefaultPolicyFile)(), 'utf-8');
106
+ console.log(c(`Created default policy at ${policyPath}`, 'green'));
107
+ }
108
+ return;
109
+ }
110
+ // --verify-audit: Verify audit chain integrity
111
+ if (args['verify-audit']) {
112
+ const logger = new audit_1.AuditLogger();
113
+ const sessionId = typeof args['verify-audit'] === 'string' ? args['verify-audit'] : undefined;
114
+ if (sessionId) {
115
+ const entries = logger.query({ sessionId });
116
+ if (entries.length === 0) {
117
+ console.log(c(`No audit entries found for session ${sessionId}`, 'yellow'));
118
+ return;
119
+ }
120
+ const result = audit_1.AuditLogger.verify(entries);
121
+ if (result.valid) {
122
+ console.log(c(`Audit chain valid (${result.entriesChecked} entries checked)`, 'green'));
123
+ }
124
+ else {
125
+ console.log(c(`Audit chain INVALID at sequence ${result.firstInvalidAt}`, 'red'));
126
+ console.log(c(`Reason: ${result.reason}`, 'red'));
127
+ }
128
+ }
129
+ else {
130
+ // Verify all entries from today's log
131
+ const entries = logger.query();
132
+ if (entries.length === 0) {
133
+ console.log(c('No audit entries found.', 'yellow'));
134
+ return;
135
+ }
136
+ // Group by session and verify each
137
+ const sessions = new Map();
138
+ for (const e of entries) {
139
+ if (!sessions.has(e.sessionId))
140
+ sessions.set(e.sessionId, []);
141
+ sessions.get(e.sessionId).push(e);
142
+ }
143
+ let allValid = true;
144
+ for (const [sid, sessionEntries] of sessions) {
145
+ const result = audit_1.AuditLogger.verify(sessionEntries);
146
+ const shortId = sid.substring(0, 12);
147
+ if (result.valid) {
148
+ console.log(c(` ${shortId} ${result.entriesChecked} entries valid`, 'green'));
149
+ }
150
+ else {
151
+ console.log(c(` ${shortId} INVALID at seq ${result.firstInvalidAt}: ${result.reason}`, 'red'));
152
+ allValid = false;
153
+ }
154
+ }
155
+ console.log(allValid
156
+ ? c(`\nAll ${sessions.size} session chains verified.`, 'green')
157
+ : c(`\nSome chains are invalid — possible tampering detected.`, 'red'));
158
+ }
159
+ return;
160
+ }
161
+ // --sandbox-info: Show sandbox status
162
+ if (args['sandbox-info']) {
163
+ const info = (0, sandbox_1.getSandboxInfo)();
164
+ console.log(c('Sandbox Status:', 'bold'));
165
+ console.log(` Docker: ${info.available ? c('available', 'green') : c('not available', 'yellow')}`);
166
+ console.log(` Image: ${info.image}`);
167
+ console.log(` CPU: ${info.defaults.cpus} cores max`);
168
+ console.log(` Memory: ${info.defaults.memoryMb}MB max`);
169
+ console.log(` Network: ${info.defaults.network ? 'enabled' : 'disabled'} by default`);
170
+ return;
171
+ }
172
+ // --replay: Replay a saved session
173
+ if (args.replay) {
174
+ const replayId = typeof args.replay === 'string'
175
+ ? args.replay
176
+ : history_1.SessionManager.latest();
177
+ if (!replayId) {
178
+ console.log(c('No session to replay. Specify an ID or ensure a previous session exists.', 'yellow'));
179
+ return;
180
+ }
181
+ const data = (0, replay_1.loadSessionForReplay)(replayId);
182
+ if (!data) {
183
+ console.log(c(`Session ${replayId} not found or empty.`, 'red'));
184
+ return;
185
+ }
186
+ console.log(c(`\nReplaying session ${replayId.substring(0, 12)}...`, 'cyan'));
187
+ console.log(c(` ${data.messages.length} messages (${data.userMessages.length} user, ${data.assistantMessages.length} assistant)`, 'dim'));
188
+ const replayProvider = new replay_1.ReplayProvider(data.assistantMessages);
189
+ const config = await resolveConfig(args);
190
+ const agent = new agent_1.Agent({
191
+ provider: replayProvider,
192
+ model: config.model,
193
+ providerName: 'replay',
194
+ autoApprove: true,
195
+ });
196
+ // Collect recorded tool results in order for sequential comparison
197
+ const recordedResults = Array.from(data.toolResults.values());
198
+ let resultIndex = 0;
199
+ let divergences = 0;
200
+ for (const userMsg of data.userMessages) {
201
+ console.log(c(`\n> ${truncate(userMsg.content, 100)}`, 'cyan'));
202
+ for await (const event of agent.run(userMsg.content)) {
203
+ if (event.type === 'tool_result' && event.toolResult && !event.toolResult.is_error) {
204
+ const recorded = recordedResults[resultIndex++];
205
+ if (recorded !== undefined) {
206
+ const diff = (0, replay_1.compareOutputs)(recorded, event.toolResult.result);
207
+ if (diff) {
208
+ divergences++;
209
+ console.log(c(` ⚠ Divergence in ${event.toolResult.name || 'tool'}:`, 'yellow'));
210
+ console.log(c(` ${diff.split('\n').join('\n ')}`, 'dim'));
211
+ }
212
+ else {
213
+ console.log(c(` ✓ ${event.toolResult.name || 'tool'} — output matches`, 'green'));
214
+ }
215
+ }
216
+ }
217
+ else if (event.type === 'text') {
218
+ process.stdout.write(c('.', 'dim'));
219
+ }
220
+ }
221
+ }
222
+ console.log(c(`\n\nReplay complete.`, 'bold'));
223
+ if (divergences === 0) {
224
+ console.log(c(' All tool outputs match — session is reproducible.', 'green'));
225
+ }
226
+ else {
227
+ console.log(c(` ${divergences} divergence(s) detected — environment may have changed.`, 'yellow'));
228
+ }
229
+ return;
230
+ }
89
231
  // First run: auto-launch setup if nothing is configured
90
232
  if ((0, setup_1.isFirstRun)() && process.stdin.isTTY && !args.message) {
91
233
  console.log(c('Welcome! No configuration found — launching setup...', 'cyan'));
92
234
  await (0, setup_1.runSetup)();
93
- // If setup saved a config, continue to main flow
94
- // Otherwise exit
95
235
  if ((0, setup_1.isFirstRun)())
96
236
  return;
97
237
  }
98
238
  const config = await resolveConfig(args);
99
239
  const provider = createProvider(config);
240
+ // Deterministic mode: set temperature=0
241
+ if (args.deterministic) {
242
+ provider.temperature = 0;
243
+ console.log(c(' Deterministic mode: temperature=0', 'dim'));
244
+ }
100
245
  // Session management
101
246
  let resumeId;
102
247
  if (args.continue) {
@@ -118,6 +263,7 @@ async function main() {
118
263
  const agent = new agent_1.Agent({
119
264
  provider,
120
265
  model: config.model,
266
+ providerName: config.provider,
121
267
  maxIterations: config.maxIterations,
122
268
  autoApprove: config.autoApprove,
123
269
  onMessage: (msg) => session.save(msg),
@@ -133,6 +279,7 @@ async function main() {
133
279
  // Non-interactive: single message from CLI args
134
280
  if (typeof args.message === 'string') {
135
281
  await runOnce(agent, args.message);
282
+ printSessionSummary(agent);
136
283
  return;
137
284
  }
138
285
  // Non-interactive: piped stdin
@@ -140,6 +287,7 @@ async function main() {
140
287
  const input = await readStdin();
141
288
  if (input.trim()) {
142
289
  await runOnce(agent, input.trim());
290
+ printSessionSummary(agent);
143
291
  }
144
292
  return;
145
293
  }
@@ -151,6 +299,22 @@ async function main() {
151
299
  // Cleanup scheduler on exit
152
300
  scheduler.stop();
153
301
  }
302
+ /** Print session summary with tokens, cost, tool calls, files modified */
303
+ function printSessionSummary(agent) {
304
+ const tracker = agent.getTokenTracker();
305
+ tracker.saveUsage();
306
+ const summary = tracker.getSummary();
307
+ const duration = (new Date(summary.endTime).getTime() - new Date(summary.startTime).getTime()) / 1000;
308
+ const mins = Math.floor(duration / 60);
309
+ const secs = Math.floor(duration % 60);
310
+ console.log(c('\n── Session Summary ──', 'dim'));
311
+ console.log(` Duration: ${mins}m ${secs}s`);
312
+ console.log(` Model: ${summary.model} via ${summary.provider}`);
313
+ console.log(` Tokens: ${summary.totalInputTokens.toLocaleString()} in / ${summary.totalOutputTokens.toLocaleString()} out (${tracker.formatCost()})`);
314
+ console.log(` Requests: ${summary.requestCount}`);
315
+ console.log(` Tools: ${summary.toolCalls} calls`);
316
+ console.log(` Files: ${summary.filesModified} modified`);
317
+ }
154
318
  function createProvider(config) {
155
319
  if (config.provider === 'anthropic') {
156
320
  return new anthropic_1.AnthropicProvider({
@@ -159,7 +323,6 @@ function createProvider(config) {
159
323
  model: config.model,
160
324
  });
161
325
  }
162
- // All other providers use OpenAI-compatible format
163
326
  return new openai_1.OpenAIProvider({
164
327
  baseUrl: config.baseUrl,
165
328
  apiKey: config.apiKey,
@@ -186,7 +349,7 @@ async function repl(agent, config, session) {
186
349
  }
187
350
  try {
188
351
  for await (const event of agent.run(input)) {
189
- renderEvent(event);
352
+ renderEvent(event, agent);
190
353
  }
191
354
  }
192
355
  catch (err) {
@@ -197,18 +360,19 @@ async function repl(agent, config, session) {
197
360
  rl.prompt();
198
361
  });
199
362
  rl.on('close', () => {
363
+ printSessionSummary(agent);
200
364
  console.log(c('\nBye!', 'dim'));
201
365
  process.exit(0);
202
366
  });
203
367
  }
204
368
  async function runOnce(agent, message) {
205
369
  for await (const event of agent.run(message)) {
206
- renderEvent(event);
370
+ renderEvent(event, agent);
207
371
  }
208
372
  console.log();
209
373
  }
210
374
  let isThinking = false;
211
- function renderEvent(event) {
375
+ function renderEvent(event, agent) {
212
376
  switch (event.type) {
213
377
  case 'thinking':
214
378
  if (!isThinking) {
@@ -248,20 +412,15 @@ function renderEvent(event) {
248
412
  }
249
413
  break;
250
414
  case 'usage':
251
- if (event.usage) {
252
- if (event.usage.inputTokens)
253
- sessionTokens.input += event.usage.inputTokens;
254
- if (event.usage.outputTokens)
255
- sessionTokens.output += event.usage.outputTokens;
256
- if (event.usage.totalTokens)
257
- sessionTokens.total += event.usage.totalTokens;
415
+ if (event.usage && agent) {
416
+ const tracker = agent.getTokenTracker();
258
417
  const parts = [];
259
418
  if (event.usage.inputTokens)
260
419
  parts.push(`in: ${event.usage.inputTokens}`);
261
420
  if (event.usage.outputTokens)
262
421
  parts.push(`out: ${event.usage.outputTokens}`);
263
422
  if (parts.length > 0) {
264
- console.log(c(` [${parts.join(', ')} tokens]`, 'dim'));
423
+ console.log(c(` [${parts.join(', ')} tokens | ${tracker.formatCost()}]`, 'dim'));
265
424
  }
266
425
  }
267
426
  break;
@@ -306,7 +465,10 @@ function handleSlashCommand(input, agent, config) {
306
465
  /auto Toggle autonomous mode
307
466
  /routines List scheduled routines
308
467
  /undo Undo last file edit (/undo [path])
309
- /usage Show token usage for this session
468
+ /usage Show token usage & cost for this session
469
+ /cost Show running cost
470
+ /policy Show current security policy
471
+ /audit Verify audit chain for this session
310
472
  /config Show current config
311
473
  /quit Exit`);
312
474
  break;
@@ -365,10 +527,41 @@ function handleSlashCommand(input, agent, config) {
365
527
  break;
366
528
  }
367
529
  case '/usage': {
368
- console.log(c('\nToken Usage (this session):', 'bold'));
369
- console.log(` Input: ${sessionTokens.input.toLocaleString()} tokens`);
370
- console.log(` Output: ${sessionTokens.output.toLocaleString()} tokens`);
371
- console.log(` Total: ${(sessionTokens.input + sessionTokens.output).toLocaleString()} tokens`);
530
+ const tracker = agent.getTokenTracker();
531
+ const summary = tracker.getSummary();
532
+ console.log(c('\nSession Usage:', 'bold'));
533
+ console.log(` Input: ${summary.totalInputTokens.toLocaleString()} tokens`);
534
+ console.log(` Output: ${summary.totalOutputTokens.toLocaleString()} tokens`);
535
+ console.log(` Cost: ${tracker.formatCost()}`);
536
+ console.log(` Requests: ${summary.requestCount}`);
537
+ console.log(` Tools: ${summary.toolCalls} calls`);
538
+ console.log(` Files: ${summary.filesModified} modified`);
539
+ break;
540
+ }
541
+ case '/cost': {
542
+ const tracker = agent.getTokenTracker();
543
+ console.log(c(` ${tracker.formatStatusLine()}`, 'dim'));
544
+ break;
545
+ }
546
+ case '/policy': {
547
+ const policy = agent.getPolicyEnforcer().getPolicy();
548
+ console.log(c('\nCurrent Policy:', 'bold'));
549
+ console.log(JSON.stringify(policy, null, 2));
550
+ break;
551
+ }
552
+ case '/audit': {
553
+ const auditLogger = agent.getAuditLogger();
554
+ const result = auditLogger.verifySession();
555
+ if (result.entriesChecked === 0) {
556
+ console.log(c('No audit entries yet.', 'dim'));
557
+ }
558
+ else if (result.valid) {
559
+ console.log(c(`Audit chain valid (${result.entriesChecked} entries)`, 'green'));
560
+ }
561
+ else {
562
+ console.log(c(`Audit chain INVALID at sequence ${result.firstInvalidAt}`, 'red'));
563
+ console.log(c(` ${result.reason}`, 'red'));
564
+ }
372
565
  break;
373
566
  }
374
567
  case '/routines': {
@@ -409,7 +602,6 @@ function showModels() {
409
602
  }
410
603
  }
411
604
  async function resolveConfig(args) {
412
- // Load saved config (CLI args override saved config)
413
605
  const saved = (0, setup_1.loadConfig)();
414
606
  const model = args.model || process.env.CODEBOT_MODEL || saved.model || 'qwen2.5-coder:32b';
415
607
  const detected = (0, registry_1.detectProvider)(model);
@@ -421,7 +613,6 @@ async function resolveConfig(args) {
421
613
  maxIterations: parseInt(args['max-iterations'] || String(saved.maxIterations || 50), 10),
422
614
  autoApprove: !!args['auto-approve'] || !!args.autonomous || !!args.auto || !!saved.autoApprove,
423
615
  };
424
- // Auto-resolve base URL and API key from provider
425
616
  if (!config.baseUrl || !config.apiKey) {
426
617
  const defaults = registry_1.PROVIDER_DEFAULTS[config.provider];
427
618
  if (defaults) {
@@ -431,14 +622,12 @@ async function resolveConfig(args) {
431
622
  config.apiKey = process.env[defaults.envKey] || process.env.CODEBOT_API_KEY || '';
432
623
  }
433
624
  }
434
- // Fallback: try saved config API key, then generic env vars
435
625
  if (!config.apiKey && saved.apiKey) {
436
626
  config.apiKey = saved.apiKey;
437
627
  }
438
628
  if (!config.apiKey) {
439
629
  config.apiKey = process.env.CODEBOT_API_KEY || process.env.OPENAI_API_KEY || '';
440
630
  }
441
- // If still no base URL, auto-detect local provider
442
631
  if (!config.baseUrl) {
443
632
  config.baseUrl = await autoDetectProvider();
444
633
  }
@@ -494,6 +683,40 @@ function parseArgs(argv) {
494
683
  result.setup = true;
495
684
  continue;
496
685
  }
686
+ if (arg === '--init-policy') {
687
+ result['init-policy'] = true;
688
+ continue;
689
+ }
690
+ if (arg === '--sandbox-info') {
691
+ result['sandbox-info'] = true;
692
+ continue;
693
+ }
694
+ if (arg === '--verify-audit') {
695
+ const next = argv[i + 1];
696
+ if (next && !next.startsWith('--')) {
697
+ result['verify-audit'] = next;
698
+ i++;
699
+ }
700
+ else {
701
+ result['verify-audit'] = true;
702
+ }
703
+ continue;
704
+ }
705
+ if (arg === '--replay') {
706
+ const next = argv[i + 1];
707
+ if (next && !next.startsWith('--')) {
708
+ result['replay'] = next;
709
+ i++;
710
+ }
711
+ else {
712
+ result['replay'] = true; // replay latest
713
+ }
714
+ continue;
715
+ }
716
+ if (arg === '--deterministic') {
717
+ result['deterministic'] = true;
718
+ continue;
719
+ }
497
720
  if (arg.startsWith('--')) {
498
721
  const key = arg.slice(2);
499
722
  const next = argv[i + 1];
@@ -540,9 +763,19 @@ ${c('Options:', 'bold')}
540
763
  --resume <id> Resume a previous session by ID
541
764
  --continue, -c Resume the most recent session
542
765
  --max-iterations <n> Max agent loop iterations (default: 50)
766
+ --sandbox <mode> Execution sandbox: docker, host, auto (default: auto)
543
767
  -h, --help Show this help
544
768
  -v, --version Show version
545
769
 
770
+ ${c('Security & Policy:', 'bold')}
771
+ --init-policy Generate default .codebot/policy.json
772
+ --verify-audit [id] Verify audit log hash chain integrity
773
+ --sandbox-info Show Docker sandbox status
774
+
775
+ ${c('Debugging & Replay:', 'bold')}
776
+ --replay [id] Replay a session, re-execute tools, compare outputs
777
+ --deterministic Set temperature=0 for reproducible outputs
778
+
546
779
  ${c('Supported Providers:', 'bold')}
547
780
  Local: Ollama, LM Studio, vLLM (auto-detected)
548
781
  Anthropic: Claude Opus/Sonnet/Haiku (ANTHROPIC_API_KEY)
@@ -560,6 +793,8 @@ ${c('Examples:', 'bold')}
560
793
  codebot --model deepseek-chat Uses DeepSeek API
561
794
  codebot --model qwen2.5-coder:32b Uses local Ollama
562
795
  codebot --autonomous "refactor src/" Full auto, no prompts
796
+ codebot --init-policy Create security policy
797
+ codebot --verify-audit Check audit integrity
563
798
 
564
799
  ${c('Interactive Commands:', 'bold')}
565
800
  /help Show commands
@@ -569,6 +804,10 @@ ${c('Interactive Commands:', 'bold')}
569
804
  /auto Toggle autonomous mode
570
805
  /clear Clear conversation
571
806
  /compact Force context compaction
807
+ /usage Show token usage & cost
808
+ /cost Show running cost
809
+ /policy Show security policy
810
+ /audit Verify session audit chain
572
811
  /config Show configuration
573
812
  /quit Exit`);
574
813
  }
package/dist/history.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Message } from './types';
2
+ import { IntegrityResult } from './integrity';
2
3
  export interface SessionMeta {
3
4
  id: string;
4
5
  model: string;
@@ -11,14 +12,17 @@ export declare class SessionManager {
11
12
  private sessionId;
12
13
  private filePath;
13
14
  private model;
15
+ private integrityKey;
14
16
  constructor(model: string, sessionId?: string);
15
17
  getId(): string;
16
- /** Append a message to the session file */
18
+ /** Append a message to the session file (HMAC signed) */
17
19
  save(message: Message): void;
18
- /** Save all messages (atomic overwrite via temp file + rename) */
20
+ /** Save all messages (atomic overwrite, HMAC signed) */
19
21
  saveAll(messages: Message[]): void;
20
- /** Load messages from a session file (skips malformed lines) */
22
+ /** Load messages from a session file (verifies HMAC, drops tampered) */
21
23
  load(): Message[];
24
+ /** Verify integrity of all messages in this session. */
25
+ verifyIntegrity(): IntegrityResult;
22
26
  /** List recent sessions */
23
27
  static list(limit?: number): SessionMeta[];
24
28
  /** Get the most recent session ID */