Rename content_hash to cid in CLI

Update CLI argument names and help text:
- Change argument names from content_hash to cid
- Update user-facing messages to use CID terminology
- Update README examples with CID syntax

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-12 08:04:07 +00:00
parent c0414a9e0d
commit 6c1735e6b4
2 changed files with 62 additions and 62 deletions

View File

@@ -75,8 +75,8 @@ export ARTDAG_L2=https://artdag.rose-ash.com
# Using asset name # Using asset name
./artdag.py run dog cat ./artdag.py run dog cat
# Using content hash # Using CID
./artdag.py run dog 33268b6e167deaf018cc538de12dbe562612b33e89a749391cef855b320a269b ./artdag.py run dog Qm33268b6e167deaf018cc538de12dbe562612b33e
# Wait for completion # Wait for completion
./artdag.py run dog cat --wait ./artdag.py run dog cat --wait
@@ -112,13 +112,13 @@ export ARTDAG_L2=https://artdag.rose-ash.com
### View/Download Cached Content ### View/Download Cached Content
```bash ```bash
# Show info # Show info
./artdag.py view <content-hash> ./artdag.py view <cid>
# Download to file # Download to file
./artdag.py view <content-hash> -o output.mkv ./artdag.py view <cid> -o output.mkv
# Pipe to mpv (use -o - for stdout) # Pipe to mpv (use -o - for stdout)
./artdag.py view <content-hash> -o - | mpv - ./artdag.py view <cid> -o - | mpv -
``` ```
### Import Local File to Cache ### Import Local File to Cache
@@ -128,10 +128,10 @@ export ARTDAG_L2=https://artdag.rose-ash.com
### Delete Cached Content ### Delete Cached Content
```bash ```bash
./artdag.py delete-cache <content-hash> ./artdag.py delete-cache <cid>
# Skip confirmation # Skip confirmation
./artdag.py delete-cache <content-hash> -f ./artdag.py delete-cache <cid> -f
``` ```
Note: Items that are inputs/outputs of runs, or published to L2, cannot be deleted. Note: Items that are inputs/outputs of runs, or published to L2, cannot be deleted.
@@ -151,7 +151,7 @@ Configs are reusable DAG definitions with fixed and variable inputs.
./artdag.py config <config-id> ./artdag.py config <config-id>
# Run a config with variable inputs # Run a config with variable inputs
./artdag.py run-config <config-id> -i node_id:content_hash --wait ./artdag.py run-config <config-id> -i node_id:cid --wait
# Delete a config # Delete a config
./artdag.py delete-config <config-id> ./artdag.py delete-config <config-id>
@@ -173,5 +173,5 @@ Configs are reusable DAG definitions with fixed and variable inputs.
./artdag.py runs ./artdag.py runs
# Download the output # Download the output
./artdag.py view <output-hash> -o result.mkv ./artdag.py view <output-cid> -o result.mkv
``` ```

106
artdag.py
View File

@@ -308,7 +308,7 @@ def run(recipe, input_hash, name, wait):
if status["status"] == "completed": if status["status"] == "completed":
click.echo(f"Completed!") click.echo(f"Completed!")
click.echo(f"Output: {status['output_hash']}") click.echo(f"Output: {status['output_cid']}")
else: else:
click.echo(f"Failed: {status.get('error', 'Unknown error')}") click.echo(f"Failed: {status.get('error', 'Unknown error')}")
@@ -327,7 +327,7 @@ def list_runs(limit):
click.echo("-" * 80) click.echo("-" * 80)
for run in runs[:limit]: for run in runs[:limit]:
output = run.get("output_hash", "")[:16] + "..." if run.get("output_hash") else "-" output = run.get("output_cid", "")[:16] + "..." if run.get("output_cid") else "-"
click.echo(f"{run['run_id']} {run['status']:<10} {run['recipe']:<10} {output}") click.echo(f"{run['run_id']} {run['status']:<10} {run['recipe']:<10} {output}")
@@ -353,8 +353,8 @@ def status(run_id):
if run.get("completed_at"): if run.get("completed_at"):
click.echo(f"Completed: {run['completed_at']}") click.echo(f"Completed: {run['completed_at']}")
if run.get("output_hash"): if run.get("output_cid"):
click.echo(f"Output Hash: {run['output_hash']}") click.echo(f"Output Hash: {run['output_cid']}")
if run.get("error"): if run.get("error"):
click.echo(f"Error: {run['error']}") click.echo(f"Error: {run['error']}")
@@ -411,12 +411,12 @@ def delete_run(run_id, force):
@cli.command("delete-cache") @cli.command("delete-cache")
@click.argument("content_hash") @click.argument("cid")
@click.option("--force", "-f", is_flag=True, help="Skip confirmation") @click.option("--force", "-f", is_flag=True, help="Skip confirmation")
def delete_cache(content_hash, force): def delete_cache(cid, force):
"""Delete a cached item. Requires login. """Delete a cached item. Requires login.
CONTENT_HASH: The content hash to delete CID: The content identifier (IPFS CID) to delete
""" """
token_data = load_token() token_data = load_token()
if not token_data.get("access_token"): if not token_data.get("access_token"):
@@ -424,14 +424,14 @@ def delete_cache(content_hash, force):
sys.exit(1) sys.exit(1)
if not force: if not force:
click.echo(f"Content hash: {content_hash}") click.echo(f"CID: {cid}")
if not click.confirm("Delete this cached item?"): if not click.confirm("Delete this cached item?"):
click.echo("Cancelled.") click.echo("Cancelled.")
return return
try: try:
headers = {"Authorization": f"Bearer {token_data['access_token']}"} headers = {"Authorization": f"Bearer {token_data['access_token']}"}
resp = requests.delete(f"{get_server()}/cache/{content_hash}", headers=headers) resp = requests.delete(f"{get_server()}/cache/{cid}", headers=headers)
if resp.status_code == 400: if resp.status_code == 400:
click.echo(f"Cannot delete: {resp.json().get('detail', 'Unknown error')}", err=True) click.echo(f"Cannot delete: {resp.json().get('detail', 'Unknown error')}", err=True)
sys.exit(1) sys.exit(1)
@@ -439,14 +439,14 @@ def delete_cache(content_hash, force):
click.echo("Access denied", err=True) click.echo("Access denied", err=True)
sys.exit(1) sys.exit(1)
if resp.status_code == 404: if resp.status_code == 404:
click.echo(f"Content not found: {content_hash}", err=True) click.echo(f"Content not found: {cid}", err=True)
sys.exit(1) sys.exit(1)
resp.raise_for_status() resp.raise_for_status()
except requests.RequestException as e: except requests.RequestException as e:
click.echo(f"Failed to delete cache item: {e}", err=True) click.echo(f"Failed to delete cache item: {e}", err=True)
sys.exit(1) sys.exit(1)
click.echo(f"Deleted: {content_hash}") click.echo(f"Deleted: {cid}")
@cli.command() @cli.command()
@@ -465,14 +465,14 @@ def cache(limit):
@cli.command() @cli.command()
@click.argument("content_hash") @click.argument("cid")
@click.option("--output", "-o", type=click.Path(), help="Save to file (use - for stdout)") @click.option("--output", "-o", type=click.Path(), help="Save to file (use - for stdout)")
def view(content_hash, output): def view(cid, output):
"""View or download cached content. """View or download cached content.
Use -o - to pipe to stdout, e.g.: artdag view <hash> -o - | mpv - Use -o - to pipe to stdout, e.g.: artdag view <cid> -o - | mpv -
""" """
url = f"{get_server()}/cache/{content_hash}" url = f"{get_server()}/cache/{cid}"
try: try:
if output == "-": if output == "-":
@@ -495,14 +495,14 @@ def view(content_hash, output):
resp.raise_for_status() resp.raise_for_status()
size = resp.headers.get("content-length", "unknown") size = resp.headers.get("content-length", "unknown")
content_type = resp.headers.get("content-type", "unknown") content_type = resp.headers.get("content-type", "unknown")
click.echo(f"Hash: {content_hash}") click.echo(f"CID: {cid}")
click.echo(f"Size: {size} bytes") click.echo(f"Size: {size} bytes")
click.echo(f"Type: {content_type}") click.echo(f"Type: {content_type}")
click.echo(f"URL: {url}") click.echo(f"URL: {url}")
resp.close() resp.close()
except requests.HTTPError as e: except requests.HTTPError as e:
if e.response.status_code == 404: if e.response.status_code == 404:
click.echo(f"Not found: {content_hash}", err=True) click.echo(f"Not found: {cid}", err=True)
else: else:
raise raise
@@ -513,7 +513,7 @@ def import_file(filepath):
"""Import a local file to cache (local server only).""" """Import a local file to cache (local server only)."""
path = str(Path(filepath).resolve()) path = str(Path(filepath).resolve())
result = api_post("/cache/import", params={"path": path}) result = api_post("/cache/import", params={"path": path})
click.echo(f"Imported: {result['content_hash']}") click.echo(f"Imported: {result['cid']}")
@cli.command() @cli.command()
@@ -588,14 +588,14 @@ def publish(run_id, output_name):
result = resp.json() result = resp.json()
click.echo(f"Published to L2!") click.echo(f"Published to L2!")
click.echo(f"Asset: {result['asset']['name']}") click.echo(f"Asset: {result['asset']['name']}")
click.echo(f"Hash: {result['asset']['content_hash']}") click.echo(f"CID: {result['asset']['cid']}")
click.echo(f"Activity: {result['activity']['activity_id']}") click.echo(f"Activity: {result['activity']['activity_id']}")
# ============ Metadata Commands ============ # ============ Metadata Commands ============
@cli.command() @cli.command()
@click.argument("content_hash") @click.argument("cid")
@click.option("--origin", type=click.Choice(["self", "external"]), help="Set origin type") @click.option("--origin", type=click.Choice(["self", "external"]), help="Set origin type")
@click.option("--origin-url", help="Set external origin URL") @click.option("--origin-url", help="Set external origin URL")
@click.option("--origin-note", help="Note about the origin") @click.option("--origin-note", help="Note about the origin")
@@ -607,7 +607,7 @@ def publish(run_id, output_name):
@click.option("--publish", "publish_name", help="Publish to L2 with given asset name") @click.option("--publish", "publish_name", help="Publish to L2 with given asset name")
@click.option("--publish-type", default="image", help="Asset type for publishing (image, video)") @click.option("--publish-type", default="image", help="Asset type for publishing (image, video)")
@click.option("--republish", is_flag=True, help="Re-sync with L2 after metadata changes") @click.option("--republish", is_flag=True, help="Re-sync with L2 after metadata changes")
def meta(content_hash, origin, origin_url, origin_note, description, tags, folder, add_collection, remove_collection, publish_name, publish_type, republish): def meta(cid, origin, origin_url, origin_note, description, tags, folder, add_collection, remove_collection, publish_name, publish_type, republish):
"""View or update metadata for a cached item. """View or update metadata for a cached item.
With no options, displays current metadata. With no options, displays current metadata.
@@ -627,7 +627,7 @@ def meta(content_hash, origin, origin_url, origin_note, description, tags, folde
if publish_name: if publish_name:
try: try:
resp = requests.post( resp = requests.post(
f"{get_server()}/cache/{content_hash}/publish", f"{get_server()}/cache/{cid}/publish",
json={"asset_name": publish_name, "asset_type": publish_type}, json={"asset_name": publish_name, "asset_type": publish_type},
headers=headers headers=headers
) )
@@ -635,7 +635,7 @@ def meta(content_hash, origin, origin_url, origin_note, description, tags, folde
click.echo(f"Error: {resp.json().get('detail', 'Bad request')}", err=True) click.echo(f"Error: {resp.json().get('detail', 'Bad request')}", err=True)
sys.exit(1) sys.exit(1)
if resp.status_code == 404: if resp.status_code == 404:
click.echo(f"Content not found: {content_hash}", err=True) click.echo(f"Content not found: {cid}", err=True)
sys.exit(1) sys.exit(1)
resp.raise_for_status() resp.raise_for_status()
result = resp.json() result = resp.json()
@@ -651,14 +651,14 @@ def meta(content_hash, origin, origin_url, origin_note, description, tags, folde
if republish: if republish:
try: try:
resp = requests.patch( resp = requests.patch(
f"{get_server()}/cache/{content_hash}/republish", f"{get_server()}/cache/{cid}/republish",
headers=headers headers=headers
) )
if resp.status_code == 400: if resp.status_code == 400:
click.echo(f"Error: {resp.json().get('detail', 'Bad request')}", err=True) click.echo(f"Error: {resp.json().get('detail', 'Bad request')}", err=True)
sys.exit(1) sys.exit(1)
if resp.status_code == 404: if resp.status_code == 404:
click.echo(f"Content not found: {content_hash}", err=True) click.echo(f"Content not found: {cid}", err=True)
sys.exit(1) sys.exit(1)
resp.raise_for_status() resp.raise_for_status()
result = resp.json() result = resp.json()
@@ -675,9 +675,9 @@ def meta(content_hash, origin, origin_url, origin_note, description, tags, folde
if not has_updates: if not has_updates:
# GET metadata # GET metadata
try: try:
resp = requests.get(f"{get_server()}/cache/{content_hash}/meta", headers=headers) resp = requests.get(f"{get_server()}/cache/{cid}/meta", headers=headers)
if resp.status_code == 404: if resp.status_code == 404:
click.echo(f"Content not found: {content_hash}", err=True) click.echo(f"Content not found: {cid}", err=True)
sys.exit(1) sys.exit(1)
if resp.status_code == 403: if resp.status_code == 403:
click.echo("Access denied", err=True) click.echo("Access denied", err=True)
@@ -688,7 +688,7 @@ def meta(content_hash, origin, origin_url, origin_note, description, tags, folde
click.echo(f"Failed to get metadata: {e}", err=True) click.echo(f"Failed to get metadata: {e}", err=True)
sys.exit(1) sys.exit(1)
click.echo(f"Content Hash: {content_hash}") click.echo(f"Content Hash: {cid}")
click.echo(f"Uploader: {meta.get('uploader', 'unknown')}") click.echo(f"Uploader: {meta.get('uploader', 'unknown')}")
click.echo(f"Uploaded: {meta.get('uploaded_at', 'unknown')}") click.echo(f"Uploaded: {meta.get('uploaded_at', 'unknown')}")
if meta.get("origin"): if meta.get("origin"):
@@ -715,7 +715,7 @@ def meta(content_hash, origin, origin_url, origin_note, description, tags, folde
if origin or origin_url or origin_note: if origin or origin_url or origin_note:
# Get current origin first # Get current origin first
try: try:
resp = requests.get(f"{get_server()}/cache/{content_hash}/meta", headers=headers) resp = requests.get(f"{get_server()}/cache/{cid}/meta", headers=headers)
resp.raise_for_status() resp.raise_for_status()
current = resp.json() current = resp.json()
current_origin = current.get("origin", {}) current_origin = current.get("origin", {})
@@ -740,7 +740,7 @@ def meta(content_hash, origin, origin_url, origin_note, description, tags, folde
if add_collection or remove_collection: if add_collection or remove_collection:
# Get current collections # Get current collections
try: try:
resp = requests.get(f"{get_server()}/cache/{content_hash}/meta", headers=headers) resp = requests.get(f"{get_server()}/cache/{cid}/meta", headers=headers)
resp.raise_for_status() resp.raise_for_status()
current = resp.json() current = resp.json()
collections = set(current.get("collections", [])) collections = set(current.get("collections", []))
@@ -756,12 +756,12 @@ def meta(content_hash, origin, origin_url, origin_note, description, tags, folde
# PATCH metadata # PATCH metadata
try: try:
resp = requests.patch( resp = requests.patch(
f"{get_server()}/cache/{content_hash}/meta", f"{get_server()}/cache/{cid}/meta",
json=update, json=update,
headers=headers headers=headers
) )
if resp.status_code == 404: if resp.status_code == 404:
click.echo(f"Content not found: {content_hash}", err=True) click.echo(f"Content not found: {cid}", err=True)
sys.exit(1) sys.exit(1)
if resp.status_code == 400: if resp.status_code == 400:
click.echo(f"Error: {resp.json().get('detail', 'Bad request')}", err=True) click.echo(f"Error: {resp.json().get('detail', 'Bad request')}", err=True)
@@ -1088,7 +1088,7 @@ def list_effects(limit):
for effect in effects: for effect in effects:
meta = effect.get("meta", {}) meta = effect.get("meta", {})
click.echo(f" {meta.get('name', 'unknown')} v{meta.get('version', '?')}") click.echo(f" {meta.get('name', 'unknown')} v{meta.get('version', '?')}")
click.echo(f" Hash: {effect['content_hash'][:32]}...") click.echo(f" Hash: {effect['cid'][:32]}...")
click.echo(f" Temporal: {meta.get('temporal', False)}") click.echo(f" Temporal: {meta.get('temporal', False)}")
if meta.get('params'): if meta.get('params'):
click.echo(f" Params: {', '.join(p['name'] for p in meta['params'])}") click.echo(f" Params: {', '.join(p['name'] for p in meta['params'])}")
@@ -1155,12 +1155,12 @@ def show_recipe(recipe_id):
if recipe.get("fixed_inputs"): if recipe.get("fixed_inputs"):
click.echo("\nFixed Inputs:") click.echo("\nFixed Inputs:")
for inp in recipe["fixed_inputs"]: for inp in recipe["fixed_inputs"]:
click.echo(f" - {inp['asset']}: {inp['content_hash'][:16]}...") click.echo(f" - {inp['asset']}: {inp['cid'][:16]}...")
@cli.command("run-recipe") @cli.command("run-recipe")
@click.argument("recipe_id") @click.argument("recipe_id")
@click.option("--input", "-i", "inputs", multiple=True, help="Input as node_id:content_hash") @click.option("--input", "-i", "inputs", multiple=True, help="Input as node_id:cid")
@click.option("--wait", "-w", is_flag=True, help="Wait for completion") @click.option("--wait", "-w", is_flag=True, help="Wait for completion")
def run_recipe(recipe_id, inputs, wait): def run_recipe(recipe_id, inputs, wait):
"""Run a recipe with variable inputs. Requires login. """Run a recipe with variable inputs. Requires login.
@@ -1178,10 +1178,10 @@ def run_recipe(recipe_id, inputs, wait):
input_dict = {} input_dict = {}
for inp in inputs: for inp in inputs:
if ":" not in inp: if ":" not in inp:
click.echo(f"Invalid input format: {inp} (expected node_id:content_hash)", err=True) click.echo(f"Invalid input format: {inp} (expected node_id:cid)", err=True)
sys.exit(1) sys.exit(1)
node_id, content_hash = inp.split(":", 1) node_id, cid = inp.split(":", 1)
input_dict[node_id] = content_hash input_dict[node_id] = cid
# Run # Run
try: try:
@@ -1225,7 +1225,7 @@ def run_recipe(recipe_id, inputs, wait):
continue continue
if run["status"] == "completed": if run["status"] == "completed":
click.echo(f"Completed! Output: {run.get('output_hash', 'N/A')}") click.echo(f"Completed! Output: {run.get('output_cid', 'N/A')}")
break break
elif run["status"] == "failed": elif run["status"] == "failed":
click.echo(f"Failed: {run.get('error', 'Unknown error')}", err=True) click.echo(f"Failed: {run.get('error', 'Unknown error')}", err=True)
@@ -1272,7 +1272,7 @@ def delete_recipe(recipe_id, force):
@cli.command("plan") @cli.command("plan")
@click.argument("recipe_file", type=click.Path(exists=True)) @click.argument("recipe_file", type=click.Path(exists=True))
@click.option("--input", "-i", "inputs", multiple=True, help="Input as name:content_hash") @click.option("--input", "-i", "inputs", multiple=True, help="Input as name:cid")
@click.option("--features", "-f", multiple=True, help="Features to extract (default: beats, energy)") @click.option("--features", "-f", multiple=True, help="Features to extract (default: beats, energy)")
@click.option("--output", "-o", type=click.Path(), help="Save plan JSON to file") @click.option("--output", "-o", type=click.Path(), help="Save plan JSON to file")
def generate_plan(recipe_file, inputs, features, output): def generate_plan(recipe_file, inputs, features, output):
@@ -1297,10 +1297,10 @@ def generate_plan(recipe_file, inputs, features, output):
input_hashes = {} input_hashes = {}
for inp in inputs: for inp in inputs:
if ":" not in inp: if ":" not in inp:
click.echo(f"Invalid input format: {inp} (expected name:content_hash)", err=True) click.echo(f"Invalid input format: {inp} (expected name:cid)", err=True)
sys.exit(1) sys.exit(1)
name, content_hash = inp.split(":", 1) name, cid = inp.split(":", 1)
input_hashes[name] = content_hash input_hashes[name] = cid
# Build request # Build request
request_data = { request_data = {
@@ -1401,7 +1401,7 @@ def execute_plan(plan_file, wait):
@cli.command("run-v2") @cli.command("run-v2")
@click.argument("recipe_file", type=click.Path(exists=True)) @click.argument("recipe_file", type=click.Path(exists=True))
@click.option("--input", "-i", "inputs", multiple=True, help="Input as name:content_hash") @click.option("--input", "-i", "inputs", multiple=True, help="Input as name:cid")
@click.option("--features", "-f", multiple=True, help="Features to extract (default: beats, energy)") @click.option("--features", "-f", multiple=True, help="Features to extract (default: beats, energy)")
@click.option("--wait", "-w", is_flag=True, help="Wait for completion") @click.option("--wait", "-w", is_flag=True, help="Wait for completion")
def run_recipe_v2(recipe_file, inputs, features, wait): def run_recipe_v2(recipe_file, inputs, features, wait):
@@ -1433,10 +1433,10 @@ def run_recipe_v2(recipe_file, inputs, features, wait):
input_hashes = {} input_hashes = {}
for inp in inputs: for inp in inputs:
if ":" not in inp: if ":" not in inp:
click.echo(f"Invalid input format: {inp} (expected name:content_hash)", err=True) click.echo(f"Invalid input format: {inp} (expected name:cid)", err=True)
sys.exit(1) sys.exit(1)
name, content_hash = inp.split(":", 1) name, cid = inp.split(":", 1)
input_hashes[name] = content_hash input_hashes[name] = cid
# Build request # Build request
request_data = { request_data = {
@@ -1473,8 +1473,8 @@ def run_recipe_v2(recipe_file, inputs, features, wait):
click.echo(f"Run ID: {run_id}") click.echo(f"Run ID: {run_id}")
click.echo(f"Status: {result['status']}") click.echo(f"Status: {result['status']}")
if result.get("output_hash"): if result.get("output_cid"):
click.echo(f"Output: {result['output_hash']}") click.echo(f"Output: {result['output_cid']}")
if result.get("output_ipfs_cid"): if result.get("output_ipfs_cid"):
click.echo(f"IPFS CID: {result['output_ipfs_cid']}") click.echo(f"IPFS CID: {result['output_ipfs_cid']}")
return return
@@ -1505,8 +1505,8 @@ def _wait_for_v2_run(token_data: dict, run_id: str):
if status == "completed": if status == "completed":
click.echo(f"\nCompleted!") click.echo(f"\nCompleted!")
if run.get("output_hash"): if run.get("output_cid"):
click.echo(f"Output: {run['output_hash']}") click.echo(f"Output: {run['output_cid']}")
if run.get("output_ipfs_cid"): if run.get("output_ipfs_cid"):
click.echo(f"IPFS CID: {run['output_ipfs_cid']}") click.echo(f"IPFS CID: {run['output_ipfs_cid']}")
if run.get("cached"): if run.get("cached"):
@@ -1555,8 +1555,8 @@ def run_status_v2(run_id):
click.echo(f"Recipe: {run['recipe']}") click.echo(f"Recipe: {run['recipe']}")
if run.get("plan_id"): if run.get("plan_id"):
click.echo(f"Plan ID: {run['plan_id'][:16]}...") click.echo(f"Plan ID: {run['plan_id'][:16]}...")
if run.get("output_hash"): if run.get("output_cid"):
click.echo(f"Output: {run['output_hash']}") click.echo(f"Output: {run['output_cid']}")
if run.get("output_ipfs_cid"): if run.get("output_ipfs_cid"):
click.echo(f"IPFS CID: {run['output_ipfs_cid']}") click.echo(f"IPFS CID: {run['output_ipfs_cid']}")
if run.get("cached") is not None: if run.get("cached") is not None: