@parall/daemon 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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "1.52.2",
3
- "built_at": "2026-08-05T06:13:33.099Z",
2
+ "version": "1.54.0",
3
+ "built_at": "2026-08-06T10:25:37.612Z",
4
4
  "min_node_version": "20.0.0",
5
5
  "files": {
6
6
  "bb-browser-daemon.js": {
@@ -16,29 +16,29 @@
16
16
  "size": 18
17
17
  },
18
18
  "parall-browser-pod.js": {
19
- "sha256": "4d5b8c1f431963dac5ab39cbca769e63db91d0e0c15a118b9c8aa9fba0a19ecf",
20
- "size": 2916134
19
+ "sha256": "1fdbc5185fc1237a1037d9a1e95134a29313cdadc5b8fd2b52045a93caebe265",
20
+ "size": 2917480
21
21
  },
22
22
  "parall-channel-exec.js": {
23
23
  "sha256": "7c2a6e12483a45e9eb4a42cd7d4cdd3c4fa21dd09ac1347d9a3e177b4ddee930",
24
24
  "size": 7220
25
25
  },
26
26
  "parall-claude-agent.js": {
27
- "sha256": "2b780cc9718945476c05ae4f173426c5f064150ce7af499dba339ed293b4afff",
28
- "size": 2779447
27
+ "sha256": "3872308f00bc9e0e57f00f65b528d5259736aa84af3ee1281c60be413b65d6f8",
28
+ "size": 2795199
29
29
  },
30
30
  "parall-codex-agent.js": {
31
- "sha256": "40ec7af38394abf1a926e4b982a08a110f3902cd609bdb1d9c5453f1e3d43434",
32
- "size": 2810151
31
+ "sha256": "0c8d5d7d02a67183f9ec63ddb7b68ea77c807411e0b421394f71ee02cf90d218",
32
+ "size": 2825903
33
33
  },
34
34
  "parall-daemon.js": {
35
- "sha256": "b2e754ab09cf56e435b5947275968401721b3f194193701a6447e4d0ef2d61a9",
36
- "size": 2977953
35
+ "sha256": "e160bf95877db7628e7fc78d2c0e263f3221a7d7f51a53f1c5dd008e4920b382",
36
+ "size": 2979620
37
37
  },
38
38
  "parall-openclaw-agent.js": {
39
39
  "sha256": "856a1c3a9ae0ffef8efe2481fa1171dbeb1d67cccb88616aa9bf16da96a69355",
40
40
  "size": 9923
41
41
  }
42
42
  },
43
- "signature": "d11VXm3N6e4KIaGx7lp08AdQA9IE3RNCbcj673a+Wd7u2ibl/5VgBCXAMlOaO9HS3PoCGF1IMh3lsFciaHQ7Ag=="
43
+ "signature": "HsmD3HAuYZGhuFBt8M3hzMsL62hbiAN8oJ4+JX7uUm+/H4IbQJwF20/56hjuC5LIHlD8jVF80c+5fYqhjWm/Dg=="
44
44
  }
@@ -63904,10 +63904,17 @@ var ENDPOINTS = {
63904
63904
  // Per-org agent exec access for one clip (default all agents; PUT human-only)
63905
63905
  ORG_CLIP_EXEC_ACCESS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clips/${clipId}/exec-access`,
63906
63906
  // MCP clip server config (cap:clip-mcp; publisher-org only — cross-org gets
63907
- // 403 MCP_CROSS_ORG_DISABLED on the whole family, reads included)
63907
+ // 403 MCP_CROSS_ORG_DISABLED on the whole family, reads included). The
63908
+ // singular family addresses the org's DEFAULT connection (pre-165
63909
+ // back-compat shim); the plural family is the multi-connection surface —
63910
+ // one config row (credential slot) per connection, addressed by config id.
63908
63911
  ORG_CLIP_MCP_CONFIG: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config`,
63909
63912
  ORG_CLIP_MCP_TOOLS_REFRESH: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/tools/refresh`,
63910
- ORG_CLIP_MCP_OAUTH_DISCONNECT: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/oauth/disconnect`
63913
+ ORG_CLIP_MCP_OAUTH_DISCONNECT: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/oauth/disconnect`,
63914
+ ORG_CLIP_MCP_CONFIGS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs`,
63915
+ ORG_CLIP_MCP_CONFIG_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}`,
63916
+ ORG_CLIP_MCP_TOOLS_REFRESH_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}/tools/refresh`,
63917
+ ORG_CLIP_MCP_OAUTH_DISCONNECT_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}/oauth/disconnect`
63911
63918
  };
63912
63919
 
63913
63920
  // ts/sdk/dist/client.js
@@ -66114,6 +66121,17 @@ var ParallClient = class _ParallClient {
66114
66121
  async deleteClipMCPConfig(orgId, clipId, expectedVersion) {
66115
66122
  return this.request("DELETE", ENDPOINTS.ORG_CLIP_MCP_CONFIG(orgId, clipId), void 0, void 0, false, { headers: { "If-Match": `"${expectedVersion}"` } });
66116
66123
  }
66124
+ /**
66125
+ * List the org's MCP connections for a clip, default first — one row per
66126
+ * connection (credential slot), WITHOUT the tools snapshot (`tool_count`
66127
+ * summarizes it; the per-config GET carries the full snapshot). Same
66128
+ * family-wide fence as the singular GET: publisher-org members only —
66129
+ * cross-org installs get `403 MCP_CROSS_ORG_DISABLED`, and with
66130
+ * `cap:clip-mcp` off the route is not registered at all.
66131
+ */
66132
+ async listClipMCPConnections(orgId, clipId) {
66133
+ return this.request("GET", ENDPOINTS.ORG_CLIP_MCP_CONFIGS(orgId, clipId));
66134
+ }
66117
66135
  /**
66118
66136
  * Re-run tools/list and replace the cached snapshot (org-admin only).
66119
66137
  * Deliberately does NOT advance the CAS version, so an in-flight edit in
@@ -51700,10 +51700,17 @@ var ENDPOINTS = {
51700
51700
  // Per-org agent exec access for one clip (default all agents; PUT human-only)
51701
51701
  ORG_CLIP_EXEC_ACCESS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clips/${clipId}/exec-access`,
51702
51702
  // MCP clip server config (cap:clip-mcp; publisher-org only — cross-org gets
51703
- // 403 MCP_CROSS_ORG_DISABLED on the whole family, reads included)
51703
+ // 403 MCP_CROSS_ORG_DISABLED on the whole family, reads included). The
51704
+ // singular family addresses the org's DEFAULT connection (pre-165
51705
+ // back-compat shim); the plural family is the multi-connection surface —
51706
+ // one config row (credential slot) per connection, addressed by config id.
51704
51707
  ORG_CLIP_MCP_CONFIG: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config`,
51705
51708
  ORG_CLIP_MCP_TOOLS_REFRESH: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/tools/refresh`,
51706
- ORG_CLIP_MCP_OAUTH_DISCONNECT: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/oauth/disconnect`
51709
+ ORG_CLIP_MCP_OAUTH_DISCONNECT: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/oauth/disconnect`,
51710
+ ORG_CLIP_MCP_CONFIGS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs`,
51711
+ ORG_CLIP_MCP_CONFIG_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}`,
51712
+ ORG_CLIP_MCP_TOOLS_REFRESH_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}/tools/refresh`,
51713
+ ORG_CLIP_MCP_OAUTH_DISCONNECT_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}/oauth/disconnect`
51707
51714
  };
51708
51715
  var WS_EVENTS = {
51709
51716
  // Client -> Server
@@ -53996,6 +54003,17 @@ var ParallClient = class _ParallClient {
53996
54003
  async deleteClipMCPConfig(orgId, clipId, expectedVersion) {
53997
54004
  return this.request("DELETE", ENDPOINTS.ORG_CLIP_MCP_CONFIG(orgId, clipId), void 0, void 0, false, { headers: { "If-Match": `"${expectedVersion}"` } });
53998
54005
  }
54006
+ /**
54007
+ * List the org's MCP connections for a clip, default first — one row per
54008
+ * connection (credential slot), WITHOUT the tools snapshot (`tool_count`
54009
+ * summarizes it; the per-config GET carries the full snapshot). Same
54010
+ * family-wide fence as the singular GET: publisher-org members only —
54011
+ * cross-org installs get `403 MCP_CROSS_ORG_DISABLED`, and with
54012
+ * `cap:clip-mcp` off the route is not registered at all.
54013
+ */
54014
+ async listClipMCPConnections(orgId, clipId) {
54015
+ return this.request("GET", ENDPOINTS.ORG_CLIP_MCP_CONFIGS(orgId, clipId));
54016
+ }
53999
54017
  /**
54000
54018
  * Re-run tools/list and replace the cached snapshot (org-admin only).
54001
54019
  * Deliberately does NOT advance the CAS version, so an in-flight edit in
@@ -60492,16 +60510,23 @@ guessing which signed-in account you are about to act through.
60492
60510
  \`\`\`bash
60493
60511
  parall clip exec <clip> <command> [args] --connection <ccn_|alias> # the normal form
60494
60512
  parall clip exec browser-tools screenshot '{"url":"\u2026"}' --connection cloud-main
60513
+ parall clip exec browser-tools scrape --args-file - --connection cloud-main <<'EOF'
60514
+ {"url":"https://example.com","fields":["title","price"]}
60515
+ EOF
60495
60516
  \`\`\`
60496
60517
 
60497
60518
  Build \`args\` as JSON per the command's params from \`clip info\` (or the
60498
60519
  tool's \`inputSchema\` from \`clip tools\` for MCP clips \u2014 tool names are NOT
60499
60520
  frozen in the manifest, so never guess a tool name or its argument shape).
60521
+ Quote-heavy or large JSON goes through \`--args-file <path>\` (\`-\` = stdin
60522
+ via a quoted heredoc, as above; strict JSON, mutually exclusive with the
60523
+ \`[args]\` positional) \u2014 the shell mangles inline quotes long before the CLI
60524
+ sees them, exactly the trap \`messages send --text-file\` exists for.
60500
60525
  Results are JSON on stdout; failures print a typed error.
60501
60526
 
60502
- **Name the target explicitly.** A cloud profile has NO implicit route;
60503
- omitting the target entirely is a desktop-only legacy form that reaches just
60504
- YOUR OWN online desktop device \u2014 never a shared cloud profile.
60527
+ **Name the target explicitly \u2014 every exec.** \`clip exec\` refuses to run
60528
+ without \`--connection\` or \`--edge\`; there is no implicit route to any
60529
+ profile, and the legacy no-flag desktop fallback is gone from the CLI.
60505
60530
 
60506
60531
  - A cloud (hosted) profile is reachable ONLY via \`--connection\` \u2014 the clip
60507
60532
  connection its maintainer bound. That binding IS your authorization;
@@ -60519,21 +60544,39 @@ YOUR OWN online desktop device \u2014 never a shared cloud profile.
60519
60544
  ## MCP clips (remote tool servers)
60520
60545
 
60521
60546
  A connection with target \`mcp\` routes to a remote MCP server; the command
60522
- is an MCP tool name and the args are that tool's JSON arguments. Read each
60523
- tool's \`inputSchema\` from \`clip tools\` first, then exec against the
60524
- explicit target \u2014 the same form as any other clip:
60547
+ is an MCP tool name and the args are that tool's JSON arguments. **An MCP
60548
+ clip can hold SEVERAL connections \u2014 one per account of the same service**
60549
+ (e.g. two Google Drive accounts), exactly like the browser arm's multiple
60550
+ profiles. The flow is: discover the connections, pick by alias, exec with the
60551
+ explicit target:
60525
60552
 
60526
60553
  \`\`\`bash
60554
+ parall clip connections <clip> # per-connection auth status, alias, is_default
60555
+ parall clip tools <clip> --connection <ccn_|alias> # tools are PER CONNECTION
60527
60556
  parall clip exec <clip> <tool> [json-args] --connection <ccn_|alias>
60528
60557
  \`\`\`
60529
60558
 
60559
+ - \`clip connections\` marks MCP rows with their auth mode, \`credential_set\`
60560
+ / \`oauth_status\` and \`tool_count\` \u2014 a \`needs_reauth\` or credential-less
60561
+ row will refuse exec until an org admin fixes it in the Clip Console.
60562
+ - \`--connection\` is REQUIRED to exec an MCP connection, exactly like a cloud
60563
+ profile \u2014 the only other target form, \`--edge\`, is a desktop device and
60564
+ never routes to MCP. The alias tells you WHICH account you act as \u2014
60565
+ always name it.
60566
+ (\`is_default\` marks the org's primary connection in listings and the
60567
+ Console, not an implicit exec route.)
60568
+ - \`clip tools\` without \`--connection\` reads the default connection's
60569
+ snapshot; with several connections and no default it asks you to name one.
60530
60570
  - No cold start: MCP clips never return \`EDGE_ACTIVATING\`.
60531
60571
  - \`MCP_TOOL_FAILED\` = the tool RAN and reported failure; a sanitized summary
60532
60572
  of its output rides in the error details. Read it and decide \u2014 do not
60533
60573
  blind-retry.
60534
60574
  - \`MCP_CONCURRENCY_LIMIT\` = not started; back off briefly, then retry.
60535
- - \`MCP_CONFIG_MISSING\` / \`MCP_DISABLED\` = the clip isn't configured, or MCP
60536
- is off for this deployment \u2014 ask an org admin; retrying won't help.
60575
+ - \`MCP_CONFIG_MISSING\` / \`MCP_DISABLED\` = that connection isn't configured,
60576
+ or MCP is off for this deployment \u2014 ask an org admin; retrying won't help.
60577
+ - \`MCP_OAUTH_REAUTH_REQUIRED\` = that connection's authorization expired or
60578
+ was disconnected \u2014 ask an org admin to re-authorize it in the Clip Console
60579
+ (name the connection's alias); retrying won't help.
60537
60580
  - \`OUTCOME_UNKNOWN\` follows the rule below: dispatched and MAY HAVE
60538
60581
  EXECUTED \u2014 never auto-retry.
60539
60582
 
@@ -60565,6 +60608,275 @@ parall clip exec <clip> <tool> [json-args] --connection <ccn_|alias>
60565
60608
  built-in tools.
60566
60609
  `;
60567
60610
 
60611
+ // ts/agent-core/dist/skills/parall-clip-authoring.js
60612
+ var PARALL_CLIP_AUTHORING_SKILL = `# Authoring a Parall Clip (v3)
60613
+
60614
+ Write a **registry (v3) clip**. This skill is for CREATING clips; to DISCOVER
60615
+ and CALL clips that are already installed, use the Parall Clips skill instead.
60616
+
60617
+ Two kinds of clip exist, and this document covers authoring both:
60618
+
60619
+ - A **browser clip** \u2014 the main subject here \u2014 is a folder of named commands
60620
+ that run on an Edge (a member's desktop or an org-shared cloud profile) and
60621
+ drive a real browser session: one manifest (\`manifest.json\` or \`site.json\`,
60622
+ either name works) + one \`.js\` file per command + optional \`_\`-prefixed
60623
+ helpers. The Edge runs each command's JS in a sandbox with a browser handle
60624
+ bound to the target profile.
60625
+ - An **MCP clip** is a manifest-ONLY declaration pointing at a remote MCP tool
60626
+ server \u2014 no \`.js\` files; its tools come live from that server's own
60627
+ \`tools/list\`, never from the manifest. See "MCP clips" below.
60628
+
60629
+ ## Project layout
60630
+
60631
+ \`\`\`
60632
+ my-clip/
60633
+ \u251C\u2500\u2500 manifest.json # name, description, version, command params
60634
+ \u251C\u2500\u2500 _helpers.js # OPTIONAL \u2014 any _-prefixed file is auto-injected into every command
60635
+ \u251C\u2500\u2500 search.js # one command = one file; filename (minus .js) IS the command name
60636
+ \u2514\u2500\u2500 profile.js # another command
60637
+ \`\`\`
60638
+
60639
+ ## manifest.json
60640
+
60641
+ \`\`\`json
60642
+ {
60643
+ "name": "twitter",
60644
+ "description": "Twitter / X",
60645
+ "version": "1.0.0",
60646
+ "commands": {
60647
+ "search": {
60648
+ "description": "Search tweets",
60649
+ "params": {
60650
+ "query": { "type": "string", "required": true },
60651
+ "count": { "type": "number", "required": false }
60652
+ }
60653
+ },
60654
+ "profile": {
60655
+ "description": "Fetch a user profile",
60656
+ "params": { "handle": { "type": "string", "required": true } }
60657
+ }
60658
+ }
60659
+ }
60660
+ \`\`\`
60661
+
60662
+ - \`commands\` keys MUST match the \`.js\` filenames (\`search\` \u2194 \`search.js\`).
60663
+ - \`params\` is the input contract the caller sees in \`clip info\`; validate them in code too.
60664
+ - **No top-level \`"type"\` needed for a browser clip** \u2014 omitting it means
60665
+ browser. The only accepted values are \`"browser"\` and \`"mcp"\` (for the
60666
+ latter, see "MCP clips" below). **\`"type": "clip"\` is the legacy Pinix v2
60667
+ package value and is refused at publish** \u2014 don't copy it in from an older
60668
+ clip.
60669
+
60670
+ ## A command file
60671
+
60672
+ \`\`\`js
60673
+ // search.js \u2014 a command is a single async function of (args).
60674
+ // The code does NOT know or pick the profile; the Edge binds it per invocation.
60675
+ //
60676
+ // SHAPE, not a runnable Twitter client: the "..." parts (the GraphQL path,
60677
+ // parseTweets' body) are what you fill in per target site. Deliberately not
60678
+ // pinned to a real X endpoint \u2014 a site's internal API paths rotate, and a
60679
+ // stale one baked into this skill would teach a URL that 404s.
60680
+ module.exports = async function (args) {
60681
+ if (!args.query) return { error: "Missing argument: query" };
60682
+
60683
+ const tab = await browser.open("https://x.com");
60684
+ // Everything after open() goes in try/finally: an early return or a thrown
60685
+ // fetch would otherwise leak the tab, and the Edge is long-lived.
60686
+ try {
60687
+ const ct0 = await tab.cookie("ct0");
60688
+ if (!ct0) return { error: "Not logged in" };
60689
+
60690
+ const data = await tab.fetch("/i/api/graphql/.../SearchTimeline?...", {
60691
+ headers: twitterHeaders(ct0), // from _helpers.js, auto-injected
60692
+ });
60693
+ return { query: args.query, tweets: parseTweets(data) };
60694
+ } finally {
60695
+ await tab.close();
60696
+ }
60697
+ };
60698
+ \`\`\`
60699
+
60700
+ Return a plain JSON-serializable object. A thrown error surfaces to the caller as
60701
+ \`SCRIPT_ERROR\`; a returned \`{ error: "..." }\` is your own typed failure \u2014 prefer it
60702
+ for expected cases (not logged in, missing arg).
60703
+
60704
+ ## Helpers (\`_\`-prefixed)
60705
+
60706
+ Any file whose name starts with \`_\` is NOT a command. Its top-level functions are
60707
+ injected into every command's scope \u2014 no import/require needed:
60708
+
60709
+ \`\`\`js
60710
+ // _helpers.js
60711
+ function twitterHeaders(ct0) {
60712
+ return { "X-Csrf-Token": ct0, "X-Twitter-Auth-Type": "OAuth2Session" };
60713
+ }
60714
+ function parseTweets(data) { /* ... */ }
60715
+ \`\`\`
60716
+
60717
+ ## Runtime API (globals available in every command)
60718
+
60719
+ - \`browser.open(url)\` \u2192 tab handle \xB7 \`browser.tabs()\` \u2192 open tabs
60720
+ - \`tab.cookie(name)\` \xB7 \`tab.fetch(url, opts)\` (in-browser fetch, carries the session)
60721
+ - \`tab.eval(expr)\` (escape hatch) \xB7 \`tab.click(sel)\` \xB7 \`tab.fill(sel, text)\` \xB7 \`tab.navigate(url)\`
60722
+ - \`tab.waitForSelector(sel)\` \xB7 \`tab.getTitle()\` \xB7 \`tab.getURL()\` \xB7 \`tab.screenshot()\` \xB7 \`tab.close()\`
60723
+ - \`fetch\` \u2014 runtime-side HTTP, does NOT go through the browser (no session)
60724
+ - \`console\` \u2014 logs \xB7 \`args\` \u2014 the invocation input
60725
+
60726
+ **Prefer \`tab.fetch\` over \`tab.eval\`**: fetch reuses the logged-in session and
60727
+ returns structured data; eval is the last resort. Always \`tab.close()\` what you
60728
+ open, and do it in a \`finally\` \u2014 an early return or a thrown fetch is exactly
60729
+ when the tab leaks.
60730
+
60731
+ ## Develop \u2192 publish \u2192 iterate
60732
+
60733
+ Use the platform \`parall clip\` subcommands \u2014 they reuse the credentials you
60734
+ already have (\`PRLL_API_KEY\` / \`PRLL_ORG_ID\`), so there is nothing to install
60735
+ or configure.
60736
+
60737
+ > A separate **standalone \`parall-clip\`** binary also exists (the Edge-side
60738
+ > authoring tool). It takes the SAME operations but a DIFFERENT argument shape \u2014
60739
+ > \`parall-clip exec <clip> <cmd> --query "AI" --count 10\` passes one flag per
60740
+ > param, while \`parall clip exec\` takes a single JSON blob. Do not mix the two
60741
+ > forms; everything below is the platform CLI.
60742
+
60743
+ 1. **Publish** the clip directory to the org registry. Publishing is not a
60744
+ release step here \u2014 it is the edit loop's SAVE button, because exec only
60745
+ ever sees published files:
60746
+
60747
+ \`\`\`sh
60748
+ parall clip publish ./my-clip/
60749
+ \`\`\`
60750
+
60751
+ It packages the directory (the manifest plus the directory's top-level
60752
+ \`.js\` files; an MCP clip is manifest-only) and POSTs it for you (5 MB cap).
60753
+ \`name\` is the org-wide upsert key \u2014 manifest fields win, else the directory
60754
+ name / \`0.0.1\` / \`private\` fill the gaps. Re-publishing an existing name is
60755
+ **author-only** and REPLACES the file set. Publishing into YOUR org makes it
60756
+ usable there immediately (same-org self-reference, no review);
60757
+ \`"visibility": "public"\` additionally submits the version for platform
60758
+ review before it can spread cross-org.
60759
+
60760
+ Programmatic equivalent (what \`publish\` calls under the hood \u2014 use only if
60761
+ you can't run the CLI). Send it verbatim-shaped: \`visibility\` is exactly
60762
+ one of \`"private"\` / \`"public"\`, and \`files\` maps each filename to its
60763
+ source as a string:
60764
+
60765
+ \`\`\`
60766
+ POST /api/v1/orgs/{orgId}/clip-registry/publish
60767
+ {
60768
+ "name": "twitter",
60769
+ "description": "Twitter / X",
60770
+ "version": "1.0.0",
60771
+ "visibility": "private",
60772
+ "manifest": {
60773
+ "name": "twitter",
60774
+ "version": "1.0.0",
60775
+ "commands": { "search": { "description": "Search tweets" } }
60776
+ },
60777
+ "files": { "search.js": "module.exports = async function (args) { return {}; };" }
60778
+ }
60779
+ \`\`\`
60780
+
60781
+ 2. **Exec** a command against a real target. Args are ONE argument \u2014 a JSON
60782
+ string (or plain text for a single-value command), not per-param flags:
60783
+
60784
+ \`\`\`sh
60785
+ parall clip exec <clip> <command> '{"query":"AI","count":10}' --connection <ccn_id|alias>
60786
+ # or route to a desktop (BYOC) device you own: --edge <edge-id>
60787
+ # --connection and --edge are mutually exclusive; --timeout <ms> defaults to 30000
60788
+ \`\`\`
60789
+
60790
+ A **cloud (hosted) profile is reachable ONLY via \`--connection\`** \u2014 the
60791
+ binding its maintainer created IS the authorization. With neither flag the
60792
+ server resolves only your own online desktop device, never a cloud profile.
60793
+ Discover the bindings with \`parall clip connections <clip>\`.
60794
+
60795
+ 3. **Iterate**: edit locally \u2192 \`parall clip publish\` again \u2192 re-exec. Exec
60796
+ resolves the file set from the REGISTRY, server-side \u2014 your own org always
60797
+ runs the live working copy, i.e. the latest publish. It NEVER reads your
60798
+ local directory: an edit you did not re-publish silently runs the previous
60799
+ version.
60800
+
60801
+ ## Install model & self-development (v3)
60802
+
60803
+ - Installing a clip is a **reference**, not a copy \u2014 the JS lives once in the Market DB.
60804
+ - **Your own org** always executes its **live working copy** (latest published files),
60805
+ so re-publishing is your edit loop.
60806
+ - **Other orgs** installing your \`public\` clip execute only the **approved snapshot**
60807
+ (\`approved_version_id\`); unreviewed public edits are invisible/unexecutable cross-org.
60808
+ - To customize someone else's public clip: install \u2192 **fetch its effective
60809
+ file set** \u2192 modify \u2192 **publish into your OWN org** (a derived private
60810
+ entry). You cannot edit a published clip in place. The CLI has no files
60811
+ subcommand (\`clip info\` returns only the manifest) \u2014 read the source via
60812
+ the API:
60813
+
60814
+ \`\`\`
60815
+ GET /api/v1/orgs/{orgId}/clip-registry/{clipId}/files
60816
+ \`\`\`
60817
+
60818
+ It returns exactly what you may read and execute: your own clip \u2192 the live
60819
+ working copy; an installed public clip \u2192 the approved snapshot.
60820
+
60821
+ ## MCP clips (manifest-only)
60822
+
60823
+ An MCP clip declares a remote MCP tool server. There is nothing to code: no
60824
+ \`.js\` files (the no-scripts refusal at publish applies to browser clips only),
60825
+ and the folder is just a manifest:
60826
+
60827
+ \`\`\`json
60828
+ {
60829
+ "name": "linear",
60830
+ "description": "Linear (MCP)",
60831
+ "version": "1.0.0",
60832
+ "type": "mcp",
60833
+ "mcp": { "server_url": "https://mcp.linear.app/mcp", "auth": "oauth" }
60834
+ }
60835
+ \`\`\`
60836
+
60837
+ - The \`mcp\` block takes ONLY \`server_url\` and \`auth\` (\`"none" | "bearer" |
60838
+ "api_key" | "oauth"\`). Any other key is refused at publish \u2014 a credential
60839
+ belongs to the installing org's own configuration, NEVER to the clip
60840
+ definition.
60841
+ - \`server_url\` must be an absolute **https** URL with no embedded credentials,
60842
+ query, or fragment. It is review material, frozen with the approved version.
60843
+ - Do NOT put the server in the top-level \`server\` / \`auth\` manifest keys \u2014
60844
+ those are legacy Edge-manifest fields nothing reads. Only the \`mcp\` block
60845
+ declares the server.
60846
+ - Both fields are optional, but what you declare is LOCKED: the installing
60847
+ org's config must match it, and changing the URL or auth mode means
60848
+ republishing.
60849
+ - Entering the credential / completing OAuth is a HUMAN step in the Clip
60850
+ Console (the config-write endpoints are session-only \u2014 an API key cannot
60851
+ call them). An org can add SEVERAL connections to one MCP clip \u2014 one
60852
+ credential slot per account of the same service \u2014 all sharing the declared
60853
+ \`server_url\`/\`auth\`. Once configured, discover the live tool schemas with
60854
+ \`parall clip tools <clip> [--connection <ref>]\` and exec like any other
60855
+ clip (\`--connection\` picks the account).
60856
+ - Do NOT declare anything about OAuth client registration in the manifest \u2014
60857
+ there is no such field. Whether the provider needs a manually registered
60858
+ OAuth app (Google-style) is probed by the server at connect time; the
60859
+ Console walks the admin through it when required.
60860
+ - Publish is the same command: \`parall clip publish ./my-clip/\`.
60861
+
60862
+ ## Cloud (hosted) vs desktop (BYOC) Edge
60863
+
60864
+ The same command JS runs on either. Hosted profiles are org-shared cloud browsers
60865
+ reachable ONLY via an explicit \`--connection\`; cloud state lives in S3 and is
60866
+ hydrated per pod. Your code never touches this \u2014 it just gets a \`browser\`/\`tab\`
60867
+ bound to whatever profile the connection selected.
60868
+
60869
+ ## Constraints
60870
+
60871
+ - Browser clips: one command = one file; keep a command's work self-contained
60872
+ (open what you need, close it, return). The Edge is stateless about your code
60873
+ between calls.
60874
+ - Never embed credentials in the clip source \u2014 rely on the profile's logged-in
60875
+ session (\`tab.cookie\` / \`tab.fetch\`). Published source is visible to installers.
60876
+ - Exec has a timeout (default 30s, caller-set up to 120s). Long scrapes should page,
60877
+ not block.
60878
+ `;
60879
+
60568
60880
  // ts/agent-core/dist/skills/index.js
60569
60881
  var SKILLS = [
60570
60882
  {
@@ -60594,8 +60906,13 @@ var SKILLS = [
60594
60906
  },
60595
60907
  {
60596
60908
  name: "parall-clips",
60597
- 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.",
60909
+ 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.",
60598
60910
  content: PARALL_CLIPS_SKILL
60911
+ },
60912
+ {
60913
+ name: "parall-clip-authoring",
60914
+ 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` \u2014 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.",
60915
+ content: PARALL_CLIP_AUTHORING_SKILL
60599
60916
  }
60600
60917
  ];
60601
60918
  function writeSkillFiles(targetDir) {
@@ -51700,10 +51700,17 @@ var ENDPOINTS = {
51700
51700
  // Per-org agent exec access for one clip (default all agents; PUT human-only)
51701
51701
  ORG_CLIP_EXEC_ACCESS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clips/${clipId}/exec-access`,
51702
51702
  // MCP clip server config (cap:clip-mcp; publisher-org only — cross-org gets
51703
- // 403 MCP_CROSS_ORG_DISABLED on the whole family, reads included)
51703
+ // 403 MCP_CROSS_ORG_DISABLED on the whole family, reads included). The
51704
+ // singular family addresses the org's DEFAULT connection (pre-165
51705
+ // back-compat shim); the plural family is the multi-connection surface —
51706
+ // one config row (credential slot) per connection, addressed by config id.
51704
51707
  ORG_CLIP_MCP_CONFIG: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config`,
51705
51708
  ORG_CLIP_MCP_TOOLS_REFRESH: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/tools/refresh`,
51706
- ORG_CLIP_MCP_OAUTH_DISCONNECT: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/oauth/disconnect`
51709
+ ORG_CLIP_MCP_OAUTH_DISCONNECT: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/oauth/disconnect`,
51710
+ ORG_CLIP_MCP_CONFIGS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs`,
51711
+ ORG_CLIP_MCP_CONFIG_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}`,
51712
+ ORG_CLIP_MCP_TOOLS_REFRESH_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}/tools/refresh`,
51713
+ ORG_CLIP_MCP_OAUTH_DISCONNECT_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}/oauth/disconnect`
51707
51714
  };
51708
51715
  var WS_EVENTS = {
51709
51716
  // Client -> Server
@@ -53996,6 +54003,17 @@ var ParallClient = class _ParallClient {
53996
54003
  async deleteClipMCPConfig(orgId, clipId, expectedVersion) {
53997
54004
  return this.request("DELETE", ENDPOINTS.ORG_CLIP_MCP_CONFIG(orgId, clipId), void 0, void 0, false, { headers: { "If-Match": `"${expectedVersion}"` } });
53998
54005
  }
54006
+ /**
54007
+ * List the org's MCP connections for a clip, default first — one row per
54008
+ * connection (credential slot), WITHOUT the tools snapshot (`tool_count`
54009
+ * summarizes it; the per-config GET carries the full snapshot). Same
54010
+ * family-wide fence as the singular GET: publisher-org members only —
54011
+ * cross-org installs get `403 MCP_CROSS_ORG_DISABLED`, and with
54012
+ * `cap:clip-mcp` off the route is not registered at all.
54013
+ */
54014
+ async listClipMCPConnections(orgId, clipId) {
54015
+ return this.request("GET", ENDPOINTS.ORG_CLIP_MCP_CONFIGS(orgId, clipId));
54016
+ }
53999
54017
  /**
54000
54018
  * Re-run tools/list and replace the cached snapshot (org-admin only).
54001
54019
  * Deliberately does NOT advance the CAS version, so an in-flight edit in
@@ -60492,16 +60510,23 @@ guessing which signed-in account you are about to act through.
60492
60510
  \`\`\`bash
60493
60511
  parall clip exec <clip> <command> [args] --connection <ccn_|alias> # the normal form
60494
60512
  parall clip exec browser-tools screenshot '{"url":"\u2026"}' --connection cloud-main
60513
+ parall clip exec browser-tools scrape --args-file - --connection cloud-main <<'EOF'
60514
+ {"url":"https://example.com","fields":["title","price"]}
60515
+ EOF
60495
60516
  \`\`\`
60496
60517
 
60497
60518
  Build \`args\` as JSON per the command's params from \`clip info\` (or the
60498
60519
  tool's \`inputSchema\` from \`clip tools\` for MCP clips \u2014 tool names are NOT
60499
60520
  frozen in the manifest, so never guess a tool name or its argument shape).
60521
+ Quote-heavy or large JSON goes through \`--args-file <path>\` (\`-\` = stdin
60522
+ via a quoted heredoc, as above; strict JSON, mutually exclusive with the
60523
+ \`[args]\` positional) \u2014 the shell mangles inline quotes long before the CLI
60524
+ sees them, exactly the trap \`messages send --text-file\` exists for.
60500
60525
  Results are JSON on stdout; failures print a typed error.
60501
60526
 
60502
- **Name the target explicitly.** A cloud profile has NO implicit route;
60503
- omitting the target entirely is a desktop-only legacy form that reaches just
60504
- YOUR OWN online desktop device \u2014 never a shared cloud profile.
60527
+ **Name the target explicitly \u2014 every exec.** \`clip exec\` refuses to run
60528
+ without \`--connection\` or \`--edge\`; there is no implicit route to any
60529
+ profile, and the legacy no-flag desktop fallback is gone from the CLI.
60505
60530
 
60506
60531
  - A cloud (hosted) profile is reachable ONLY via \`--connection\` \u2014 the clip
60507
60532
  connection its maintainer bound. That binding IS your authorization;
@@ -60519,21 +60544,39 @@ YOUR OWN online desktop device \u2014 never a shared cloud profile.
60519
60544
  ## MCP clips (remote tool servers)
60520
60545
 
60521
60546
  A connection with target \`mcp\` routes to a remote MCP server; the command
60522
- is an MCP tool name and the args are that tool's JSON arguments. Read each
60523
- tool's \`inputSchema\` from \`clip tools\` first, then exec against the
60524
- explicit target \u2014 the same form as any other clip:
60547
+ is an MCP tool name and the args are that tool's JSON arguments. **An MCP
60548
+ clip can hold SEVERAL connections \u2014 one per account of the same service**
60549
+ (e.g. two Google Drive accounts), exactly like the browser arm's multiple
60550
+ profiles. The flow is: discover the connections, pick by alias, exec with the
60551
+ explicit target:
60525
60552
 
60526
60553
  \`\`\`bash
60554
+ parall clip connections <clip> # per-connection auth status, alias, is_default
60555
+ parall clip tools <clip> --connection <ccn_|alias> # tools are PER CONNECTION
60527
60556
  parall clip exec <clip> <tool> [json-args] --connection <ccn_|alias>
60528
60557
  \`\`\`
60529
60558
 
60559
+ - \`clip connections\` marks MCP rows with their auth mode, \`credential_set\`
60560
+ / \`oauth_status\` and \`tool_count\` \u2014 a \`needs_reauth\` or credential-less
60561
+ row will refuse exec until an org admin fixes it in the Clip Console.
60562
+ - \`--connection\` is REQUIRED to exec an MCP connection, exactly like a cloud
60563
+ profile \u2014 the only other target form, \`--edge\`, is a desktop device and
60564
+ never routes to MCP. The alias tells you WHICH account you act as \u2014
60565
+ always name it.
60566
+ (\`is_default\` marks the org's primary connection in listings and the
60567
+ Console, not an implicit exec route.)
60568
+ - \`clip tools\` without \`--connection\` reads the default connection's
60569
+ snapshot; with several connections and no default it asks you to name one.
60530
60570
  - No cold start: MCP clips never return \`EDGE_ACTIVATING\`.
60531
60571
  - \`MCP_TOOL_FAILED\` = the tool RAN and reported failure; a sanitized summary
60532
60572
  of its output rides in the error details. Read it and decide \u2014 do not
60533
60573
  blind-retry.
60534
60574
  - \`MCP_CONCURRENCY_LIMIT\` = not started; back off briefly, then retry.
60535
- - \`MCP_CONFIG_MISSING\` / \`MCP_DISABLED\` = the clip isn't configured, or MCP
60536
- is off for this deployment \u2014 ask an org admin; retrying won't help.
60575
+ - \`MCP_CONFIG_MISSING\` / \`MCP_DISABLED\` = that connection isn't configured,
60576
+ or MCP is off for this deployment \u2014 ask an org admin; retrying won't help.
60577
+ - \`MCP_OAUTH_REAUTH_REQUIRED\` = that connection's authorization expired or
60578
+ was disconnected \u2014 ask an org admin to re-authorize it in the Clip Console
60579
+ (name the connection's alias); retrying won't help.
60537
60580
  - \`OUTCOME_UNKNOWN\` follows the rule below: dispatched and MAY HAVE
60538
60581
  EXECUTED \u2014 never auto-retry.
60539
60582
 
@@ -60565,6 +60608,275 @@ parall clip exec <clip> <tool> [json-args] --connection <ccn_|alias>
60565
60608
  built-in tools.
60566
60609
  `;
60567
60610
 
60611
+ // ts/agent-core/dist/skills/parall-clip-authoring.js
60612
+ var PARALL_CLIP_AUTHORING_SKILL = `# Authoring a Parall Clip (v3)
60613
+
60614
+ Write a **registry (v3) clip**. This skill is for CREATING clips; to DISCOVER
60615
+ and CALL clips that are already installed, use the Parall Clips skill instead.
60616
+
60617
+ Two kinds of clip exist, and this document covers authoring both:
60618
+
60619
+ - A **browser clip** \u2014 the main subject here \u2014 is a folder of named commands
60620
+ that run on an Edge (a member's desktop or an org-shared cloud profile) and
60621
+ drive a real browser session: one manifest (\`manifest.json\` or \`site.json\`,
60622
+ either name works) + one \`.js\` file per command + optional \`_\`-prefixed
60623
+ helpers. The Edge runs each command's JS in a sandbox with a browser handle
60624
+ bound to the target profile.
60625
+ - An **MCP clip** is a manifest-ONLY declaration pointing at a remote MCP tool
60626
+ server \u2014 no \`.js\` files; its tools come live from that server's own
60627
+ \`tools/list\`, never from the manifest. See "MCP clips" below.
60628
+
60629
+ ## Project layout
60630
+
60631
+ \`\`\`
60632
+ my-clip/
60633
+ \u251C\u2500\u2500 manifest.json # name, description, version, command params
60634
+ \u251C\u2500\u2500 _helpers.js # OPTIONAL \u2014 any _-prefixed file is auto-injected into every command
60635
+ \u251C\u2500\u2500 search.js # one command = one file; filename (minus .js) IS the command name
60636
+ \u2514\u2500\u2500 profile.js # another command
60637
+ \`\`\`
60638
+
60639
+ ## manifest.json
60640
+
60641
+ \`\`\`json
60642
+ {
60643
+ "name": "twitter",
60644
+ "description": "Twitter / X",
60645
+ "version": "1.0.0",
60646
+ "commands": {
60647
+ "search": {
60648
+ "description": "Search tweets",
60649
+ "params": {
60650
+ "query": { "type": "string", "required": true },
60651
+ "count": { "type": "number", "required": false }
60652
+ }
60653
+ },
60654
+ "profile": {
60655
+ "description": "Fetch a user profile",
60656
+ "params": { "handle": { "type": "string", "required": true } }
60657
+ }
60658
+ }
60659
+ }
60660
+ \`\`\`
60661
+
60662
+ - \`commands\` keys MUST match the \`.js\` filenames (\`search\` \u2194 \`search.js\`).
60663
+ - \`params\` is the input contract the caller sees in \`clip info\`; validate them in code too.
60664
+ - **No top-level \`"type"\` needed for a browser clip** \u2014 omitting it means
60665
+ browser. The only accepted values are \`"browser"\` and \`"mcp"\` (for the
60666
+ latter, see "MCP clips" below). **\`"type": "clip"\` is the legacy Pinix v2
60667
+ package value and is refused at publish** \u2014 don't copy it in from an older
60668
+ clip.
60669
+
60670
+ ## A command file
60671
+
60672
+ \`\`\`js
60673
+ // search.js \u2014 a command is a single async function of (args).
60674
+ // The code does NOT know or pick the profile; the Edge binds it per invocation.
60675
+ //
60676
+ // SHAPE, not a runnable Twitter client: the "..." parts (the GraphQL path,
60677
+ // parseTweets' body) are what you fill in per target site. Deliberately not
60678
+ // pinned to a real X endpoint \u2014 a site's internal API paths rotate, and a
60679
+ // stale one baked into this skill would teach a URL that 404s.
60680
+ module.exports = async function (args) {
60681
+ if (!args.query) return { error: "Missing argument: query" };
60682
+
60683
+ const tab = await browser.open("https://x.com");
60684
+ // Everything after open() goes in try/finally: an early return or a thrown
60685
+ // fetch would otherwise leak the tab, and the Edge is long-lived.
60686
+ try {
60687
+ const ct0 = await tab.cookie("ct0");
60688
+ if (!ct0) return { error: "Not logged in" };
60689
+
60690
+ const data = await tab.fetch("/i/api/graphql/.../SearchTimeline?...", {
60691
+ headers: twitterHeaders(ct0), // from _helpers.js, auto-injected
60692
+ });
60693
+ return { query: args.query, tweets: parseTweets(data) };
60694
+ } finally {
60695
+ await tab.close();
60696
+ }
60697
+ };
60698
+ \`\`\`
60699
+
60700
+ Return a plain JSON-serializable object. A thrown error surfaces to the caller as
60701
+ \`SCRIPT_ERROR\`; a returned \`{ error: "..." }\` is your own typed failure \u2014 prefer it
60702
+ for expected cases (not logged in, missing arg).
60703
+
60704
+ ## Helpers (\`_\`-prefixed)
60705
+
60706
+ Any file whose name starts with \`_\` is NOT a command. Its top-level functions are
60707
+ injected into every command's scope \u2014 no import/require needed:
60708
+
60709
+ \`\`\`js
60710
+ // _helpers.js
60711
+ function twitterHeaders(ct0) {
60712
+ return { "X-Csrf-Token": ct0, "X-Twitter-Auth-Type": "OAuth2Session" };
60713
+ }
60714
+ function parseTweets(data) { /* ... */ }
60715
+ \`\`\`
60716
+
60717
+ ## Runtime API (globals available in every command)
60718
+
60719
+ - \`browser.open(url)\` \u2192 tab handle \xB7 \`browser.tabs()\` \u2192 open tabs
60720
+ - \`tab.cookie(name)\` \xB7 \`tab.fetch(url, opts)\` (in-browser fetch, carries the session)
60721
+ - \`tab.eval(expr)\` (escape hatch) \xB7 \`tab.click(sel)\` \xB7 \`tab.fill(sel, text)\` \xB7 \`tab.navigate(url)\`
60722
+ - \`tab.waitForSelector(sel)\` \xB7 \`tab.getTitle()\` \xB7 \`tab.getURL()\` \xB7 \`tab.screenshot()\` \xB7 \`tab.close()\`
60723
+ - \`fetch\` \u2014 runtime-side HTTP, does NOT go through the browser (no session)
60724
+ - \`console\` \u2014 logs \xB7 \`args\` \u2014 the invocation input
60725
+
60726
+ **Prefer \`tab.fetch\` over \`tab.eval\`**: fetch reuses the logged-in session and
60727
+ returns structured data; eval is the last resort. Always \`tab.close()\` what you
60728
+ open, and do it in a \`finally\` \u2014 an early return or a thrown fetch is exactly
60729
+ when the tab leaks.
60730
+
60731
+ ## Develop \u2192 publish \u2192 iterate
60732
+
60733
+ Use the platform \`parall clip\` subcommands \u2014 they reuse the credentials you
60734
+ already have (\`PRLL_API_KEY\` / \`PRLL_ORG_ID\`), so there is nothing to install
60735
+ or configure.
60736
+
60737
+ > A separate **standalone \`parall-clip\`** binary also exists (the Edge-side
60738
+ > authoring tool). It takes the SAME operations but a DIFFERENT argument shape \u2014
60739
+ > \`parall-clip exec <clip> <cmd> --query "AI" --count 10\` passes one flag per
60740
+ > param, while \`parall clip exec\` takes a single JSON blob. Do not mix the two
60741
+ > forms; everything below is the platform CLI.
60742
+
60743
+ 1. **Publish** the clip directory to the org registry. Publishing is not a
60744
+ release step here \u2014 it is the edit loop's SAVE button, because exec only
60745
+ ever sees published files:
60746
+
60747
+ \`\`\`sh
60748
+ parall clip publish ./my-clip/
60749
+ \`\`\`
60750
+
60751
+ It packages the directory (the manifest plus the directory's top-level
60752
+ \`.js\` files; an MCP clip is manifest-only) and POSTs it for you (5 MB cap).
60753
+ \`name\` is the org-wide upsert key \u2014 manifest fields win, else the directory
60754
+ name / \`0.0.1\` / \`private\` fill the gaps. Re-publishing an existing name is
60755
+ **author-only** and REPLACES the file set. Publishing into YOUR org makes it
60756
+ usable there immediately (same-org self-reference, no review);
60757
+ \`"visibility": "public"\` additionally submits the version for platform
60758
+ review before it can spread cross-org.
60759
+
60760
+ Programmatic equivalent (what \`publish\` calls under the hood \u2014 use only if
60761
+ you can't run the CLI). Send it verbatim-shaped: \`visibility\` is exactly
60762
+ one of \`"private"\` / \`"public"\`, and \`files\` maps each filename to its
60763
+ source as a string:
60764
+
60765
+ \`\`\`
60766
+ POST /api/v1/orgs/{orgId}/clip-registry/publish
60767
+ {
60768
+ "name": "twitter",
60769
+ "description": "Twitter / X",
60770
+ "version": "1.0.0",
60771
+ "visibility": "private",
60772
+ "manifest": {
60773
+ "name": "twitter",
60774
+ "version": "1.0.0",
60775
+ "commands": { "search": { "description": "Search tweets" } }
60776
+ },
60777
+ "files": { "search.js": "module.exports = async function (args) { return {}; };" }
60778
+ }
60779
+ \`\`\`
60780
+
60781
+ 2. **Exec** a command against a real target. Args are ONE argument \u2014 a JSON
60782
+ string (or plain text for a single-value command), not per-param flags:
60783
+
60784
+ \`\`\`sh
60785
+ parall clip exec <clip> <command> '{"query":"AI","count":10}' --connection <ccn_id|alias>
60786
+ # or route to a desktop (BYOC) device you own: --edge <edge-id>
60787
+ # --connection and --edge are mutually exclusive; --timeout <ms> defaults to 30000
60788
+ \`\`\`
60789
+
60790
+ A **cloud (hosted) profile is reachable ONLY via \`--connection\`** \u2014 the
60791
+ binding its maintainer created IS the authorization. With neither flag the
60792
+ server resolves only your own online desktop device, never a cloud profile.
60793
+ Discover the bindings with \`parall clip connections <clip>\`.
60794
+
60795
+ 3. **Iterate**: edit locally \u2192 \`parall clip publish\` again \u2192 re-exec. Exec
60796
+ resolves the file set from the REGISTRY, server-side \u2014 your own org always
60797
+ runs the live working copy, i.e. the latest publish. It NEVER reads your
60798
+ local directory: an edit you did not re-publish silently runs the previous
60799
+ version.
60800
+
60801
+ ## Install model & self-development (v3)
60802
+
60803
+ - Installing a clip is a **reference**, not a copy \u2014 the JS lives once in the Market DB.
60804
+ - **Your own org** always executes its **live working copy** (latest published files),
60805
+ so re-publishing is your edit loop.
60806
+ - **Other orgs** installing your \`public\` clip execute only the **approved snapshot**
60807
+ (\`approved_version_id\`); unreviewed public edits are invisible/unexecutable cross-org.
60808
+ - To customize someone else's public clip: install \u2192 **fetch its effective
60809
+ file set** \u2192 modify \u2192 **publish into your OWN org** (a derived private
60810
+ entry). You cannot edit a published clip in place. The CLI has no files
60811
+ subcommand (\`clip info\` returns only the manifest) \u2014 read the source via
60812
+ the API:
60813
+
60814
+ \`\`\`
60815
+ GET /api/v1/orgs/{orgId}/clip-registry/{clipId}/files
60816
+ \`\`\`
60817
+
60818
+ It returns exactly what you may read and execute: your own clip \u2192 the live
60819
+ working copy; an installed public clip \u2192 the approved snapshot.
60820
+
60821
+ ## MCP clips (manifest-only)
60822
+
60823
+ An MCP clip declares a remote MCP tool server. There is nothing to code: no
60824
+ \`.js\` files (the no-scripts refusal at publish applies to browser clips only),
60825
+ and the folder is just a manifest:
60826
+
60827
+ \`\`\`json
60828
+ {
60829
+ "name": "linear",
60830
+ "description": "Linear (MCP)",
60831
+ "version": "1.0.0",
60832
+ "type": "mcp",
60833
+ "mcp": { "server_url": "https://mcp.linear.app/mcp", "auth": "oauth" }
60834
+ }
60835
+ \`\`\`
60836
+
60837
+ - The \`mcp\` block takes ONLY \`server_url\` and \`auth\` (\`"none" | "bearer" |
60838
+ "api_key" | "oauth"\`). Any other key is refused at publish \u2014 a credential
60839
+ belongs to the installing org's own configuration, NEVER to the clip
60840
+ definition.
60841
+ - \`server_url\` must be an absolute **https** URL with no embedded credentials,
60842
+ query, or fragment. It is review material, frozen with the approved version.
60843
+ - Do NOT put the server in the top-level \`server\` / \`auth\` manifest keys \u2014
60844
+ those are legacy Edge-manifest fields nothing reads. Only the \`mcp\` block
60845
+ declares the server.
60846
+ - Both fields are optional, but what you declare is LOCKED: the installing
60847
+ org's config must match it, and changing the URL or auth mode means
60848
+ republishing.
60849
+ - Entering the credential / completing OAuth is a HUMAN step in the Clip
60850
+ Console (the config-write endpoints are session-only \u2014 an API key cannot
60851
+ call them). An org can add SEVERAL connections to one MCP clip \u2014 one
60852
+ credential slot per account of the same service \u2014 all sharing the declared
60853
+ \`server_url\`/\`auth\`. Once configured, discover the live tool schemas with
60854
+ \`parall clip tools <clip> [--connection <ref>]\` and exec like any other
60855
+ clip (\`--connection\` picks the account).
60856
+ - Do NOT declare anything about OAuth client registration in the manifest \u2014
60857
+ there is no such field. Whether the provider needs a manually registered
60858
+ OAuth app (Google-style) is probed by the server at connect time; the
60859
+ Console walks the admin through it when required.
60860
+ - Publish is the same command: \`parall clip publish ./my-clip/\`.
60861
+
60862
+ ## Cloud (hosted) vs desktop (BYOC) Edge
60863
+
60864
+ The same command JS runs on either. Hosted profiles are org-shared cloud browsers
60865
+ reachable ONLY via an explicit \`--connection\`; cloud state lives in S3 and is
60866
+ hydrated per pod. Your code never touches this \u2014 it just gets a \`browser\`/\`tab\`
60867
+ bound to whatever profile the connection selected.
60868
+
60869
+ ## Constraints
60870
+
60871
+ - Browser clips: one command = one file; keep a command's work self-contained
60872
+ (open what you need, close it, return). The Edge is stateless about your code
60873
+ between calls.
60874
+ - Never embed credentials in the clip source \u2014 rely on the profile's logged-in
60875
+ session (\`tab.cookie\` / \`tab.fetch\`). Published source is visible to installers.
60876
+ - Exec has a timeout (default 30s, caller-set up to 120s). Long scrapes should page,
60877
+ not block.
60878
+ `;
60879
+
60568
60880
  // ts/agent-core/dist/skills/index.js
60569
60881
  var SKILLS = [
60570
60882
  {
@@ -60594,8 +60906,13 @@ var SKILLS = [
60594
60906
  },
60595
60907
  {
60596
60908
  name: "parall-clips",
60597
- 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.",
60909
+ 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.",
60598
60910
  content: PARALL_CLIPS_SKILL
60911
+ },
60912
+ {
60913
+ name: "parall-clip-authoring",
60914
+ 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` \u2014 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.",
60915
+ content: PARALL_CLIP_AUTHORING_SKILL
60599
60916
  }
60600
60917
  ];
60601
60918
  function writeSkillFiles(targetDir) {
@@ -542,10 +542,17 @@ var init_constants = __esm({
542
542
  // Per-org agent exec access for one clip (default all agents; PUT human-only)
543
543
  ORG_CLIP_EXEC_ACCESS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clips/${clipId}/exec-access`,
544
544
  // MCP clip server config (cap:clip-mcp; publisher-org only — cross-org gets
545
- // 403 MCP_CROSS_ORG_DISABLED on the whole family, reads included)
545
+ // 403 MCP_CROSS_ORG_DISABLED on the whole family, reads included). The
546
+ // singular family addresses the org's DEFAULT connection (pre-165
547
+ // back-compat shim); the plural family is the multi-connection surface —
548
+ // one config row (credential slot) per connection, addressed by config id.
546
549
  ORG_CLIP_MCP_CONFIG: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config`,
547
550
  ORG_CLIP_MCP_TOOLS_REFRESH: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/tools/refresh`,
548
- ORG_CLIP_MCP_OAUTH_DISCONNECT: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/oauth/disconnect`
551
+ ORG_CLIP_MCP_OAUTH_DISCONNECT: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/oauth/disconnect`,
552
+ ORG_CLIP_MCP_CONFIGS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs`,
553
+ ORG_CLIP_MCP_CONFIG_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}`,
554
+ ORG_CLIP_MCP_TOOLS_REFRESH_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}/tools/refresh`,
555
+ ORG_CLIP_MCP_OAUTH_DISCONNECT_BY_ID: (orgId, clipId, configId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-configs/${configId}/oauth/disconnect`
549
556
  };
550
557
  WS_EVENTS = {
551
558
  // Client -> Server
@@ -2883,6 +2890,17 @@ var init_client = __esm({
2883
2890
  async deleteClipMCPConfig(orgId, clipId, expectedVersion) {
2884
2891
  return this.request("DELETE", ENDPOINTS.ORG_CLIP_MCP_CONFIG(orgId, clipId), void 0, void 0, false, { headers: { "If-Match": `"${expectedVersion}"` } });
2885
2892
  }
2893
+ /**
2894
+ * List the org's MCP connections for a clip, default first — one row per
2895
+ * connection (credential slot), WITHOUT the tools snapshot (`tool_count`
2896
+ * summarizes it; the per-config GET carries the full snapshot). Same
2897
+ * family-wide fence as the singular GET: publisher-org members only —
2898
+ * cross-org installs get `403 MCP_CROSS_ORG_DISABLED`, and with
2899
+ * `cap:clip-mcp` off the route is not registered at all.
2900
+ */
2901
+ async listClipMCPConnections(orgId, clipId) {
2902
+ return this.request("GET", ENDPOINTS.ORG_CLIP_MCP_CONFIGS(orgId, clipId));
2903
+ }
2886
2904
  /**
2887
2905
  * Re-run tools/list and replace the cached snapshot (org-admin only).
2888
2906
  * Deliberately does NOT advance the CAS version, so an in-flight edit in
@@ -54849,6 +54867,13 @@ var init_parall_clips = __esm({
54849
54867
  }
54850
54868
  });
54851
54869
 
54870
+ // ts/agent-core/dist/skills/parall-clip-authoring.js
54871
+ var init_parall_clip_authoring = __esm({
54872
+ "ts/agent-core/dist/skills/parall-clip-authoring.js"() {
54873
+ "use strict";
54874
+ }
54875
+ });
54876
+
54852
54877
  // ts/agent-core/dist/skills/index.js
54853
54878
  var init_skills = __esm({
54854
54879
  "ts/agent-core/dist/skills/index.js"() {
@@ -54859,12 +54884,14 @@ var init_skills = __esm({
54859
54884
  init_parall_schedules();
54860
54885
  init_parall_external_triggers();
54861
54886
  init_parall_clips();
54887
+ init_parall_clip_authoring();
54862
54888
  init_parall_platform();
54863
54889
  init_parall_tasks();
54864
54890
  init_parall_wiki();
54865
54891
  init_parall_schedules();
54866
54892
  init_parall_external_triggers();
54867
54893
  init_parall_clips();
54894
+ init_parall_clip_authoring();
54868
54895
  }
54869
54896
  });
54870
54897
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/daemon",
3
- "version": "1.52.2",
3
+ "version": "1.54.0",
4
4
  "description": "Parall local agent runtime — daemon supervisor + bridge runtimes, bundled as standalone JS files",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,11 +33,11 @@
33
33
  "@aws-sdk/client-s3": "3.984.0",
34
34
  "@pinixai/bb-browser-pro": "0.15.0",
35
35
  "ws": "^8.18.0",
36
- "@parall/agent-core": "1.52.2",
37
- "@parall/sdk": "1.52.2",
38
- "@parall/claude-agent": "1.52.2",
39
- "@parall/codex-agent": "1.52.2",
40
- "@parall/openclaw-agent": "1.52.2"
36
+ "@parall/agent-core": "1.54.0",
37
+ "@parall/codex-agent": "1.54.0",
38
+ "@parall/openclaw-agent": "1.54.0",
39
+ "@parall/sdk": "1.54.0",
40
+ "@parall/claude-agent": "1.54.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^22.0.0",