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/README.md +172 -58
- package/cli.js +366 -75
- package/index.cjs +29 -30
- package/legacy/index-v1.cjs +2 -90
- package/legacy/osuite-v1-runtime.js +7 -0
- package/legacy/osuite-v1.js +1 -1
- package/osuite.js +505 -1
- package/package.json +3 -9
- package/reviewSurface.js +131 -0
- package/LICENSE +0 -21
- package/dashclaw.js +0 -628
- package/legacy/dashclaw-v1.js +0 -2888
package/cli.js
CHANGED
|
@@ -1,126 +1,417 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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[
|
|
35
|
-
if (!next || next.startsWith('--'))
|
|
36
|
-
|
|
37
|
-
} else {
|
|
47
|
+
const next = rest[index + 1];
|
|
48
|
+
if (!next || next.startsWith('--')) args[key] = true;
|
|
49
|
+
else {
|
|
38
50
|
args[key] = next;
|
|
39
|
-
|
|
51
|
+
index += 1;
|
|
40
52
|
}
|
|
41
|
-
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
96
|
-
|
|
238
|
+
function riskBand(score) {
|
|
239
|
+
if (score >= 75) return 'High';
|
|
240
|
+
if (score >= 45) return 'Moderate';
|
|
241
|
+
return 'Low';
|
|
242
|
+
}
|
|
97
243
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
// We use a simplified bridge that forwards calls to the async ESM import
|
|
11
|
-
let _module;
|
|
3
|
+
let modulePromise;
|
|
12
4
|
|
|
13
|
-
|
|
14
|
-
if (!
|
|
15
|
-
|
|
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(
|
|
22
|
-
this.
|
|
23
|
-
|
|
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,
|
|
29
|
-
if (
|
|
30
|
-
if (
|
|
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
|
-
|
|
35
|
-
|
|
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
|
|
27
|
+
return value.apply(target._instance, args);
|
|
38
28
|
};
|
|
39
29
|
},
|
|
40
30
|
});
|
|
41
31
|
}
|
|
42
32
|
|
|
43
|
-
static async create(
|
|
33
|
+
static async create(options) {
|
|
44
34
|
const mod = await loadModule();
|
|
45
|
-
return new mod.OSuite(
|
|
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
|
};
|
package/legacy/index-v1.cjs
CHANGED
|
@@ -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
|
-
|
|
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');
|