Skip to main content

Administration and system

A plugin can look after the installation itself: the vault, accounts, plugins, backups, the project browsers, the machine's health. Requires apiVersion: 10. Every permission on this page is sensitive except docs:read and license:read: timon plugin add names them apart before you confirm the install, and a plugin that asks for one should say why.

All calls run with the rights of the account that makes the request, never more. Called outside a request (a background job), the plugin acts as itself and gets only what is said below. Errors carry a code: invalid, not_found, conflict, forbidden or unavailable (PluginAdminError); a refused caller gets a PluginCallerError, a permission missing from the manifest a PluginPermissionError. Every change made this way leaves a line in the audit log naming the plugin and the account.

PermissionSurface
secrets:listctx.secrets.list() — names, modes and grants, never a value
secrets:managectx.secrets.set / grant / revoke
secrets:brokerctx.secrets.httpCall() — an HTTP call under a brokered identity
accounts:read, accounts:managectx.users, ctx.roles, ctx.groups, ctx.backendAccounts
plugins:read, plugins:managectx.plugins.list / setEnabled, ctx.modules.list
system:read, system:logsctx.system.health / doctor, ctx.system.logs
backup:read, backup:managectx.backup.status / enable
browser:read, browser:managectx.browser.list / status / start / stop
agenda:readctx.agenda.list
docs:readctx.docs.search / read
license:readctx.license.edition()

A :manage permission covers its :read.

Secrets​

Nothing here returns a value. ctx.secrets.get (permission secrets:read) still only returns what was granted to your plugin.

const known = await ctx.secrets.list(); // account needs secrets:manage
await ctx.secrets.set({ name: 'CRM_TOKEN', value }); // creates; nobody can read it until a person grants it
await ctx.secrets.grant('CRM_TOKEN', 'agent:support'); // a superadmin's request only
const res = await ctx.secrets.httpCall({ identity: 'CRM_API', path: '/v1/contacts' });
  • list in a request shows the whole vault to an account that manages secrets. In a job it shows the secrets your plugin deposited and the ones granted to it.
  • set creates a name that does not exist yet, with no grant. It changes an existing secret only if your plugin created it, or if the request comes from a superadmin. A name held at several scopes answers conflict: use the Secrets page.
  • grant and revoke are a superadmin's, in a request: a job and a plain account are refused. scope is project:<slug> or agent:<name>. A plugin never grants global nor agent:plugin:…, since either would let a plugin read a value it was not given. revoke takes any scope.
  • httpCall acts as the agent plugin:<your id>: the identity must be a brokered secret granted to it (agent:plugin:<id>) or to everyone. The identity's own host allowlist applies, redirects are not followed, the value is masked in the answer and every call is journalled. Fields: identity, path, optional method, host, headers, body. The answer is { status, body, host }. A refusal has code forbidden/not_found/invalid and a reason (no-grant, host-denied, unknown-identity…).

Accounts, roles and groups​

A superadmin's request only — a call triggered by a signed webhook or an event, which runs as an account but never as a superadmin, is refused too. A job, an anonymous request and any other account — even one holding every right — are refused.

const users = await ctx.users.list();
await ctx.users.setStatus(id, 'disabled'); // revokes sessions and tokens
await ctx.users.setGroups(id, [groupId]);
const role = await ctx.roles.create({ name: 'Reader', permissions: ['kanban:read'] });
const group = await ctx.groups.create({ name: 'Support', roleIds: [role.id] });
await ctx.backendAccounts.grant(accountId, [userId]); // who may use a subscription account

What you do not get, on purpose: no credential ever comes back; no account can be created, invited, promoted or have its password reset; the status of a superadmin cannot be changed; and a plugin confers only ordinary rights of reading and use, from a short allowlist.

Rights a plugin may confer​

A role or group a plugin writes may hold only: missions:spawn, missions:read, missions:stop, agents:communicate, trace:read, kanban:read, kanban:write, notify, storage, and the surfaces a person may see (page:/route, nav:slug, widget:id, theme:id, layout:id, each named, never with a wildcard), plus plugin:<your own id>, which opens your own package's API to the role (another plugin's plugin:<id> is refused). Everything else is refused with code: 'forbidden' and reason: 'right_not_grantable': projects:manage and agents:manage as much as *, terminal:open, host:exec, users:manage, plugins:manage, secrets:*, a :read:all scope, any wildcard such as kanban:*, and a deny (-right). A malformed right is invalid. The same rule covers a group: setGroups refuses a group, and groups.create / update a role, that holds anything outside the allowlist, even a role a person made.

Plugins and modules​

const installed = await ctx.plugins.list(); // a request sees what its account may see; a job sees everything
const modules = await ctx.modules.list();
await ctx.plugins.setEnabled('@timon/agenda', false);

setEnabled needs the plugins:manage right on the account for a bundled module, and a superadmin for a third-party plugin. It is refused in a job and on your own plugin. Mind what that means: a plugin holding plugins:manage, in a request made by a superadmin, can switch off another third-party plugin, a monitoring plugin for instance. That is why plugins:manage is sensitive and why the operator reads it at install. Core modules cannot be switched off. Installing and removing plugins stays with the operator.

System, backup, browser, agenda, docs, license​

  • ctx.system.health() — liveness, live missions, resource and memory figures. doctor() — the machine report (binaries, capabilities); a superadmin's request, or a job. logs({ kind: 'daemon' | 'desktop', lines }) — the last lines, newest first, vault values masked, never the prompts log; a superadmin's request only, never a job.
  • ctx.backup.status() and enable() (idempotent; there is no disable). enable is a superadmin's request, or a job.
  • ctx.browser.list / status / start / stop(project) — a request sees its account's sessions, a job the ones it started; another's session answers like a missing one. The debugging endpoint of the browser is never returned.
  • ctx.agenda.list({ range, from, to, limit, … }) — the connected Google calendars, read-only.
  • ctx.docs.search(query) and ctx.docs.read(route, heading?) — this version's documentation, offline.
  • ctx.license.edition() — 'lite' or 'expert', and nothing else. Activating, installing or removing a licence is not available to plugins.