osuite 2.9.0 → 2.9.2
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 +128 -62
- package/cli.js +373 -79
- package/index.cjs +29 -139
- 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 -10
- package/package.json +3 -9
- package/reviewSurface.js +131 -0
- package/LICENSE +0 -21
- package/dashclaw.js +0 -988
- package/legacy/dashclaw-v1.js +0 -2888
package/cli.js
CHANGED
|
@@ -1,129 +1,423 @@
|
|
|
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
|
+
};
|
|
22
|
+
|
|
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']);
|
|
8
27
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
+
}
|
|
15
35
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
OSUITE_URL Optional fallback for hosted/base URL
|
|
19
|
-
NEXTAUTH_URL Optional fallback when reusing app env files
|
|
20
|
-
OSUITE_API_KEY Required, admin API key for approval operations
|
|
21
|
-
OSUITE_AGENT_ID Optional, defaults to "osuite-cli"
|
|
22
|
-
`);
|
|
36
|
+
function write(value = '') {
|
|
37
|
+
process.stdout.write(`${value}\n`);
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
function parseArgs(argv) {
|
|
26
41
|
const [command = 'help', ...rest] = argv;
|
|
27
42
|
const args = { _: [] };
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const token = rest[i];
|
|
43
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
44
|
+
const token = rest[index];
|
|
31
45
|
if (token.startsWith('--')) {
|
|
32
46
|
const key = token.slice(2);
|
|
33
|
-
const next = rest[
|
|
34
|
-
if (!next || next.startsWith('--'))
|
|
35
|
-
|
|
36
|
-
} else {
|
|
47
|
+
const next = rest[index + 1];
|
|
48
|
+
if (!next || next.startsWith('--')) args[key] = true;
|
|
49
|
+
else {
|
|
37
50
|
args[key] = next;
|
|
38
|
-
|
|
51
|
+
index += 1;
|
|
39
52
|
}
|
|
40
|
-
|
|
53
|
+
} else {
|
|
54
|
+
args._.push(token);
|
|
41
55
|
}
|
|
42
|
-
args._.push(token);
|
|
43
56
|
}
|
|
44
|
-
|
|
45
57
|
return { command, args };
|
|
46
58
|
}
|
|
47
59
|
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
}
|
|
56
73
|
|
|
57
|
-
|
|
58
|
-
|
|
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');
|
|
59
80
|
process.exit(1);
|
|
60
81
|
}
|
|
82
|
+
return new OSuite(config);
|
|
83
|
+
}
|
|
61
84
|
|
|
62
|
-
|
|
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');
|
|
63
91
|
}
|
|
64
92
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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 [sdk|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
|
+
}
|
|
71
115
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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.' };
|
|
75
136
|
}
|
|
76
137
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
+
};
|
|
81
152
|
}
|
|
82
|
-
}
|
|
83
153
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
};
|
|
88
164
|
}
|
|
89
165
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
+
};
|
|
95
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
|
+
};
|
|
96
236
|
}
|
|
97
237
|
|
|
98
|
-
|
|
99
|
-
|
|
238
|
+
function riskBand(score) {
|
|
239
|
+
if (score >= 75) return 'High';
|
|
240
|
+
if (score >= 45) return 'Moderate';
|
|
241
|
+
return 'Low';
|
|
242
|
+
}
|
|
100
243
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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}`));
|
|
104
263
|
}
|
|
264
|
+
}
|
|
105
265
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
+
}
|
|
109
298
|
}
|
|
110
299
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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);
|
|
114
307
|
}
|
|
115
308
|
|
|
116
|
-
|
|
117
|
-
|
|
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.');
|
|
118
346
|
return;
|
|
119
347
|
}
|
|
120
348
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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);
|
|
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
|
+
}
|
|
363
|
+
|
|
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);
|
|
124
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 === 'sdk') {
|
|
386
|
+
write(c('Embedded SDK runtime lane', ANSI.bold));
|
|
387
|
+
write(' npm install osuite');
|
|
388
|
+
write(' export OSUITE_RUNTIME_FAMILY=framework_sdk');
|
|
389
|
+
write(' export OSUITE_ADAPTER_MODE=inline_sdk');
|
|
390
|
+
write(' osuite doctor');
|
|
391
|
+
} else if (target === 'codex') {
|
|
392
|
+
write(c('Codex runtime lane', ANSI.bold));
|
|
393
|
+
write(' osuite doctor');
|
|
394
|
+
write(' npm run runtime:install:codex-plugin');
|
|
395
|
+
} else if (target === 'claude') {
|
|
396
|
+
write(c('Claude Code runtime lane', ANSI.bold));
|
|
397
|
+
write(' export OSUITE_RUNTIME_ADAPTER_ID=claude_code_hooks');
|
|
398
|
+
write(' export OSUITE_SIGNATURE_MODE=required');
|
|
399
|
+
} else if (target === 'mcp') {
|
|
400
|
+
write(c('MCP runtime lane', ANSI.bold));
|
|
401
|
+
write(' Use OSuite preflight, wait-for-approval, and complete-action tools around consequential actions.');
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function main() {
|
|
406
|
+
const { command, args } = parseArgs(process.argv.slice(2));
|
|
407
|
+
|
|
408
|
+
if (['help', '--help', '-h'].includes(command)) return printHelp();
|
|
409
|
+
if (['version', '--version', '-v'].includes(command)) return write(pkg.version);
|
|
410
|
+
if (command === 'doctor') return doctor();
|
|
411
|
+
if (command === 'status') return status();
|
|
412
|
+
if (command === 'explain') return printExplanation(args._.join(' '));
|
|
413
|
+
if (command === 'approvals') return approvals(args);
|
|
414
|
+
if (command === 'review') return review(args._[0]);
|
|
415
|
+
if (command === 'approve') return decide(args._[0], 'allow', args);
|
|
416
|
+
if (command === 'deny') return decide(args._[0], 'deny', args);
|
|
417
|
+
if (command === 'init') return init(args._[0] || 'generic');
|
|
125
418
|
|
|
126
|
-
|
|
419
|
+
write(`Unknown command: ${command}`);
|
|
420
|
+
write('');
|
|
127
421
|
printHelp();
|
|
128
422
|
process.exit(1);
|
|
129
423
|
}
|