@shardflux/cli 0.28.1 → 0.28.3
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/CHANGELOG.md +33 -0
- package/README.md +49 -4
- package/dist/args.d.ts +1 -1
- package/dist/args.js +16 -1
- package/dist/board-args.js +9 -1
- package/dist/board-commands.d.ts +1 -1
- package/dist/board-commands.js +50 -7
- package/dist/commands.d.ts +14 -0
- package/dist/commands.js +123 -6
- package/dist/conversations.d.ts +2 -0
- package/dist/conversations.js +1 -1
- package/dist/credential-device-capability.d.ts +8 -0
- package/dist/credential-device-capability.js +21 -0
- package/dist/credential-setup.d.ts +13 -0
- package/dist/credential-setup.js +170 -0
- package/dist/credential-sources.d.ts +34 -0
- package/dist/credential-sources.js +400 -0
- package/dist/device-bridge.d.ts +8 -1
- package/dist/device-bridge.js +46 -3
- package/dist/device-folders.d.ts +2 -1
- package/dist/device-folders.js +3 -3
- package/dist/device-helper.js +15 -7
- package/dist/device-mode.d.ts +7 -0
- package/dist/device-mode.js +13 -0
- package/dist/device-outcomes.d.ts +3 -0
- package/dist/device-outcomes.js +19 -0
- package/dist/handler-loader.js +2 -1
- package/dist/handlers.js +2 -1
- package/dist/land.d.ts +4 -1
- package/dist/land.js +32 -3
- package/dist/machine-setup.d.ts +1 -0
- package/dist/machine-setup.js +5 -2
- package/dist/main.js +30 -2
- package/dist/orchestrator-args.js +11 -1
- package/dist/orchestrator-commands.js +44 -3
- package/dist/task-auto-land.d.ts +22 -0
- package/dist/task-auto-land.js +162 -0
- package/dist/task-land-hub.py +6 -0
- package/dist/vault.js +3 -8
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- The Shardflux app 0.9.4 (0.28.3+): `shard` installs and updates `@shardflux/desktop-darwin-arm64` / `-x64` and
|
|
6
|
+
`@shardflux/desktop-linux-x64` / `-arm64` 0.9.4 (files with a ⌘2 task, ⌘2 Credentials and Set up from this Mac,
|
|
7
|
+
talking in Safari, new answers stay visible, device request outcomes).
|
|
8
|
+
|
|
9
|
+
- Device request outcomes (0.28.3+, SDK 0.30.3): `shard orchestrator show <input-id>` and `shard task show <task-id>` show device requests, paths and recorded errors, failures first. Device activity keeps the actual error before API delivery.
|
|
10
|
+
|
|
11
|
+
- Input attachments (0.28.3+, SDK 0.30.3): `shard orchestrator say --file <path>` attaches files; repeat the flag, or use `--file - --file-name <name>` to stream stdin. Files-only inputs are supported.
|
|
12
|
+
|
|
13
|
+
- Automatic task landing (0.28.3+): the device helper automatically lands a finished board task's tested result in its linked repository even when the desktop board is closed. It respects Auto land and project landing settings, keeps local edits, waits for a missing folder, retries an interrupted receipt, and records the board only after delivery. Conflicts and unfinished master integration wait for a new result instead of repeating a steer after a service restart.
|
|
14
|
+
|
|
15
|
+
- Linux helper shutdown (0.28.3+): short Linux commands shut down their command-lifetime helper cleanly; service helpers leave stdin with the foreground command.
|
|
16
|
+
|
|
17
|
+
- Task results (0.28.3+, SDK 0.30.3): keep canonical task-result links and automatic resend metadata in orchestrator input output.
|
|
18
|
+
|
|
19
|
+
- Coordinator questions (0.28.3+, SDK 0.30.3): input reads preserve a coordinator wait and its message; internal worker blockers reach their coordinator before a question reaches the person. Existing needs-you answer commands remain unchanged.
|
|
20
|
+
|
|
21
|
+
- Credential setup (0.28.3+, SDK 0.30.3): `shard credentials setup --from-this-computer` brings existing shell, project and login credentials along automatically. `--if-needed` leaves unchanged values alone; `--plan` offers an optional names-only review. Connected agents can request the same secure local transfer through the device helper.
|
|
22
|
+
|
|
23
|
+
- Credential errors (0.28.3+): write-only credential failures never copy upstream prose or details into output, including values shorter than eight bytes and JSON-escaped values. Status and request id remain available.
|
|
24
|
+
|
|
25
|
+
- The Shardflux app 0.9.3 (0.28.2+): `shard` installs and updates `@shardflux/desktop-darwin-arm64` / `-x64` and
|
|
26
|
+
`@shardflux/desktop-linux-x64` / `-arm64` 0.9.3 (files open beside your work, pictures and videos picture in picture,
|
|
27
|
+
⌘2 shows the agents your orchestrator starts, needs-you questions read whole).
|
|
28
|
+
|
|
29
|
+
- Agent credentials (0.28.2+, SDK 0.30.2): environment credential questions can be answered with `shard needs-you answer acred_ID --credential --ask` (or stdin / `--from-file`), with scope chosen before the write. Scope flags preserve organization and thread identity; workspace-only permission flags are rejected for agent credentials.
|
|
30
|
+
|
|
31
|
+
- Agent credential scopes (0.28.2+): `shard secrets create NAME --scope agents --org ORG --ask` adds a credential for all your agents; use `--scope project --project PROJECT` or `--scope conversation --conversation CONVERSATION` for a local override. Import, list, get, update, rotate, versions, access-log and delete accept the same scope. Values use the secure page, stdin, or `--from-file` and never command arguments.
|
|
32
|
+
- Conversation origin (0.28.2+, SDK 0.30.2): conversation starts stay compatible with older APIs: send launching-conversation provenance only after `/v1/meta` advertises `capabilities.conversation_origin: true`; no create fallback retry.
|
|
33
|
+
|
|
34
|
+
- Question explanations (0.28.2+, SDK 0.30.2): needs-you output prints each question and its optional `option_details` in full, including what each choice does and what follows it.
|
|
35
|
+
|
|
36
|
+
- Launch attribution (0.28.2+, SDK 0.30.2): preserve the requesting orchestrator thread when `project_link` starts work in a laptop folder. API and guest title sources strip pasted-content/image transport markers, so `shard agents list` shows the request text. Tasks delete their workers and spawned descendants along with their computers.
|
|
37
|
+
|
|
5
38
|
- The Shardflux app 0.9.2 (0.28.1+): `shard` installs and updates `@shardflux/desktop-darwin-arm64` / `-x64` and
|
|
6
39
|
`@shardflux/desktop-linux-x64` / `-arm64` 0.9.2 (Stop in ⌘1, organization choice at the first sign-in, Sign in after
|
|
7
40
|
signing out).
|
package/README.md
CHANGED
|
@@ -34,6 +34,12 @@ an explicit selection keeps the default credentials and target status. An approv
|
|
|
34
34
|
|
|
35
35
|
Filesystem recovery timing (0.28.0+): cold resume output identifies `guest_filesystem` in JSON and the restart notice. See the [workspace lifecycle](https://docs.shardflux.dev/concepts/lifecycle) for retained files and process restart semantics.
|
|
36
36
|
|
|
37
|
+
## Agent credentials (0.28.2+)
|
|
38
|
+
|
|
39
|
+
Answer a credential question with `shard needs-you answer acred_ID --credential --ask --scope project`. The value goes to the write-only answer field and the asking agent receives an availability message.
|
|
40
|
+
|
|
41
|
+
`shard secrets create NAME --scope agents --org ORG --ask` adds a credential for all your agents; use `--scope project --project PROJECT` or `--scope conversation --conversation CONVERSATION` for a local override. Import, list, get, update, rotate, versions, access-log and delete accept the same scope. Values use the secure page, stdin, or `--from-file` and never command arguments.
|
|
42
|
+
|
|
37
43
|
## Support and bug reports
|
|
38
44
|
|
|
39
45
|
[Report a bug](https://github.com/shardfluxdev/community/issues/new?template=bug_report.yml) or
|
|
@@ -95,7 +101,7 @@ and restarts after a CLI update. It keeps the live twin, `~/Shardflux` files and
|
|
|
95
101
|
Use `shard device requests`, `shard device approve <id>` or `shard device deny <id>` for local approvals, and
|
|
96
102
|
`shard device approvals auto|read|ask` to choose whether your agents ask first (0.28.0+);
|
|
97
103
|
desktop notifications point to pending requests. On systems without a systemd user session, the helper runs
|
|
98
|
-
while your `shard` commands are open. `shard agents open` opens a terminal when launched outside one, using
|
|
104
|
+
while your `shard` commands are open; their input stays with the foreground command. `shard agents open` opens a terminal when launched outside one, using
|
|
99
105
|
`$TERMINAL`, `x-terminal-emulator` or `gnome-terminal`, or prints the attach command. On macOS the same app lives in
|
|
100
106
|
the menu bar.
|
|
101
107
|
|
|
@@ -257,6 +263,9 @@ Finished cloud work lands in your folder by itself: a conversation's commits com
|
|
|
257
263
|
board task's result into your linked folder and the project's default branch. `shard settings --auto-land off`
|
|
258
264
|
(0.28.0+) keeps it in the cloud on this computer (`shard task land` and `shard conversations land` still land on
|
|
259
265
|
request); `--auto-land on` turns it back on. The Shardflux app's Settings → Projects changes the same switch.
|
|
266
|
+
The device helper brings finished task results back while your computer is reachable, including work requested from
|
|
267
|
+
your phone; it catches up after your computer reconnects. Your local edits stay in place, and a conflicting merge
|
|
268
|
+
waits for a decision instead of overwriting them.
|
|
260
269
|
|
|
261
270
|
Terminal conversations open in the terminal you choose (0.22.0+):
|
|
262
271
|
|
|
@@ -1523,8 +1532,10 @@ your agent uses the matching machine or project save/rollback commands in its so
|
|
|
1523
1532
|
`shard machine open` (0.17.1+): it creates or resumes the machine VM without attaching and prints its key for
|
|
1524
1533
|
`shard ws exec` and `shard files write`. `shard machine setup --from-this-computer` (0.20.0+) does it for you: your own
|
|
1525
1534
|
Claude Code (`claude -p`, else Codex) recreates this computer's CLIs and tools, language runtimes, shell config and
|
|
1526
|
-
aliases, git identity and dotfiles on the machine, on your subscription, then saves it.
|
|
1527
|
-
`~/.ssh`, keychains, `.env`, token and credential files
|
|
1535
|
+
aliases, git identity and dotfiles on the machine, on your subscription, then saves it. That setup agent reads
|
|
1536
|
+
tool and configuration metadata and excludes `~/.ssh`, keychains, `.env`, token and credential files. Optional
|
|
1537
|
+
`--with-credentials` (0.28.3+) runs the trusted local credential transfer afterwards; onboarding includes that transfer.
|
|
1538
|
+
The apps and the first run start machine setup once,
|
|
1528
1539
|
right after you connect your agents. `shard machine setup --status` shows the machine as your account has it, in the
|
|
1529
1540
|
same line the apps show: ready, saving (with its place in the queue), or the one command to run next (0.22.0+); `shard
|
|
1530
1541
|
setup --inspect --json` carries it as `onboarding.machine`. `connections_ready` (and the legacy `ready` field) means
|
|
@@ -1635,6 +1646,8 @@ hour), and `--json` prints the API's answer. Pass an id to `shard agents new --m
|
|
|
1635
1646
|
|
|
1636
1647
|
## Conversations
|
|
1637
1648
|
|
|
1649
|
+
When an orchestrator starts work through a device project link (0.28.2+), the new conversation carries its requesting thread through to the API. `shard agents list --json` retains the orchestrator conversation and known input IDs.
|
|
1650
|
+
|
|
1638
1651
|
From 0.14.0, `shard conversations list|get|send|wait|land|move` manages project conversations. `shard c` is the short form. Conversations retain their branch and transcript as their VM binding changes.
|
|
1639
1652
|
|
|
1640
1653
|
Watch an agent work: `shard conversations watch <id>` shows the conversation's terminal live, read-only. It starts
|
|
@@ -1907,7 +1920,7 @@ shard board --follow # each change as it happens,
|
|
|
1907
1920
|
changes the words (`--text`, and the task's master hears it), the title (`--title`), the bucket (`--now`, `--next`,
|
|
1908
1921
|
`--later`) and the order (`--first`, `--after`), keeps an agent's suggestion as your own (`--keep`) or clears the task
|
|
1909
1922
|
(`--clear`; `--no-clear` brings it back). `shard task steer <id> <words...>` sends steering words to the task's master,
|
|
1910
|
-
`shard task rm <id>` deletes the task, and `shard task clear-done` clears every finished one. `shard task show <id>`
|
|
1923
|
+
`shard task rm <id>` deletes the task, every nested follow-up, and their owned conversation trees and computers; masters shared by other tasks stay. `shard task clear-done` clears every finished one. `shard task show <id>`
|
|
1911
1924
|
prints the task, its plan and each report: milestone, outcome and evidence (`tests 12/12 (pnpm test)`).
|
|
1912
1925
|
`shard task land <id>` lands a finished task: its result (the branch `shard/<task id>`, where its master merged its
|
|
1913
1926
|
workers' branches) goes into the project's default branch and into your folder on this computer the way finished
|
|
@@ -2086,3 +2099,35 @@ shard volumes detach --machine datasets
|
|
|
2086
2099
|
Defaults mount in new and running conversations. Removal waits for current work to finish and preserves manual attachments. Add `--json` to read propagation results.
|
|
2087
2100
|
|
|
2088
2101
|
Bare `shard` in a coding-agent shell or without a TTY returns its setup guide using locally saved sign-in and API-key state.
|
|
2102
|
+
|
|
2103
|
+
### Question explanations (0.28.2+)
|
|
2104
|
+
|
|
2105
|
+
Needs-you output prints each question and its optional `option_details` in full, including what each choice does and what follows it. The first suggested answer is the agent’s recommendation. Secret values go through the project’s secure setup, never a question or its ordinary answer.
|
|
2106
|
+
|
|
2107
|
+
Conversation creation (0.28.2+) includes the optional launching conversation only when `/v1/meta` advertises `capabilities.conversation_origin: true`. Older APIs receive the existing create body; no rejected create is replayed with different fields.
|
|
2108
|
+
|
|
2109
|
+
|
|
2110
|
+
## Input attachments (0.28.3+)
|
|
2111
|
+
|
|
2112
|
+
`shard orchestrator say --file <path>` attaches files; repeat the flag, or use `--file - --file-name <name>` to stream stdin. Files-only inputs are supported.
|
|
2113
|
+
|
|
2114
|
+
```sh
|
|
2115
|
+
shard orchestrator say "Review these" --file spec.pdf --file photo.png
|
|
2116
|
+
cat archive.tar | shard orchestrator say --file - --file-name archive.tar
|
|
2117
|
+
```
|
|
2118
|
+
File uploads stream to durable API storage, then the input carries their pinned references. Each receiving thread gets the files before its turn, under `~/inputs/`.
|
|
2119
|
+
|
|
2120
|
+
### Device request outcomes (0.28.3+)
|
|
2121
|
+
|
|
2122
|
+
```sh
|
|
2123
|
+
shard orchestrator show in_abcdefghijklmnopqrstuvwxyz
|
|
2124
|
+
shard task show task_abcdefghijklmnopqrstuvwxyz
|
|
2125
|
+
```
|
|
2126
|
+
|
|
2127
|
+
Each request shows what ran, its paths and outcome, and its recorded error. Failures appear first. Add `--json` for the full detail object. Reading starts no computer.
|
|
2128
|
+
|
|
2129
|
+
Scoped vault imports (0.28.3+) use `shard secrets import NAME --from hsec --scope project --project PROJECT` (or `--scope agents --org ORG` while signed in). Re-importing an identical value leaves its version unchanged; a changed value rotates it. The vault sends the value directly to the encrypted write-only API. Failures show a safe status and request id without upstream prose or details that could echo the value.
|
|
2130
|
+
|
|
2131
|
+
## Credential setup (0.28.3+)
|
|
2132
|
+
|
|
2133
|
+
Run `shard credentials setup --from-this-computer --if-needed` after machine setup to bring your existing credentials along. Machine credentials are available to all your agents; project `.env` credentials stay local and override machine values by name. Values transfer directly from the local source into the write-only secret store and private credential files. The command prints names and delivery progress. Use `--plan` for an optional review, then `--apply PLAN_ID`. If a requested credential is missing everywhere, your agent asks once through the secure credential field.
|
package/dist/args.d.ts
CHANGED
package/dist/args.js
CHANGED
|
@@ -15,7 +15,7 @@ import { BOARD_COMMANDS } from "./board-args.js";
|
|
|
15
15
|
import { ORCHESTRATOR_COMMANDS } from "./orchestrator-args.js";
|
|
16
16
|
import { UsageError } from "./errors.js";
|
|
17
17
|
import { DEFAULT_CHOICE, MODEL_CHOICES } from "./agent-models.js";
|
|
18
|
-
export const CLI_VERSION = '0.28.
|
|
18
|
+
export const CLI_VERSION = '0.28.3';
|
|
19
19
|
export const GLOBAL_OPTIONS = {
|
|
20
20
|
'api-url': { type: 'string', value: '<url>', description: 'API base URL (default $SHARDFLUX_API_URL, else https://api.shardflux.dev).' },
|
|
21
21
|
json: { type: 'boolean', description: 'Print machine-readable JSON on stdout (errors as JSON on stderr).' },
|
|
@@ -127,6 +127,9 @@ const LIFETIME_FILTER = {
|
|
|
127
127
|
};
|
|
128
128
|
const SECRET_REF = { name: 'id|name', required: true };
|
|
129
129
|
const SECRET_SCOPE = {
|
|
130
|
+
scope: { type: 'string', value: '<agents|project|conversation>', description: 'Agent credential scope: all your agents, this project, or one thread. Omit for workspace-bound secrets.' },
|
|
131
|
+
project: { type: 'string', value: '<project-id>', description: 'Project for a local agent credential (default: the key’s project).' },
|
|
132
|
+
conversation: { type: 'string', value: '<conversation-id>', description: 'Thread for --scope conversation.' },
|
|
130
133
|
org: { type: 'string', value: '<organization-id>', description: 'Organization-wide secrets of this organization (owners/admins; project API keys get 403). Default: the API key’s project.' },
|
|
131
134
|
};
|
|
132
135
|
const SECRET_VALUE = {
|
|
@@ -241,6 +244,7 @@ const KEY_COMMANDS = [
|
|
|
241
244
|
{ path: ['machine', 'setup'], summary: 'Let your own Claude Code or Codex set up your machine from this computer (0.20.0+)', positionals: [], options: {
|
|
242
245
|
'from-this-computer': { type: 'boolean', description: 'Recreate this computer’s development setup on your machine: its CLIs and tools, language runtimes, shell config and aliases, git identity and dotfiles. Required.' },
|
|
243
246
|
'if-needed': { type: 'boolean', description: 'Only when your machine was never set up from this computer (what the apps run after onboarding).' },
|
|
247
|
+
'with-credentials': { type: 'boolean', description: 'After the machine is saved, bring existing credentials along with the trusted local credential setup.' },
|
|
244
248
|
status: { type: 'boolean', description: 'Show the last setup from this computer instead of running one.' },
|
|
245
249
|
agent: { type: 'string', value: '<claude|codex|cloud>', description: 'Which agent sets it up (default: this computer’s Claude Code, else its Codex, else your cloud Claude in the machine VM).' },
|
|
246
250
|
timeout: { type: 'string', value: '<duration>', description: 'Time box for the agent (default 10m).' },
|
|
@@ -979,6 +983,16 @@ const KEY_COMMANDS = [
|
|
|
979
983
|
examples: ['shard executions get acme/agent-7 build-42', 'shard ws executions get acme/agent-7 ex-6f1c2a9e-0d7b-4c1e-9a53-2b8e1f0c4d7a --wait 10m --json'],
|
|
980
984
|
},
|
|
981
985
|
{ path: ['secrets', 'log'], summary: 'Show credential request and fill audit events', positionals: [], options: {} },
|
|
986
|
+
{ path: ['credentials', 'setup'], summary: 'Bring existing credentials from this computer to your machine and project', positionals: [], options: {
|
|
987
|
+
'from-this-computer': { type: 'boolean', description: 'Discover source names locally and transfer values directly; no value goes to a model.' },
|
|
988
|
+
'if-needed': { type: 'boolean', description: 'Reconcile existing sources; report needed:false when their stored versions are unchanged.' },
|
|
989
|
+
plan: { type: 'boolean', description: 'Review the names and scopes once before transferring selected sources.' },
|
|
990
|
+
apply: { type: 'string', value: '<plan-id>', description: 'Continue a reviewed setup using its original source descriptors.' },
|
|
991
|
+
deny: { type: 'string', value: '<plan-id>', description: 'Decline a reviewed setup.' },
|
|
992
|
+
picks: { type: 'string', value: '<file|->', description: 'JSON source IDs and scopes for --apply; - reads stdin. No values.' },
|
|
993
|
+
project: { type: 'string', value: '<project-id>', description: 'Project for local credentials (default current context).' },
|
|
994
|
+
path: { type: 'string', value: '<folder>', description: 'Project folder whose .env files to discover (default current folder).' },
|
|
995
|
+
} },
|
|
982
996
|
{ path: ['credentials', 'list'], summary: 'Show pending login requests', positionals: [], options: {} },
|
|
983
997
|
{ path: ['credentials', 'enable'], summary: 'Enable credential fill in a workspace', positionals: [REF], options: {} },
|
|
984
998
|
{ path: ['credentials', 'deny'], summary: 'Deny a login request', positionals: [{ name: 'request-id', required: true }], options: {} },
|
|
@@ -993,6 +1007,7 @@ const KEY_COMMANDS = [
|
|
|
993
1007
|
summary: 'Import named items from your vault as encrypted project secrets',
|
|
994
1008
|
positionals: [{ name: 'name', required: true, variadic: true }],
|
|
995
1009
|
options: {
|
|
1010
|
+
...SECRET_SCOPE,
|
|
996
1011
|
from: { type: 'string', value: '<hsec|1password|bitwarden|keychain>', description: 'Vault to read; values go straight into the encrypted secrets request.' },
|
|
997
1012
|
as: { type: 'string', value: '<secret-name>', description: 'Secret name for a single imported item.' },
|
|
998
1013
|
keychain: { type: 'string', value: '<path>', description: 'Read from this Keychain (keychain source only).' },
|
package/dist/board-args.js
CHANGED
|
@@ -84,7 +84,8 @@ export const BOARD_COMMANDS = [
|
|
|
84
84
|
path: ['task', 'land'],
|
|
85
85
|
summary: 'Land a finished task: its result into your folder and the project’s default branch (0.25.0+)',
|
|
86
86
|
positionals: [{ name: 'id', required: true }],
|
|
87
|
-
options: { path: { type: 'string', value: '<folder>', description: 'The folder to land in (default: the project’s folder on this computer, if any).' }
|
|
87
|
+
options: { path: { type: 'string', value: '<folder>', description: 'The folder to land in (default: the project’s folder on this computer, if any).' },
|
|
88
|
+
automatic: { type: 'boolean', hidden: true, description: 'Device automatic delivery: require Auto land, a linked folder and the master’s tested result.' } },
|
|
88
89
|
description: [
|
|
89
90
|
'A task has one result: the branch shard/<task id>, where its master merged its workers’ branches. Landing merges it',
|
|
90
91
|
'into the project’s default branch on the project hub (a fast-forward, or a merge commit when the merge is clean),',
|
|
@@ -123,6 +124,12 @@ export const BOARD_COMMANDS = [
|
|
|
123
124
|
summary: 'Answer something that waits on you (0.25.0+)',
|
|
124
125
|
positionals: [{ name: 'id', required: true }, { name: 'decision' }],
|
|
125
126
|
options: {
|
|
127
|
+
credential: { type: 'boolean', description: 'Provide an environment credential through the secure value input; never through --text.' },
|
|
128
|
+
scope: { type: 'string', value: '<agents|project|conversation>', description: 'Credential storage scope (default: what the request suggests).' },
|
|
129
|
+
org: { type: 'string', value: '<organization-id>', description: 'The organization for a signed-in credential answer.' },
|
|
130
|
+
ask: { type: 'boolean', description: 'Open the secure page for the credential value.' },
|
|
131
|
+
'from-file': { type: 'string', value: '<file>', description: 'Read the credential value from this file; never from an argument.' },
|
|
132
|
+
timeout: { type: 'string', value: '<duration>', description: 'How long the secure page waits (default 10m).' },
|
|
126
133
|
everywhere: { type: 'boolean', description: 'With always: the rule covers every project.' },
|
|
127
134
|
text: { type: 'string', value: '<answer>', description: 'Your answer to an agent’s question.' },
|
|
128
135
|
pick: { type: 'string', multiple: true, value: '<option|question=option>', description: 'The option you pick for an agent’s question in a permission request (repeatable for a multi-select question; "<question>=<option>" when it asks several).' },
|
|
@@ -136,6 +143,7 @@ export const BOARD_COMMANDS = [
|
|
|
136
143
|
' ask_… --text <answer>: the task’s master gets it',
|
|
137
144
|
' oq_… --text <answer>: the orchestrator thread that asked gets it (0.27.0+)',
|
|
138
145
|
' cred_… deny (the value goes through the credential request, sealed for its computer)',
|
|
146
|
+
' acred_… --credential --ask: provide an environment value securely, or deny to decline it',
|
|
139
147
|
' land_… land (as shard task land: the result into your folder and the default branch), keep (the',
|
|
140
148
|
' branch stays) or discard',
|
|
141
149
|
' prune_… --keep <task-id> for each task to keep, keep for all of them, or discard to clear them',
|
package/dist/board-commands.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export declare function feedJson(u: BoardUpdate): Record<string, unknown>;
|
|
|
44
44
|
/** `shard task show`: the task, its plan and conversations, then every report oldest first with its evidence. Every
|
|
45
45
|
* title, step and outcome prints whole (0.27.0+), however long the agent wrote it; the terminal wraps it. */
|
|
46
46
|
export declare function taskDetailText(d: TaskDetail): string;
|
|
47
|
-
/** An item's kind from its id (perm_, cq_, ask_, oq_, cred_, land_, prune_, dev_); undefined for another id. */
|
|
47
|
+
/** An item's kind from its id (perm_, cq_, ask_, oq_, cred_, acred_, land_, prune_, dev_); undefined for another id. */
|
|
48
48
|
export declare function itemKind(id: string): NeedsYouItem['kind'] | undefined;
|
|
49
49
|
export interface AnswerInput {
|
|
50
50
|
word?: string | undefined;
|
package/dist/board-commands.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deviceOutcomeLines } from "./device-outcomes.js";
|
|
2
|
+
import { findOrg, resolveOrg } from "./account-scope.js";
|
|
3
|
+
import { out, principal, str, strs, readSecretValue, askSecretValue } from "./commands.js";
|
|
2
4
|
import { detailLine, pickAnswers } from "./conversation-commands.js";
|
|
3
5
|
import { NotFoundError, UsageError } from "./errors.js";
|
|
4
6
|
import { time } from "./format.js";
|
|
@@ -36,8 +38,8 @@ export function taskLine(t) {
|
|
|
36
38
|
* request your orchestrator made to your computer while it was offline reads "Waiting for your computer: list ~/code".
|
|
37
39
|
*/
|
|
38
40
|
export function needsLine(n) {
|
|
39
|
-
const detail = n.permission ? detailLine(n.permission.detail) : n.options?.length ? n.options.join(' / ') : n.land ? n.land.branch : n.device ? 'cancel drops it' : '';
|
|
40
|
-
return `${n.id}\t${oneLine(n.ask, 100)}${detail ? `\t${detail}` : ''}`;
|
|
41
|
+
const detail = n.credential?.kind === 'environment' ? [n.credential.name, n.credential.suggested_scope].filter(Boolean).join(' · ') : n.permission ? detailLine(n.permission.detail) : n.options?.length ? n.options.map(o => n.option_details?.[o] ? `${o}: ${n.option_details[o]}` : o).join(' / ') : n.land ? n.land.branch : n.device ? 'cancel drops it' : '';
|
|
42
|
+
return `${n.id}\t${oneLine(n.ask, n.kind === 'question' || n.credential?.kind === 'environment' ? Infinity : 100)}${detail ? `\t${detail}` : ''}`;
|
|
41
43
|
}
|
|
42
44
|
/** A sealed report as --follow prints it: id, milestone, title, then its evidence labels. */
|
|
43
45
|
export function reportLine(r) {
|
|
@@ -140,7 +142,7 @@ export function taskDetailText(d) {
|
|
|
140
142
|
if (r.outcome.trim())
|
|
141
143
|
lines.push(` ${whole(r.outcome)}`);
|
|
142
144
|
if (r.options?.length)
|
|
143
|
-
lines.push(` Options: ${r.options.join(' / ')}`);
|
|
145
|
+
lines.push(` Options: ${r.options.map(o => r.option_details?.[o] ? `${o}: ${r.option_details[o]}` : o).join(' / ')}`);
|
|
144
146
|
if (r.evidence.length)
|
|
145
147
|
lines.push(` Evidence: ${r.evidence.map(evidenceText).join('; ')}`);
|
|
146
148
|
if (r.media.length)
|
|
@@ -149,6 +151,8 @@ export function taskDetailText(d) {
|
|
|
149
151
|
lines.push(` Landed: ${r.landed.how} ${r.landed.ref}`);
|
|
150
152
|
}
|
|
151
153
|
}
|
|
154
|
+
if (d.device_requests !== undefined)
|
|
155
|
+
lines.push(...deviceOutcomeLines(d.device_requests));
|
|
152
156
|
return `${lines.join('\n')}\n`;
|
|
153
157
|
}
|
|
154
158
|
/** --now, --next or --later (one at most); undefined when none was given. */
|
|
@@ -166,8 +170,8 @@ function orderOf(values) {
|
|
|
166
170
|
return first ? null : after;
|
|
167
171
|
}
|
|
168
172
|
// ---- needs you -------------------------------------------------------------------------------
|
|
169
|
-
const KINDS = { perm: 'permission', cq: 'question', ask: 'question', oq: 'question', cred: 'credential', land: 'land', prune: 'prune', dev: 'device' };
|
|
170
|
-
/** An item's kind from its id (perm_, cq_, ask_, oq_, cred_, land_, prune_, dev_); undefined for another id. */
|
|
173
|
+
const KINDS = { perm: 'permission', cq: 'question', ask: 'question', oq: 'question', cred: 'credential', acred: 'credential', land: 'land', prune: 'prune', dev: 'device' };
|
|
174
|
+
/** An item's kind from its id (perm_, cq_, ask_, oq_, cred_, acred_, land_, prune_, dev_); undefined for another id. */
|
|
171
175
|
export function itemKind(id) {
|
|
172
176
|
return KINDS[id.slice(0, Math.max(0, id.indexOf('_')))];
|
|
173
177
|
}
|
|
@@ -349,7 +353,7 @@ export const BOARD_HANDLERS = {
|
|
|
349
353
|
return 0;
|
|
350
354
|
},
|
|
351
355
|
async 'task land'(ctx, values, [id]) {
|
|
352
|
-
const o = await landTask(landDeps(ctx), id, { path: str(values.path) });
|
|
356
|
+
const o = await landTask(landDeps(ctx), id, { path: str(values.path), automatic: values.automatic === true });
|
|
353
357
|
out(ctx, landedText(o), o);
|
|
354
358
|
return 0;
|
|
355
359
|
},
|
|
@@ -364,6 +368,45 @@ export const BOARD_HANDLERS = {
|
|
|
364
368
|
return 0;
|
|
365
369
|
},
|
|
366
370
|
async 'needs-you answer'(ctx, values, [id, word]) {
|
|
371
|
+
if (values.credential === true) {
|
|
372
|
+
if (!id?.startsWith('acred_') || word !== undefined || values.text !== undefined || values.pick !== undefined || values.keep !== undefined || values.everywhere === true) {
|
|
373
|
+
throw new UsageError('Use --credential for an environment request, without a decision or chat answer.');
|
|
374
|
+
}
|
|
375
|
+
const hasKey = Boolean(ctx.io.env.SHARDFLUX_API_KEY?.trim()) || ctx.store.profile(ctx.apiUrl).api_key !== undefined;
|
|
376
|
+
const scope = str(values.scope);
|
|
377
|
+
if (scope !== undefined && !['agents', 'project', 'conversation'].includes(scope))
|
|
378
|
+
throw new UsageError('--scope must be agents, project or conversation');
|
|
379
|
+
const signed = scope === 'agents' || !hasKey;
|
|
380
|
+
let cloud = signed ? ctx.account() : ctx.cloud();
|
|
381
|
+
let organizationId = str(values.org);
|
|
382
|
+
if (signed) {
|
|
383
|
+
const org = organizationId ? await findOrg(ctx.account(), organizationId) : await resolveOrg(ctx, ctx.account(), {});
|
|
384
|
+
organizationId = org.id;
|
|
385
|
+
}
|
|
386
|
+
// Inspect the request before reading a file, stdin or opening the secure page.
|
|
387
|
+
const item = (await cloud.needsYou.list({ ...(organizationId ? { organizationId } : {}), signal: ctx.signal })).find(n => n.id === id);
|
|
388
|
+
if (!item || item.kind !== 'credential' || item.credential?.kind !== 'environment' || !item.credential.name)
|
|
389
|
+
throw new NotFoundError('Nothing waits for that environment credential.');
|
|
390
|
+
const target = scope ?? item.credential.suggested_scope ?? 'project';
|
|
391
|
+
if (target === 'agents' && !signed) {
|
|
392
|
+
organizationId ??= (await principal(ctx)).organization_id;
|
|
393
|
+
cloud = ctx.account();
|
|
394
|
+
await findOrg(ctx.account(), organizationId);
|
|
395
|
+
}
|
|
396
|
+
const save = (value) => {
|
|
397
|
+
ctx.addWriteOnlyValue(value);
|
|
398
|
+
return cloud.needsYou.answer(id, { credential: { value, scope: target } }, { ...(organizationId ? { organizationId } : {}), signal: ctx.signal });
|
|
399
|
+
};
|
|
400
|
+
const value = await readSecretValue(ctx, values, item.credential.name);
|
|
401
|
+
const r = value !== null ? await save(value) : await askSecretValue(ctx, values, {
|
|
402
|
+
title: `Provide ${item.credential.name}`, purpose: item.credential.purpose ?? item.ask, name: item.credential.name,
|
|
403
|
+
}, save);
|
|
404
|
+
out(ctx, `Credential ${item.credential.name} is available (${target}).\n`, r);
|
|
405
|
+
return 0;
|
|
406
|
+
}
|
|
407
|
+
if (values.scope !== undefined || values.ask === true || values['from-file'] !== undefined || values.org !== undefined || values.timeout !== undefined) {
|
|
408
|
+
throw new UsageError('Use --credential for a secure environment credential answer.');
|
|
409
|
+
}
|
|
367
410
|
const input = { word, text: str(values.text), picks: strs(values.pick), keep: strs(values.keep), everywhere: values.everywhere === true };
|
|
368
411
|
const cloud = boardClient(ctx);
|
|
369
412
|
// Land merges here (shard task land) and records itself after; the board only takes keep and discard.
|
package/dist/commands.d.ts
CHANGED
|
@@ -61,6 +61,8 @@ export interface Ctx {
|
|
|
61
61
|
};
|
|
62
62
|
/** A credential this command received (redacted from error output). */
|
|
63
63
|
addSecret(value: string): void;
|
|
64
|
+
/** A credential value this command writes: redacted like addSecret, and failures report only status-based codes. */
|
|
65
|
+
addWriteOnlyValue(value: string): void;
|
|
64
66
|
/** The update status for `shard version` (forced: ask the API now). Null when unknown. */
|
|
65
67
|
versionStatus(force: boolean): Promise<UpdateView | null>;
|
|
66
68
|
/** The credentials file (config directory from the environment). */
|
|
@@ -152,4 +154,16 @@ export declare function withConversations(rows: import('@shardflux/sdk').Agent[]
|
|
|
152
154
|
question?: string | null;
|
|
153
155
|
}[] | null): ThreadRow[];
|
|
154
156
|
export declare const KEY_HANDLERS: Record<string, Handler>;
|
|
157
|
+
/**
|
|
158
|
+
* The secret value (never from arguments): --from-file, piped standard input, or a no-echo prompt on a terminal. Null
|
|
159
|
+
* when the person types it on the page shard opens instead (0.20.1+): --ask, or a coding agent's shell with nothing
|
|
160
|
+
* piped (a terminal there is not the person's).
|
|
161
|
+
*/
|
|
162
|
+
export declare function readSecretValue(ctx: Ctx, values: Values, name: string): Promise<string | null>;
|
|
163
|
+
/** The secret's value from the page shard opens, handed straight to `save` (create or rotate); the page shows its outcome. */
|
|
164
|
+
export declare function askSecretValue<T>(ctx: Ctx, values: Values, page: {
|
|
165
|
+
title: string;
|
|
166
|
+
purpose: string;
|
|
167
|
+
name: string;
|
|
168
|
+
}, save: (value: string) => Promise<T>): Promise<T>;
|
|
155
169
|
export {};
|
package/dist/commands.js
CHANGED
|
@@ -2345,17 +2345,20 @@ export const KEY_HANDLERS = {
|
|
|
2345
2345
|
if (keychain && from !== 'keychain')
|
|
2346
2346
|
throw new UsageError('--keychain applies to the keychain vault');
|
|
2347
2347
|
const targets = secretNames(names.map(n => alias ?? n), '--as');
|
|
2348
|
-
const
|
|
2348
|
+
const agent = values.scope !== undefined ? await agentSecretContext(ctx, values) : null;
|
|
2349
|
+
const project = agent ? null : (await principal(ctx)).project_id;
|
|
2349
2350
|
const imported = [];
|
|
2350
2351
|
for (const [i, name] of names.entries()) {
|
|
2351
|
-
const value = await vaultValue(from, name, { signal: ctx.signal, ...(keychain ? { keychain } : {}) });
|
|
2352
|
-
ctx.
|
|
2353
|
-
imported.push(await ctx.cloud().secrets.create(project, { name: targets[i], value }));
|
|
2352
|
+
const value = await vaultValue(from, name, { signal: ctx.signal, env: ctx.io.env, ...(keychain ? { keychain } : {}) });
|
|
2353
|
+
ctx.addWriteOnlyValue(value);
|
|
2354
|
+
imported.push(agent ? await agent.cloud.secrets.importAgent(agent.target, { name: targets[i], value }) : await ctx.cloud().secrets.create(project, { name: targets[i], value }));
|
|
2354
2355
|
}
|
|
2355
2356
|
out(ctx, `Imported ${imported.map(s => s.name).join(', ')}. ${SECRET_SECURITY_NOTE}\n`, { data: imported, security_note: SECRET_SECURITY_NOTE });
|
|
2356
2357
|
return 0;
|
|
2357
2358
|
},
|
|
2358
2359
|
async 'secrets create'(ctx, values, [name]) {
|
|
2360
|
+
if (values.scope !== undefined)
|
|
2361
|
+
return agentSecretCommand(ctx, values, 'create', name);
|
|
2359
2362
|
if (!SECRET_NAME.test(name))
|
|
2360
2363
|
throw new UsageError(`<name> must be an environment variable name like OPENAI_API_KEY (^[A-Z_][A-Z0-9_]*$), got "${name}"`);
|
|
2361
2364
|
const scope = scopeFlag(values);
|
|
@@ -2377,6 +2380,8 @@ export const KEY_HANDLERS = {
|
|
|
2377
2380
|
return 0;
|
|
2378
2381
|
},
|
|
2379
2382
|
async 'secrets list'(ctx, values) {
|
|
2383
|
+
if (values.scope !== undefined)
|
|
2384
|
+
return agentSecretCommand(ctx, values, 'list', undefined);
|
|
2380
2385
|
const cloud = ctx.cloud();
|
|
2381
2386
|
const org = scopeFlag(values);
|
|
2382
2387
|
const project = org === null ? (await principal(ctx)).project_id : null;
|
|
@@ -2404,11 +2409,15 @@ export const KEY_HANDLERS = {
|
|
|
2404
2409
|
return 0;
|
|
2405
2410
|
},
|
|
2406
2411
|
async 'secrets get'(ctx, values, [ref]) {
|
|
2412
|
+
if (values.scope !== undefined)
|
|
2413
|
+
return agentSecretCommand(ctx, values, 'get', ref);
|
|
2407
2414
|
const s = await resolveSecret(ctx, values, ref);
|
|
2408
2415
|
out(ctx, secretDetail(s), s);
|
|
2409
2416
|
return 0;
|
|
2410
2417
|
},
|
|
2411
2418
|
async 'secrets update'(ctx, values, [ref]) {
|
|
2419
|
+
if (values.scope !== undefined)
|
|
2420
|
+
return agentSecretCommand(ctx, values, 'update', ref);
|
|
2412
2421
|
const s = await resolveSecret(ctx, values, ref);
|
|
2413
2422
|
const params = { ...permissionParams(values, s.scope, 'update') };
|
|
2414
2423
|
const description = str(values.description);
|
|
@@ -2421,6 +2430,8 @@ export const KEY_HANDLERS = {
|
|
|
2421
2430
|
return 0;
|
|
2422
2431
|
},
|
|
2423
2432
|
async 'secrets rotate'(ctx, values, [ref]) {
|
|
2433
|
+
if (values.scope !== undefined)
|
|
2434
|
+
return agentSecretCommand(ctx, values, 'rotate', ref);
|
|
2424
2435
|
const value = await readSecretValue(ctx, values, ref);
|
|
2425
2436
|
const s = await resolveSecret(ctx, values, ref);
|
|
2426
2437
|
const rotate = (secret) => ctx.cloud().secrets.rotate(s.id, secret);
|
|
@@ -2433,6 +2444,8 @@ export const KEY_HANDLERS = {
|
|
|
2433
2444
|
return 0;
|
|
2434
2445
|
},
|
|
2435
2446
|
async 'secrets versions'(ctx, values, [ref]) {
|
|
2447
|
+
if (values.scope !== undefined)
|
|
2448
|
+
return agentSecretCommand(ctx, values, 'versions', ref);
|
|
2436
2449
|
const s = await resolveSecret(ctx, values, ref);
|
|
2437
2450
|
const limitText = str(values.limit);
|
|
2438
2451
|
const page = await ctx.cloud().secrets.versions(s.id, limitText ? { limit: parsePositiveInt(limitText, 'limit', 200) } : {});
|
|
@@ -2440,6 +2453,8 @@ export const KEY_HANDLERS = {
|
|
|
2440
2453
|
return 0;
|
|
2441
2454
|
},
|
|
2442
2455
|
async 'secrets delete'(ctx, values, [ref]) {
|
|
2456
|
+
if (values.scope !== undefined)
|
|
2457
|
+
return agentSecretCommand(ctx, values, 'delete', ref);
|
|
2443
2458
|
if (values.yes !== true)
|
|
2444
2459
|
throw new UsageError('refusing to delete without --yes (the values are erased and the name is removed from every workspace binding)');
|
|
2445
2460
|
const s = await resolveSecret(ctx, values, ref);
|
|
@@ -2448,6 +2463,8 @@ export const KEY_HANDLERS = {
|
|
|
2448
2463
|
return 0;
|
|
2449
2464
|
},
|
|
2450
2465
|
async 'secrets access-log'(ctx, values, [ref]) {
|
|
2466
|
+
if (values.scope !== undefined)
|
|
2467
|
+
return agentSecretCommand(ctx, values, 'access-log', ref);
|
|
2451
2468
|
const outcome = str(values.outcome);
|
|
2452
2469
|
if (outcome !== undefined && !['granted', 'denied', 'failed'].includes(outcome))
|
|
2453
2470
|
throw new UsageError('--outcome must be granted, denied or failed');
|
|
@@ -2549,6 +2566,106 @@ function updateLine(u, d) {
|
|
|
2549
2566
|
}
|
|
2550
2567
|
}
|
|
2551
2568
|
/** `--org <id>`: organization scope; null: the API key's project. */
|
|
2569
|
+
async function agentSecretContext(ctx, values) {
|
|
2570
|
+
const scope = str(values.scope);
|
|
2571
|
+
if (!['agents', 'project', 'conversation'].includes(scope ?? ''))
|
|
2572
|
+
throw new UsageError('--scope must be agents, project or conversation');
|
|
2573
|
+
const restrictions = ['tool', 'allow-workspace', 'any-workspace', 'allow-project', 'all-projects', 'clear-projects'];
|
|
2574
|
+
if (restrictions.some(k => Array.isArray(values[k]) ? values[k].length > 0 : values[k] !== undefined && values[k] !== false))
|
|
2575
|
+
throw new UsageError('Workspace tool permissions apply to workspace-bound secrets. Agent credentials use their agents, project or conversation scope.');
|
|
2576
|
+
if (scope === 'agents' && (values.project !== undefined || values.conversation !== undefined))
|
|
2577
|
+
throw new UsageError('--scope agents takes --org; use a local scope for --project or --conversation.');
|
|
2578
|
+
if (scope === 'project' && values.conversation !== undefined)
|
|
2579
|
+
throw new UsageError('Use --scope conversation with --conversation.');
|
|
2580
|
+
const hasKey = Boolean(ctx.io.env.SHARDFLUX_API_KEY?.trim()) || ctx.store.profile(ctx.apiUrl).api_key !== undefined;
|
|
2581
|
+
const cloud = scope === 'agents' || !hasKey ? ctx.account() : ctx.cloud();
|
|
2582
|
+
if (scope === 'agents') {
|
|
2583
|
+
const account = ctx.account(), org = str(values.org);
|
|
2584
|
+
const organization = org ? await findOrg(account, org) : await resolveOrg(ctx, account, {});
|
|
2585
|
+
return { cloud, target: { scope, organizationId: organization.id } };
|
|
2586
|
+
}
|
|
2587
|
+
const localOrg = str(values.org);
|
|
2588
|
+
if (localOrg !== undefined && !UUID.test(localOrg))
|
|
2589
|
+
throw new UsageError('--org must be an organization id for a local credential.');
|
|
2590
|
+
const organization = localOrg ? { organizationId: localOrg } : {};
|
|
2591
|
+
if (scope === 'conversation') {
|
|
2592
|
+
const conversationId = str(values.conversation);
|
|
2593
|
+
if (!conversationId)
|
|
2594
|
+
throw new UsageError('--conversation is required with --scope conversation');
|
|
2595
|
+
return { cloud, target: { scope, conversationId, ...organization, ...(str(values.project) ? { projectId: str(values.project) } : {}) } };
|
|
2596
|
+
}
|
|
2597
|
+
const projectId = str(values.project) ?? (hasKey ? (await principal(ctx)).project_id : undefined);
|
|
2598
|
+
if (!projectId)
|
|
2599
|
+
throw new UsageError('--project is required with a signed-in session and --scope project');
|
|
2600
|
+
return { cloud, target: { scope: 'project', projectId, ...organization } };
|
|
2601
|
+
}
|
|
2602
|
+
async function agentSecretCommand(ctx, values, action, ref) {
|
|
2603
|
+
const { cloud, target } = await agentSecretContext(ctx, values);
|
|
2604
|
+
if (action === 'list' && (values.limit !== undefined || values.cursor !== undefined || values['include-deleted'] === true))
|
|
2605
|
+
throw new UsageError('Agent credential lists return all active names. Pagination and deleted-history flags apply to workspace-bound secrets.');
|
|
2606
|
+
const describe = (r) => `${r.name} (${r.id}) · ${r.scope} · version ${r.current_version}\n`;
|
|
2607
|
+
if (action === 'list') {
|
|
2608
|
+
const data = await cloud.secrets.listAgents(target);
|
|
2609
|
+
out(ctx, data.map(describe).join('') || 'No credentials.\n', { data });
|
|
2610
|
+
return 0;
|
|
2611
|
+
}
|
|
2612
|
+
if (action === 'create') {
|
|
2613
|
+
if (!SECRET_NAME.test(ref ?? ''))
|
|
2614
|
+
throw new UsageError('Use an environment variable name for the credential.');
|
|
2615
|
+
const value = await readSecretValue(ctx, values, ref), save = (value) => { ctx.addWriteOnlyValue(value); return cloud.secrets.createAgent(target, { name: ref, value, ...(str(values.description) !== undefined ? { description: str(values.description) } : {}) }); };
|
|
2616
|
+
const r = value !== null ? await save(value) : await askSecretValue(ctx, values, { title: `Add ${ref}`, purpose: `Saved encrypted for your agents as ${ref}.`, name: ref }, save);
|
|
2617
|
+
out(ctx, describe(r), r);
|
|
2618
|
+
return 0;
|
|
2619
|
+
}
|
|
2620
|
+
const r = UUID.test(ref ?? '') ? await cloud.secrets.getAgent(ref) : (await cloud.secrets.listAgents(target)).find(r => r.name === ref);
|
|
2621
|
+
if (!r)
|
|
2622
|
+
throw new NotFoundError(`No credential named ${ref} in this scope.`);
|
|
2623
|
+
// An id is still checked against the explicitly named target rather than silently operating in another scope.
|
|
2624
|
+
if (r.scope !== target.scope || target.organizationId && r.organization_id !== target.organizationId || target.projectId && r.project_id !== target.projectId || target.conversationId && r.conversation_id !== target.conversationId)
|
|
2625
|
+
throw new NotFoundError('That credential belongs to another scope.');
|
|
2626
|
+
if (action === 'get') {
|
|
2627
|
+
out(ctx, describe(r), r);
|
|
2628
|
+
return 0;
|
|
2629
|
+
}
|
|
2630
|
+
if (action === 'update') {
|
|
2631
|
+
const description = str(values.description);
|
|
2632
|
+
if (description === undefined)
|
|
2633
|
+
throw new UsageError('Pass --description to update an agent credential.');
|
|
2634
|
+
const changed = await cloud.secrets.updateAgent(r.id, description);
|
|
2635
|
+
out(ctx, describe(changed), changed);
|
|
2636
|
+
return 0;
|
|
2637
|
+
}
|
|
2638
|
+
if (action === 'rotate') {
|
|
2639
|
+
const value = await readSecretValue(ctx, values, r.name), save = (value) => { ctx.addWriteOnlyValue(value); return cloud.secrets.rotateAgent(r.id, value); };
|
|
2640
|
+
const changed = value !== null ? await save(value) : await askSecretValue(ctx, values, { title: `New value for ${r.name}`, purpose: `Replaces the encrypted credential ${r.name}.`, name: r.name }, save);
|
|
2641
|
+
out(ctx, describe(changed), changed);
|
|
2642
|
+
return 0;
|
|
2643
|
+
}
|
|
2644
|
+
if (action === 'delete') {
|
|
2645
|
+
if (values.yes !== true)
|
|
2646
|
+
throw new UsageError('Pass --yes to erase this credential.');
|
|
2647
|
+
await cloud.secrets.deleteAgent(r.id);
|
|
2648
|
+
out(ctx, `Deleted ${r.name}.\n`, { deleted: true, id: r.id, name: r.name, scope: r.scope });
|
|
2649
|
+
return 0;
|
|
2650
|
+
}
|
|
2651
|
+
if (action === 'versions') {
|
|
2652
|
+
const limit = str(values.limit);
|
|
2653
|
+
const page = await cloud.secrets.versions(r.id, limit ? { limit: parsePositiveInt(limit, 'limit', 200) } : {});
|
|
2654
|
+
out(ctx, secretVersionTable(page.data), { data: page.data, next_cursor: page.nextCursor });
|
|
2655
|
+
return 0;
|
|
2656
|
+
}
|
|
2657
|
+
if (action === 'access-log') {
|
|
2658
|
+
const outcome = str(values.outcome), workspace = str(values.workspace), limit = str(values.limit);
|
|
2659
|
+
if (outcome !== undefined && !['granted', 'denied', 'failed'].includes(outcome))
|
|
2660
|
+
throw new UsageError('--outcome must be granted, denied or failed');
|
|
2661
|
+
if (workspace !== undefined && !UUID.test(workspace))
|
|
2662
|
+
throw new UsageError('--workspace must be a workspace id');
|
|
2663
|
+
const page = await cloud.secrets.accessEvents(r.id, { ...(outcome ? { outcome: outcome } : {}), ...(workspace ? { workspaceId: workspace } : {}), ...(limit ? { limit: parsePositiveInt(limit, 'limit', 200) } : {}) });
|
|
2664
|
+
out(ctx, JSON.stringify(page.data) + '\n', { data: page.data, next_cursor: page.nextCursor });
|
|
2665
|
+
return 0;
|
|
2666
|
+
}
|
|
2667
|
+
throw new UsageError('Unknown credential action.');
|
|
2668
|
+
}
|
|
2552
2669
|
function scopeFlag(values) {
|
|
2553
2670
|
const org = str(values.org);
|
|
2554
2671
|
if (org === undefined)
|
|
@@ -2599,7 +2716,7 @@ function permissionParams(values, scope, mode) {
|
|
|
2599
2716
|
* when the person types it on the page shard opens instead (0.20.1+): --ask, or a coding agent's shell with nothing
|
|
2600
2717
|
* piped (a terminal there is not the person's).
|
|
2601
2718
|
*/
|
|
2602
|
-
async function readSecretValue(ctx, values, name) {
|
|
2719
|
+
export async function readSecretValue(ctx, values, name) {
|
|
2603
2720
|
const file = str(values['from-file']);
|
|
2604
2721
|
if (values.ask === true) {
|
|
2605
2722
|
if (file !== undefined)
|
|
@@ -2643,7 +2760,7 @@ function secretText(value) {
|
|
|
2643
2760
|
return text;
|
|
2644
2761
|
}
|
|
2645
2762
|
/** The secret's value from the page shard opens, handed straight to `save` (create or rotate); the page shows its outcome. */
|
|
2646
|
-
async function askSecretValue(ctx, values, page, save) {
|
|
2763
|
+
export async function askSecretValue(ctx, values, page, save) {
|
|
2647
2764
|
const asked = await askInBrowser(ctx, {
|
|
2648
2765
|
kind: 'secret',
|
|
2649
2766
|
title: page.title,
|
package/dist/conversations.d.ts
CHANGED
package/dist/conversations.js
CHANGED
|
@@ -250,7 +250,7 @@ export async function createConversationRecord(ctx, folder, harness, route, name
|
|
|
250
250
|
capture.catch(() => undefined);
|
|
251
251
|
// The title marker this computer chose (`shard settings --title-prefix`, 0.20.1+); none sent means the platform's.
|
|
252
252
|
const titlePrefix = storedTitlePrefix(ctx.store.dir);
|
|
253
|
-
const response = await conversationClient(ctx).conversations.create({ repoRemote: project.remote ?? `file://${project.folder}`, harness, route, mode: 'new',
|
|
253
|
+
const response = await conversationClient(ctx).conversations.create({ fromConversationId: ctx.io.env.SF_CONVERSATION_ID, repoRemote: project.remote ?? `file://${project.folder}`, harness, route, mode: 'new',
|
|
254
254
|
base: { commit, ...(branch ? { branch } : {}) }, ...(name ? { name } : {}), ...choice, projectId: ctx.store.profile(ctx.apiUrl).project_id,
|
|
255
255
|
...(titlePrefix !== undefined ? { titlePrefix } : {}),
|
|
256
256
|
...(movedIn ? { harnessSessionId: movedIn.session_id, ...(movedIn.title ? { title: movedIn.title.slice(0, 200) } : {}) } : {}) }, { idempotencyKey, signal: ctx.signal });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Ctx } from './commands.js';
|
|
2
|
+
import { conversationClient } from './conversation-commands.js';
|
|
3
|
+
type Input = Parameters<ReturnType<typeof conversationClient>['deviceRequests']['heartbeat']>[0];
|
|
4
|
+
export declare function credentialHeartbeat(ctx: Ctx, input: Input): Promise<{
|
|
5
|
+
device_id: string;
|
|
6
|
+
online_until: string;
|
|
7
|
+
}>;
|
|
8
|
+
export {};
|