@parall/cli 1.55.5 → 1.56.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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clip.d.ts","sourceRoot":"","sources":["../../src/commands/clip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"clip.d.ts","sourceRoot":"","sources":["../../src/commands/clip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwLpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QA2jBpD"}
|
package/dist/commands/clip.js
CHANGED
|
@@ -52,6 +52,87 @@ async function loadDeviceIndex(client, orgId) {
|
|
|
52
52
|
return null;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
const CLIP_REF_HELP = 'Registry clip id (crg_...), an exact clip name (your org first, then a unique public match), or org_<id>/<name> to pin the publisher (discover ids with `parall clip search`)';
|
|
56
|
+
/**
|
|
57
|
+
* The clip-ref grammar shared with the standalone Solo CLI
|
|
58
|
+
* (desktop/edge/cli/parall-clip.mjs, parseClipRef) — the two install surfaces
|
|
59
|
+
* must read the same ref the same way. Any slash makes a namespaced ref, and
|
|
60
|
+
* only org ids are authoritative namespaces; throws on a malformed ref.
|
|
61
|
+
*/
|
|
62
|
+
function parseClipRef(ref) {
|
|
63
|
+
if (ref.startsWith('crg_'))
|
|
64
|
+
return { id: ref };
|
|
65
|
+
const slash = ref.indexOf('/');
|
|
66
|
+
if (slash === -1)
|
|
67
|
+
return { name: ref };
|
|
68
|
+
const namespace = ref.slice(0, slash);
|
|
69
|
+
const name = ref.slice(slash + 1);
|
|
70
|
+
if (!namespace || !name || name.includes('/')) {
|
|
71
|
+
throw new Error(`Invalid clip reference "${ref}". ${CLIP_REF_HELP}.`);
|
|
72
|
+
}
|
|
73
|
+
if (!namespace.startsWith('org_')) {
|
|
74
|
+
throw new Error(`Publisher namespace "${namespace}" is not authoritative yet. Use an immutable crg_ id or org_<id>/${name}.`);
|
|
75
|
+
}
|
|
76
|
+
return { namespace, name };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve an install/info argument to a registry entry with per-name queries
|
|
80
|
+
* only — never by listing the catalog, which spans every public clip on the
|
|
81
|
+
* platform. Bare names resolve in the CALLER'S org first (an authoritative
|
|
82
|
+
* point read immune to a third party publishing the same name), then fall
|
|
83
|
+
* back to a unique exact match among visible public clips; several publishers
|
|
84
|
+
* sharing the name is an explicit ambiguity error, never "first match wins".
|
|
85
|
+
* Returns null on a clean not-found; non-404 errors (e.g. 403
|
|
86
|
+
* CLIP_NOT_APPROVED) propagate untouched.
|
|
87
|
+
*/
|
|
88
|
+
async function resolveRegistryRef(client, orgId, ref) {
|
|
89
|
+
const notFoundToNull = (err) => {
|
|
90
|
+
if (err.status === 404)
|
|
91
|
+
return null;
|
|
92
|
+
throw err;
|
|
93
|
+
};
|
|
94
|
+
const parsed = parseClipRef(ref);
|
|
95
|
+
if ('id' in parsed) {
|
|
96
|
+
return client.getRegistryClip(orgId, parsed.id).catch(notFoundToNull);
|
|
97
|
+
}
|
|
98
|
+
if ('namespace' in parsed) {
|
|
99
|
+
return client.resolveRegistryClip(orgId, parsed.namespace, parsed.name).catch(notFoundToNull);
|
|
100
|
+
}
|
|
101
|
+
const own = await client.resolveRegistryClip(orgId, orgId, parsed.name).catch(notFoundToNull);
|
|
102
|
+
if (own)
|
|
103
|
+
return own;
|
|
104
|
+
// Bare name, not published by the caller's org: accept a UNIQUE exact match
|
|
105
|
+
// among visible clips. Search is fuzzy (name+description substrings), so
|
|
106
|
+
// exact-filter its max-size page; a page-sized crowd of lookalikes could
|
|
107
|
+
// still hide an exact match — the crg_ id and org_<id>/<name> forms stay
|
|
108
|
+
// the always-sound addresses. A 404 here is a server without the search
|
|
109
|
+
// route, not a lookup miss — degrade to "no matches" so the caller gets
|
|
110
|
+
// the guidance error instead of a bare route error.
|
|
111
|
+
const page = await client
|
|
112
|
+
.searchOrgRegistryClips(orgId, parsed.name, { limit: 100 })
|
|
113
|
+
.catch((err) => {
|
|
114
|
+
if (err.status === 404)
|
|
115
|
+
return [];
|
|
116
|
+
throw err;
|
|
117
|
+
});
|
|
118
|
+
const matches = page.filter((c) => c.name === parsed.name);
|
|
119
|
+
if (matches.length > 1) {
|
|
120
|
+
const candidates = matches.map((c) => `${c.org_id}/${c.name} (${c.id})`).join(', ');
|
|
121
|
+
throw new Error(`Clip name "${parsed.name}" is ambiguous across publishers: ${candidates}. Use the crg_ id or the org_<id>/<name> form.`);
|
|
122
|
+
}
|
|
123
|
+
// A FULL page proves nothing about uniqueness or absence: rows order by
|
|
124
|
+
// (name, id), so the exact-name block can straddle the page boundary and a
|
|
125
|
+
// single visible match — or none — may be a truncation artifact. Two exact
|
|
126
|
+
// matches on the page prove ambiguity outright (handled above); otherwise
|
|
127
|
+
// abstain rather than risk installing the wrong publisher's clip.
|
|
128
|
+
if (page.length >= 100) {
|
|
129
|
+
throw new Error(`Too many clips have names or descriptions similar to "${parsed.name}" for a bare name to resolve safely. Pass the crg_ id or the org_<id>/<name> form (find them with \`parall clip search <text>\`).`);
|
|
130
|
+
}
|
|
131
|
+
return matches[0] ?? null;
|
|
132
|
+
}
|
|
133
|
+
function clipNotFoundError(ref) {
|
|
134
|
+
return new Error(`No registry clip matching "${ref}". Pass a crg_ id, the exact name of a visible clip, or org_<id>/<name> to pin its publisher — discover ids with \`parall clip search <text>\`.`);
|
|
135
|
+
}
|
|
55
136
|
export function registerClipCommands(program) {
|
|
56
137
|
const clip = program
|
|
57
138
|
.command('clip')
|
|
@@ -101,22 +182,70 @@ export function registerClipCommands(program) {
|
|
|
101
182
|
printError(err);
|
|
102
183
|
}
|
|
103
184
|
});
|
|
185
|
+
clip
|
|
186
|
+
.command('search')
|
|
187
|
+
.description('Search the visible clip registry server-side (your org + cross-org public clips) — rows carry the crg_ id that `clip install` takes')
|
|
188
|
+
.argument('<query>', 'Text matched against clip names and descriptions (substring, case-insensitive)')
|
|
189
|
+
.option('--limit <n>', 'Rows per page, up to 100 (server default 50)')
|
|
190
|
+
.option('--offset <n>', 'Rows to skip — page through large result sets')
|
|
191
|
+
.action(async (query, opts) => {
|
|
192
|
+
try {
|
|
193
|
+
const { client, orgId } = resolveCredentials();
|
|
194
|
+
// Strict grammar BEFORE the wire (same rationale as exec --timeout):
|
|
195
|
+
// the server silently clamps malformed or out-of-range values, which
|
|
196
|
+
// would page differently than the caller asked.
|
|
197
|
+
const limit = parsePositiveInt(opts?.limit);
|
|
198
|
+
if (opts?.limit !== undefined && (limit === undefined || limit > 100)) {
|
|
199
|
+
printError(new Error('--limit must be an integer between 1 and 100'));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Unlike limit, 0 is a valid offset (the explicit first page), so
|
|
203
|
+
// parsePositiveInt alone would reject a legal boundary value.
|
|
204
|
+
let offset;
|
|
205
|
+
if (opts?.offset !== undefined) {
|
|
206
|
+
offset = opts.offset.trim() === '0' ? 0 : parsePositiveInt(opts.offset);
|
|
207
|
+
if (offset === undefined) {
|
|
208
|
+
printError(new Error('--offset must be a non-negative integer'));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const entries = await client.searchOrgRegistryClips(orgId, query, { limit, offset });
|
|
213
|
+
if (entries.length === 0) {
|
|
214
|
+
printJson({ data: [], message: `No registry clips matched "${query}"` });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// publisher tells the caller which install form works: rows from your
|
|
218
|
+
// own org install by bare name; cross-org rows need org_<id>/<name>
|
|
219
|
+
// (publisher IS that org id) or the crg_ id.
|
|
220
|
+
printJson(entries.map((c) => ({
|
|
221
|
+
id: c.id,
|
|
222
|
+
name: c.name,
|
|
223
|
+
publisher: c.org_id === orgId ? 'your org' : c.org_id,
|
|
224
|
+
...(c.description ? { description: c.description } : {}),
|
|
225
|
+
version: c.version ?? '-',
|
|
226
|
+
visibility: c.visibility,
|
|
227
|
+
})));
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
printError(err);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
104
233
|
clip
|
|
105
234
|
.command('install')
|
|
106
235
|
.description('Install a registry clip into the organization')
|
|
107
|
-
.argument('<clip>',
|
|
236
|
+
.argument('<clip>', CLIP_REF_HELP)
|
|
108
237
|
.action(async (clip) => {
|
|
109
238
|
try {
|
|
110
239
|
const { client, orgId } = resolveCredentials();
|
|
111
240
|
// v3 installs a REFERENCE into clip_installs, keyed by the registry id.
|
|
112
241
|
// There is no machine to pick: a v3 clip runs wherever the connection
|
|
113
242
|
// it is bound to points, which is why the old --machine-id is gone.
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
const found =
|
|
243
|
+
// Resolution is a point query — the visible catalog spans every
|
|
244
|
+
// public clip on the platform, so it must never be listed to find one
|
|
245
|
+
// (the old list+find lost every clip past the first page).
|
|
246
|
+
const found = await resolveRegistryRef(client, orgId, clip);
|
|
118
247
|
if (!found) {
|
|
119
|
-
printError(
|
|
248
|
+
printError(clipNotFoundError(clip));
|
|
120
249
|
return;
|
|
121
250
|
}
|
|
122
251
|
// Idempotent server-side: installing an already-installed clip is a no-op.
|
|
@@ -130,48 +259,52 @@ export function registerClipCommands(program) {
|
|
|
130
259
|
clip
|
|
131
260
|
.command('info')
|
|
132
261
|
.description("Show detailed clip information, including its manifest commands and params. For your org's OWN clips this includes review_status/review_note (the platform-review state of a public publish)")
|
|
133
|
-
.argument('<clip>',
|
|
262
|
+
.argument('<clip>', `Installed clip id or name, or — for uninstalled clips — ${CLIP_REF_HELP}`)
|
|
134
263
|
.action(async (clip) => {
|
|
135
264
|
try {
|
|
136
265
|
const { client, orgId } = resolveCredentials();
|
|
137
|
-
// Resolve installed-first (the historical semantic
|
|
138
|
-
//
|
|
266
|
+
// Resolve installed-first (the historical semantic; the installed list
|
|
267
|
+
// is org-local and uncapped), then fall back to point-querying the
|
|
268
|
+
// registry: an author checking a clip they published but never
|
|
139
269
|
// installed — the publish→review→revise loop — must not dead-end here.
|
|
270
|
+
// The installed shortcut obeys the SAME bare-name contract as
|
|
271
|
+
// resolveRegistryRef: an org can install same-name clips from several
|
|
272
|
+
// publishers (installs are keyed by id), so a plain first-match would
|
|
273
|
+
// show an arbitrary one — own org wins, and a foreign tie is the same
|
|
274
|
+
// explicit ambiguity error, never a silent pick.
|
|
140
275
|
const installed = await client.listInstalledRegistryClips(orgId);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// The detail read is what carries review_status/review_note (server
|
|
149
|
-
// attaches them only for the owning org). A cross-org entry can lose
|
|
150
|
-
// detail visibility between the list and this read — the list entry
|
|
151
|
-
// is still correct data, so fall back to it rather than failing info.
|
|
152
|
-
try {
|
|
153
|
-
entry = await client.getRegistryClip(orgId, found.id);
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
entry = found;
|
|
157
|
-
}
|
|
276
|
+
const byId = installed.find((c) => c.id === clip);
|
|
277
|
+
const byName = byId ? [] : installed.filter((c) => c.name === clip);
|
|
278
|
+
let found = byId ?? byName.find((c) => c.org_id === orgId);
|
|
279
|
+
if (!found && byName.length > 1) {
|
|
280
|
+
const candidates = byName.map((c) => `${c.org_id}/${c.name} (${c.id})`).join(', ');
|
|
281
|
+
printError(new Error(`Several installed clips are named "${clip}": ${candidates}. Use the crg_ id or the org_<id>/<name> form.`));
|
|
282
|
+
return;
|
|
158
283
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
284
|
+
found ??= byName[0];
|
|
285
|
+
let entry = found ?? (await resolveRegistryRef(client, orgId, clip));
|
|
286
|
+
// The detail read is what carries review_status/review_note (server
|
|
287
|
+
// attaches them only for the owning org; the resolve read does not).
|
|
288
|
+
// resolveRegistryRef's crg_ branch IS that detail read already. A
|
|
289
|
+
// cross-org entry can lose detail visibility between the lookup and
|
|
290
|
+
// this read (a 404) — the lookup entry is still correct data, so fall
|
|
291
|
+
// back to it rather than failing info. Any OTHER failure (auth, 5xx,
|
|
292
|
+
// network) must surface: silently printing partial data would report
|
|
293
|
+
// a broken read as success.
|
|
294
|
+
const alreadyDetail = !found && clip.startsWith('crg_');
|
|
295
|
+
if (entry && !alreadyDetail) {
|
|
164
296
|
try {
|
|
165
|
-
entry = await client.getRegistryClip(orgId,
|
|
297
|
+
entry = await client.getRegistryClip(orgId, entry.id);
|
|
166
298
|
}
|
|
167
|
-
catch {
|
|
168
|
-
|
|
299
|
+
catch (err) {
|
|
300
|
+
if (err.status !== 404)
|
|
301
|
+
throw err;
|
|
169
302
|
}
|
|
170
303
|
}
|
|
171
304
|
if (!entry) {
|
|
172
305
|
// `return` matters: without it the lookup falls through and dies
|
|
173
306
|
// with a TypeError instead of the message just printed.
|
|
174
|
-
printError(
|
|
307
|
+
printError(clipNotFoundError(clip));
|
|
175
308
|
return;
|
|
176
309
|
}
|
|
177
310
|
const info = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/commands/messages.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBzC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5C,OAAO,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GACtD;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CASzC;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/commands/messages.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBzC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5C,OAAO,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GACtD;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CASzC;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QAkSvD"}
|
|
@@ -245,4 +245,47 @@ export function registerMessageCommands(program) {
|
|
|
245
245
|
printError(err);
|
|
246
246
|
}
|
|
247
247
|
});
|
|
248
|
+
// ---- Thread watchers ----
|
|
249
|
+
messages
|
|
250
|
+
.command('watch')
|
|
251
|
+
.description('Watch a thread (subscribe to its replies)')
|
|
252
|
+
.argument('<threadRootId>', 'Thread root message ID')
|
|
253
|
+
.action(async (threadRootId) => {
|
|
254
|
+
try {
|
|
255
|
+
const { client } = resolveCredentials();
|
|
256
|
+
await client.watchThread(stripPrllScheme(threadRootId));
|
|
257
|
+
printJson({ ok: true });
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
printError(err);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
messages
|
|
264
|
+
.command('unwatch')
|
|
265
|
+
.description('Unwatch a thread (stop receiving its replies; sticky — only replying or watch re-subscribes)')
|
|
266
|
+
.argument('<threadRootId>', 'Thread root message ID')
|
|
267
|
+
.action(async (threadRootId) => {
|
|
268
|
+
try {
|
|
269
|
+
const { client } = resolveCredentials();
|
|
270
|
+
await client.unwatchThread(stripPrllScheme(threadRootId));
|
|
271
|
+
printJson({ ok: true });
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
printError(err);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
messages
|
|
278
|
+
.command('watchers')
|
|
279
|
+
.description('List watchers of a thread')
|
|
280
|
+
.argument('<threadRootId>', 'Thread root message ID')
|
|
281
|
+
.action(async (threadRootId) => {
|
|
282
|
+
try {
|
|
283
|
+
const { client } = resolveCredentials();
|
|
284
|
+
const result = await client.getThreadWatchers(stripPrllScheme(threadRootId));
|
|
285
|
+
printJson(result);
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
printError(err);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
248
291
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/commands/tasks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiEpC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;;;;;;;;;;GAaA;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;YAS0B,iBAAiB,CAAC,QAAQ,CAAC;cACvB,iBAAiB,CAAC,UAAU,CAAC;;;;;;;;GAS3D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/commands/tasks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiEpC,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;;;;;;;;;;GAaA;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;;YAS0B,iBAAiB,CAAC,QAAQ,CAAC;cACvB,iBAAiB,CAAC,UAAU,CAAC;;;;;;;;GAS3D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QA0YpD"}
|
package/dist/commands/tasks.js
CHANGED
|
@@ -170,6 +170,25 @@ export function registerTaskCommands(program) {
|
|
|
170
170
|
printError(err);
|
|
171
171
|
}
|
|
172
172
|
});
|
|
173
|
+
tasks
|
|
174
|
+
.command('move')
|
|
175
|
+
.description('Move a task (and its whole subtree) to another project. The subtree is renumbered from the target project, old KEY-N identifiers stay resolvable as aliases, and subscribers who cannot read the target project lose their subscription. Requires update access on the task (source project membership) and read access to the target project.')
|
|
176
|
+
.argument('<taskId>', 'Task ID')
|
|
177
|
+
.requiredOption('--project <projectId>', 'Target project ID')
|
|
178
|
+
.option('--parent <taskId>', 'Attach under this parent task in the target project (omit to land top-level)')
|
|
179
|
+
.action(async (taskId, opts) => {
|
|
180
|
+
try {
|
|
181
|
+
const { client, orgId } = resolveCredentials();
|
|
182
|
+
const result = await client.moveTask(orgId, stripPrllScheme(taskId), {
|
|
183
|
+
project_id: stripPrllScheme(opts.project),
|
|
184
|
+
...(opts.parent ? { parent_id: stripPrllScheme(opts.parent) } : {}),
|
|
185
|
+
});
|
|
186
|
+
printJson(result);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
printError(err);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
173
192
|
tasks
|
|
174
193
|
.command('update')
|
|
175
194
|
.description('Update a task')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.56.0",
|
|
4
4
|
"description": "CLI client for Parall — universal agent & human access to Parall API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"diff": "^8.0.3",
|
|
37
37
|
"js-yaml": "^4.1.0",
|
|
38
38
|
"zod": "^4.3.6",
|
|
39
|
-
"@parall/agent-core": "1.
|
|
40
|
-
"@parall/sdk": "1.
|
|
39
|
+
"@parall/agent-core": "1.56.0",
|
|
40
|
+
"@parall/sdk": "1.56.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/js-yaml": "^4.0.9",
|
|
44
44
|
"@types/node": "^22.0.0",
|
|
45
45
|
"typescript": "^5.7.0",
|
|
46
|
-
"@parall/agent-core": "1.
|
|
46
|
+
"@parall/agent-core": "1.56.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsc",
|