Skip to main content
HUMΛN
Developer
Developer

Writing a JXA catalog entry and publishing it to the HUMΛN marketplace

HUMΛN Team··8 min·Technical (Developers)

The @human/connector-applescript catalog is the contract between HUMΛN and macOS apps. Built-in entries (builtin.*) cover Mail, OmniFocus, Calendar, and Reminders. Community entries (community.*) extend it for any scriptable app — Things 3, BBEdit, Reeder, Logic Pro, whatever your org uses.

This is the complete path from idea to marketplace.

Step 1: Understand the catalog shape

Every catalog entry is a JSON object:

{
  "id": "community.acme.things3.project.add",
  "tier": 2,
  "app": "Things 3",
  "description": "Add a project to Things 3",
  "jxa_template": "var things = Application('Things 3'); things.projects.push(things.Project({ name: '{{name}}', notes: '{{notes}}' })); 'ok'",
  "params_schema": {
    "type": "object",
    "properties": {
      "name": { "type": "string", "description": "Project name" },
      "notes": { "type": "string", "description": "Optional project notes", "default": "" }
    },
    "required": ["name"]
  }
}

The fields:

Field Rules
id community.<your-org>.<path> — must match [a-z][a-z0-9._-]+, never builtin.*
tier 0–3 (see tier model below — choose carefully, the intake agent validates)
app Display name of the target macOS app
description One sentence, plain English — shown in approval UI
jxa_template JXA source with {{param}} placeholders — validated as non-empty
params_schema JSON Schema for template params — validated as a JSON object

Step 2: Choose the right tier

Tier assignment is the most important decision you make. The intake agent validates it and flags mismatches:

Tier What belongs here Examples
0 Pure reads, no side effects, no app state change jxa.ping, reading preferences
1 Non-destructive reads that open app connections Read calendar events, list reminders
2 Mutations that create or change data Add a task, draft an email, create a project
3 Irreversible actions Send an email, delete a project, submit a form

Anti-patterns:

  • Tier 0 for a command that writes anything — the intake agent will flag it
  • Tier 3 for a create (not irreversible) — overly conservative but wastes approval UX
  • Tier 2 for send — the human must see a red "cannot undo" card for sends

Step 3: Write the JXA

JXA runs in the osascript -l JavaScript runtime. The connector pre-renders {{param}} placeholders before passing the source to osascript. Always return a string — stdout is what the muscle receives.

// Things 3 — add project
var things = Application('Things 3');
var proj = things.Project({
  name: '{{name}}',
  notes: '{{notes}}'
});
things.projects.push(proj);
JSON.stringify({ ok: true, name: '{{name}}' });

Safety rules enforced by the intake agent:

  • .keystroke( is blocked (PLATFORM:JXA_SAFETY_KEYSTROKE) — keystroke injection is never approved
  • doScript is blocked (PLATFORM:JXA_SAFETY_DOSCRIPT) — arbitrary AppleScript execution bypasses the catalog tier model
  • The template must be static — no dynamic code generation in jxa_template

Test it locally before publishing:

# Create a temp file and run it
cat > /tmp/test-things.js << 'EOF'
var things = Application('Things 3');
var proj = things.Project({ name: 'Test from HUMΛN', notes: 'Marketplace submission test' });
things.projects.push(proj);
JSON.stringify({ ok: true });
EOF

osascript -l JavaScript /tmp/test-things.js
# → {"ok":true}

If osascript reports "Not authorized to send Apple Events to Things 3", open System Settings > Privacy & Security > Automation and grant your terminal permission.

Step 4: Test against the connector

The connector validates your params schema before building the JXA:

import { buildJxaForCatalogCommand, mergeCatalogEntries } from '@human/connector-applescript';

// Register your community entry alongside the built-ins
mergeCatalogEntries([{
  id: 'community.acme.things3.project.add',
  tier: 2,
  app: 'Things 3',
  description: 'Add a project to Things 3',
  jxa_template: "var things = Application('Things 3'); things.projects.push(things.Project({ name: '{{name}}', notes: '{{notes}}' })); 'ok'",
  params_schema: {
    type: 'object',
    properties: {
      name: { type: 'string' },
      notes: { type: 'string', default: '' },
    },
    required: ['name'],
  },
}]);

// Build JXA with params
const { jxa } = buildJxaForCatalogCommand('community.acme.things3.project.add', {
  name: 'Q3 Planning',
  notes: 'From the HUMΛN Companion',
});

console.log(jxa);
// → var things = Application('Things 3'); things.projects.push(things.Project({ name: 'Q3 Planning', notes: 'From the HUMΛN Companion' })); 'ok'

Step 5: Submit to the marketplace

Once the JXA is tested locally, submit via the API:

curl -X POST https://api.haio.run/v1/control-plane/marketplace/submit \
  -H "Authorization: Bearer $HUMAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "asset_type": "applescript_catalog_entry",
    "name": "Things 3 — Add Project",
    "description": "Add a project to Things 3 directly from the HUMΛN Companion or any macOS agent.",
    "manifest_json": {
      "catalog_entry": {
        "id": "community.acme.things3.project.add",
        "tier": 2,
        "app": "Things 3",
        "description": "Add a project to Things 3",
        "jxa_template": "var things = Application('"'"'Things 3'"'"'); things.projects.push(things.Project({ name: '"'"'{{name}}'"'"', notes: '"'"'{{notes}}'"'"' })); '"'"'ok'"'"'",
        "params_schema": {
          "type": "object",
          "properties": {
            "name": { "type": "string" },
            "notes": { "type": "string" }
          },
          "required": ["name"]
        }
      }
    }
  }'

The response includes a review_status: "pending". The marketplace intake agent runs:

  1. Structural validation — id format, tier range, jxa_template non-empty, params_schema is an object
  2. JXA safety flags.keystroke, doScript → automatic rejection
  3. Mission scan — checks for exclusion language, surveillance patterns, monetisation of personal data (same checks as connectors and extensions)
  4. Risk classificationlow | medium | high based on all violations

Typical turnaround for tier 0–2 community entries with clean JXA: hours, not days.

Step 6: Install in your org

Once approved, install the catalog entry via the Console or API:

curl -X POST https://api.haio.run/v1/orgs/$ORG_DID/marketplace/install \
  -H "Authorization: Bearer $HUMAN_TOKEN" \
  -d '{ "asset_id": "your-entry-asset-id" }'

After installation, mergeCatalogEntries is called at connector load time with the org's approved community entries. Any muscle or agent calling bridge.invoke with your command ID will now resolve it.

What the muscle does with it

When your community.acme.things3.project.add command is invoked via the muscle:

  1. Muscle validates tier: 2 → calls ctx.approval.request() → Workforce inbox
  2. Reviewer sees "Add a project to Things 3" with params { name: 'Q3 Planning', notes: '...' }
  3. On approval: ctx.call.invoke('applescript', { action: 'bridge.invoke', command_id: '...', arguments: { name, notes } })
  4. On Path A: connector builds JXA, runs it, returns { ok: true }
  5. On Path B: muscle returns MacAutomationIntent → Companion renders tier-amber card
  6. In both cases: ctx.provenance.log({ action: 'mac.invoke.executed', status: 'success', metadata: { command_id, tier } })

Your community command has the same governance, the same HITL gate, and the same provenance trail as any built-in command. The catalog is just the data — the muscle provides the policy.