@workser/cli 0.1.0 → 0.2.1
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 +35 -0
- package/README.md +16 -1
- package/dist/index.js +1191 -137
- package/package.json +4 -3
- package/skills/workser/SKILL.md +57 -164
- package/skills/workser/reference/automation.md +55 -0
- package/skills/workser/reference/business-data.md +51 -0
- package/skills/workser/reference/computer-use.md +35 -0
- package/skills/workser/reference/database.md +41 -0
- package/skills/workser/reference/deliverables.md +65 -0
- package/skills/workser/reference/deploy.md +56 -0
- package/skills/workser/reference/images.md +35 -0
- package/skills/workser/reference/memory.md +34 -0
- package/skills/workser/reference/neon-backend.md +42 -0
- package/skills/workser/reference/roles.md +33 -0
- package/skills/workser/reference/storage.md +37 -0
package/dist/index.js
CHANGED
|
@@ -3650,16 +3650,580 @@ function exitCodeFor(e) {
|
|
|
3650
3650
|
return 1;
|
|
3651
3651
|
}
|
|
3652
3652
|
|
|
3653
|
-
// src/commands/
|
|
3653
|
+
// src/commands/help.ts
|
|
3654
3654
|
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
3655
3655
|
|
|
3656
|
+
// src/help-content.ts
|
|
3657
|
+
var HELP_TOPICS = [
|
|
3658
|
+
{
|
|
3659
|
+
topic: "automation",
|
|
3660
|
+
title: "Workflows & connected apps",
|
|
3661
|
+
summary: "Build automations that outlive the run; use Gmail, Slack, Stripe, Sheets.",
|
|
3662
|
+
commands: ["workflow", "app"],
|
|
3663
|
+
source: "skills/workser/reference/automation.md",
|
|
3664
|
+
body: `# Workflows & connected apps
|
|
3665
|
+
|
|
3666
|
+
Wire up **automations** that keep running after you're done, and use third-party
|
|
3667
|
+
accounts (Gmail, Slack, Stripe, Google Sheets) the project has connected.
|
|
3668
|
+
|
|
3669
|
+
\`\`\`
|
|
3670
|
+
workser workflow list | create <name> [--body <json>] | get <id>
|
|
3671
|
+
workser workflow activate <id> | deactivate <id> | run <id> [--wait] [--body <json>]
|
|
3672
|
+
workser workflow runs <id> # past executions of a workflow
|
|
3673
|
+
workser workflow nodes [query] # search the node-type catalog
|
|
3674
|
+
|
|
3675
|
+
workser app list [--toolkit <slug>] # connectable + connected third-party apps
|
|
3676
|
+
workser app connect <toolkit> | disconnect <connectionId>
|
|
3677
|
+
workser app tools <toolkit> # a connected app's callable actions
|
|
3678
|
+
workser app run <toolSlug> [--body <json>] # execute one action
|
|
3679
|
+
\`\`\`
|
|
3680
|
+
|
|
3681
|
+
## Building a workflow
|
|
3682
|
+
|
|
3683
|
+
\`workser workflow create\` builds an event-driven, multi-step automation \u2014 the same
|
|
3684
|
+
engine Workser's own web Workflow tab uses. Nodes, connections and triggers go in
|
|
3685
|
+
\`--body\` as JSON.
|
|
3686
|
+
|
|
3687
|
+
**Browse \`workser workflow nodes\` first.** Inventing a node type that doesn't exist
|
|
3688
|
+
produces a workflow that saves and then never runs.
|
|
3689
|
+
|
|
3690
|
+
Created workflows start inactive: \`workser workflow activate <id>\` when it's ready.
|
|
3691
|
+
|
|
3692
|
+
## Using a connected app
|
|
3693
|
+
|
|
3694
|
+
1. \`workser app list\` \u2014 check what's already connected before asking for anything.
|
|
3695
|
+
2. If it isn't: \`workser app connect <toolkit>\` returns an OAuth link. The **user**
|
|
3696
|
+
must open it; you cannot complete OAuth on their behalf. Wait, then continue.
|
|
3697
|
+
3. \`workser app tools <toolkit>\` \u2014 read the argument schema rather than guessing
|
|
3698
|
+
field names.
|
|
3699
|
+
4. \`workser app run <toolSlug> --body '{"\u2026":\u2026}'\` \u2014 e.g. \`GOOGLESHEETS_APPEND_ROW\`,
|
|
3700
|
+
\`GMAIL_SEND_EMAIL\`.
|
|
3701
|
+
|
|
3702
|
+
**A \`run\` is a real side effect in someone's real account.** Sending an email or
|
|
3703
|
+
charging a card is not a dry run \u2014 say what you're about to do before you do it.
|
|
3704
|
+
|
|
3705
|
+
## The half people forget
|
|
3706
|
+
|
|
3707
|
+
A workflow-backed feature is two-way. Triggering it is the outbound half; when the
|
|
3708
|
+
workflow produces a result the app needs, its final node has to POST back to a
|
|
3709
|
+
webhook route in the app. Build only the trigger and the workflow runs perfectly
|
|
3710
|
+
while nothing ever appears in the product. The app-side receiver is covered in the
|
|
3711
|
+
\`workser-sdk\` skill under workflows.
|
|
3712
|
+
`
|
|
3713
|
+
},
|
|
3714
|
+
{
|
|
3715
|
+
topic: "business",
|
|
3716
|
+
title: "Business hub data",
|
|
3717
|
+
summary: "Products, orders, customers, sales, content, marketing, support, analytics.",
|
|
3718
|
+
commands: ["business"],
|
|
3719
|
+
source: "skills/workser/reference/business-data.md",
|
|
3720
|
+
body: `# Business hub data
|
|
3721
|
+
|
|
3722
|
+
The project's own commerce and CRM records \u2014 the **same rows** the Orbit desktop
|
|
3723
|
+
Business tab and workser-web's Business hub show. One generic CRUD surface across
|
|
3724
|
+
every resource rather than a command per domain.
|
|
3725
|
+
|
|
3726
|
+
\`\`\`
|
|
3727
|
+
workser business resources # the known resource names
|
|
3728
|
+
workser business list <resource> [subpath] # list/read (--query '<json>')
|
|
3729
|
+
workser business get <resource> <id>
|
|
3730
|
+
workser business create <resource> --body '<json>'
|
|
3731
|
+
workser business update <resource> <id> --body '<json>'
|
|
3732
|
+
workser business delete <resource> <id>
|
|
3733
|
+
workser business action <resource> <id> <verb> # POST .../<id>/<verb>
|
|
3734
|
+
\`\`\`
|
|
3735
|
+
|
|
3736
|
+
## Resources
|
|
3737
|
+
|
|
3738
|
+
\`business-config\`, \`business-settings\`, \`products\`, \`collections\`, \`navigations\`,
|
|
3739
|
+
\`orders\`, \`customers\`, \`sales-pipelines\`, \`sales-deals\`, \`pages\`, \`blog-posts\`,
|
|
3740
|
+
\`media\`, \`campaigns\`, \`email-templates\`, \`discounts\`, \`seo-configs\`,
|
|
3741
|
+
\`social-accounts\`, \`support-conversations\`, \`automation-rules\`, \`analytics\`.
|
|
3742
|
+
|
|
3743
|
+
Run \`workser business resources --json\` rather than trusting this list \u2014 the CLI's
|
|
3744
|
+
copy is the current one.
|
|
3745
|
+
|
|
3746
|
+
## Notes that matter
|
|
3747
|
+
|
|
3748
|
+
- **Sales and Support nest.** \`sales-deals\` maps to \`/sales/deals\`,
|
|
3749
|
+
\`support-conversations\` to \`/support/conversations\`. Use the dashed names above;
|
|
3750
|
+
the flat \`resource/id\` shape then works for \`get\`/\`update\`/\`delete\`/\`action\`.
|
|
3751
|
+
- **\`list\` takes a raw subpath** for anything the map doesn't cover:
|
|
3752
|
+
\`workser business list sales-pipelines <id>/stages\`.
|
|
3753
|
+
- **\`action\` is for named verbs** \u2014 \`workser business action orders <id> cancel\`,
|
|
3754
|
+
\`workser business action sales-deals <dealId> win\`. Check the resource's routes
|
|
3755
|
+
before inventing a verb.
|
|
3756
|
+
- **These are real business records.** Cancelling an order or deleting a customer is
|
|
3757
|
+
not a dry run. Say what you're about to do first.
|
|
3758
|
+
|
|
3759
|
+
## This is for you, not for the app
|
|
3760
|
+
|
|
3761
|
+
\`workser business\` is how **you** inspect and fix data while building. The app reads
|
|
3762
|
+
the same records at runtime through \`workser.business\` in \`@workser/app\` \u2014 see the
|
|
3763
|
+
\`workser-sdk\` skill. An app shelling out to this CLI per request is wrong.
|
|
3764
|
+
`
|
|
3765
|
+
},
|
|
3766
|
+
{
|
|
3767
|
+
topic: "computer-use",
|
|
3768
|
+
title: "Computer-use tools",
|
|
3769
|
+
summary: "Files, shell, screen, input, clipboard and browser on this machine.",
|
|
3770
|
+
commands: ["tool"],
|
|
3771
|
+
source: "skills/workser/reference/computer-use.md",
|
|
3772
|
+
body: `# Computer-use tools \u2014 your hands on this machine
|
|
3773
|
+
|
|
3774
|
+
\`\`\`
|
|
3775
|
+
workser tool list # what's available to you right now
|
|
3776
|
+
workser tool run <name> [--body <json>] # run one
|
|
3777
|
+
\`\`\`
|
|
3778
|
+
|
|
3779
|
+
\`workser tool list\` shows what's available \u2014 filesystem (read/write/list/delete/move),
|
|
3780
|
+
shell (run a command / Python / Node), screenshots and screen info, mouse and keyboard
|
|
3781
|
+
input, clipboard, notifications, and basic browser control (open a URL, read the page,
|
|
3782
|
+
click, fill, type, screenshot).
|
|
3783
|
+
|
|
3784
|
+
This is the **same engine** Workser's cloud Computer Use agent uses when it controls a
|
|
3785
|
+
user's machine remotely \u2014 you're getting it locally, gated by the same safety policy.
|
|
3786
|
+
|
|
3787
|
+
## Notes that matter
|
|
3788
|
+
|
|
3789
|
+
- **Check \`tool list\` rather than assuming a capability exists.** This is a curated
|
|
3790
|
+
subset, not full desktop automation.
|
|
3791
|
+
- **The safety policy applies.** Blocked paths (\`~/.ssh\` and friends), blocked
|
|
3792
|
+
destructive commands, rate limits. Refusals are the policy working, not a bug to
|
|
3793
|
+
route around.
|
|
3794
|
+
- **Sensitive actions are approval-gated.** Writing or deleting files, running a shell
|
|
3795
|
+
command, clicking or typing may return \`awaiting_approval\` (exit 5) \u2014 tell the user
|
|
3796
|
+
to approve in Orbit, then retry.
|
|
3797
|
+
- **You already have your own tools.** For editing files in this repo, use them. Reach
|
|
3798
|
+
for \`workser tool\` when you need something *outside* the project \u2014 the screen, the
|
|
3799
|
+
clipboard, a browser, another app on the machine.
|
|
3800
|
+
`
|
|
3801
|
+
},
|
|
3802
|
+
{
|
|
3803
|
+
topic: "database",
|
|
3804
|
+
title: "Database & end users",
|
|
3805
|
+
summary: "Provision Postgres, browse tables, run SQL, provision auth.",
|
|
3806
|
+
commands: ["db", "auth"],
|
|
3807
|
+
source: "skills/workser/reference/database.md",
|
|
3808
|
+
body: `# Database & end users
|
|
3809
|
+
|
|
3810
|
+
The project's Postgres (Neon behind Workser) and its end-user auth. Provisioning is
|
|
3811
|
+
idempotent \u2014 running \`create\` twice is safe.
|
|
3812
|
+
|
|
3813
|
+
\`\`\`
|
|
3814
|
+
workser db create # provision the Neon Postgres database (idempotent)
|
|
3815
|
+
workser db url # connection string (sensitive; least-privilege role)
|
|
3816
|
+
workser db list # database status
|
|
3817
|
+
workser db tables # list tables in the database
|
|
3818
|
+
workser db schema <table> # a table's columns
|
|
3819
|
+
workser db data <table> [-n N] [--offset N] # read rows
|
|
3820
|
+
workser db query "<sql>" # run SQL (writes are approval-gated)
|
|
3821
|
+
|
|
3822
|
+
workser auth enable # provision auth for the project (idempotent)
|
|
3823
|
+
workser auth status # is auth enabled? + Neon auth mode
|
|
3824
|
+
\`\`\`
|
|
3825
|
+
|
|
3826
|
+
## Notes that matter
|
|
3827
|
+
|
|
3828
|
+
- **\`db url\` is a credential.** Don't print it into the conversation, don't paste it
|
|
3829
|
+
into a file the user will commit. The app gets it from its environment already.
|
|
3830
|
+
- **Writes are approval-gated.** A \`db query\` that mutates may return
|
|
3831
|
+
\`awaiting_approval\` (exit 5). Ask the user to approve in Orbit, then retry.
|
|
3832
|
+
- **\`DROP\` / \`TRUNCATE\` are refused** by the safety policy. Change schema with a
|
|
3833
|
+
migration in the app's own migration folder, not with a destructive one-off.
|
|
3834
|
+
- **The database is the project's, not the app's.** Sibling apps in the same project
|
|
3835
|
+
share it. Don't assume a table is yours because you created it.
|
|
3836
|
+
|
|
3837
|
+
## Reading rows vs. reading data at runtime
|
|
3838
|
+
|
|
3839
|
+
\`db data\` / \`db query\` are for **you**, inspecting while you build. The app itself
|
|
3840
|
+
should read through \`@workser/app\` (\`workser.db\`, \`workser.business\`) \u2014 see the
|
|
3841
|
+
\`workser-sdk\` skill. An app that shells out to the CLI at request time is wrong.
|
|
3842
|
+
`
|
|
3843
|
+
},
|
|
3844
|
+
{
|
|
3845
|
+
topic: "deliverables",
|
|
3846
|
+
title: "Deliverables & asking the user",
|
|
3847
|
+
summary: "Record finished output on the task, and ask a blocking question.",
|
|
3848
|
+
commands: ["artifact", "ask"],
|
|
3849
|
+
source: "skills/workser/reference/deliverables.md",
|
|
3850
|
+
body: `# Deliverables & asking the user
|
|
3851
|
+
|
|
3852
|
+
Two things that reach the user directly: what you produced, and what you need from
|
|
3853
|
+
them.
|
|
3854
|
+
|
|
3855
|
+
\`\`\`
|
|
3856
|
+
workser artifact add <path> [--kind <k>] [-d <text>] # record a finished deliverable
|
|
3857
|
+
workser artifact add --url <url> --kind app # record a deployed app
|
|
3858
|
+
workser artifact run # which task you're attached to
|
|
3859
|
+
|
|
3860
|
+
workser ask "<question>" [--type <t>] [--option <o>] # ask the user, WAIT for the answer
|
|
3861
|
+
\`\`\`
|
|
3862
|
+
|
|
3863
|
+
## Record what you produced
|
|
3864
|
+
|
|
3865
|
+
Workser shows the user a **Deliverables** list on the task. If you don't say what you
|
|
3866
|
+
made, it has to guess \u2014 it watches your file edits and treats any path it sees as a
|
|
3867
|
+
deliverable, so scratch files and half-finished drafts show up next to the real
|
|
3868
|
+
output, and things that aren't files at all (a folder of results, a deployed app)
|
|
3869
|
+
can't show up correctly.
|
|
3870
|
+
|
|
3871
|
+
\`\`\`
|
|
3872
|
+
workser artifact add ./report.pdf -d "Q3 sales summary"
|
|
3873
|
+
workser artifact add ./exports --kind folder -d "generated CSVs"
|
|
3874
|
+
workser artifact add --url https://acme.workser.app --kind app -t "Storefront"
|
|
3875
|
+
\`\`\`
|
|
3876
|
+
|
|
3877
|
+
Only register **finished** output the user should get \u2014 not temp files, not
|
|
3878
|
+
intermediate steps. \`--kind\` is inferred from the path when you omit it (directories
|
|
3879
|
+
are detected automatically); pass it explicitly for \`app\` / \`url\`.
|
|
3880
|
+
|
|
3881
|
+
To publish an app: \`workser deploy\` (preview) or \`workser deploy --prod\` (live), then
|
|
3882
|
+
register the URL it returns as an \`app\` artifact so the user can open it from the task.
|
|
3883
|
+
|
|
3884
|
+
## Ask the user something (and get an answer back)
|
|
3885
|
+
|
|
3886
|
+
When you're blocked \u2014 a missing value, an ambiguous requirement, permission for
|
|
3887
|
+
something consequential \u2014 don't guess, and don't just write the question into your
|
|
3888
|
+
final message where nobody will answer it.
|
|
3889
|
+
|
|
3890
|
+
\`\`\`
|
|
3891
|
+
workser ask "Which email should order confirmations come from?"
|
|
3892
|
+
workser ask "Which plan should I wire up?" --option Free --option Pro --option Team
|
|
3893
|
+
workser ask "Delete the 1,240 archived rows?" --type approval
|
|
3894
|
+
\`\`\`
|
|
3895
|
+
|
|
3896
|
+
This shows the user a real card in the conversation and **blocks until they answer**,
|
|
3897
|
+
then prints their answer \u2014 so you ask, read the reply, and keep working in the same
|
|
3898
|
+
turn.
|
|
3899
|
+
|
|
3900
|
+
Types: \`input\` (default, free text), \`choice\` (with \`--option\`), \`approval\`
|
|
3901
|
+
(permission), \`confirmation\` (check an assumption), \`information\` (FYI, no answer
|
|
3902
|
+
needed). It times out (default 10 min) rather than hanging forever; if it does, carry
|
|
3903
|
+
on and state clearly what you assumed.
|
|
3904
|
+
|
|
3905
|
+
**Never ask for a secret value this way** \u2014 the answer is stored and displayed. Ask
|
|
3906
|
+
*where* a key should go, then have the user set it (\`workser env set\` writes it
|
|
3907
|
+
without you ever seeing it).
|
|
3908
|
+
`
|
|
3909
|
+
},
|
|
3910
|
+
{
|
|
3911
|
+
topic: "deploy",
|
|
3912
|
+
title: "Deploy, environment variables & logs",
|
|
3913
|
+
summary: "Ship the app, configure it, and find out why it is down.",
|
|
3914
|
+
commands: ["deploy", "env", "logs", "versions", "domain", "open", "verify"],
|
|
3915
|
+
source: "skills/workser/reference/deploy.md",
|
|
3916
|
+
body: `# Deploy, environment variables & logs
|
|
3917
|
+
|
|
3918
|
+
Getting the app online and configured, and finding out why it isn't.
|
|
3919
|
+
|
|
3920
|
+
\`\`\`
|
|
3921
|
+
workser deploy [--prod] [--watch] # deploy (git \u2192 Vercel); --watch waits for live URL
|
|
3922
|
+
workser deploy status [id] # status of a deploy (default: latest)
|
|
3923
|
+
workser logs [-n 100] [-f] # recent logs
|
|
3924
|
+
workser versions # deploy history
|
|
3925
|
+
workser domain list # custom domains (read)
|
|
3926
|
+
workser open # open the live app
|
|
3927
|
+
workser verify # run typecheck/lint/build
|
|
3928
|
+
|
|
3929
|
+
workser env set KEY=VALUE [K2=V2\u2026] # set env vars
|
|
3930
|
+
workser env list # list keys (values masked)
|
|
3931
|
+
workser env get KEY # one value (sensitive)
|
|
3932
|
+
\`\`\`
|
|
3933
|
+
|
|
3934
|
+
## Notes that matter
|
|
3935
|
+
|
|
3936
|
+
- **\`verify\` gates "done".** Run \`workser verify --json\` before you say a task is
|
|
3937
|
+
finished. \`"ok": false\` means fix the listed errors and re-run \u2014 a green build is
|
|
3938
|
+
the bar, not your reading of the diff.
|
|
3939
|
+
- **\`deploy\` without \`--prod\` is a preview.** Preview first when the change is
|
|
3940
|
+
risky; \`--prod\` puts it in front of real users.
|
|
3941
|
+
- **\`--watch\` blocks until there's a live URL.** Without it you get a deploy id and
|
|
3942
|
+
have to poll \`deploy status\`.
|
|
3943
|
+
- **\`env set\` writes a value you never see.** That's the point \u2014 when the user has
|
|
3944
|
+
a secret, have them run it (or set it in Orbit) rather than pasting it to you.
|
|
3945
|
+
- **\`env get\` returns a secret.** Don't echo it into the conversation.
|
|
3946
|
+
- **\`env rm\` and \`domain set\` are owner-only** (exit 6). Tell the user to do it in
|
|
3947
|
+
Orbit; don't look for a workaround.
|
|
3948
|
+
|
|
3949
|
+
## After a successful deploy
|
|
3950
|
+
|
|
3951
|
+
Register the URL so it shows up on the user's task:
|
|
3952
|
+
|
|
3953
|
+
\`\`\`
|
|
3954
|
+
workser artifact add --url https://acme.workser.app --kind app -t "Storefront"
|
|
3955
|
+
\`\`\`
|
|
3956
|
+
|
|
3957
|
+
See \`reference/deliverables.md\`.
|
|
3958
|
+
|
|
3959
|
+
## Local vs cloud environment
|
|
3960
|
+
|
|
3961
|
+
\`env set\` configures the **cloud** environment (production and preview). The \`.env\`
|
|
3962
|
+
files in the app folder configure **this computer** \u2014 the user edits those in Orbit
|
|
3963
|
+
under Settings \u2192 "On this computer", and saving there restarts the dev server. Don't
|
|
3964
|
+
hand-edit \`.env.local\` to change cloud behaviour; they are different environments.
|
|
3965
|
+
`
|
|
3966
|
+
},
|
|
3967
|
+
{
|
|
3968
|
+
topic: "images",
|
|
3969
|
+
title: "Image generation",
|
|
3970
|
+
summary: "Generate images from a prompt, optionally conditioned on existing images.",
|
|
3971
|
+
commands: ["image"],
|
|
3972
|
+
source: "skills/workser/reference/images.md",
|
|
3973
|
+
body: `# Image generation
|
|
3974
|
+
|
|
3975
|
+
\`\`\`
|
|
3976
|
+
workser image generate "<prompt>" # alias: workser image gen
|
|
3977
|
+
-r, --reference <url...> # condition on existing images (up to 4)
|
|
3978
|
+
-o, --output <path> # also download the first image locally
|
|
3979
|
+
\`\`\`
|
|
3980
|
+
|
|
3981
|
+
Returns the generated image's public URL, so the usual move is to generate, then use
|
|
3982
|
+
that URL directly in the app.
|
|
3983
|
+
|
|
3984
|
+
\`\`\`bash
|
|
3985
|
+
workser image generate "flat illustration of a farm delivery van, brand colors" --json
|
|
3986
|
+
workser image gen "same van, from the side" -r https://\u2026 -o ./public/van.png --json
|
|
3987
|
+
\`\`\`
|
|
3988
|
+
|
|
3989
|
+
## Notes that matter
|
|
3990
|
+
|
|
3991
|
+
- **Reference images are image-to-image conditioning**, not attachments. Up to 4;
|
|
3992
|
+
anything beyond that is dropped.
|
|
3993
|
+
- **The model sometimes narrates instead of drawing** \u2014 a refusal or a clarifying
|
|
3994
|
+
question comes back as text rather than an image. Check that you actually got an
|
|
3995
|
+
image before wiring the URL into a page; an empty result is not a transport error
|
|
3996
|
+
to retry.
|
|
3997
|
+
- **\`--output\` writes only the first image.** If you asked for several, the rest
|
|
3998
|
+
exist only as URLs.
|
|
3999
|
+
- **Placeholder art is not a deliverable.** Generating a hero image to unblock a
|
|
4000
|
+
layout is fine; shipping it as the user's brand asset without asking is not.
|
|
4001
|
+
`
|
|
4002
|
+
},
|
|
4003
|
+
{
|
|
4004
|
+
topic: "memory",
|
|
4005
|
+
title: "Memory across conversations",
|
|
4006
|
+
summary: "Store and recall durable project knowledge shared with cloud agents.",
|
|
4007
|
+
commands: ["memory"],
|
|
4008
|
+
source: "skills/workser/reference/memory.md",
|
|
4009
|
+
body: `# Memory \u2014 remember across conversations, not just this one
|
|
4010
|
+
|
|
4011
|
+
\`\`\`
|
|
4012
|
+
workser memory add "<content>" [--metadata <json>] # remember for future conversations
|
|
4013
|
+
workser memory search "<query>" [--limit N] # recall what was learned before
|
|
4014
|
+
workser memory forget <memoryId> # soft-delete an outdated memory
|
|
4015
|
+
\`\`\`
|
|
4016
|
+
|
|
4017
|
+
## Why this exists
|
|
4018
|
+
|
|
4019
|
+
Every conversation you run is otherwise a fresh start \u2014 no memory of what you or the
|
|
4020
|
+
user decided last time. This stores durable, searchable memory for the **current
|
|
4021
|
+
project**, and it is the **same memory space** Workser's cloud agents write to for
|
|
4022
|
+
this project. Add something here and a cloud agent \u2014 or your own next conversation \u2014
|
|
4023
|
+
can \`memory search\` and find it.
|
|
4024
|
+
|
|
4025
|
+
## Using it well
|
|
4026
|
+
|
|
4027
|
+
- **Search before assuming you don't know.** Before concluding something about this
|
|
4028
|
+
project is undocumented, run \`workser memory search "<topic>"\`. It may already be
|
|
4029
|
+
recorded.
|
|
4030
|
+
- **Store decisions, not chatter.** User preferences, decisions made, constraints,
|
|
4031
|
+
requirements \u2014 things worth knowing next week. Not "the build passed".
|
|
4032
|
+
- **\`forget\` is a soft delete.** The content stays retrievable by id but is excluded
|
|
4033
|
+
from future searches. Use it when something is wrong or outdated, rather than
|
|
4034
|
+
adding a contradicting memory on top.
|
|
4035
|
+
- **Never store a secret.** Memory is retrievable and displayable.
|
|
4036
|
+
`
|
|
4037
|
+
},
|
|
4038
|
+
{
|
|
4039
|
+
topic: "neon",
|
|
4040
|
+
title: "The project's own Neon backend",
|
|
4041
|
+
summary: "Neon-branch object storage and functions. Dedicated tenancy only.",
|
|
4042
|
+
commands: ["neon"],
|
|
4043
|
+
source: "skills/workser/reference/neon-backend.md",
|
|
4044
|
+
body: `# The project's own Neon backend
|
|
4045
|
+
|
|
4046
|
+
S3-compatible object storage and Node.js HTTP functions on the project's own Neon
|
|
4047
|
+
branch \u2014 they branch with the database. **Additive** infrastructure, not a
|
|
4048
|
+
replacement for \`workser storage\`.
|
|
4049
|
+
|
|
4050
|
+
\`\`\`
|
|
4051
|
+
workser neon status # tenancy + toggles + region verdict
|
|
4052
|
+
workser neon storage list | create <name> | rm <bucket>
|
|
4053
|
+
workser neon storage ls <bucket> [prefix]
|
|
4054
|
+
workser neon storage put <bucket> <local> [key]
|
|
4055
|
+
workser neon storage get <bucket> <key> [dest]
|
|
4056
|
+
workser neon storage url <bucket> <key> # temporary download URL
|
|
4057
|
+
workser neon functions list | deploy <slug> <zip> | rm <slug>
|
|
4058
|
+
\`\`\`
|
|
4059
|
+
|
|
4060
|
+
## Check \`neon status\` first \u2014 always
|
|
4061
|
+
|
|
4062
|
+
Three things must all be true: **dedicated tenancy**, the capability **switched on**,
|
|
4063
|
+
and a **supported region**.
|
|
4064
|
+
|
|
4065
|
+
Region is fixed when the project is created. \`regionSupportsNeonBackend: false\` is
|
|
4066
|
+
**final, not retryable** \u2014 no amount of waiting or retrying changes it. When you see
|
|
4067
|
+
it, say so plainly and fall back to \`workser storage\` (the default bucket).
|
|
4068
|
+
|
|
4069
|
+
## Notes that matter
|
|
4070
|
+
|
|
4071
|
+
- **\`neon storage rm <bucket>\` deletes the bucket and everything in it.** Not
|
|
4072
|
+
reversible. Confirm with the user first.
|
|
4073
|
+
- **\`neon storage url\`** issues a short-lived URL \u2014 prefer it to moving large files
|
|
4074
|
+
through Workser.
|
|
4075
|
+
- **Functions deploy from a zip.** Build the bundle first, then
|
|
4076
|
+
\`workser neon functions deploy <slug> <zip>\`.
|
|
4077
|
+
- **Most apps don't need this.** If the user just wants to store uploads, the default
|
|
4078
|
+
bucket in \`reference/storage.md\` is the answer.
|
|
4079
|
+
`
|
|
4080
|
+
},
|
|
4081
|
+
{
|
|
4082
|
+
topic: "roles",
|
|
4083
|
+
title: "Delegate to roles",
|
|
4084
|
+
summary: "Hand a focused subtask to another configured local agent.",
|
|
4085
|
+
commands: ["agent"],
|
|
4086
|
+
source: "skills/workser/reference/roles.md",
|
|
4087
|
+
body: `# Delegate to roles
|
|
4088
|
+
|
|
4089
|
+
The user can configure **roles** \u2014 named specialists each backed by a local CLI agent
|
|
4090
|
+
(e.g. \`qa\` \u2192 codex, \`designer\` \u2192 claude_code).
|
|
4091
|
+
|
|
4092
|
+
\`\`\`
|
|
4093
|
+
workser agent list # main agent + configured roles (+ which are runnable)
|
|
4094
|
+
workser agent run <role> "<task>" # delegate a focused subtask (runs isolated)
|
|
4095
|
+
workser agent main # show the configured main agent
|
|
4096
|
+
\`\`\`
|
|
4097
|
+
|
|
4098
|
+
## How to use it
|
|
4099
|
+
|
|
4100
|
+
Run \`workser agent list --json\` first \u2014 it tells you which roles exist **and** which
|
|
4101
|
+
are actually runnable on this machine. Delegating to a role that isn't installed just
|
|
4102
|
+
fails.
|
|
4103
|
+
|
|
4104
|
+
\`workser agent run <role> "<task>" --json\` runs the role as an **isolated local
|
|
4105
|
+
subagent** with its own context and returns \`{role, agent, output, exitCode}\`.
|
|
4106
|
+
|
|
4107
|
+
- Hand off focused subtasks \u2014 review this diff, design this screen \u2014 to keep your own
|
|
4108
|
+
context lean and get a specialized second perspective.
|
|
4109
|
+
- **A non-zero \`exitCode\` means the role's run failed.** Surface that; don't quietly
|
|
4110
|
+
treat empty output as success.
|
|
4111
|
+
- The subagent doesn't share your context. Put everything it needs in the task
|
|
4112
|
+
string; it cannot see the conversation you're in.
|
|
4113
|
+
`
|
|
4114
|
+
},
|
|
4115
|
+
{
|
|
4116
|
+
topic: "storage",
|
|
4117
|
+
title: "File storage",
|
|
4118
|
+
summary: "The project's default bucket \u2014 upload, list, download.",
|
|
4119
|
+
commands: ["storage"],
|
|
4120
|
+
source: "skills/workser/reference/storage.md",
|
|
4121
|
+
body: `# File storage
|
|
4122
|
+
|
|
4123
|
+
The project's default bucket (Cloudflare R2 behind Workser). Every project gets one;
|
|
4124
|
+
\`create\` is idempotent.
|
|
4125
|
+
|
|
4126
|
+
\`\`\`
|
|
4127
|
+
workser storage create [name] # provision the bucket (idempotent)
|
|
4128
|
+
workser storage list # the project's bucket
|
|
4129
|
+
workser storage ls [prefix] # list objects in the bucket
|
|
4130
|
+
workser storage put <local> <key> # upload a file into the bucket
|
|
4131
|
+
workser storage get <key> [dest] # download an object (or print its URL)
|
|
4132
|
+
\`\`\`
|
|
4133
|
+
|
|
4134
|
+
## Notes that matter
|
|
4135
|
+
|
|
4136
|
+
- **One bucket per project, shared by its apps.** Namespace your keys by app or
|
|
4137
|
+
feature (\`invoices/2026/\u2026\`) rather than assuming the root is yours.
|
|
4138
|
+
- **\`storage get\` with no destination prints a URL** instead of writing a file \u2014
|
|
4139
|
+
useful when you just want to hand the user something to click.
|
|
4140
|
+
- **This is not where app uploads should go through you.** At runtime the app uses
|
|
4141
|
+
\`workser.storage\` from \`@workser/app\`, and for anything large it should request a
|
|
4142
|
+
presigned upload URL so the bytes never pass through Workser. See the
|
|
4143
|
+
\`workser-sdk\` skill.
|
|
4144
|
+
|
|
4145
|
+
## Not the same as \`workser neon storage\`
|
|
4146
|
+
|
|
4147
|
+
\`storage\` is the default R2 bucket every project has. \`neon storage\` is additive
|
|
4148
|
+
infrastructure on the project's own Neon branch, available only on dedicated tenancy
|
|
4149
|
+
in a supported region. They are different stores \u2014 a file put in one is not visible
|
|
4150
|
+
in the other. See \`reference/neon-backend.md\`.
|
|
4151
|
+
`
|
|
4152
|
+
}
|
|
4153
|
+
];
|
|
4154
|
+
|
|
4155
|
+
// src/commands/help.ts
|
|
4156
|
+
function findTopic(name) {
|
|
4157
|
+
const wanted = name.trim().toLowerCase();
|
|
4158
|
+
return HELP_TOPICS.find((t) => t.topic === wanted) ?? // A command name is what an agent reaches for first — `workser help db`
|
|
4159
|
+
// should not be a dead end just because the topic is called "database".
|
|
4160
|
+
HELP_TOPICS.find((t) => t.commands.includes(wanted));
|
|
4161
|
+
}
|
|
4162
|
+
function listTopics() {
|
|
4163
|
+
ok(
|
|
4164
|
+
HELP_TOPICS.map((t) => ({
|
|
4165
|
+
topic: t.topic,
|
|
4166
|
+
title: t.title,
|
|
4167
|
+
summary: t.summary,
|
|
4168
|
+
commands: t.commands
|
|
4169
|
+
})),
|
|
4170
|
+
() => {
|
|
4171
|
+
line(import_picocolors2.default.bold("Guides") + import_picocolors2.default.dim(" \u2014 workser help <topic>"));
|
|
4172
|
+
line();
|
|
4173
|
+
const width = Math.max(...HELP_TOPICS.map((t) => t.topic.length));
|
|
4174
|
+
for (const t of HELP_TOPICS) {
|
|
4175
|
+
line(` ${import_picocolors2.default.cyan(t.topic.padEnd(width))} ${t.summary}`);
|
|
4176
|
+
}
|
|
4177
|
+
line();
|
|
4178
|
+
line(import_picocolors2.default.dim(" workser <command> --help exact flags, generated from the code"));
|
|
4179
|
+
}
|
|
4180
|
+
);
|
|
4181
|
+
}
|
|
4182
|
+
function registerHelp(program3) {
|
|
4183
|
+
const withHelpCommand = program3;
|
|
4184
|
+
withHelpCommand.helpCommand?.(false);
|
|
4185
|
+
program3.command("help [topic]").description("guides for using this CLI (`workser help` lists them)").action((topic) => {
|
|
4186
|
+
if (!topic) {
|
|
4187
|
+
listTopics();
|
|
4188
|
+
return;
|
|
4189
|
+
}
|
|
4190
|
+
const found = findTopic(topic);
|
|
4191
|
+
if (found) {
|
|
4192
|
+
ok({ topic: found.topic, title: found.title, content: found.body }, () => {
|
|
4193
|
+
process.stdout.write(found.body.endsWith("\n") ? found.body : found.body + "\n");
|
|
4194
|
+
});
|
|
4195
|
+
return;
|
|
4196
|
+
}
|
|
4197
|
+
const command = program3.commands.find(
|
|
4198
|
+
(c) => c.name() === topic || c.aliases().includes(topic)
|
|
4199
|
+
);
|
|
4200
|
+
if (command && !isJson()) {
|
|
4201
|
+
command.outputHelp();
|
|
4202
|
+
return;
|
|
4203
|
+
}
|
|
4204
|
+
throw new WorkserError(
|
|
4205
|
+
`No guide for "${topic}". Run \`workser help\` to list them` + (command ? `, or \`workser ${topic} --help\` for its flags` : "") + ".",
|
|
4206
|
+
{ code: "not_found" }
|
|
4207
|
+
);
|
|
4208
|
+
});
|
|
4209
|
+
}
|
|
4210
|
+
|
|
4211
|
+
// src/commands/status.ts
|
|
4212
|
+
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
4213
|
+
|
|
3656
4214
|
// src/context.ts
|
|
3657
4215
|
import { resolve } from "path";
|
|
3658
4216
|
|
|
3659
4217
|
// src/config.ts
|
|
3660
4218
|
import { homedir } from "os";
|
|
3661
4219
|
import { join } from "path";
|
|
3662
|
-
import {
|
|
4220
|
+
import {
|
|
4221
|
+
chmodSync,
|
|
4222
|
+
existsSync,
|
|
4223
|
+
mkdirSync,
|
|
4224
|
+
readFileSync,
|
|
4225
|
+
writeFileSync
|
|
4226
|
+
} from "fs";
|
|
3663
4227
|
var GLOBAL_DIR = join(homedir(), ".workser");
|
|
3664
4228
|
var SESSION_FILE = join(GLOBAL_DIR, "session.json");
|
|
3665
4229
|
function readSession() {
|
|
@@ -3672,13 +4236,17 @@ function readSession() {
|
|
|
3672
4236
|
}
|
|
3673
4237
|
function writeSession(patch) {
|
|
3674
4238
|
const next = { ...readSession(), ...patch };
|
|
3675
|
-
mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
3676
|
-
writeFileSync(SESSION_FILE, JSON.stringify(next, null, 2));
|
|
4239
|
+
mkdirSync(GLOBAL_DIR, { recursive: true, mode: 448 });
|
|
4240
|
+
writeFileSync(SESSION_FILE, JSON.stringify(next, null, 2), { mode: 384 });
|
|
4241
|
+
try {
|
|
4242
|
+
chmodSync(SESSION_FILE, 384);
|
|
4243
|
+
} catch {
|
|
4244
|
+
}
|
|
3677
4245
|
return next;
|
|
3678
4246
|
}
|
|
3679
4247
|
function clearSession() {
|
|
3680
4248
|
try {
|
|
3681
|
-
writeFileSync(SESSION_FILE, "{}");
|
|
4249
|
+
writeFileSync(SESSION_FILE, "{}", { mode: 384 });
|
|
3682
4250
|
} catch {
|
|
3683
4251
|
}
|
|
3684
4252
|
}
|
|
@@ -3695,21 +4263,52 @@ function readProjectLink(cwd) {
|
|
|
3695
4263
|
}
|
|
3696
4264
|
}
|
|
3697
4265
|
|
|
4266
|
+
// src/env.ts
|
|
4267
|
+
var ENV_BASE_URLS = {
|
|
4268
|
+
local: "http://localhost:8000",
|
|
4269
|
+
dev: "https://dev-api.workser.ai",
|
|
4270
|
+
prod: "https://api.workser.ai"
|
|
4271
|
+
};
|
|
4272
|
+
var ALIASES = {
|
|
4273
|
+
local: "local",
|
|
4274
|
+
localhost: "local",
|
|
4275
|
+
development: "dev",
|
|
4276
|
+
dev: "dev",
|
|
4277
|
+
staging: "dev",
|
|
4278
|
+
prod: "prod",
|
|
4279
|
+
production: "prod"
|
|
4280
|
+
};
|
|
4281
|
+
function resolveEnv(raw = process.env.WORKSER_ENV) {
|
|
4282
|
+
if (!raw || !raw.trim()) return "prod";
|
|
4283
|
+
const env = ALIASES[raw.trim().toLowerCase()];
|
|
4284
|
+
if (!env) {
|
|
4285
|
+
throw new WorkserError(
|
|
4286
|
+
`Unknown WORKSER_ENV "${raw}". Expected one of: local, dev, prod.`,
|
|
4287
|
+
{ code: "bad_env" }
|
|
4288
|
+
);
|
|
4289
|
+
}
|
|
4290
|
+
return env;
|
|
4291
|
+
}
|
|
4292
|
+
function cloudBaseUrl() {
|
|
4293
|
+
return process.env.WORKSER_API_URL || ENV_BASE_URLS[resolveEnv()];
|
|
4294
|
+
}
|
|
4295
|
+
|
|
3698
4296
|
// src/context.ts
|
|
3699
|
-
var CLOUD_DEFAULT = process.env.WORKSER_API_URL || "https://api.workser.ai";
|
|
3700
4297
|
function buildContext(opts) {
|
|
3701
4298
|
const session = readSession();
|
|
3702
4299
|
const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();
|
|
3703
|
-
const endpointRaw = opts.endpoint || process.env.WORKSER_DAEMON_URL || session.endpoint || CLOUD_DEFAULT;
|
|
3704
|
-
const endpoint = endpointRaw.replace(/\/+$/, "");
|
|
3705
4300
|
const token = opts.token || process.env.WORKSER_TOKEN || session.token;
|
|
3706
|
-
const
|
|
3707
|
-
endpoint
|
|
3708
|
-
)
|
|
4301
|
+
const overridden = Boolean(
|
|
4302
|
+
opts.endpoint || opts.token || process.env.WORKSER_DAEMON_URL || process.env.WORKSER_TOKEN
|
|
4303
|
+
);
|
|
4304
|
+
const socketPath = overridden ? void 0 : session.socketPath;
|
|
4305
|
+
const endpointRaw = socketPath ? "http://localhost" : opts.endpoint || process.env.WORKSER_DAEMON_URL || session.endpoint || cloudBaseUrl();
|
|
4306
|
+
const endpoint = endpointRaw.replace(/\/+$/, "");
|
|
4307
|
+
const mode2 = socketPath ? "daemon" : /^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:|\/|$)/i.test(endpoint) ? "daemon" : "cloud";
|
|
3709
4308
|
const link = readProjectLink(cwd);
|
|
3710
4309
|
const projectId = opts.project || process.env.WORKSER_PROJECT_ID || link?.projectId || session.defaultProjectId;
|
|
3711
4310
|
const runId = process.env.WORKSER_RUN_ID || void 0;
|
|
3712
|
-
return { endpoint, token, mode: mode2, cwd, projectId, runId };
|
|
4311
|
+
return { endpoint, socketPath, token, mode: mode2, cwd, projectId, runId };
|
|
3713
4312
|
}
|
|
3714
4313
|
function runTarget(ctx) {
|
|
3715
4314
|
return ctx.runId || "current";
|
|
@@ -3740,6 +4339,31 @@ function action(fn) {
|
|
|
3740
4339
|
}
|
|
3741
4340
|
|
|
3742
4341
|
// src/client.ts
|
|
4342
|
+
import * as http from "http";
|
|
4343
|
+
function requestOverSocket(socketPath, pathWithQuery, init) {
|
|
4344
|
+
return new Promise((resolve4, reject) => {
|
|
4345
|
+
const req = http.request(
|
|
4346
|
+
{ socketPath, path: pathWithQuery, method: init.method, headers: init.headers },
|
|
4347
|
+
(res) => {
|
|
4348
|
+
let text = "";
|
|
4349
|
+
res.setEncoding("utf8");
|
|
4350
|
+
res.on("data", (c) => text += c);
|
|
4351
|
+
res.on(
|
|
4352
|
+
"end",
|
|
4353
|
+
() => resolve4({
|
|
4354
|
+
ok: (res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300,
|
|
4355
|
+
status: res.statusCode ?? 0,
|
|
4356
|
+
statusText: res.statusMessage ?? "",
|
|
4357
|
+
text
|
|
4358
|
+
})
|
|
4359
|
+
);
|
|
4360
|
+
}
|
|
4361
|
+
);
|
|
4362
|
+
req.on("error", reject);
|
|
4363
|
+
if (init.body !== void 0) req.write(init.body);
|
|
4364
|
+
req.end();
|
|
4365
|
+
});
|
|
4366
|
+
}
|
|
3743
4367
|
async function api(ctx, path, opts = {}) {
|
|
3744
4368
|
const url = new URL(ctx.endpoint + path);
|
|
3745
4369
|
if (opts.query) {
|
|
@@ -3752,21 +4376,42 @@ async function api(ctx, path, opts = {}) {
|
|
|
3752
4376
|
"user-agent": "workser-cli"
|
|
3753
4377
|
};
|
|
3754
4378
|
if (ctx.token) headers.authorization = `Bearer ${ctx.token}`;
|
|
4379
|
+
if (ctx.mode === "daemon" && ctx.runId) {
|
|
4380
|
+
headers["x-workser-run-id"] = ctx.runId;
|
|
4381
|
+
}
|
|
3755
4382
|
if (opts.body !== void 0) headers["content-type"] = "application/json";
|
|
4383
|
+
const method = opts.method ?? (opts.body !== void 0 ? "POST" : "GET");
|
|
4384
|
+
const bodyText = opts.body !== void 0 ? JSON.stringify(opts.body) : void 0;
|
|
3756
4385
|
let res;
|
|
3757
4386
|
try {
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
4387
|
+
if (ctx.socketPath) {
|
|
4388
|
+
if (bodyText !== void 0) {
|
|
4389
|
+
headers["content-length"] = String(Buffer.byteLength(bodyText));
|
|
4390
|
+
}
|
|
4391
|
+
res = await requestOverSocket(ctx.socketPath, url.pathname + url.search, {
|
|
4392
|
+
method,
|
|
4393
|
+
headers,
|
|
4394
|
+
body: bodyText
|
|
4395
|
+
});
|
|
4396
|
+
} else {
|
|
4397
|
+
const r = await fetch(url, { method, headers, body: bodyText });
|
|
4398
|
+
res = {
|
|
4399
|
+
ok: r.ok,
|
|
4400
|
+
status: r.status,
|
|
4401
|
+
statusText: r.statusText,
|
|
4402
|
+
text: await r.text()
|
|
4403
|
+
};
|
|
4404
|
+
}
|
|
3763
4405
|
} catch (e) {
|
|
3764
4406
|
throw new WorkserError(
|
|
3765
4407
|
ctx.mode === "daemon" ? "Can't reach Workser Orbit (local daemon). Is the app running?" : `Can't reach Workser at ${ctx.endpoint}.`,
|
|
3766
|
-
{
|
|
4408
|
+
{
|
|
4409
|
+
code: "not_connected",
|
|
4410
|
+
details: e instanceof Error ? e.message : String(e)
|
|
4411
|
+
}
|
|
3767
4412
|
);
|
|
3768
4413
|
}
|
|
3769
|
-
const text =
|
|
4414
|
+
const text = res.text;
|
|
3770
4415
|
const data = text ? safeJson(text) : void 0;
|
|
3771
4416
|
if (!res.ok) {
|
|
3772
4417
|
const nested = data && typeof data === "object" && data.error && typeof data.error === "object" ? data.error : void 0;
|
|
@@ -3803,15 +4448,15 @@ function registerStatus(program3) {
|
|
|
3803
4448
|
action(async ({ ctx }) => {
|
|
3804
4449
|
const data = await api(ctx, "/v1/status", { query: { project: ctx.projectId } });
|
|
3805
4450
|
ok(data, () => {
|
|
3806
|
-
line(
|
|
4451
|
+
line(import_picocolors3.default.bold("Workser") + import_picocolors3.default.dim(` (${ctx.mode} \xB7 ${ctx.endpoint})`));
|
|
3807
4452
|
line(` user: ${data.user?.email ?? "\u2014"}`);
|
|
3808
4453
|
line(` workspace: ${data.workspace?.name ?? "\u2014"}`);
|
|
3809
4454
|
line(
|
|
3810
|
-
` project: ${data.project?.name ?? "\u2014"}` + (data.project?.id ?
|
|
4455
|
+
` project: ${data.project?.name ?? "\u2014"}` + (data.project?.id ? import_picocolors3.default.dim(` (${data.project.id})`) : "")
|
|
3811
4456
|
);
|
|
3812
4457
|
if (data.latestDeploy) {
|
|
3813
4458
|
line(
|
|
3814
|
-
` deploy: ${colorStatus(data.latestDeploy.status)}` + (data.latestDeploy.url ? ` ${
|
|
4459
|
+
` deploy: ${colorStatus(data.latestDeploy.status)}` + (data.latestDeploy.url ? ` ${import_picocolors3.default.cyan(data.latestDeploy.url)}` : "")
|
|
3815
4460
|
);
|
|
3816
4461
|
}
|
|
3817
4462
|
});
|
|
@@ -3823,15 +4468,15 @@ function colorStatus(s) {
|
|
|
3823
4468
|
case "ready":
|
|
3824
4469
|
case "live":
|
|
3825
4470
|
case "success":
|
|
3826
|
-
return
|
|
4471
|
+
return import_picocolors3.default.green(s);
|
|
3827
4472
|
case "error":
|
|
3828
4473
|
case "failed":
|
|
3829
4474
|
case "canceled":
|
|
3830
|
-
return
|
|
4475
|
+
return import_picocolors3.default.red(s);
|
|
3831
4476
|
case "building":
|
|
3832
4477
|
case "queued":
|
|
3833
4478
|
case "deploying":
|
|
3834
|
-
return
|
|
4479
|
+
return import_picocolors3.default.yellow(s);
|
|
3835
4480
|
default:
|
|
3836
4481
|
return s ?? "\u2014";
|
|
3837
4482
|
}
|
|
@@ -3892,7 +4537,7 @@ function registerWhoami(program3) {
|
|
|
3892
4537
|
}
|
|
3893
4538
|
|
|
3894
4539
|
// src/commands/project.ts
|
|
3895
|
-
var
|
|
4540
|
+
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
3896
4541
|
function registerProject(program3) {
|
|
3897
4542
|
const project = program3.command("project").description("Inspect the project linked to this directory");
|
|
3898
4543
|
project.command("show").description("Show the project pinned to this directory").action(
|
|
@@ -3900,8 +4545,8 @@ function registerProject(program3) {
|
|
|
3900
4545
|
const projectId = requireProject(ctx);
|
|
3901
4546
|
const p = await api(ctx, `/v1/projects/${encodeURIComponent(projectId)}`);
|
|
3902
4547
|
ok(p, () => {
|
|
3903
|
-
line(`${
|
|
3904
|
-
if (p.url) line(
|
|
4548
|
+
line(`${import_picocolors4.default.bold(p.name ?? "\u2014")}${p.id ? import_picocolors4.default.dim(` (${p.id})`) : ""}`);
|
|
4549
|
+
if (p.url) line(import_picocolors4.default.cyan(p.url));
|
|
3905
4550
|
});
|
|
3906
4551
|
})
|
|
3907
4552
|
);
|
|
@@ -3923,22 +4568,79 @@ function registerProject(program3) {
|
|
|
3923
4568
|
})
|
|
3924
4569
|
)
|
|
3925
4570
|
);
|
|
4571
|
+
project.command("apps").description("List the project's apps (id, type, status, URL, local folder)").action(
|
|
4572
|
+
action(async ({ ctx }) => {
|
|
4573
|
+
const projectId = requireProject(ctx);
|
|
4574
|
+
const [apps, folders] = await Promise.all([
|
|
4575
|
+
api(ctx, `/v1/apps`, { query: { project: projectId } }),
|
|
4576
|
+
api(ctx, `/v1/app-folders`, { query: { project: projectId } }).catch(
|
|
4577
|
+
() => []
|
|
4578
|
+
)
|
|
4579
|
+
]);
|
|
4580
|
+
const withFolders = (apps ?? []).map((a) => ({
|
|
4581
|
+
...a,
|
|
4582
|
+
localPath: folderFor(folders, a.id)
|
|
4583
|
+
}));
|
|
4584
|
+
ok(withFolders, () => {
|
|
4585
|
+
if (!withFolders.length) return line(import_picocolors4.default.dim("No apps in this project."));
|
|
4586
|
+
for (const a of withFolders) printApp(a);
|
|
4587
|
+
});
|
|
4588
|
+
})
|
|
4589
|
+
);
|
|
4590
|
+
project.command("app <id>").description("Show one app: type, status, URLs and where its code lives here").action(
|
|
4591
|
+
action(async ({ ctx, args }) => {
|
|
4592
|
+
const projectId = requireProject(ctx);
|
|
4593
|
+
const id = args[0];
|
|
4594
|
+
const [app, folders] = await Promise.all([
|
|
4595
|
+
api(ctx, `/v1/apps/${encodeURIComponent(id)}`),
|
|
4596
|
+
api(ctx, `/v1/app-folders`, { query: { project: projectId } }).catch(
|
|
4597
|
+
() => []
|
|
4598
|
+
)
|
|
4599
|
+
]);
|
|
4600
|
+
const merged = { ...app, localPath: folderFor(folders, app?.id ?? id) };
|
|
4601
|
+
ok(merged, () => {
|
|
4602
|
+
printApp(merged);
|
|
4603
|
+
if (merged.previewUrl) line(` preview ${import_picocolors4.default.cyan(merged.previewUrl)}`);
|
|
4604
|
+
if (merged.productionUrl)
|
|
4605
|
+
line(` production ${import_picocolors4.default.cyan(merged.productionUrl)}`);
|
|
4606
|
+
if (!merged.localPath) {
|
|
4607
|
+
line(
|
|
4608
|
+
import_picocolors4.default.dim(
|
|
4609
|
+
" no folder linked on this computer \u2014 link one in Workser Orbit"
|
|
4610
|
+
)
|
|
4611
|
+
);
|
|
4612
|
+
}
|
|
4613
|
+
});
|
|
4614
|
+
})
|
|
4615
|
+
);
|
|
3926
4616
|
project.command("list").description("List the workspace's projects").action(
|
|
3927
4617
|
action(async ({ ctx }) => {
|
|
3928
4618
|
const items = await api(ctx, `/v1/projects`);
|
|
3929
4619
|
ok(items, () => {
|
|
3930
|
-
if (!items?.length) return line(
|
|
4620
|
+
if (!items?.length) return line(import_picocolors4.default.dim("No projects."));
|
|
3931
4621
|
for (const p of items) {
|
|
3932
|
-
const pinned = ctx.projectId && p.id === ctx.projectId ?
|
|
3933
|
-
line(`${pinned}${p.name ?? "\u2014"}${p.id ?
|
|
4622
|
+
const pinned = ctx.projectId && p.id === ctx.projectId ? import_picocolors4.default.green("\u25CF ") : " ";
|
|
4623
|
+
line(`${pinned}${p.name ?? "\u2014"}${p.id ? import_picocolors4.default.dim(` (${p.id})`) : ""}`);
|
|
3934
4624
|
}
|
|
3935
4625
|
});
|
|
3936
4626
|
})
|
|
3937
4627
|
);
|
|
3938
4628
|
}
|
|
4629
|
+
function folderFor(folders, appId) {
|
|
4630
|
+
if (!Array.isArray(folders)) return void 0;
|
|
4631
|
+
const hit = folders.find((f) => f?.web_app_id === appId);
|
|
4632
|
+
return hit?.local_path || void 0;
|
|
4633
|
+
}
|
|
4634
|
+
function printApp(a) {
|
|
4635
|
+
const bits = [a.type, a.status].filter(Boolean).join(" \xB7 ");
|
|
4636
|
+
line(
|
|
4637
|
+
`${import_picocolors4.default.bold(a.name ?? "\u2014")}${a.id ? import_picocolors4.default.dim(` (${a.id})`) : ""}${bits ? " " + import_picocolors4.default.dim(bits) : ""}`
|
|
4638
|
+
);
|
|
4639
|
+
if (a.localPath) line(import_picocolors4.default.dim(` ${a.localPath}`));
|
|
4640
|
+
}
|
|
3939
4641
|
|
|
3940
4642
|
// src/commands/db.ts
|
|
3941
|
-
var
|
|
4643
|
+
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
3942
4644
|
function registerDb(program3) {
|
|
3943
4645
|
const db = program3.command("db").description("Provision and inspect the project's Neon Postgres database");
|
|
3944
4646
|
db.command("create").description("Provision a Postgres database for the project (idempotent)").action(
|
|
@@ -3948,7 +4650,7 @@ function registerDb(program3) {
|
|
|
3948
4650
|
ok(
|
|
3949
4651
|
res,
|
|
3950
4652
|
() => line(
|
|
3951
|
-
res.created === false ? `Database already provisioned${
|
|
4653
|
+
res.created === false ? `Database already provisioned${import_picocolors5.default.dim(` (${res.name ?? "db"})`)}.` : `Provisioned database ${import_picocolors5.default.bold(res.name ?? "db")}${import_picocolors5.default.dim(` ${res.region ?? ""} ${res.status ?? ""}`.trimEnd())}.`
|
|
3952
4654
|
)
|
|
3953
4655
|
);
|
|
3954
4656
|
})
|
|
@@ -3958,8 +4660,8 @@ function registerDb(program3) {
|
|
|
3958
4660
|
const projectId = requireProject(ctx);
|
|
3959
4661
|
const items = await api(ctx, `/v1/projects/${projectId}/databases`);
|
|
3960
4662
|
ok(items, () => {
|
|
3961
|
-
if (!items?.length) return line(
|
|
3962
|
-
for (const d of items) line(`${d.name}${
|
|
4663
|
+
if (!items?.length) return line(import_picocolors5.default.dim("No database yet. `workser db create`."));
|
|
4664
|
+
for (const d of items) line(`${d.name}${import_picocolors5.default.dim(` ${d.region ?? ""} ${d.status ?? ""}`.trimEnd())}`);
|
|
3963
4665
|
});
|
|
3964
4666
|
})
|
|
3965
4667
|
);
|
|
@@ -3976,12 +4678,12 @@ function registerDb(program3) {
|
|
|
3976
4678
|
const projectId = requireProject(ctx);
|
|
3977
4679
|
const rows = await api(ctx, `/v1/projects/${projectId}/db/tables`);
|
|
3978
4680
|
ok(rows, () => {
|
|
3979
|
-
if (!rows?.length) return line(
|
|
4681
|
+
if (!rows?.length) return line(import_picocolors5.default.dim("No tables."));
|
|
3980
4682
|
for (const t of rows) {
|
|
3981
|
-
const schema = t.table_schema && t.table_schema !== "public" ?
|
|
3982
|
-
const cols = t.column_count != null ?
|
|
3983
|
-
const count = t.row_count != null ?
|
|
3984
|
-
const size = t.table_size != null ?
|
|
4683
|
+
const schema = t.table_schema && t.table_schema !== "public" ? import_picocolors5.default.dim(`${t.table_schema}.`) : "";
|
|
4684
|
+
const cols = t.column_count != null ? import_picocolors5.default.dim(` ${t.column_count} cols`) : "";
|
|
4685
|
+
const count = t.row_count != null ? import_picocolors5.default.dim(` ${t.row_count} rows`) : "";
|
|
4686
|
+
const size = t.table_size != null ? import_picocolors5.default.dim(` ${fmtBytes(t.table_size)}`) : "";
|
|
3985
4687
|
line(`${schema}${t.table_name}${cols}${count}${size}`);
|
|
3986
4688
|
}
|
|
3987
4689
|
});
|
|
@@ -3993,11 +4695,11 @@ function registerDb(program3) {
|
|
|
3993
4695
|
const table = args[0];
|
|
3994
4696
|
const cols = await api(ctx, `/v1/projects/${projectId}/db/tables/${encodeURIComponent(table)}/schema`);
|
|
3995
4697
|
ok(cols, () => {
|
|
3996
|
-
if (!cols?.length) return line(
|
|
4698
|
+
if (!cols?.length) return line(import_picocolors5.default.dim("No columns (does the table exist?)."));
|
|
3997
4699
|
for (const c of cols) {
|
|
3998
|
-
const nn = c.is_nullable === "NO" || c.is_nullable === false ?
|
|
3999
|
-
const def = c.column_default ?
|
|
4000
|
-
line(`${c.column_name} ${
|
|
4700
|
+
const nn = c.is_nullable === "NO" || c.is_nullable === false ? import_picocolors5.default.dim(" not null") : "";
|
|
4701
|
+
const def = c.column_default ? import_picocolors5.default.dim(` default ${c.column_default}`) : "";
|
|
4702
|
+
line(`${c.column_name} ${import_picocolors5.default.cyan(c.data_type)}${nn}${def}`);
|
|
4001
4703
|
}
|
|
4002
4704
|
});
|
|
4003
4705
|
})
|
|
@@ -4023,23 +4725,23 @@ function registerDb(program3) {
|
|
|
4023
4725
|
}
|
|
4024
4726
|
function printRows(rows, total) {
|
|
4025
4727
|
if (!rows?.length) {
|
|
4026
|
-
line(
|
|
4728
|
+
line(import_picocolors5.default.dim("(0 rows)"));
|
|
4027
4729
|
return;
|
|
4028
4730
|
}
|
|
4029
4731
|
const cols = Object.keys(rows[0]);
|
|
4030
|
-
line(
|
|
4732
|
+
line(import_picocolors5.default.dim(cols.join(" ")));
|
|
4031
4733
|
for (const r of rows) {
|
|
4032
4734
|
line(cols.map((c) => fmtCell(r[c])).join(" "));
|
|
4033
4735
|
}
|
|
4034
4736
|
const shown = rows.length;
|
|
4035
4737
|
line(
|
|
4036
|
-
|
|
4738
|
+
import_picocolors5.default.dim(
|
|
4037
4739
|
total != null && total > shown ? `(${shown} of ${total} rows)` : `(${shown} row${shown === 1 ? "" : "s"})`
|
|
4038
4740
|
)
|
|
4039
4741
|
);
|
|
4040
4742
|
}
|
|
4041
4743
|
function fmtCell(v) {
|
|
4042
|
-
if (v === null || v === void 0) return
|
|
4744
|
+
if (v === null || v === void 0) return import_picocolors5.default.dim("\u2205");
|
|
4043
4745
|
if (typeof v === "object") return JSON.stringify(v);
|
|
4044
4746
|
return String(v);
|
|
4045
4747
|
}
|
|
@@ -4052,7 +4754,7 @@ function fmtBytes(n) {
|
|
|
4052
4754
|
}
|
|
4053
4755
|
|
|
4054
4756
|
// src/commands/auth.ts
|
|
4055
|
-
var
|
|
4757
|
+
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
4056
4758
|
function registerAuth(program3) {
|
|
4057
4759
|
const auth = program3.command("auth").description("Provision and inspect the project's auth (Better Auth)");
|
|
4058
4760
|
auth.command("enable").description("Enable auth for the project (idempotent)").action(
|
|
@@ -4062,7 +4764,7 @@ function registerAuth(program3) {
|
|
|
4062
4764
|
ok(res, () => {
|
|
4063
4765
|
const providers = (res.providers ?? []).join(", ") || "email";
|
|
4064
4766
|
line(
|
|
4065
|
-
res.created === false ? `Auth already enabled${
|
|
4767
|
+
res.created === false ? `Auth already enabled${import_picocolors6.default.dim(` (${providers})`)}.` : `Enabled auth ${import_picocolors6.default.dim(`(${providers})`)}.`
|
|
4066
4768
|
);
|
|
4067
4769
|
});
|
|
4068
4770
|
})
|
|
@@ -4074,7 +4776,7 @@ function registerAuth(program3) {
|
|
|
4074
4776
|
ok(res, () => {
|
|
4075
4777
|
if (!res.enabled) return line("disabled");
|
|
4076
4778
|
const providers = (res.providers ?? []).join(", ") || "email";
|
|
4077
|
-
line(`enabled ${
|
|
4779
|
+
line(`enabled ${import_picocolors6.default.dim(`(${providers})`)}`);
|
|
4078
4780
|
if (res.authMode) line(` mode: ${res.authMode}`);
|
|
4079
4781
|
if (res.authMode === "neon_managed") {
|
|
4080
4782
|
if (res.neonAuthOwnedBy) line(` owned by: ${res.neonAuthOwnedBy}`);
|
|
@@ -4086,7 +4788,7 @@ function registerAuth(program3) {
|
|
|
4086
4788
|
}
|
|
4087
4789
|
|
|
4088
4790
|
// src/commands/storage.ts
|
|
4089
|
-
var
|
|
4791
|
+
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
4090
4792
|
import { readFile, writeFile } from "fs/promises";
|
|
4091
4793
|
import { basename, dirname } from "path";
|
|
4092
4794
|
function registerStorage(program3) {
|
|
@@ -4100,7 +4802,7 @@ function registerStorage(program3) {
|
|
|
4100
4802
|
ok(
|
|
4101
4803
|
res,
|
|
4102
4804
|
() => line(
|
|
4103
|
-
res.created === false ? `Bucket already exists${
|
|
4805
|
+
res.created === false ? `Bucket already exists${import_picocolors7.default.dim(` (${res.bucket})`)}.` : `Provisioned bucket ${import_picocolors7.default.bold(res.bucket || "(pending)")}.`
|
|
4104
4806
|
)
|
|
4105
4807
|
);
|
|
4106
4808
|
})
|
|
@@ -4110,7 +4812,7 @@ function registerStorage(program3) {
|
|
|
4110
4812
|
const projectId = requireProject(ctx);
|
|
4111
4813
|
const items = await api(ctx, `/v1/projects/${projectId}/storage`);
|
|
4112
4814
|
ok(items, () => {
|
|
4113
|
-
if (!items?.length) return line(
|
|
4815
|
+
if (!items?.length) return line(import_picocolors7.default.dim("No bucket yet. `workser storage create`."));
|
|
4114
4816
|
for (const b of items) line(b.bucket ?? b.name);
|
|
4115
4817
|
});
|
|
4116
4818
|
})
|
|
@@ -4120,9 +4822,9 @@ function registerStorage(program3) {
|
|
|
4120
4822
|
const projectId = requireProject(ctx);
|
|
4121
4823
|
const objects = await listFiles(ctx, projectId, args[0]);
|
|
4122
4824
|
ok(objects, () => {
|
|
4123
|
-
if (!objects.length) return line(
|
|
4825
|
+
if (!objects.length) return line(import_picocolors7.default.dim("No objects."));
|
|
4124
4826
|
for (const o of objects) {
|
|
4125
|
-
line(`${o.key}${
|
|
4827
|
+
line(`${o.key}${import_picocolors7.default.dim(` ${fmtSize(o.size)}${o.lastModified ? " " + o.lastModified : ""}`)}`);
|
|
4126
4828
|
}
|
|
4127
4829
|
});
|
|
4128
4830
|
})
|
|
@@ -4144,7 +4846,7 @@ function registerStorage(program3) {
|
|
|
4144
4846
|
});
|
|
4145
4847
|
ok(
|
|
4146
4848
|
res,
|
|
4147
|
-
() => success(`Uploaded ${
|
|
4849
|
+
() => success(`Uploaded ${import_picocolors7.default.bold(res.key ?? key)} ${import_picocolors7.default.dim(`(${fmtSize(bytes.length)})`)}.`)
|
|
4148
4850
|
);
|
|
4149
4851
|
})
|
|
4150
4852
|
);
|
|
@@ -4163,7 +4865,7 @@ function registerStorage(program3) {
|
|
|
4163
4865
|
await writeFile(out, bytes);
|
|
4164
4866
|
ok(
|
|
4165
4867
|
{ key, dest: out, bytes: bytes.length },
|
|
4166
|
-
() => success(`Downloaded ${
|
|
4868
|
+
() => success(`Downloaded ${import_picocolors7.default.bold(key)} \u2192 ${out} ${import_picocolors7.default.dim(`(${fmtSize(bytes.length)})`)}.`)
|
|
4167
4869
|
);
|
|
4168
4870
|
})
|
|
4169
4871
|
);
|
|
@@ -4194,8 +4896,253 @@ function fmtSize(n) {
|
|
|
4194
4896
|
return `${(n / 1024 / 1024).toFixed(1)}MB`;
|
|
4195
4897
|
}
|
|
4196
4898
|
|
|
4899
|
+
// src/commands/neon.ts
|
|
4900
|
+
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
4901
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
4902
|
+
import { basename as basename2 } from "path";
|
|
4903
|
+
function registerNeon(program3) {
|
|
4904
|
+
const neon = program3.command("neon").description(
|
|
4905
|
+
"The project's own Neon backend: object storage buckets and functions"
|
|
4906
|
+
);
|
|
4907
|
+
neon.command("status").description(
|
|
4908
|
+
"Whether this project can use Neon storage/functions (tenancy, toggles, region)"
|
|
4909
|
+
).action(
|
|
4910
|
+
action(async ({ ctx }) => {
|
|
4911
|
+
const projectId = requireProject(ctx);
|
|
4912
|
+
const s = await api(ctx, `/v1/projects/${projectId}/neon-backend/status`);
|
|
4913
|
+
ok(s, () => {
|
|
4914
|
+
line(
|
|
4915
|
+
`Dedicated infrastructure: ${s.dedicated ? import_picocolors8.default.green("yes") : import_picocolors8.default.yellow("no")}`
|
|
4916
|
+
);
|
|
4917
|
+
line(
|
|
4918
|
+
`Region: ${s.regionId ? import_picocolors8.default.bold(s.regionId) : import_picocolors8.default.dim("unknown")}` + (s.regionId ? s.regionSupportsNeonBackend ? import_picocolors8.default.green(" (supported)") : import_picocolors8.default.red(" (Neon storage/functions unavailable here)") : "")
|
|
4919
|
+
);
|
|
4920
|
+
line(`Object storage: ${s.neonBackendStorageEnabled ? "on" : "off"}`);
|
|
4921
|
+
line(`Functions: ${s.neonBackendFunctionsEnabled ? "on" : "off"}`);
|
|
4922
|
+
if (s.regionId && !s.regionSupportsNeonBackend) {
|
|
4923
|
+
line(
|
|
4924
|
+
import_picocolors8.default.dim(
|
|
4925
|
+
`Supported regions: ${(s.supportedRegions ?? []).join(", ")}. A project's region is fixed at creation \u2014 this cannot be changed here.`
|
|
4926
|
+
)
|
|
4927
|
+
);
|
|
4928
|
+
}
|
|
4929
|
+
});
|
|
4930
|
+
})
|
|
4931
|
+
);
|
|
4932
|
+
const storage = neon.command("storage").description("S3-compatible buckets on the project's Neon branch");
|
|
4933
|
+
storage.command("list").description("List the project's Neon buckets").action(
|
|
4934
|
+
action(async ({ ctx }) => {
|
|
4935
|
+
const projectId = requireProject(ctx);
|
|
4936
|
+
const buckets = await api(
|
|
4937
|
+
ctx,
|
|
4938
|
+
`/v1/projects/${projectId}/neon-storage/buckets`
|
|
4939
|
+
);
|
|
4940
|
+
ok(buckets, () => {
|
|
4941
|
+
if (!buckets?.length)
|
|
4942
|
+
return line(import_picocolors8.default.dim("No buckets. `workser neon storage create <name>`."));
|
|
4943
|
+
for (const b of buckets)
|
|
4944
|
+
line(`${b.bucket_name}${import_picocolors8.default.dim(` (${b.access_level})`)}`);
|
|
4945
|
+
});
|
|
4946
|
+
})
|
|
4947
|
+
);
|
|
4948
|
+
storage.command("create <name>").description("Create a bucket on the project's Neon branch").option("--public", "Allow public reads (default: private)").action(
|
|
4949
|
+
action(async ({ ctx, args, opts }) => {
|
|
4950
|
+
const projectId = requireProject(ctx);
|
|
4951
|
+
const res = await api(
|
|
4952
|
+
ctx,
|
|
4953
|
+
`/v1/projects/${projectId}/neon-storage/buckets`,
|
|
4954
|
+
{
|
|
4955
|
+
body: {
|
|
4956
|
+
name: args[0],
|
|
4957
|
+
accessLevel: opts.public ? "public_read" : "private"
|
|
4958
|
+
}
|
|
4959
|
+
}
|
|
4960
|
+
);
|
|
4961
|
+
ok(res, () => success(`Created bucket ${import_picocolors8.default.bold(res.bucket_name ?? args[0])}.`));
|
|
4962
|
+
})
|
|
4963
|
+
);
|
|
4964
|
+
storage.command("rm <bucket>").description("Delete a bucket AND everything in it (asks for approval)").action(
|
|
4965
|
+
action(async ({ ctx, args }) => {
|
|
4966
|
+
const projectId = requireProject(ctx);
|
|
4967
|
+
const res = await api(
|
|
4968
|
+
ctx,
|
|
4969
|
+
`/v1/projects/${projectId}/neon-storage/buckets/${encodeURIComponent(args[0])}`,
|
|
4970
|
+
{ method: "DELETE" }
|
|
4971
|
+
);
|
|
4972
|
+
ok(res, () => success(`Deleted bucket ${args[0]}.`));
|
|
4973
|
+
})
|
|
4974
|
+
);
|
|
4975
|
+
storage.command("ls <bucket> [prefix]").description("List objects in a bucket").action(
|
|
4976
|
+
action(async ({ ctx, args }) => {
|
|
4977
|
+
const projectId = requireProject(ctx);
|
|
4978
|
+
const res = await api(
|
|
4979
|
+
ctx,
|
|
4980
|
+
`/v1/projects/${projectId}/neon-storage/buckets/${encodeURIComponent(args[0])}/objects`,
|
|
4981
|
+
{ query: { prefix: args[1] } }
|
|
4982
|
+
);
|
|
4983
|
+
const objects = res?.objects ?? res ?? [];
|
|
4984
|
+
ok(res, () => {
|
|
4985
|
+
if (!objects.length) return line(import_picocolors8.default.dim("Empty."));
|
|
4986
|
+
for (const o of objects)
|
|
4987
|
+
line(`${o.key ?? o.name}${o.size ? import_picocolors8.default.dim(` ${o.size} bytes`) : ""}`);
|
|
4988
|
+
});
|
|
4989
|
+
})
|
|
4990
|
+
);
|
|
4991
|
+
storage.command("put <bucket> <local> [key]").description("Upload a file (key defaults to the file's name)").action(
|
|
4992
|
+
action(async ({ ctx, args }) => {
|
|
4993
|
+
const projectId = requireProject(ctx);
|
|
4994
|
+
const [bucket, local] = args;
|
|
4995
|
+
const key = args[2] || basename2(local);
|
|
4996
|
+
const body = await readFile2(local).catch(() => {
|
|
4997
|
+
throw new WorkserError(`Can't read ${local}.`, { code: "not_found" });
|
|
4998
|
+
});
|
|
4999
|
+
const signed = await presign(ctx, projectId, bucket, key, "upload");
|
|
5000
|
+
const res = await fetch(signed.url, {
|
|
5001
|
+
method: "PUT",
|
|
5002
|
+
body,
|
|
5003
|
+
headers: signed.headers ?? {}
|
|
5004
|
+
});
|
|
5005
|
+
if (!res.ok) {
|
|
5006
|
+
throw new WorkserError(
|
|
5007
|
+
`Upload failed (${res.status} ${res.statusText}).`,
|
|
5008
|
+
{ code: "upload_failed", status: res.status }
|
|
5009
|
+
);
|
|
5010
|
+
}
|
|
5011
|
+
ok(
|
|
5012
|
+
{ bucket, key, bytes: body.length },
|
|
5013
|
+
() => success(`Uploaded ${key} to ${bucket} ${import_picocolors8.default.dim(`(${body.length} bytes)`)}.`)
|
|
5014
|
+
);
|
|
5015
|
+
})
|
|
5016
|
+
);
|
|
5017
|
+
storage.command("get <bucket> <key> [dest]").description("Download an object (dest defaults to the key's file name)").action(
|
|
5018
|
+
action(async ({ ctx, args }) => {
|
|
5019
|
+
const projectId = requireProject(ctx);
|
|
5020
|
+
const [bucket, key] = args;
|
|
5021
|
+
const dest = args[2] || basename2(key);
|
|
5022
|
+
const signed = await presign(ctx, projectId, bucket, key, "download");
|
|
5023
|
+
const res = await fetch(signed.url);
|
|
5024
|
+
if (!res.ok) {
|
|
5025
|
+
throw new WorkserError(
|
|
5026
|
+
`Download failed (${res.status} ${res.statusText}).`,
|
|
5027
|
+
{ code: "download_failed", status: res.status }
|
|
5028
|
+
);
|
|
5029
|
+
}
|
|
5030
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
5031
|
+
await writeFile2(dest, buf);
|
|
5032
|
+
ok(
|
|
5033
|
+
{ bucket, key, dest, bytes: buf.length },
|
|
5034
|
+
() => success(`Saved ${dest} ${import_picocolors8.default.dim(`(${buf.length} bytes)`)}.`)
|
|
5035
|
+
);
|
|
5036
|
+
})
|
|
5037
|
+
);
|
|
5038
|
+
storage.command("url <bucket> <key>").description("Print a temporary download URL for one object").option("--expires <seconds>", "Lifetime of the URL", "3600").action(
|
|
5039
|
+
action(async ({ ctx, args, opts }) => {
|
|
5040
|
+
const projectId = requireProject(ctx);
|
|
5041
|
+
const signed = await presign(
|
|
5042
|
+
ctx,
|
|
5043
|
+
projectId,
|
|
5044
|
+
args[0],
|
|
5045
|
+
args[1],
|
|
5046
|
+
"download",
|
|
5047
|
+
Number(opts.expires) || 3600
|
|
5048
|
+
);
|
|
5049
|
+
ok(signed, () => line(signed.url));
|
|
5050
|
+
})
|
|
5051
|
+
);
|
|
5052
|
+
storage.command("rm-object <bucket> <key>").description("Delete one object from a bucket (asks for approval)").action(
|
|
5053
|
+
action(async ({ ctx, args }) => {
|
|
5054
|
+
const projectId = requireProject(ctx);
|
|
5055
|
+
const res = await api(
|
|
5056
|
+
ctx,
|
|
5057
|
+
`/v1/projects/${projectId}/neon-storage/buckets/${encodeURIComponent(args[0])}/objects`,
|
|
5058
|
+
{ method: "DELETE", query: { key: args[1] } }
|
|
5059
|
+
);
|
|
5060
|
+
ok(res, () => success(`Deleted ${args[1]} from ${args[0]}.`));
|
|
5061
|
+
})
|
|
5062
|
+
);
|
|
5063
|
+
const functions = neon.command("functions").description("Node.js HTTP functions on the project's Neon branch");
|
|
5064
|
+
functions.command("list").description("List the project's Neon functions").action(
|
|
5065
|
+
action(async ({ ctx }) => {
|
|
5066
|
+
const projectId = requireProject(ctx);
|
|
5067
|
+
const fns = await api(ctx, `/v1/projects/${projectId}/neon-functions`);
|
|
5068
|
+
ok(fns, () => {
|
|
5069
|
+
if (!fns?.length)
|
|
5070
|
+
return line(import_picocolors8.default.dim("No functions. `workser neon functions deploy`."));
|
|
5071
|
+
for (const f of fns)
|
|
5072
|
+
line(`${f.slug ?? f.name}${f.url ? import_picocolors8.default.dim(` ${f.url}`) : ""}`);
|
|
5073
|
+
});
|
|
5074
|
+
})
|
|
5075
|
+
);
|
|
5076
|
+
functions.command("deploy <slug> <zip>").description("Deploy a function from a zip bundle").option(
|
|
5077
|
+
"--env <pairs...>",
|
|
5078
|
+
"Environment variables for the function (KEY=VALUE)"
|
|
5079
|
+
).option("--runtime <runtime>", "Runtime override").action(
|
|
5080
|
+
action(async ({ ctx, args, opts }) => {
|
|
5081
|
+
const projectId = requireProject(ctx);
|
|
5082
|
+
const [slug, zipPath] = args;
|
|
5083
|
+
const zip = await readFile2(zipPath).catch(() => {
|
|
5084
|
+
throw new WorkserError(`Can't read ${zipPath}.`, { code: "not_found" });
|
|
5085
|
+
});
|
|
5086
|
+
const environment = {};
|
|
5087
|
+
for (const pair of opts.env ?? []) {
|
|
5088
|
+
const eq = String(pair).indexOf("=");
|
|
5089
|
+
if (eq <= 0) {
|
|
5090
|
+
throw new WorkserError(
|
|
5091
|
+
`--env expects KEY=VALUE, got "${pair}".`,
|
|
5092
|
+
{ code: "bad_request" }
|
|
5093
|
+
);
|
|
5094
|
+
}
|
|
5095
|
+
environment[String(pair).slice(0, eq)] = String(pair).slice(eq + 1);
|
|
5096
|
+
}
|
|
5097
|
+
const res = await api(ctx, `/v1/projects/${projectId}/neon-functions`, {
|
|
5098
|
+
body: {
|
|
5099
|
+
slug,
|
|
5100
|
+
// JSON rather than multipart: the caller is an agent shelling out,
|
|
5101
|
+
// and base64 in a JSON body is the shape it can produce unaided.
|
|
5102
|
+
zipBase64: zip.toString("base64"),
|
|
5103
|
+
zipFilename: basename2(zipPath),
|
|
5104
|
+
runtime: opts.runtime,
|
|
5105
|
+
environment: Object.keys(environment).length ? environment : void 0
|
|
5106
|
+
}
|
|
5107
|
+
});
|
|
5108
|
+
ok(
|
|
5109
|
+
res,
|
|
5110
|
+
() => success(
|
|
5111
|
+
`Deployed ${import_picocolors8.default.bold(slug)}${res?.url ? import_picocolors8.default.dim(` ${res.url}`) : ""}.`
|
|
5112
|
+
)
|
|
5113
|
+
);
|
|
5114
|
+
})
|
|
5115
|
+
);
|
|
5116
|
+
functions.command("rm <slug>").description("Delete a function (asks for approval)").action(
|
|
5117
|
+
action(async ({ ctx, args }) => {
|
|
5118
|
+
const projectId = requireProject(ctx);
|
|
5119
|
+
const res = await api(
|
|
5120
|
+
ctx,
|
|
5121
|
+
`/v1/projects/${projectId}/neon-functions/${encodeURIComponent(args[0])}`,
|
|
5122
|
+
{ method: "DELETE" }
|
|
5123
|
+
);
|
|
5124
|
+
ok(res, () => success(`Deleted function ${args[0]}.`));
|
|
5125
|
+
})
|
|
5126
|
+
);
|
|
5127
|
+
}
|
|
5128
|
+
async function presign(ctx, projectId, bucket, key, operation, expiresInSeconds) {
|
|
5129
|
+
const res = await api(
|
|
5130
|
+
ctx,
|
|
5131
|
+
`/v1/projects/${projectId}/neon-storage/buckets/${encodeURIComponent(bucket)}/presign`,
|
|
5132
|
+
{ body: { key, operation, expiresInSeconds } }
|
|
5133
|
+
);
|
|
5134
|
+
const url = res?.url ?? res?.signedUrl ?? res?.presignedUrl;
|
|
5135
|
+
if (!url) {
|
|
5136
|
+
throw new WorkserError(
|
|
5137
|
+
`The daemon did not return a presigned URL for ${key}.`,
|
|
5138
|
+
{ code: "unexpected_response", details: res }
|
|
5139
|
+
);
|
|
5140
|
+
}
|
|
5141
|
+
return { url, headers: res?.headers };
|
|
5142
|
+
}
|
|
5143
|
+
|
|
4197
5144
|
// src/commands/env.ts
|
|
4198
|
-
var
|
|
5145
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
4199
5146
|
function appQuery(opts) {
|
|
4200
5147
|
const app = typeof opts?.app === "string" ? opts.app : "";
|
|
4201
5148
|
return app ? `?webAppId=${encodeURIComponent(app)}` : "";
|
|
@@ -4218,7 +5165,7 @@ function registerEnv(program3) {
|
|
|
4218
5165
|
ok(res, () => {
|
|
4219
5166
|
success(`Set ${count} variable(s): ${pairs.map((p) => p.key).join(", ")}`);
|
|
4220
5167
|
if (res?.usedDefault && res?.webAppName) {
|
|
4221
|
-
line(
|
|
5168
|
+
line(import_picocolors9.default.dim(`on ${res.webAppName} (primary app) \u2014 use --app to target another`));
|
|
4222
5169
|
}
|
|
4223
5170
|
});
|
|
4224
5171
|
})
|
|
@@ -4238,8 +5185,8 @@ function registerEnv(program3) {
|
|
|
4238
5185
|
const projectId = requireProject(ctx);
|
|
4239
5186
|
const items = await api(ctx, `/v1/projects/${projectId}/env${appQuery(opts)}`);
|
|
4240
5187
|
ok(items, () => {
|
|
4241
|
-
if (!items?.length) return line(
|
|
4242
|
-
for (const v of items) line(`${v.key}${
|
|
5188
|
+
if (!items?.length) return line(import_picocolors9.default.dim("No variables set."));
|
|
5189
|
+
for (const v of items) line(`${v.key}${import_picocolors9.default.dim(" = " + (v.masked ?? "\u2022\u2022\u2022\u2022"))}`);
|
|
4243
5190
|
});
|
|
4244
5191
|
})
|
|
4245
5192
|
);
|
|
@@ -4255,14 +5202,21 @@ function registerEnv(program3) {
|
|
|
4255
5202
|
}
|
|
4256
5203
|
|
|
4257
5204
|
// src/commands/deploy.ts
|
|
4258
|
-
var
|
|
5205
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
4259
5206
|
var TERMINAL = /* @__PURE__ */ new Set(["ready", "live", "success", "error", "failed", "canceled"]);
|
|
4260
5207
|
function registerDeploy(program3) {
|
|
4261
|
-
const deploy = program3.command("deploy").description("Deploy the current project to Workser (git \u2192 Vercel) and return a live URL").option("--prod", "deploy to production", false).option("--watch", "wait for the deploy to finish, streaming status", false).
|
|
5208
|
+
const deploy = program3.command("deploy").description("Deploy the current project to Workser (git \u2192 Vercel) and return a live URL").option("--prod", "deploy to production", false).option("--watch", "wait for the deploy to finish, streaming status", false).option(
|
|
5209
|
+
"--app <webAppId>",
|
|
5210
|
+
"which app to publish (default: the app this folder is linked to)"
|
|
5211
|
+
).action(
|
|
4262
5212
|
action(async ({ ctx, opts }) => {
|
|
4263
5213
|
const projectId = requireProject(ctx);
|
|
4264
5214
|
const dep = await api(ctx, `/v1/projects/${projectId}/deploy`, {
|
|
4265
|
-
body: {
|
|
5215
|
+
body: {
|
|
5216
|
+
prod: Boolean(opts.prod),
|
|
5217
|
+
cwd: ctx.cwd,
|
|
5218
|
+
...opts.app ? { webAppId: opts.app } : {}
|
|
5219
|
+
}
|
|
4266
5220
|
});
|
|
4267
5221
|
if (opts.watch && dep?.id) {
|
|
4268
5222
|
const final = await watchDeploy(ctx, dep.id);
|
|
@@ -4284,7 +5238,7 @@ async function watchDeploy(ctx, id) {
|
|
|
4284
5238
|
for (; ; ) {
|
|
4285
5239
|
const dep = await api(ctx, `/v1/deployments/${encodeURIComponent(id)}`);
|
|
4286
5240
|
if (!isJson() && dep.status !== last) {
|
|
4287
|
-
line(` ${colorStatus(dep.status)}${dep.url ? " " +
|
|
5241
|
+
line(` ${colorStatus(dep.status)}${dep.url ? " " + import_picocolors10.default.cyan(dep.url) : ""}`);
|
|
4288
5242
|
last = dep.status;
|
|
4289
5243
|
}
|
|
4290
5244
|
if (TERMINAL.has(String(dep.status).toLowerCase())) return dep;
|
|
@@ -4294,19 +5248,19 @@ async function watchDeploy(ctx, id) {
|
|
|
4294
5248
|
function printDeploy(dep) {
|
|
4295
5249
|
if (!dep) return;
|
|
4296
5250
|
const ready = ["ready", "live", "success"].includes(String(dep.status).toLowerCase());
|
|
4297
|
-
if (ready && dep.url) success(`Live at ${
|
|
4298
|
-
else line(`deploy ${colorStatus(dep.status)} ${
|
|
5251
|
+
if (ready && dep.url) success(`Live at ${import_picocolors10.default.cyan(dep.url)}`);
|
|
5252
|
+
else line(`deploy ${colorStatus(dep.status)} ${import_picocolors10.default.dim(`(${dep.id ?? "?"})`)}${dep.url ? " " + dep.url : ""}`);
|
|
4299
5253
|
}
|
|
4300
5254
|
|
|
4301
5255
|
// src/commands/versions.ts
|
|
4302
|
-
var
|
|
5256
|
+
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
4303
5257
|
function registerVersions(program3) {
|
|
4304
5258
|
program3.command("versions").description("List the Workser-managed versions of the project (deploy history)").action(
|
|
4305
5259
|
action(async ({ ctx }) => {
|
|
4306
5260
|
const projectId = requireProject(ctx);
|
|
4307
5261
|
const items = await api(ctx, `/v1/projects/${projectId}/versions`);
|
|
4308
5262
|
ok(items, () => {
|
|
4309
|
-
if (!items?.length) return line(
|
|
5263
|
+
if (!items?.length) return line(import_picocolors11.default.dim("No versions yet. `workser deploy` to create one."));
|
|
4310
5264
|
for (const v of items) {
|
|
4311
5265
|
line(formatVersion(v));
|
|
4312
5266
|
}
|
|
@@ -4315,11 +5269,11 @@ function registerVersions(program3) {
|
|
|
4315
5269
|
);
|
|
4316
5270
|
}
|
|
4317
5271
|
function formatVersion(v) {
|
|
4318
|
-
const ref =
|
|
4319
|
-
const when =
|
|
4320
|
-
const msg = (v.message ?? "").trim() ||
|
|
4321
|
-
const badge = v.deployed ? " " +
|
|
4322
|
-
const url = v.url ? " " +
|
|
5272
|
+
const ref = import_picocolors11.default.yellow(shortRef(v.ref));
|
|
5273
|
+
const when = import_picocolors11.default.dim(formatTime(v.createdAt));
|
|
5274
|
+
const msg = (v.message ?? "").trim() || import_picocolors11.default.dim("(no message)");
|
|
5275
|
+
const badge = v.deployed ? " " + import_picocolors11.default.green("deployed") : "";
|
|
5276
|
+
const url = v.url ? " " + import_picocolors11.default.cyan(v.url) : "";
|
|
4323
5277
|
return `${ref} ${when} ${msg}${badge}${url}`;
|
|
4324
5278
|
}
|
|
4325
5279
|
function shortRef(ref) {
|
|
@@ -4359,7 +5313,7 @@ function formatLog(e) {
|
|
|
4359
5313
|
}
|
|
4360
5314
|
|
|
4361
5315
|
// src/commands/domain.ts
|
|
4362
|
-
var
|
|
5316
|
+
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
4363
5317
|
function registerDomain(program3) {
|
|
4364
5318
|
const domain = program3.command("domain").description("Inspect the project's custom domains");
|
|
4365
5319
|
domain.command("set <domain>").description("(owner-only) Attach a custom domain \u2014 do this in Workser Orbit").action(
|
|
@@ -4376,8 +5330,8 @@ function registerDomain(program3) {
|
|
|
4376
5330
|
const projectId = requireProject(ctx);
|
|
4377
5331
|
const items = await api(ctx, `/v1/projects/${projectId}/domains`);
|
|
4378
5332
|
ok(items, () => {
|
|
4379
|
-
if (!items?.length) return line(
|
|
4380
|
-
for (const d of items) line(`${d.domain}${
|
|
5333
|
+
if (!items?.length) return line(import_picocolors12.default.dim("No custom domains."));
|
|
5334
|
+
for (const d of items) line(`${d.domain}${import_picocolors12.default.dim(" " + (d.status ?? ""))}`);
|
|
4381
5335
|
});
|
|
4382
5336
|
})
|
|
4383
5337
|
);
|
|
@@ -4409,18 +5363,22 @@ function openUrl(url) {
|
|
|
4409
5363
|
}
|
|
4410
5364
|
|
|
4411
5365
|
// src/commands/doctor.ts
|
|
4412
|
-
var
|
|
5366
|
+
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
4413
5367
|
function registerDoctor(program3) {
|
|
4414
5368
|
program3.command("doctor").description("Print the resolved endpoint, mode, token presence (masked), and current project").action(
|
|
4415
5369
|
action(({ ctx, opts }) => {
|
|
4416
5370
|
const session = readSession();
|
|
4417
5371
|
const link = readProjectLink(ctx.cwd);
|
|
5372
|
+
const env = resolveEnv();
|
|
5373
|
+
const envIgnored = Boolean(process.env.WORKSER_ENV) && ctx.mode === "cloud" && ctx.endpoint !== ENV_BASE_URLS[env];
|
|
4418
5374
|
const tokenSource = opts.token ? "--token" : process.env.WORKSER_TOKEN ? "$WORKSER_TOKEN" : session.token ? "session" : void 0;
|
|
4419
|
-
const endpointSource = opts.endpoint ? "--endpoint" : process.env.WORKSER_DAEMON_URL ? "$WORKSER_DAEMON_URL" : session.endpoint ? "session" : "cloud-default
|
|
5375
|
+
const endpointSource = opts.endpoint ? "--endpoint" : process.env.WORKSER_DAEMON_URL ? "$WORKSER_DAEMON_URL" : session.endpoint ? "session" : process.env.WORKSER_API_URL ? "$WORKSER_API_URL" : `cloud-default: ${env}`;
|
|
4420
5376
|
const projectSource = opts.project ? "--project" : link?.projectId ? ".workser link" : session.defaultProjectId ? "session" : void 0;
|
|
4421
5377
|
const report = {
|
|
4422
5378
|
endpoint: ctx.endpoint,
|
|
4423
5379
|
endpointSource,
|
|
5380
|
+
env,
|
|
5381
|
+
envIgnored,
|
|
4424
5382
|
mode: ctx.mode,
|
|
4425
5383
|
token: {
|
|
4426
5384
|
present: Boolean(ctx.token),
|
|
@@ -4436,16 +5394,33 @@ function registerDoctor(program3) {
|
|
|
4436
5394
|
workspace: session.workspaceName ?? null
|
|
4437
5395
|
};
|
|
4438
5396
|
ok(report, () => {
|
|
4439
|
-
line(
|
|
4440
|
-
line(` endpoint: ${ctx.endpoint} ${
|
|
5397
|
+
line(import_picocolors13.default.bold("workser doctor"));
|
|
5398
|
+
line(` endpoint: ${ctx.endpoint} ${import_picocolors13.default.dim(`(${endpointSource})`)}`);
|
|
5399
|
+
line(
|
|
5400
|
+
` env: ${env === "prod" ? import_picocolors13.default.yellow(env) : env}` + import_picocolors13.default.dim(process.env.WORKSER_ENV ? " ($WORKSER_ENV)" : " (default)")
|
|
5401
|
+
);
|
|
4441
5402
|
line(` mode: ${ctx.mode}`);
|
|
4442
5403
|
line(
|
|
4443
|
-
` token: ${ctx.token ? `${maskToken(ctx.token)} ${
|
|
5404
|
+
` token: ${ctx.token ? `${maskToken(ctx.token)} ${import_picocolors13.default.dim(`(${tokenSource})`)}` : import_picocolors13.default.yellow("none \u2014 run `workser login`")}`
|
|
4444
5405
|
);
|
|
4445
5406
|
line(
|
|
4446
|
-
` project: ${ctx.projectId ??
|
|
5407
|
+
` project: ${ctx.projectId ?? import_picocolors13.default.dim("none")}` + (link?.name ? ` ${import_picocolors13.default.dim(`(${link.name})`)}` : "") + (projectSource ? import_picocolors13.default.dim(` [${projectSource}]`) : "")
|
|
4447
5408
|
);
|
|
4448
5409
|
line(` cwd: ${ctx.cwd}`);
|
|
5410
|
+
if (envIgnored) {
|
|
5411
|
+
line("");
|
|
5412
|
+
line(
|
|
5413
|
+
import_picocolors13.default.yellow(
|
|
5414
|
+
` WORKSER_ENV=${env} is not in effect \u2014 ${endpointSource} wins.`
|
|
5415
|
+
)
|
|
5416
|
+
);
|
|
5417
|
+
line(
|
|
5418
|
+
import_picocolors13.default.dim(
|
|
5419
|
+
` Re-run \`workser login\` to switch (the saved token is tied to ${ctx.endpoint}),`
|
|
5420
|
+
)
|
|
5421
|
+
);
|
|
5422
|
+
line(import_picocolors13.default.dim(` or pass --endpoint ${ENV_BASE_URLS[env]}.`));
|
|
5423
|
+
}
|
|
4449
5424
|
});
|
|
4450
5425
|
})
|
|
4451
5426
|
);
|
|
@@ -4456,25 +5431,25 @@ function maskToken(token) {
|
|
|
4456
5431
|
}
|
|
4457
5432
|
|
|
4458
5433
|
// src/commands/agent.ts
|
|
4459
|
-
var
|
|
5434
|
+
var import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
4460
5435
|
function registerAgent(program3) {
|
|
4461
5436
|
const agent = program3.command("agent").description("Delegate focused subtasks to your configured agent roles (each runs isolated)");
|
|
4462
5437
|
agent.command("list").description("List the main agent (+ backup) and the configured subagents").action(
|
|
4463
5438
|
action(async ({ ctx }) => {
|
|
4464
5439
|
const cfg = await api(ctx, "/v1/agents");
|
|
4465
5440
|
ok(cfg, () => {
|
|
4466
|
-
line(
|
|
4467
|
-
line(
|
|
5441
|
+
line(import_picocolors14.default.bold("main agent:") + " " + (cfg?.mainAgent ?? import_picocolors14.default.dim("none")));
|
|
5442
|
+
line(import_picocolors14.default.bold("backup agent:") + " " + (cfg?.backupAgent ?? import_picocolors14.default.dim("none")));
|
|
4468
5443
|
if (cfg?.effectiveMainAgent && cfg.effectiveMainAgent !== cfg.mainAgent) {
|
|
4469
5444
|
line(
|
|
4470
|
-
|
|
5445
|
+
import_picocolors14.default.yellow(
|
|
4471
5446
|
` \u2937 failover active: runs use ${cfg.effectiveMainAgent} (main not available)`
|
|
4472
5447
|
)
|
|
4473
5448
|
);
|
|
4474
5449
|
}
|
|
4475
5450
|
const roles = cfg?.roles ?? [];
|
|
4476
|
-
if (!roles.length) return line(
|
|
4477
|
-
line(
|
|
5451
|
+
if (!roles.length) return line(import_picocolors14.default.dim("No subagents configured. Add them in the Workser Orbit Agents screen."));
|
|
5452
|
+
line(import_picocolors14.default.bold("subagents:"));
|
|
4478
5453
|
for (const r of roles) line(" " + formatRole(r));
|
|
4479
5454
|
});
|
|
4480
5455
|
})
|
|
@@ -4488,8 +5463,8 @@ function registerAgent(program3) {
|
|
|
4488
5463
|
backupAgent: cfg?.backupAgent ?? null
|
|
4489
5464
|
},
|
|
4490
5465
|
() => {
|
|
4491
|
-
line(
|
|
4492
|
-
line(
|
|
5466
|
+
line(import_picocolors14.default.bold("main agent:") + " " + (cfg?.mainAgent ?? import_picocolors14.default.dim("none")));
|
|
5467
|
+
line(import_picocolors14.default.bold("backup agent:") + " " + (cfg?.backupAgent ?? import_picocolors14.default.dim("none")));
|
|
4493
5468
|
}
|
|
4494
5469
|
);
|
|
4495
5470
|
})
|
|
@@ -4511,21 +5486,21 @@ function registerAgent(program3) {
|
|
|
4511
5486
|
);
|
|
4512
5487
|
}
|
|
4513
5488
|
function formatRole(r) {
|
|
4514
|
-
const label =
|
|
4515
|
-
const agent =
|
|
4516
|
-
const enabled = r.enabled === false ?
|
|
5489
|
+
const label = import_picocolors14.default.yellow(r.role);
|
|
5490
|
+
const agent = import_picocolors14.default.dim("\xB7 " + (r.agent ?? "?"));
|
|
5491
|
+
const enabled = r.enabled === false ? import_picocolors14.default.red("disabled") : import_picocolors14.default.green("enabled");
|
|
4517
5492
|
const runnable = r.installed && r.authed !== false;
|
|
4518
|
-
const ready = runnable ?
|
|
5493
|
+
const ready = runnable ? import_picocolors14.default.green("runnable") : import_picocolors14.default.dim("not runnable");
|
|
4519
5494
|
const extras = [];
|
|
4520
5495
|
if (r.model) extras.push(`model ${r.model}`);
|
|
4521
5496
|
if (Array.isArray(r.apps) && r.apps.length) extras.push(`apps: ${r.apps.join(",")}`);
|
|
4522
5497
|
if (Array.isArray(r.mcp) && r.mcp.length) extras.push(`mcp: ${r.mcp.length}`);
|
|
4523
|
-
const tail = extras.length ? " " +
|
|
5498
|
+
const tail = extras.length ? " " + import_picocolors14.default.dim(extras.join(" \xB7 ")) : "";
|
|
4524
5499
|
return `${label} ${agent} ${enabled} ${ready}${tail}`;
|
|
4525
5500
|
}
|
|
4526
5501
|
|
|
4527
5502
|
// src/commands/verify.ts
|
|
4528
|
-
var
|
|
5503
|
+
var import_picocolors15 = __toESM(require_picocolors(), 1);
|
|
4529
5504
|
function registerVerify(program3) {
|
|
4530
5505
|
program3.command("verify").description(
|
|
4531
5506
|
"Run the project's checks (typecheck/lint/build) \u2014 use before declaring a task done"
|
|
@@ -4546,23 +5521,23 @@ function registerVerify(program3) {
|
|
|
4546
5521
|
function printVerify(res) {
|
|
4547
5522
|
if (!res) return;
|
|
4548
5523
|
if (!res.checks?.length) {
|
|
4549
|
-
line(
|
|
5524
|
+
line(import_picocolors15.default.dim(res.note ?? "No checks detected."));
|
|
4550
5525
|
return;
|
|
4551
5526
|
}
|
|
4552
5527
|
for (const c of res.checks) {
|
|
4553
5528
|
line(
|
|
4554
|
-
` ${c.ok ?
|
|
5529
|
+
` ${c.ok ? import_picocolors15.default.green("\u2713") : import_picocolors15.default.red("\u2717")} ${c.name}${c.ok ? "" : import_picocolors15.default.dim(` (exit ${c.exitCode})`)}`
|
|
4555
5530
|
);
|
|
4556
5531
|
}
|
|
4557
5532
|
if (res.ok) success("All checks passed");
|
|
4558
5533
|
else
|
|
4559
5534
|
line(
|
|
4560
|
-
|
|
5535
|
+
import_picocolors15.default.red("Some checks failed \u2014 fix the errors above and re-run ") + import_picocolors15.default.bold("workser verify") + import_picocolors15.default.red(".")
|
|
4561
5536
|
);
|
|
4562
5537
|
}
|
|
4563
5538
|
|
|
4564
5539
|
// src/commands/workflow.ts
|
|
4565
|
-
var
|
|
5540
|
+
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
4566
5541
|
function registerWorkflow(program3) {
|
|
4567
5542
|
const wf = program3.command("workflow").description("Create, run, and inspect workflow automations for the project");
|
|
4568
5543
|
wf.command("list").description("List the project's workflows").action(
|
|
@@ -4570,10 +5545,10 @@ function registerWorkflow(program3) {
|
|
|
4570
5545
|
const projectId = requireProject(ctx);
|
|
4571
5546
|
const items = await api(ctx, `/v1/projects/${projectId}/workflows`);
|
|
4572
5547
|
ok(items, () => {
|
|
4573
|
-
if (!items?.length) return line(
|
|
5548
|
+
if (!items?.length) return line(import_picocolors16.default.dim("No workflows yet. `workser workflow create`."));
|
|
4574
5549
|
for (const w of items) {
|
|
4575
|
-
const status = w.is_active ?
|
|
4576
|
-
line(`${w.id} ${
|
|
5550
|
+
const status = w.is_active ? import_picocolors16.default.green("active") : import_picocolors16.default.dim("inactive");
|
|
5551
|
+
line(`${w.id} ${import_picocolors16.default.bold(w.name ?? "Untitled")} ${status}`);
|
|
4577
5552
|
}
|
|
4578
5553
|
});
|
|
4579
5554
|
})
|
|
@@ -4585,7 +5560,7 @@ function registerWorkflow(program3) {
|
|
|
4585
5560
|
const res = await api(ctx, `/v1/projects/${projectId}/workflows`, {
|
|
4586
5561
|
body: { name: args[0], ...extra }
|
|
4587
5562
|
});
|
|
4588
|
-
ok(res, () => line(`Created workflow ${
|
|
5563
|
+
ok(res, () => line(`Created workflow ${import_picocolors16.default.bold(res.id)}.`));
|
|
4589
5564
|
})
|
|
4590
5565
|
);
|
|
4591
5566
|
wf.command("get <id>").description("Show a workflow's full definition").action(
|
|
@@ -4620,8 +5595,8 @@ function registerWorkflow(program3) {
|
|
|
4620
5595
|
action(async ({ ctx, args }) => {
|
|
4621
5596
|
const items = await api(ctx, `/v1/workflows/${args[0]}/executions`);
|
|
4622
5597
|
ok(items, () => {
|
|
4623
|
-
if (!items?.length) return line(
|
|
4624
|
-
for (const e of items) line(`${e.id} ${e.status ?? ""} ${
|
|
5598
|
+
if (!items?.length) return line(import_picocolors16.default.dim("No runs yet."));
|
|
5599
|
+
for (const e of items) line(`${e.id} ${e.status ?? ""} ${import_picocolors16.default.dim(e.started_at ?? "")}`);
|
|
4625
5600
|
});
|
|
4626
5601
|
})
|
|
4627
5602
|
);
|
|
@@ -4629,15 +5604,15 @@ function registerWorkflow(program3) {
|
|
|
4629
5604
|
action(async ({ ctx, args }) => {
|
|
4630
5605
|
const items = await api(ctx, `/v1/node-types`, { query: { q: args[0] } });
|
|
4631
5606
|
ok(items, () => {
|
|
4632
|
-
if (!items?.length) return line(
|
|
4633
|
-
for (const n of items) line(`${n.name ?? n.type} ${
|
|
5607
|
+
if (!items?.length) return line(import_picocolors16.default.dim("No matching node types."));
|
|
5608
|
+
for (const n of items) line(`${n.name ?? n.type} ${import_picocolors16.default.dim(n.category ?? "")}`);
|
|
4634
5609
|
});
|
|
4635
5610
|
})
|
|
4636
5611
|
);
|
|
4637
5612
|
}
|
|
4638
5613
|
|
|
4639
5614
|
// src/commands/app.ts
|
|
4640
|
-
var
|
|
5615
|
+
var import_picocolors17 = __toESM(require_picocolors(), 1);
|
|
4641
5616
|
function registerApp(program3) {
|
|
4642
5617
|
const appCmd = program3.command("app").description("Connect and use third-party app integrations (Gmail, Slack, Stripe, ...)");
|
|
4643
5618
|
appCmd.command("list").description("List connectable toolkits and this project's existing connections").option("--toolkit <slug>", "filter connections to one toolkit").action(
|
|
@@ -4650,8 +5625,8 @@ function registerApp(program3) {
|
|
|
4650
5625
|
ok({ catalog, connections }, () => {
|
|
4651
5626
|
const connected = new Set((connections ?? []).map((c) => c.toolkit ?? c.composio_app));
|
|
4652
5627
|
for (const t of catalog ?? []) {
|
|
4653
|
-
const status = connected.has(t.slug) ?
|
|
4654
|
-
line(`${t.slug} ${
|
|
5628
|
+
const status = connected.has(t.slug) ? import_picocolors17.default.green("connected") : import_picocolors17.default.dim("not connected");
|
|
5629
|
+
line(`${t.slug} ${import_picocolors17.default.bold(t.name ?? t.slug)} ${status}`);
|
|
4655
5630
|
}
|
|
4656
5631
|
});
|
|
4657
5632
|
})
|
|
@@ -4668,7 +5643,7 @@ function registerApp(program3) {
|
|
|
4668
5643
|
});
|
|
4669
5644
|
ok(
|
|
4670
5645
|
res,
|
|
4671
|
-
() => res.oauth_url ? line(`Open this URL to finish connecting: ${
|
|
5646
|
+
() => res.oauth_url ? line(`Open this URL to finish connecting: ${import_picocolors17.default.underline(res.oauth_url)}`) : line(`Connection ${res.connection_id} is ${res.status}.`)
|
|
4672
5647
|
);
|
|
4673
5648
|
})
|
|
4674
5649
|
);
|
|
@@ -4686,8 +5661,8 @@ function registerApp(program3) {
|
|
|
4686
5661
|
const projectId = requireProject(ctx);
|
|
4687
5662
|
const items = await api(ctx, `/v1/projects/${projectId}/integrations/${args[0]}/tools`);
|
|
4688
5663
|
ok(items, () => {
|
|
4689
|
-
if (!items?.length) return line(
|
|
4690
|
-
for (const t of items) line(`${t.slug} ${
|
|
5664
|
+
if (!items?.length) return line(import_picocolors17.default.dim("No tools found."));
|
|
5665
|
+
for (const t of items) line(`${t.slug} ${import_picocolors17.default.dim(t.description ?? "")}`);
|
|
4691
5666
|
});
|
|
4692
5667
|
})
|
|
4693
5668
|
);
|
|
@@ -4703,7 +5678,7 @@ function registerApp(program3) {
|
|
|
4703
5678
|
}
|
|
4704
5679
|
|
|
4705
5680
|
// src/commands/tool.ts
|
|
4706
|
-
var
|
|
5681
|
+
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
4707
5682
|
function registerTool(program3) {
|
|
4708
5683
|
const tool = program3.command("tool").description(
|
|
4709
5684
|
"Computer-use tools: filesystem, shell, screenshot, input control, clipboard, notifications, basic browser"
|
|
@@ -4712,7 +5687,7 @@ function registerTool(program3) {
|
|
|
4712
5687
|
action(async ({ ctx }) => {
|
|
4713
5688
|
const tools = await api(ctx, "/v1/tool/list");
|
|
4714
5689
|
ok(tools, () => {
|
|
4715
|
-
if (!tools?.length) return line(
|
|
5690
|
+
if (!tools?.length) return line(import_picocolors18.default.dim("No tools available."));
|
|
4716
5691
|
const byCategory = /* @__PURE__ */ new Map();
|
|
4717
5692
|
for (const t of tools) {
|
|
4718
5693
|
const list = byCategory.get(t.category) ?? [];
|
|
@@ -4720,9 +5695,9 @@ function registerTool(program3) {
|
|
|
4720
5695
|
byCategory.set(t.category, list);
|
|
4721
5696
|
}
|
|
4722
5697
|
for (const [category, items] of byCategory) {
|
|
4723
|
-
line(
|
|
5698
|
+
line(import_picocolors18.default.bold(category) + ":");
|
|
4724
5699
|
for (const t of items) {
|
|
4725
|
-
line(` ${t.name} ${
|
|
5700
|
+
line(` ${t.name} ${import_picocolors18.default.dim(t.description ?? "")}`);
|
|
4726
5701
|
}
|
|
4727
5702
|
}
|
|
4728
5703
|
});
|
|
@@ -4740,7 +5715,7 @@ function registerTool(program3) {
|
|
|
4740
5715
|
}
|
|
4741
5716
|
|
|
4742
5717
|
// src/commands/memory.ts
|
|
4743
|
-
var
|
|
5718
|
+
var import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
4744
5719
|
function registerMemory(program3) {
|
|
4745
5720
|
const memory = program3.command("memory").description("Durable, cross-conversation project memory (shared with cloud agents on the same project)");
|
|
4746
5721
|
memory.command("add <content>").description("Store something worth remembering across future conversations").option("--metadata <json>", "extra metadata for filtering, as a JSON string").option("--id <customId>", "custom id for dedup/updates").action(
|
|
@@ -4764,9 +5739,9 @@ function registerMemory(program3) {
|
|
|
4764
5739
|
});
|
|
4765
5740
|
ok(res, () => {
|
|
4766
5741
|
const results = res?.results ?? res ?? [];
|
|
4767
|
-
if (!results?.length) return line(
|
|
5742
|
+
if (!results?.length) return line(import_picocolors19.default.dim("No matching memories."));
|
|
4768
5743
|
for (const r of results) {
|
|
4769
|
-
line(`${
|
|
5744
|
+
line(`${import_picocolors19.default.dim(r.id ?? "?")} ${r.memory ?? r.content ?? ""}`);
|
|
4770
5745
|
}
|
|
4771
5746
|
});
|
|
4772
5747
|
})
|
|
@@ -4783,7 +5758,7 @@ function registerMemory(program3) {
|
|
|
4783
5758
|
}
|
|
4784
5759
|
|
|
4785
5760
|
// src/commands/business.ts
|
|
4786
|
-
var
|
|
5761
|
+
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
4787
5762
|
var RESOURCE_PATHS = {
|
|
4788
5763
|
"business-config": "business-config",
|
|
4789
5764
|
"business-settings": "business-settings",
|
|
@@ -4843,7 +5818,7 @@ function registerBusiness(program3) {
|
|
|
4843
5818
|
const projectId = requireProject(ctx);
|
|
4844
5819
|
const [resource] = args;
|
|
4845
5820
|
const res = await api(ctx, businessPath(projectId, resource), { body: JSON.parse(opts.body) });
|
|
4846
|
-
ok(res, () => line(`Created ${resource} ${
|
|
5821
|
+
ok(res, () => line(`Created ${resource} ${import_picocolors20.default.bold(res?.id ?? "")}.`));
|
|
4847
5822
|
})
|
|
4848
5823
|
);
|
|
4849
5824
|
biz.command("update <resource> <id>").description("Update a record by id (PATCH/PUT \u2014 matches the underlying route)").option("--body <json>", "changed fields as a JSON object string", "{}").action(
|
|
@@ -4885,9 +5860,9 @@ function businessPath(projectId, resource, subpath) {
|
|
|
4885
5860
|
}
|
|
4886
5861
|
|
|
4887
5862
|
// src/commands/artifact.ts
|
|
4888
|
-
var
|
|
5863
|
+
var import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
4889
5864
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
4890
|
-
import { resolve as resolve2, basename as
|
|
5865
|
+
import { resolve as resolve2, basename as basename3 } from "path";
|
|
4891
5866
|
var KINDS = [
|
|
4892
5867
|
"file",
|
|
4893
5868
|
"folder",
|
|
@@ -4944,14 +5919,14 @@ function registerArtifact(program3) {
|
|
|
4944
5919
|
path: absPath,
|
|
4945
5920
|
url,
|
|
4946
5921
|
kind,
|
|
4947
|
-
title: opts.title || (absPath ?
|
|
5922
|
+
title: opts.title || (absPath ? basename3(absPath) : url),
|
|
4948
5923
|
description: opts.description
|
|
4949
5924
|
}
|
|
4950
5925
|
});
|
|
4951
5926
|
ok(
|
|
4952
5927
|
res,
|
|
4953
5928
|
() => success(
|
|
4954
|
-
`Recorded ${
|
|
5929
|
+
`Recorded ${import_picocolors21.default.bold(res?.title ?? "artifact")}${res?.kind ? import_picocolors21.default.dim(` (${res.kind})`) : ""}`
|
|
4955
5930
|
)
|
|
4956
5931
|
);
|
|
4957
5932
|
})
|
|
@@ -4965,15 +5940,85 @@ function registerArtifact(program3) {
|
|
|
4965
5940
|
}
|
|
4966
5941
|
function printRun(run) {
|
|
4967
5942
|
if (!run) return;
|
|
4968
|
-
line(` run ${
|
|
5943
|
+
line(` run ${import_picocolors21.default.bold(run.runId)}`);
|
|
4969
5944
|
if (run.taskId) line(` task ${run.taskId}`);
|
|
4970
5945
|
if (run.conversationId) line(` chat ${run.conversationId}`);
|
|
4971
5946
|
if (run.projectId) line(` project ${run.projectId}`);
|
|
4972
|
-
if (run.cwd) line(` folder ${
|
|
5947
|
+
if (run.cwd) line(` folder ${import_picocolors21.default.dim(run.cwd)}`);
|
|
5948
|
+
}
|
|
5949
|
+
|
|
5950
|
+
// src/commands/image.ts
|
|
5951
|
+
import { writeFile as writeFile3, mkdir } from "fs/promises";
|
|
5952
|
+
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
5953
|
+
function registerImage(program3) {
|
|
5954
|
+
const image = program3.command("image").description("Generate images from a text prompt");
|
|
5955
|
+
image.command("generate <prompt>").alias("gen").description("Generate an image and return its public URL").option(
|
|
5956
|
+
"-r, --reference <url...>",
|
|
5957
|
+
"condition on existing image URLs (image-to-image); up to 4"
|
|
5958
|
+
).option(
|
|
5959
|
+
"-o, --output <path>",
|
|
5960
|
+
"also download the first image to this local path"
|
|
5961
|
+
).action(
|
|
5962
|
+
action(async ({ ctx, opts, args }) => {
|
|
5963
|
+
const projectId = requireProject(ctx);
|
|
5964
|
+
const prompt = String(args[0] ?? "").trim();
|
|
5965
|
+
if (!prompt) {
|
|
5966
|
+
throw new WorkserError("A prompt is required.", {
|
|
5967
|
+
code: "bad_request"
|
|
5968
|
+
});
|
|
5969
|
+
}
|
|
5970
|
+
const references = opts.reference?.filter(
|
|
5971
|
+
Boolean
|
|
5972
|
+
);
|
|
5973
|
+
const res = await api(
|
|
5974
|
+
ctx,
|
|
5975
|
+
`/projects/${projectId}/images/generate`,
|
|
5976
|
+
{
|
|
5977
|
+
method: "POST",
|
|
5978
|
+
body: {
|
|
5979
|
+
prompt,
|
|
5980
|
+
...references?.length ? { referenceImageUrls: references.slice(0, 4) } : {}
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
);
|
|
5984
|
+
const images = res.images ?? [];
|
|
5985
|
+
if (!images.length) {
|
|
5986
|
+
const said = res.texts?.join(" ").trim();
|
|
5987
|
+
throw new WorkserError(
|
|
5988
|
+
said ? `No image was generated. The model said: ${said}` : "No image was generated.",
|
|
5989
|
+
{ code: "no_image" }
|
|
5990
|
+
);
|
|
5991
|
+
}
|
|
5992
|
+
let savedTo;
|
|
5993
|
+
if (opts.output) {
|
|
5994
|
+
savedTo = await download(images[0].publicUrl, String(opts.output));
|
|
5995
|
+
}
|
|
5996
|
+
ok({ images, texts: res.texts, savedTo }, () => {
|
|
5997
|
+
for (const img of images) {
|
|
5998
|
+
success(img.publicUrl);
|
|
5999
|
+
}
|
|
6000
|
+
if (savedTo) info(`Saved to ${savedTo}`);
|
|
6001
|
+
for (const text of res.texts ?? []) line(text);
|
|
6002
|
+
});
|
|
6003
|
+
})
|
|
6004
|
+
);
|
|
6005
|
+
}
|
|
6006
|
+
async function download(url, output) {
|
|
6007
|
+
const target = resolve3(output);
|
|
6008
|
+
const res = await fetch(url);
|
|
6009
|
+
if (!res.ok) {
|
|
6010
|
+
throw new WorkserError(
|
|
6011
|
+
`The image was generated but could not be downloaded (${res.status}). It is still available at ${url}`,
|
|
6012
|
+
{ code: "download_failed" }
|
|
6013
|
+
);
|
|
6014
|
+
}
|
|
6015
|
+
await mkdir(dirname2(target), { recursive: true });
|
|
6016
|
+
await writeFile3(target, Buffer.from(await res.arrayBuffer()));
|
|
6017
|
+
return target;
|
|
4973
6018
|
}
|
|
4974
6019
|
|
|
4975
6020
|
// src/commands/ask.ts
|
|
4976
|
-
var
|
|
6021
|
+
var import_picocolors22 = __toESM(require_picocolors(), 1);
|
|
4977
6022
|
var TYPES = [
|
|
4978
6023
|
"input",
|
|
4979
6024
|
"choice",
|
|
@@ -5024,7 +6069,7 @@ function registerAsk(program3) {
|
|
|
5024
6069
|
code: "bad_request"
|
|
5025
6070
|
});
|
|
5026
6071
|
}
|
|
5027
|
-
info(
|
|
6072
|
+
info(import_picocolors22.default.dim("Waiting for the user to answer\u2026"));
|
|
5028
6073
|
const res = await api(ctx, `/v1/runs/${runTarget(ctx)}/ask`, {
|
|
5029
6074
|
body: {
|
|
5030
6075
|
type,
|
|
@@ -5050,12 +6095,12 @@ function deriveTitle(message) {
|
|
|
5050
6095
|
function printAnswer(res) {
|
|
5051
6096
|
if (!res) return;
|
|
5052
6097
|
if (res.status === "answered") {
|
|
5053
|
-
line(` ${
|
|
6098
|
+
line(` ${import_picocolors22.default.green("answered")}`);
|
|
5054
6099
|
const value = extract(res.response);
|
|
5055
6100
|
if (value) line(` ${value}`);
|
|
5056
6101
|
return;
|
|
5057
6102
|
}
|
|
5058
|
-
line(` ${
|
|
6103
|
+
line(` ${import_picocolors22.default.yellow(res.status)} ${import_picocolors22.default.dim(res.reason ?? "")}`);
|
|
5059
6104
|
}
|
|
5060
6105
|
function extract(response) {
|
|
5061
6106
|
if (response == null) return "";
|
|
@@ -5075,15 +6120,22 @@ function extract(response) {
|
|
|
5075
6120
|
|
|
5076
6121
|
// src/index.ts
|
|
5077
6122
|
var pkg = {
|
|
5078
|
-
version: true ? "0.1
|
|
6123
|
+
version: true ? "0.2.1" : "0.0.0-dev"
|
|
5079
6124
|
};
|
|
5080
6125
|
var program2 = new Command();
|
|
5081
6126
|
program2.name("workser").description(
|
|
5082
6127
|
"Workser CLI \u2014 give your local AI agent native DevOps & infrastructure.\nThe agent runs `workser \u2026` to provision databases, deploy, and manage real apps\non Workser \u2014 on the user's own tokens, through the Orbit cockpit (auth + approvals)."
|
|
5083
|
-
).version(pkg.version, "-v, --version", "print the CLI version").option(
|
|
6128
|
+
).version(pkg.version, "-v, --version", "print the CLI version").option(
|
|
6129
|
+
"--json",
|
|
6130
|
+
"machine-readable JSON output (always use this from agents/scripts)"
|
|
6131
|
+
).option("-q, --quiet", "suppress non-essential output").option(
|
|
6132
|
+
"-p, --project <id>",
|
|
6133
|
+
"target project id (overrides the linked project)"
|
|
6134
|
+
).option("-C, --cwd <dir>", "run as if started in <dir>").option("--endpoint <url>", "override the Workser endpoint (daemon or cloud)").option("--token <token>", "override the auth token").hook("preAction", (thisCommand, actionCommand) => {
|
|
5084
6135
|
const o = actionCommand.optsWithGlobals();
|
|
5085
6136
|
configureOutput({ json: o.json, quiet: o.quiet });
|
|
5086
6137
|
});
|
|
6138
|
+
registerHelp(program2);
|
|
5087
6139
|
registerStatus(program2);
|
|
5088
6140
|
registerLogin(program2);
|
|
5089
6141
|
registerWhoami(program2);
|
|
@@ -5091,6 +6143,7 @@ registerProject(program2);
|
|
|
5091
6143
|
registerDb(program2);
|
|
5092
6144
|
registerAuth(program2);
|
|
5093
6145
|
registerStorage(program2);
|
|
6146
|
+
registerNeon(program2);
|
|
5094
6147
|
registerEnv(program2);
|
|
5095
6148
|
registerDeploy(program2);
|
|
5096
6149
|
registerVersions(program2);
|
|
@@ -5106,5 +6159,6 @@ registerTool(program2);
|
|
|
5106
6159
|
registerMemory(program2);
|
|
5107
6160
|
registerBusiness(program2);
|
|
5108
6161
|
registerArtifact(program2);
|
|
6162
|
+
registerImage(program2);
|
|
5109
6163
|
registerAsk(program2);
|
|
5110
6164
|
program2.parseAsync(process.argv).catch((e) => fail(e));
|