npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

json-object-editor

v0.10.671

Published

JOE the Json Object Editor | Platform Edition

Readme

Json Object Editor

JOE is software that allows you to manage data models via JSON objects. There are two flavors, the client-side version and nodejs server platform.

What's new in 0.10.663 (brief)

  • Plugin Inventory debug page:
    • New _www/plugins-test.html page (linked from the MCP nav) shows all active plugins, their async/top-level methods, and which JOE apps reference them. It’s auth-protected and backed by the plugin-utils plugin, and is especially useful for AI agents and developers to confirm that methods like chatgpt.autofill and chatgpt.widgetStart are present on a given instance.

What's new in 0.10.662 (brief)

  • React Form Integration with JSON Definitions:
    • JSON includes: include schema now supports filetype: 'json' for storing JSON form definitions (served at /_include/{id} with proper content-type).
    • Form-page linking: Added form reference field to page schema to link pages to JOE forms.
    • Form definition API: New /API/plugin/formBuilder/definition endpoint (merged from formDefinition plugin) serves JSON form definitions. Automatically finds JSON includes from form metadata or page includes.
    • React form renderer: New joe-react-form.js client library renders multi-step React forms from JSON definitions with conditional visibility, validation, and submission to JOE's form submission system.
    • Page rendering fix: Enhanced template variable processing to preserve newlines in page content, fixing JavaScript comment issues in code and module content types.

What’s new in 0.10.660 (brief)

  • MCP everywhere (prompts, autofill, widget):
    • Prompts (ai_prompt): new MCP config block (mcp_enabled, mcp_toolset, mcp_selected_tools, mcp_instructions_mode) lets you turn MCP tools on per‑prompt, pick a toolset (read-only, minimal, all, or custom), and auto‑generate short tool instructions.
    • Autofill fields: the same MCP keys are now supported under a field’s ai config so autofill runs can optionally call MCP tools with the same toolset/playlist model.
    • Audit: ai_response records MCP config and actual tool calls (mcp_tools_used[]) plus used_openai_file_ids[] so you can see which tools and files were used for any run.
  • Uploader file roles + AI‑aware attachments:
    • Uploader fields can define file_roles (e.g. { value:'transcript', label:'Intake Transcript', default:true }) and JOE renders a per‑file role <select> that saves to file_role on each file object.
    • executeJOEAiPrompt now sends a compact uploaded_files[] header (including itemtype, field, filename, file_role, and openai_file_id) alongside Responses input so prompts can reason about “transcript vs summary” sources while the OpenAI Files integration still handles raw content.
    • Responses+tools (runWithTools) now attaches files on both the initial tool‑planning call and the final answer call, so MCP runs see the same attachments end‑to‑end.
  • History safety:
    • Hardened JOE.Storage.save history diffing to avoid a craydent-object edge case where comparing null/undefined values could throw on .toString(). This only affects _history.changes, not what is saved.
  • Object chat unification:
    • The objectChat field now launches a floating <joe-ai-widget> shell with <joe-ai-assistant-picker>, reusing the same AIHub chat stack for all schemas.
    • ai_widget_conversation gained scope_itemtype/scope_id so each chat can be scoped to a specific object, plus attached_openai_file_ids and attached_files_meta to track which uploader files were attached.
    • On the first turn of an object-scoped chat, the server preloads a slimmed understandObject snapshot for the scoped object and merges object files + assistant files into openai_file_ids, so the assistant immediately knows “which record this is” and can reason over its files.

What’s new in 0.10.654 (brief)

  • OpenAI Files mirrored on S3 upload; uploader tiles show the openai_file_id. Retry upload is available per file.
  • Responses integration improvements:
    • Per‑prompt attachments_mode on ai_prompt (direct vs file_search). Direct sends input_file parts; file search auto‑creates a vector store and attaches it.
    • Safe retry if a model rejects temperature/top_p (we strip and retry once).
    • Select Prompt lists prompts by active status where either datasets[] or content_items[].itemtype matches the current object.
    • ai_response now shows used_openai_file_ids and correctly records referenced_objects for Select Prompt runs.
  • UX: “Run AI Prompt” and “Run Thought Agent” buttons disable and pulse while running to avoid double‑submits.

Architecture & Mental Model (Server)

  • Global JOE
    • Created in server/init.js; exposes subsystems like JOE.Utils, JOE.Schemas, JOE.Storage, JOE.Mongo/JOE.MySQL, JOE.Cache, JOE.Apps, JOE.Server (Express), JOE.Sites, JOE.io (Socket), and JOE.auth.
  • Init pattern
    • Modules can optionally export init(); init.js loads/watches modules and calls init() after JOE.Server is ready. This enables hot-reload and keeps Server.js thin.
  • Schemas & events
    • JOE.Schemas loads from server/schemas/ and app schema dir into JOE.Schemas.schema with names in JOE.Schemas.schemaList. Raw copies live in JOE.Schemas.raw_schemas for event hooks.
    • JOE.Storage.save() triggers schema events (create, save, status, delete) via JOE.Schemas.events() and writes history documents to _history.
  • Storage & cache
    • JOE.Storage.load(collection, query, cb) chooses backend per schema storage.type (mongo, mysql, file, api), with Mongo/file fallback.
    • JOE.Storage.save(item, collection, cb, { user, history }) emits item_updated to sockets, records history, and fires events.
    • JOE.Cache.update(cb, collections) populates JOE.Data, flattens a list, and builds lookup. Use JOE.Cache.findByID(collection, idOrCsv) for fast lookups; JOE.Cache.search(query) for in-memory filtering.
  • Auth
    • JOE.auth middleware checks cookie token or Basic Auth; many API routes (and /mcp) are protected.
  • Shorthand $J
    • Server and client convenience: $J.get(_id), $J.search(query), $J.schema(name).
    • On server, provided by server/modules/UniversalShorthand.js and assigned to global.$J in init.js.
  • MCP overview
    • Manifest: /.well-known/mcp/manifest.json; JSON-RPC: POST /mcp (auth-protected). Tools map to real JOE APIs (Schemas, Storage, Cache) and sanitize sensitive fields.

MCP routes and local testing

  • Endpoints

    • Manifest (public): GET /.well-known/mcp/manifest.json
    • JSON-RPC (auth): POST /mcp
    • Privacy (public): /privacy (uses Setting PRIVACY_CONTACT for contact email)
    • Terms (public): /terms
  • Auth

    • If users exist, POST /mcp requires cookie or Basic Auth (same as other APIs). If no users configured, it is effectively open.
  • Test pages

    • JOE ships several debug/test pages in /_www/:
      • mcp-test.html - Simple MCP tool tester
      • mcp-schemas.html - Schema health and summary viewer
      • mcp-prompt.html - MCP prompt/instructions viewer
      • matrix.html - Interactive schema relationship visualization with D3.js force-directed graph
    • Access via JOE path: http://localhost:<PORT>/JsonObjectEditor/_www/<page>.html
    • If your host app serves its own _www, pages can also be available at the root (fallback) if running with the updated server that mounts JOE’s _www as a secondary static directory. Then: http://localhost:<PORT>/<page>.html
  • Tools

    • listSchemas(name?), getSchema(name)
    • getObject(_id, itemtype?) (supports optional flatten and depth)
    • search (exact): unified tool for cache and storage
      • Params: { itemtype?, query?, ids?, source?: 'cache'|'storage', limit?, flatten?, depth? }
      • Defaults to cache across all collections; add itemtype to filter; set source:"storage" to query a specific collection in the DB. Runtime accepts legacy alias schema (maps to itemtype). Use fuzzySearch for typo-tolerant free text.
    • fuzzySearch (typo-tolerant free text across weighted fields)
      • Params: { itemtype?, q, filters?, fields?, threshold?, limit?, offset?, highlight?, minQueryLength? }
      • Defaults: fields resolved from schema searchables (plural) if present; otherwise weights name:0.6, info:0.3, description:0.1. threshold:0.5, limit:50, minQueryLength:2.
      • Returns: { items, count }. Each item may include _score (0..1) and _matches when highlight is true.
    • findObjectsByTag { tags, itemtype?, limit?, offset?, source?, slim?, withCount?, countOnly?, tagThreshold? }
      • Find objects that have ALL specified tags (AND logic). Tags can be provided as IDs (CUIDs) or names (strings) - names are resolved via fuzzy search.
      • Returns: { items, tags, count?, error? } where tags contains the resolved tag objects used in the search.
      • Use countOnly: true to get just the count and matched tags without fetching items.
      • If tags cannot be resolved, returns { items: [], tags: [...resolved ones...], error: "message" } instead of throwing.
    • findObjectsByStatus { status, itemtype?, limit?, offset?, source?, slim?, withCount?, countOnly?, statusThreshold? }
      • Find objects by status. Status can be provided as ID (CUID) or name (string) - name is resolved via fuzzy search.
      • Returns: { items, status, count?, error? } where status is the resolved status object used in the search.
      • Use countOnly: true to get just the count and matched status without fetching items.
      • If status cannot be resolved, returns { items: [], status: null, error: "message" } instead of throwing.
    • saveObject({ object })
    • saveObjects({ objects, stopOnError?, concurrency? })
      • Batch save with per-item history/events. Defaults: stopOnError=false, concurrency=5.
      • Each object must include itemtype; _id and joeUpdated are set when omitted. created is set on first save when missing.
      • Return shape: { results, errors, saved, failed }.
  • Quick tests (PowerShell)

    • Prepare headers if using Basic Auth:
      $pair = "user:pass"; $b64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair))
      $h = @{ Authorization = "Basic $b64"; "Content-Type" = "application/json" }
      $base = "http://localhost:<PORT>"
    • Manifest:
      Invoke-RestMethod "$base/.well-known/mcp/manifest.json"
    • listSchemas:
      $body = @{ jsonrpc="2.0"; id="1"; method="listSchemas"; params=@{} } | ConvertTo-Json -Depth 6
      Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
    • getSchema:
      $body = @{ jsonrpc="2.0"; id="2"; method="getSchema"; params=@{ name="<schemaName>" } } | ConvertTo-Json -Depth 6
      Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
    • search (cache default):
      $body = @{ jsonrpc="2.0"; id="3"; method="search"; params=@{ query=@{ itemtype="<schemaName>" }; limit=10 } } | ConvertTo-Json -Depth 10
      Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
    • fuzzySearch (cache):
      $body = @{ jsonrpc="2.0"; id="6"; method="fuzzySearch"; params=@{ itemtype="<schemaName>"; q="st paal"; threshold=0.35; limit=10 } } | ConvertTo-Json -Depth 10
      Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
    • search (storage):
      $body = @{ jsonrpc="2.0"; id="4"; method="search"; params=@{ itemtype="<schemaName>"; source="storage"; query=@{ }; limit=10 } } | ConvertTo-Json -Depth 10
      Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
    • search (ids + flatten):
      $body = @{ jsonrpc="2.0"; id="5"; method="search"; params=@{ itemtype="<schemaName>"; ids=@("<id1>","<id2>"); flatten=$true; depth=2 } } | ConvertTo-Json -Depth 10
      Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
    • saveObject:
      $object = @{ itemtype="<schemaName>"; name="Test via MCP" }
      $body = @{ jsonrpc="2.0"; id="4"; method="saveObject"; params=@{ object=$object } } | ConvertTo-Json -Depth 10
      Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
    • saveObjects (batch):
      $objs = @(
        @{ itemtype="<schemaName>"; name="Batch A" },
        @{ itemtype="<schemaName>"; name="Batch B" }
      )
      $body = @{ jsonrpc="2.0"; id="7"; method="saveObjects"; params=@{ objects=$objs; stopOnError=$false; concurrency=5 } } | ConvertTo-Json -Depth 10
      Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
  • Quick tests (curl)

    • Manifest:
      curl -s http://localhost:<PORT>/.well-known/mcp/manifest.json | jq
    • listSchemas:
    • search (cache):
      curl -s -X POST http://localhost:<PORT>/mcp \
        -H 'Content-Type: application/json' \
        -d '{"jsonrpc":"2.0","id":"3","method":"search","params":{"query":{"itemtype":"<schemaName>"},"limit":10}}' | jq
    • fuzzySearch:
      curl -s -X POST http://localhost:<PORT>/mcp \
        -H 'Content-Type: application/json' \
        -d '{"jsonrpc":"2.0","id":"6","method":"fuzzySearch","params":{"itemtype":"<schemaName>","q":"st paal","threshold":0.35,"limit":10}}' | jq
    • search (storage):
      curl -s -X POST http://localhost:<PORT>/mcp \
        -H 'Content-Type: application/json' \
        -d '{"jsonrpc":"2.0","id":"4","method":"search","params":{"itemtype":"<schemaName>","source":"storage","query":{},"limit":10}}' | jq
      curl -s -X POST http://localhost:<PORT>/mcp \
        -H 'Content-Type: application/json' \
        -d '{"jsonrpc":"2.0","id":"1","method":"listSchemas","params":{}}' | jq
    • saveObjects (batch):
      curl -s -X POST http://localhost:<PORT>/mcp \
        -H 'Content-Type: application/json' \
        -d '{"jsonrpc":"2.0","id":"7","method":"saveObjects","params":{"objects":[{"itemtype":"<schemaName>","name":"Batch A"}],"stopOnError":false,"concurrency":5}}' | jq
  • Troubleshooting

    • If you see a payload like { originalURL: "/...", site: "no site found" }, the request hit the Sites catch-all. Ensure MCP routes are initialized before Sites (handled by default in server/init.js via MCP.init()), and use the correct URL: /.well-known/mcp/manifest.json or /mcp.
    • To update contact email on /privacy and /terms, set Setting PRIVACY_CONTACT.

Thoughts & Thought Pipeline

  • thought schema stores AI/human hypotheses, syntheses, and link-thoughts with about[] (what it concerns), because[] (receipts), status, and AI lineage fields like source_ai_response / created_by.
  • ThoughtPipeline module compiles a deterministic context from a scope_object (the item you’re thinking about), schema summaries, and accepted Thoughts, and the runThoughtAgent MCP tool runs an OpenAI Responses call to propose new Thoughts.
  • Each Thought run persists an ai_response with response_type:'thought_generation', referenced_objects:[scope_id], and generated_thoughts[] containing the ids of created Thought records.
  • In any schema UI you can include core fields proposeThought and ai_responses to (a) trigger a Thought run for the current object and (b) list all related ai_response records for audit and reuse.

File uploads (S3 + OpenAI Files)

  • Uploader field options:

    • allowmultiple: true|false — allow selecting multiple files.
    • url_field: 'image_url' — on success, sets this property to the remote URL and rerenders that field.
    • ACL: 'public-read' — optional per-field ACL. When omitted, server currently defaults to public-read (temporary during migration).
  • Flow:

    • Client posts { Key, base64, contentType, ACL? } to /API/plugin/awsConnect.
    • Server uploads to S3 (AWS SDK v3) and, if OPENAI_API_KEY is configured, also uploads the same bytes to OpenAI Files (purpose=assistants).
    • Response shape: { url, Key, bucket, etag, openai_file_id?, openai_purpose?, openai_error? }.
    • Client:
      • Sets the url on the file object; if url_field is set on the schema field, it assigns that property and rerenders.
      • Persists OpenAI metadata on the file object: openai_file_id, openai_purpose, openai_status, openai_error.
      • Renders the OpenAI file id under the filename on each uploader tile. The “OpenAI: OK” banner has been removed.
      • Shows a per‑file “Upload to OpenAI” / “Retry OpenAI” action when no id is present or when an error occurred. This calls POST /API/plugin/chatgpt/filesRetryFromUrl with { url, filename, contentType } and updates the file metadata.
  • Errors:

    • If bucket or region config is missing, server returns 400 with a clear message.
    • If the bucket has ACLs disabled, server returns 400: “Bucket has ACLs disabled… remove ACL or switch to presigned/proxy access.”
    • If OpenAI upload fails, the uploader shows OpenAI error: <message> inline; you can retry from the file row.
  • Using OpenAI file ids:

    • File ids are private; there is no public URL to view them.
    • Use the OpenAI Files API (with your API key) to retrieve metadata or download content:
      • Metadata: GET /v1/files/{file_id}
      • Content: GET /v1/files/{file_id}/content
    • Node example:
      const OpenAI = require('openai');
      const fs = require('fs');
      const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
      const meta = await client.files.retrieve('file_abc123');
      const stream = await client.files.content('file_abc123');
      const buf = Buffer.from(await stream.arrayBuffer());
      fs.writeFileSync('downloaded.bin', buf);

File roles on uploader fields

  • Schema configuration:
    • Any uploader field can declare file_roles as an array of { value, label?, default? } objects, for example:
      • { value:'transcript', label:'Intake Transcript', default:true }
      • { value:'summary', label:'Intake Summary' }
    • label is optional; it falls back to value. At most one role should have default:true.
  • Runtime behavior:
    • JOE renders a role <select> next to each uploaded file with:
      • A blank option, and one option per configured role.
      • The select updates the file object’s file_role property in the parent object (e.g. client.files[].file_role).
      • Existing uploads show the role selector on first render as long as file_roles is configured on the field.
    • When OpenAI Files are enabled, uploader files still receive openai_file_id, openai_purpose, openai_status, and openai_error as before; file_role is an additional, JOE‑level label.
  • AI integration:
    • When running an AI prompt via executeJOEAiPrompt, JOE inspects referenced objects for uploader fields and builds an uploaded_files[] header:
      • Each entry includes { itemtype, field, name, role, openai_file_id }.
      • This header is merged into the user input so prompts can explicitly reason about which files are “transcripts”, “summaries”, etc., while the actual file bytes are attached via Responses input_file parts / file_search tool resources.

Related endpoints (server/plugins)

  • POST /API/plugin/awsConnect – S3 upload (and OpenAI mirror when configured)

    • Input: { Key, base64, contentType, ACL? }
    • Output: { url, Key, bucket, etag, openai_file_id?, openai_purpose?, openai_error? }
  • POST /API/plugin/chatgpt/filesRetryFromUrl – (Re)upload an existing S3 file to OpenAI

    • Input: { url, filename?, contentType? }
    • Output: { success, openai_file_id?, openai_purpose?, error? }

SERVER/PLATFORM mode

check port 2099
/JOE/
/JsonObjectEditor/docs.html
*Should auto-open on start

Json Object Editor (Universal-esque software) (requires connection to a mongo server for full functionality)

JOE server instantiation (add to entry point js file)

var joe = require('json-object-editor');
or here's a custom example
var joe = require('json-object-editor')({
	name:'name_your_joe' (JOE),
	joedb:'local_custom_database_name' // {{mongoURL}}' if using mongolab or remote mongodb,
	port:'4099', (2099)
	socketPort:'4098', (2098)
	sitesPort:'4100' (2100),
	clusters:1,
	hostname:'server name if not localhost'(localhost)
});

$J (universal) Shorthand JOE

$J to access helper funtions on client and server. (callbacks are optional)
$J.get(itemId,[callback])
$J.schema(schemaname,[callback])

Client-Side (front end only)

js client only instantiation

var specs = {
	fields:{
		species:{label:'Species',type:'select', values:['cat','dog','rat','thing'], onchange:adjustSchema},
		gender:{type:'select', values:['male','female']},
		legs:{label:'# of Legs',type:'int', onblur:logit},
		weight:{label:' Weight (lbs)',type:'number', onblur:logit},
		name:{label:' pet Name', onkeyup:logValue},
		//id:{label:'ID',type:'text', locked:true},
		id:{label:'ID',type:'guid'},
		
	//example of select that takes function (function is passed item)	
		animalLink:{label:'Link to other animal',type:'select', values:getAnimals},
		hiddenizer:{hidden:true}
	},
	schemas:{
		animal:animalschema,
		thing:thingschema			
	},
	container:string ('body'),
    compact:false,
    useBackButton:true,
	autosave:2000,
    listSubMenu:{filters:{}},
    useHashlink:true,
    title:'${itemtype} | ${display}'
}
var JOE = new JsonObjectEditor(specs);
JOE.init();

##JOE CONFIG ##specs

  • useBackButton:[false] if true, back button moves through joe panels when joe has history to go to (is open).

  • useHashlink:[false], true or a template for hashlinks. default template is '${schema_name}_${_id}' default server tempalte is '${schema_name}/${_id}' ##SCHEMA CONFIG ###fields Properties for all Fields

  • label / display: what the field should display as *If the field type is boolean, label controls checkbox/boolean label

  • value: default value if not one in object

  • default: default value for field || function(object)

  • type: what type of field should JOE show

  • hidden: boolean / function, value will be added (but unsees by user)

  • locked: boolean

  • condition: boolean

  • width: used for layout control.

    • can use pixels or percentages (as string)
  • comment: a commentthat shows up at the beginning of the field

  • tooltip: hover/clickable tooltip that shows up next to name

field types:

  • rendering: for css html and js

  • text: default single line text.

    • autocomplete: boolean // obj of specs (template, idprop)
      • values:array of possibilities -maxlength:string
  • int: integer field

  • number: number (float) field

  • select: select list.

    • multiple(bool)
    • values(array of objects, [{value:"",name/display:""]), can be a function
      • disabled:boolean(func acceptable)
    • idprop: string of prop name
  • geo: shows a map

    • takes a string array "[lat,lon]"
    • center:[lat,lon], center of map
    • zoom: zoom level (higher zooms in more)
    • returns "[lat,lon]" -image : shows an image and HxW as th image url is typed in.
  • multisorter : allows arrays of objects to be selected and sorted in right bin.

    • values(array of objects, [{value:"",name/display:""]), can be a function
  • content : show content on in the editor

    • run: function to be run(current_object,field_properties)
    • template: html template for fillTemplate(template,current_object);
  • objectlist : a table of objects with editable properties

    • properties: array of objects|strings for the object property names -name: value in object -display: header in objectList
    • max: integer, number or items that can be added. use zero for infinite.
    • hideHeadings: don't show table headings
  • objectReference : a list of object ids

    • template
    • autocomplete_template
    • idprop
    • values
    • max(0 unlimited)
    • sortable(true)
  • code :

    • language
  • json :

    • edit/store JSON subobjects as objects (not strings) using the code editor in JSON mode; pretty-prints on blur/save and treats whitespace-only reformatting as no-op changes.
  • boolean:

    • label:controls checkbox label
  • preview : -content: string or function(current joe object) to replace everything on page (template). -bodycontent: same as content, only replaces body content. -url: preview page if not the default one.

    • encoded: boolean, if pre uriencoded labels:
  • pass an object instead of a string to the fields array.

    {label:'Name of the following properties section'}

##page sections {section_start: 'SectionName', section_label:'Section Name with Labels', condition:function(item){ return item.show;} }, {section_end: 'CreativeBrief'}

  • pass an object instead of a string to the fields array. these show up on the details view as anchors.

  • Object Properties

    • section_start: name/id of section
    • 'section_label:use instead of section_start for display name
    • section_end: name/id of section(str)
    • template: html template for fillTemplate(template,current_object);
    • Note: for fields, condition removes the field from the DOM while hidden only toggles visibility; sections use condition (and sidebar sections use hidden) differently than fields.

##page sidebar {sidebar_start: 'SectionName', sidebar_label:'Section Name with Labels', condition:function(item){ return item.show;} }, {sidebar_end: 'CreativeBrief'}

  • pass an object instead of a string to the fields array. these show up on the details view as anchors.

  • Object Properties

    • sidebar_start: name/id of sidebar
    • sidebar_label:use instead of sidebar_start for display name
    • sidebar_end: name/id of sidebar(str)
    • template: html template for fillTemplate(template,current_object);

###defaultProfile overwrites the default profile

#schemas

a list of schema objects that can configure the editor fields, these can be given properties that are delegated to all the corresponding fields.

var animalschema = 
{
	title:'Animal', *what shows as the panel header* 
	fields:['id','name','legs','species','weight','color','gender','animalLink'], *list of visible fields*
	_listID:'id', *the id for finding the object*
	_listTitle:'${name} ${species}', *how to display items in the list*
	menu:[array of menu buttons],
	listMenuTitle: (string) template forjoe window title in list view,
	listmenu:[array of menu buttons] (multi-edit and select all always show),
	/*callback:function(obj){
		alert(obj.name);
	},*/
	onblur:logit,
	
	hideNumbers:boolean *toggle list numbers*
	multipleCallback:function to be called after a multi-edit. passed list of edited items.
	onUpdate: callback for after update. passed single edited items.
	onMultipleUpdate:callback for after multi update.passed list of edited items.
            filters: array of objects
	checkChanges:(bool) whether or not to check for changes via interval and on leave
}

##Table View - add tableView object to a schema; -cols = [strings||objects] -string is the name and value -display/header is the column title -property/name = object property ###Pre-formating you can preformat at the joe call or schema level. The data item will be affected by the passed function (which should return the preformated item).

##menu## an array of menu buttons

//the default save button
//this is the dom object, 
//use _joe.current.object for current object
condition:function(field,object) to call
self = Joe object
var __saveBtn__ = {name:'save',label:'Save', action:'_joe.updateObject(this);', css:'joe-save-button'};

##itemMenu## as array of buttons for each item in list views - name - action (action string) - url (instead of js action) - condition

##itemExpander## template or run for content to be shown under the main list item block.

###Addition properties Changing the schema on the fly?

_joe.resetSchema(new schema name);

css (included) options

  • joe-left-button
  • joe-right-button

##FIELDS

{extend:'name',specs:{display:'Request Title'}},//extends the field 'name' with the specs provided.

##usage

a | adding a new object

_joe.show({},{schema:'animal',callback:addAnimal); 
//or goJoe(object,specs)

...
function addAnimal(obj){
	animals.push(obj);
}

b | viewing a list of objects

goJoe([array of objects],specs:{schema,subsets,subset})
goJoe.show(animals,{schema:'animal',subsets:[{name:'Two-Legged',filter:{legs:2}}]});
//use the specs property subset to pre-select a subset by name

properties

  • _listWindowTitle: the title of the window (can be passed in with the schema);

  • _listCount: added to the current object and can be used in the title.

  • _listTitle:'${name} ${species}', how to display items in the list

  • _icon: [str] template for a list item icon (standard min 50x50), 'http://www.icons.com/${itemname}', can be obj with width, height, url

  • listSubMenu:a function or object that represents the list submenu

  • stripeColor:string or function that returns valid css color descriptor.

  • bgColor:string or function that returns valid css color descriptor.

  • subsets: name:string, filter:object

  • subMenu:a function or object that represents the single item submenu

  • _listTemplate: html template that uses ${var} to write out the item properties for the list item.

    • standard css class joe-panel-content-option

Helper shortcuts: subsets and stripes

Use these helpers to quickly generate subset/filter options and add per-item stripe colors.

// In a schema definition
{
  // 1) Subsets from statuses of the current schema (auto colors)
  subsets: function () {
    return _joe.Filter.Options.status({
      group: 'status',        // optional grouping label
      collapsed: true,        // start group collapsed
      none: true              // include a "no status" option
      // color: 'stripecolor' // uncomment to render colored stripes in the menu
    });
  },

  // 2) Subsets for all distinct values of a property
  //    Example: recommendation_domain on 'recommendation' items
  //    (pass values to control order/allowlist)
  // subsets: () => _joe.Filter.Options.getDatasetPropertyValues(
  //   'recommendation', 'recommendation_domain', { group: 'domain', values: ['product','dietary','activity'] }
  // ),

  // 3) Subsets from another dataset (reference values)
  // subsets: () => _joe.Filter.Options.datasetProperty('user','members',{ group: 'assignees', collapsed: true }),

  // 4) Row stripe color by status (string or { color, title })
  stripeColor: function (item) {
    if (!item.status) return '';
    const s = _joe.getDataItem(item.status, 'status');
    return s && { color: s.color, title: s.name };
  }
}

###c | Conditional select that changes the item schema

fields:{
	species:{label:'Species',type:'select', values:['cat','dog','rat','thing'], onchange:adjustSchema},
	[field_id]:{
		
		+label : STR
		+type : STR
		value : STR (default value)
		+values : ARRAY/FUNC (for select)
		
		//modifiers
		+hidden:BOOL/STRING(name of field that toggles this) //don't show, but value is passed
		+locked:BOOL // show, but uneditable
		//events
		+onchange : FUNC
		+onblur : FUNC
		+onkeypress : FUNC
		+rerender : STRING // name of field to rerender
	}
}

function adjustSchema(dom){
	var species = $(dom).val();
	if(species == "thing"){
		JOE.resetSchema('thing')
	}
	else{
		JOE.resetSchema('animal')
	
	}
}

###d | duplicating an item

//duplicates the currently active object (being edited)
_joe.duplicateObject(specs);

specs

  • deletes:array of properties to clear for new item
    • note that you will need to delete guid/id fields or the id will be the same.

e | exporting an object in pretty format json (or minified)

JOE.exportJSON = function(object,objvarname,minify)

##Useful Functions _joe.reload(hideMessage,specs)

  • use specs.overwreite object to extend reloaded object.

_joe.constructObjectFromFields()

AI / Chat Integrations (OpenAI Responses + Assistants)

JOE ships a few plugins and schemas to help you connect OpenAI assistants and the new Responses API to your data model.

  • chatgpt plugin

    • Central entry point for OpenAI calls (uses the openai Node SDK and the Responses API).
    • Looks up the OpenAI API key from the setting schema:
      • OPENAI_API_KEY: your secret key (can be a service‑account key, e.g. sk-svcacct-...).
    • Key methods:
      • autofill (POST /API/plugin/chatgpt/autofill):
        • Given { object_id, schema, fields, prompt?, assistant_id?, model? }, calls openai.responses.create(...) and returns a JSON patch for the requested fields only.
        • Used by _joe.Ai.populateField(...) for schema‑driven “AI fill” buttons.
      • executeJOEAiPrompt:
        • Drives the ai_promptai_response workflow using Responses API + optional helper functions.
      • Widget‑focused methods:
        • widgetStart, widgetMessage, widgetHistory for the embeddable AI widget (see below).
  • chatgpt-assistants plugin (legacy Assistants API)

    • Manages the older beta.assistants / beta.threads API for JOE’s rich in‑app chat (<joe-ai-chatbox>).
    • Still used by the ai_conversation flow; new integrations should prefer the Responses‑based chatgpt plugin.
  • chatgpt-tools plugin

    • Exposes JOE “AI tools” configured via the ai_tool schema.
    • Useful when you want OpenAI tools that call back into JOE (e.g. search, profile, etc.).

AI Assistant schemas

  • ai_assistant schema

    • Represents a JOE‑side “assistant config” that can be synced to an OpenAI assistant.
    • Key fields:
      • ai_model: default model (e.g. gpt-4.1-mini, gpt-4o).
      • instructions: system prompt for the assistant.
      • assistant_id: OpenAI assistant ID (asst_...) set by the sync button.
      • file_search_enabled, code_interpreter_enabled: toggle built‑in tools.
      • tools: JSON definition array for custom function tools.
      • assistant_thinking_text: what the UI shows while the assistant is “thinking”.
    • UI highlights the default assistant:
      • The assistant whose _id matches the DEFAULT_AI_ASSISTANT setting is given a golden stripe in list view.
    • Default assistant resolution:
      • The client helper Ai.getDefaultAssistant() in joe-ai.js looks up _joe.Data.setting.where({ name: 'DEFAULT_AI_ASSISTANT' })[0] and caches it.
  • ai_widget_conversation schema

    • Lightweight conversation log used by the new embeddable widget (<joe-ai-widget>).
    • Fields:
      • model: model name used for the conversation (or inherited from ai_assistant.ai_model).
      • assistant: optional reference to the JOE ai_assistant used.
      • assistant_id: OpenAI assistant ID (asst_...) used by the Responses API.
      • system: effective system prompt.
      • messages: JSON array of { role, content, created_at } for the widget chat.
      • last_message_at, source, tags, standard system fields.
    • This schema is what the widget endpoints in chatgpt read/write.

Embeddable AI Widget (<joe-ai-widget>)

JOE includes a small web component, defined in js/joe-ai.js, that can be dropped into any web page and talks to your JOE instance via the chatgpt plugin.

  • Custom element: <joe-ai-widget>

    • Attributes:
      • endpoint (optional): Base URL to the JOE instance. Defaults to same origin.
        Example: endpoint="https://my-joe.example.com".
      • ai_assistant_id (optional): _id of an ai_assistant document in JOE. If present, the server will load this assistant and use its ai_model, instructions, and assistant_id when starting the conversation.
      • assistant_id (optional): Direct OpenAI assistant ID (asst_...). If provided, the widget uses Responses API with assistant_id instead of a bare model.
      • model (optional): Model name to use when no assistant is supplied (e.g. gpt-4.1-mini).
      • title (optional): Header title text shown in the widget panel.
      • placeholder (optional): Input placeholder text.
      • source (optional): Arbitrary string stored on the conversation (ai_widget_conversation.source).
      • conversation_id (optional): Existing ai_widget_conversation._id to resume a saved chat.
    • Behavior:
      • On first attach:
        • If conversation_id is set → calls GET /API/plugin/chatgpt/widgetHistory?conversation_id=... and renders messages.
        • Otherwise → calls POST /API/plugin/chatgpt/widgetStart to create a new ai_widget_conversation and stores the returned conversation_id (and any assistant_id/model) as attributes.
      • On send:
        • Optimistically appends a { role:'user', content } message in the UI.
        • Calls POST /API/plugin/chatgpt/widgetMessage with { conversation_id, content, role:'user', assistant_id?, model? }.
        • Renders the updated messages and assistant reply from the response.
  • Server endpoints (chatgpt plugin):

    • POST /API/plugin/chatgpt/widgetStartchatgpt.widgetStart(data)
      • Input: { model?, ai_assistant_id?, source? }.
      • Behavior:
        • If ai_assistant_id is provided, loads that ai_assistant and seeds model, assistant_id, system from it.
        • Creates ai_widget_conversation with empty messages and timestamps.
        • Returns { success, conversation_id, model, assistant_id }.
    • GET /API/plugin/chatgpt/widgetHistory?conversation_id=...chatgpt.widgetHistory(data)
      • Input: { conversation_id }.
      • Returns { success, conversation_id, model, assistant_id, messages }.
    • POST /API/plugin/chatgpt/widgetMessagechatgpt.widgetMessage(data)
      • Input: { conversation_id, content, role='user', assistant_id?, model? }.
      • Behavior:
        • Loads ai_widget_conversation, appends a user message.
        • Calls openai.responses.create:
          • With assistant_id if available (preferred), or
          • With model and instructions (from system) otherwise.
        • Appends an assistant message, updates last_message_at, saves the conversation.
        • Returns { success, conversation_id, model, assistant_id, messages, last_message, usage }.

AI Widget Test Page

To help you develop and debug the widget + plugin in your instance, JOE exposes an auth‑protected test page, similar to the MCP test pages.

  • Routes (both require standard JOE auth):

    • /ai-widget-test.html
    • ${JOE.webconfig.joepath}ai-widget-test.html (e.g. /JsonObjectEditor/ai-widget-test.html).
  • Behavior (server/modules/Server.js):

    • The route handler:
      • Reads assistant_id or assistant from the query string.
      • If not present, tries to resolve the default assistant via:
        • JOE.Utils.Settings('DEFAULT_AI_ASSISTANT', { object: true }) and uses its .value as ai_assistant_id.
      • Renders a small HTML page that:
        • Includes the MCP nav (_www/mcp-nav.js) so you can jump between MCP and widget tests.
        • Mounts <joe-ai-widget title="JOE AI Assistant" ai_assistant_id="..."> in a centered card.
        • Loads js/joe-ai.js, which defines both <joe-ai-chatbox> (for in‑app use) and <joe-ai-widget> (for embedding).
  • Shared nav link:

    • The shared dev nav (_www/mcp-nav.js) includes an “AI Widget” link:

      <a href="/ai-widget-test.html" target="ai_widget_test_win" rel="noopener">AI Widget</a>
    • This appears on MCP test/export/prompt pages and on the AI widget test page itself.

AI / Widget Changelog (current work – 0.10.632)

  • Added a Responses‑based tool runner for <joe-ai-widget> that wires ai_assistant.tools into MCP functions via chatgpt.runWithTools.
  • Enhanced widget UX: assistant/user bubble theming (using assistant_color and user color), inline “tools used this turn” meta messages, and markdown rendering for assistant replies.
  • Expanded the AI widget test page with an assistant picker, live tool JSON viewer, a clickable conversation history list (resume existing ai_widget_conversation threads), and safer user handling (widget conversations now store user id/name/color explicitly and OAuth token‑exchange errors from Google are surfaced clearly during login).
  • Added field-level AI autofill support: schemas can declare ai config on a field (e.g. { name:'ai_summary', type:'rendering', ai:{ prompt:'Summarize the project in a few sentences.' } }), which renders an inline “AI” button that calls _joe.Ai.populateField('ai_summary') and posts to /API/plugin/chatgpt/autofill to compute a JSON patch and update the UI (with confirmation before overwriting non-empty values).