osuite 2.8.1 → 2.9.1

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/cli.js CHANGED
@@ -1,126 +1,417 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import pkg from './package.json' with { type: 'json' };
4
- import { OSuite } from './osuite.js';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { OSuite, buildReviewSurface } from './osuite.js';
5
7
 
6
- function printHelp() {
7
- process.stdout.write(`OSuite CLI v${pkg.version}
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
11
+
12
+ const ANSI = {
13
+ reset: '\x1b[0m',
14
+ bold: '\x1b[1m',
15
+ dim: '\x1b[2m',
16
+ red: '\x1b[31m',
17
+ green: '\x1b[32m',
18
+ yellow: '\x1b[33m',
19
+ blue: '\x1b[34m',
20
+ cyan: '\x1b[36m',
21
+ };
8
22
 
9
- Usage:
10
- osuite help
11
- osuite version
12
- osuite approvals [--limit 20] [--offset 0]
13
- osuite approve <actionId> [--reason "Approved by operator"]
14
- osuite deny <actionId> [--reason "Outside change window"]
23
+ const PROTECTED_REFS = new Set(['main', 'master', 'prod', 'production', 'stable']);
24
+ const OBSERVE_OPS = new Set(['get', 'describe', 'logs', 'top', 'version', 'diff']);
25
+ const PLAN_OPS = new Set(['plan', 'show', 'validate', 'output', 'fmt', 'providers', 'graph']);
26
+ const MUTATION_OPS = new Set(['apply', 'destroy', 'delete', 'create', 'replace', 'taint', 'untaint', 'import', 'upgrade', 'rollback', 'install', 'uninstall']);
15
27
 
16
- Environment:
17
- OSUITE_BASE_URL Required, your OSuite base URL
18
- OSUITE_API_KEY Required, admin API key for approval operations
19
- OSUITE_AGENT_ID Optional, defaults to "osuite-cli"
28
+ function useColor() {
29
+ return !process.env.NO_COLOR && process.stdout.isTTY !== false;
30
+ }
31
+
32
+ function c(value, color) {
33
+ return useColor() ? `${color}${value}${ANSI.reset}` : value;
34
+ }
20
35
 
21
- Legacy compatibility:
22
- DASHCLAW_BASE_URL, DASHCLAW_API_KEY, and DASHCLAW_AGENT_ID are still accepted.
23
- `);
36
+ function write(value = '') {
37
+ process.stdout.write(`${value}\n`);
24
38
  }
25
39
 
26
40
  function parseArgs(argv) {
27
41
  const [command = 'help', ...rest] = argv;
28
42
  const args = { _: [] };
29
-
30
- for (let i = 0; i < rest.length; i += 1) {
31
- const token = rest[i];
43
+ for (let index = 0; index < rest.length; index += 1) {
44
+ const token = rest[index];
32
45
  if (token.startsWith('--')) {
33
46
  const key = token.slice(2);
34
- const next = rest[i + 1];
35
- if (!next || next.startsWith('--')) {
36
- args[key] = true;
37
- } else {
47
+ const next = rest[index + 1];
48
+ if (!next || next.startsWith('--')) args[key] = true;
49
+ else {
38
50
  args[key] = next;
39
- i += 1;
51
+ index += 1;
40
52
  }
41
- continue;
53
+ } else {
54
+ args._.push(token);
42
55
  }
43
- args._.push(token);
44
56
  }
45
-
46
57
  return { command, args };
47
58
  }
48
59
 
49
- function getClient() {
50
- const baseUrl = process.env.OSUITE_BASE_URL || process.env.DASHCLAW_BASE_URL;
51
- const apiKey = process.env.OSUITE_API_KEY || process.env.DASHCLAW_API_KEY;
52
- const agentId = process.env.OSUITE_AGENT_ID || process.env.DASHCLAW_AGENT_ID || 'osuite-cli';
60
+ function envValue(name, fallback) {
61
+ return process.env[name] || process.env[fallback] || '';
62
+ }
63
+
64
+ function readConfig() {
65
+ return {
66
+ baseUrl: envValue('OSUITE_BASE_URL', 'DASHCLAW_BASE_URL'),
67
+ apiKey: envValue('OSUITE_API_KEY', 'DASHCLAW_API_KEY'),
68
+ agentId: envValue('OSUITE_AGENT_ID', 'DASHCLAW_AGENT_ID') || 'osuite-cli',
69
+ runtimeAdapterId: process.env.OSUITE_RUNTIME_ADAPTER_ID || '',
70
+ signatureMode: process.env.OSUITE_SIGNATURE_MODE || '',
71
+ };
72
+ }
53
73
 
54
- if (!baseUrl || !apiKey) {
55
- process.stderr.write('Missing OSUITE_BASE_URL or OSUITE_API_KEY.\n');
74
+ function requireClient() {
75
+ const config = readConfig();
76
+ if (!config.baseUrl || !config.apiKey) {
77
+ write(c('OSuite is not connected yet.', ANSI.yellow));
78
+ write('Set OSUITE_BASE_URL and OSUITE_API_KEY, or run:');
79
+ write(' osuite init');
56
80
  process.exit(1);
57
81
  }
82
+ return new OSuite(config);
83
+ }
58
84
 
59
- return new OSuite({ baseUrl, apiKey, agentId });
85
+ function banner() {
86
+ return [
87
+ c('OSUITE', `${ANSI.bold}${ANSI.cyan}`),
88
+ c('Governed action layer for AI agents', ANSI.bold),
89
+ c('PCAA authority | CAVA action understanding | Decision V2 review', ANSI.dim),
90
+ ].join('\n');
60
91
  }
61
92
 
62
- async function listApprovals(args) {
63
- const client = getClient();
64
- const limit = Number.parseInt(args.limit || '20', 10);
65
- const offset = Number.parseInt(args.offset || '0', 10);
66
- const result = await client.getPendingApprovals(limit, offset);
67
- const actions = result.actions || [];
93
+ function printHelp() {
94
+ write(banner());
95
+ write('');
96
+ write(c(`OSuite CLI v${pkg.version}`, ANSI.dim));
97
+ write('');
98
+ write(c('Useful commands', ANSI.bold));
99
+ write(' osuite doctor Check connection, runtime, signatures, package health');
100
+ write(' osuite status Ping Studio and show current workspace connection');
101
+ write(' osuite explain "git push origin main" Explain how CAVA/Decision V2 will read a command');
102
+ write(' osuite approvals --limit 20 Show pending approval inbox');
103
+ write(' osuite review <actionId> Render the decision review card');
104
+ write(' osuite approve <actionId> --reason "Looks safe"');
105
+ write(' osuite deny <actionId> --reason "Outside change window"');
106
+ write(' osuite init [codex|claude|mcp] Print setup steps for a runtime lane');
107
+ write('');
108
+ write(c('Environment', ANSI.bold));
109
+ write(' OSUITE_BASE_URL Studio URL, for example https://studio.osuite.ai');
110
+ write(' OSUITE_API_KEY Workspace or admin API key');
111
+ write(' OSUITE_AGENT_ID Optional actor label for CLI actions');
112
+ write('');
113
+ write(c('Old DASHCLAW_* environment names are still accepted for compatibility.', ANSI.dim));
114
+ }
68
115
 
69
- if (actions.length === 0) {
70
- process.stdout.write('No pending approvals.\n');
71
- return;
116
+ function tokenize(command) {
117
+ return String(command || '').match(/"[^"]*"|'[^']*'|\S+/g)?.map((token) => token.replace(/^['"]|['"]$/g, '')) || [];
118
+ }
119
+
120
+ function protectedGitTarget(tokens) {
121
+ if (tokens[0] !== 'git' || tokens[1] !== 'push') return null;
122
+ const refs = tokens.slice(2).filter((token) => !token.startsWith('-') && token !== 'origin' && !token.includes(':'));
123
+ const target = refs[0] || 'unknown';
124
+ const normalized = target.replace(/^refs\/heads\//, '');
125
+ if (PROTECTED_REFS.has(normalized) || normalized.startsWith('release/') || normalized.includes('prod')) return normalized;
126
+ return null;
127
+ }
128
+
129
+ function classifyCommand(command) {
130
+ const tokens = tokenize(command);
131
+ const executable = tokens[0] || '';
132
+ const operation = tokens[1] || '';
133
+
134
+ if (!tokens.length) {
135
+ return { category: 'unknown', operation: 'unknown', risk: 20, reversible: true, summary: 'No command provided.' };
72
136
  }
73
137
 
74
- for (const action of actions) {
75
- process.stdout.write(
76
- `${action.action_id}\t${action.agent_name || action.agent_id}\t${action.action_type}\t${action.declared_goal || '-'}\n`
77
- );
138
+ if (executable === 'git' && operation === 'push') {
139
+ const protectedTarget = protectedGitTarget(tokens);
140
+ const target = protectedTarget || tokens.filter((token) => !token.startsWith('-')).at(-1) || 'remote';
141
+ return {
142
+ category: 'deployment',
143
+ operation: 'push',
144
+ risk: protectedTarget ? 82 : 58,
145
+ reversible: false,
146
+ systems: ['git', 'remote_repository'],
147
+ summary: protectedTarget
148
+ ? `Git push to protected target ${protectedTarget}.`
149
+ : `Git push to ${target}.`,
150
+ notes: protectedTarget ? ['protected target', 'approval recommended before side effects'] : ['feature branch target'],
151
+ };
78
152
  }
79
- }
80
153
 
81
- async function decide(actionId, decision, args) {
82
- if (!actionId) {
83
- process.stderr.write('Missing actionId.\n');
84
- process.exit(1);
154
+ if (executable === 'npm' && operation === 'run' && tokens[2] === 'build') {
155
+ return {
156
+ category: 'build',
157
+ operation: 'build',
158
+ risk: 30,
159
+ reversible: true,
160
+ systems: ['local_workspace'],
161
+ summary: 'Local build or compilation step.',
162
+ notes: ['usually does not require approval unless chained with deployment'],
163
+ };
85
164
  }
86
165
 
87
- const client = getClient();
88
- const result = await client.approveAction(actionId, decision, args.reason || args.reasoning);
89
- process.stdout.write(`${decision === 'allow' ? 'Approved' : 'Denied'} ${actionId}\n`);
90
- if (result?.action?.status) {
91
- process.stdout.write(`New status: ${result.action.status}\n`);
166
+ if (['kubectl', 'helm', 'terraform', 'pulumi', 'ansible'].includes(executable)) {
167
+ const op = operation || 'unknown';
168
+ if (OBSERVE_OPS.has(op)) {
169
+ return {
170
+ category: 'observation',
171
+ operation: op,
172
+ risk: 28,
173
+ reversible: true,
174
+ systems: [executable],
175
+ summary: `${executable} observation command.`,
176
+ notes: ['read-oriented runtime action'],
177
+ };
178
+ }
179
+ if (PLAN_OPS.has(op)) {
180
+ return {
181
+ category: 'infrastructure_plan',
182
+ operation: op,
183
+ risk: 38,
184
+ reversible: true,
185
+ systems: [executable],
186
+ summary: `${executable} planning command.`,
187
+ notes: ['plan evidence can improve approval confidence'],
188
+ };
189
+ }
190
+ if (MUTATION_OPS.has(op)) {
191
+ return {
192
+ category: 'infrastructure_change',
193
+ operation: op,
194
+ risk: op === 'apply' || op === 'destroy' ? 90 : 78,
195
+ reversible: false,
196
+ systems: [executable],
197
+ summary: `${executable} infrastructure mutation.`,
198
+ notes: ['approval recommended', 'capture plan/output as evidence'],
199
+ };
200
+ }
201
+ }
202
+
203
+ if (executable === 'az' && tokens[1] === 'keyvault' && tokens[2] === 'secret') {
204
+ return {
205
+ category: 'secret_access',
206
+ operation: tokens[3] || 'secret',
207
+ risk: 74,
208
+ reversible: true,
209
+ systems: ['azure_key_vault'],
210
+ summary: 'Azure Key Vault secret access.',
211
+ notes: ['sensitive read', 'approval may be required by policy'],
212
+ };
213
+ }
214
+
215
+ if (['rm', 'drop', 'truncate'].some((prefix) => String(command).toLowerCase().includes(prefix))) {
216
+ return {
217
+ category: 'destructive_operation',
218
+ operation: executable,
219
+ risk: 92,
220
+ reversible: false,
221
+ systems: ['workspace'],
222
+ summary: 'Potentially destructive operation.',
223
+ notes: ['approval strongly recommended'],
224
+ };
92
225
  }
226
+
227
+ return {
228
+ category: 'shell_command',
229
+ operation: executable,
230
+ risk: 45,
231
+ reversible: null,
232
+ systems: ['shell'],
233
+ summary: 'General shell command. OSuite will use runtime context and policy to decide.',
234
+ notes: ['provide declared_goal and evidence for better scoring'],
235
+ };
93
236
  }
94
237
 
95
- async function main() {
96
- const { command, args } = parseArgs(process.argv.slice(2));
238
+ function riskBand(score) {
239
+ if (score >= 75) return 'High';
240
+ if (score >= 45) return 'Moderate';
241
+ return 'Low';
242
+ }
97
243
 
98
- if (command === 'help' || command === '--help' || command === '-h') {
99
- printHelp();
100
- return;
244
+ function printExplanation(command) {
245
+ const result = classifyCommand(command);
246
+ const band = riskBand(result.risk);
247
+ const bandColor = band === 'High' ? ANSI.red : band === 'Moderate' ? ANSI.yellow : ANSI.green;
248
+
249
+ write(c('OSuite CAVA explanation', `${ANSI.bold}${ANSI.cyan}`));
250
+ write(c('Local preview. No server call was made.', ANSI.dim));
251
+ write('');
252
+ write(`Command ${command}`);
253
+ write(`Category ${result.category}`);
254
+ write(`Operation ${result.operation}`);
255
+ write(`Risk ${c(`${band} (${result.risk}/100)`, bandColor)}`);
256
+ write(`Reversible ${result.reversible === null ? 'unknown' : String(result.reversible)}`);
257
+ write(`Systems touched ${(result.systems || []).join(', ') || 'unknown'}`);
258
+ write(`OSuite understood ${result.summary}`);
259
+ if (result.notes?.length) {
260
+ write('');
261
+ write(c('Decision hints', ANSI.bold));
262
+ result.notes.forEach((note) => write(` - ${note}`));
263
+ }
264
+ }
265
+
266
+ async function doctor() {
267
+ const config = readConfig();
268
+ let ok = true;
269
+
270
+ write(c('OSuite Doctor', `${ANSI.bold}${ANSI.cyan}`));
271
+ write(c('Checking the local developer entry point.', ANSI.dim));
272
+ write('');
273
+
274
+ const checks = [
275
+ ['OSUITE_BASE_URL', Boolean(config.baseUrl), config.baseUrl || 'missing'],
276
+ ['OSUITE_API_KEY', Boolean(config.apiKey), config.apiKey ? 'present' : 'missing'],
277
+ ['OSUITE_AGENT_ID', Boolean(config.agentId), config.agentId || 'osuite-cli'],
278
+ ['Runtime adapter', Boolean(config.runtimeAdapterId), config.runtimeAdapterId || 'not declared'],
279
+ ['Signature mode', Boolean(config.signatureMode), config.signatureMode || 'not declared'],
280
+ ];
281
+
282
+ checks.forEach(([label, passed, detail]) => {
283
+ if (!passed && (label === 'OSUITE_BASE_URL' || label === 'OSUITE_API_KEY')) ok = false;
284
+ write(`${passed ? c('OK ', ANSI.green) : c('MISS', ANSI.yellow)} ${label.padEnd(18)} ${detail}`);
285
+ });
286
+
287
+ if (config.baseUrl) {
288
+ try {
289
+ const response = await fetch(`${config.baseUrl.replace(/\/$/, '')}/api/health`);
290
+ const body = await response.text();
291
+ const healthy = response.ok && body.includes('healthy');
292
+ ok = ok && healthy;
293
+ write(`${healthy ? c('OK ', ANSI.green) : c('WARN', ANSI.yellow)} ${'Studio health'.padEnd(18)} HTTP ${response.status}`);
294
+ } catch (error) {
295
+ ok = false;
296
+ write(`${c('FAIL', ANSI.red)} ${'Studio health'.padEnd(18)} ${error.message}`);
297
+ }
101
298
  }
102
299
 
103
- if (command === 'version' || command === '--version' || command === '-v') {
104
- process.stdout.write(`${pkg.version}\n`);
105
- return;
300
+ write('');
301
+ if (!ok) {
302
+ write(c('Next step', ANSI.bold));
303
+ write(' osuite init');
304
+ write(' export OSUITE_BASE_URL=https://studio.osuite.ai');
305
+ write(' export OSUITE_API_KEY=<workspace-api-key>');
306
+ process.exit(1);
106
307
  }
107
308
 
108
- if (command === 'approvals') {
109
- await listApprovals(args);
309
+ write(c('Your OSuite CLI is ready.', ANSI.green));
310
+ }
311
+
312
+ async function status() {
313
+ const config = readConfig();
314
+ write(c('OSuite Status', `${ANSI.bold}${ANSI.cyan}`));
315
+ write(`Base URL ${config.baseUrl || 'not configured'}`);
316
+ write(`Agent ID ${config.agentId}`);
317
+ write(`Runtime adapter ${config.runtimeAdapterId || 'not declared'}`);
318
+ if (!config.baseUrl) return;
319
+
320
+ const response = await fetch(`${config.baseUrl.replace(/\/$/, '')}/api/health`);
321
+ write(`Studio health HTTP ${response.status}`);
322
+ }
323
+
324
+ function renderActionRow(action) {
325
+ const id = action.action_id || action.id || '-';
326
+ const score = action.risk_score ?? action.agent_risk_score ?? '-';
327
+ const type = action.action_type || '-';
328
+ const agent = action.agent_name || action.agent_id || '-';
329
+ const goal = action.declared_goal || action.input_summary || '-';
330
+ return `${id.padEnd(18)} ${String(score).padStart(3)} ${type.padEnd(24)} ${agent.padEnd(18)} ${goal}`;
331
+ }
332
+
333
+ async function approvals(args) {
334
+ const client = requireClient();
335
+ const limit = Number.parseInt(args.limit || '20', 10);
336
+ const offset = Number.parseInt(args.offset || '0', 10);
337
+ const payload = await client.getPendingApprovals(limit, offset);
338
+ const actions = payload.actions || [];
339
+
340
+ write(c('OSuite Approval Inbox', `${ANSI.bold}${ANSI.cyan}`));
341
+ write(c('Actions waiting for human authority.', ANSI.dim));
342
+ write('');
343
+
344
+ if (!actions.length) {
345
+ write('No pending approvals.');
110
346
  return;
111
347
  }
112
348
 
113
- if (command === 'approve') {
114
- await decide(args._[0], 'allow', args);
115
- return;
349
+ write(`${'Action ID'.padEnd(18)} ${'Risk'.padStart(3)} ${'Type'.padEnd(24)} ${'Agent'.padEnd(18)} Goal`);
350
+ actions.forEach((action) => write(renderActionRow(action)));
351
+ }
352
+
353
+ async function review(actionId) {
354
+ if (!actionId) {
355
+ write('Missing actionId. Usage: osuite review <actionId>');
356
+ process.exit(1);
116
357
  }
358
+ const client = requireClient();
359
+ const payload = await client.getAction(actionId);
360
+ const action = payload.action || payload;
361
+ write(buildReviewSurface(action, { baseUrl: client.baseUrl }));
362
+ }
117
363
 
118
- if (command === 'deny') {
119
- await decide(args._[0], 'deny', args);
120
- return;
364
+ async function decide(actionId, decision, args) {
365
+ if (!actionId) {
366
+ write(`Missing actionId. Usage: osuite ${decision === 'allow' ? 'approve' : 'deny'} <actionId>`);
367
+ process.exit(1);
121
368
  }
369
+ const client = requireClient();
370
+ const result = await client.approveAction(actionId, decision, args.reason || args.reasoning);
371
+ const label = decision === 'allow' ? 'Approved' : 'Denied';
372
+ write(c(`${label} ${actionId}`, decision === 'allow' ? ANSI.green : ANSI.red));
373
+ if (result?.action?.status) write(`New status ${result.action.status}`);
374
+ }
375
+
376
+ function init(target = 'generic') {
377
+ write(c('OSuite init', `${ANSI.bold}${ANSI.cyan}`));
378
+ write('Paste these into your shell, then run `osuite doctor`:');
379
+ write('');
380
+ write('export OSUITE_BASE_URL=https://studio.osuite.ai');
381
+ write('export OSUITE_API_KEY=<workspace-api-key>');
382
+ write('export OSUITE_AGENT_ID=<agent-or-runtime-id>');
383
+ write('');
384
+
385
+ if (target === 'codex') {
386
+ write(c('Codex runtime lane', ANSI.bold));
387
+ write(' osuite doctor');
388
+ write(' npm run runtime:install:codex-plugin');
389
+ } else if (target === 'claude') {
390
+ write(c('Claude Code runtime lane', ANSI.bold));
391
+ write(' export OSUITE_RUNTIME_ADAPTER_ID=claude_code_hooks');
392
+ write(' export OSUITE_SIGNATURE_MODE=required');
393
+ } else if (target === 'mcp') {
394
+ write(c('MCP runtime lane', ANSI.bold));
395
+ write(' Use OSuite preflight, wait-for-approval, and complete-action tools around consequential actions.');
396
+ }
397
+ }
398
+
399
+ async function main() {
400
+ const { command, args } = parseArgs(process.argv.slice(2));
401
+
402
+ if (['help', '--help', '-h'].includes(command)) return printHelp();
403
+ if (['version', '--version', '-v'].includes(command)) return write(pkg.version);
404
+ if (command === 'doctor') return doctor();
405
+ if (command === 'status') return status();
406
+ if (command === 'explain') return printExplanation(args._.join(' '));
407
+ if (command === 'approvals') return approvals(args);
408
+ if (command === 'review') return review(args._[0]);
409
+ if (command === 'approve') return decide(args._[0], 'allow', args);
410
+ if (command === 'deny') return decide(args._[0], 'deny', args);
411
+ if (command === 'init') return init(args._[0] || 'generic');
122
412
 
123
- process.stderr.write(`Unknown command: ${command}\n\n`);
413
+ write(`Unknown command: ${command}`);
414
+ write('');
124
415
  printHelp();
125
416
  process.exit(1);
126
417
  }
package/index.cjs CHANGED
@@ -1,48 +1,38 @@
1
- /**
2
- * OSuite SDK v2 (Stable Runtime API)
3
- * CommonJS compatibility bridge.
4
- *
5
- * ESM: import { OSuite } from 'osuite'
6
- * CJS: const { OSuite } = require('osuite')
7
- */
1
+ 'use strict';
8
2
 
9
- // Minimal CommonJS shim for the v2 SDK
10
- // We use a simplified bridge that forwards calls to the async ESM import
11
- let _module;
3
+ let modulePromise;
12
4
 
13
- async function loadModule() {
14
- if (!_module) {
15
- _module = await import('./osuite.js');
16
- }
17
- return _module;
5
+ function loadModule() {
6
+ if (!modulePromise) modulePromise = import('./osuite.js');
7
+ return modulePromise;
18
8
  }
19
9
 
20
10
  class OSuiteProxy {
21
- constructor(opts) {
22
- this._opts = opts;
23
- this._ready = loadModule().then((m) => {
24
- this._instance = new m.OSuite(opts);
11
+ constructor(options) {
12
+ this._ready = loadModule().then((mod) => {
13
+ this._instance = new mod.OSuite(options);
25
14
  });
26
15
 
27
16
  return new Proxy(this, {
28
- get(target, prop) {
29
- if (prop in target) return target[prop];
30
- if (prop === 'then') return undefined;
17
+ get(target, property) {
18
+ if (property in target) return target[property];
19
+ if (property === 'then') return undefined;
31
20
 
32
21
  return async (...args) => {
33
22
  await target._ready;
34
- if (!target._instance[prop]) {
35
- throw new Error(`Method ${String(prop)} does not exist on OSuite v2`);
23
+ const value = target._instance[property];
24
+ if (typeof value !== 'function') {
25
+ throw new Error(`Method ${String(property)} does not exist on OSuite.`);
36
26
  }
37
- return target._instance[prop](...args);
27
+ return value.apply(target._instance, args);
38
28
  };
39
29
  },
40
30
  });
41
31
  }
42
32
 
43
- static async create(opts) {
33
+ static async create(options) {
44
34
  const mod = await loadModule();
45
- return new mod.OSuite(opts);
35
+ return new mod.OSuite(options);
46
36
  }
47
37
  }
48
38
 
@@ -56,15 +46,24 @@ class ApprovalDeniedError extends Error {
56
46
 
57
47
  class GuardBlockedError extends Error {
58
48
  constructor(decision) {
59
- super(decision.reason || 'Action blocked by policy');
49
+ super((decision && decision.reason) || 'Action blocked by policy');
60
50
  this.name = 'GuardBlockedError';
61
51
  this.decision = decision;
62
52
  }
63
53
  }
64
54
 
55
+ async function buildReviewSurface(...args) {
56
+ const mod = await loadModule();
57
+ return mod.buildReviewSurface(...args);
58
+ }
59
+
65
60
  module.exports = {
66
- OSuite: OSuiteProxy,
67
- DashClaw: OSuiteProxy,
68
61
  ApprovalDeniedError,
62
+ DashClaw: OSuiteProxy,
69
63
  GuardBlockedError,
64
+ OpenClawAgent: OSuiteProxy,
65
+ OSuite: OSuiteProxy,
66
+ Osuite: OSuiteProxy,
67
+ buildReviewSurface,
68
+ default: OSuiteProxy,
70
69
  };
@@ -1,91 +1,3 @@
1
- /**
2
- * OSuite SDK v1 (Legacy): CommonJS compatibility wrapper.
3
- * For ESM: import { OSuite } from 'osuite/legacy'
4
- * For CJS: const { OSuite } = require('osuite/legacy')
5
- */
1
+ 'use strict';
6
2
 
7
- let _module;
8
-
9
- async function loadModule() {
10
- if (!_module) {
11
- _module = await import('./osuite-v1.js');
12
- }
13
- return _module;
14
- }
15
-
16
- // Re-export via dynamic import (CJS → ESM bridge)
17
- module.exports = new Proxy({}, {
18
- get(target, prop) {
19
- if (prop === '__esModule') return true;
20
- if (prop === 'then') return undefined; // Prevent Promise-like behavior
21
-
22
- // Return a lazy-loading constructor wrapper
23
- if (prop === 'GuardBlockedError') {
24
- const Placeholder = class GuardBlockedError extends Error {
25
- constructor(decision) {
26
- const reasons = (decision.reasons || []).join('; ') || 'no reason';
27
- super(`Guard blocked action: ${decision.decision}. Reasons: ${reasons}`);
28
- this.name = 'GuardBlockedError';
29
- this.decision = decision.decision;
30
- this.reasons = decision.reasons || [];
31
- this.warnings = decision.warnings || [];
32
- this.matchedPolicies = decision.matched_policies || [];
33
- this.riskScore = decision.risk_score ?? null;
34
- }
35
- };
36
-
37
- // Support instanceof across ESM/CJS boundary
38
- loadModule().then(m => {
39
- if (m.GuardBlockedError) {
40
- Object.defineProperty(Placeholder, Symbol.hasInstance, {
41
- value: (instance) => instance && (instance.name === 'GuardBlockedError' || instance instanceof m.GuardBlockedError)
42
- });
43
- }
44
- });
45
-
46
- return Placeholder;
47
- }
48
- if (prop === 'OSuite' || prop === 'DashClaw' || prop === 'OpenClawAgent' || prop === 'default') {
49
- return class OSuiteProxy {
50
- constructor(opts) {
51
- this._opts = opts;
52
- this._ready = loadModule().then(m => {
53
- const Cls = m.OSuite || m.DashClaw || m.default;
54
- this._instance = new Cls(opts);
55
- });
56
-
57
- // Return a proxy that forwards all method calls to the async instance
58
- return new Proxy(this, {
59
- get(target, prop) {
60
- if (prop in target) return target[prop];
61
- if (prop === 'then') return undefined;
62
-
63
- return async (...args) => {
64
- await target._ready;
65
- if (!target._instance[prop]) {
66
- throw new Error(`Method ${String(prop)} does not exist on OSuite`);
67
- }
68
- return target._instance[prop](...args);
69
- };
70
- }
71
- });
72
- }
73
-
74
- // For synchronous construction, use OSuite.create()
75
- static async create(opts) {
76
- const mod = await loadModule();
77
- const Cls = mod.OSuite || mod.DashClaw || mod.default;
78
- return new Cls(opts);
79
- }
80
- };
81
- }
82
- return undefined;
83
- }
84
- });
85
-
86
- // Preferred: async factory
87
- module.exports.create = async function create(opts) {
88
- const mod = await loadModule();
89
- const Cls = mod.OSuite || mod.DashClaw || mod.default;
90
- return new Cls(opts);
91
- };
3
+ module.exports = require('../index.cjs');