@aryacode/dsh-plugin-arya-computer-use 1.0.0 → 1.0.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.
Files changed (3) hide show
  1. package/README.md +11 -0
  2. package/index.js +119 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -97,6 +97,17 @@ node mcp/browser-launcher.mjs # → agent-browser mcp --tools core [--p
97
97
 
98
98
  So sign-in reuse and attaching to your own Chrome are settings, not hand-edited rows. Web-lane changes land on the next MCP restart; the settings page says so.
99
99
 
100
+ ## Plugin market
101
+
102
+ Installing from Plugins -> Arya Plugin Market needs no manual source: on first run the plugin registers
103
+ `https://cdn.jsdelivr.net/gh/KonnectSuite/dsh-arya-computer-use@main/index.json`
104
+ with the market (existing sources are kept) and every npm release shows up there as an update. Releases go
105
+ through npm staged publishing, so a publish is visible in the market once the version is approved on npmjs.com.
106
+
107
+ The plugin manager blocks dependency build scripts by default. The engines (`agent-desktop`, `agent-browser`)
108
+ fetch their native binary in a postinstall script; approve those two builds when installing, or let 1.0.1+
109
+ repair them itself at start-up - the status route reports the outcome under `mcp.engines`.
110
+
100
111
  ## Tool presets
101
112
 
102
113
  - `lite` (default) — all 16 desktop tools listed up front.
package/index.js CHANGED
@@ -18,8 +18,10 @@
18
18
 
19
19
  import z from '@deepseek-ai/schemastery';
20
20
  import os from 'node:os';
21
- import { readFile, readFileSync, readdirSync } from 'node:fs';
21
+ import { readFile, readFileSync, readdirSync, statSync } from 'node:fs';
22
+ import { execFileSync } from 'node:child_process';
22
23
  import { dirname, join } from 'node:path';
24
+ import { createRequire } from 'node:module';
23
25
  import { fileURLToPath, pathToFileURL } from 'node:url';
24
26
  import { DEFAULT_SETTINGS, normalizeSettings, STOP_SHORTCUT_PATTERN, repairStaleSettings } from './mcp/settings.mjs';
25
27
  import { TakeoverControl, atomicJson } from './mcp/takeover.mjs';
@@ -34,6 +36,11 @@ const SKILL_FILE = join(SKILL_DIR, 'SKILL.md');
34
36
  const PROVIDER_NAME = 'arya-computer-use';
35
37
  const RANK = 350;
36
38
  const STATUS_PATH = '/plugins/arya/status';
39
+ // Market index that lists this plugin. The market ships no default source, so the
40
+ // plugin registers its own on first apply: users then see "Arya Computer Use" in
41
+ // Plugins -> Arya Plugin Market without pasting a URL, and every release published
42
+ // to npm shows up there as an update.
43
+ const MARKET_INDEX_URL = 'https://cdn.jsdelivr.net/gh/KonnectSuite/dsh-arya-computer-use@main/index.json';
37
44
  const PLUGIN_VERSION = (() => {
38
45
  try { return JSON.parse(readFileSync(join(HERE, 'package.json'), 'utf8')).version ?? '0.0.0'; }
39
46
  catch { return '0.0.0'; }
@@ -84,6 +91,68 @@ async function loadMcpBridge() {
84
91
  return { bridge: null, resolveErrors };
85
92
  }
86
93
 
94
+ // ---------------------------------------------------------------------------
95
+ // Engine binaries.
96
+ //
97
+ // agent-desktop and agent-browser ship their native binary through a
98
+ // postinstall script. pnpm (and therefore the plugin manager) refuses to run
99
+ // dependency build scripts unless they are approved, so a market install can
100
+ // leave both engines without a binary. Rather than fail with an opaque spawn
101
+ // error much later, run the vendor's own postinstall once, ourselves, and
102
+ // report what happened in the status route.
103
+ // ---------------------------------------------------------------------------
104
+
105
+ const ENGINE_PACKAGES = ['agent-desktop', 'agent-browser'];
106
+
107
+ function engineRoot(pkg) {
108
+ try {
109
+ return dirname(createRequire(import.meta.url).resolve(`${pkg}/package.json`));
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ function engineBinaryReady(root) {
116
+ const binDir = join(root, 'bin');
117
+ const token = `${process.platform}-${process.arch}`;
118
+ const wantSuffix = process.platform === 'win32' ? '.exe' : '';
119
+ try {
120
+ return readdirSync(binDir).some(name => {
121
+ if (name.endsWith('.sha256') || !name.includes(token)) return false;
122
+ return name.slice(name.indexOf(token) + token.length) === wantSuffix;
123
+ });
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+
129
+ async function ensureEngine(pkg) {
130
+ const root = engineRoot(pkg);
131
+ if (!root) return { package: pkg, ok: false, reason: 'not-installed' };
132
+ if (engineBinaryReady(root)) return { package: pkg, ok: true, binary: 'present' };
133
+ const script = join(root, 'scripts', 'postinstall.js');
134
+ try {
135
+ if (!statSync(script).isFile()) return { package: pkg, ok: false, reason: 'no-postinstall' };
136
+ } catch {
137
+ return { package: pkg, ok: false, reason: 'no-postinstall' };
138
+ }
139
+ try {
140
+ execFileSync(process.execPath, [script], { cwd: root, stdio: 'ignore', timeout: 600000, windowsHide: true });
141
+ } catch (error) {
142
+ return { package: pkg, ok: false, reason: 'postinstall-failed', message: error?.message ?? String(error) };
143
+ }
144
+ return { package: pkg, ok: engineBinaryReady(root), binary: 'downloaded' };
145
+ }
146
+
147
+ async function ensureEngines() {
148
+ const results = [];
149
+ for (const pkg of ENGINE_PACKAGES) results.push(await ensureEngine(pkg));
150
+ return results;
151
+ }
152
+
153
+ // Internal hook so a diagnostic can exercise the repair without a host.
154
+ export { ensureEngines as ensureEngineBinaries };
155
+
87
156
  // One in-process mcp-client per lane. stdio transport with our own files, so
88
157
  // the same code path serves a checkout and a registry install.
89
158
  async function mountMcpServers(ctx) {
@@ -192,11 +261,59 @@ Object.defineProperty(Config, '~standard', { value: { ...validation, validate(in
192
261
  return result;
193
262
  } } });
194
263
 
264
+ // Register this plugin's market index with the plugin market, once, so the
265
+ // market lists Arya Computer Use without the user adding a source by hand.
266
+ // setIndexes keeps any source the user already configured.
267
+ function ensureMarketSource(ctx) {
268
+ const canonical = (url) => {
269
+ try { return new URL(url).href; }
270
+ catch { return url; }
271
+ };
272
+ try {
273
+ ctx.inject(['pluginMarket'], (scope) => {
274
+ void (async () => {
275
+ try {
276
+ const market = scope.pluginMarket ?? scope.get('pluginMarket');
277
+ if (!market || typeof market.listIndexes !== 'function' || typeof market.setIndexes !== 'function') return;
278
+ const sources = market.listIndexes() ?? [];
279
+ const urls = sources.map(entry => entry?.url).filter(url => typeof url === 'string');
280
+ if (urls.some(url => canonical(url) === canonical(MARKET_INDEX_URL))) return;
281
+ await market.setIndexes([...urls, MARKET_INDEX_URL]);
282
+ ctx.logger.info('dsh-arya-computer-use: registered its market index source');
283
+ } catch (error) {
284
+ ctx.logger.warn('dsh-arya-computer-use: could not register the market index source: %s', error?.message ?? String(error));
285
+ }
286
+ })();
287
+ });
288
+ } catch (error) {
289
+ ctx.logger?.warn?.('dsh-arya-computer-use: plugin market is unavailable: %s', error?.message ?? String(error));
290
+ }
291
+ }
292
+
293
+ // Internal hook so a diagnostic can exercise the market registration.
294
+ export { ensureMarketSource as __ensureMarketSource };
295
+
195
296
  /* -------------------------------------------------------------------- main */
196
297
 
197
298
  export async function apply(ctx, config) {
198
299
  registerSkillProvider(ctx);
199
- void mountMcpServers(ctx);
300
+ ensureMarketSource(ctx);
301
+ void (async () => {
302
+ try {
303
+ const engines = await ensureEngines();
304
+ mcpStatus = { ...mcpStatus, engines };
305
+ const broken = engines.filter(engine => !engine.ok);
306
+ if (broken.length) {
307
+ ctx.logger.warn(
308
+ 'dsh-arya-computer-use: engine binaries are missing: %s (run the vendor postinstall, or approve dependency build scripts in the profile)',
309
+ broken.map(engine => `${engine.package}${engine.reason ? ` (${engine.reason})` : ''}`).join(', '),
310
+ );
311
+ }
312
+ } catch (error) {
313
+ ctx.logger.warn('dsh-arya-computer-use: could not prepare engine binaries: %s', error?.message ?? String(error));
314
+ }
315
+ await mountMcpServers(ctx);
316
+ })();
200
317
 
201
318
  const root = controlDir();
202
319
  const runtime = { configFile: join(root, 'settings.json'), stateFile: join(root, 'state.json'), activityFile: join(root, 'activity.json'), layoutFile: join(root, 'overlay-layout.json'), targetFile: join(root, 'target.json') };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aryacode/dsh-plugin-arya-computer-use",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Computer use for DSH/AryaCode: agent-desktop UIA engine, agent-browser web lane, takeover UI with a ghost cursor",
5
5
  "license": "MIT",
6
6
  "type": "module",