@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.
- package/README.md +29 -2
- package/dist/api.d.ts +10 -0
- package/dist/api.js +147 -0
- package/dist/channel.d.ts +184 -0
- package/dist/channel.js +13 -0
- package/dist/cli.d.ts +71 -0
- package/dist/cli.js +26 -0
- package/dist/dispatcher.d.ts +67 -0
- package/dist/dispatcher.js +13 -0
- package/dist/forge.d.ts +243 -0
- package/dist/forge.js +30 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +22 -0
- package/dist/module.d.ts +306 -0
- package/dist/module.js +28 -0
- package/dist/plugin.d.ts +38 -0
- package/dist/plugin.js +13 -0
- package/dist/run.d.ts +144 -0
- package/dist/run.js +18 -0
- package/dist/setup-project.d.ts +128 -0
- package/dist/setup-project.js +48 -0
- package/dist/setup.d.ts +62 -0
- package/dist/setup.js +24 -0
- package/dist/tracker.d.ts +473 -0
- package/dist/tracker.js +38 -0
- package/dist/voice.d.ts +148 -0
- package/dist/voice.js +23 -0
- package/package.json +41 -4
package/dist/plugin.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/setup.d.ts
ADDED
|
@@ -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
|
+
}
|