Repository navigation
mammoth-cli 2.3.0
·
183 commits
to main
since this release
Added (mammoth-cli 2.3.0)
- CLI: typed output schemas (#315). Every command (346 reads and 505 writes) declares the shape of its
dataasoutput_schemain the manifest, andschema get --fullreturns it as a self-contained JSON Schema 2020-12. A command declaresopenapi(itsdatais the API's own200body), a named schema fromspec/manifests/output-schemas.yaml({ref: NAME}, shared between commands), or an inline schema. Writes shareWriteResult: theverifyblock every API-backed write returns, thestateread-back where the command has one, and the API's own fields. Transforms, exports, bulk deletes, agent turns and runs, and skill writes build on it (TransformResult,ExportResult,DatabaseExportResult,BulkDeleteResult,AgentTurnResult,AgentRunResult,SkillWriteResultand others); a delete with no response body isnull(WriteResultOrNull).output_schemais always the real run'sdata.--dry-runof an API-backed write returns a report instead, andschema get --fullreturns its schema asdry_run_output_schema(nullfor a read). A contract test checks that each of the 851 commands declares one and that it resolves, and a fixture test runs each command's example (and each write's--dry-run) through the real handler and validates the result. It found two wrong read schemas, fixed here:view data comparereturnsdata, notrows, and a folder'screated_bycan be text. - CLI: ETag'd bodies are kept on disk between commands, so repeated
project list,dataset listorview getcalls download only what changed. There is one owner-only directory per credential, profile, server and workspace, under the user cache dir. A write through that credential empties it, andauth login/auth logoutremove all of them.MAMMOTH_CLI_ETAG_CACHE=0keeps the bodies for one command only. Embedded calls (embed.invoke(runtime=...)) never touch the disk. The host'sEmbedRuntimekeeps one in-memory cache, capped at 4096 bodies and 128 MB, and each caller uses only their own scope of it, named by a digest of their credential. Two users therefore never share a body, even when the server gives them the same tag for differently filtered lists. - CLI: every API request names the command that sent it in its
User-Agent, which the server's request log already stores:mammoth-cli/<version> (cmd=view.data.get; inv=3f9c2a1b7d4e; surface=run; agent=claude-code) mammoth-io/<version>.invis one id per command invocation, shared by all its requests and equal to the local run log'srun_id.surfaceisterminal,run(amammoth runline) orembed.agentis set fromMAMMOTH_AGENT, or from the variable a coding agent sets (CLAUDECODE,CURSOR_AGENT,GEMINI_CLI,CODEX_SANDBOX), and never for an embedded call. Only identifiers are sent: no argument values, paths or data. - CLI: steps in
mammoth run. A line with"name"keeps its envelope, and a later line reads it inargs,inputorflagsas"{{steps.<name>.data...}}"."when"skips a line on a JSON condition (eq,in,not,all,any,present), and"for_each"runs a line once per item of a list, with{{item}}and{{index}}. A reference that does not resolve fails the line withunresolved_reference(exit 2); under--dry-runthe line is planned instead. It is plain JSON: no code runs. A$-key other than a value operator ($schema,$ref) in a line is data. Awhenthat is a bare string (no braces) is refused, as it would always be true. Under--dry-run, only a reference into the result of an earlier line plans the line; a malformed expression or a step no line makes fails it. Afor_eachover an empty list prints a skipped envelope withdata: [], and with--continueit runs the items after a failed one. - CLI:
mammoth history list,history show IDandhistory undo ID. Every write is journaled on this machine (owner-only, under the log directory, rotated at 10 MB) with themammoth runlines that reverse it: a trash is undone by a restore, an object a write made goes to the trash, and a pipeline edit returns the view to the pipeline version it had before.history undotakes one write'sinvor a wholemammoth run'sgroup, runs the lines newest first through the normal command path, stops at the first failure (outcome: partial, exit 3) and needs a prompt or--yes;--dry-runpreviews them. A permanent delete, or a write with an effect outside Mammoth, is listed with the reason it cannot be undone.MAMMOTH_CLI_JOURNAL=0turns the journal off; embedded calls are never journaled. An append (file uploadwithappend_to_ds_id) or an export into an existing dataset is nevercreated, so its undo does not trash the dataset (rows cannot be taken out again, and the undo says so). Undo records only the--profilethat was typed, not the default, so an env-token write is not undone as another account. An undo of a pipeline edit refuses when the view's pipeline changed since the edit (--input '{"force": true}'overrides), a stopped undo goes on from the line that failed, and two undos of one write cannot run together. The journal takes a file lock for each append, writes each record in one write, and keeps a run's plan in its own file (pruned after 60 days) so a rotation never drops it. Plans and named results are stored with credential-looking values replaced.history list --input '{"limit": 0}'is refused. An undo runs as the account the write used: a write made under a stored profile is undone under that profile even afterprofile usechanged, and one made withMAMMOTH_API_TOKENis refused withundo_needs_tokenwhen no token is set. The fingerprint that detects a changed pipeline redacts like the CLI output. When another process holds a write's undo, the writes already undone stay in the result (exit 3,not_undonelists the rest), and an undo's lock file is removed when it ends. A journal that cannot be written warns once on stderr. - CLI: every write command declares how
history undoreverses it in its manifest, asundo:{strategy: inverse, command}(run the named command with the write's own arguments, e.g.agent run pauseandresume,dataset file-settings updateandundo),pipeline_version(apply the view's version from before the edit),trash_created(trash what the write made),manual(a command reverses it but needs an id the write made, so it is named, not run) orirreversiblewith a specific reason (a permanent delete, an export to a named system, an email, a refund).journal.compensationreads the declaration instead of working it out frommutation_class.scripts/build_manifests.pyderives the default;spec/manifests/commands/<group>.yamloverrides it, and a contract test fails a write with noundo. #311 Aninverseundo line carries the--confirmtarget and the--sessionthe original write named, so a high-impact reversal runs; an inverse that needs input the write lacks (support partner client cancel-detach) ismanual. Amanualentry says why it is not run, including for pairs that take the same arguments (addon,project user,dashboard filter).file upload-folderanddataset create-from-pdfare undone by trashing what they made;view variants createlists every view it made; a repeateddashboard live-chart createis not journaled as created. - CLI: every success envelope carries
meta.run_id, the invocation id: the same id as the run log'srun_id(log tail), the journal'sinv(history show) and theinvin the request's User-Agent.embed.invoke(..., schema_version=1)pins the envelope version a host was written against;mammoth-cli/docs/envelope-compatibility.mdsays what may change in a minor release and what needs a newschema_version(still 1: nothing in 2.3 breaks it). - CLI: the servers that honour
Idempotency-Keyare remembered between commands (one owner-only file of API base URLs in the user cache dir), so a command's first write can be replayed safely too. Embedded calls never write it. - CLI:
mammoth recipe listandmammoth recipe run ID --input '{"inputs": {...}}'run a common task as one command. All 32 recipes indiscovery/recipes.jsonare executable: append-rows, upload-dashboard, export-csv (takesview_idanddataset_id), share-dashboard, refresh-daily, blend-by-key, stuck-upload, export-powerbi, clean-data, cleanup-mine, upload-clean, join-files, lookup-column, add-calculated-column, extract-date-part, split-column, find-replace, fill-blanks-with-value, rename-columns, remove-duplicates, pivot-summary, top-n, filter-export-csv, export-xlsx, export-to-s3, email-view, dashboard-from-view, summarize-by, compare-views, refresh-now, undo-last-change and revert-view-version. The tasks that would replace a view's rows (pivot-summary, top-n, filter-export-csv) work on a copy. A recipe input can be a string, an integer, an array, an object orany. A recipe's steps run through themammoth runpath in one process, printing one envelope per step and a summary (recipe,inputs,partial_lines). A recipe that writes asks once (prompt or--yes), and--dry-runpreviews every step.mammoth howreturns the recipe'srun_command;schema findlists matching recipes (recipes), andschema listandcapability findlisttasks, before single commands.scripts/eval_task_calls.pycounts the calls: 32 with recipes against 113 step by step for the 32 tasks (mammoth-cli/docs/task-call-eval.md). New error codes:recipe_not_found,recipe_input_invalid.cleanup-minemoves things to the trash (nothing is deleted for good), after a name check and a dependents dry run. A recipe never confirms a destructive line for the caller: it needs an explicit--yes, and the one prompt names those lines. Ananyinput takes a list or a boolean.upload-clean,upload-dashboardandjoin-filesskip the lines that need the upload's view when it has none, and end with adataset getthat shows the dataset's state. In--dry-runawhenthat reads a preview is still planned, and a step skipped bywhenis not taken for a preview. A binding with$first(or another operator) beside other keys is refused. Embedded in a host (mammoth_cli.embed.invoke),recipe runruns each step as a step of the host's call, with the host's login, project and session, and returns one envelope: the summary, with each step's envelope inlines_run. A recipe with a line the host cannot run (file upload,history undo) is refused before any line runs (not_available_embedded). - CLI:
mammoth history resume GROUPgoes on with amammoth runorrecipe runthat stopped (a failed line, Ctrl-C, a crash). It reruns from the first line that did not finish, and later lines still read the earlier lines' results ({{steps...}}). The run's lines and those results are kept in the journal; a result larger than 256 KB is not kept. If the run stopped while a write was being sent, resume refuses (resume_outcome_unknown): check the write, then pass--input '{"from_line": N}'. Needs a prompt or--yes;--dry-runpreviews. A failed named line is run again without analready namederror. A write that was interrupted, timed out or lost its connection after it was sent makes resume refuse (resume_outcome_unknown) untilfrom_linenames the line to start from; a partial line ofrecipe runis not run again. A resume is recorded under the run it resumed, so a second resume andhistory undo GROUPsee what the first did, and afor_eachline goes on from its first unfinished item. A line that needs a secret the history does not keep is refused (resume_secret_needed). Afor_eachitem result over 256 KB is journaled as omitted: a resume runs a read item again, refuses to repeat a write item (resume_result_too_large), and names thefrom_linethat makes a result a later line reads. A resume also refuses a line that reads redacted data in an unfinishedfor_each.
Changed (mammoth-cli 2.3.0)
- CLI: needs mammoth-io 0.8.52 or later: the response cache (
mammoth.etag) and the safe write retries are not in 0.8.51. - CLI: embedded in a host (
mammoth_cli.embed.invoke), the discovery commands leave out what the host refuses:schema list,schema find,capability findandcapability listdrop the host-machine commands (history,auth,config,log,skill...) and those that read a local file (file upload);recipe list, thetasksandrecipesof the index commands, andhowdrop the recipes that use them (append-rows, upload-clean, upload-dashboard, join-files, undo-last-change). When the best recipe for a goal is one of them,howsays so (matched: false, a hint) instead of answering with a weaker recipe, and a several-part goal keeps the parts the host can run. A terminal run is unchanged. - CLI: this is mammoth-cli 2.3.0, the ver3 architecture (#307): one command path with interceptors, a write journal with undo and resume, steps and recipes in
mammoth run. Nothing breaks: no command, flag or input was removed or changed, and every envelope field was reviewed and kept, soschema_versionstays 1 (the field review is inmammoth-cli/docs/envelope-compatibility.md). A host or agent written against 2.2 needs no change.
Fixed (mammoth-cli 2.3.0)
- CLI: two embedded calls that start at the same moment in a fresh process no longer fail with
internal_error("partially initialized module 'mammoth.view'"). One call could read the SDK's view module while the other was still importing it. - CLI:
dataset refreshon a dataset made from an uploaded file returnsdataset_not_refreshablewith the ways forward (append the new file, or connect a cloud source), asautomation createalready did. Before, it was a genericapi_error("Invalid source type [4DSET048]"). - CLI:
numbers_stored_as_text(anddates_stored_as_text) now catch a column whose missing values are written as a placeholder. Values likeN/A,NA,#N/A,-,--,null,none,nil,?andnanno longer count against the column's share of numbers. Before,100, 250, 75, N/A(3 of 4, under the 80% bar) uploaded as TEXT with no warning. The finding still lists the placeholders, which the conversion makes empty. - CLI: the
dashboard v3 generateagent example now includes the requiredintent. Without it the backend refused the example with 4GENR007 ("intent must not be empty"). - CLI:
view version applywaits for the pipeline to be rebuilt before it returns, so the view shows the applied steps. Before, it returned the job handle and a read straight after it still showed the old steps. - CLI:
view conditional-format createandreplacelist the allowedcolorswatches (red, amber, green, blue, purple, teal, coral, grey,text-*) inschema getandschema find, and refuse another word before the request with the list. Before,colorwas an open string and the list was only in the backend's 400. - CLI:
schema gettakes ids separated by spaces (schema get A B) as well as by,or;. Before, it refused the second id withunexpected_argument. - CLI: a
file uploadlists the datasets it made inmeta.opens(created: true), as other creating writes already did. - CLI: an append (
file upload --input '{"append_to_ds_id": N}') waits for the target dataset to take the rows before it reports. Before, it could read the target while it was stillprocessing:rows_added: 0and a dataset health ofunknown, though the rows did land. If the target is stillprocessingwhen the wait ends, the result saysappend_pending: truewith a warning.