@workser/cli 0.6.6 → 0.6.7
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.
- package/AGENTS.md +23 -9
- package/dist/index.js +187 -26
- package/package.json +1 -1
- package/skills/workser/reference/automation.md +12 -10
- package/skills/workser/reference/images.md +41 -5
package/AGENTS.md
CHANGED
|
@@ -71,10 +71,11 @@ workser workflow list | create <name> [--body <json>] | get <id>
|
|
|
71
71
|
workser workflow activate <id> | deactivate <id> | run <id> [--wait] [--body <json>]
|
|
72
72
|
workser workflow runs <id> # past executions of a workflow
|
|
73
73
|
workser workflow nodes [query] # search the node-type catalog
|
|
74
|
-
workser
|
|
75
|
-
workser
|
|
76
|
-
workser
|
|
77
|
-
workser
|
|
74
|
+
workser connection list [--toolkit <slug>] # connectable + connected third-party apps
|
|
75
|
+
workser connection search "<query>" [--toolkit <slug>] [--limit N] # full-text search across every toolkit's actions
|
|
76
|
+
workser connection connect <toolkit> | disconnect <connectionId>
|
|
77
|
+
workser connection tools <toolkit> # browse one connected toolkit's callable actions
|
|
78
|
+
workser connection run <toolSlug> [--body <json>] # execute one action (e.g. GOOGLESHEETS_APPEND_ROW)
|
|
78
79
|
|
|
79
80
|
workser tool list # computer-use tools available to you now
|
|
80
81
|
workser tool run <name> [--body <json>] # filesystem/shell/screenshot/input/clipboard/browser
|
|
@@ -87,6 +88,16 @@ workser artifact add <path> [--kind <k>] [-d <text>] # record a finished delive
|
|
|
87
88
|
workser artifact add --url <url> --kind app # record a deployed app
|
|
88
89
|
workser artifact run # which task you're attached to
|
|
89
90
|
|
|
91
|
+
workser search "<query>" [-n <maxResults>] # Google-grounded web search
|
|
92
|
+
|
|
93
|
+
workser image generate "<prompt>" [-r <url>...] [-o <path>] # generate an image, get a public URL
|
|
94
|
+
workser image understand "<query>" [--url <u>|--file <p>] [-t <task>] # describe/caption/answer questions about an image
|
|
95
|
+
workser video understand "<query>" [--url <u>|--file <p>] [-t <task>] # summarize/describe a video (URL also accepts YouTube)
|
|
96
|
+
workser audio understand "<query>" [--url <u>|--file <p>] [-t <task>] # transcribe/describe audio (URL also accepts YouTube)
|
|
97
|
+
# image/video/audio understand: the fallback for a text-only model, or media you have no other
|
|
98
|
+
# way to see/hear. --url is fetched server-side (no size ceiling); --file reads a small local
|
|
99
|
+
# file and sends it inline — for anything bigger, `workser storage upload` it and pass --url.
|
|
100
|
+
|
|
90
101
|
workser ask "<question>" [--type <t>] [--option <o>] # ask the user, WAIT for the answer
|
|
91
102
|
|
|
92
103
|
# owner-only (will return owner_only / exit 6 — ask the user to do these in Orbit):
|
|
@@ -215,11 +226,14 @@ done: `workser workflow create` builds an event-driven, multi-step automation (t
|
|
|
215
226
|
same engine Workser's own web Workflow tab uses) — nodes, connections, and triggers go
|
|
216
227
|
in `--body` as JSON; browse `workser workflow nodes` first to see what's available.
|
|
217
228
|
Before an automation can use a third-party app (Gmail, Slack, Stripe, Google Sheets,
|
|
218
|
-
...), the user connects it once via `workser
|
|
219
|
-
OAuth link — ask the user to complete it, then continue); after that, `workser
|
|
220
|
-
run <toolSlug> --body '{"...":...}'` calls any of its actions directly, and
|
|
221
|
-
node can call the same toolkit. Use `workser
|
|
222
|
-
connected before assuming you need to ask the user to connect something new.
|
|
229
|
+
...), the user connects it once via `workser connection connect <toolkit>` (this opens
|
|
230
|
+
an OAuth link — ask the user to complete it, then continue); after that, `workser
|
|
231
|
+
connection run <toolSlug> --body '{"...":...}'` calls any of its actions directly, and
|
|
232
|
+
a workflow node can call the same toolkit. Use `workser connection list` to see what's
|
|
233
|
+
already connected before assuming you need to ask the user to connect something new.
|
|
234
|
+
Don't know the exact action slug? `workser connection search "<what you want to do>"`
|
|
235
|
+
searches across every toolkit's actions at once — cheaper than browsing toolkit by
|
|
236
|
+
toolkit with `workser connection tools <toolkit>`.
|
|
223
237
|
|
|
224
238
|
## Delegate to roles
|
|
225
239
|
The user can configure **roles** — named specialists each backed by a local CLI agent
|
package/dist/index.js
CHANGED
|
@@ -3794,7 +3794,7 @@ and the spec is the difference.
|
|
|
3794
3794
|
topic: "automation",
|
|
3795
3795
|
title: "Workflows & connected apps",
|
|
3796
3796
|
summary: "Build automations that outlive the run; use Gmail, Slack, Stripe, Sheets.",
|
|
3797
|
-
commands: ["workflow", "
|
|
3797
|
+
commands: ["workflow", "connection"],
|
|
3798
3798
|
source: "skills/workser/reference/automation.md",
|
|
3799
3799
|
body: `# Workflows & connected apps
|
|
3800
3800
|
|
|
@@ -3807,10 +3807,11 @@ workser workflow activate <id> | deactivate <id> | run <id> [--wait] [--body <js
|
|
|
3807
3807
|
workser workflow runs <id> # past executions of a workflow
|
|
3808
3808
|
workser workflow nodes [query] # search the node-type catalog
|
|
3809
3809
|
|
|
3810
|
-
workser
|
|
3811
|
-
workser
|
|
3812
|
-
workser
|
|
3813
|
-
workser
|
|
3810
|
+
workser connection list [--toolkit <slug>] # connectable + connected third-party apps
|
|
3811
|
+
workser connection search "<query>" [--toolkit <slug>] [--limit N] # find an action across every toolkit
|
|
3812
|
+
workser connection connect <toolkit> | disconnect <connectionId>
|
|
3813
|
+
workser connection tools <toolkit> # browse one connected toolkit's actions
|
|
3814
|
+
workser connection run <toolSlug> [--body <json>] # execute one action
|
|
3814
3815
|
\`\`\`
|
|
3815
3816
|
|
|
3816
3817
|
## Building a workflow
|
|
@@ -3826,12 +3827,13 @@ Created workflows start inactive: \`workser workflow activate <id>\` when it's r
|
|
|
3826
3827
|
|
|
3827
3828
|
## Using a connected app
|
|
3828
3829
|
|
|
3829
|
-
1. \`workser
|
|
3830
|
-
2. If it isn't: \`workser
|
|
3830
|
+
1. \`workser connection list\` \u2014 check what's already connected before asking for anything.
|
|
3831
|
+
2. If it isn't: \`workser connection connect <toolkit>\` returns an OAuth link. The **user**
|
|
3831
3832
|
must open it; you cannot complete OAuth on their behalf. Wait, then continue.
|
|
3832
|
-
3.
|
|
3833
|
-
|
|
3834
|
-
|
|
3833
|
+
3. Don't know the exact action? \`workser connection search "<query>"\` finds it across
|
|
3834
|
+
every toolkit; \`workser connection tools <toolkit>\` browses one toolkit you already
|
|
3835
|
+
know. Either way, read the argument schema rather than guessing field names.
|
|
3836
|
+
4. \`workser connection run <toolSlug> --body '{"\u2026":\u2026}'\` \u2014 e.g. \`GOOGLESHEETS_APPEND_ROW\`,
|
|
3835
3837
|
\`GMAIL_SEND_EMAIL\`.
|
|
3836
3838
|
|
|
3837
3839
|
**A \`run\` is a real side effect in someone's real account.** Sending an email or
|
|
@@ -4566,11 +4568,11 @@ up front \u2014 most of the apps a goal will touch don't exist when it's propose
|
|
|
4566
4568
|
},
|
|
4567
4569
|
{
|
|
4568
4570
|
topic: "images",
|
|
4569
|
-
title: "Image generation",
|
|
4570
|
-
summary: "Generate images from a prompt,
|
|
4571
|
-
commands: ["image"],
|
|
4571
|
+
title: "Image generation & media understanding",
|
|
4572
|
+
summary: "Generate images from a prompt; describe/transcribe an image, video, or audio clip you can't natively see or hear.",
|
|
4573
|
+
commands: ["image", "video", "audio"],
|
|
4572
4574
|
source: "skills/workser/reference/images.md",
|
|
4573
|
-
body: `# Image generation
|
|
4575
|
+
body: `# Image generation & media understanding
|
|
4574
4576
|
|
|
4575
4577
|
\`\`\`
|
|
4576
4578
|
workser image generate "<prompt>" # alias: workser image gen
|
|
@@ -4586,7 +4588,7 @@ workser image generate "flat illustration of a farm delivery van, brand colors"
|
|
|
4586
4588
|
workser image gen "same van, from the side" -r https://\u2026 -o ./public/van.png --json
|
|
4587
4589
|
\`\`\`
|
|
4588
4590
|
|
|
4589
|
-
## Notes that matter
|
|
4591
|
+
## Notes that matter (generation)
|
|
4590
4592
|
|
|
4591
4593
|
- **Reference images are image-to-image conditioning**, not attachments. Up to 4;
|
|
4592
4594
|
anything beyond that is dropped.
|
|
@@ -4598,6 +4600,42 @@ workser image gen "same van, from the side" -r https://\u2026 -o ./public/van.pn
|
|
|
4598
4600
|
exist only as URLs.
|
|
4599
4601
|
- **Placeholder art is not a deliverable.** Generating a hero image to unblock a
|
|
4600
4602
|
layout is fine; shipping it as the user's brand asset without asking is not.
|
|
4603
|
+
|
|
4604
|
+
## Understanding media you can't natively see or hear
|
|
4605
|
+
|
|
4606
|
+
The fallback for a text-only model, or media you have no other way to reach: describe
|
|
4607
|
+
an image, summarize/transcribe a video, transcribe/describe audio. Runs server-side
|
|
4608
|
+
(Gemini) \u2014 you never need a model key.
|
|
4609
|
+
|
|
4610
|
+
\`\`\`
|
|
4611
|
+
workser image understand "<query>" [--url <u> | --file <p>] [-t <task>]
|
|
4612
|
+
workser video understand "<query>" [--url <u> | --file <p>] [-t <task>]
|
|
4613
|
+
workser audio understand "<query>" [--url <u> | --file <p>] [-t <task>]
|
|
4614
|
+
\`\`\`
|
|
4615
|
+
|
|
4616
|
+
\`\`\`bash
|
|
4617
|
+
workser image understand "what's wrong with this layout?" --url https://\u2026/screenshot.png --json
|
|
4618
|
+
workser video understand "what happens at the end?" --url https://youtu.be/\u2026 -t timestamp_analysis --json
|
|
4619
|
+
workser audio understand "transcribe this" --file ./voicemail.m4a -t transcribe --json
|
|
4620
|
+
\`\`\`
|
|
4621
|
+
|
|
4622
|
+
## Notes that matter (understanding)
|
|
4623
|
+
|
|
4624
|
+
- **\`--url\` vs \`--file\`**: \`--url\` is fetched server-side with no size ceiling \u2014 the
|
|
4625
|
+
right choice for anything already hosted (a project's own storage bucket, a public
|
|
4626
|
+
link, a YouTube URL for video/audio). \`--file\` is read and sent inline by the CLI
|
|
4627
|
+
itself, so it's bounded by the daemon's own request-size limit \u2014 for a small local
|
|
4628
|
+
file only (a screenshot, a short voice memo). Something bigger: \`workser storage
|
|
4629
|
+
upload\` it first, then pass the returned URL with \`--url\`.
|
|
4630
|
+
- **\`-t/--task\` shapes the answer, it doesn't gate what you can ask** \u2014 \`general\` (the
|
|
4631
|
+
default) takes any free-form \`<query>\`. The other values just bias the prompt
|
|
4632
|
+
toward a specific shape: \`caption\`/\`visual_qa\`/\`object_detection\`/\`segmentation\`
|
|
4633
|
+
for images; \`summarize\`/\`describe\`/\`visual_qa\`/\`timestamp_analysis\` for video;
|
|
4634
|
+
\`transcribe\`/\`describe\`/\`audio_qa\`/\`speaker_diarization\`/\`emotion_detection\` for
|
|
4635
|
+
audio.
|
|
4636
|
+
- **This is billed to the project's organization**, same as image generation \u2014 it's
|
|
4637
|
+
a real provider call, not free introspection. Don't loop it over every file in a
|
|
4638
|
+
folder "just in case"; use it when you actually need to know what's in one.
|
|
4601
4639
|
`
|
|
4602
4640
|
},
|
|
4603
4641
|
{
|
|
@@ -7498,11 +7536,11 @@ function registerWorkflow(program3) {
|
|
|
7498
7536
|
);
|
|
7499
7537
|
}
|
|
7500
7538
|
|
|
7501
|
-
// src/commands/
|
|
7539
|
+
// src/commands/connection.ts
|
|
7502
7540
|
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
7503
|
-
function
|
|
7504
|
-
const
|
|
7505
|
-
|
|
7541
|
+
function registerConnection(program3) {
|
|
7542
|
+
const connection = program3.command("connection").description("Connect and use third-party app connections (Gmail, Slack, Stripe, ...)");
|
|
7543
|
+
connection.command("list").description("List connectable toolkits and this project's existing connections").option("--toolkit <slug>", "filter connections to one toolkit").action(
|
|
7506
7544
|
action(async ({ ctx, opts }) => {
|
|
7507
7545
|
const projectId = requireProject(ctx);
|
|
7508
7546
|
const [catalog, connections] = await Promise.all([
|
|
@@ -7518,7 +7556,21 @@ function registerApp(program3) {
|
|
|
7518
7556
|
});
|
|
7519
7557
|
})
|
|
7520
7558
|
);
|
|
7521
|
-
|
|
7559
|
+
connection.command("search <query>").description('Full-text search for actions across every toolkit (or one, with --toolkit) \u2014 e.g. "send email"').option("--toolkit <slug>", "narrow the search to one toolkit").option("--limit <n>", "max results").action(
|
|
7560
|
+
action(async ({ ctx, args, opts }) => {
|
|
7561
|
+
const projectId = requireProject(ctx);
|
|
7562
|
+
const items = await api(ctx, `/v1/projects/${projectId}/integrations/search`, {
|
|
7563
|
+
query: { q: args[0], toolkit: opts.toolkit, limit: opts.limit }
|
|
7564
|
+
});
|
|
7565
|
+
ok(items, () => {
|
|
7566
|
+
if (!items?.length) return line(import_picocolors20.default.dim("No matching actions."));
|
|
7567
|
+
for (const t of items) {
|
|
7568
|
+
line(`${t.slug} ${import_picocolors20.default.dim(`[${t.toolkit}]`)} ${t.description ?? ""}`);
|
|
7569
|
+
}
|
|
7570
|
+
});
|
|
7571
|
+
})
|
|
7572
|
+
);
|
|
7573
|
+
connection.command("connect <toolkit>").description("Start OAuth to connect a toolkit").option("--reference-user-id <id>", "connect on behalf of one of the app's own end-users").option("--redirect-url <url>", "where to send the user after OAuth completes").action(
|
|
7522
7574
|
action(async ({ ctx, args, opts }) => {
|
|
7523
7575
|
const projectId = requireProject(ctx);
|
|
7524
7576
|
const res = await api(ctx, `/v1/projects/${projectId}/integrations/connect`, {
|
|
@@ -7534,7 +7586,7 @@ function registerApp(program3) {
|
|
|
7534
7586
|
);
|
|
7535
7587
|
})
|
|
7536
7588
|
);
|
|
7537
|
-
|
|
7589
|
+
connection.command("disconnect <connectionId>").description("Disconnect a connection").action(
|
|
7538
7590
|
action(async ({ ctx, args }) => {
|
|
7539
7591
|
const projectId = requireProject(ctx);
|
|
7540
7592
|
const res = await api(ctx, `/v1/projects/${projectId}/integrations/${args[0]}`, {
|
|
@@ -7543,7 +7595,7 @@ function registerApp(program3) {
|
|
|
7543
7595
|
ok(res, () => line("Disconnected."));
|
|
7544
7596
|
})
|
|
7545
7597
|
);
|
|
7546
|
-
|
|
7598
|
+
connection.command("tools <toolkit>").description("List a connected toolkit's callable tools + their arguments").action(
|
|
7547
7599
|
action(async ({ ctx, args }) => {
|
|
7548
7600
|
const projectId = requireProject(ctx);
|
|
7549
7601
|
const items = await api(ctx, `/v1/projects/${projectId}/integrations/${args[0]}/tools`);
|
|
@@ -7553,7 +7605,7 @@ function registerApp(program3) {
|
|
|
7553
7605
|
});
|
|
7554
7606
|
})
|
|
7555
7607
|
);
|
|
7556
|
-
|
|
7608
|
+
connection.command("run <toolSlug>").description("Execute one tool action (e.g. GOOGLESHEETS_APPEND_ROW)").option("--body <args>", "the tool's arguments as a JSON string", "{}").option("--reference-user-id <id>", "run on behalf of one of the app's own end-users").action(
|
|
7557
7609
|
action(async ({ ctx, args, opts }) => {
|
|
7558
7610
|
const projectId = requireProject(ctx);
|
|
7559
7611
|
const res = await api(ctx, `/v1/projects/${projectId}/integrations/${args[0]}/execute`, {
|
|
@@ -7917,6 +7969,56 @@ function printRun(run) {
|
|
|
7917
7969
|
// src/commands/image.ts
|
|
7918
7970
|
import { writeFile as writeFile4, mkdir } from "fs/promises";
|
|
7919
7971
|
import { dirname as dirname3, resolve as resolve4 } from "path";
|
|
7972
|
+
|
|
7973
|
+
// src/media-source.ts
|
|
7974
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
7975
|
+
import { extname } from "path";
|
|
7976
|
+
var EXT_MIME = {
|
|
7977
|
+
".jpg": "image/jpeg",
|
|
7978
|
+
".jpeg": "image/jpeg",
|
|
7979
|
+
".png": "image/png",
|
|
7980
|
+
".webp": "image/webp",
|
|
7981
|
+
".gif": "image/gif",
|
|
7982
|
+
".heic": "image/heic",
|
|
7983
|
+
".mp4": "video/mp4",
|
|
7984
|
+
".mov": "video/quicktime",
|
|
7985
|
+
".webm": "video/webm",
|
|
7986
|
+
".avi": "video/x-msvideo",
|
|
7987
|
+
".mp3": "audio/mp3",
|
|
7988
|
+
".wav": "audio/wav",
|
|
7989
|
+
".m4a": "audio/mp4",
|
|
7990
|
+
".ogg": "audio/ogg",
|
|
7991
|
+
".flac": "audio/flac",
|
|
7992
|
+
".aac": "audio/aac"
|
|
7993
|
+
};
|
|
7994
|
+
async function resolveMediaSource(opts) {
|
|
7995
|
+
if (opts.url && opts.file) {
|
|
7996
|
+
throw new WorkserError("Pass --url or --file, not both.", {
|
|
7997
|
+
code: "bad_request"
|
|
7998
|
+
});
|
|
7999
|
+
}
|
|
8000
|
+
if (opts.url) {
|
|
8001
|
+
return { type: "url", url: opts.url };
|
|
8002
|
+
}
|
|
8003
|
+
if (opts.file) {
|
|
8004
|
+
let bytes;
|
|
8005
|
+
try {
|
|
8006
|
+
bytes = await readFile4(opts.file);
|
|
8007
|
+
} catch (e) {
|
|
8008
|
+
throw new WorkserError(
|
|
8009
|
+
`Could not read ${opts.file}: ${e instanceof Error ? e.message : String(e)}`,
|
|
8010
|
+
{ code: "bad_request" }
|
|
8011
|
+
);
|
|
8012
|
+
}
|
|
8013
|
+
const mimeType = EXT_MIME[extname(opts.file).toLowerCase()] ?? "application/octet-stream";
|
|
8014
|
+
return { type: "base64", data: bytes.toString("base64"), mimeType };
|
|
8015
|
+
}
|
|
8016
|
+
throw new WorkserError("Pass --url <url> or --file <path> for the media to look at.", {
|
|
8017
|
+
code: "bad_request"
|
|
8018
|
+
});
|
|
8019
|
+
}
|
|
8020
|
+
|
|
8021
|
+
// src/commands/image.ts
|
|
7920
8022
|
function registerImage(program3) {
|
|
7921
8023
|
const image = program3.command("image").description("Generate images from a text prompt");
|
|
7922
8024
|
image.command("generate <prompt>").alias("gen").description("Generate an image and return its public URL").option(
|
|
@@ -7969,6 +8071,23 @@ function registerImage(program3) {
|
|
|
7969
8071
|
});
|
|
7970
8072
|
})
|
|
7971
8073
|
);
|
|
8074
|
+
image.command("understand <query>").description(
|
|
8075
|
+
"Describe/caption/answer questions about an image \u2014 the fallback for a text-only model or an image you have no other way to see"
|
|
8076
|
+
).option("-u, --url <url>", "the image's URL (fetched server-side)").option("-f, --file <path>", "a local image file (read + sent inline; small files only)").option(
|
|
8077
|
+
"-t, --task <task>",
|
|
8078
|
+
"caption | visual_qa | object_detection | segmentation | general",
|
|
8079
|
+
"general"
|
|
8080
|
+
).action(
|
|
8081
|
+
action(async ({ ctx, opts, args }) => {
|
|
8082
|
+
const projectId = requireProject(ctx);
|
|
8083
|
+
const source = await resolveMediaSource({ url: opts.url, file: opts.file });
|
|
8084
|
+
const res = await api(ctx, `/projects/${projectId}/images/understand`, {
|
|
8085
|
+
method: "POST",
|
|
8086
|
+
body: { source, query: args[0], task: opts.task }
|
|
8087
|
+
});
|
|
8088
|
+
ok(res, () => line(res?.answer ?? ""));
|
|
8089
|
+
})
|
|
8090
|
+
);
|
|
7972
8091
|
}
|
|
7973
8092
|
async function download(url, output) {
|
|
7974
8093
|
const target = resolve4(output);
|
|
@@ -7984,6 +8103,46 @@ async function download(url, output) {
|
|
|
7984
8103
|
return target;
|
|
7985
8104
|
}
|
|
7986
8105
|
|
|
8106
|
+
// src/commands/video.ts
|
|
8107
|
+
function registerVideo(program3) {
|
|
8108
|
+
const video = program3.command("video").description("Understand video (the fallback for a model that can't watch it itself)");
|
|
8109
|
+
video.command("understand <query>").description("Summarize/describe/answer questions about a video").option("-u, --url <url>", "the video's URL (fetched server-side; also accepts a YouTube URL)").option("-f, --file <path>", "a local video file (read + sent inline; small clips only)").option(
|
|
8110
|
+
"-t, --task <task>",
|
|
8111
|
+
"summarize | describe | visual_qa | timestamp_analysis | general",
|
|
8112
|
+
"general"
|
|
8113
|
+
).action(
|
|
8114
|
+
action(async ({ ctx, opts, args }) => {
|
|
8115
|
+
const projectId = requireProject(ctx);
|
|
8116
|
+
const source = await resolveMediaSource({ url: opts.url, file: opts.file });
|
|
8117
|
+
const res = await api(ctx, `/projects/${projectId}/video/understand`, {
|
|
8118
|
+
method: "POST",
|
|
8119
|
+
body: { source, query: args[0], task: opts.task }
|
|
8120
|
+
});
|
|
8121
|
+
ok(res, () => line(res?.answer ?? ""));
|
|
8122
|
+
})
|
|
8123
|
+
);
|
|
8124
|
+
}
|
|
8125
|
+
|
|
8126
|
+
// src/commands/audio.ts
|
|
8127
|
+
function registerAudio(program3) {
|
|
8128
|
+
const audio = program3.command("audio").description("Understand audio (the fallback for a model that can't hear it itself)");
|
|
8129
|
+
audio.command("understand <query>").description("Transcribe/describe/answer questions about audio").option("-u, --url <url>", "the audio's URL (fetched server-side; also accepts a YouTube URL)").option("-f, --file <path>", "a local audio file (read + sent inline; small clips only)").option(
|
|
8130
|
+
"-t, --task <task>",
|
|
8131
|
+
"transcribe | describe | audio_qa | speaker_diarization | emotion_detection | general",
|
|
8132
|
+
"general"
|
|
8133
|
+
).action(
|
|
8134
|
+
action(async ({ ctx, opts, args }) => {
|
|
8135
|
+
const projectId = requireProject(ctx);
|
|
8136
|
+
const source = await resolveMediaSource({ url: opts.url, file: opts.file });
|
|
8137
|
+
const res = await api(ctx, `/projects/${projectId}/audio/understand`, {
|
|
8138
|
+
method: "POST",
|
|
8139
|
+
body: { source, query: args[0], task: opts.task }
|
|
8140
|
+
});
|
|
8141
|
+
ok(res, () => line(res?.answer ?? ""));
|
|
8142
|
+
})
|
|
8143
|
+
);
|
|
8144
|
+
}
|
|
8145
|
+
|
|
7987
8146
|
// src/commands/ask.ts
|
|
7988
8147
|
var import_picocolors25 = __toESM(require_picocolors(), 1);
|
|
7989
8148
|
var TYPES = [
|
|
@@ -9841,7 +10000,7 @@ function requireApp(app) {
|
|
|
9841
10000
|
const value = typeof app === "string" ? app.trim() : "";
|
|
9842
10001
|
if (value) return value;
|
|
9843
10002
|
throw new WorkserError(
|
|
9844
|
-
"Which service? Pass --app <webAppId>; `workser
|
|
10003
|
+
"Which service? Pass --app <webAppId>; `workser project apps` shows them.",
|
|
9845
10004
|
{ code: "bad_request" }
|
|
9846
10005
|
);
|
|
9847
10006
|
}
|
|
@@ -10632,7 +10791,7 @@ function colour(d) {
|
|
|
10632
10791
|
|
|
10633
10792
|
// src/index.ts
|
|
10634
10793
|
var pkg = {
|
|
10635
|
-
version: true ? "0.6.
|
|
10794
|
+
version: true ? "0.6.7" : "0.0.0-dev"
|
|
10636
10795
|
};
|
|
10637
10796
|
var program2 = new Command();
|
|
10638
10797
|
program2.name("workser").description(
|
|
@@ -10675,12 +10834,14 @@ registerUsage(program2);
|
|
|
10675
10834
|
registerCheckpoint(program2);
|
|
10676
10835
|
registerSync(program2);
|
|
10677
10836
|
registerWorkflow(program2);
|
|
10678
|
-
|
|
10837
|
+
registerConnection(program2);
|
|
10679
10838
|
registerTool(program2);
|
|
10680
10839
|
registerMemory(program2);
|
|
10681
10840
|
registerBusiness(program2);
|
|
10682
10841
|
registerArtifact(program2);
|
|
10683
10842
|
registerImage(program2);
|
|
10843
|
+
registerVideo(program2);
|
|
10844
|
+
registerAudio(program2);
|
|
10684
10845
|
registerAsk(program2);
|
|
10685
10846
|
registerSearch(program2);
|
|
10686
10847
|
registerBoard(program2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workser/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "Workser CLI — give your local AI agent native DevOps & infrastructure on Workser. The agent runs `workser …` to provision, deploy, and manage real apps.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
topic: automation
|
|
3
3
|
title: Workflows & connected apps
|
|
4
4
|
summary: Build automations that outlive the run; use Gmail, Slack, Stripe, Sheets.
|
|
5
|
-
commands: [workflow,
|
|
5
|
+
commands: [workflow, connection]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Workflows & connected apps
|
|
@@ -16,10 +16,11 @@ workser workflow activate <id> | deactivate <id> | run <id> [--wait] [--body <js
|
|
|
16
16
|
workser workflow runs <id> # past executions of a workflow
|
|
17
17
|
workser workflow nodes [query] # search the node-type catalog
|
|
18
18
|
|
|
19
|
-
workser
|
|
20
|
-
workser
|
|
21
|
-
workser
|
|
22
|
-
workser
|
|
19
|
+
workser connection list [--toolkit <slug>] # connectable + connected third-party apps
|
|
20
|
+
workser connection search "<query>" [--toolkit <slug>] [--limit N] # find an action across every toolkit
|
|
21
|
+
workser connection connect <toolkit> | disconnect <connectionId>
|
|
22
|
+
workser connection tools <toolkit> # browse one connected toolkit's actions
|
|
23
|
+
workser connection run <toolSlug> [--body <json>] # execute one action
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
## Building a workflow
|
|
@@ -35,12 +36,13 @@ Created workflows start inactive: `workser workflow activate <id>` when it's rea
|
|
|
35
36
|
|
|
36
37
|
## Using a connected app
|
|
37
38
|
|
|
38
|
-
1. `workser
|
|
39
|
-
2. If it isn't: `workser
|
|
39
|
+
1. `workser connection list` — check what's already connected before asking for anything.
|
|
40
|
+
2. If it isn't: `workser connection connect <toolkit>` returns an OAuth link. The **user**
|
|
40
41
|
must open it; you cannot complete OAuth on their behalf. Wait, then continue.
|
|
41
|
-
3.
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
3. Don't know the exact action? `workser connection search "<query>"` finds it across
|
|
43
|
+
every toolkit; `workser connection tools <toolkit>` browses one toolkit you already
|
|
44
|
+
know. Either way, read the argument schema rather than guessing field names.
|
|
45
|
+
4. `workser connection run <toolSlug> --body '{"…":…}'` — e.g. `GOOGLESHEETS_APPEND_ROW`,
|
|
44
46
|
`GMAIL_SEND_EMAIL`.
|
|
45
47
|
|
|
46
48
|
**A `run` is a real side effect in someone's real account.** Sending an email or
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
topic: images
|
|
3
|
-
title: Image generation
|
|
4
|
-
summary: Generate images from a prompt,
|
|
5
|
-
commands: [image]
|
|
3
|
+
title: Image generation & media understanding
|
|
4
|
+
summary: Generate images from a prompt; describe/transcribe an image, video, or audio clip you can't natively see or hear.
|
|
5
|
+
commands: [image, video, audio]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Image generation
|
|
8
|
+
# Image generation & media understanding
|
|
9
9
|
|
|
10
10
|
```
|
|
11
11
|
workser image generate "<prompt>" # alias: workser image gen
|
|
@@ -21,7 +21,7 @@ workser image generate "flat illustration of a farm delivery van, brand colors"
|
|
|
21
21
|
workser image gen "same van, from the side" -r https://… -o ./public/van.png --json
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
## Notes that matter
|
|
24
|
+
## Notes that matter (generation)
|
|
25
25
|
|
|
26
26
|
- **Reference images are image-to-image conditioning**, not attachments. Up to 4;
|
|
27
27
|
anything beyond that is dropped.
|
|
@@ -33,3 +33,39 @@ workser image gen "same van, from the side" -r https://… -o ./public/van.png -
|
|
|
33
33
|
exist only as URLs.
|
|
34
34
|
- **Placeholder art is not a deliverable.** Generating a hero image to unblock a
|
|
35
35
|
layout is fine; shipping it as the user's brand asset without asking is not.
|
|
36
|
+
|
|
37
|
+
## Understanding media you can't natively see or hear
|
|
38
|
+
|
|
39
|
+
The fallback for a text-only model, or media you have no other way to reach: describe
|
|
40
|
+
an image, summarize/transcribe a video, transcribe/describe audio. Runs server-side
|
|
41
|
+
(Gemini) — you never need a model key.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
workser image understand "<query>" [--url <u> | --file <p>] [-t <task>]
|
|
45
|
+
workser video understand "<query>" [--url <u> | --file <p>] [-t <task>]
|
|
46
|
+
workser audio understand "<query>" [--url <u> | --file <p>] [-t <task>]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
workser image understand "what's wrong with this layout?" --url https://…/screenshot.png --json
|
|
51
|
+
workser video understand "what happens at the end?" --url https://youtu.be/… -t timestamp_analysis --json
|
|
52
|
+
workser audio understand "transcribe this" --file ./voicemail.m4a -t transcribe --json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Notes that matter (understanding)
|
|
56
|
+
|
|
57
|
+
- **`--url` vs `--file`**: `--url` is fetched server-side with no size ceiling — the
|
|
58
|
+
right choice for anything already hosted (a project's own storage bucket, a public
|
|
59
|
+
link, a YouTube URL for video/audio). `--file` is read and sent inline by the CLI
|
|
60
|
+
itself, so it's bounded by the daemon's own request-size limit — for a small local
|
|
61
|
+
file only (a screenshot, a short voice memo). Something bigger: `workser storage
|
|
62
|
+
upload` it first, then pass the returned URL with `--url`.
|
|
63
|
+
- **`-t/--task` shapes the answer, it doesn't gate what you can ask** — `general` (the
|
|
64
|
+
default) takes any free-form `<query>`. The other values just bias the prompt
|
|
65
|
+
toward a specific shape: `caption`/`visual_qa`/`object_detection`/`segmentation`
|
|
66
|
+
for images; `summarize`/`describe`/`visual_qa`/`timestamp_analysis` for video;
|
|
67
|
+
`transcribe`/`describe`/`audio_qa`/`speaker_diarization`/`emotion_detection` for
|
|
68
|
+
audio.
|
|
69
|
+
- **This is billed to the project's organization**, same as image generation — it's
|
|
70
|
+
a real provider call, not free introspection. Don't loop it over every file in a
|
|
71
|
+
folder "just in case"; use it when you actually need to know what's in one.
|