@frontendxlab/opencode-computer-use 0.1.2 → 0.1.3
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 +3 -2
- package/SECURITY.md +2 -1
- package/bin/opencode-computer-use-setup.js +47 -16
- package/docs/index.html +1 -1
- package/docs/llms-full.txt +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,8 +111,9 @@ npx opencode-computer-use-setup --global --v1
|
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
Use `--dry-run` to inspect the action without changing files. Set
|
|
114
|
-
`OPENCODE_COMPUTER_USE_SKIP_SETUP=1` to disable the postinstall hook.
|
|
115
|
-
|
|
114
|
+
`OPENCODE_COMPUTER_USE_SKIP_SETUP=1` to disable the postinstall hook. Setup also
|
|
115
|
+
skips automatically when `CI=1` or `CI=true`. Restart OpenCode after a
|
|
116
|
+
successful setup so the plugin is loaded.
|
|
116
117
|
|
|
117
118
|
Recent pnpm releases may require explicit approval before running lifecycle
|
|
118
119
|
scripts from dependencies. If pnpm reports `ERR_PNPM_IGNORED_BUILDS`, approve
|
package/SECURITY.md
CHANGED
|
@@ -26,7 +26,8 @@ sandbox.
|
|
|
26
26
|
- The postinstall setup only adds this package to OpenCode's plugin list, keeps
|
|
27
27
|
JSONC comments, creates a `.bak` before replacement, and never edits a
|
|
28
28
|
conflicting or malformed configuration. Ambiguous setup is non-destructive
|
|
29
|
-
and prints manual instructions.
|
|
29
|
+
and prints manual instructions. CI and explicit skip environments bypass
|
|
30
|
+
postinstall mutation.
|
|
30
31
|
|
|
31
32
|
## Operator responsibilities
|
|
32
33
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
6
|
import { delimiter, dirname, join, resolve } from "node:path";
|
|
6
7
|
import { spawnSync } from "node:child_process";
|
|
@@ -189,6 +190,7 @@ function configFlavor(text) {
|
|
|
189
190
|
}
|
|
190
191
|
|
|
191
192
|
function existingConfigFiles(root, scope, env = process.env) {
|
|
193
|
+
if (env.OPENCODE_CONFIG) return [resolve(env.OPENCODE_CONFIG)];
|
|
192
194
|
const bases = scope === "global"
|
|
193
195
|
? [globalConfigDir(env)]
|
|
194
196
|
: [root, join(root, ".opencode")];
|
|
@@ -290,15 +292,29 @@ export function updateConfigText(text, flavor) {
|
|
|
290
292
|
function writeConfig(file, content, dryRun) {
|
|
291
293
|
if (dryRun) return;
|
|
292
294
|
mkdirSync(dirname(file), { recursive: true });
|
|
295
|
+
const mode = existsSync(file) ? statSync(file).mode & 0o777 : 0o600;
|
|
293
296
|
if (existsSync(file)) {
|
|
294
|
-
|
|
297
|
+
const backup = `${file}.bak`;
|
|
298
|
+
copyFileSync(file, backup);
|
|
299
|
+
chmodSync(backup, mode);
|
|
295
300
|
}
|
|
296
|
-
const temporary = `${file}.tmp-${process.pid}`;
|
|
297
|
-
writeFileSync(temporary, content, "utf8");
|
|
301
|
+
const temporary = `${file}.tmp-${process.pid}-${randomUUID()}`;
|
|
302
|
+
writeFileSync(temporary, content, { encoding: "utf8", mode, flag: "wx" });
|
|
303
|
+
chmodSync(temporary, mode);
|
|
298
304
|
if (process.platform === "win32" && existsSync(file)) rmSync(file, { force: true });
|
|
299
305
|
renameSync(temporary, file);
|
|
300
306
|
}
|
|
301
307
|
|
|
308
|
+
function validateConfigFiles(root, scope, env) {
|
|
309
|
+
for (const file of existingConfigFiles(root, scope, env)) {
|
|
310
|
+
const text = readFileSync(file, "utf8");
|
|
311
|
+
const data = parse(text);
|
|
312
|
+
if (text.trim() && (data === undefined || data === null || typeof data !== "object" || Array.isArray(data))) {
|
|
313
|
+
throw new Error(`configuration is not valid JSON or JSONC: ${file}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
302
318
|
function packageAlreadyConfigured(root, scope, env) {
|
|
303
319
|
for (const file of existingConfigFiles(root, scope, env)) {
|
|
304
320
|
try {
|
|
@@ -312,6 +328,12 @@ function packageAlreadyConfigured(root, scope, env) {
|
|
|
312
328
|
return undefined;
|
|
313
329
|
}
|
|
314
330
|
|
|
331
|
+
function redactMessage(value) {
|
|
332
|
+
return String(value)
|
|
333
|
+
.replace(/\b(token|secret|password|api[_-]?key|authorization)\s*[:=]\s*(?:bearer|basic)?[^\n\r,;]*/giu, "$1=[redacted]")
|
|
334
|
+
.slice(0, 500);
|
|
335
|
+
}
|
|
336
|
+
|
|
315
337
|
function runPluginCli(flavor, scope, root, env) {
|
|
316
338
|
const command = flavor === "v1" ? "opencode" : "opencode2";
|
|
317
339
|
const args = ["plugin", "add", PACKAGE_NAME];
|
|
@@ -325,7 +347,7 @@ function runPluginCli(flavor, scope, root, env) {
|
|
|
325
347
|
timeout: 120_000,
|
|
326
348
|
});
|
|
327
349
|
if (result.error || result.status !== 0) {
|
|
328
|
-
const detail = `${result.stdout || ""}${result.stderr || ""}`.trim()
|
|
350
|
+
const detail = redactMessage(`${result.stdout || ""}${result.stderr || ""}`.trim());
|
|
329
351
|
throw new Error(`${command} plugin add failed${detail ? `: ${detail}` : ""}`);
|
|
330
352
|
}
|
|
331
353
|
}
|
|
@@ -388,12 +410,16 @@ export async function setup(options = {}, env = process.env, io = print) {
|
|
|
388
410
|
const requestedFlavor = options.flavor || "auto";
|
|
389
411
|
const scope = inferScope(requestedScope, env);
|
|
390
412
|
const root = chooseRoot(scope, env);
|
|
413
|
+
if (env.OPENCODE_CONFIG_CONTENT) {
|
|
414
|
+
throw new Error("OPENCODE_CONFIG_CONTENT is immutable; add the plugin to that configuration manually");
|
|
415
|
+
}
|
|
391
416
|
const detection = detectOpenCode(env);
|
|
392
417
|
const config = detectConfigFlavor(root, scope, env);
|
|
393
418
|
const flavor = chooseFlavor(requestedFlavor, detection, config);
|
|
394
419
|
const dryRun = Boolean(options.dryRun);
|
|
395
420
|
let result;
|
|
396
421
|
if (scope === "global" && !dryRun) {
|
|
422
|
+
validateConfigFiles(root, scope, env);
|
|
397
423
|
const existing = packageAlreadyConfigured(root, scope, env);
|
|
398
424
|
if (existing) {
|
|
399
425
|
result = { file: existing, changed: false };
|
|
@@ -406,9 +432,10 @@ export async function setup(options = {}, env = process.env, io = print) {
|
|
|
406
432
|
} else {
|
|
407
433
|
result = configForSetup(root, scope, flavor, env, dryRun);
|
|
408
434
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
435
|
+
const action = options.dryRun
|
|
436
|
+
? (result.changed ? "would configure" : "already configured")
|
|
437
|
+
: (result.changed ? "configured" : "already configured");
|
|
438
|
+
io(`OpenCode Computer Use setup ${action} for ${flavor.toUpperCase()} (${scope}).`);
|
|
412
439
|
if (result.changed) io(`Configuration: ${result.file}`);
|
|
413
440
|
return { ...result, scope, flavor, detection };
|
|
414
441
|
}
|
|
@@ -423,14 +450,6 @@ function isDevelopmentInstall(env) {
|
|
|
423
450
|
}
|
|
424
451
|
|
|
425
452
|
export async function main(argv = process.argv.slice(2), env = process.env, io = print) {
|
|
426
|
-
if (isDevelopmentInstall(env)) {
|
|
427
|
-
io("OpenCode Computer Use setup skipped for a package-development install.");
|
|
428
|
-
return 0;
|
|
429
|
-
}
|
|
430
|
-
if (env.OPENCODE_COMPUTER_USE_SKIP_SETUP === "1") {
|
|
431
|
-
io("OpenCode Computer Use setup skipped by OPENCODE_COMPUTER_USE_SKIP_SETUP.");
|
|
432
|
-
return 0;
|
|
433
|
-
}
|
|
434
453
|
let options;
|
|
435
454
|
try {
|
|
436
455
|
options = parseArguments(argv);
|
|
@@ -443,6 +462,18 @@ export async function main(argv = process.argv.slice(2), env = process.env, io =
|
|
|
443
462
|
printHelp();
|
|
444
463
|
return 0;
|
|
445
464
|
}
|
|
465
|
+
if (env.CI === "true" || env.CI === "1") {
|
|
466
|
+
io("OpenCode Computer Use setup skipped in CI.");
|
|
467
|
+
return 0;
|
|
468
|
+
}
|
|
469
|
+
if (isDevelopmentInstall(env)) {
|
|
470
|
+
io("OpenCode Computer Use setup skipped for a package-development install.");
|
|
471
|
+
return 0;
|
|
472
|
+
}
|
|
473
|
+
if (env.OPENCODE_COMPUTER_USE_SKIP_SETUP === "1") {
|
|
474
|
+
io("OpenCode Computer Use setup skipped by OPENCODE_COMPUTER_USE_SKIP_SETUP.");
|
|
475
|
+
return 0;
|
|
476
|
+
}
|
|
446
477
|
return setup(options, env, io)
|
|
447
478
|
.then(() => 0)
|
|
448
479
|
.catch((error) => {
|
package/docs/index.html
CHANGED
|
@@ -459,7 +459,7 @@ npm install</code></pre><button class="copy" type="button" data-copy="#clone">Co
|
|
|
459
459
|
<div class="code"><pre><code id="npm-install">npm install @frontendxlab/opencode-computer-use
|
|
460
460
|
# pnpm install @frontendxlab/opencode-computer-use also works</code></pre><button class="copy" type="button" data-copy="#npm-install">Copy</button></div>
|
|
461
461
|
<p>The package postinstall step detects OpenCode 1 versus OpenCode2 and local versus global installation. It edits JSONC without discarding comments, avoids duplicate entries, and prints both manual formats when the choice is ambiguous. Run it explicitly with <code>npx opencode-computer-use-setup</code>, or add <code>--local</code>, <code>--global</code>, <code>--v1</code>, or <code>--v2</code> when you want to choose explicitly.</p>
|
|
462
|
-
<p>Recent pnpm releases can require explicit lifecycle-script approval. If pnpm reports <code>ERR_PNPM_IGNORED_BUILDS</code>, run <code>pnpm approve-builds @frontendxlab/opencode-computer-use</code> once, then rerun <code>pnpm install</code>. If scripts are disabled, run <code>pnpm exec opencode-computer-use-setup</code> manually.</p>
|
|
462
|
+
<p>Recent pnpm releases can require explicit lifecycle-script approval. If pnpm reports <code>ERR_PNPM_IGNORED_BUILDS</code>, run <code>pnpm approve-builds @frontendxlab/opencode-computer-use</code> once, then rerun <code>pnpm install</code>. If scripts are disabled, run <code>pnpm exec opencode-computer-use-setup</code> manually. CI environments skip setup automatically.</p>
|
|
463
463
|
<div class="tabs" role="tablist" aria-label="OpenCode generation">
|
|
464
464
|
<button role="tab" id="tab-v1" aria-controls="panel-v1" aria-selected="true">OpenCode 1</button>
|
|
465
465
|
<button role="tab" id="tab-v2" aria-controls="panel-v2" aria-selected="false" tabindex="-1">OpenCode2</button>
|
package/docs/llms-full.txt
CHANGED
|
@@ -90,7 +90,7 @@ manual instructions. Run it explicitly with npx opencode-computer-use-setup;
|
|
|
90
90
|
use --local, --global, --v1, or --v2 to choose explicitly. If pnpm reports
|
|
91
91
|
ERR_PNPM_IGNORED_BUILDS, approve the package once with pnpm approve-builds
|
|
92
92
|
@frontendxlab/opencode-computer-use, or run pnpm exec
|
|
93
|
-
opencode-computer-use-setup manually.
|
|
93
|
+
opencode-computer-use-setup manually. CI environments skip setup.
|
|
94
94
|
OpenCode 1 (opencode.json or opencode.jsonc, plugin list):
|
|
95
95
|
{ "$schema": "https://opencode.ai/config.json",
|
|
96
96
|
"plugin": [["@frontendxlab/opencode-computer-use", { "backend": "auto" }]] }
|