@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/dist/index.js CHANGED
@@ -3650,16 +3650,580 @@ function exitCodeFor(e) {
3650
3650
  return 1;
3651
3651
  }
3652
3652
 
3653
- // src/commands/status.ts
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 { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
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 mode2 = /^https?:\/\/(127\.0\.0\.1|localhost|\[::1\])(:|\/|$)/i.test(
3707
- endpoint
3708
- ) ? "daemon" : "cloud";
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
- res = await fetch(url, {
3759
- method: opts.method ?? (opts.body !== void 0 ? "POST" : "GET"),
3760
- headers,
3761
- body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
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
- { code: "not_connected", details: e instanceof Error ? e.message : String(e) }
4408
+ {
4409
+ code: "not_connected",
4410
+ details: e instanceof Error ? e.message : String(e)
4411
+ }
3767
4412
  );
3768
4413
  }
3769
- const text = await res.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(import_picocolors2.default.bold("Workser") + import_picocolors2.default.dim(` (${ctx.mode} \xB7 ${ctx.endpoint})`));
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 ? import_picocolors2.default.dim(` (${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 ? ` ${import_picocolors2.default.cyan(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 import_picocolors2.default.green(s);
4471
+ return import_picocolors3.default.green(s);
3827
4472
  case "error":
3828
4473
  case "failed":
3829
4474
  case "canceled":
3830
- return import_picocolors2.default.red(s);
4475
+ return import_picocolors3.default.red(s);
3831
4476
  case "building":
3832
4477
  case "queued":
3833
4478
  case "deploying":
3834
- return import_picocolors2.default.yellow(s);
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 import_picocolors3 = __toESM(require_picocolors(), 1);
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(`${import_picocolors3.default.bold(p.name ?? "\u2014")}${p.id ? import_picocolors3.default.dim(` (${p.id})`) : ""}`);
3904
- if (p.url) line(import_picocolors3.default.cyan(p.url));
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(import_picocolors3.default.dim("No projects."));
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 ? import_picocolors3.default.green("\u25CF ") : " ";
3933
- line(`${pinned}${p.name ?? "\u2014"}${p.id ? import_picocolors3.default.dim(` (${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 import_picocolors4 = __toESM(require_picocolors(), 1);
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${import_picocolors4.default.dim(` (${res.name ?? "db"})`)}.` : `Provisioned database ${import_picocolors4.default.bold(res.name ?? "db")}${import_picocolors4.default.dim(` ${res.region ?? ""} ${res.status ?? ""}`.trimEnd())}.`
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(import_picocolors4.default.dim("No database yet. `workser db create`."));
3962
- for (const d of items) line(`${d.name}${import_picocolors4.default.dim(` ${d.region ?? ""} ${d.status ?? ""}`.trimEnd())}`);
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(import_picocolors4.default.dim("No tables."));
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" ? import_picocolors4.default.dim(`${t.table_schema}.`) : "";
3982
- const cols = t.column_count != null ? import_picocolors4.default.dim(` ${t.column_count} cols`) : "";
3983
- const count = t.row_count != null ? import_picocolors4.default.dim(` ${t.row_count} rows`) : "";
3984
- const size = t.table_size != null ? import_picocolors4.default.dim(` ${fmtBytes(t.table_size)}`) : "";
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(import_picocolors4.default.dim("No columns (does the table exist?)."));
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 ? import_picocolors4.default.dim(" not null") : "";
3999
- const def = c.column_default ? import_picocolors4.default.dim(` default ${c.column_default}`) : "";
4000
- line(`${c.column_name} ${import_picocolors4.default.cyan(c.data_type)}${nn}${def}`);
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(import_picocolors4.default.dim("(0 rows)"));
4728
+ line(import_picocolors5.default.dim("(0 rows)"));
4027
4729
  return;
4028
4730
  }
4029
4731
  const cols = Object.keys(rows[0]);
4030
- line(import_picocolors4.default.dim(cols.join(" ")));
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
- import_picocolors4.default.dim(
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 import_picocolors4.default.dim("\u2205");
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 import_picocolors5 = __toESM(require_picocolors(), 1);
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${import_picocolors5.default.dim(` (${providers})`)}.` : `Enabled auth ${import_picocolors5.default.dim(`(${providers})`)}.`
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 ${import_picocolors5.default.dim(`(${providers})`)}`);
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 import_picocolors6 = __toESM(require_picocolors(), 1);
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${import_picocolors6.default.dim(` (${res.bucket})`)}.` : `Provisioned bucket ${import_picocolors6.default.bold(res.bucket || "(pending)")}.`
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(import_picocolors6.default.dim("No bucket yet. `workser storage create`."));
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(import_picocolors6.default.dim("No objects."));
4825
+ if (!objects.length) return line(import_picocolors7.default.dim("No objects."));
4124
4826
  for (const o of objects) {
4125
- line(`${o.key}${import_picocolors6.default.dim(` ${fmtSize(o.size)}${o.lastModified ? " " + o.lastModified : ""}`)}`);
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 ${import_picocolors6.default.bold(res.key ?? key)} ${import_picocolors6.default.dim(`(${fmtSize(bytes.length)})`)}.`)
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 ${import_picocolors6.default.bold(key)} \u2192 ${out} ${import_picocolors6.default.dim(`(${fmtSize(bytes.length)})`)}.`)
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 import_picocolors7 = __toESM(require_picocolors(), 1);
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(import_picocolors7.default.dim(`on ${res.webAppName} (primary app) \u2014 use --app to target another`));
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(import_picocolors7.default.dim("No variables set."));
4242
- for (const v of items) line(`${v.key}${import_picocolors7.default.dim(" = " + (v.masked ?? "\u2022\u2022\u2022\u2022"))}`);
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 import_picocolors8 = __toESM(require_picocolors(), 1);
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).action(
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: { prod: Boolean(opts.prod), cwd: ctx.cwd }
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 ? " " + import_picocolors8.default.cyan(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 ${import_picocolors8.default.cyan(dep.url)}`);
4298
- else line(`deploy ${colorStatus(dep.status)} ${import_picocolors8.default.dim(`(${dep.id ?? "?"})`)}${dep.url ? " " + dep.url : ""}`);
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 import_picocolors9 = __toESM(require_picocolors(), 1);
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(import_picocolors9.default.dim("No versions yet. `workser deploy` to create one."));
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 = import_picocolors9.default.yellow(shortRef(v.ref));
4319
- const when = import_picocolors9.default.dim(formatTime(v.createdAt));
4320
- const msg = (v.message ?? "").trim() || import_picocolors9.default.dim("(no message)");
4321
- const badge = v.deployed ? " " + import_picocolors9.default.green("deployed") : "";
4322
- const url = v.url ? " " + import_picocolors9.default.cyan(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 import_picocolors10 = __toESM(require_picocolors(), 1);
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(import_picocolors10.default.dim("No custom domains."));
4380
- for (const d of items) line(`${d.domain}${import_picocolors10.default.dim(" " + (d.status ?? ""))}`);
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 import_picocolors11 = __toESM(require_picocolors(), 1);
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(import_picocolors11.default.bold("workser doctor"));
4440
- line(` endpoint: ${ctx.endpoint} ${import_picocolors11.default.dim(`(${endpointSource})`)}`);
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)} ${import_picocolors11.default.dim(`(${tokenSource})`)}` : import_picocolors11.default.yellow("none \u2014 run `workser login`")}`
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 ?? import_picocolors11.default.dim("none")}` + (link?.name ? ` ${import_picocolors11.default.dim(`(${link.name})`)}` : "") + (projectSource ? import_picocolors11.default.dim(` [${projectSource}]`) : "")
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 import_picocolors12 = __toESM(require_picocolors(), 1);
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(import_picocolors12.default.bold("main agent:") + " " + (cfg?.mainAgent ?? import_picocolors12.default.dim("none")));
4467
- line(import_picocolors12.default.bold("backup agent:") + " " + (cfg?.backupAgent ?? import_picocolors12.default.dim("none")));
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
- import_picocolors12.default.yellow(
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(import_picocolors12.default.dim("No subagents configured. Add them in the Workser Orbit Agents screen."));
4477
- line(import_picocolors12.default.bold("subagents:"));
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(import_picocolors12.default.bold("main agent:") + " " + (cfg?.mainAgent ?? import_picocolors12.default.dim("none")));
4492
- line(import_picocolors12.default.bold("backup agent:") + " " + (cfg?.backupAgent ?? import_picocolors12.default.dim("none")));
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 = import_picocolors12.default.yellow(r.role);
4515
- const agent = import_picocolors12.default.dim("\xB7 " + (r.agent ?? "?"));
4516
- const enabled = r.enabled === false ? import_picocolors12.default.red("disabled") : import_picocolors12.default.green("enabled");
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 ? import_picocolors12.default.green("runnable") : import_picocolors12.default.dim("not 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 ? " " + import_picocolors12.default.dim(extras.join(" \xB7 ")) : "";
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 import_picocolors13 = __toESM(require_picocolors(), 1);
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(import_picocolors13.default.dim(res.note ?? "No checks detected."));
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 ? import_picocolors13.default.green("\u2713") : import_picocolors13.default.red("\u2717")} ${c.name}${c.ok ? "" : import_picocolors13.default.dim(` (exit ${c.exitCode})`)}`
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
- import_picocolors13.default.red("Some checks failed \u2014 fix the errors above and re-run ") + import_picocolors13.default.bold("workser verify") + import_picocolors13.default.red(".")
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 import_picocolors14 = __toESM(require_picocolors(), 1);
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(import_picocolors14.default.dim("No workflows yet. `workser workflow create`."));
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 ? import_picocolors14.default.green("active") : import_picocolors14.default.dim("inactive");
4576
- line(`${w.id} ${import_picocolors14.default.bold(w.name ?? "Untitled")} ${status}`);
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 ${import_picocolors14.default.bold(res.id)}.`));
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(import_picocolors14.default.dim("No runs yet."));
4624
- for (const e of items) line(`${e.id} ${e.status ?? ""} ${import_picocolors14.default.dim(e.started_at ?? "")}`);
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(import_picocolors14.default.dim("No matching node types."));
4633
- for (const n of items) line(`${n.name ?? n.type} ${import_picocolors14.default.dim(n.category ?? "")}`);
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 import_picocolors15 = __toESM(require_picocolors(), 1);
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) ? import_picocolors15.default.green("connected") : import_picocolors15.default.dim("not connected");
4654
- line(`${t.slug} ${import_picocolors15.default.bold(t.name ?? t.slug)} ${status}`);
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: ${import_picocolors15.default.underline(res.oauth_url)}`) : line(`Connection ${res.connection_id} is ${res.status}.`)
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(import_picocolors15.default.dim("No tools found."));
4690
- for (const t of items) line(`${t.slug} ${import_picocolors15.default.dim(t.description ?? "")}`);
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 import_picocolors16 = __toESM(require_picocolors(), 1);
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(import_picocolors16.default.dim("No tools available."));
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(import_picocolors16.default.bold(category) + ":");
5698
+ line(import_picocolors18.default.bold(category) + ":");
4724
5699
  for (const t of items) {
4725
- line(` ${t.name} ${import_picocolors16.default.dim(t.description ?? "")}`);
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 import_picocolors17 = __toESM(require_picocolors(), 1);
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(import_picocolors17.default.dim("No matching memories."));
5742
+ if (!results?.length) return line(import_picocolors19.default.dim("No matching memories."));
4768
5743
  for (const r of results) {
4769
- line(`${import_picocolors17.default.dim(r.id ?? "?")} ${r.memory ?? r.content ?? ""}`);
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 import_picocolors18 = __toESM(require_picocolors(), 1);
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} ${import_picocolors18.default.bold(res?.id ?? "")}.`));
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 import_picocolors19 = __toESM(require_picocolors(), 1);
5863
+ var import_picocolors21 = __toESM(require_picocolors(), 1);
4889
5864
  import { existsSync as existsSync2, statSync } from "fs";
4890
- import { resolve as resolve2, basename as basename2 } from "path";
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 ? basename2(absPath) : url),
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 ${import_picocolors19.default.bold(res?.title ?? "artifact")}${res?.kind ? import_picocolors19.default.dim(` (${res.kind})`) : ""}`
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 ${import_picocolors19.default.bold(run.runId)}`);
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 ${import_picocolors19.default.dim(run.cwd)}`);
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 import_picocolors20 = __toESM(require_picocolors(), 1);
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(import_picocolors20.default.dim("Waiting for the user to answer\u2026"));
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(` ${import_picocolors20.default.green("answered")}`);
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(` ${import_picocolors20.default.yellow(res.status)} ${import_picocolors20.default.dim(res.reason ?? "")}`);
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.0" : "0.0.0-dev"
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("--json", "machine-readable JSON output (always use this from agents/scripts)").option("-q, --quiet", "suppress non-essential output").option("-p, --project <id>", "target project id (overrides the linked project)").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) => {
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));