osuite 2.9.3 → 2.9.4
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 +8 -7
- package/cli.js +84 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# OSuite SDK (v2.9.
|
|
1
|
+
# OSuite SDK (v2.9.4)
|
|
2
2
|
|
|
3
3
|
**Governed action SDK for AI agents.**
|
|
4
4
|
|
|
@@ -451,11 +451,11 @@ OSuite uses standard HTTP status codes and custom error classes:
|
|
|
451
451
|
Install the OSuite CLI from the project root where your agent works. For Codex and Claude Code, the installer adds project-scoped hook files (`.codex` or `.claude`) while the CLI remains the control surface for doctor, explain, review, and approval:
|
|
452
452
|
|
|
453
453
|
```bash
|
|
454
|
-
curl -fsSL https://studio.osuite.ai/install.sh | bash
|
|
455
|
-
# or
|
|
456
|
-
curl -fsSL https://studio.osuite.ai/install.sh | bash -s -- claude
|
|
454
|
+
curl -fsSL https://studio.osuite.ai/install.sh | bash
|
|
457
455
|
```
|
|
458
456
|
|
|
457
|
+
The default installer auto-detects common project lanes and starts a browser handoff. If auto-detection is wrong, force a lane with `OSUITE_INSTALL_TARGET=codex`, `OSUITE_INSTALL_TARGET=claude`, `OSUITE_INSTALL_TARGET=sdk`, or `OSUITE_INSTALL_TARGET=mcp`.
|
|
458
|
+
|
|
459
459
|
If you prefer npm directly:
|
|
460
460
|
|
|
461
461
|
```bash
|
|
@@ -464,6 +464,7 @@ npm install -g osuite
|
|
|
464
464
|
|
|
465
465
|
```bash
|
|
466
466
|
osuite # branded welcome and command map
|
|
467
|
+
osuite connect auto # detect the current project and start browser handoff
|
|
467
468
|
osuite doctor # check env, Studio health, runtime, and signature posture
|
|
468
469
|
osuite status # show the current Studio connection
|
|
469
470
|
osuite init codex # install .codex hook files into the current project
|
|
@@ -475,7 +476,7 @@ osuite approve <actionId> # approve a specific action
|
|
|
475
476
|
osuite deny <actionId> # deny a specific action
|
|
476
477
|
```
|
|
477
478
|
|
|
478
|
-
For local CLI agents, copy `.codex/.env.example` or `.claude/.env.example` to `.env`, fill `OSUITE_API_KEY`, and run the agent from the same project root. The CLI is intentionally more than an API wrapper:
|
|
479
|
+
For local CLI agents, `osuite connect auto` prints a browser handoff URL and installs the project hook lane when it can detect Codex or Claude Code. If browser handoff is unavailable, copy `.codex/.env.example` or `.claude/.env.example` to `.env`, fill `OSUITE_API_KEY`, and run the agent from the same project root. The CLI is intentionally more than an API wrapper:
|
|
479
480
|
- `doctor` tells a developer what is missing before they connect an agent.
|
|
480
481
|
- `explain` gives a local CAVA preview before a command reaches OSuite.
|
|
481
482
|
- `approvals` provides a readable approval inbox instead of raw JSON.
|
|
@@ -488,10 +489,10 @@ When an agent calls `waitForApproval()`, it prints the action ID and replay link
|
|
|
488
489
|
Govern Claude Code tool calls without any SDK instrumentation. The preferred path is the project-root installer:
|
|
489
490
|
|
|
490
491
|
```bash
|
|
491
|
-
curl -fsSL https://studio.osuite.ai/install.sh | bash
|
|
492
|
+
curl -fsSL https://studio.osuite.ai/install.sh | bash
|
|
492
493
|
```
|
|
493
494
|
|
|
494
|
-
It creates `.claude/hooks/*`, `.claude/settings.json`, `.claude/settings.local.json`, and `.claude/.env.example
|
|
495
|
+
It detects `.claude`, creates `.claude/hooks/*`, `.claude/settings.json`, `.claude/settings.local.json`, and `.claude/.env.example`, then prints the browser handoff. Copy the env example to `.claude/.env` and fill `OSUITE_API_KEY` only when the browser flow is not available.
|
|
495
496
|
|
|
496
497
|
---
|
|
497
498
|
|
package/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import crypto from 'node:crypto';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
7
|
import { OSuite, buildReviewSurface } from './osuite.js';
|
|
7
8
|
|
|
@@ -126,6 +127,41 @@ function normalizedBaseUrl(value) {
|
|
|
126
127
|
return String(value || DEFAULT_BASE_URL).replace(/\/$/, '');
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
function sanitizeRuntimeTarget(value, fallback = 'auto') {
|
|
131
|
+
const normalized = String(value || fallback)
|
|
132
|
+
.trim()
|
|
133
|
+
.toLowerCase()
|
|
134
|
+
.replace(/[^a-z0-9_-]/g, '');
|
|
135
|
+
if (!normalized) return fallback;
|
|
136
|
+
if (normalized === 'claude-code' || normalized === 'claudecode') return 'claude';
|
|
137
|
+
if (normalized === 'generic') return 'auto';
|
|
138
|
+
return normalized;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function detectRuntimeTarget(preferred = 'auto') {
|
|
142
|
+
const target = sanitizeRuntimeTarget(preferred);
|
|
143
|
+
if (target !== 'auto') return target;
|
|
144
|
+
|
|
145
|
+
const cwd = process.cwd();
|
|
146
|
+
if (fs.existsSync(path.join(cwd, '.codex'))) return 'codex';
|
|
147
|
+
if (fs.existsSync(path.join(cwd, '.claude'))) return 'claude';
|
|
148
|
+
if (fs.existsSync(path.join(cwd, 'mcp.json')) || fs.existsSync(path.join(cwd, '.mcp.json'))) return 'mcp';
|
|
149
|
+
if (fs.existsSync(path.join(cwd, 'package.json'))) return 'sdk';
|
|
150
|
+
return 'sdk';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function generateConnectCode() {
|
|
154
|
+
return `OS-${crypto.randomBytes(3).toString('hex').toUpperCase()}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function buildConnectHandoffUrl({ baseUrl, runtime, code }) {
|
|
158
|
+
const url = new URL('/connect', normalizedBaseUrl(baseUrl));
|
|
159
|
+
url.searchParams.set('runtime', runtime);
|
|
160
|
+
url.searchParams.set('source', 'cli');
|
|
161
|
+
url.searchParams.set('code', code);
|
|
162
|
+
return url.toString();
|
|
163
|
+
}
|
|
164
|
+
|
|
129
165
|
function projectFilePath(relativePath) {
|
|
130
166
|
const clean = String(relativePath || '').replace(/^\/+/, '');
|
|
131
167
|
const root = path.resolve(process.cwd());
|
|
@@ -217,6 +253,7 @@ function printHelp() {
|
|
|
217
253
|
write(' osuite review <actionId> Render the decision review card');
|
|
218
254
|
write(' osuite approve <actionId> --reason "Looks safe"');
|
|
219
255
|
write(' osuite deny <actionId> --reason "Outside change window"');
|
|
256
|
+
write(' osuite connect [auto|codex|claude|sdk|mcp] Detect runtime and start browser handoff');
|
|
220
257
|
write(' osuite init [sdk|codex|claude|mcp] Print setup steps for a runtime lane');
|
|
221
258
|
write('');
|
|
222
259
|
write(c('Environment', ANSI.bold));
|
|
@@ -513,6 +550,52 @@ async function init(target = 'generic', args = {}) {
|
|
|
513
550
|
}
|
|
514
551
|
}
|
|
515
552
|
|
|
553
|
+
async function connect(target = 'auto', args = {}) {
|
|
554
|
+
const config = readConfig();
|
|
555
|
+
const runtime = detectRuntimeTarget(target);
|
|
556
|
+
const baseUrl = normalizedBaseUrl(args['base-url'] || config.baseUrl || DEFAULT_BASE_URL);
|
|
557
|
+
const code = String(args.code || generateConnectCode()).trim() || generateConnectCode();
|
|
558
|
+
const handoffUrl = buildConnectHandoffUrl({ baseUrl, runtime, code });
|
|
559
|
+
const hasApiKey = Boolean(config.apiKey);
|
|
560
|
+
|
|
561
|
+
write(c('OSuite Connect', `${ANSI.bold}${ANSI.cyan}`));
|
|
562
|
+
write(c('Project-root setup for governed agent actions.', ANSI.dim));
|
|
563
|
+
write('');
|
|
564
|
+
write(`Detected runtime ${runtime}`);
|
|
565
|
+
write(`Project root ${process.cwd()}`);
|
|
566
|
+
write(`Browser handoff ${handoffUrl}`);
|
|
567
|
+
write('');
|
|
568
|
+
write(c('What happens next', ANSI.bold));
|
|
569
|
+
write(' 1. Open the browser handoff URL and choose the workspace that should own this runtime.');
|
|
570
|
+
write(' 2. OSuite prepares the runtime identity and project hook lane for the detected agent.');
|
|
571
|
+
write(' 3. Run osuite doctor, then trigger one low-risk governed action from the same project root.');
|
|
572
|
+
write('');
|
|
573
|
+
|
|
574
|
+
if (PROJECT_BOOTSTRAPS[runtime]) {
|
|
575
|
+
if (args['dry-run']) {
|
|
576
|
+
write(`Dry run: would install project hook files for ${runtime}.`);
|
|
577
|
+
} else {
|
|
578
|
+
await installRuntimeProjectBootstrap(runtime, args);
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
write(c('Runtime setup', ANSI.bold));
|
|
582
|
+
write(' This runtime uses the embedded SDK/MCP lane. Install the SDK in the app code path that dispatches actions.');
|
|
583
|
+
if (runtime === 'sdk') write(' npm install osuite');
|
|
584
|
+
if (runtime === 'mcp') write(' Wrap consequential tool calls with OSuite preflight, wait-for-approval, and complete-action steps.');
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
write('');
|
|
588
|
+
write(c('API key fallback', ANSI.bold));
|
|
589
|
+
if (hasApiKey) {
|
|
590
|
+
write(' OSUITE_API_KEY is already present in this shell or project env. Browser handoff can be skipped for this project.');
|
|
591
|
+
} else if (PROJECT_BOOTSTRAPS[runtime]) {
|
|
592
|
+
const envPath = PROJECT_BOOTSTRAPS[runtime].envPath.replace(/\.example$/, '');
|
|
593
|
+
write(` If browser handoff is unavailable, copy ${PROJECT_BOOTSTRAPS[runtime].envPath} to ${envPath} and paste a workspace API key.`);
|
|
594
|
+
} else {
|
|
595
|
+
write(' If browser handoff is unavailable, export OSUITE_BASE_URL and OSUITE_API_KEY before running the agent.');
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
516
599
|
async function main() {
|
|
517
600
|
const { command, args } = parseArgs(process.argv.slice(2));
|
|
518
601
|
|
|
@@ -525,6 +608,7 @@ async function main() {
|
|
|
525
608
|
if (command === 'review') return review(args._[0]);
|
|
526
609
|
if (command === 'approve') return decide(args._[0], 'allow', args);
|
|
527
610
|
if (command === 'deny') return decide(args._[0], 'deny', args);
|
|
611
|
+
if (command === 'connect') return connect(args._[0] || 'auto', args);
|
|
528
612
|
if (command === 'init') return init(args._[0] || 'generic', args);
|
|
529
613
|
|
|
530
614
|
write(`Unknown command: ${command}`);
|