fruxon 0.13.0__tar.gz → 0.13.2__tar.gz
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.
- {fruxon-0.13.0 → fruxon-0.13.2}/HISTORY.md +188 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/PKG-INFO +9 -7
- {fruxon-0.13.0 → fruxon-0.13.2}/README.md +8 -6
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/__init__.py +4 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/_version.py +2 -2
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/__init__.py +61 -1
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/_crash.py +37 -4
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/_schema.py +5 -2
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_draft.py +28 -3
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_revisions.py +12 -1
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/examples.py +48 -1
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/guides.py +8 -1
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/tools.py +121 -3
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/triggers.py +117 -2
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/triggers_ledger.py +604 -22
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/fruxon.py +222 -15
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/models.py +177 -5
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/skills/fruxon-agent-mode/SKILL.md +1 -1
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/skills/fruxon-build-agent/SKILL.md +2 -2
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/skills/fruxon-debug-trace/SKILL.md +49 -9
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/skills/fruxon-meet/SKILL.md +5 -2
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/skills/fruxon-use-integrations/SKILL.md +22 -1
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_cli.py +1 -1
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_cli_crash.py +57 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_describe.py +14 -0
- fruxon-0.13.2/tests/test_kb_trial_cli.py +263 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_triggers_ledger.py +972 -5
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_triggers_write.py +121 -2
- {fruxon-0.13.0 → fruxon-0.13.2}/.gitignore +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/LICENSE +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/pyproject.toml +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/__main__.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/_ssl.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/_shared.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/_stream.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_approvals.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_budget.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_channels.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_executions.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_lifecycle.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_memory.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_sandbox.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_sandbox_test.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_slots.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_tests.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/agents_topics.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/applications.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/applications_entry_points.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/assets.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/auth.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/capabilities.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/completion.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/config.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/consult_pins.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/describe.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/doctor.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/environments.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/escalations.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/integrations.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/keys.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/llm_providers.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/messages.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/metrics.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/participants.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/pipelines.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/secrets.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/skills.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/storage.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/triggers_questions.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/triggers_shape.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/cli/workspaces.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/credentials.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/device_auth.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/doctor.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/exceptions.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/flow_validation.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/output.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/params.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/sandbox_scenario.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/skills/__init__.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/skills/fruxon-create-integration/SKILL.md +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/telemetry.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/ui.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/update_check.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/src/fruxon/validation.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/__init__.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/conftest.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_actor.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_agent_lifecycle.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_agent_slots.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_agents_check_cli.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_agents_revisions_cli.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_agents_sandbox.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_applications.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_approvals.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_assets.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_budgets.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_capabilities.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_channels.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_cli_base_url_routing.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_client.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_connect_nudge.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_consult.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_credentials.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_device_auth.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_doctor.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_draft_bootstrap.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_draft_evaluate_cli.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_draft_pull_shape.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_draft_validate_cli.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_drafts.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_entry_points.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_environments.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_escalations.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_execution_records.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_flow_validation.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_fruxon.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_guides.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_integration_triggers_cli.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_integrations_authorize_cli.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_llm_provider_configs_cli.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_memory.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_messages.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_metrics.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_online_secret_refs.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_output.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_params.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_participants.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_participants_write.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_pipelines.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_sandbox_scenario.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_schema.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_secrets.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_skills.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_ssl.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_storage.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_telemetry.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_test_chats.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_topics.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_triggers.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_triggers_questions.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_triggers_shape.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_ui.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_update_check.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_validation.py +0 -0
- {fruxon-0.13.0 → fruxon-0.13.2}/tests/test_workspaces_cli.py +0 -0
|
@@ -1,5 +1,193 @@
|
|
|
1
1
|
# History
|
|
2
2
|
|
|
3
|
+
## Unreleased — What a listing leaves out, and what a typo returns
|
|
4
|
+
|
|
5
|
+
A patch release from a CLI trial run against a live workspace. Four defects,
|
|
6
|
+
all of them the same shape: the CLI gave a confident answer that was missing
|
|
7
|
+
something the server had already sent, or that no driver could parse.
|
|
8
|
+
|
|
9
|
+
- **`triggers ledger items` carries each item's `label`.** The server sends
|
|
10
|
+
it, the Work grid shows it, and the CLI dropped it — so a listing named every
|
|
11
|
+
row by the provider's opaque key and nothing else. In `-o text` it sits next
|
|
12
|
+
to the key, and only when the door supplies one.
|
|
13
|
+
- **`triggers doors` carries `labelPaths` and `labelFieldName`.** Where a door
|
|
14
|
+
reads an item's title from, printed as `labels work from`. These are the
|
|
15
|
+
paths a `door.projection` is forbidden to drop, so an author who could not
|
|
16
|
+
see them was authoring blind against a save-time refusal.
|
|
17
|
+
- **A ledger listing is one page of 20, not the whole backlog.** The healthy
|
|
18
|
+
state of a busy ledger is thousands of `READY` rows served ten per request,
|
|
19
|
+
and walking them all was hundreds of round trips that the server rate-limits
|
|
20
|
+
long before the end — so an unqualified `triggers ledger items` returned an
|
|
21
|
+
error or nothing at all against a real automation. A full page now reports
|
|
22
|
+
what it is a page of; `-n` sizes it and `--all` still walks the lot. The SDK's
|
|
23
|
+
`list_ledger_items()` is unchanged: a library caller asking for items still
|
|
24
|
+
gets them all.
|
|
25
|
+
- **`tools list` recovers the internal platform tools.** `knowledge_base` and
|
|
26
|
+
`assets` answered `[]` about tools an agent can attach and run, because the
|
|
27
|
+
tenant list endpoint filters them and takes no parameter that says otherwise.
|
|
28
|
+
The single-tool endpoint has no such filter, so the listing fetches them by
|
|
29
|
+
ID — each one confirmed against the server, so a stale ID disappears instead
|
|
30
|
+
of sending you to a `tools get` that 404s. `--no-include-internal` returns the
|
|
31
|
+
raw server answer.
|
|
32
|
+
- **Every parse error is the same envelope, at every depth.** A bad flag on the
|
|
33
|
+
root command (`fruxon --bogus`) was the one case that still printed Click's
|
|
34
|
+
ASCII box to a driver that had been promised `{"error": {"code": "usage"}}`
|
|
35
|
+
and exit 2 for every other one. A person at the terminal still gets Click's
|
|
36
|
+
`Usage:` block.
|
|
37
|
+
- **SDK:** `LedgerItem` gains `label`; `WorkShapeDoor` gains `label_paths` and
|
|
38
|
+
`label_field_name`.
|
|
39
|
+
|
|
40
|
+
## Unreleased — A ledger automation can say what it cost
|
|
41
|
+
|
|
42
|
+
The ledger could account for *where* work sat but not for what it came to, so
|
|
43
|
+
a schedule's spend was only reachable one execution record at a time. The
|
|
44
|
+
backend now carries cost on the accounting reads and adds a per-fire view
|
|
45
|
+
(fruxon-backend#741).
|
|
46
|
+
|
|
47
|
+
- **`triggers ledger funnel` and `triggers ledger items` carry `cost` and
|
|
48
|
+
`runs`.** The funnel therefore answers which *stage* the money goes to, not
|
|
49
|
+
only how much the ledger has spent; its trailing line totals both and shows
|
|
50
|
+
cost per item over the items that have actually run. An item's `runs` counts
|
|
51
|
+
every stage it passed through and so is deliberately not `stageAttempts`,
|
|
52
|
+
which resets per stage. `cost` is not sortable — the endpoint's `--order-by`
|
|
53
|
+
allow-list does not carry it, and an unlisted field is a 400.
|
|
54
|
+
- **A cost column distinguishes "never ran" from "ran for free".** `runs` of 0
|
|
55
|
+
prints as `—`, not `$0.0000`: the second is a real and different state (an
|
|
56
|
+
exclusion that fired before any model call), and carrying `runs` alongside
|
|
57
|
+
`cost` is what keeps the two apart.
|
|
58
|
+
- **New `fruxon triggers ledger passes <trigger>`** — one row per fire, newest
|
|
59
|
+
first, with `admitted` / `dispatched` / `completed` / `runs` / `cost` and the
|
|
60
|
+
fire log's own detail. `runs` sits below `dispatched` while runs are in
|
|
61
|
+
flight or when a dispatch was parked on a human, and a pass's `cost` rises
|
|
62
|
+
after `firedAt` as those runs finish, so an early read is a floor. `-n` /
|
|
63
|
+
`--limit` bounds the walk.
|
|
64
|
+
- **SDK:** `FruxonClient.list_ledger_passes()` and the `LedgerPass` model;
|
|
65
|
+
`LedgerItem` and `LedgerFunnelCell` gain defaulted `cost` / `runs`.
|
|
66
|
+
|
|
67
|
+
## Unreleased — Keeping only the fields an item needs
|
|
68
|
+
|
|
69
|
+
- **`workShape.door.projection` (a list of dotted record paths)** names the
|
|
70
|
+
fields an admitted item keeps out of each source record. Without one an item
|
|
71
|
+
carries the record whole — stored per item, shown in the Work grid, and
|
|
72
|
+
handed to every stage on every pass. **Empty keeps everything**, which is
|
|
73
|
+
what every shape saved before this does, so no existing automation changes
|
|
74
|
+
shape. The item key, the door's label paths, the cursor and a write-back
|
|
75
|
+
target are kept whatever you name, and a projection that drops the id or a
|
|
76
|
+
label is refused at save time rather than quietly repaired.
|
|
77
|
+
- **`triggers test-source` prints the projected record.** Each `record` is what
|
|
78
|
+
an item would actually carry, and an `item keeps` line names the paths that
|
|
79
|
+
survived (`projection` in `-o json`). Without it a field the projection drops
|
|
80
|
+
and a field the source never sent are the same silence, and they call for
|
|
81
|
+
opposite fixes.
|
|
82
|
+
- **`triggers doors` carries `defaultProjection`.** The door's own suggestion
|
|
83
|
+
for what to keep, printed as `suggests keeping` — what to seed a new shape's
|
|
84
|
+
`door.projection` with. The sheet and table doors suggest nothing, because a
|
|
85
|
+
row's columns are yours and the row is the work.
|
|
86
|
+
|
|
87
|
+
## Unreleased — Counting an exclusion before you undo one
|
|
88
|
+
|
|
89
|
+
`redrive-many --ignore-reason` could reconsider what one revised instruction set
|
|
90
|
+
aside, but the count it confirms had to be assembled here: the CLI listed the
|
|
91
|
+
ledger and filtered it in memory, which is 40 round trips and 4,000 items on a
|
|
92
|
+
real automation, and a second opinion about a set that moves.
|
|
93
|
+
|
|
94
|
+
- **`triggers ledger items --ignore-reason "…"`** narrows to the items one
|
|
95
|
+
exclusion reason set aside, server-side — the same filter `redrive-many`
|
|
96
|
+
selects by, matched whole and case-sensitively, and only ever against
|
|
97
|
+
`IGNORED` items. It is how you read the batch a revised instruction is about
|
|
98
|
+
before putting it back.
|
|
99
|
+
- **The selection verbs confirm the server's count** whenever the endpoint's
|
|
100
|
+
filters express the selection exactly: a `--status` or an `--ignore-reason`,
|
|
101
|
+
with no `--id` and no `--before`/`--after`. Everything else still resolves
|
|
102
|
+
here — an admission window has no query parameter, ids have to be intersected
|
|
103
|
+
and the invented ones named, and "DEAD or FAILED" is not a filter the endpoint
|
|
104
|
+
can be handed.
|
|
105
|
+
- An API that reports no total sends the CLI back to the listing rather than
|
|
106
|
+
confirming a zero, which is the one count a selection verb accepts while
|
|
107
|
+
moving nothing.
|
|
108
|
+
|
|
109
|
+
## Unreleased — Seeing what a source filter drops before you save it
|
|
110
|
+
|
|
111
|
+
A work shape can now say which listed records it does not want:
|
|
112
|
+
`workShape.door.skipWhen` is a `{match, conditions[]}` filter in the same
|
|
113
|
+
vocabulary as a trigger's event filter, and a record it matches is never
|
|
114
|
+
admitted — no item, no stage, no model call, nothing in the IGNORED pile.
|
|
115
|
+
Before this, the only place such a rule could live was a first stage's
|
|
116
|
+
`ignoreReasons`, which spends a model call per record to decide what a dotted
|
|
117
|
+
path already knows.
|
|
118
|
+
|
|
119
|
+
It has to be on the `door` rather than on `source`: saving re-expands `source`
|
|
120
|
+
from the door, so a filter written onto `source` is discarded by the next save.
|
|
121
|
+
|
|
122
|
+
- **`triggers test-source` marks each dropped record `FILTERED`** and counts
|
|
123
|
+
them under the listing, which is where to see what a rule costs before it
|
|
124
|
+
goes onto a live automation. A filter that reached the server and matched
|
|
125
|
+
nothing says so, because otherwise it is indistinguishable from one that
|
|
126
|
+
never arrived.
|
|
127
|
+
- **`--count` reports `filtered` beside the total**, and `already held` is now
|
|
128
|
+
counted after the filter, so `total − filtered − already held` is the work
|
|
129
|
+
that would actually arrive. The CLI prints that subtraction rather than
|
|
130
|
+
leaving four numbers on one line for you to do it. On a source with no filter
|
|
131
|
+
every number is exactly what it was.
|
|
132
|
+
- Records already admitted stay admitted: the rule governs admission, never
|
|
133
|
+
retraction.
|
|
134
|
+
|
|
135
|
+
## Unreleased — Trying an automation on five instead of three thousand
|
|
136
|
+
|
|
137
|
+
A work-shape automation had two settings: off, or draining everything. `triggers
|
|
138
|
+
fire` ran a whole pass — admission to exhaustion, every stage claiming its full
|
|
139
|
+
limit — so trying the Restigo TexterChat automation on a handful meant firing it
|
|
140
|
+
at 3,401 resolved chats, and draining a stuck merge queue by hand pulled twenty
|
|
141
|
+
more chats in behind the pile on every fire.
|
|
142
|
+
|
|
143
|
+
- **`triggers fire` takes three ceilings for that one pass.** `--max-admit N`
|
|
144
|
+
stops admission after that many genuinely-new items (`0` leaves the source
|
|
145
|
+
untouched); `--max-dispatch N` caps how many items the whole pass claims,
|
|
146
|
+
across stages rather than per stage; `--stage <id>`, repeatable, claims only at
|
|
147
|
+
the stages named. They ride the request: the schedule keeps running the
|
|
148
|
+
automation exactly as configured, and the next fire is unbounded again unless
|
|
149
|
+
it asks for bounds of its own. A stage the work shape does not declare is
|
|
150
|
+
refused by the backend with a message naming the stages it does have.
|
|
151
|
+
- **A bounded pass that stops mid-listing leaves the cursor where it was**, so
|
|
152
|
+
nothing is skipped — the next pass re-lists the same window and admission
|
|
153
|
+
de-duplicates it. That is also why the bound counts items admitted rather than
|
|
154
|
+
records listed: a second bounded fire reads past what it already knows and
|
|
155
|
+
takes the next N.
|
|
156
|
+
- **The fire result says the pass was bounded.** `fire_trigger(...)` returns the
|
|
157
|
+
bounds under `bounds`, and the text output prints `admitted N, dispatched N —
|
|
158
|
+
bounded pass (…)`. Without it a pass sized by hand and an automation with
|
|
159
|
+
nothing to do print the same two counts. `--max-admit -1` is refused locally
|
|
160
|
+
rather than spending a round trip, because `0` means something specific here.
|
|
161
|
+
|
|
162
|
+
## Unreleased — Patch: KB-trial CLI crash fixes
|
|
163
|
+
|
|
164
|
+
The 2026-09-11 knowledge-base trial exposed a common failure underneath six
|
|
165
|
+
apparently unrelated commands: Typer 0.26 vendors Click, so its parse exceptions
|
|
166
|
+
are not subclasses of the top-level `click.ClickException` the new crash handler
|
|
167
|
+
excluded. Unknown options, missing arguments, and no-argument help were therefore
|
|
168
|
+
reported as Fruxon bugs.
|
|
169
|
+
|
|
170
|
+
- **Agent-mode usage failures are typed again.** `NoSuchOption`,
|
|
171
|
+
`MissingParameter`, and `NoArgsIsHelpError` from either external or
|
|
172
|
+
Typer-vendored Click now emit the standard `{"error": {"code": "usage",
|
|
173
|
+
"exit_code": 2, ...}}` envelope and never write a crash report.
|
|
174
|
+
- **The trial command forms now match the surrounding CLI.** `agents revisions
|
|
175
|
+
get` accepts `-o/--output json`; `tools get <integration> --tool/-t <id>`
|
|
176
|
+
matches `tools run --tool` (the old positional tool ID remains accepted for
|
|
177
|
+
compatibility); and `agents draft discard` now exposes `--yes/-y`, confirms
|
|
178
|
+
for humans, and requires the flag under an agent driver.
|
|
179
|
+
- **Schema read timeouts are network errors, not crashes.** The live OpenAPI
|
|
180
|
+
fetch used by `--schema` catches a socket `TimeoutError`, exits 15, and points
|
|
181
|
+
at `--base-url` and the expected Swagger endpoint.
|
|
182
|
+
- **Hidden internal tools are discoverable.** The backend's tenant list API
|
|
183
|
+
intentionally omits the internal `knowledge_base` and `assets` tools. An empty
|
|
184
|
+
`tools list` now explains that behavior, names the built-in IDs shipped with
|
|
185
|
+
this release, and shows how to inspect each tool's parameters with `tools get
|
|
186
|
+
... --tool ... -o json`.
|
|
187
|
+
- **`fruxon guides` prints its local help directly**, including in agent mode;
|
|
188
|
+
`integrations configs list` without its integration argument is a typed usage
|
|
189
|
+
error naming `INTEGRATION`.
|
|
190
|
+
|
|
3
191
|
## Unreleased — A channel connected through an entry point is a way in
|
|
4
192
|
|
|
5
193
|
`agents check` looked for an inbound path in two places — a bound trigger and a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: fruxon
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.2
|
|
4
4
|
Summary: The Fruxon SDK is a lightweight Python client for integrating with the Fruxon platform.
|
|
5
5
|
Project-URL: bugs, https://github.com/fruxon-ai/fruxon-sdk/issues
|
|
6
6
|
Project-URL: changelog, https://github.com/fruxon-ai/fruxon-sdk/blob/main/HISTORY.md
|
|
@@ -134,18 +134,20 @@ Integrations + tools:
|
|
|
134
134
|
| `fruxon integrations authorize <integration>` | Mint an application-level OAuth authorization URL to **connect** an integration — auto-detects its OAuth2 method (`--auth-method` to pick, `--scope`/`--config-param` to tune). OAuth needs a browser consent step, so the CLI hands you the link; a human clicks it, and the connection is saved as a tenant config a slot can pin. |
|
|
135
135
|
| `fruxon integrations triggers <integration>` | List the event types an integration can fire an agent on — each descriptor's `id` is the `eventType` a trigger listens for, plus the `payloadFields` (dotted paths) a binding's `parameterMappings` can read. Discovery for wiring an inbound-event trigger without guessing. |
|
|
136
136
|
| `fruxon integrations mcp …` | Inspect and enable the MCP server for an integration. |
|
|
137
|
-
| `fruxon tools list/get/create/update/delete/run` | Manage the tools inside an integration. Integration-scoped. `run`
|
|
137
|
+
| `fruxon tools list/get/create/update/delete/run` | Manage the tools inside an integration. Integration-scoped. `get` and `run` select the tool with `--tool`; `run` executes it outside any agent — real credentials, real result — with `-p key=value`, and `-c` naming the config that supplies credentials. The tenant list API hides internal `knowledge_base` / `assets` tools; `tools list` recovers them by ID and lists them like any other (`--no-include-internal` for the raw server answer). |
|
|
138
138
|
| `fruxon keys list/mint/revoke/delete/history/scopes` | Audit and revoke scoped tokens. Minting opens the dashboard so the secret never enters the CLI process. |
|
|
139
139
|
| `fruxon llm-providers list/get/models` | Browse LLM providers and models supported at the tenant level. |
|
|
140
140
|
| `fruxon assets create/list/get/wait/operations/delete` | Manage knowledge-base (RAG) assets a step can query — upload local files, wait for async ingestion, inspect operations, and use the ids for `assetConfig.assetIds`. `delete` confirms + refuses without `--yes` in agent mode. |
|
|
141
|
-
| `fruxon assets search/documents/chunks` | Reproduce and inspect a retrieval. `search` runs the same query an agent's `search_assets` does (`--mode HYBRID/VECTOR/FULL_TEXT`, `--top-k`, fusion weights) — note `score` is on a different scale per mode, and in hybrid the top hit is always 1.0. `documents` lists what the index contains (a `chunkCount` of 0 means that file yielded no indexable text); `chunks` reads one document's chunks in order. |
|
|
141
|
+
| `fruxon assets search/documents/chunks` | Reproduce and inspect a retrieval. `search` runs the same query an agent's `search_assets` does (`--mode HYBRID/VECTOR/FULL_TEXT`, `--top-k`, fusion weights) — note `score` is on a different scale per mode, and in hybrid the top hit is always 1.0. `documents` lists what the index contains (a `chunkCount` of 0 means that file yielded no indexable text); `chunks` reads one document's chunks in order. The `documentId` these print is the **index's** id, which is what `chunks` takes — on a knowledge base's backing asset it is not the editable document's id, and it changes whenever a document's body is re-indexed. The `fileName` (`<knowledge document id>.md`) is the document a `knowledge_base` tool would edit. |
|
|
142
142
|
| `fruxon metrics list` | Browse the evaluation-metric catalog — the ids a step's LLM-judge config (`judge.metrics[]`) binds, with a default weight each. |
|
|
143
143
|
| `fruxon triggers list/get/create/update/delete/fire/bind/unbind` | Manage triggers — the schedule/event sources that fire agents. `create`/`update` take `--file` (+`--schema`); `create` also takes `--application`, the Application that will own the trigger and must own everything it fires. `bind`/`unbind` wire which agents fire; `fire` runs it now. `fire`/`delete`/`unbind` confirm + refuse without `--yes` in agent mode. The control plane for autonomy. `get` returns the full stored `workShape` and every `scheduleTimes` slot, so `get -o json | jq .workShape` produces a body `update --file` can post straight back — a PATCH replaces the whole shape, so edit what you read. |
|
|
144
|
-
| `fruxon triggers doors` | The pre-configured source doors a work shape can be built on — the **first** question of authoring an automation, and the one `--schema` cannot answer: it tells you a `workShape.door` carries a `doorId`, not which ids exist. Each row is the integration a chosen `integrationConfigId` must belong to, plus the one query field that is genuinely yours (label + example), and `supportsStartFrom` — whether that door's source accepts a lower bound, and so whether a `door.startFrom` horizon does anything there. Read-only and static: a door is a claim that one combination of list tool, identity path and watermark actually works, made in platform code by someone who verified it. An integration with no door is still fully usable — configure `workShape.source` by hand. |
|
|
145
|
-
| `fruxon triggers preview-shape/test-source` | Dry-run a work shape before saving or activating it; both write nothing. `preview-shape` renders, per stage, the resolved instruction, the whole `user_query` the agent is handed (item payload included), and the JSON schema the stage's declared `produces` keys become — against a real backlog item when you name a trigger. `test-source` runs the door's list tool once and says, per record, whether admission would take it, plus the exact arguments the tool was called with (for Gmail, your query with the day-granular `after:` folded in). `--count` pages the whole door instead and answers how much is waiting: the total, how many the ledger already holds, how many clear the watermark, and which server ceiling stopped the scan. `--file` tests a shape being edited — and a draft whose `door.startFrom` (Unix ms) sets a first-run horizon gets a `first run sends` line with the `after:` term that horizon produces, which is the only place to see it: `startFrom` seeds the ledger cursor once, when the ledger is created. |
|
|
146
|
-
| `fruxon triggers ledger funnel/items/cursor/transitions/effects/batches` | The work items a schedule trigger's work shape admitted. `funnel` counts per stage and status (every item is in exactly one cell). `items` is the grid — `--stage`, `--status`, `--item-key` (client-side; the endpoint has no such filter),
|
|
144
|
+
| `fruxon triggers doors` | The pre-configured source doors a work shape can be built on — the **first** question of authoring an automation, and the one `--schema` cannot answer: it tells you a `workShape.door` carries a `doorId`, not which ids exist. Each row is the integration a chosen `integrationConfigId` must belong to, plus the one query field that is genuinely yours (label + example), and `supportsStartFrom` — whether that door's source accepts a lower bound, and so whether a `door.startFrom` horizon does anything there. `door.skipWhen` is the other field that is yours rather than the integration's: a `{match, conditions[]}` filter, same shape as a trigger's event filter, whose matching records are **never admitted** — no item, no stage, no model call. It has to live on the `door` because saving re-expands `source` from it, so the same filter written onto `source` is discarded by the next save. `door.projection` is the third: a list of dotted record paths naming the fields an admitted item **keeps**, with the door's own `defaultProjection` as the suggestion to seed it from — without one an item carries the whole record, into the Work grid and into every stage's prompt on every pass. Read that against the door's `labelPaths` (or, for the sheet and table doors, `labelFieldName` naming the field whose answer picks the label column): those are where an item's title comes from, and a projection that drops one is refused at save time rather than quietly repaired. Read-only and static: a door is a claim that one combination of list tool, identity path and watermark actually works, made in platform code by someone who verified it. An integration with no door is still fully usable — configure `workShape.source` by hand. |
|
|
145
|
+
| `fruxon triggers preview-shape/test-source` | Dry-run a work shape before saving or activating it; both write nothing. `preview-shape` renders, per stage, the resolved instruction, the whole `user_query` the agent is handed (item payload included), and the JSON schema the stage's declared `produces` keys become — against a real backlog item when you name a trigger. `test-source` runs the door's list tool once and says, per record, whether admission would take it, plus the exact arguments the tool was called with (for Gmail, your query with the day-granular `after:` folded in). Records a `door.skipWhen` filter would drop read `FILTERED`, with a footer count — which is where to see what a rule costs before it goes onto a live automation. `--count` pages the whole door instead and answers how much is waiting: the total, how many the filter drops, how many the ledger already holds, how many clear the watermark, and which server ceiling stopped the scan — so `total − filtered − already held` is the work that would actually arrive. Each record prints **as the item will carry it** — narrowed to the shape's `door.projection`, with an `item keeps` line naming the paths that survived, so a field your projection drops and a field the source never sent are not the same silence. `--file` tests a shape being edited — and a draft whose `door.startFrom` (Unix ms) sets a first-run horizon gets a `first run sends` line with the `after:` term that horizon produces, which is the only place to see it: `startFrom` seeds the ledger cursor once, when the ledger is created. |
|
|
146
|
+
| `fruxon triggers ledger funnel/items/cursor/transitions/effects/batches` | The work items a schedule trigger's work shape admitted. `funnel` counts per stage and status (every item is in exactly one cell). `items` is the grid — `--stage`, `--status`, `--ignore-reason`, `--item-key` (client-side; the endpoint has no such filter), each item's `label` (what the Work grid calls the row, as against the provider's opaque `itemKey`), and its accumulated `data`, which is where a stage's declared `produces` keys land. A listing is **one page of 20** that reports how many matched in total — a ledger's healthy state is thousands of rows served ten per request, so an unbounded walk is rate-limited long before the end; `-n` sizes the page and `--all` walks the lot. `--ignore-reason` narrows to the items one exclusion reason set aside — the same filter `redrive-many` selects by, matched whole and case-sensitively, so it is how you read a batch before putting it back. `cursor` is the watermark and its freshness. `transitions` is one item's attempt history; `effects` is what it did to the outside world — an `UNKNOWN` row parks the item until someone settles it. `batches` is what an ASK stage is waiting on a human for. `funnel` and `items` also carry `cost` (all-in USD) and `runs` — so the funnel says *which stage* the money goes to, not merely how much. An item's `runs` spans every stage it passed through and so is not `stageAttempts`, which resets per stage; a `Cost` of `—` means nothing there has ever been claimed, as against `$0.0000`, which means it ran and was free. Cost is not sortable: the endpoint's `--order-by` allow-list does not carry it. |
|
|
147
|
+
| `fruxon triggers ledger passes` | Every time the automation fired, newest first — `admitted` (what the door took), `dispatched` (what the pass claimed and sent to work, questions parked on a human included), `completed` (how many of *this pass's* dispatches reached the terminal stage), `runs`, and the pass's all-in USD. `runs` sits **below** `dispatched` while runs are in flight or when a dispatch went to a person rather than a model — that gap is normal, not a lost item. And a pass's `cost` **rises after** it fired, as those runs finish, so a pass read seconds later reports a floor rather than its bill. An `outcome` other than `FIRED` means no sweep happened: `FILTERED` / `COALESCED` are healthy (nothing to do, or folded into a pass already running), `FAILED` / `FILTER_ERROR` are not. `-n/--limit` bounds the walk. |
|
|
147
148
|
| `fruxon triggers ledger redrive/ignore/settle/cancel-batch` | The operator verbs; all confirm and refuse without `--yes` in agent mode. `redrive` returns a DEAD or FAILED item to the queue with its attempts reset — settle any `UNKNOWN` effect first or the next attempt hits the same refusal. `ignore` excludes an item for a **required** reason, which is the only thing separating IGNORED from DEAD later. `settle` answers an effect the platform could not confirm (`--landed` / `--did-not-land` + `--reason`); wrong in the second direction duplicates the write. `cancel-batch` calls off a question — the items go back to READY and a later pass re-batches them. |
|
|
148
|
-
| `fruxon triggers ledger redrive-many/ignore-many` | The same two verbs over a **selection** — `--id` (repeatable), `--stage`, `--status`, `--before`/`--after` (Unix ms). The CLI resolves the selection to a count under the verb's own eligibility, shows it, and the server acts on exactly that count (409 if the set changed). `ignore-many --stage X` is how you drain a stage. When the selection is `--id` values, the result names every id it did **not** act on and why — not a work item, another automation's, or a status the verb cannot touch (usually a pass holding the claim). |
|
|
149
|
+
| `fruxon triggers ledger redrive-many/ignore-many` | The same two verbs over a **selection** — `--id` (repeatable), `--stage`, `--status`, `--before`/`--after` (Unix ms). The CLI resolves the selection to a count under the verb's own eligibility, shows it, and the server acts on exactly that count (409 if the set changed). The count is the server's own whenever the endpoint's filters say exactly what the selection says (a `--status` or an `--ignore-reason`, with no `--id` and no `--before`/`--after`) — one request instead of walking every page of a 4,000-item ledger, and one fewer place for the two sides to disagree. `ignore-many --stage X` is how you drain a stage. When the selection is `--id` values, the result names every id it did **not** act on and why — not a work item, another automation's, or a status the verb cannot touch (usually a pass holding the claim). |
|
|
150
|
+
| `fruxon triggers ledger redrive-many --ignore-reason "…"` | Reconsider what one instruction set aside. `IGNORED` is terminal for the scheduler, not for you: a re-drive returns an excluded item to `READY` at the stage that set it aside, with a fresh attempt budget. Exclusions are **opt-in** — they come in only with `--ignore-reason`, `--status IGNORED`, or `--id`, so `--stage X` still means the stuck items there. `--status IGNORED` alone is refused with the count it would have moved, because each item costs an agent run. The reason is matched whole and case-sensitively against what the stage recorded; a wording the shape does not declare but an item carries is still accepted. |
|
|
149
151
|
| `fruxon triggers ledger reset-cursor/purge` | `reset-cursor` moves where the next scan starts, as a compare-and-swap on the version it just read (`--value` omitted = the door's own horizon); it never re-runs admitted work. It is the *after* to `workShape.door.startFrom`'s *before* — a horizon set at creation seeds the cursor before anything sweeps, and is ignored once a pass has read the source. `purge` starts the ledger over — cancels open questions, deletes items/effects/transitions, bumps the cursor version — and confirms the exact item count first. Destructive; the effects' external targets are not touched. |
|
|
150
152
|
| `fruxon triggers questions list/answer` | The human half of an ASK stage, tenant-wide. `ledger batches <trigger>` answers *what is this automation waiting on*; this answers *what is waiting on anyone* — soonest deadline first, which is how an undelivered question gets found at all. Read **`delivery`** first: `UNDELIVERABLE` (nobody reachable in the slot) and `FAILED` (a target resolved, the send did not land) both mean nobody was asked and nobody will answer unless you do — `QUEUE_ONLY` is healthy. `--undelivered-only` narrows to that pair; `--trigger` filters client-side (the endpoint has no such filter). `answer` settles one on the assignee's behalf and is `--yes`-gated: the reply interpreter **fails closed**, so an answer it cannot place advances nothing and the question stays open, and each item advances only once the stage's `answeredWhenPath` is filled — answering two of three advances two. |
|
|
151
153
|
| `fruxon triggers set-stages <id> --file` | Replace an automation's stages without rewriting the rest of its work shape (a PATCH replaces the whole shape, so this reads it, splices, and posts). Takes a bare stages array or any document carrying one. Pre-flights the two refusals the server only reports after the write: live items at a stage your list drops, and a `door` that will re-expand `source` on save. `--dry-run` prints the merged shape and the warnings. |
|
|
@@ -101,18 +101,20 @@ Integrations + tools:
|
|
|
101
101
|
| `fruxon integrations authorize <integration>` | Mint an application-level OAuth authorization URL to **connect** an integration — auto-detects its OAuth2 method (`--auth-method` to pick, `--scope`/`--config-param` to tune). OAuth needs a browser consent step, so the CLI hands you the link; a human clicks it, and the connection is saved as a tenant config a slot can pin. |
|
|
102
102
|
| `fruxon integrations triggers <integration>` | List the event types an integration can fire an agent on — each descriptor's `id` is the `eventType` a trigger listens for, plus the `payloadFields` (dotted paths) a binding's `parameterMappings` can read. Discovery for wiring an inbound-event trigger without guessing. |
|
|
103
103
|
| `fruxon integrations mcp …` | Inspect and enable the MCP server for an integration. |
|
|
104
|
-
| `fruxon tools list/get/create/update/delete/run` | Manage the tools inside an integration. Integration-scoped. `run`
|
|
104
|
+
| `fruxon tools list/get/create/update/delete/run` | Manage the tools inside an integration. Integration-scoped. `get` and `run` select the tool with `--tool`; `run` executes it outside any agent — real credentials, real result — with `-p key=value`, and `-c` naming the config that supplies credentials. The tenant list API hides internal `knowledge_base` / `assets` tools; `tools list` recovers them by ID and lists them like any other (`--no-include-internal` for the raw server answer). |
|
|
105
105
|
| `fruxon keys list/mint/revoke/delete/history/scopes` | Audit and revoke scoped tokens. Minting opens the dashboard so the secret never enters the CLI process. |
|
|
106
106
|
| `fruxon llm-providers list/get/models` | Browse LLM providers and models supported at the tenant level. |
|
|
107
107
|
| `fruxon assets create/list/get/wait/operations/delete` | Manage knowledge-base (RAG) assets a step can query — upload local files, wait for async ingestion, inspect operations, and use the ids for `assetConfig.assetIds`. `delete` confirms + refuses without `--yes` in agent mode. |
|
|
108
|
-
| `fruxon assets search/documents/chunks` | Reproduce and inspect a retrieval. `search` runs the same query an agent's `search_assets` does (`--mode HYBRID/VECTOR/FULL_TEXT`, `--top-k`, fusion weights) — note `score` is on a different scale per mode, and in hybrid the top hit is always 1.0. `documents` lists what the index contains (a `chunkCount` of 0 means that file yielded no indexable text); `chunks` reads one document's chunks in order. |
|
|
108
|
+
| `fruxon assets search/documents/chunks` | Reproduce and inspect a retrieval. `search` runs the same query an agent's `search_assets` does (`--mode HYBRID/VECTOR/FULL_TEXT`, `--top-k`, fusion weights) — note `score` is on a different scale per mode, and in hybrid the top hit is always 1.0. `documents` lists what the index contains (a `chunkCount` of 0 means that file yielded no indexable text); `chunks` reads one document's chunks in order. The `documentId` these print is the **index's** id, which is what `chunks` takes — on a knowledge base's backing asset it is not the editable document's id, and it changes whenever a document's body is re-indexed. The `fileName` (`<knowledge document id>.md`) is the document a `knowledge_base` tool would edit. |
|
|
109
109
|
| `fruxon metrics list` | Browse the evaluation-metric catalog — the ids a step's LLM-judge config (`judge.metrics[]`) binds, with a default weight each. |
|
|
110
110
|
| `fruxon triggers list/get/create/update/delete/fire/bind/unbind` | Manage triggers — the schedule/event sources that fire agents. `create`/`update` take `--file` (+`--schema`); `create` also takes `--application`, the Application that will own the trigger and must own everything it fires. `bind`/`unbind` wire which agents fire; `fire` runs it now. `fire`/`delete`/`unbind` confirm + refuse without `--yes` in agent mode. The control plane for autonomy. `get` returns the full stored `workShape` and every `scheduleTimes` slot, so `get -o json | jq .workShape` produces a body `update --file` can post straight back — a PATCH replaces the whole shape, so edit what you read. |
|
|
111
|
-
| `fruxon triggers doors` | The pre-configured source doors a work shape can be built on — the **first** question of authoring an automation, and the one `--schema` cannot answer: it tells you a `workShape.door` carries a `doorId`, not which ids exist. Each row is the integration a chosen `integrationConfigId` must belong to, plus the one query field that is genuinely yours (label + example), and `supportsStartFrom` — whether that door's source accepts a lower bound, and so whether a `door.startFrom` horizon does anything there. Read-only and static: a door is a claim that one combination of list tool, identity path and watermark actually works, made in platform code by someone who verified it. An integration with no door is still fully usable — configure `workShape.source` by hand. |
|
|
112
|
-
| `fruxon triggers preview-shape/test-source` | Dry-run a work shape before saving or activating it; both write nothing. `preview-shape` renders, per stage, the resolved instruction, the whole `user_query` the agent is handed (item payload included), and the JSON schema the stage's declared `produces` keys become — against a real backlog item when you name a trigger. `test-source` runs the door's list tool once and says, per record, whether admission would take it, plus the exact arguments the tool was called with (for Gmail, your query with the day-granular `after:` folded in). `--count` pages the whole door instead and answers how much is waiting: the total, how many the ledger already holds, how many clear the watermark, and which server ceiling stopped the scan. `--file` tests a shape being edited — and a draft whose `door.startFrom` (Unix ms) sets a first-run horizon gets a `first run sends` line with the `after:` term that horizon produces, which is the only place to see it: `startFrom` seeds the ledger cursor once, when the ledger is created. |
|
|
113
|
-
| `fruxon triggers ledger funnel/items/cursor/transitions/effects/batches` | The work items a schedule trigger's work shape admitted. `funnel` counts per stage and status (every item is in exactly one cell). `items` is the grid — `--stage`, `--status`, `--item-key` (client-side; the endpoint has no such filter),
|
|
111
|
+
| `fruxon triggers doors` | The pre-configured source doors a work shape can be built on — the **first** question of authoring an automation, and the one `--schema` cannot answer: it tells you a `workShape.door` carries a `doorId`, not which ids exist. Each row is the integration a chosen `integrationConfigId` must belong to, plus the one query field that is genuinely yours (label + example), and `supportsStartFrom` — whether that door's source accepts a lower bound, and so whether a `door.startFrom` horizon does anything there. `door.skipWhen` is the other field that is yours rather than the integration's: a `{match, conditions[]}` filter, same shape as a trigger's event filter, whose matching records are **never admitted** — no item, no stage, no model call. It has to live on the `door` because saving re-expands `source` from it, so the same filter written onto `source` is discarded by the next save. `door.projection` is the third: a list of dotted record paths naming the fields an admitted item **keeps**, with the door's own `defaultProjection` as the suggestion to seed it from — without one an item carries the whole record, into the Work grid and into every stage's prompt on every pass. Read that against the door's `labelPaths` (or, for the sheet and table doors, `labelFieldName` naming the field whose answer picks the label column): those are where an item's title comes from, and a projection that drops one is refused at save time rather than quietly repaired. Read-only and static: a door is a claim that one combination of list tool, identity path and watermark actually works, made in platform code by someone who verified it. An integration with no door is still fully usable — configure `workShape.source` by hand. |
|
|
112
|
+
| `fruxon triggers preview-shape/test-source` | Dry-run a work shape before saving or activating it; both write nothing. `preview-shape` renders, per stage, the resolved instruction, the whole `user_query` the agent is handed (item payload included), and the JSON schema the stage's declared `produces` keys become — against a real backlog item when you name a trigger. `test-source` runs the door's list tool once and says, per record, whether admission would take it, plus the exact arguments the tool was called with (for Gmail, your query with the day-granular `after:` folded in). Records a `door.skipWhen` filter would drop read `FILTERED`, with a footer count — which is where to see what a rule costs before it goes onto a live automation. `--count` pages the whole door instead and answers how much is waiting: the total, how many the filter drops, how many the ledger already holds, how many clear the watermark, and which server ceiling stopped the scan — so `total − filtered − already held` is the work that would actually arrive. Each record prints **as the item will carry it** — narrowed to the shape's `door.projection`, with an `item keeps` line naming the paths that survived, so a field your projection drops and a field the source never sent are not the same silence. `--file` tests a shape being edited — and a draft whose `door.startFrom` (Unix ms) sets a first-run horizon gets a `first run sends` line with the `after:` term that horizon produces, which is the only place to see it: `startFrom` seeds the ledger cursor once, when the ledger is created. |
|
|
113
|
+
| `fruxon triggers ledger funnel/items/cursor/transitions/effects/batches` | The work items a schedule trigger's work shape admitted. `funnel` counts per stage and status (every item is in exactly one cell). `items` is the grid — `--stage`, `--status`, `--ignore-reason`, `--item-key` (client-side; the endpoint has no such filter), each item's `label` (what the Work grid calls the row, as against the provider's opaque `itemKey`), and its accumulated `data`, which is where a stage's declared `produces` keys land. A listing is **one page of 20** that reports how many matched in total — a ledger's healthy state is thousands of rows served ten per request, so an unbounded walk is rate-limited long before the end; `-n` sizes the page and `--all` walks the lot. `--ignore-reason` narrows to the items one exclusion reason set aside — the same filter `redrive-many` selects by, matched whole and case-sensitively, so it is how you read a batch before putting it back. `cursor` is the watermark and its freshness. `transitions` is one item's attempt history; `effects` is what it did to the outside world — an `UNKNOWN` row parks the item until someone settles it. `batches` is what an ASK stage is waiting on a human for. `funnel` and `items` also carry `cost` (all-in USD) and `runs` — so the funnel says *which stage* the money goes to, not merely how much. An item's `runs` spans every stage it passed through and so is not `stageAttempts`, which resets per stage; a `Cost` of `—` means nothing there has ever been claimed, as against `$0.0000`, which means it ran and was free. Cost is not sortable: the endpoint's `--order-by` allow-list does not carry it. |
|
|
114
|
+
| `fruxon triggers ledger passes` | Every time the automation fired, newest first — `admitted` (what the door took), `dispatched` (what the pass claimed and sent to work, questions parked on a human included), `completed` (how many of *this pass's* dispatches reached the terminal stage), `runs`, and the pass's all-in USD. `runs` sits **below** `dispatched` while runs are in flight or when a dispatch went to a person rather than a model — that gap is normal, not a lost item. And a pass's `cost` **rises after** it fired, as those runs finish, so a pass read seconds later reports a floor rather than its bill. An `outcome` other than `FIRED` means no sweep happened: `FILTERED` / `COALESCED` are healthy (nothing to do, or folded into a pass already running), `FAILED` / `FILTER_ERROR` are not. `-n/--limit` bounds the walk. |
|
|
114
115
|
| `fruxon triggers ledger redrive/ignore/settle/cancel-batch` | The operator verbs; all confirm and refuse without `--yes` in agent mode. `redrive` returns a DEAD or FAILED item to the queue with its attempts reset — settle any `UNKNOWN` effect first or the next attempt hits the same refusal. `ignore` excludes an item for a **required** reason, which is the only thing separating IGNORED from DEAD later. `settle` answers an effect the platform could not confirm (`--landed` / `--did-not-land` + `--reason`); wrong in the second direction duplicates the write. `cancel-batch` calls off a question — the items go back to READY and a later pass re-batches them. |
|
|
115
|
-
| `fruxon triggers ledger redrive-many/ignore-many` | The same two verbs over a **selection** — `--id` (repeatable), `--stage`, `--status`, `--before`/`--after` (Unix ms). The CLI resolves the selection to a count under the verb's own eligibility, shows it, and the server acts on exactly that count (409 if the set changed). `ignore-many --stage X` is how you drain a stage. When the selection is `--id` values, the result names every id it did **not** act on and why — not a work item, another automation's, or a status the verb cannot touch (usually a pass holding the claim). |
|
|
116
|
+
| `fruxon triggers ledger redrive-many/ignore-many` | The same two verbs over a **selection** — `--id` (repeatable), `--stage`, `--status`, `--before`/`--after` (Unix ms). The CLI resolves the selection to a count under the verb's own eligibility, shows it, and the server acts on exactly that count (409 if the set changed). The count is the server's own whenever the endpoint's filters say exactly what the selection says (a `--status` or an `--ignore-reason`, with no `--id` and no `--before`/`--after`) — one request instead of walking every page of a 4,000-item ledger, and one fewer place for the two sides to disagree. `ignore-many --stage X` is how you drain a stage. When the selection is `--id` values, the result names every id it did **not** act on and why — not a work item, another automation's, or a status the verb cannot touch (usually a pass holding the claim). |
|
|
117
|
+
| `fruxon triggers ledger redrive-many --ignore-reason "…"` | Reconsider what one instruction set aside. `IGNORED` is terminal for the scheduler, not for you: a re-drive returns an excluded item to `READY` at the stage that set it aside, with a fresh attempt budget. Exclusions are **opt-in** — they come in only with `--ignore-reason`, `--status IGNORED`, or `--id`, so `--stage X` still means the stuck items there. `--status IGNORED` alone is refused with the count it would have moved, because each item costs an agent run. The reason is matched whole and case-sensitively against what the stage recorded; a wording the shape does not declare but an item carries is still accepted. |
|
|
116
118
|
| `fruxon triggers ledger reset-cursor/purge` | `reset-cursor` moves where the next scan starts, as a compare-and-swap on the version it just read (`--value` omitted = the door's own horizon); it never re-runs admitted work. It is the *after* to `workShape.door.startFrom`'s *before* — a horizon set at creation seeds the cursor before anything sweeps, and is ignored once a pass has read the source. `purge` starts the ledger over — cancels open questions, deletes items/effects/transitions, bumps the cursor version — and confirms the exact item count first. Destructive; the effects' external targets are not touched. |
|
|
117
119
|
| `fruxon triggers questions list/answer` | The human half of an ASK stage, tenant-wide. `ledger batches <trigger>` answers *what is this automation waiting on*; this answers *what is waiting on anyone* — soonest deadline first, which is how an undelivered question gets found at all. Read **`delivery`** first: `UNDELIVERABLE` (nobody reachable in the slot) and `FAILED` (a target resolved, the send did not land) both mean nobody was asked and nobody will answer unless you do — `QUEUE_ONLY` is healthy. `--undelivered-only` narrows to that pair; `--trigger` filters client-side (the endpoint has no such filter). `answer` settles one on the assignee's behalf and is `--yes`-gated: the reply interpreter **fails closed**, so an answer it cannot place advances nothing and the question stays open, and each item advances only once the stage's `answeredWhenPath` is filled — answering two of three advances two. |
|
|
118
120
|
| `fruxon triggers set-stages <id> --file` | Replace an automation's stages without rewriting the rest of its work shape (a PATCH replaces the whole shape, so this reads it, splices, and posts). Takes a bare stages array or any document carrying one. Pre-flights the two refusals the server only reports after the write: live items at a stage your list drops, and a `door` that will re-expand `source` on save. `--dry-run` prints the merged shape and the warnings. |
|
|
@@ -48,6 +48,7 @@ from fruxon.models import (
|
|
|
48
48
|
LedgerFunnelCell,
|
|
49
49
|
LedgerItem,
|
|
50
50
|
LedgerItemTransition,
|
|
51
|
+
LedgerPass,
|
|
51
52
|
LedgerPurgeResult,
|
|
52
53
|
LedgerSelectionResult,
|
|
53
54
|
LedgerSkippedItem,
|
|
@@ -73,6 +74,7 @@ from fruxon.models import (
|
|
|
73
74
|
TriggerRevision,
|
|
74
75
|
UnhandledEscalation,
|
|
75
76
|
WorkShapeDoor,
|
|
77
|
+
WorkShapeDoorField,
|
|
76
78
|
WorkShapeSourceCount,
|
|
77
79
|
WorkShapeSourceTest,
|
|
78
80
|
WorkShapeSourceTestRecord,
|
|
@@ -119,6 +121,7 @@ __all__ = [
|
|
|
119
121
|
"LedgerFunnelCell",
|
|
120
122
|
"LedgerItem",
|
|
121
123
|
"LedgerItemTransition",
|
|
124
|
+
"LedgerPass",
|
|
122
125
|
"LedgerPurgeResult",
|
|
123
126
|
"LedgerSelectionResult",
|
|
124
127
|
"LedgerSkippedItem",
|
|
@@ -147,6 +150,7 @@ __all__ = [
|
|
|
147
150
|
"UnhandledEscalation",
|
|
148
151
|
"ValidationError",
|
|
149
152
|
"WorkShapeDoor",
|
|
153
|
+
"WorkShapeDoorField",
|
|
150
154
|
"WorkShapeSourceCount",
|
|
151
155
|
"WorkShapeSourceTest",
|
|
152
156
|
"WorkShapeSourceTestRecord",
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.13.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 13,
|
|
21
|
+
__version__ = version = '0.13.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 13, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -76,6 +76,8 @@ def _classify_exception(exc: BaseException) -> str:
|
|
|
76
76
|
import ssl
|
|
77
77
|
import urllib.error as _ue
|
|
78
78
|
|
|
79
|
+
from fruxon.cli import _crash
|
|
80
|
+
|
|
79
81
|
if isinstance(exc, typer.Exit):
|
|
80
82
|
# ``typer.Exit`` is a normal-flow signal even with a non-zero exit
|
|
81
83
|
# code — it's how we surface a handled failure, not a crash. But it
|
|
@@ -86,6 +88,8 @@ def _classify_exception(exc: BaseException) -> str:
|
|
|
86
88
|
# ``fail()``" and the schema's ``auth`` / ``not_found`` /
|
|
87
89
|
# ``server_error`` values were allowlisted but never once sent.
|
|
88
90
|
return _EXIT_CODE_CLASSES.get(getattr(exc, "exit_code", 1), "other")
|
|
91
|
+
if _crash.is_usage_error(exc):
|
|
92
|
+
return "user_input"
|
|
89
93
|
if isinstance(exc, KeyboardInterrupt):
|
|
90
94
|
return "user_input"
|
|
91
95
|
if isinstance(exc, (_ue.URLError, socket.timeout, ConnectionError, ssl.SSLError, OSError)):
|
|
@@ -143,6 +147,31 @@ def _report_crash(exc: BaseException, command: str) -> None:
|
|
|
143
147
|
stderr.print(" [dim]Full traceback here:[/dim] FRUXON_DEBUG=1 fruxon …")
|
|
144
148
|
|
|
145
149
|
|
|
150
|
+
def _render_usage_error(exc: BaseException, command: str) -> None:
|
|
151
|
+
"""Turn a Click parse failure into the agent contract's ``usage`` envelope.
|
|
152
|
+
|
|
153
|
+
Typer 0.26 vendors Click, so these exceptions bypass Typer's normal
|
|
154
|
+
top-level formatter while our custom group is on the stack — and what
|
|
155
|
+
reaches an AI driver is Click's bordered ASCII box, which is neither JSON
|
|
156
|
+
nor distinguishable from a crash. The contract (``fruxon guides show
|
|
157
|
+
fruxon-agent-mode``) says a parse error is one JSON envelope on stderr and
|
|
158
|
+
exit 2, and this is the single place that renders it.
|
|
159
|
+
|
|
160
|
+
Raises ``typer.Exit(2)``; never returns.
|
|
161
|
+
"""
|
|
162
|
+
message = exc.format_message().strip() if hasattr(exc, "format_message") else str(exc).strip()
|
|
163
|
+
if not message:
|
|
164
|
+
message = "A subcommand is required."
|
|
165
|
+
usage_ctx = getattr(exc, "ctx", None)
|
|
166
|
+
command_path = getattr(usage_ctx, "command_path", None) or f"fruxon {command}"
|
|
167
|
+
say_err(
|
|
168
|
+
message,
|
|
169
|
+
hint=f"Run [bold]{command_path} --help[/bold] for usage.",
|
|
170
|
+
exit_code=2,
|
|
171
|
+
)
|
|
172
|
+
raise typer.Exit(2) from None
|
|
173
|
+
|
|
174
|
+
|
|
146
175
|
class _FruxonTyperGroup(TyperGroup):
|
|
147
176
|
"""Branded replacement for Typer's stock ``no such command`` error.
|
|
148
177
|
|
|
@@ -160,6 +189,34 @@ class _FruxonTyperGroup(TyperGroup):
|
|
|
160
189
|
signal.
|
|
161
190
|
"""
|
|
162
191
|
|
|
192
|
+
def make_context(self, info_name, args, parent=None, **extra):
|
|
193
|
+
"""Catch the parse failures that happen *before* any subcommand runs.
|
|
194
|
+
|
|
195
|
+
``invoke`` below covers everything raised while dispatching into the
|
|
196
|
+
tree, which is most of it. It cannot cover the root's own argument
|
|
197
|
+
parsing: Click builds the root context first and hands the failure
|
|
198
|
+
straight to its top-level handler, so ``fruxon --bogus`` printed the
|
|
199
|
+
ASCII box to an AI driver that had been promised JSON for every other
|
|
200
|
+
parse error in the CLI. Same envelope, same exit code, one fewer shape
|
|
201
|
+
for a caller to special-case.
|
|
202
|
+
"""
|
|
203
|
+
try:
|
|
204
|
+
return super().make_context(info_name, args, parent=parent, **extra)
|
|
205
|
+
except BaseException as exc:
|
|
206
|
+
from fruxon.cli import _crash
|
|
207
|
+
|
|
208
|
+
if not (_crash.is_usage_error(exc) and is_agent_mode()):
|
|
209
|
+
raise
|
|
210
|
+
# The event the command would have sent had it got far enough to
|
|
211
|
+
# run. Reported as "(root)" because that is literally as far as it
|
|
212
|
+
# got — argv here has not been parsed, and putting an unparsed
|
|
213
|
+
# token on the wire is the thing `_command_path` exists to prevent.
|
|
214
|
+
telemetry.track(
|
|
215
|
+
"CLI Command Failed",
|
|
216
|
+
{"command": "(root)", "error_class": "user_input", "duration_ms": 0},
|
|
217
|
+
)
|
|
218
|
+
_render_usage_error(exc, "(root)")
|
|
219
|
+
|
|
163
220
|
def invoke(self, ctx):
|
|
164
221
|
"""Wrap subcommand execution with telemetry timing.
|
|
165
222
|
|
|
@@ -206,6 +263,9 @@ class _FruxonTyperGroup(TyperGroup):
|
|
|
206
263
|
properties["error_fingerprint"] = mark
|
|
207
264
|
telemetry.track("CLI Command Failed", properties)
|
|
208
265
|
|
|
266
|
+
if _crash.is_usage_error(exc) and is_agent_mode():
|
|
267
|
+
_render_usage_error(exc, command_name)
|
|
268
|
+
|
|
209
269
|
if unexpected and not _crash.debug_enabled():
|
|
210
270
|
_report_crash(exc, command_name)
|
|
211
271
|
# Replaces the traceback rather than printing alongside it.
|
|
@@ -263,7 +323,7 @@ class _FruxonTyperGroup(TyperGroup):
|
|
|
263
323
|
)
|
|
264
324
|
else:
|
|
265
325
|
hint = "Run [bold]fruxon --help[/bold] for the full command list."
|
|
266
|
-
say_err(f"Unknown command [bold]{cmd_name}[/bold].", hint=hint)
|
|
326
|
+
say_err(f"Unknown command [bold]{cmd_name}[/bold].", hint=hint, exit_code=2)
|
|
267
327
|
ctx.exit(2)
|
|
268
328
|
return None # unreachable but keeps the type checker happy
|
|
269
329
|
|
|
@@ -47,15 +47,48 @@ def is_unexpected(exc: BaseException) -> bool:
|
|
|
47
47
|
changing their mind, and greeting it with a crash report would be absurd.
|
|
48
48
|
What is left is a bug.
|
|
49
49
|
"""
|
|
50
|
-
import click
|
|
51
50
|
import typer
|
|
52
51
|
|
|
53
|
-
return
|
|
54
|
-
exc,
|
|
55
|
-
|
|
52
|
+
return (
|
|
53
|
+
not isinstance(exc, (typer.Exit, typer.Abort, SystemExit, KeyboardInterrupt))
|
|
54
|
+
and not _is_click_exception(exc)
|
|
55
|
+
and not _is_click_exception(exc, class_name="Abort")
|
|
56
56
|
)
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def is_usage_error(exc: BaseException) -> bool:
|
|
60
|
+
"""Whether ``exc`` is a parse/usage error from either Click namespace.
|
|
61
|
+
|
|
62
|
+
Typer 0.26 vendors Click under :mod:`typer._click`. Its
|
|
63
|
+
``NoSuchOption`` / ``MissingParameter`` / ``NoArgsIsHelpError`` classes
|
|
64
|
+
are therefore *not* subclasses of the top-level ``click.UsageError``.
|
|
65
|
+
Checking both namespaces is what keeps an ordinary typo out of the crash
|
|
66
|
+
reporter while still supporting Typer releases that use external Click.
|
|
67
|
+
"""
|
|
68
|
+
return _is_click_exception(exc, class_name="UsageError")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _is_click_exception(exc: BaseException, *, class_name: str = "ClickException") -> bool:
|
|
72
|
+
"""Match Click's public exceptions and Typer's vendored equivalents."""
|
|
73
|
+
import click
|
|
74
|
+
|
|
75
|
+
classes: list[type[BaseException]] = []
|
|
76
|
+
external = getattr(click.exceptions, class_name, None)
|
|
77
|
+
if isinstance(external, type) and issubclass(external, BaseException):
|
|
78
|
+
classes.append(external)
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
from typer._click import exceptions as typer_click_exceptions
|
|
82
|
+
|
|
83
|
+
vendored = getattr(typer_click_exceptions, class_name, None)
|
|
84
|
+
if isinstance(vendored, type) and issubclass(vendored, BaseException):
|
|
85
|
+
classes.append(vendored)
|
|
86
|
+
except ImportError: # Older Typer releases use the top-level Click package.
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
return bool(classes) and isinstance(exc, tuple(classes))
|
|
90
|
+
|
|
91
|
+
|
|
59
92
|
def fingerprint(exc: BaseException) -> str | None:
|
|
60
93
|
"""``"KeyError at cli/agents_revisions.py:97"`` — safe to put on the wire.
|
|
61
94
|
|
|
@@ -71,10 +71,13 @@ def _fetch_spec(base_url: str) -> dict:
|
|
|
71
71
|
try:
|
|
72
72
|
with urllib.request.urlopen(url, timeout=10, context=ctx) as resp: # noqa: S310 — http(s) only
|
|
73
73
|
data = json.load(resp)
|
|
74
|
-
except urllib.error.URLError as exc:
|
|
74
|
+
except (urllib.error.URLError, TimeoutError) as exc:
|
|
75
75
|
fail(
|
|
76
76
|
f"Couldn't fetch the OpenAPI spec from {url}: {exc}",
|
|
77
|
-
hint=
|
|
77
|
+
hint=(
|
|
78
|
+
"Schema output is fetched from the target server. Check that it is reachable, "
|
|
79
|
+
"or pass --base-url for a server that exposes /swagger/v1/swagger.json."
|
|
80
|
+
),
|
|
78
81
|
code=EXIT_NETWORK,
|
|
79
82
|
)
|
|
80
83
|
except json.JSONDecodeError as exc:
|
|
@@ -17,7 +17,7 @@ from typing import Annotated
|
|
|
17
17
|
|
|
18
18
|
import typer
|
|
19
19
|
|
|
20
|
-
from fruxon.cli._shared import build_client
|
|
20
|
+
from fruxon.cli._shared import build_client, confirm_or_die
|
|
21
21
|
from fruxon.cli.agents import (
|
|
22
22
|
_hint_for_get_error,
|
|
23
23
|
_hint_for_write_error,
|
|
@@ -1021,6 +1021,10 @@ def draft_evaluate(
|
|
|
1021
1021
|
@draft_app.command("discard")
|
|
1022
1022
|
def draft_discard(
|
|
1023
1023
|
agent: Annotated[str, typer.Argument(help="Agent identifier.")],
|
|
1024
|
+
yes: Annotated[
|
|
1025
|
+
bool,
|
|
1026
|
+
typer.Option("--yes", "-y", help="Skip the confirmation prompt. REQUIRED under agent mode (CLAUDECODE / CI)."),
|
|
1027
|
+
] = False,
|
|
1024
1028
|
workspace: Annotated[str | None, typer.Option("--workspace", help="Workspace identifier override.")] = None,
|
|
1025
1029
|
base_url: Annotated[str | None, typer.Option("--base-url", help="API base URL override.")] = None,
|
|
1026
1030
|
output: Annotated[
|
|
@@ -1032,7 +1036,29 @@ def draft_discard(
|
|
|
1032
1036
|
),
|
|
1033
1037
|
] = None,
|
|
1034
1038
|
):
|
|
1035
|
-
"""Delete the draft on the server and remove the local sidecar.
|
|
1039
|
+
"""Delete the draft on the server and remove the local sidecar.
|
|
1040
|
+
|
|
1041
|
+
The draft and its undo history cannot be recovered. Confirms first; under
|
|
1042
|
+
agent mode, pass ``--yes`` explicitly so the command never blocks waiting
|
|
1043
|
+
for input.
|
|
1044
|
+
|
|
1045
|
+
Examples:
|
|
1046
|
+
fruxon agents draft discard my-agent --yes
|
|
1047
|
+
fruxon agents draft discard my-agent --yes -o json
|
|
1048
|
+
"""
|
|
1049
|
+
output = resolve_output_format(output, human_default="text", agent_default="json")
|
|
1050
|
+
if output not in {"text", "json"}:
|
|
1051
|
+
fail(
|
|
1052
|
+
f"Unknown output format: [bold]{output}[/bold]",
|
|
1053
|
+
hint="Valid formats: text, json.",
|
|
1054
|
+
code=EXIT_VALIDATION,
|
|
1055
|
+
)
|
|
1056
|
+
confirm_or_die(
|
|
1057
|
+
f"Discard the server draft for [bold]{agent}[/bold]? Its undo history will also be deleted.",
|
|
1058
|
+
yes=yes,
|
|
1059
|
+
action=f"discard the draft for {agent}",
|
|
1060
|
+
)
|
|
1061
|
+
|
|
1036
1062
|
client = build_client(workspace, base_url)
|
|
1037
1063
|
meta = _read_sidecar(agent)
|
|
1038
1064
|
try:
|
|
@@ -1041,7 +1067,6 @@ def draft_discard(
|
|
|
1041
1067
|
fail_from_api_error(e, hint=_hint_for_write_error(e))
|
|
1042
1068
|
_sidecar_path(agent).unlink(missing_ok=True)
|
|
1043
1069
|
|
|
1044
|
-
output = resolve_output_format(output, human_default="text", agent_default="json")
|
|
1045
1070
|
if output == "json":
|
|
1046
1071
|
print(json.dumps({"agent": agent, "discarded": True}, indent=2))
|
|
1047
1072
|
return
|
|
@@ -146,6 +146,10 @@ def revisions_get(
|
|
|
146
146
|
] = False,
|
|
147
147
|
workspace: Annotated[str | None, typer.Option("--workspace", help="Workspace identifier override.")] = None,
|
|
148
148
|
base_url: Annotated[str | None, typer.Option("--base-url", help="API base URL override.")] = None,
|
|
149
|
+
output: Annotated[
|
|
150
|
+
str | None,
|
|
151
|
+
typer.Option("--output", "-o", help="Output format: json (the only format for this pipe-oriented command)."),
|
|
152
|
+
] = None,
|
|
149
153
|
):
|
|
150
154
|
"""Print a revision's full body.
|
|
151
155
|
|
|
@@ -168,9 +172,16 @@ def revisions_get(
|
|
|
168
172
|
Examples:
|
|
169
173
|
fruxon agents revisions get my-agent # current deployed revision
|
|
170
174
|
fruxon agents revisions get my-agent 3 # a specific revision
|
|
171
|
-
fruxon agents revisions get my-agent --as-draft # postable body
|
|
175
|
+
fruxon agents revisions get my-agent --as-draft -o json # postable body
|
|
172
176
|
fruxon agents revisions get my-agent | jq .definition # just the agent
|
|
173
177
|
"""
|
|
178
|
+
if output not in {None, "json"}:
|
|
179
|
+
fail(
|
|
180
|
+
f"Unknown output format: [bold]{output}[/bold]",
|
|
181
|
+
hint="This command always emits JSON; pass [bold]--output json[/bold] or omit the flag.",
|
|
182
|
+
code=EXIT_VALIDATION,
|
|
183
|
+
)
|
|
184
|
+
|
|
174
185
|
client = build_client(workspace, base_url)
|
|
175
186
|
|
|
176
187
|
target_revision: int | str
|