@parall/agent-core 1.52.1 → 1.53.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.
@@ -0,0 +1,260 @@
1
+ export const PARALL_CLIP_AUTHORING_SKILL = `# Authoring a Parall Clip (v3)
2
+
3
+ Write a **registry (v3) clip**. This skill is for CREATING clips; to DISCOVER
4
+ and CALL clips that are already installed, use the Parall Clips skill instead.
5
+
6
+ Two kinds of clip exist, and this document covers authoring both:
7
+
8
+ - A **browser clip** — the main subject here — is a folder of named commands
9
+ that run on an Edge (a member's desktop or an org-shared cloud profile) and
10
+ drive a real browser session: one manifest (\`manifest.json\` or \`site.json\`,
11
+ either name works) + one \`.js\` file per command + optional \`_\`-prefixed
12
+ helpers. The Edge runs each command's JS in a sandbox with a browser handle
13
+ bound to the target profile.
14
+ - An **MCP clip** is a manifest-ONLY declaration pointing at a remote MCP tool
15
+ server — no \`.js\` files; its tools come live from that server's own
16
+ \`tools/list\`, never from the manifest. See "MCP clips" below.
17
+
18
+ ## Project layout
19
+
20
+ \`\`\`
21
+ my-clip/
22
+ ├── manifest.json # name, description, version, command params
23
+ ├── _helpers.js # OPTIONAL — any _-prefixed file is auto-injected into every command
24
+ ├── search.js # one command = one file; filename (minus .js) IS the command name
25
+ └── profile.js # another command
26
+ \`\`\`
27
+
28
+ ## manifest.json
29
+
30
+ \`\`\`json
31
+ {
32
+ "name": "twitter",
33
+ "description": "Twitter / X",
34
+ "version": "1.0.0",
35
+ "commands": {
36
+ "search": {
37
+ "description": "Search tweets",
38
+ "params": {
39
+ "query": { "type": "string", "required": true },
40
+ "count": { "type": "number", "required": false }
41
+ }
42
+ },
43
+ "profile": {
44
+ "description": "Fetch a user profile",
45
+ "params": { "handle": { "type": "string", "required": true } }
46
+ }
47
+ }
48
+ }
49
+ \`\`\`
50
+
51
+ - \`commands\` keys MUST match the \`.js\` filenames (\`search\` ↔ \`search.js\`).
52
+ - \`params\` is the input contract the caller sees in \`clip info\`; validate them in code too.
53
+ - **No top-level \`"type"\` needed for a browser clip** — omitting it means
54
+ browser. The only accepted values are \`"browser"\` and \`"mcp"\` (for the
55
+ latter, see "MCP clips" below). **\`"type": "clip"\` is the legacy Pinix v2
56
+ package value and is refused at publish** — don't copy it in from an older
57
+ clip.
58
+
59
+ ## A command file
60
+
61
+ \`\`\`js
62
+ // search.js — a command is a single async function of (args).
63
+ // The code does NOT know or pick the profile; the Edge binds it per invocation.
64
+ //
65
+ // SHAPE, not a runnable Twitter client: the "..." parts (the GraphQL path,
66
+ // parseTweets' body) are what you fill in per target site. Deliberately not
67
+ // pinned to a real X endpoint — a site's internal API paths rotate, and a
68
+ // stale one baked into this skill would teach a URL that 404s.
69
+ module.exports = async function (args) {
70
+ if (!args.query) return { error: "Missing argument: query" };
71
+
72
+ const tab = await browser.open("https://x.com");
73
+ // Everything after open() goes in try/finally: an early return or a thrown
74
+ // fetch would otherwise leak the tab, and the Edge is long-lived.
75
+ try {
76
+ const ct0 = await tab.cookie("ct0");
77
+ if (!ct0) return { error: "Not logged in" };
78
+
79
+ const data = await tab.fetch("/i/api/graphql/.../SearchTimeline?...", {
80
+ headers: twitterHeaders(ct0), // from _helpers.js, auto-injected
81
+ });
82
+ return { query: args.query, tweets: parseTweets(data) };
83
+ } finally {
84
+ await tab.close();
85
+ }
86
+ };
87
+ \`\`\`
88
+
89
+ Return a plain JSON-serializable object. A thrown error surfaces to the caller as
90
+ \`SCRIPT_ERROR\`; a returned \`{ error: "..." }\` is your own typed failure — prefer it
91
+ for expected cases (not logged in, missing arg).
92
+
93
+ ## Helpers (\`_\`-prefixed)
94
+
95
+ Any file whose name starts with \`_\` is NOT a command. Its top-level functions are
96
+ injected into every command's scope — no import/require needed:
97
+
98
+ \`\`\`js
99
+ // _helpers.js
100
+ function twitterHeaders(ct0) {
101
+ return { "X-Csrf-Token": ct0, "X-Twitter-Auth-Type": "OAuth2Session" };
102
+ }
103
+ function parseTweets(data) { /* ... */ }
104
+ \`\`\`
105
+
106
+ ## Runtime API (globals available in every command)
107
+
108
+ - \`browser.open(url)\` → tab handle · \`browser.tabs()\` → open tabs
109
+ - \`tab.cookie(name)\` · \`tab.fetch(url, opts)\` (in-browser fetch, carries the session)
110
+ - \`tab.eval(expr)\` (escape hatch) · \`tab.click(sel)\` · \`tab.fill(sel, text)\` · \`tab.navigate(url)\`
111
+ - \`tab.waitForSelector(sel)\` · \`tab.getTitle()\` · \`tab.getURL()\` · \`tab.screenshot()\` · \`tab.close()\`
112
+ - \`fetch\` — runtime-side HTTP, does NOT go through the browser (no session)
113
+ - \`console\` — logs · \`args\` — the invocation input
114
+
115
+ **Prefer \`tab.fetch\` over \`tab.eval\`**: fetch reuses the logged-in session and
116
+ returns structured data; eval is the last resort. Always \`tab.close()\` what you
117
+ open, and do it in a \`finally\` — an early return or a thrown fetch is exactly
118
+ when the tab leaks.
119
+
120
+ ## Develop → publish → iterate
121
+
122
+ Use the platform \`parall clip\` subcommands — they reuse the credentials you
123
+ already have (\`PRLL_API_KEY\` / \`PRLL_ORG_ID\`), so there is nothing to install
124
+ or configure.
125
+
126
+ > A separate **standalone \`parall-clip\`** binary also exists (the Edge-side
127
+ > authoring tool). It takes the SAME operations but a DIFFERENT argument shape —
128
+ > \`parall-clip exec <clip> <cmd> --query "AI" --count 10\` passes one flag per
129
+ > param, while \`parall clip exec\` takes a single JSON blob. Do not mix the two
130
+ > forms; everything below is the platform CLI.
131
+
132
+ 1. **Publish** the clip directory to the org registry. Publishing is not a
133
+ release step here — it is the edit loop's SAVE button, because exec only
134
+ ever sees published files:
135
+
136
+ \`\`\`sh
137
+ parall clip publish ./my-clip/
138
+ \`\`\`
139
+
140
+ It packages the directory (the manifest plus the directory's top-level
141
+ \`.js\` files; an MCP clip is manifest-only) and POSTs it for you (5 MB cap).
142
+ \`name\` is the org-wide upsert key — manifest fields win, else the directory
143
+ name / \`0.0.1\` / \`private\` fill the gaps. Re-publishing an existing name is
144
+ **author-only** and REPLACES the file set. Publishing into YOUR org makes it
145
+ usable there immediately (same-org self-reference, no review);
146
+ \`"visibility": "public"\` additionally submits the version for platform
147
+ review before it can spread cross-org.
148
+
149
+ Programmatic equivalent (what \`publish\` calls under the hood — use only if
150
+ you can't run the CLI). Send it verbatim-shaped: \`visibility\` is exactly
151
+ one of \`"private"\` / \`"public"\`, and \`files\` maps each filename to its
152
+ source as a string:
153
+
154
+ \`\`\`
155
+ POST /api/v1/orgs/{orgId}/clip-registry/publish
156
+ {
157
+ "name": "twitter",
158
+ "description": "Twitter / X",
159
+ "version": "1.0.0",
160
+ "visibility": "private",
161
+ "manifest": {
162
+ "name": "twitter",
163
+ "version": "1.0.0",
164
+ "commands": { "search": { "description": "Search tweets" } }
165
+ },
166
+ "files": { "search.js": "module.exports = async function (args) { return {}; };" }
167
+ }
168
+ \`\`\`
169
+
170
+ 2. **Exec** a command against a real target. Args are ONE argument — a JSON
171
+ string (or plain text for a single-value command), not per-param flags:
172
+
173
+ \`\`\`sh
174
+ parall clip exec <clip> <command> '{"query":"AI","count":10}' --connection <ccn_id|alias>
175
+ # or route to a desktop (BYOC) device you own: --edge <edge-id>
176
+ # --connection and --edge are mutually exclusive; --timeout <ms> defaults to 30000
177
+ \`\`\`
178
+
179
+ A **cloud (hosted) profile is reachable ONLY via \`--connection\`** — the
180
+ binding its maintainer created IS the authorization. With neither flag the
181
+ server resolves only your own online desktop device, never a cloud profile.
182
+ Discover the bindings with \`parall clip connections <clip>\`.
183
+
184
+ 3. **Iterate**: edit locally → \`parall clip publish\` again → re-exec. Exec
185
+ resolves the file set from the REGISTRY, server-side — your own org always
186
+ runs the live working copy, i.e. the latest publish. It NEVER reads your
187
+ local directory: an edit you did not re-publish silently runs the previous
188
+ version.
189
+
190
+ ## Install model & self-development (v3)
191
+
192
+ - Installing a clip is a **reference**, not a copy — the JS lives once in the Market DB.
193
+ - **Your own org** always executes its **live working copy** (latest published files),
194
+ so re-publishing is your edit loop.
195
+ - **Other orgs** installing your \`public\` clip execute only the **approved snapshot**
196
+ (\`approved_version_id\`); unreviewed public edits are invisible/unexecutable cross-org.
197
+ - To customize someone else's public clip: install → **fetch its effective
198
+ file set** → modify → **publish into your OWN org** (a derived private
199
+ entry). You cannot edit a published clip in place. The CLI has no files
200
+ subcommand (\`clip info\` returns only the manifest) — read the source via
201
+ the API:
202
+
203
+ \`\`\`
204
+ GET /api/v1/orgs/{orgId}/clip-registry/{clipId}/files
205
+ \`\`\`
206
+
207
+ It returns exactly what you may read and execute: your own clip → the live
208
+ working copy; an installed public clip → the approved snapshot.
209
+
210
+ ## MCP clips (manifest-only)
211
+
212
+ An MCP clip declares a remote MCP tool server. There is nothing to code: no
213
+ \`.js\` files (the no-scripts refusal at publish applies to browser clips only),
214
+ and the folder is just a manifest:
215
+
216
+ \`\`\`json
217
+ {
218
+ "name": "linear",
219
+ "description": "Linear (MCP)",
220
+ "version": "1.0.0",
221
+ "type": "mcp",
222
+ "mcp": { "server_url": "https://mcp.linear.app/mcp", "auth": "oauth" }
223
+ }
224
+ \`\`\`
225
+
226
+ - The \`mcp\` block takes ONLY \`server_url\` and \`auth\` (\`"none" | "bearer" |
227
+ "api_key" | "oauth"\`). Any other key is refused at publish — a credential
228
+ belongs to the installing org's own configuration, NEVER to the clip
229
+ definition.
230
+ - \`server_url\` must be an absolute **https** URL with no embedded credentials,
231
+ query, or fragment. It is review material, frozen with the approved version.
232
+ - Do NOT put the server in the top-level \`server\` / \`auth\` manifest keys —
233
+ those are legacy Edge-manifest fields nothing reads. Only the \`mcp\` block
234
+ declares the server.
235
+ - Both fields are optional, but what you declare is LOCKED: the installing
236
+ org's config must match it, and changing the URL or auth mode means
237
+ republishing.
238
+ - Entering the credential / completing OAuth is a HUMAN step in the Clip
239
+ Console (the config-write endpoints are session-only — an API key cannot
240
+ call them). Once configured, discover the live tool schemas with
241
+ \`parall clip tools <clip>\` and exec like any other clip.
242
+ - Publish is the same command: \`parall clip publish ./my-clip/\`.
243
+
244
+ ## Cloud (hosted) vs desktop (BYOC) Edge
245
+
246
+ The same command JS runs on either. Hosted profiles are org-shared cloud browsers
247
+ reachable ONLY via an explicit \`--connection\`; cloud state lives in S3 and is
248
+ hydrated per pod. Your code never touches this — it just gets a \`browser\`/\`tab\`
249
+ bound to whatever profile the connection selected.
250
+
251
+ ## Constraints
252
+
253
+ - Browser clips: one command = one file; keep a command's work self-contained
254
+ (open what you need, close it, return). The Edge is stateless about your code
255
+ between calls.
256
+ - Never embed credentials in the clip source — rely on the profile's logged-in
257
+ session (\`tab.cookie\` / \`tab.fetch\`). Published source is visible to installers.
258
+ - Exec has a timeout (default 30s, caller-set up to 120s). Long scrapes should page,
259
+ not block.
260
+ `;
@@ -1,41 +1,55 @@
1
1
  export const PARALL_CLIPS_SKILL = `# Parall Clips
2
2
 
3
3
  Clips are packaged capabilities that let agents operate external systems —
4
- APIs and websites — through named commands installed in the org.
4
+ APIs, websites, remote tools — through named commands installed in the org.
5
5
 
6
6
  ## Discover
7
7
 
8
8
  \`\`\`bash
9
- parall clip list # clips installed in this org
10
- parall clip info <alias> # commands, params, version
9
+ parall clip list # installed clips + each clip's connections
10
+ parall clip info <clip> # commands + per-command params (manifest)
11
+ parall clip connections <clip> # one clip's connections, full rows
12
+ parall clip tools <clip> # MCP clips only: live tool schemas
11
13
  \`\`\`
12
14
 
13
- ## Invoke
15
+ \`clip list\` answers both discovery questions at once: WHICH clip (name,
16
+ description, version) and WHERE it can run — every connection with its
17
+ \`ccn_…\` id, alias, and target kind:
14
18
 
15
- \`\`\`bash
16
- parall clip invoke <alias> <command> [input] [--timeout <ms>] # timeout default 30s
17
- # input: JSON string or plain text, per the command's params in \`info\`
18
- parall clip invoke github-tools list-repos '{"org":"acme"}'
19
- \`\`\`
20
-
21
- Results are JSON on stdout; failures print an error.
19
+ - \`cloud\` — an org-shared cloud profile (a maintainer's signed-in browser)
20
+ - \`desktop\` a member's own device (only its owner can exec through it)
21
+ - \`mcp\` a remote MCP tool server
22
+ - \`device\` a device whose placement could not be resolved just now (the
23
+ device list was unavailable); don't guess which kind it is — re-run
24
+ discovery, and treat a persistent \`device\` like an unverified target
25
+ - \`orphaned\` the target device is gone; the connection is unusable
22
26
 
23
- ## Execute on an Edge device (registry clips)
27
+ Pick the connection whose alias names the account/device the task needs
28
+ (e.g. \`ins-nyc\` vs \`ins-boston\`). A row without an alias can only be
29
+ referenced by \`ccn_\` id — when aliases are missing and several connections
30
+ could match, ask a human to name them in the Clip Console rather than
31
+ guessing which signed-in account you are about to act through.
24
32
 
25
- Registry clips run on an Edge — a member's desktop, or an org-shared cloud
26
- profile. **Name the target explicitly.** A cloud profile has NO implicit
27
- route; omitting the target entirely is a desktop-only legacy form that
28
- reaches just YOUR OWN online desktop device — never a shared cloud profile.
33
+ ## Execute
29
34
 
30
35
  \`\`\`bash
31
- parall clip exec <clip> <command> [args] --connection <id|alias> # the normal form
36
+ parall clip exec <clip> <command> [args] --connection <ccn_|alias> # the normal form
32
37
  parall clip exec browser-tools screenshot '{"url":"…"}' --connection cloud-main
33
38
  \`\`\`
34
39
 
40
+ Build \`args\` as JSON per the command's params from \`clip info\` (or the
41
+ tool's \`inputSchema\` from \`clip tools\` for MCP clips — tool names are NOT
42
+ frozen in the manifest, so never guess a tool name or its argument shape).
43
+ Results are JSON on stdout; failures print a typed error.
44
+
45
+ **Name the target explicitly.** A cloud profile has NO implicit route;
46
+ omitting the target entirely is a desktop-only legacy form that reaches just
47
+ YOUR OWN online desktop device — never a shared cloud profile.
48
+
35
49
  - A cloud (hosted) profile is reachable ONLY via \`--connection\` — the clip
36
- connection its maintainer bound (\`ccn_…\` id or alias). That binding IS your
37
- authorization; without one the server answers \`HOSTED_CONNECTION_REQUIRED\`
38
- and the fix is to ask an owner/admin to bind the clip, never to retry.
50
+ connection its maintainer bound. That binding IS your authorization;
51
+ without one the server answers \`HOSTED_CONNECTION_REQUIRED\` and the fix
52
+ is to ask an owner/admin to bind the clip, never to retry.
39
53
  - \`--edge <edgeId>\` targets only a desktop device YOU own.
40
54
  - Waiting on a cloud profile is handled by the CLI: \`EDGE_ACTIVATING\` (cold
41
55
  start), \`EDGE_BUSY\` (another exec is running) and
@@ -47,19 +61,10 @@ parall clip exec browser-tools screenshot '{"url":"…"}' --connection cloud-mai
47
61
 
48
62
  ## MCP clips (remote tool servers)
49
63
 
50
- Some registry clips are backed by a remote MCP server instead of an Edge
51
- device. The command is an MCP tool name and the args are that tool's JSON
52
- arguments — but **MCP tool names are NOT frozen in \`clip info\`, so discover
53
- them first; never guess a tool name or its argument shape**. Before invoking,
54
- find the connection AND the tool schemas:
55
-
56
- \`\`\`bash
57
- parall clip connections <alias> # the ccn_ id / alias to pass to --connection
58
- parall clip tools <alias> # tool names + descriptions + inputSchema (JSON)
59
- \`\`\`
60
-
61
- Read each tool's \`inputSchema\` from \`clip tools\` to build valid args, then
62
- exec against that explicit target — same form as an Edge clip:
64
+ A connection with target \`mcp\` routes to a remote MCP server; the command
65
+ is an MCP tool name and the args are that tool's JSON arguments. Read each
66
+ tool's \`inputSchema\` from \`clip tools\` first, then exec against the
67
+ explicit target the same form as any other clip:
63
68
 
64
69
  \`\`\`bash
65
70
  parall clip exec <clip> <tool> [json-args] --connection <ccn_|alias>
@@ -77,13 +82,11 @@ parall clip exec <clip> <tool> [json-args] --connection <ccn_|alias>
77
82
 
78
83
  ## Behavior rules
79
84
 
80
- - An authorization error (clip not bound to you) is a fail-fast: ask the
81
- clip's owner or an admin to bind it do not retry or work around it.
82
- - If the executing runtime is offline or the call times out, report that
85
+ - An authorization error (\`HOSTED_CONNECTION_REQUIRED\`, \`FORBIDDEN\`) is a
86
+ fail-fast: ask the clip's maintainer or an org admin to bind the clip or
87
+ grant the connection do not retry or work around it.
88
+ - If the target device is offline or the call times out, report that
83
89
  plainly; do not queue, and never fabricate a result for a run that errored.
84
- - Hosted browser activation is handled by the CLI: it waits (bounded) while a
85
- cold hosted browser starts, so if the invoke still fails, report the error —
86
- do not blind-retry in a loop.
87
90
  - **\`OUTCOME_UNKNOWN\` is never retryable.** It means the command was
88
91
  dispatched and MAY HAVE EXECUTED even though no result came back. Retrying
89
92
  could post, order or delete twice. Verify the effect through the system you
@@ -99,6 +102,8 @@ parall clip exec <clip> <tool> [json-args] --connection <ccn_|alias>
99
102
  - A clip may act through a person's real logged-in account — outward,
100
103
  irreversible, or spending actions (post, order, delete, pay) get the same
101
104
  caution as any shared-state change: confirm when intent isn't explicit.
105
+ The connection's alias/target tells you WHICH account you are acting as —
106
+ if that is ambiguous, resolve it with a human before acting, not after.
102
107
  - Reach for \`parall clip list\` whenever a task needs capabilities beyond
103
108
  built-in tools.
104
109
  `;