@goplus/agentguard 1.1.1 → 1.1.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 +41 -2
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +335 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloud/client.d.ts +41 -0
- package/dist/cloud/client.d.ts.map +1 -0
- package/dist/cloud/client.js +145 -0
- package/dist/cloud/client.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +133 -0
- package/dist/config.js.map +1 -0
- package/dist/feed/selfcheck.d.ts +36 -0
- package/dist/feed/selfcheck.d.ts.map +1 -0
- package/dist/feed/selfcheck.js +198 -0
- package/dist/feed/selfcheck.js.map +1 -0
- package/dist/feed/state.d.ts +14 -0
- package/dist/feed/state.d.ts.map +1 -0
- package/dist/feed/state.js +57 -0
- package/dist/feed/state.js.map +1 -0
- package/dist/feed/types.d.ts +102 -0
- package/dist/feed/types.d.ts.map +1 -0
- package/dist/feed/types.js +15 -0
- package/dist/feed/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -1
- package/dist/index.js.map +1 -1
- package/dist/installers.d.ts +10 -0
- package/dist/installers.d.ts.map +1 -0
- package/dist/installers.js +137 -0
- package/dist/installers.js.map +1 -0
- package/dist/mcp-server.js +3 -2
- package/dist/mcp-server.js.map +1 -1
- package/dist/postinstall.d.ts +3 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +13 -0
- package/dist/postinstall.js.map +1 -0
- package/dist/runtime/audit.d.ts +10 -0
- package/dist/runtime/audit.d.ts.map +1 -0
- package/dist/runtime/audit.js +94 -0
- package/dist/runtime/audit.js.map +1 -0
- package/dist/runtime/evaluator.d.ts +3 -0
- package/dist/runtime/evaluator.d.ts.map +1 -0
- package/dist/runtime/evaluator.js +197 -0
- package/dist/runtime/evaluator.js.map +1 -0
- package/dist/runtime/policy.d.ts +12 -0
- package/dist/runtime/policy.d.ts.map +1 -0
- package/dist/runtime/policy.js +81 -0
- package/dist/runtime/policy.js.map +1 -0
- package/dist/runtime/protect.d.ts +22 -0
- package/dist/runtime/protect.d.ts.map +1 -0
- package/dist/runtime/protect.js +172 -0
- package/dist/runtime/protect.js.map +1 -0
- package/dist/runtime/redaction.d.ts +6 -0
- package/dist/runtime/redaction.d.ts.map +1 -0
- package/dist/runtime/redaction.js +103 -0
- package/dist/runtime/redaction.js.map +1 -0
- package/dist/runtime/types.d.ts +62 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +3 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/tests/cloud-live.test.d.ts +2 -0
- package/dist/tests/cloud-live.test.d.ts.map +1 -0
- package/dist/tests/cloud-live.test.js +68 -0
- package/dist/tests/cloud-live.test.js.map +1 -0
- package/dist/tests/feed-cloud.test.d.ts +2 -0
- package/dist/tests/feed-cloud.test.d.ts.map +1 -0
- package/dist/tests/feed-cloud.test.js +93 -0
- package/dist/tests/feed-cloud.test.js.map +1 -0
- package/dist/tests/feed-selfcheck.test.d.ts +2 -0
- package/dist/tests/feed-selfcheck.test.d.ts.map +1 -0
- package/dist/tests/feed-selfcheck.test.js +118 -0
- package/dist/tests/feed-selfcheck.test.js.map +1 -0
- package/dist/tests/installer.test.d.ts +2 -0
- package/dist/tests/installer.test.d.ts.map +1 -0
- package/dist/tests/installer.test.js +32 -0
- package/dist/tests/installer.test.js.map +1 -0
- package/dist/tests/runtime-cloud.test.d.ts +2 -0
- package/dist/tests/runtime-cloud.test.d.ts.map +1 -0
- package/dist/tests/runtime-cloud.test.js +206 -0
- package/dist/tests/runtime-cloud.test.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/docs/SECURITY-POLICY.md +558 -0
- package/docs/architecture.md +54 -0
- package/docs/claude-code.md +41 -0
- package/docs/cloud-connect.md +73 -0
- package/docs/cloud-native-api.md +526 -0
- package/docs/codex.md +38 -0
- package/docs/goplus-api.md +38 -0
- package/docs/mcp-server.md +39 -0
- package/docs/openclaw.md +41 -0
- package/docs/privacy-boundary.md +37 -0
- package/docs/sdk.md +83 -0
- package/docs/trust-cli.md +58 -0
- package/examples/openclaw-docker/Dockerfile +10 -0
- package/examples/openclaw-docker/README.md +16 -0
- package/examples/openclaw-docker/docker-compose.yml +8 -0
- package/examples/openclaw-docker/plugin.ts +8 -0
- package/package.json +7 -2
- package/skills/agentguard/SKILL.md +19 -0
- package/skills/agentguard/package.json +2 -1
- package/skills/agentguard/scripts/checkup-report.js +2 -15
package/docs/openclaw.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# OpenClaw
|
|
2
|
+
|
|
3
|
+
OpenClaw can use AgentGuard as a local runtime guard and optional Cloud-connected audit source.
|
|
4
|
+
|
|
5
|
+
## Plugin usage
|
|
6
|
+
|
|
7
|
+
To write a starter plugin file in the current project:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
agentguard init --agent openclaw
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This creates `openclaw.agentguard.plugin.ts`.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { registerOpenClawPlugin } from '@goplus/agentguard';
|
|
17
|
+
|
|
18
|
+
export default function setup(api) {
|
|
19
|
+
registerOpenClawPlugin(api, {
|
|
20
|
+
level: 'balanced',
|
|
21
|
+
skipAutoScan: false,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Runtime hook shape
|
|
27
|
+
|
|
28
|
+
For direct hook integration, send events to:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
AGENTGUARD_AGENT_HOST=openclaw \
|
|
32
|
+
AGENTGUARD_ACTION_TYPE=shell \
|
|
33
|
+
AGENTGUARD_TOOL_NAME=exec \
|
|
34
|
+
agentguard protect
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
AgentGuard accepts OpenClaw-style JSON with `toolName` and `params`, plus Claude-style `tool_name` and `tool_input`.
|
|
38
|
+
|
|
39
|
+
## Docker demo
|
|
40
|
+
|
|
41
|
+
See `examples/openclaw-docker/` for a minimal Docker demo that installs `@goplus/agentguard`, runs `agentguard init --agent openclaw`, and provides a starter plugin.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# OSS / Cloud Privacy Boundary
|
|
2
|
+
|
|
3
|
+
AgentGuard OSS protects your machine without requiring a Cloud account.
|
|
4
|
+
|
|
5
|
+
## Stays local by default
|
|
6
|
+
|
|
7
|
+
- Full prompts
|
|
8
|
+
- Full file contents
|
|
9
|
+
- Full command output
|
|
10
|
+
- Full secrets and private keys
|
|
11
|
+
- Local audit file at `~/.agentguard/audit.jsonl`
|
|
12
|
+
- Cached policy at `~/.agentguard/policy-cache.json`
|
|
13
|
+
|
|
14
|
+
## Sent to Cloud when connected
|
|
15
|
+
|
|
16
|
+
Only redacted runtime audit previews are uploaded by default:
|
|
17
|
+
|
|
18
|
+
- `sessionId`, `agentHost`, `actionType`, `toolName`
|
|
19
|
+
- Redacted `input` preview, capped at 2,000 characters
|
|
20
|
+
- Decision, risk score, risk level, reasons, and policy version
|
|
21
|
+
- Optional approval request metadata for `require_approval`
|
|
22
|
+
|
|
23
|
+
## Built-in redaction
|
|
24
|
+
|
|
25
|
+
AgentGuard redacts common sensitive values before Cloud sync:
|
|
26
|
+
|
|
27
|
+
- AgentGuard/OpenAI-style API keys
|
|
28
|
+
- `Bearer` tokens
|
|
29
|
+
- `token=`, `api_key=`, `secret=`, `password=`, and similar query/env values
|
|
30
|
+
- Private key PEM blocks
|
|
31
|
+
- URL credentials and sensitive query parameters
|
|
32
|
+
|
|
33
|
+
Cloud endpoints also apply server-side redaction, but clients should not rely on server redaction as the first line of defense.
|
|
34
|
+
|
|
35
|
+
## Offline behavior
|
|
36
|
+
|
|
37
|
+
If Cloud is unreachable, AgentGuard continues local enforcement and spools redacted audit events for later retry. It must never fail open for local `block` decisions.
|
package/docs/sdk.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# SDK Usage
|
|
2
|
+
|
|
3
|
+
Use GoPlus AgentGuard as a library in your own project.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @goplus/agentguard
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createAgentGuard } from '@goplus/agentguard';
|
|
15
|
+
|
|
16
|
+
const { scanner, registry, actionScanner } = createAgentGuard();
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Scan Code
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
const result = await scanner.scan({
|
|
23
|
+
skill: {
|
|
24
|
+
id: 'my-skill',
|
|
25
|
+
source: 'github.com/org/skill',
|
|
26
|
+
version_ref: 'v1.0.0',
|
|
27
|
+
artifact_hash: '',
|
|
28
|
+
},
|
|
29
|
+
payload: { type: 'dir', ref: '/path/to/skill' },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(result.risk_level); // 'low' | 'medium' | 'high' | 'critical'
|
|
33
|
+
console.log(result.hits); // Array of detection rule matches
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Evaluate Actions
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
const decision = await actionScanner.decide({
|
|
40
|
+
actor: {
|
|
41
|
+
skill: { id: 'my-skill', source: 'cli', version_ref: '1.0.0', artifact_hash: '' },
|
|
42
|
+
},
|
|
43
|
+
action: {
|
|
44
|
+
type: 'exec_command',
|
|
45
|
+
data: { command: 'rm -rf /' },
|
|
46
|
+
},
|
|
47
|
+
context: {
|
|
48
|
+
session_id: 's1',
|
|
49
|
+
user_present: true,
|
|
50
|
+
env: 'prod',
|
|
51
|
+
time: new Date().toISOString(),
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log(decision.decision); // 'allow' | 'deny' | 'confirm'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Trust Registry
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Register a skill
|
|
62
|
+
await registry.attest({
|
|
63
|
+
skill: { id: 'bot', source: 'github.com/org/bot', version_ref: 'v1.0.0', artifact_hash: 'sha256:abc' },
|
|
64
|
+
trust_level: 'trusted',
|
|
65
|
+
capabilities: { network_allowlist: ['api.example.com'], filesystem_allowlist: [], exec: 'deny', secrets_allowlist: [] },
|
|
66
|
+
review: { reviewed_by: 'admin', evidence_refs: [], notes: 'Verified safe' },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Look up
|
|
70
|
+
const result = await registry.lookup({
|
|
71
|
+
id: 'bot', source: 'github.com/org/bot', version_ref: 'v1.0.0', artifact_hash: 'sha256:abc',
|
|
72
|
+
});
|
|
73
|
+
console.log(result.effective_trust_level); // 'trusted'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## API Reference
|
|
77
|
+
|
|
78
|
+
See the TypeScript types exported from `@goplus/agentguard` for full API details:
|
|
79
|
+
|
|
80
|
+
- `SkillScanner` — Static analysis engine
|
|
81
|
+
- `SkillRegistry` — Trust level management
|
|
82
|
+
- `ActionScanner` — Runtime action evaluator
|
|
83
|
+
- `CAPABILITY_PRESETS` — Predefined capability sets (none, read_only, trading_bot, defi)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Trust Management
|
|
2
|
+
|
|
3
|
+
GoPlus AgentGuard includes a trust registry for managing skill permissions.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
### Attest (Register a Skill)
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
/agentguard trust attest --id my-bot --source github.com/org/bot --version v1.0.0 --hash abc --trust-level restricted --preset trading_bot --reviewed-by admin
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Lookup
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
/agentguard trust lookup --source github.com/org/bot
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Revoke
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
/agentguard trust revoke --source github.com/org/bot --reason "security concern"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### List
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
/agentguard trust list --trust-level trusted
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Trust Levels
|
|
32
|
+
|
|
33
|
+
| Level | Description |
|
|
34
|
+
|-------|-------------|
|
|
35
|
+
| `trusted` | Full access per its capability model |
|
|
36
|
+
| `restricted` | Limited access, some actions require confirmation |
|
|
37
|
+
| `untrusted` | Minimal access, most actions blocked or require confirmation |
|
|
38
|
+
|
|
39
|
+
## Capability Presets
|
|
40
|
+
|
|
41
|
+
| Preset | Network | Filesystem | Exec | Secrets | Web3 |
|
|
42
|
+
|--------|---------|------------|------|---------|------|
|
|
43
|
+
| `none` | No | No | No | No | No |
|
|
44
|
+
| `read_only` | No | Read `./**` | No | No | No |
|
|
45
|
+
| `trading_bot` | Binance, Bybit, OKX, Coinbase, Dextools, CoinGecko | `./config/**`, `./logs/**` | No | `*_API_KEY`, `*_API_SECRET` | Chains 1, 56, 137, 42161 |
|
|
46
|
+
| `defi` | All | No | No | No | Chains 1, 56, 137, 42161, 10, 8453, 43114 |
|
|
47
|
+
|
|
48
|
+
## Auto-Scan on Session Start
|
|
49
|
+
|
|
50
|
+
When installed as a Claude Code plugin, AgentGuard automatically scans new skills on session start:
|
|
51
|
+
|
|
52
|
+
- Discovers skills in `~/.claude/skills/`
|
|
53
|
+
- Calculates artifact hash and checks the registry
|
|
54
|
+
- Runs `quickScan` on new or updated skills
|
|
55
|
+
- Auto-registers with trust level based on scan results:
|
|
56
|
+
- Low risk → `trusted`
|
|
57
|
+
- Medium risk → `restricted`
|
|
58
|
+
- High/Critical risk → `untrusted`
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
FROM node:22-slim
|
|
2
|
+
|
|
3
|
+
WORKDIR /workspace
|
|
4
|
+
|
|
5
|
+
RUN npm install -g @goplus/agentguard
|
|
6
|
+
RUN agentguard init --agent openclaw
|
|
7
|
+
|
|
8
|
+
COPY plugin.ts ./plugin.ts
|
|
9
|
+
|
|
10
|
+
CMD ["node", "-e", "console.log('AgentGuard OpenClaw demo ready. Mount your OpenClaw workspace and register plugin.ts.')"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# OpenClaw Docker Demo
|
|
2
|
+
|
|
3
|
+
This demo shows the minimal AgentGuard runtime wiring for OpenClaw.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
docker compose build
|
|
7
|
+
docker compose run --rm agentguard-openclaw-demo
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
In a real OpenClaw workspace, register `plugin.ts` as a plugin. It uses `registerOpenClawPlugin` to scan loaded skills/plugins and evaluate runtime tool calls before execution.
|
|
11
|
+
|
|
12
|
+
For Cloud policy and audit sync:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
AGENTGUARD_API_KEY=ag_live_xxxxx agentguard connect --url https://agentguard.gopluslabs.io
|
|
16
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goplus/agentguard",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "GoPlus AgentGuard — Security guard for AI agents. Blocks dangerous commands, prevents data leaks, protects secrets. 20 detection rules, runtime action evaluation, trust registry.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,13 +10,16 @@
|
|
|
10
10
|
]
|
|
11
11
|
},
|
|
12
12
|
"bin": {
|
|
13
|
-
"agentguard": "./dist/
|
|
13
|
+
"agentguard": "./dist/cli.js",
|
|
14
|
+
"agentguard-mcp": "./dist/mcp-server.js"
|
|
14
15
|
},
|
|
15
16
|
"scripts": {
|
|
16
17
|
"build": "tsc",
|
|
17
18
|
"start": "node dist/mcp-server.js",
|
|
18
19
|
"dev": "tsc -w",
|
|
19
20
|
"test": "node --test dist/tests/*.test.js",
|
|
21
|
+
"test:cloud-live": "node --test dist/tests/cloud-live.test.js",
|
|
22
|
+
"postinstall": "node dist/postinstall.js || true",
|
|
20
23
|
"prepublishOnly": "npm run build"
|
|
21
24
|
},
|
|
22
25
|
"keywords": [
|
|
@@ -59,6 +62,8 @@
|
|
|
59
62
|
},
|
|
60
63
|
"files": [
|
|
61
64
|
"dist",
|
|
65
|
+
"docs",
|
|
66
|
+
"examples/openclaw-docker",
|
|
62
67
|
"README.md",
|
|
63
68
|
"LICENSE",
|
|
64
69
|
"openclaw.plugin.json",
|
|
@@ -7,6 +7,25 @@ metadata:
|
|
|
7
7
|
author: GoPlusSecurity
|
|
8
8
|
version: "1.1"
|
|
9
9
|
optional_env: "GOPLUS_API_KEY, GOPLUS_API_SECRET (for Web3 transaction simulation only)"
|
|
10
|
+
filesystem-access:
|
|
11
|
+
- path: "~/.ssh/"
|
|
12
|
+
access: read-only
|
|
13
|
+
reason: "Credential safety audit — check directory permissions (stat only, no key content read)"
|
|
14
|
+
- path: "~/.gnupg/"
|
|
15
|
+
access: read-only
|
|
16
|
+
reason: "Credential safety audit — check directory permissions (stat only)"
|
|
17
|
+
- path: "~/.claude/"
|
|
18
|
+
access: read-only
|
|
19
|
+
reason: "Discover installed skills and read security hook configuration"
|
|
20
|
+
- path: "~/.openclaw/"
|
|
21
|
+
access: read-only
|
|
22
|
+
reason: "Discover installed skills and read OpenClaw config for patrol checks"
|
|
23
|
+
- path: "~/.qclaw/"
|
|
24
|
+
access: read-only
|
|
25
|
+
reason: "Discover installed skills in QClaw environments"
|
|
26
|
+
- path: "~/.agentguard/"
|
|
27
|
+
access: read-write
|
|
28
|
+
reason: "Read/write audit log (audit.jsonl) and protection level config (config.json)"
|
|
10
29
|
user-invocable: true
|
|
11
30
|
allowed-tools: Read, Write, Grep, Glob, Bash(node *trust-cli.ts *) Bash(node *action-cli.ts *) Bash(*checkup-report.js) Bash(echo *checkup-report.js) Bash(cat *checkup-report.js) Bash(openclaw *) Bash(ss *) Bash(lsof *) Bash(ufw *) Bash(iptables *) Bash(crontab *) Bash(systemctl list-timers *) Bash(find *) Bash(stat *) Bash(env) Bash(sha256sum *) Bash(node *) Bash(cd *)
|
|
12
31
|
argument-hint: "[scan|action|patrol|trust|report|config|checkup] [args...]"
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
16
16
|
import { join, dirname } from 'node:path';
|
|
17
17
|
import { tmpdir, homedir } from 'node:os';
|
|
18
|
-
import
|
|
18
|
+
import open from 'open';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
20
|
|
|
21
21
|
const DIM_META = {
|
|
@@ -1376,20 +1376,7 @@ body{background:#0a0e14;color:#dfe2eb;font-family:'Inter',sans-serif}
|
|
|
1376
1376
|
// the buffer is flushed, causing the caller (Claude) to receive an empty path.
|
|
1377
1377
|
process.stdout.write(outPath + '\n', () => {
|
|
1378
1378
|
if (!isHeadless) {
|
|
1379
|
-
|
|
1380
|
-
// Use PowerShell Start-Process to open the file via Shell Execute API,
|
|
1381
|
-
// bypassing cmd.exe entirely — cmd /c start creates a visible intermediate
|
|
1382
|
-
// window whose title is the file path, which is the UX bug in #23.
|
|
1383
|
-
spawn('powershell', [
|
|
1384
|
-
'-NoProfile', '-WindowStyle', 'Hidden', '-Command',
|
|
1385
|
-
`Start-Process '${outPath.replace(/'/g, "''")}'`,
|
|
1386
|
-
], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
|
|
1387
|
-
} else {
|
|
1388
|
-
const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
1389
|
-
exec(`${cmd} "${outPath}"`, (err) => {
|
|
1390
|
-
if (err) process.stderr.write(`Could not open browser: ${err.message}\n`);
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1379
|
+
open(outPath).catch(err => process.stderr.write(`Could not open browser: ${err.message}\n`));
|
|
1393
1380
|
}
|
|
1394
1381
|
// Hard exit after 3s — guards against exec child process hanging and
|
|
1395
1382
|
// blocking Node from exiting naturally (e.g. xdg-open on misconfigured Linux).
|