@parall/parall 1.52.2 → 1.54.0

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.
@@ -51698,10 +51698,17 @@ var ENDPOINTS = {
51698
51698
  // Per-org agent exec access for one clip (default all agents; PUT human-only)
51699
51699
  ORG_CLIP_EXEC_ACCESS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clips/${clipId}/exec-access`,
51700
51700
  // MCP clip server config (cap:clip-mcp; publisher-org only — cross-org gets
51701
- // 403 MCP_CROSS_ORG_DISABLED on the whole family, reads included)
51701
+ // 403 MCP_CROSS_ORG_DISABLED on the whole family, reads included). The
51702
+ // singular family addresses the org's DEFAULT connection (pre-165
51703
+ // back-compat shim); the plural family is the multi-connection surface —
51704
+ // one config row (credential slot) per connection, addressed by config id.
51702
51705
  ORG_CLIP_MCP_CONFIG: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config`,
51703
51706
  ORG_CLIP_MCP_TOOLS_REFRESH: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/tools/refresh`,
51704
- ORG_CLIP_MCP_OAUTH_DISCONNECT: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/oauth/disconnect`
51707
+ ORG_CLIP_MCP_OAUTH_DISCONNECT: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/oauth/disconnect`,
51708
+ ORG_CLIP_MCP_CONFIGS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs`,
51709
+ ORG_CLIP_MCP_CONFIG_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}`,
51710
+ ORG_CLIP_MCP_TOOLS_REFRESH_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}/tools/refresh`,
51711
+ ORG_CLIP_MCP_OAUTH_DISCONNECT_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}/oauth/disconnect`
51705
51712
  };
51706
51713
  var WS_EVENTS = {
51707
51714
  // Client -> Server
@@ -53994,6 +54001,17 @@ var ParallClient = class _ParallClient {
53994
54001
  async deleteClipMCPConfig(orgId, clipId, expectedVersion) {
53995
54002
  return this.request("DELETE", ENDPOINTS.ORG_CLIP_MCP_CONFIG(orgId, clipId), void 0, void 0, false, { headers: { "If-Match": `"${expectedVersion}"` } });
53996
54003
  }
54004
+ /**
54005
+ * List the org's MCP connections for a clip, default first — one row per
54006
+ * connection (credential slot), WITHOUT the tools snapshot (`tool_count`
54007
+ * summarizes it; the per-config GET carries the full snapshot). Same
54008
+ * family-wide fence as the singular GET: publisher-org members only —
54009
+ * cross-org installs get `403 MCP_CROSS_ORG_DISABLED`, and with
54010
+ * `cap:clip-mcp` off the route is not registered at all.
54011
+ */
54012
+ async listClipMCPConnections(orgId, clipId) {
54013
+ return this.request("GET", ENDPOINTS.ORG_CLIP_MCP_CONFIGS(orgId, clipId));
54014
+ }
53997
54015
  /**
53998
54016
  * Re-run tools/list and replace the cached snapshot (org-admin only).
53999
54017
  * Deliberately does NOT advance the CAS version, so an in-flight edit in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/parall",
3
- "version": "1.52.2",
3
+ "version": "1.54.0",
4
4
  "description": "OpenClaw channel plugin for Parall IM",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -16,8 +16,8 @@
16
16
  "openclaw.plugin.json"
17
17
  ],
18
18
  "dependencies": {
19
- "@parall/agent-core": "1.52.2",
20
- "@parall/sdk": "1.52.2"
19
+ "@parall/agent-core": "1.54.0",
20
+ "@parall/sdk": "1.54.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^22.0.0",
@@ -0,0 +1,271 @@
1
+ ---
2
+ name: parall-clip-authoring
3
+ description: "Authoring a v3 registry clip: write manifest.json + command .js files + _helpers, drive the browser/tab runtime API, publish the directory to the org registry with `parall clip publish` — or declare a manifest-only MCP clip pointing at a remote tool server. Use when: user asks to write/create/build a new clip, author a clip command, package a browser automation as a clip, declare an MCP clip, or publish a clip. For CALLING clips that already exist, use parall-clips instead."
4
+ ---
5
+
6
+ # Authoring a Parall Clip (v3)
7
+
8
+ Write a **registry (v3) clip**. This skill is for CREATING clips; to DISCOVER
9
+ and CALL clips that are already installed, use the Parall Clips skill instead.
10
+
11
+ Two kinds of clip exist, and this document covers authoring both:
12
+
13
+ - A **browser clip** — the main subject here — is a folder of named commands
14
+ that run on an Edge (a member's desktop or an org-shared cloud profile) and
15
+ drive a real browser session: one manifest (`manifest.json` or `site.json`,
16
+ either name works) + one `.js` file per command + optional `_`-prefixed
17
+ helpers. The Edge runs each command's JS in a sandbox with a browser handle
18
+ bound to the target profile.
19
+ - An **MCP clip** is a manifest-ONLY declaration pointing at a remote MCP tool
20
+ server — no `.js` files; its tools come live from that server's own
21
+ `tools/list`, never from the manifest. See "MCP clips" below.
22
+
23
+ ## Project layout
24
+
25
+ ```
26
+ my-clip/
27
+ ├── manifest.json # name, description, version, command params
28
+ ├── _helpers.js # OPTIONAL — any _-prefixed file is auto-injected into every command
29
+ ├── search.js # one command = one file; filename (minus .js) IS the command name
30
+ └── profile.js # another command
31
+ ```
32
+
33
+ ## manifest.json
34
+
35
+ ```json
36
+ {
37
+ "name": "twitter",
38
+ "description": "Twitter / X",
39
+ "version": "1.0.0",
40
+ "commands": {
41
+ "search": {
42
+ "description": "Search tweets",
43
+ "params": {
44
+ "query": { "type": "string", "required": true },
45
+ "count": { "type": "number", "required": false }
46
+ }
47
+ },
48
+ "profile": {
49
+ "description": "Fetch a user profile",
50
+ "params": { "handle": { "type": "string", "required": true } }
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ - `commands` keys MUST match the `.js` filenames (`search` ↔ `search.js`).
57
+ - `params` is the input contract the caller sees in `clip info`; validate them in code too.
58
+ - **No top-level `"type"` needed for a browser clip** — omitting it means
59
+ browser. The only accepted values are `"browser"` and `"mcp"` (for the
60
+ latter, see "MCP clips" below). **`"type": "clip"` is the legacy Pinix v2
61
+ package value and is refused at publish** — don't copy it in from an older
62
+ clip.
63
+
64
+ ## A command file
65
+
66
+ ```js
67
+ // search.js — a command is a single async function of (args).
68
+ // The code does NOT know or pick the profile; the Edge binds it per invocation.
69
+ //
70
+ // SHAPE, not a runnable Twitter client: the "..." parts (the GraphQL path,
71
+ // parseTweets' body) are what you fill in per target site. Deliberately not
72
+ // pinned to a real X endpoint — a site's internal API paths rotate, and a
73
+ // stale one baked into this skill would teach a URL that 404s.
74
+ module.exports = async function (args) {
75
+ if (!args.query) return { error: "Missing argument: query" };
76
+
77
+ const tab = await browser.open("https://x.com");
78
+ // Everything after open() goes in try/finally: an early return or a thrown
79
+ // fetch would otherwise leak the tab, and the Edge is long-lived.
80
+ try {
81
+ const ct0 = await tab.cookie("ct0");
82
+ if (!ct0) return { error: "Not logged in" };
83
+
84
+ const data = await tab.fetch("/i/api/graphql/.../SearchTimeline?...", {
85
+ headers: twitterHeaders(ct0), // from _helpers.js, auto-injected
86
+ });
87
+ return { query: args.query, tweets: parseTweets(data) };
88
+ } finally {
89
+ await tab.close();
90
+ }
91
+ };
92
+ ```
93
+
94
+ Return a plain JSON-serializable object. A thrown error surfaces to the caller as
95
+ `SCRIPT_ERROR`; a returned `{ error: "..." }` is your own typed failure — prefer it
96
+ for expected cases (not logged in, missing arg).
97
+
98
+ ## Helpers (`_`-prefixed)
99
+
100
+ Any file whose name starts with `_` is NOT a command. Its top-level functions are
101
+ injected into every command's scope — no import/require needed:
102
+
103
+ ```js
104
+ // _helpers.js
105
+ function twitterHeaders(ct0) {
106
+ return { "X-Csrf-Token": ct0, "X-Twitter-Auth-Type": "OAuth2Session" };
107
+ }
108
+ function parseTweets(data) { /* ... */ }
109
+ ```
110
+
111
+ ## Runtime API (globals available in every command)
112
+
113
+ - `browser.open(url)` → tab handle · `browser.tabs()` → open tabs
114
+ - `tab.cookie(name)` · `tab.fetch(url, opts)` (in-browser fetch, carries the session)
115
+ - `tab.eval(expr)` (escape hatch) · `tab.click(sel)` · `tab.fill(sel, text)` · `tab.navigate(url)`
116
+ - `tab.waitForSelector(sel)` · `tab.getTitle()` · `tab.getURL()` · `tab.screenshot()` · `tab.close()`
117
+ - `fetch` — runtime-side HTTP, does NOT go through the browser (no session)
118
+ - `console` — logs · `args` — the invocation input
119
+
120
+ **Prefer `tab.fetch` over `tab.eval`**: fetch reuses the logged-in session and
121
+ returns structured data; eval is the last resort. Always `tab.close()` what you
122
+ open, and do it in a `finally` — an early return or a thrown fetch is exactly
123
+ when the tab leaks.
124
+
125
+ ## Develop → publish → iterate
126
+
127
+ Use the platform `parall clip` subcommands — they reuse the credentials you
128
+ already have (`PRLL_API_KEY` / `PRLL_ORG_ID`), so there is nothing to install
129
+ or configure.
130
+
131
+ > A separate **standalone `parall-clip`** binary also exists (the Edge-side
132
+ > authoring tool). It takes the SAME operations but a DIFFERENT argument shape —
133
+ > `parall-clip exec <clip> <cmd> --query "AI" --count 10` passes one flag per
134
+ > param, while `parall clip exec` takes a single JSON blob. Do not mix the two
135
+ > forms; everything below is the platform CLI.
136
+
137
+ 1. **Publish** the clip directory to the org registry. Publishing is not a
138
+ release step here — it is the edit loop's SAVE button, because exec only
139
+ ever sees published files:
140
+
141
+ ```sh
142
+ parall clip publish ./my-clip/
143
+ ```
144
+
145
+ It packages the directory (the manifest plus the directory's top-level
146
+ `.js` files; an MCP clip is manifest-only) and POSTs it for you (5 MB cap).
147
+ `name` is the org-wide upsert key — manifest fields win, else the directory
148
+ name / `0.0.1` / `private` fill the gaps. Re-publishing an existing name is
149
+ **author-only** and REPLACES the file set. Publishing into YOUR org makes it
150
+ usable there immediately (same-org self-reference, no review);
151
+ `"visibility": "public"` additionally submits the version for platform
152
+ review before it can spread cross-org.
153
+
154
+ Programmatic equivalent (what `publish` calls under the hood — use only if
155
+ you can't run the CLI). Send it verbatim-shaped: `visibility` is exactly
156
+ one of `"private"` / `"public"`, and `files` maps each filename to its
157
+ source as a string:
158
+
159
+ ```
160
+ POST /api/v1/orgs/{orgId}/clip-registry/publish
161
+ {
162
+ "name": "twitter",
163
+ "description": "Twitter / X",
164
+ "version": "1.0.0",
165
+ "visibility": "private",
166
+ "manifest": {
167
+ "name": "twitter",
168
+ "version": "1.0.0",
169
+ "commands": { "search": { "description": "Search tweets" } }
170
+ },
171
+ "files": { "search.js": "module.exports = async function (args) { return {}; };" }
172
+ }
173
+ ```
174
+
175
+ 2. **Exec** a command against a real target. Args are ONE argument — a JSON
176
+ string (or plain text for a single-value command), not per-param flags:
177
+
178
+ ```sh
179
+ parall clip exec <clip> <command> '{"query":"AI","count":10}' --connection <ccn_id|alias>
180
+ # or route to a desktop (BYOC) device you own: --edge <edge-id>
181
+ # --connection and --edge are mutually exclusive; --timeout <ms> defaults to 30000
182
+ ```
183
+
184
+ A **cloud (hosted) profile is reachable ONLY via `--connection`** — the
185
+ binding its maintainer created IS the authorization. With neither flag the
186
+ server resolves only your own online desktop device, never a cloud profile.
187
+ Discover the bindings with `parall clip connections <clip>`.
188
+
189
+ 3. **Iterate**: edit locally → `parall clip publish` again → re-exec. Exec
190
+ resolves the file set from the REGISTRY, server-side — your own org always
191
+ runs the live working copy, i.e. the latest publish. It NEVER reads your
192
+ local directory: an edit you did not re-publish silently runs the previous
193
+ version.
194
+
195
+ ## Install model & self-development (v3)
196
+
197
+ - Installing a clip is a **reference**, not a copy — the JS lives once in the Market DB.
198
+ - **Your own org** always executes its **live working copy** (latest published files),
199
+ so re-publishing is your edit loop.
200
+ - **Other orgs** installing your `public` clip execute only the **approved snapshot**
201
+ (`approved_version_id`); unreviewed public edits are invisible/unexecutable cross-org.
202
+ - To customize someone else's public clip: install → **fetch its effective
203
+ file set** → modify → **publish into your OWN org** (a derived private
204
+ entry). You cannot edit a published clip in place. The CLI has no files
205
+ subcommand (`clip info` returns only the manifest) — read the source via
206
+ the API:
207
+
208
+ ```
209
+ GET /api/v1/orgs/{orgId}/clip-registry/{clipId}/files
210
+ ```
211
+
212
+ It returns exactly what you may read and execute: your own clip → the live
213
+ working copy; an installed public clip → the approved snapshot.
214
+
215
+ ## MCP clips (manifest-only)
216
+
217
+ An MCP clip declares a remote MCP tool server. There is nothing to code: no
218
+ `.js` files (the no-scripts refusal at publish applies to browser clips only),
219
+ and the folder is just a manifest:
220
+
221
+ ```json
222
+ {
223
+ "name": "linear",
224
+ "description": "Linear (MCP)",
225
+ "version": "1.0.0",
226
+ "type": "mcp",
227
+ "mcp": { "server_url": "https://mcp.linear.app/mcp", "auth": "oauth" }
228
+ }
229
+ ```
230
+
231
+ - The `mcp` block takes ONLY `server_url` and `auth` (`"none" | "bearer" |
232
+ "api_key" | "oauth"`). Any other key is refused at publish — a credential
233
+ belongs to the installing org's own configuration, NEVER to the clip
234
+ definition.
235
+ - `server_url` must be an absolute **https** URL with no embedded credentials,
236
+ query, or fragment. It is review material, frozen with the approved version.
237
+ - Do NOT put the server in the top-level `server` / `auth` manifest keys —
238
+ those are legacy Edge-manifest fields nothing reads. Only the `mcp` block
239
+ declares the server.
240
+ - Both fields are optional, but what you declare is LOCKED: the installing
241
+ org's config must match it, and changing the URL or auth mode means
242
+ republishing.
243
+ - Entering the credential / completing OAuth is a HUMAN step in the Clip
244
+ Console (the config-write endpoints are session-only — an API key cannot
245
+ call them). An org can add SEVERAL connections to one MCP clip — one
246
+ credential slot per account of the same service — all sharing the declared
247
+ `server_url`/`auth`. Once configured, discover the live tool schemas with
248
+ `parall clip tools <clip> [--connection <ref>]` and exec like any other
249
+ clip (`--connection` picks the account).
250
+ - Do NOT declare anything about OAuth client registration in the manifest —
251
+ there is no such field. Whether the provider needs a manually registered
252
+ OAuth app (Google-style) is probed by the server at connect time; the
253
+ Console walks the admin through it when required.
254
+ - Publish is the same command: `parall clip publish ./my-clip/`.
255
+
256
+ ## Cloud (hosted) vs desktop (BYOC) Edge
257
+
258
+ The same command JS runs on either. Hosted profiles are org-shared cloud browsers
259
+ reachable ONLY via an explicit `--connection`; cloud state lives in S3 and is
260
+ hydrated per pod. Your code never touches this — it just gets a `browser`/`tab`
261
+ bound to whatever profile the connection selected.
262
+
263
+ ## Constraints
264
+
265
+ - Browser clips: one command = one file; keep a command's work self-contained
266
+ (open what you need, close it, return). The Edge is stateless about your code
267
+ between calls.
268
+ - Never embed credentials in the clip source — rely on the profile's logged-in
269
+ session (`tab.cookie` / `tab.fetch`). Published source is visible to installers.
270
+ - Exec has a timeout (default 30s, caller-set up to 120s). Long scrapes should page,
271
+ not block.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: parall-clips
3
- description: "Parall clip operations: list installed clips and their connections, inspect clip commands/tools, execute clip commands on an explicit connection. Use when: the task requires external capabilities (GitHub, web search, etc.), user asks about available tools/clips, or you need to call a clip command."
3
+ description: "Parall clip operations: list installed clips and their connections, inspect clip commands/tools, execute clip commands on an explicit connection. Use when: the task requires external capabilities (GitHub, web search, etc.), user asks about available tools/clips, or you need to call a clip command. For WRITING a new clip, use parall-clip-authoring instead."
4
4
  ---
5
5
 
6
6
  # Parall Clips
@@ -40,16 +40,23 @@ guessing which signed-in account you are about to act through.
40
40
  ```bash
41
41
  parall clip exec <clip> <command> [args] --connection <ccn_|alias> # the normal form
42
42
  parall clip exec browser-tools screenshot '{"url":"…"}' --connection cloud-main
43
+ parall clip exec browser-tools scrape --args-file - --connection cloud-main <<'EOF'
44
+ {"url":"https://example.com","fields":["title","price"]}
45
+ EOF
43
46
  ```
44
47
 
45
48
  Build `args` as JSON per the command's params from `clip info` (or the
46
49
  tool's `inputSchema` from `clip tools` for MCP clips — tool names are NOT
47
50
  frozen in the manifest, so never guess a tool name or its argument shape).
51
+ Quote-heavy or large JSON goes through `--args-file <path>` (`-` = stdin
52
+ via a quoted heredoc, as above; strict JSON, mutually exclusive with the
53
+ `[args]` positional) — the shell mangles inline quotes long before the CLI
54
+ sees them, exactly the trap `messages send --text-file` exists for.
48
55
  Results are JSON on stdout; failures print a typed error.
49
56
 
50
- **Name the target explicitly.** A cloud profile has NO implicit route;
51
- omitting the target entirely is a desktop-only legacy form that reaches just
52
- YOUR OWN online desktop device never a shared cloud profile.
57
+ **Name the target explicitly every exec.** `clip exec` refuses to run
58
+ without `--connection` or `--edge`; there is no implicit route to any
59
+ profile, and the legacy no-flag desktop fallback is gone from the CLI.
53
60
 
54
61
  - A cloud (hosted) profile is reachable ONLY via `--connection` — the clip
55
62
  connection its maintainer bound. That binding IS your authorization;
@@ -67,21 +74,39 @@ YOUR OWN online desktop device — never a shared cloud profile.
67
74
  ## MCP clips (remote tool servers)
68
75
 
69
76
  A connection with target `mcp` routes to a remote MCP server; the command
70
- is an MCP tool name and the args are that tool's JSON arguments. Read each
71
- tool's `inputSchema` from `clip tools` first, then exec against the
72
- explicit target the same form as any other clip:
77
+ is an MCP tool name and the args are that tool's JSON arguments. **An MCP
78
+ clip can hold SEVERAL connections one per account of the same service**
79
+ (e.g. two Google Drive accounts), exactly like the browser arm's multiple
80
+ profiles. The flow is: discover the connections, pick by alias, exec with the
81
+ explicit target:
73
82
 
74
83
  ```bash
84
+ parall clip connections <clip> # per-connection auth status, alias, is_default
85
+ parall clip tools <clip> --connection <ccn_|alias> # tools are PER CONNECTION
75
86
  parall clip exec <clip> <tool> [json-args] --connection <ccn_|alias>
76
87
  ```
77
88
 
89
+ - `clip connections` marks MCP rows with their auth mode, `credential_set`
90
+ / `oauth_status` and `tool_count` — a `needs_reauth` or credential-less
91
+ row will refuse exec until an org admin fixes it in the Clip Console.
92
+ - `--connection` is REQUIRED to exec an MCP connection, exactly like a cloud
93
+ profile — the only other target form, `--edge`, is a desktop device and
94
+ never routes to MCP. The alias tells you WHICH account you act as —
95
+ always name it.
96
+ (`is_default` marks the org's primary connection in listings and the
97
+ Console, not an implicit exec route.)
98
+ - `clip tools` without `--connection` reads the default connection's
99
+ snapshot; with several connections and no default it asks you to name one.
78
100
  - No cold start: MCP clips never return `EDGE_ACTIVATING`.
79
101
  - `MCP_TOOL_FAILED` = the tool RAN and reported failure; a sanitized summary
80
102
  of its output rides in the error details. Read it and decide — do not
81
103
  blind-retry.
82
104
  - `MCP_CONCURRENCY_LIMIT` = not started; back off briefly, then retry.
83
- - `MCP_CONFIG_MISSING` / `MCP_DISABLED` = the clip isn't configured, or MCP
84
- is off for this deployment — ask an org admin; retrying won't help.
105
+ - `MCP_CONFIG_MISSING` / `MCP_DISABLED` = that connection isn't configured,
106
+ or MCP is off for this deployment — ask an org admin; retrying won't help.
107
+ - `MCP_OAUTH_REAUTH_REQUIRED` = that connection's authorization expired or
108
+ was disconnected — ask an org admin to re-authorize it in the Clip Console
109
+ (name the connection's alias); retrying won't help.
85
110
  - `OUTCOME_UNKNOWN` follows the rule below: dispatched and MAY HAVE
86
111
  EXECUTED — never auto-retry.
87
112