diff --git a/.icons/1claw.svg b/.icons/1claw.svg
new file mode 100644
index 000000000..f6854deae
--- /dev/null
+++ b/.icons/1claw.svg
@@ -0,0 +1,9 @@
+
diff --git a/registry/kmjones1979/.images/avatar.png b/registry/kmjones1979/.images/avatar.png
new file mode 100644
index 000000000..dd7f47e62
Binary files /dev/null and b/registry/kmjones1979/.images/avatar.png differ
diff --git a/registry/kmjones1979/README.md b/registry/kmjones1979/README.md
new file mode 100644
index 000000000..5d0510782
--- /dev/null
+++ b/registry/kmjones1979/README.md
@@ -0,0 +1,11 @@
+---
+display_name: Kevin Jones
+bio: Developer building modules for Coder workspaces
+avatar: ./.images/avatar.png
+github: kmjones1979
+status: community
+---
+
+# Kevin Jones
+
+Developer building modules for Coder workspaces.
diff --git a/registry/kmjones1979/modules/oneclaw/README.md b/registry/kmjones1979/modules/oneclaw/README.md
new file mode 100644
index 000000000..559040181
--- /dev/null
+++ b/registry/kmjones1979/modules/oneclaw/README.md
@@ -0,0 +1,74 @@
+---
+display_name: 1Claw
+description: Vault-backed secrets and MCP server wiring for 1Claw in Coder workspaces
+icon: ../../../../.icons/1claw.svg
+verified: false
+tags: [secrets, mcp, ai]
+---
+
+# 1Claw
+
+Give every Coder workspace scoped access to [1Claw](https://1claw.xyz) so AI coding agents can read secrets from an encrypted vault instead of hardcoded credentials. The module merges a `streamable-http` MCP server entry into Cursor and Claude Code config files without overwriting other MCP servers.
+
+Upstream source: [github.com/1clawAI/1claw-coder-workspace-module](https://github.com/1clawAI/1claw-coder-workspace-module).
+
+## Usage
+
+### Bootstrap mode (recommended)
+
+Creates a vault, agent, and access policy on the first workspace boot using a human `1ck_` API key, then caches credentials in `~/.1claw/bootstrap.json` for subsequent starts.
+
+```tf
+module "oneclaw" {
+ source = "registry.coder.com/kmjones1979/oneclaw/coder"
+ version = "1.0.0"
+ agent_id = coder_agent.main.id
+ human_api_key = var.oneclaw_human_key
+}
+```
+
+#### Post-bootstrap cleanup (recommended)
+
+The `1ck_` human key is a privileged credential that can create and destroy vaults in your 1Claw account. It is only needed the first time the workspace boots. After the initial bootstrap succeeds:
+
+1. Clear the variable in your Terraform:
+
+ ```tf
+ module "oneclaw" {
+ source = "registry.coder.com/kmjones1979/oneclaw/coder"
+ version = "1.0.0"
+ agent_id = coder_agent.main.id
+ human_api_key = "" # scrubbed after first bootstrap
+ }
+ ```
+
+2. Re-apply the template. On the next workspace start, the script loads credentials from `~/.1claw/bootstrap.json` and no longer references the human key. The workspace continues to work with the scoped `ocv_` agent key only.
+
+### Manual mode
+
+Pre-provision the vault and agent out-of-band and pass only the scoped `ocv_` agent key. Recommended for production and for threat models that include untrusted code running inside the workspace.
+
+```tf
+module "oneclaw" {
+ source = "registry.coder.com/kmjones1979/oneclaw/coder"
+ version = "1.0.0"
+ agent_id = coder_agent.main.id
+ vault_id = var.oneclaw_vault_id
+ api_token = var.oneclaw_agent_key
+}
+```
+
+## Security notes
+
+The module is written so that the `1ck_` human bootstrap key leaves no persistent trace in the workspace:
+
+- The `ocv_` agent key exposed to the AI is scoped to a single vault and a single secret-path policy. That defines the blast radius of anything the AI does.
+- The `1ck_` human key is injected into the bootstrap script as a sensitive `coder_env` variable (`_ONECLAW_HUMAN_API_KEY`), **never** templated into the script body. Because of this, it does **not** appear in `/tmp/coder-agent.log` (which records the rendered script) or in the Terraform state file's `coder_script` resource. The rendered script only contains the literal reference `HUMAN_KEY="${_ONECLAW_HUMAN_API_KEY:-}"`.
+- During bootstrap, the human key is sent to the 1Claw API via `curl --data-binary @-` from stdin, so it never appears in process argv (`ps aux` / `/proc//cmdline`).
+- The key is scrubbed from shell variables (`unset HUMAN_KEY` / `unset _ONECLAW_HUMAN_API_KEY`) immediately after authentication, so downstream processes started by the script do not inherit it.
+- The key is **never** written to `~/.1claw/bootstrap.json`, `~/.cursor/mcp.json`, `~/.config/claude/mcp.json`, or any other on-disk file. Only the scoped `ocv_` agent key and the vault id are persisted.
+- For highest assurance, use manual mode with a pre-provisioned `ocv_` key so the `1ck_` key never reaches the workspace at all.
+
+## Requirements
+
+Bootstrap mode runs inside the workspace and requires `curl` and `python3` in the container image.
diff --git a/registry/kmjones1979/modules/oneclaw/main.test.ts b/registry/kmjones1979/modules/oneclaw/main.test.ts
new file mode 100644
index 000000000..418b21de6
--- /dev/null
+++ b/registry/kmjones1979/modules/oneclaw/main.test.ts
@@ -0,0 +1,84 @@
+import { describe, expect, it } from "bun:test";
+import {
+ runTerraformApply,
+ runTerraformInit,
+ testRequiredVariables,
+ findResourceInstance,
+} from "~test";
+
+describe("oneclaw", async () => {
+ await runTerraformInit(import.meta.dir);
+
+ testRequiredVariables(import.meta.dir, {
+ agent_id: "test-agent",
+ });
+
+ it("manual mode sets env vars and run script", async () => {
+ const state = await runTerraformApply(import.meta.dir, {
+ agent_id: "test-agent",
+ vault_id: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
+ api_token: "ocv_testtoken",
+ });
+
+ const vaultEnv = findResourceInstance(state, "coder_env", "vault_id");
+ expect(vaultEnv.name).toBe("ONECLAW_VAULT_ID");
+
+ const apiKeyEnv = findResourceInstance(state, "coder_env", "agent_api_key");
+ expect(apiKeyEnv.name).toBe("ONECLAW_AGENT_API_KEY");
+
+ const baseUrlEnv = findResourceInstance(state, "coder_env", "base_url");
+ expect(baseUrlEnv.name).toBe("ONECLAW_BASE_URL");
+ expect(baseUrlEnv.value).toBe("https://api.1claw.xyz");
+
+ const runScript = findResourceInstance(state, "coder_script", "run");
+ expect(runScript.display_name).toBe("1Claw");
+ expect(runScript.start_blocks_login).toBe(false);
+
+ const provisions = state.resources.filter(
+ (r) => r.type === "null_resource" && r.name === "provision",
+ );
+ expect(provisions.length).toBe(0);
+ });
+
+ it("bootstrap mode enables blocking run script and injects human key via coder_env", async () => {
+ const state = await runTerraformApply(import.meta.dir, {
+ agent_id: "test-agent",
+ human_api_key: "1ck_test_human_key",
+ });
+
+ const runScript = findResourceInstance(state, "coder_script", "run");
+ expect(runScript.display_name).toBe("1Claw");
+ expect(runScript.start_blocks_login).toBe(true);
+
+ // The human key is delivered via coder_env (sensitive), NOT baked into the
+ // script body, so it never lands in the Coder agent's script log.
+ const humanKeyEnv = findResourceInstance(
+ state,
+ "coder_env",
+ "human_api_key",
+ );
+ expect(humanKeyEnv.name).toBe("_ONECLAW_HUMAN_API_KEY");
+
+ // And the actual key value must not appear anywhere in the rendered script text.
+ expect(runScript.script).not.toContain("1ck_test_human_key");
+ // The script must reference the env var, not a literal value.
+ expect(runScript.script).toContain("_ONECLAW_HUMAN_API_KEY");
+
+ const provisions = state.resources.filter(
+ (r) => r.type === "null_resource" && r.name === "provision",
+ );
+ expect(provisions.length).toBe(0);
+ });
+
+ it("custom base_url is reflected in env", async () => {
+ const state = await runTerraformApply(import.meta.dir, {
+ agent_id: "test-agent",
+ vault_id: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
+ api_token: "ocv_testtoken",
+ base_url: "https://api.example.com",
+ });
+
+ const baseUrlEnv = findResourceInstance(state, "coder_env", "base_url");
+ expect(baseUrlEnv.value).toBe("https://api.example.com");
+ });
+});
diff --git a/registry/kmjones1979/modules/oneclaw/main.tf b/registry/kmjones1979/modules/oneclaw/main.tf
new file mode 100644
index 000000000..b85bb2e1f
--- /dev/null
+++ b/registry/kmjones1979/modules/oneclaw/main.tf
@@ -0,0 +1,223 @@
+terraform {
+ required_version = ">= 1.4"
+
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ version = ">= 2.12"
+ }
+ }
+}
+
+variable "agent_id" {
+ type = string
+ description = "The ID of a Coder agent."
+}
+
+variable "vault_id" {
+ type = string
+ description = "The 1Claw vault ID to scope MCP access to. Optional when using bootstrap mode (human_api_key)."
+ default = ""
+
+ validation {
+ condition = var.vault_id == "" || can(regex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", var.vault_id))
+ error_message = "vault_id must be a valid UUID or empty (for bootstrap mode)."
+ }
+}
+
+variable "api_token" {
+ type = string
+ sensitive = true
+ description = "1Claw agent API key (starts with ocv_). Optional when using bootstrap mode (human_api_key)."
+ default = ""
+}
+
+variable "human_api_key" {
+ type = string
+ sensitive = true
+ default = ""
+ description = "One-time human 1ck_ API key for auto-provisioning. On first workspace start, creates a vault, agent, and policy automatically. Credentials are cached in ~/.1claw/bootstrap.json for subsequent starts."
+}
+
+variable "bootstrap_vault_name" {
+ type = string
+ default = "coder-workspace"
+ description = "Name for the auto-created vault (only used when vault_id is not provided and human_api_key is set)."
+}
+
+variable "bootstrap_agent_name" {
+ type = string
+ default = ""
+ description = "Name for the auto-created agent. Defaults to coder-."
+}
+
+variable "bootstrap_policy_path" {
+ type = string
+ default = "**"
+ description = "Secret path pattern for the auto-created policy (glob). Defaults to all secrets."
+}
+
+variable "agent_id_1claw" {
+ type = string
+ description = "Optional 1Claw agent UUID. When omitted, the MCP server resolves the agent from the API key prefix."
+ default = ""
+}
+
+variable "mcp_host" {
+ type = string
+ description = "Base URL of the 1Claw MCP server."
+ default = "https://mcp.1claw.xyz/mcp"
+
+ validation {
+ condition = can(regex("^https?://", var.mcp_host))
+ error_message = "mcp_host must start with http:// or https://."
+ }
+}
+
+variable "base_url" {
+ type = string
+ description = "Base URL of the 1Claw Vault API (used by ONECLAW_BASE_URL env var)."
+ default = "https://api.1claw.xyz"
+
+ validation {
+ condition = can(regex("^https?://", var.base_url))
+ error_message = "base_url must start with http:// or https://."
+ }
+}
+
+variable "install_cursor_config" {
+ type = bool
+ description = "Whether to write MCP config to the Cursor IDE config path."
+ default = true
+}
+
+variable "install_claude_config" {
+ type = bool
+ description = "Whether to write MCP config to the Claude Code config path."
+ default = true
+}
+
+variable "cursor_config_path" {
+ type = string
+ description = "Path where the Cursor MCP config file is written."
+ default = "$HOME/.cursor/mcp.json"
+}
+
+variable "claude_config_path" {
+ type = string
+ description = "Path where the Claude Code MCP config file is written."
+ default = "$HOME/.config/claude/mcp.json"
+}
+
+variable "icon" {
+ type = string
+ description = "Icon to display for the setup script in the Coder UI."
+ default = "/icon/vault.svg"
+}
+
+variable "order" {
+ type = number
+ description = "The order determines the position of app in the UI presentation."
+ default = null
+}
+
+data "coder_workspace" "me" {}
+
+data "coder_workspace_owner" "me" {}
+
+locals {
+ bootstrap_mode = var.human_api_key != ""
+ bootstrap_agent_name = (
+ var.bootstrap_agent_name != "" ? var.bootstrap_agent_name :
+ "coder-${data.coder_workspace.me.name}"
+ )
+}
+
+resource "coder_env" "vault_id" {
+ count = var.vault_id != "" ? 1 : 0
+ agent_id = var.agent_id
+ name = "ONECLAW_VAULT_ID"
+ value = var.vault_id
+}
+
+resource "coder_env" "agent_api_key" {
+ count = var.api_token != "" ? 1 : 0
+ agent_id = var.agent_id
+ name = "ONECLAW_AGENT_API_KEY"
+ value = var.api_token
+}
+
+resource "coder_env" "oneclaw_agent_id" {
+ count = var.agent_id_1claw != "" ? 1 : 0
+ agent_id = var.agent_id
+ name = "ONECLAW_AGENT_ID"
+ value = var.agent_id_1claw
+}
+
+resource "coder_env" "base_url" {
+ agent_id = var.agent_id
+ name = "ONECLAW_BASE_URL"
+ value = var.base_url
+}
+
+# Sensitive values are passed via coder_env (not templated into the script body)
+# so they don't appear in the Coder agent's script log. The agent log is 0600 on
+# the coder user, but that's the same user the AI runs as in most images, so we
+# want to avoid any on-disk copy of the 1ck_ key in the workspace.
+resource "coder_env" "human_api_key" {
+ count = local.bootstrap_mode ? 1 : 0
+ agent_id = var.agent_id
+ name = "_ONECLAW_HUMAN_API_KEY"
+ value = var.human_api_key
+}
+
+resource "coder_script" "run" {
+ agent_id = var.agent_id
+ display_name = "1Claw"
+ icon = var.icon
+ run_on_start = true
+ start_blocks_login = local.bootstrap_mode
+
+ script = templatefile("${path.module}/scripts/run.sh", {
+ BOOTSTRAP_MODE = local.bootstrap_mode ? "true" : "false"
+ BASE_URL = var.base_url
+ VAULT_ID_INPUT = var.vault_id
+ VAULT_NAME = var.bootstrap_vault_name
+ AGENT_NAME = local.bootstrap_agent_name
+ POLICY_PATH = var.bootstrap_policy_path
+ STATE_DIR = "$HOME/.1claw"
+ MCP_HOST = var.mcp_host
+ INSTALL_CURSOR_CONFIG = var.install_cursor_config ? "true" : "false"
+ INSTALL_CLAUDE_CONFIG = var.install_claude_config ? "true" : "false"
+ CURSOR_CONFIG_PATH = var.cursor_config_path
+ CLAUDE_CONFIG_PATH = var.claude_config_path
+ })
+}
+
+output "mcp_config_path" {
+ description = "Primary MCP config file path (Cursor). Use this to reference the config from downstream resources."
+ value = var.cursor_config_path
+}
+
+output "claude_config_path" {
+ description = "Claude Code MCP config file path."
+ value = var.install_claude_config ? var.claude_config_path : ""
+}
+
+output "vault_id" {
+ description = "The 1Claw vault ID configured for this workspace (manual mode only; bootstrap mode resolves the vault ID inside the workspace)."
+ value = var.vault_id
+ sensitive = true
+}
+
+output "agent_id_1claw" {
+ description = "The 1Claw agent UUID, if provided via variable."
+ value = var.agent_id_1claw
+ sensitive = true
+}
+
+output "provisioning_mode" {
+ description = "Which provisioning mode is active: bootstrap or manual."
+ value = local.bootstrap_mode ? "bootstrap" : "manual"
+ sensitive = true
+}
diff --git a/registry/kmjones1979/modules/oneclaw/main.tftest.hcl b/registry/kmjones1979/modules/oneclaw/main.tftest.hcl
new file mode 100644
index 000000000..e792e0d30
--- /dev/null
+++ b/registry/kmjones1979/modules/oneclaw/main.tftest.hcl
@@ -0,0 +1,99 @@
+run "manual_mode" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent-manual"
+ vault_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
+ api_token = "ocv_testtoken"
+ }
+
+ assert {
+ condition = length(coder_env.vault_id) == 1
+ error_message = "ONECLAW_VAULT_ID should be set in manual mode"
+ }
+
+ assert {
+ condition = length(coder_env.agent_api_key) == 1
+ error_message = "ONECLAW_AGENT_API_KEY should be set in manual mode"
+ }
+
+ assert {
+ condition = coder_script.run.start_blocks_login == false
+ error_message = "Manual mode should not block login"
+ }
+
+ assert {
+ condition = output.provisioning_mode == "manual"
+ error_message = "provisioning_mode should be 'manual' when no human_api_key is set"
+ }
+}
+
+run "bootstrap_mode" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent-bootstrap"
+ human_api_key = "1ck_test_human_key"
+ }
+
+ assert {
+ condition = coder_script.run.start_blocks_login == true
+ error_message = "Bootstrap mode should block login while provisioning"
+ }
+
+ assert {
+ condition = length(coder_env.vault_id) == 0
+ error_message = "No vault_id env var in pure bootstrap mode (resolved inside workspace)"
+ }
+
+ assert {
+ condition = length(coder_env.agent_api_key) == 0
+ error_message = "No agent_api_key env var in pure bootstrap mode (resolved inside workspace)"
+ }
+
+ assert {
+ condition = length(coder_env.human_api_key) == 1
+ error_message = "Bootstrap mode should inject _ONECLAW_HUMAN_API_KEY via coder_env"
+ }
+
+ assert {
+ condition = coder_env.human_api_key[0].name == "_ONECLAW_HUMAN_API_KEY"
+ error_message = "Human key env var should be named _ONECLAW_HUMAN_API_KEY"
+ }
+
+ assert {
+ condition = output.provisioning_mode == "bootstrap"
+ error_message = "provisioning_mode should be 'bootstrap' when human_api_key is set"
+ }
+}
+
+run "manual_mode_no_human_key_env" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent-manual-noenv"
+ vault_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
+ api_token = "ocv_testtoken"
+ }
+
+ assert {
+ condition = length(coder_env.human_api_key) == 0
+ error_message = "Manual mode should not inject _ONECLAW_HUMAN_API_KEY"
+ }
+}
+
+run "custom_base_url" {
+ command = plan
+
+ variables {
+ agent_id = "test-agent-mcp"
+ vault_id = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
+ api_token = "ocv_testtoken"
+ base_url = "https://api.example.com"
+ }
+
+ assert {
+ condition = coder_env.base_url.value == "https://api.example.com"
+ error_message = "ONECLAW_BASE_URL should match base_url"
+ }
+}
diff --git a/registry/kmjones1979/modules/oneclaw/scripts/run.sh b/registry/kmjones1979/modules/oneclaw/scripts/run.sh
new file mode 100755
index 000000000..1942c9456
--- /dev/null
+++ b/registry/kmjones1979/modules/oneclaw/scripts/run.sh
@@ -0,0 +1,246 @@
+#!/bin/bash
+set -euo pipefail
+
+LOG_PREFIX="[1claw]"
+log() { echo "$LOG_PREFIX $*"; }
+die() {
+ log "ERROR: $*" >&2
+ exit 1
+}
+
+BOOTSTRAP_MODE="${BOOTSTRAP_MODE}"
+API_URL="${BASE_URL}"
+VAULT_ID_INPUT="${VAULT_ID_INPUT}"
+VAULT_NAME_IN="${VAULT_NAME}"
+AGENT_NAME_IN="${AGENT_NAME}"
+POLICY_PATH_IN="${POLICY_PATH}"
+STATE_DIR=$(eval echo "${STATE_DIR}")
+STATE_FILE="$STATE_DIR/bootstrap.json"
+
+# Sensitive values come from env vars injected by coder_env (sensitive = true),
+# NOT from templatefile() substitutions, so they do not appear in the Coder
+# agent's rendered-script log (/tmp/coder-agent.log).
+HUMAN_KEY="$${_ONECLAW_HUMAN_API_KEY:-}"
+API_TOKEN="$${ONECLAW_AGENT_API_KEY:-}"
+VAULT_ID="$${ONECLAW_VAULT_ID:-}"
+
+json_get() {
+ python3 -c "import json,sys; print(json.load(sys.stdin)$1)"
+}
+
+api_call() {
+ local method="$1" path="$2" token="$3" body="$${4:-}"
+ local response http_code body_out
+ # Pass bearer token via stdin config to keep it out of process argv.
+ # Body (if any) is piped on stdin as --data-binary.
+ local curl_cfg
+ curl_cfg=$(mktemp)
+ printf -- 'header = "Authorization: Bearer %s"\n' "$token" > "$curl_cfg"
+ if [ -n "$body" ]; then
+ response=$(printf '%s' "$body" | curl -s -w "\n%%{http_code}" \
+ -K "$curl_cfg" \
+ -H "Content-Type: application/json" \
+ --data-binary @- \
+ -X "$method" "$API_URL$path" 2>&1)
+ else
+ response=$(curl -s -w "\n%%{http_code}" \
+ -K "$curl_cfg" \
+ -H "Content-Type: application/json" \
+ -X "$method" "$API_URL$path" 2>&1)
+ fi
+ local rc=$?
+ rm -f "$curl_cfg"
+ if [ $rc -ne 0 ]; then
+ log "API call failed: $method $path"
+ return 1
+ fi
+ http_code=$(echo "$response" | tail -1)
+ body_out=$(echo "$response" | sed '$d')
+ if [ "$${http_code:0:1}" != "2" ]; then
+ log "API error: $method $path returned HTTP $http_code"
+ log "Response: $body_out"
+ return 1
+ fi
+ echo "$body_out"
+}
+
+bootstrap() {
+ if [ -f "$STATE_FILE" ]; then
+ log "Bootstrap state found at $STATE_FILE — skipping provisioning"
+ return 0
+ fi
+
+ [ -n "$HUMAN_KEY" ] || die "human_api_key is required for bootstrap mode"
+
+ log "Authenticating with 1Claw API..."
+ local auth_response auth_http auth_body jwt
+ # Pipe the body via stdin so the 1ck_ key never appears in process argv (ps/proc/cmdline).
+ auth_response=$(printf '{"api_key": "%s"}' "$HUMAN_KEY" | curl -s -w "\n%%{http_code}" \
+ -H "Content-Type: application/json" \
+ --data-binary @- \
+ "$API_URL/v1/auth/api-key-token" 2>&1) || die "Failed to authenticate with human API key"
+
+ # Key is no longer needed; scrub from process memory before any other work.
+ HUMAN_KEY=""
+ unset HUMAN_KEY
+
+ auth_http=$(echo "$auth_response" | tail -1)
+ auth_body=$(echo "$auth_response" | sed '$d')
+ if [ "$${auth_http:0:1}" != "2" ]; then
+ die "Authentication failed (HTTP $auth_http)"
+ fi
+ jwt=$(echo "$auth_body" | json_get "['access_token']")
+ auth_body=""
+ auth_response=""
+ log "Authenticated successfully"
+
+ local vault="$VAULT_ID_INPUT"
+ if [ -n "$vault" ]; then
+ log "Using provided vault: $vault"
+ else
+ log "Creating vault '$VAULT_NAME_IN'..."
+ local vault_response
+ vault_response=$(api_call POST "/v1/vaults" "$jwt" \
+ "{\"name\": \"$VAULT_NAME_IN\"}") || {
+ log "Vault creation failed — looking for existing vault named '$VAULT_NAME_IN'"
+ local list_response
+ list_response=$(api_call GET "/v1/vaults" "$jwt") || die "Failed to list vaults"
+ vault=$(echo "$list_response" | python3 -c "
+import json, sys
+for v in json.load(sys.stdin).get('vaults', []):
+ if v['name'] == '$VAULT_NAME_IN':
+ print(v['id']); sys.exit(0)
+sys.exit(1)
+") || die "Could not find existing vault named '$VAULT_NAME_IN'"
+ log "Found existing vault: $vault"
+ }
+ if [ -z "$vault" ]; then
+ vault=$(echo "$vault_response" | json_get "['id']")
+ log "Created vault: $vault"
+ fi
+ fi
+
+ log "Creating agent '$AGENT_NAME_IN'..."
+ local agent_response agent_id agent_key
+ agent_response=$(api_call POST "/v1/agents" "$jwt" \
+ "{\"name\": \"$AGENT_NAME_IN\", \"vault_ids\": [\"$vault\"]}") || die "Failed to create agent"
+
+ agent_id=$(echo "$agent_response" | json_get "['agent']['id']")
+ agent_key=$(echo "$agent_response" | json_get "['api_key']")
+ if [ -z "$agent_key" ] || [ "$agent_key" = "None" ]; then
+ die "Agent created but no API key returned"
+ fi
+ log "Created agent: $agent_id"
+
+ log "Creating access policy (path: $POLICY_PATH_IN)..."
+ api_call POST "/v1/vaults/$vault/policies" "$jwt" \
+ "{\"secret_path_pattern\": \"$POLICY_PATH_IN\", \"principal_type\": \"agent\", \"principal_id\": \"$agent_id\", \"permissions\": [\"read\", \"write\"]}" \
+ > /dev/null || die "Failed to create policy"
+ log "Policy created"
+
+ mkdir -p "$STATE_DIR"
+ python3 - "$STATE_FILE" "$vault" "$agent_id" "$agent_key" << 'PYEOF'
+import json, sys
+state = {
+ "vault_id": sys.argv[2],
+ "agent_id": sys.argv[3],
+ "agent_api_key": sys.argv[4]
+}
+with open(sys.argv[1], "w") as f:
+ json.dump(state, f, indent=2)
+PYEOF
+ chmod 600 "$STATE_FILE"
+
+ jwt=""
+ unset jwt
+
+ log "Bootstrap complete — credentials saved to $STATE_FILE"
+ log " Vault: $vault"
+ log " Agent: $agent_id"
+}
+
+write_mcp_config() {
+ local target_path="$1" label="$2" tmp_file="$3"
+ target_path=$(eval echo "$target_path")
+ local target_dir
+ target_dir=$(dirname "$target_path")
+ [ -d "$target_dir" ] || mkdir -p "$target_dir"
+
+ if [ -f "$target_path" ]; then
+ log "Merging 1Claw MCP server into existing $label config at $target_path"
+ python3 - "$target_path" "$tmp_file" << 'PYEOF'
+import json, sys
+target_path = sys.argv[1]
+new_config_path = sys.argv[2]
+try:
+ with open(target_path) as f:
+ existing = json.load(f)
+except (json.JSONDecodeError, FileNotFoundError):
+ existing = {}
+with open(new_config_path) as f:
+ new_server = json.load(f)
+existing.setdefault("mcpServers", {}).update(new_server.get("mcpServers", {}))
+with open(target_path, "w") as f:
+ json.dump(existing, f, indent=2)
+PYEOF
+ else
+ log "Writing $label MCP config to $target_path"
+ cp "$tmp_file" "$target_path"
+ fi
+
+ chmod 600 "$target_path"
+ log "$label MCP config ready at $target_path"
+}
+
+if [ "$BOOTSTRAP_MODE" = "true" ]; then
+ bootstrap
+fi
+
+# Scrub the human bootstrap key from both the local var and the inherited env,
+# so downstream processes (shells, AI agents) cannot read it from this script's
+# /proc//environ or from their own inherited environment.
+HUMAN_KEY=""
+unset HUMAN_KEY
+unset _ONECLAW_HUMAN_API_KEY
+
+# Bootstrap runs first and writes creds to the state file; load them now.
+if [ -z "$API_TOKEN" ] && [ -f "$STATE_FILE" ]; then
+ log "Loading credentials from bootstrap state"
+ API_TOKEN=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['agent_api_key'])")
+ VAULT_ID=$(python3 -c "import json; print(json.load(open('$STATE_FILE'))['vault_id'])")
+fi
+
+if [ -z "$API_TOKEN" ] || [ -z "$VAULT_ID" ]; then
+ log "WARNING: No API token or vault ID available — skipping MCP config"
+ log "Provide api_token + vault_id, or set human_api_key/master_api_key"
+ exit 0
+fi
+
+MCP_CONFIG_TMP=$(mktemp)
+trap 'rm -f "$MCP_CONFIG_TMP"' EXIT
+
+python3 - "$API_TOKEN" "$VAULT_ID" "${MCP_HOST}" > "$MCP_CONFIG_TMP" << 'PYEOF'
+import json, sys
+config = {
+ "mcpServers": {
+ "1claw": {
+ "url": sys.argv[3],
+ "headers": {
+ "Authorization": "Bearer " + sys.argv[1],
+ "X-Vault-ID": sys.argv[2]
+ }
+ }
+ }
+}
+print(json.dumps(config, indent=2))
+PYEOF
+
+if [ "${INSTALL_CURSOR_CONFIG}" = "true" ]; then
+ write_mcp_config "${CURSOR_CONFIG_PATH}" "Cursor" "$MCP_CONFIG_TMP"
+fi
+
+if [ "${INSTALL_CLAUDE_CONFIG}" = "true" ]; then
+ write_mcp_config "${CLAUDE_CONFIG_PATH}" "Claude Code" "$MCP_CONFIG_TMP"
+fi
+
+log "1Claw setup complete"