@aivi/plugin 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The registry declaration an installable plugin package makes at its `./config`
3
+ * subpath. The `aivi-plugins` list in `<home>/app/package.json` names packages —
4
+ * what npm installs, what `aivi add` takes; this declaration carries the other
5
+ * identity a plugin has, its **module id**: the key of its block in `config.json`
6
+ * (`plugins.<id>`), the logger category, the id `/status` reports. A third-party
7
+ * package brings its own id, so npm names and config keys can never collide.
8
+ *
9
+ * The subpath is deliberately light: the CLI imports it for every command to
10
+ * compose the config schema and mount commands, so it pulls the schema and not
11
+ * the platform SDK — `createModule` reaches for the module code itself, lazily.
12
+ */
13
+ import type { z } from 'zod';
14
+ import type { AiviModule } from './module.ts';
15
+ /** What the registry finds at a plugin package's `./config` subpath. */
16
+ export interface AiviPlugin<C = unknown> {
17
+ /** The module id: the `plugins.<id>` config key, the logger category, the `/status` id. */
18
+ id: string;
19
+ /** The block's own schema: defaults, required fields and cross-field rules all live
20
+ * here. A plugin listed with no block is parsed against `configSchema.parse({})` —
21
+ * defaults, or this schema's own "enabled but unconfigured" complaint. */
22
+ configSchema: z.ZodType<C>;
23
+ /** The plugin's own project-section schemas: `projectSchema` is the shape of
24
+ * `projects.<id>.<id>` under every project, `projectDefaultsSchema` the shape of
25
+ * its section under `projectDefaults`, both keyed by module id. Core keeps the
26
+ * core vocabulary of a project (`enabled`, `knowledge`) and nothing else; a
27
+ * project key is valid only because core defines it or a registered plugin
28
+ * declares it here — the same closure the `plugins` blocks get. They ride the
29
+ * light `./config` subpath because the registry composes the schema before any
30
+ * module runs. The plugin reads its sections back from the loaded config; core
31
+ * never interprets them. */
32
+ projectSchema?: z.ZodType;
33
+ projectDefaultsSchema?: z.ZodType;
34
+ /** Build the module `aivi serve` runs. `home` is the aivi home: a config path that
35
+ * was written relative resolves against it here — the plugin knows its own fields,
36
+ * core never does. */
37
+ createModule(config: C, home: string): AiviModule | Promise<AiviModule>;
38
+ }
package/dist/plugin.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The registry declaration an installable plugin package makes at its `./config`
3
+ * subpath. The `aivi-plugins` list in `<home>/app/package.json` names packages —
4
+ * what npm installs, what `aivi add` takes; this declaration carries the other
5
+ * identity a plugin has, its **module id**: the key of its block in `config.json`
6
+ * (`plugins.<id>`), the logger category, the id `/status` reports. A third-party
7
+ * package brings its own id, so npm names and config keys can never collide.
8
+ *
9
+ * The subpath is deliberately light: the CLI imports it for every command to
10
+ * compose the config schema and mount commands, so it pulls the schema and not
11
+ * the platform SDK — `createModule` reaches for the module code itself, lazily.
12
+ */
13
+ export {};
package/dist/run.d.ts ADDED
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The **run** domain: what a run is, in platform-neutral words, as both sides
3
+ * of the contract read it. A run is one attempt at one ticket — not the
4
+ * ticket (which outlives it) and not any tracker's session. The orchestrator
5
+ * records these shapes; a tracker renders them on its platform and never
6
+ * drives them. This is the vocabulary the host's engine and every module
7
+ * share — which is why it lives here, below both.
8
+ *
9
+ * Core words are `tracker` and `forge`, never a platform's name: Linear, Jira
10
+ * and GitHub Issues all spell these differently on their own side, and nothing
11
+ * here notices which one is speaking. The orchestrator knows tickets, lanes
12
+ * and OpenCode — a conversation string is no orchestrator fact, and delivery
13
+ * of anything to a platform is the tracker's own business.
14
+ */
15
+ /**
16
+ * A run's life, as the orchestrator holds it. There is **no capacity gate in
17
+ * this version**: admission and a lease arrive with the dispatcher, so
18
+ * `preparing` is entered the moment work is requested and only ever means
19
+ * "the work environment and session are being made".
20
+ *
21
+ * - `preparing` — requested; the work environment and the session are being made.
22
+ * - `working` — a turn is running or queued.
23
+ * - `awaiting_input` — parked on an open elicitation: the OpenCode form is
24
+ * the durable record of the wait; the keep-alive says how long the slot
25
+ * is held for it, and after that the ticket waits without one. The run
26
+ * keeps its session and its claim either way.
27
+ * - `completed` / `failed` / `cancelled` — terminal; only a completion tool
28
+ * call, a failure, or a stop puts a run here. Never a text, never idleness.
29
+ */
30
+ export type RunState = 'preparing' | 'working' | 'awaiting_input' | 'completed' | 'failed' | 'cancelled';
31
+ /** Terminal means nothing more can happen to this run. */
32
+ export declare function isTerminal(state: RunState): boolean;
33
+ /**
34
+ * One question's options, in neutral words; a tracker renders them its way
35
+ * (Linear shows its `select` signal, GitHub a numbered comment). `label` is
36
+ * what the person sees; `value` is what an answer arrives as (they may also
37
+ * answer in free text — platforms that render options emit the chosen value
38
+ * as an ordinary message).
39
+ */
40
+ export interface RunOption {
41
+ label: string;
42
+ value: string;
43
+ }
44
+ /**
45
+ * A question the worker asked — one shape, seen from both sides: the
46
+ * orchestrator records it, the tracker renders it. `formId` is the OpenCode
47
+ * session form that holds the durable copy: the worker created it through
48
+ * `ask`, it reads `pending` until a human answers it, and it is the
49
+ * *discriminator* between an answer and an interjection. The form is the
50
+ * record; this is the rendering a tracker shows, and a platform that reads
51
+ * only the text and options ignores `formId` without losing anything.
52
+ */
53
+ export interface RunQuestion {
54
+ question: string;
55
+ options?: RunOption[];
56
+ formId?: string;
57
+ }
58
+ /**
59
+ * What the completion tool says the worker did, and the reason a run ends.
60
+ * This is an application protocol, not an OpenCode type: the worker authored
61
+ * it, the orchestrator parsed and validated it, and a tracker decides how —
62
+ * and whether — to show it. `success` and `failure` are the worker's
63
+ * knowledge; which lane each lands the ticket in is the orchestrator's lane
64
+ * config, never the worker's report.
65
+ */
66
+ export type RunOutcome = {
67
+ kind: 'success';
68
+ summary: string;
69
+ } | {
70
+ kind: 'failure';
71
+ reason: string;
72
+ code?: FailureCode;
73
+ };
74
+ /** Why aivi — not the worker — ended a run, in machine-readable words: a
75
+ * tracker renders help for its platform from the code, never by reading
76
+ * English. `kill-unconfirmed`: the dispatcher struck the session the
77
+ * configured number of times and it would not die; the person watching
78
+ * the ticket should know a worker may still be loose.
79
+ * `stop-unconfirmed`: a person asked to stop and OpenCode would not answer
80
+ * the interrupt at all, so the same may be true of a stop. */
81
+ export type FailureCode = 'kill-unconfirmed' | 'stop-unconfirmed';
82
+ /**
83
+ * A run as anyone outside the orchestrator may read it: enough to speak
84
+ * about one, never enough to drive it. It names the ticket, not a tracker's
85
+ * session — the tracker holds that pair in its own store.
86
+ */
87
+ export interface RunView {
88
+ readonly id: string;
89
+ readonly projectId: string;
90
+ readonly trackerId: string;
91
+ readonly ticketId: string;
92
+ readonly lane: string;
93
+ readonly agent: string;
94
+ readonly state: RunState;
95
+ readonly sessionId?: string;
96
+ /** The directory the worker works in: the checkout, or the worktree a
97
+ * `worktree: true` lane got on the ticket's branch. */
98
+ readonly worktree?: string;
99
+ readonly outcome?: RunOutcome;
100
+ /** The lane the lane order chose for a finished run's ticket: the
101
+ * orchestrator's decision; performing the move is the tracker's. Absent:
102
+ * the ticket stays — a stop moves nothing, an unmapped lane goes silent. */
103
+ readonly targetLane?: string;
104
+ /** When the run last changed: a tracker's rendering watermark reads
105
+ * endings through it, so nothing that arrived while aivi slept is
106
+ * missed and nothing already rendered is said twice. */
107
+ readonly updatedAt: number;
108
+ }
109
+ /**
110
+ * The worker's plan: a session-level checklist it keeps current while it
111
+ * works, in neutral words. `content` is the step; `status` where it stands.
112
+ * The whole array arrives at every update — replacing the plan wholesale is
113
+ * the shape the platforms that render one (Linear's agent plan) accept, and
114
+ * it keeps the worker's tool honest: no diffing, no partial truths.
115
+ */
116
+ export interface RunPlanStep {
117
+ content: string;
118
+ status: 'pending' | 'inProgress' | 'completed' | 'canceled';
119
+ }
120
+ export interface RunPlan {
121
+ steps: RunPlanStep[];
122
+ }
123
+ /**
124
+ * What work is asked of the orchestrator: one ticket, in a lane, worked by
125
+ * an agent in a directory, opened with the tracker's summary. The shape the
126
+ * walk builds when it claims a ticket; the door stays the walk's.
127
+ */
128
+ export interface WorkRequest {
129
+ projectId: string;
130
+ trackerId: string;
131
+ ticketId: string;
132
+ lane: string;
133
+ agent: string;
134
+ /** Where the worker works: the checkout, or a worktree the delegator made. */
135
+ directory: string;
136
+ /** The tracker's own summary of the ticket, composed into the worker's
137
+ * first prompt with the orchestrator's contract around it. */
138
+ summary: string;
139
+ /** The ticket's branch name, as the platform names it: what a worktree
140
+ * lane checks out. The push door did its platform side already, so
141
+ * `initWork` is not asked twice; a worktree lane whose request carries
142
+ * no branch is refused visibly, never named by guess. */
143
+ branch?: string;
144
+ }
package/dist/run.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The **run** domain: what a run is, in platform-neutral words, as both sides
3
+ * of the contract read it. A run is one attempt at one ticket — not the
4
+ * ticket (which outlives it) and not any tracker's session. The orchestrator
5
+ * records these shapes; a tracker renders them on its platform and never
6
+ * drives them. This is the vocabulary the host's engine and every module
7
+ * share — which is why it lives here, below both.
8
+ *
9
+ * Core words are `tracker` and `forge`, never a platform's name: Linear, Jira
10
+ * and GitHub Issues all spell these differently on their own side, and nothing
11
+ * here notices which one is speaking. The orchestrator knows tickets, lanes
12
+ * and OpenCode — a conversation string is no orchestrator fact, and delivery
13
+ * of anything to a platform is the tracker's own business.
14
+ */
15
+ /** Terminal means nothing more can happen to this run. */
16
+ export function isTerminal(state) {
17
+ return state === 'completed' || state === 'failed' || state === 'cancelled';
18
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The **project-setup** contract: how a plugin helps set up a new project for
3
+ * the *role* it plays (`forge` or `tracker` — core's system vocabulary, never
4
+ * a plugin name). `aivi projects add` walks the roles in order and, for each,
5
+ * runs the one configured plugin that serves it; the plugin asks its own
6
+ * platform questions and hands back the config section core writes under the
7
+ * project. Core writes bytes and reads none of them — the types live in the
8
+ * plugin's own `projectSchema`. A forge owns the checkout (it clones); a
9
+ * plugin that clones says so, and with no forge the setup leaves an untracked
10
+ * directory instead.
11
+ *
12
+ * Everything platform-specific lives in the plugin; the runner only wires the
13
+ * context (the same clack, fetch and store the plugin's `./setup` gets) and
14
+ * threads the project name from one role to the next so it is asked once.
15
+ */
16
+ import type { OutputBlock, ProjectLaneInput, ProjectRole } from '@aivi/core';
17
+ import type * as prompts from '@clack/prompts';
18
+ import type { Store } from './module.ts';
19
+ import { PluginSetupCancelled } from './setup.ts';
20
+ export { PluginSetupCancelled };
21
+ /** What the runner hands a project contributor. */
22
+ export interface ProjectSetupContext {
23
+ /** The aivi home that owns `config.json` and `.env`. */
24
+ home: string;
25
+ configPath: string;
26
+ /** The plugin's own config block (`config.plugins.<id>`) as written — its
27
+ * credentials live in the environment, so this is ids and options. */
28
+ config: Record<string, unknown>;
29
+ /** The plugin's own `projectDefaults.<id>` section as written — the
30
+ * company-wide convention this project inherits unless it says otherwise.
31
+ * Core hands the plugin its own section and reads nothing in it. */
32
+ projectDefaults?: Record<string, unknown>;
33
+ /** The project name chosen by an earlier role, or undefined when this role
34
+ * is the first to settle it (and so offers the default). */
35
+ projectId?: string;
36
+ /** Whether a forge is configured in this home — so a checkout (and once
37
+ * a forge gives them, worktrees) is possible for this project. Core
38
+ * computes it from the configured plugins; contributors that could work
39
+ * a lane with git ask their own worktree questions with it. */
40
+ forgeConfigured: boolean;
41
+ /** The repository an earlier role (a forge) settled, as `owner/repo` —
42
+ * undefined when no forge named one. A tracker offers it as the answer
43
+ * that is right most of the time instead of asking a person to repeat
44
+ * what they just told the wizard. */
45
+ forgeRepository?: string;
46
+ /** Raw JSON on stdout when the value is for copying (core's `print`). */
47
+ print(value: unknown, output?: OutputBlock[] | string): void;
48
+ /** The very `@clack/prompts` the runner renders with; a cancelled prompt
49
+ * throws `PluginSetupCancelled` and nothing further is written. */
50
+ prompts: typeof prompts;
51
+ /** Platform calls the contributor verifies with; the runner supplies fetch. */
52
+ fetch(url: string, init?: RequestInit): Promise<Response>;
53
+ /** The OpenCode agents that can work this project: what the project's
54
+ * checkout directory defines (its `.opencode/`), plus the global ones,
55
+ * minus the subagent-only ones — a lane works as the session's primary.
56
+ * The runner starts an OpenCode service when none answers, and a failure
57
+ * to reach or start one stops the setup — it is never swallowed. */
58
+ agents(projectId: string): Promise<string[]>;
59
+ /** The home's SQLite store, open for the callback and closed after. */
60
+ withStore<T>(fn: (store: Store) => T | Promise<T>): Promise<T>;
61
+ }
62
+ /** What a project contributor hands back for the project. */
63
+ export interface ProjectSetupResult {
64
+ /** The project name, settled here: the role's own default (a forge's repo
65
+ * name, a tracker's team name) or the one handed in by an earlier role. */
66
+ id: string;
67
+ /** The section core writes at `projects.<id>.<moduleId>`, validated against
68
+ * this plugin's own `projectSchema`; absent means the role wrote nothing. */
69
+ section?: Record<string, unknown>;
70
+ /** The project's **core** `lanes` array — the ordered tracker workflow,
71
+ * written at `projects.<id>.lanes` and validated by core (unique names,
72
+ * every `next`/`previous` a lane of the array, the queue lane's rules).
73
+ * Lanes are core's fact;
74
+ * whoever knows the board — the tracker role — offers its order, and two
75
+ * contributors naming one workflow is an error, not a merge. */
76
+ lanes?: ProjectLaneInput[];
77
+ /** True when this contributor leaves a git checkout at `projects/<id>/source`
78
+ * — it cloned one, or the checkout was already there for the repository the
79
+ * person named. A forge owns the clone because cloning reaches `origin`.
80
+ * With no clone the setup leaves an untracked directory. */
81
+ cloned?: boolean;
82
+ /** The repository this contributor settled, as `owner/repo`: a forge names
83
+ * the one it cloned so a later role can offer it instead of asking again.
84
+ * Absent means no repository was settled. */
85
+ repository?: string;
86
+ }
87
+ /** The declaration a plugin package makes at its `./setupProject` subpath:
88
+ * the role it serves and the flow that sets a project up for it. A package
89
+ * with no `./setupProject` export serves no role and is never offered. */
90
+ export interface ProjectContributor {
91
+ /** The role this contributor serves from core's system vocabulary. Absent
92
+ * means it serves **no role**: it jumps in *after* the roles (forge, then
93
+ * tracker) are done, and adds its own project section — a plugin that wants
94
+ * a say in a new project without claiming to be the source host or the
95
+ * ticket system. */
96
+ role?: ProjectRole;
97
+ setup(ctx: ProjectSetupContext): Promise<ProjectSetupResult>;
98
+ }
99
+ /** The three answers to the lane question — "What kind of lane is this?" —
100
+ * in the person's own words. Every tracker wizard asks in these words: the
101
+ * explanation prints the labels, the question offers them, so they live in
102
+ * the kit once. `agent` asks who
103
+ * works the lane; `human` writes a bare lane — aivi ignores its tickets,
104
+ * and a finished run may move one in; `none` leaves the option out of the
105
+ * array: aivi never reads it and never moves a ticket into it. */
106
+ export declare const LANE_KIND: {
107
+ readonly agent: {
108
+ readonly value: 'agent';
109
+ readonly label: 'Agent: aivi delegates tickets in this lane to an agent';
110
+ };
111
+ readonly human: {
112
+ readonly value: 'human';
113
+ readonly label: 'Human: aivi ignores tickets in this lane; it is only ever used as a queue or a destination';
114
+ };
115
+ readonly none: {
116
+ readonly value: 'none';
117
+ readonly label: 'None: this lane is not part of aivi’s workflow';
118
+ };
119
+ };
120
+ /** The three answers as a type: whatever the person picked is one of them. */
121
+ export type LaneKind = (typeof LANE_KIND)[keyof typeof LANE_KIND]['value'];
122
+ /** The explanation — **one log entry before the first lane question**,
123
+ * never repeated per option. The queue sentence belongs in the same entry:
124
+ * a queue is the other thing a no-agent lane can be, and the difference
125
+ * between the two no-agent answers is the whole
126
+ * point of asking. `note` is the wizard's own first line — a tracker that
127
+ * cannot tell what an option means (GitHub) says so here. */
128
+ export declare function laneKindLines(note?: string): string[];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The **project-setup** contract: how a plugin helps set up a new project for
3
+ * the *role* it plays (`forge` or `tracker` — core's system vocabulary, never
4
+ * a plugin name). `aivi projects add` walks the roles in order and, for each,
5
+ * runs the one configured plugin that serves it; the plugin asks its own
6
+ * platform questions and hands back the config section core writes under the
7
+ * project. Core writes bytes and reads none of them — the types live in the
8
+ * plugin's own `projectSchema`. A forge owns the checkout (it clones); a
9
+ * plugin that clones says so, and with no forge the setup leaves an untracked
10
+ * directory instead.
11
+ *
12
+ * Everything platform-specific lives in the plugin; the runner only wires the
13
+ * context (the same clack, fetch and store the plugin's `./setup` gets) and
14
+ * threads the project name from one role to the next so it is asked once.
15
+ */
16
+ import { PluginSetupCancelled } from './setup.js';
17
+ export { PluginSetupCancelled };
18
+ /** The three answers to the lane question — "What kind of lane is this?" —
19
+ * in the person's own words. Every tracker wizard asks in these words: the
20
+ * explanation prints the labels, the question offers them, so they live in
21
+ * the kit once. `agent` asks who
22
+ * works the lane; `human` writes a bare lane — aivi ignores its tickets,
23
+ * and a finished run may move one in; `none` leaves the option out of the
24
+ * array: aivi never reads it and never moves a ticket into it. */
25
+ export const LANE_KIND = {
26
+ agent: { value: 'agent', label: 'Agent: aivi delegates tickets in this lane to an agent' },
27
+ human: {
28
+ value: 'human',
29
+ label: 'Human: aivi ignores tickets in this lane; it is only ever used as a queue or a destination',
30
+ },
31
+ none: { value: 'none', label: 'None: this lane is not part of aivi’s workflow' },
32
+ };
33
+ /** The explanation — **one log entry before the first lane question**,
34
+ * never repeated per option. The queue sentence belongs in the same entry:
35
+ * a queue is the other thing a no-agent lane can be, and the difference
36
+ * between the two no-agent answers is the whole
37
+ * point of asking. `note` is the wizard's own first line — a tracker that
38
+ * cannot tell what an option means (GitHub) says so here. */
39
+ export function laneKindLines(note) {
40
+ return [
41
+ ...(note ? [note] : []),
42
+ 'Every option is asked, in board order. What each answer means:',
43
+ ` ${LANE_KIND.agent.label}`,
44
+ ` ${LANE_KIND.human.label}`,
45
+ ` ${LANE_KIND.none.label}`,
46
+ 'A queue is where tickets wait while the working lanes are full: aivi moves the next ticket out of it as soon as a slot is free.',
47
+ ];
48
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The install contract an installable plugin package may fill. `aivi add`
3
+ * npm-installs the package into the server home and then runs its `./setup`
4
+ * entry — if it has one: a plugin whose `./config` export is its whole say at
5
+ * add time joins the list on that alone, the editor schema composed from it.
6
+ * The CLI knows nothing platform-specific. Everything the flow needs arrives
7
+ * through the context, so the flow prints its own platform instructions and
8
+ * is testable without a terminal or a platform account.
9
+ */
10
+ import type { OutputBlock } from '@aivi/core';
11
+ import type * as prompts from '@clack/prompts';
12
+ import type { Store } from './module.ts';
13
+ /** A prompt the person cancelled; the runner says it stopped and writes nothing further. */
14
+ export declare class PluginSetupCancelled extends Error {
15
+ }
16
+ /** Clack answers Ctrl+C with its cancel symbol and an empty Enter with
17
+ * nothing — neither is an answer. The flow stops with
18
+ * `PluginSetupCancelled` and the runner says the one cancel line; prompts
19
+ * that must not be empty say so in their `validate`, so clack re-asks
20
+ * before this ever sees the gap. */
21
+ export declare function settled<T>(ctx: PluginSetupContext, answer: T): Exclude<NonNullable<T>, symbol>;
22
+ export interface PluginSetupContext {
23
+ /** The aivi home that owns `config.json` and `.env`. */
24
+ home: string;
25
+ configPath: string;
26
+ /** The persona from config.json `identity.name` — what the bot is called on the platform. */
27
+ identityName: string;
28
+ /** The raw parsed `config.json`, so a flow can see what is already configured. */
29
+ config: Record<string, unknown>;
30
+ /** The command output record on stdout: raw JSON text on the terminal when the
31
+ * value is for copying (core's `print`), so paste-able output stays paste-able. */
32
+ print(value: unknown, output?: OutputBlock[] | string): void;
33
+ /** The very `@clack/prompts` module the runner renders with, handed over
34
+ * so a flow draws all its own lines — notes, log lines, prompts,
35
+ * spinners — with the same instance and style as the runner's intro and
36
+ * cancel line. Whoever is called owns the screen: two clacks animating
37
+ * one terminal mangle each other (measured 2026-09-27), so a flow opens
38
+ * one animation at a time, prints nothing beside it, and settles it
39
+ * before returning or throwing. A prompt cancelled on Ctrl+C is no
40
+ * answer: the flow throws `PluginSetupCancelled` and nothing further
41
+ * is written. */
42
+ prompts: typeof prompts;
43
+ /** Platform calls the flow verifies with; the runner supplies the real fetch. */
44
+ fetch(url: string, init?: RequestInit): Promise<Response>;
45
+ /** The home's SQLite store, open for the callback and closed after, whatever
46
+ * the callback does. A flow reads what the server has recorded (a webhook's
47
+ * diary) without importing the server: `Store` is named here as a type, the
48
+ * runner opens the database. */
49
+ withStore<T>(fn: (store: Store) => T | Promise<T>): Promise<T>;
50
+ /** Write this plugin's block into `config.json`; the file must load again or the old bytes return. */
51
+ writeConfigBlock(path: string[], value: unknown): Promise<void>;
52
+ /** Replace-or-append one key in `<home>/.env`, kept 0600; the value is never echoed. */
53
+ writeSecret(key: string, value: string): Promise<void>;
54
+ }
55
+ export interface PluginSetupResult {
56
+ /** The module the flow enabled — the id `aivi add` watches in `/status`. */
57
+ module: string;
58
+ /** The verified last line: what is true now, never what may happen. */
59
+ summary: string;
60
+ }
61
+ /** Every plugin's `./setup` entry: one default-exported function. */
62
+ export type PluginSetup = (ctx: PluginSetupContext) => Promise<PluginSetupResult>;
package/dist/setup.js ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The install contract an installable plugin package may fill. `aivi add`
3
+ * npm-installs the package into the server home and then runs its `./setup`
4
+ * entry — if it has one: a plugin whose `./config` export is its whole say at
5
+ * add time joins the list on that alone, the editor schema composed from it.
6
+ * The CLI knows nothing platform-specific. Everything the flow needs arrives
7
+ * through the context, so the flow prints its own platform instructions and
8
+ * is testable without a terminal or a platform account.
9
+ */
10
+ /** A prompt the person cancelled; the runner says it stopped and writes nothing further. */
11
+ export class PluginSetupCancelled extends Error {
12
+ }
13
+ /** Clack answers Ctrl+C with its cancel symbol and an empty Enter with
14
+ * nothing — neither is an answer. The flow stops with
15
+ * `PluginSetupCancelled` and the runner says the one cancel line; prompts
16
+ * that must not be empty say so in their `validate`, so clack re-asks
17
+ * before this ever sees the gap. */
18
+ export function settled(ctx, answer) {
19
+ if (ctx.prompts.isCancel(answer))
20
+ throw new PluginSetupCancelled('a prompt was cancelled');
21
+ if (answer === undefined)
22
+ throw new PluginSetupCancelled('a prompt was submitted empty');
23
+ return answer;
24
+ }