@alisio/alisio-code 0.1.0-alpha.9 → 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 +95 -16
- package/dist/analysis.d.ts +27 -0
- package/dist/analysis.js +145 -0
- package/dist/banner.js +2 -1
- package/dist/builtin.d.ts +3 -1
- package/dist/builtin.js +0 -24
- package/dist/main.js +133 -6
- package/dist/serve.d.ts +15 -0
- package/dist/serve.js +99 -0
- package/dist/tui/agent-manager.d.ts +105 -0
- package/dist/tui/agent-manager.js +269 -0
- package/dist/tui/agents.d.ts +5 -0
- package/dist/tui/agents.js +5 -0
- package/dist/tui/app.d.ts +42 -1
- package/dist/tui/app.js +1895 -97
- package/dist/tui/artifact-preview.d.ts +52 -0
- package/dist/tui/artifact-preview.js +212 -0
- package/dist/tui/artifacts.d.ts +54 -0
- package/dist/tui/artifacts.js +207 -0
- package/dist/tui/btw.d.ts +57 -0
- package/dist/tui/btw.js +76 -0
- package/dist/tui/components.d.ts +174 -12
- package/dist/tui/components.js +962 -66
- package/dist/tui/connect-input.d.ts +2 -0
- package/dist/tui/connect-input.js +1 -1
- package/dist/tui/exit.d.ts +10 -0
- package/dist/tui/exit.js +15 -0
- package/dist/tui/git-branch.d.ts +48 -0
- package/dist/tui/git-branch.js +99 -0
- package/dist/tui/goal.d.ts +75 -0
- package/dist/tui/goal.js +171 -0
- package/dist/tui/modes.d.ts +71 -0
- package/dist/tui/modes.js +90 -0
- package/dist/tui/open-path.d.ts +38 -0
- package/dist/tui/open-path.js +80 -0
- package/dist/tui/plan-review.d.ts +48 -0
- package/dist/tui/plan-review.js +76 -0
- package/dist/tui/queue.d.ts +2 -0
- package/dist/tui/queue.js +5 -1
- package/dist/tui/scroll.d.ts +8 -0
- package/dist/tui/scroll.js +12 -0
- package/dist/tui/settings-menu.d.ts +40 -0
- package/dist/tui/settings-menu.js +132 -0
- package/dist/tui/settings.d.ts +194 -0
- package/dist/tui/settings.js +482 -0
- package/dist/tui/state.d.ts +343 -7
- package/dist/tui/state.js +823 -54
- package/dist/tui/tasks.d.ts +123 -0
- package/dist/tui/tasks.js +369 -0
- package/dist/tui/theme.d.ts +42 -1
- package/dist/tui/theme.js +516 -1
- package/dist/tui/viewer-state.d.ts +7 -0
- package/dist/tui/viewer-state.js +36 -0
- package/dist/version.d.ts +7 -5
- package/dist/version.js +44 -8
- package/package.json +7 -9
package/README.md
CHANGED
|
@@ -1,23 +1,102 @@
|
|
|
1
|
-
# alisio
|
|
1
|
+
# @alisio/alisio-code
|
|
2
2
|
|
|
3
|
-
**Alisio is an extensible, provider-agnostic coding-agent harness for your terminal.** It pairs a
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
**Alisio is an extensible, provider-agnostic coding-agent harness for your terminal.** It pairs a fast
|
|
4
|
+
TUI with any OpenAI-compatible model, runs local tools behind explicit permissions, compacts long
|
|
5
|
+
conversations into structured checkpoints, remembers decisions across sessions with an Engram-style
|
|
6
|
+
memory plugin, and exposes a typed plugin SDK.
|
|
7
|
+
|
|
8
|
+
## What it is
|
|
9
|
+
|
|
10
|
+
`alisio` is the CLI of the Alisio project. It connects to any OpenAI-compatible endpoint (Chat
|
|
11
|
+
Completions or Responses), drives its own agent tool loop, and runs interactively as a TUI or
|
|
12
|
+
headless for scripts and CI. Provider SDKs stay out of the harness: the CLI ships built-in DeepSeek,
|
|
13
|
+
OpenAI-compatible, OpenCode Console (Zen) and OpenCode Go providers, plus memory and subagents
|
|
14
|
+
plugins.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm i -g @alisio/alisio-code # or: pnpm add -g @alisio/alisio-code · bun add -g @alisio/alisio-code
|
|
20
|
+
alisio # the binary is installed as `alisio`
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Standalone binaries (Bun embedded, no Node required) are attached to GitHub releases.
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
The fastest path is the TUI's `/connect` form: pick a provider, enter its settings and key, choose a
|
|
28
|
+
model, and Alisio persists the selection globally (`/model` re-opens the same selector). A plain
|
|
29
|
+
config start looks like:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
alisio setup # writes a secret-free .alisio/config.json in .alisio/
|
|
33
|
+
export DEEPSEEK_API_KEY=... # keys come from environment variables or /connect, never config files
|
|
34
|
+
alisio --config .alisio/config.json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Run one prompt headless, emitting versioned JSONL events:
|
|
7
38
|
|
|
8
39
|
```sh
|
|
9
|
-
|
|
10
|
-
alisio
|
|
11
|
-
export DEEPSEEK_API_KEY=... # keys are read from environment variables only
|
|
12
|
-
alisio --config ./alisio.json
|
|
40
|
+
alisio run "Summarize this repository" --json
|
|
41
|
+
alisio run "/init" --allow-write # generate AGENTS.md from the built-in /init template
|
|
13
42
|
```
|
|
14
43
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
44
|
+
## Commands
|
|
45
|
+
|
|
46
|
+
| Command | Description |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `alisio` | Interactive TUI (readline with `--no-tui`) |
|
|
49
|
+
| `alisio run <prompt>` | Headless run; `/name args` runs a prompt template |
|
|
50
|
+
| `alisio resume <session> [prompt]` | Resume a session |
|
|
51
|
+
| `alisio setup` | Write a secret-free example `.alisio/config.json` |
|
|
52
|
+
| `alisio doctor` | Environment and provider diagnostics |
|
|
53
|
+
| `alisio trust list` / `trust revoke <path>` | Inspect or revoke per-directory project trust |
|
|
54
|
+
| `alisio sessions list` / `sessions recover <id> --acknowledge` | List sessions; recover after a crash |
|
|
55
|
+
| `alisio skills list` / `skills validate` | Discover and validate Agent Skills |
|
|
56
|
+
| `alisio plugins list` / `plugins doctor` | List/load plugins and show their tools and commands |
|
|
57
|
+
| `alisio mcp list` / `mcp doctor <server>` | List configured MCP servers; connect to one (`--allow-mcp`) |
|
|
58
|
+
| `alisio context explain <path>` | Show which `AGENTS.md` files apply to a path |
|
|
59
|
+
| `alisio install npm:<package>` | Install an npm plugin into the global plugins directory |
|
|
60
|
+
|
|
61
|
+
## Permissions and key flags
|
|
62
|
+
|
|
63
|
+
Reads run by default; anything else needs a flag or interactive approval:
|
|
64
|
+
|
|
65
|
+
| Flag | Effect |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `--allow-write` | Allow file writes |
|
|
68
|
+
| `--allow-process` | Allow arbitrary subprocesses (not sandboxed) |
|
|
69
|
+
| `--allow-external` | Allow network tools: `webfetch`, `websearch`, provider-native search |
|
|
70
|
+
| `--allow-mcp` | Allow configured MCP servers and remote tool calls |
|
|
71
|
+
| `--allow-agents` | Allow messaging neighboring agents through Herdr |
|
|
72
|
+
| `--read-only` | Disable writes, subprocesses, network tools, executable plugins and MCP |
|
|
73
|
+
| `--plugin <path…>` / `--trust-project` | Load explicitly trusted plugins / trust project config |
|
|
74
|
+
| `--disable-plugin <ids…>` | Disable built-in plugins (e.g. `memory`) |
|
|
75
|
+
| `--json` | Emit versioned JSONL events (headless) |
|
|
76
|
+
|
|
77
|
+
Other global flags: `--cwd`, `--config`, `--model`, `--base-url`, `--api-mode`, `--db`,
|
|
78
|
+
`--no-tui`, `--no-banner`, `--no-herdr`, `--agents <json>`, `--quiet`, `-V`, `-h`.
|
|
79
|
+
|
|
80
|
+
## In the TUI
|
|
81
|
+
|
|
82
|
+
`/connect` adds a provider; `/model` picks a saved provider/model; `/compact`, `/stats`, `/clear`,
|
|
83
|
+
`/sessions`, `/resume` manage conversations; `/plugins`, `/skills`, `/mcps`, `/settings` manage
|
|
84
|
+
extensions and preferences; `/memory` and `/agents` drive the built-in plugins; `/init` writes
|
|
85
|
+
`AGENTS.md`.
|
|
86
|
+
|
|
87
|
+
## Requirements
|
|
88
|
+
|
|
89
|
+
Node.js **>= 22.16** (`node:sqlite` with FTS5) or Bun **>= 1.4.2**. Git and ripgrep on `PATH` for the
|
|
90
|
+
Git and search tools; `alisio doctor` reports what it detects.
|
|
91
|
+
|
|
92
|
+
## Docs
|
|
93
|
+
|
|
94
|
+
Documentation (EN/ES): <https://gustavogutierrez.github.io/alisio/> — [quick start](https://gustavogutierrez.github.io/alisio/quick-start),
|
|
95
|
+
[configuration](https://gustavogutierrez.github.io/alisio/configuration), [plugins](https://gustavogutierrez.github.io/alisio/plugins),
|
|
96
|
+
[subagents](https://gustavogutierrez.github.io/alisio/subagents), [memory](https://gustavogutierrez.github.io/alisio/memory),
|
|
97
|
+
[TUI](https://gustavogutierrez.github.io/alisio/tui).
|
|
20
98
|
|
|
21
|
-
|
|
99
|
+
## License
|
|
22
100
|
|
|
23
|
-
|
|
101
|
+
MIT. Maintained by Gustavo Gutiérrez Mercado. Source: <https://github.com/GustavoGutierrez/alisio> ·
|
|
102
|
+
npm: <https://www.npmjs.com/settings/alisio/packages>.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AnalysisStatus } from "@alisio/sdk";
|
|
2
|
+
type Common = {
|
|
3
|
+
db?: string;
|
|
4
|
+
python?: string;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
config?: string;
|
|
7
|
+
trustProject?: boolean;
|
|
8
|
+
};
|
|
9
|
+
/** The unified runtime state and, when Python is missing, the install guidance for this system. */
|
|
10
|
+
export declare function analysisState(o: Common): Promise<{
|
|
11
|
+
status: AnalysisStatus;
|
|
12
|
+
guidance?: string;
|
|
13
|
+
ignored: string[];
|
|
14
|
+
}>;
|
|
15
|
+
/** Prints `alisio analysis status`. */
|
|
16
|
+
export declare function printAnalysisStatus(o: Common): Promise<void>;
|
|
17
|
+
/** `alisio analysis setup --extras analysis|science`: needs network; offline it fails cleanly. */
|
|
18
|
+
export declare function setupExtras(o: Common, extras: string | undefined): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* `alisio analysis setup --oci [--image <name>]`: pulls the image once and verifies it. With a
|
|
21
|
+
* digest-pinned reference the pull verifies the digest; with a tag it prints the digest to put in
|
|
22
|
+
* `analysis.oci.image` (Alisio never writes it: only you choose what runs your code).
|
|
23
|
+
*/
|
|
24
|
+
export declare function setupOci(o: Common, image: string | undefined): Promise<void>;
|
|
25
|
+
/** `alisio analysis sweep [--force]`: the retention sweep now (it also runs by itself, daily). */
|
|
26
|
+
export declare function sweep(o: Common, force: boolean): Promise<void>;
|
|
27
|
+
export {};
|
package/dist/analysis.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `alisio analysis …` and the analysis line of `alisio doctor`: the state of the Python runtime,
|
|
3
|
+
* the optional extras (`setup --extras`, hash-locked, needs network), the optional container
|
|
4
|
+
* runtime (`setup --oci`) and the retention sweep (`sweep`). None of these is needed to run
|
|
5
|
+
* `python_run`: the base runtime is the discovered Python with only the standard library.
|
|
6
|
+
*/
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
async function context(o) {
|
|
9
|
+
const core = await import("@alisio/core");
|
|
10
|
+
const workspace = await core.findWorkspace(o.cwd ?? process.cwd());
|
|
11
|
+
const { config, provenance } = await core.loadConfigWithProvenance(workspace, {
|
|
12
|
+
file: o.config,
|
|
13
|
+
trustProject: o.trustProject,
|
|
14
|
+
});
|
|
15
|
+
const stateDir = o.db && o.db !== ":memory:" ? dirname(o.db) : core.stateHome();
|
|
16
|
+
const manager = new core.AnalysisRuntimeManager({
|
|
17
|
+
stateDir,
|
|
18
|
+
...(o.python ? { python: o.python } : {}),
|
|
19
|
+
});
|
|
20
|
+
const oci = () => new core.OciRuntime({ ...config.analysis.oci });
|
|
21
|
+
return { core, config, provenance, stateDir, manager, oci };
|
|
22
|
+
}
|
|
23
|
+
/** The unified runtime state and, when Python is missing, the install guidance for this system. */
|
|
24
|
+
export async function analysisState(o) {
|
|
25
|
+
const { core, config, provenance, stateDir, manager, oci } = await context(o);
|
|
26
|
+
const janitor = {
|
|
27
|
+
lastSweep: () => import("node:fs/promises")
|
|
28
|
+
.then((fs) => fs.readFile(join(stateDir, "analysis", ".last-sweep"), "utf8"))
|
|
29
|
+
.then((text) => {
|
|
30
|
+
const value = Number(text.trim());
|
|
31
|
+
return Number.isFinite(value) ? value : undefined;
|
|
32
|
+
})
|
|
33
|
+
.catch(() => undefined),
|
|
34
|
+
};
|
|
35
|
+
const status = await core.analysisStatus({
|
|
36
|
+
config: config.analysis,
|
|
37
|
+
runtime: manager,
|
|
38
|
+
oci,
|
|
39
|
+
janitor,
|
|
40
|
+
enabled: config.analysis.enabled,
|
|
41
|
+
readOnly: false,
|
|
42
|
+
});
|
|
43
|
+
const guidance = status.python.found
|
|
44
|
+
? undefined
|
|
45
|
+
: (async () => {
|
|
46
|
+
const resolved = await manager.interpreter();
|
|
47
|
+
return resolved.ok
|
|
48
|
+
? undefined
|
|
49
|
+
: core.installHintsText(core.pythonInstallHints(await core.detectHintHost(resolved.found)));
|
|
50
|
+
})();
|
|
51
|
+
return {
|
|
52
|
+
status,
|
|
53
|
+
...(guidance ? { guidance: await guidance } : {}),
|
|
54
|
+
ignored: provenance.ignored ?? [],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Prints `alisio analysis status`. */
|
|
58
|
+
export async function printAnalysisStatus(o) {
|
|
59
|
+
const { status, guidance, ignored } = await analysisState(o);
|
|
60
|
+
console.log(JSON.stringify(status, null, 2));
|
|
61
|
+
if (guidance)
|
|
62
|
+
process.stderr.write(`\n${guidance}\n`);
|
|
63
|
+
if (status.mode === "oci" && status.oci.available === false)
|
|
64
|
+
process.stderr.write(`\nThe container runtime is configured but not usable: ${status.oci.reason ?? "unknown reason"}\n`);
|
|
65
|
+
for (const key of ignored)
|
|
66
|
+
process.stderr.write(`\nIgnored ${key} from the project configuration: only your user configuration decides it.\n`);
|
|
67
|
+
}
|
|
68
|
+
/** `alisio analysis setup --extras analysis|science`: needs network; offline it fails cleanly. */
|
|
69
|
+
export async function setupExtras(o, extras) {
|
|
70
|
+
if (extras !== "analysis" && extras !== "science")
|
|
71
|
+
throw new Error("Choose the extras to install: --extras analysis or --extras science");
|
|
72
|
+
const { core, manager } = await context(o);
|
|
73
|
+
const preview = core.installPreview(extras);
|
|
74
|
+
const controller = new AbortController();
|
|
75
|
+
process.on("SIGINT", () => controller.abort(new Error("Interrupted")));
|
|
76
|
+
process.stderr.write(`Installing the ${extras} extras: ${preview.packages.join(", ")} (+ dependencies, ${preview.packageCount} packages, about ${Math.round(preview.estimatedBytes / (1024 * 1024))} MB). ` +
|
|
77
|
+
"Wheels only, hashes required, needs network.\n");
|
|
78
|
+
try {
|
|
79
|
+
const state = await manager.installExtras(extras, {
|
|
80
|
+
signal: controller.signal,
|
|
81
|
+
onOutput: (text) => process.stderr.write(text),
|
|
82
|
+
});
|
|
83
|
+
console.log(JSON.stringify(state, null, 2));
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
// A clean message and a failing exit code; the base runtime is untouched.
|
|
87
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* `alisio analysis setup --oci [--image <name>]`: pulls the image once and verifies it. With a
|
|
93
|
+
* digest-pinned reference the pull verifies the digest; with a tag it prints the digest to put in
|
|
94
|
+
* `analysis.oci.image` (Alisio never writes it: only you choose what runs your code).
|
|
95
|
+
*/
|
|
96
|
+
export async function setupOci(o, image) {
|
|
97
|
+
const { core, config } = await context(o);
|
|
98
|
+
const reference = image ?? config.analysis.oci.image;
|
|
99
|
+
if (!reference)
|
|
100
|
+
throw new Error("Pass --image <name[:tag]> (or set analysis.oci.image in your user configuration) to pull.");
|
|
101
|
+
const runtime = new core.OciRuntime({ ...config.analysis.oci, image: undefined });
|
|
102
|
+
const status = await runtime.status();
|
|
103
|
+
if (!status.ok)
|
|
104
|
+
throw new Error(`${status.reason}${status.alternative ? `; ${status.alternative} is installed (set analysis.oci.engine to "${status.alternative}")` : ""}. The container runtime is optional; python_run works without it.`);
|
|
105
|
+
process.stderr.write(`Pulling ${reference} with ${status.engine} ${status.version}…\n`);
|
|
106
|
+
const controller = new AbortController();
|
|
107
|
+
process.on("SIGINT", () => controller.abort(new Error("Interrupted")));
|
|
108
|
+
const { digest } = await runtime.pull(reference, controller.signal);
|
|
109
|
+
const pinned = core.OCI_IMAGE_PATTERN.test(reference);
|
|
110
|
+
if (pinned) {
|
|
111
|
+
const wanted = reference.slice(reference.indexOf("@") + 1);
|
|
112
|
+
if (digest && !digest.endsWith(wanted))
|
|
113
|
+
throw new Error(`The pulled image reports ${digest}, not the pinned ${wanted}`);
|
|
114
|
+
console.log(`Verified ${reference}. Set analysis.runtime to "oci" to use it.`);
|
|
115
|
+
}
|
|
116
|
+
else if (digest)
|
|
117
|
+
console.log(`Pulled ${reference}. To use it, put this in your user configuration (${core.configHome()}):\n` +
|
|
118
|
+
` "analysis": { "runtime": "oci", "oci": { "engine": "${status.engine}", "image": "${digest}" } }`);
|
|
119
|
+
else
|
|
120
|
+
throw new Error(`${reference} has no repository digest (a locally built image?). Push it to a registry or pull a published image.`);
|
|
121
|
+
}
|
|
122
|
+
/** `alisio analysis sweep [--force]`: the retention sweep now (it also runs by itself, daily). */
|
|
123
|
+
export async function sweep(o, force) {
|
|
124
|
+
const { core, config, stateDir } = await context(o);
|
|
125
|
+
const store = new core.SQLiteStore(o.db ?? join(stateDir, "sessions.sqlite"));
|
|
126
|
+
try {
|
|
127
|
+
const artifacts = new core.ArtifactStore({ root: stateDir, db: store.db });
|
|
128
|
+
const datasets = new core.DatasetService({ root: stateDir, db: store.db });
|
|
129
|
+
const blobs = new core.BlobStore({ root: join(stateDir, "blobs"), db: store.db });
|
|
130
|
+
const janitor = new core.AnalysisJanitor({
|
|
131
|
+
root: stateDir,
|
|
132
|
+
db: store.db,
|
|
133
|
+
artifacts,
|
|
134
|
+
datasets,
|
|
135
|
+
blobs,
|
|
136
|
+
retention: () => config.analysis.retention,
|
|
137
|
+
});
|
|
138
|
+
const report = await janitor.sweep({ force });
|
|
139
|
+
console.log(JSON.stringify(report, null, 2));
|
|
140
|
+
datasets.close();
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
store.close();
|
|
144
|
+
}
|
|
145
|
+
}
|
package/dist/banner.js
CHANGED
|
@@ -21,7 +21,8 @@ export function terminalCapabilities(input) {
|
|
|
21
21
|
const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
|
|
22
22
|
return {
|
|
23
23
|
color: input.tty && !dumb && env.NO_COLOR === undefined,
|
|
24
|
-
|
|
24
|
+
// `C.UTF-8` is a UTF-8 locale (the default on CI runners); only bare C/POSIX is ASCII.
|
|
25
|
+
unicode: !dumb && (/utf-?8/i.test(locale) || !/^(C|POSIX)(\.|$)/i.test(locale)),
|
|
25
26
|
columns: input.columns > 0 ? input.columns : 80,
|
|
26
27
|
interactive: input.tty,
|
|
27
28
|
};
|
package/dist/builtin.d.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Built-in plugin registry wired by the CLI. Built-ins load through the trusted path of the
|
|
3
3
|
* plugin host (unprefixed names, `internal` effect) and can be disabled by configuration
|
|
4
4
|
* (`builtinPlugins.<id>.enabled: false`) or `--disable-plugin <id>`. Add a built-in by
|
|
5
|
-
* appending an entry here.
|
|
5
|
+
* appending an entry here. Dedicated model providers (DeepSeek, OpenCode Console, OpenCode Go)
|
|
6
|
+
* are no longer built-ins: users install them independently via
|
|
7
|
+
* `alisio install npm:@alisio/plugin-{deepseek,opencode,opencode-go}`.
|
|
6
8
|
*/
|
|
7
9
|
import type { BuiltinPlugin } from "@alisio/core";
|
|
8
10
|
export declare const BUILTIN_PLUGINS: BuiltinPlugin[];
|
package/dist/builtin.js
CHANGED
|
@@ -1,31 +1,7 @@
|
|
|
1
|
-
import { createDeepSeekPlugin } from "@alisio/plugin-deepseek";
|
|
2
1
|
import { createMemoryPlugin } from "@alisio/plugin-memory";
|
|
3
2
|
import { createOpenAICompatiblePlugin } from "@alisio/plugin-openai-compatible";
|
|
4
|
-
import { createOpenCodePlugin } from "@alisio/plugin-opencode";
|
|
5
|
-
import { createOpenCodeGoPlugin } from "@alisio/plugin-opencode-go";
|
|
6
3
|
import { createSubagentsPlugin } from "@alisio/plugin-subagents";
|
|
7
4
|
export const BUILTIN_PLUGINS = [
|
|
8
|
-
{
|
|
9
|
-
id: "deepseek",
|
|
10
|
-
name: "DeepSeek",
|
|
11
|
-
description: "Dedicated DeepSeek Chat Completions and Responses provider",
|
|
12
|
-
categories: ["model-provider"],
|
|
13
|
-
create: createDeepSeekPlugin,
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
id: "opencode",
|
|
17
|
-
name: "OpenCode Console (Zen)",
|
|
18
|
-
description: "Dedicated OpenCode Console (Zen) multi-protocol provider",
|
|
19
|
-
categories: ["model-provider"],
|
|
20
|
-
create: createOpenCodePlugin,
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
id: "opencode-go",
|
|
24
|
-
name: "OpenCode Go",
|
|
25
|
-
description: "Dedicated OpenCode Go multi-protocol provider",
|
|
26
|
-
categories: ["model-provider"],
|
|
27
|
-
create: createOpenCodeGoPlugin,
|
|
28
|
-
},
|
|
29
5
|
{
|
|
30
6
|
id: "openai-compatible",
|
|
31
7
|
name: "OpenAI compatible",
|
package/dist/main.js
CHANGED
|
@@ -35,9 +35,12 @@ program
|
|
|
35
35
|
.option("--api-mode <mode>", "chat or responses")
|
|
36
36
|
.option("--allow-write", "Allow file writes")
|
|
37
37
|
.option("--allow-process", "Allow arbitrary subprocesses; not sandboxed")
|
|
38
|
+
.option("--allow-analysis", "Allow Python analysis (python_run) without asking; not sandboxed, never allows shell")
|
|
39
|
+
.option("--python <path>", "Python 3.10+ interpreter for python_run (default: discovered)")
|
|
38
40
|
.option("--allow-external", "Allow network tools: webfetch, websearch and provider-native search")
|
|
39
41
|
.option("--allow-mcp", "Allow configured MCP servers and remote tool calls")
|
|
40
42
|
.option("--allow-agents", "Allow messaging neighboring agents through Herdr")
|
|
43
|
+
.option("--add-dir <paths...>", "Additional directories tools may read/write outside the workspace (repeatable)")
|
|
41
44
|
.option("--no-herdr", "Disable automatic Herdr lifecycle reports")
|
|
42
45
|
.option("--read-only", "Disable writes, arbitrary processes, network tools, executable plugins and MCP")
|
|
43
46
|
.option("--db <path>", "Session database")
|
|
@@ -54,6 +57,7 @@ const options = (cmd) => {
|
|
|
54
57
|
baseURL: o.baseUrl,
|
|
55
58
|
noHerdr: o.herdr === false,
|
|
56
59
|
disablePlugins: o.disablePlugin,
|
|
60
|
+
addDirs: o.addDir,
|
|
57
61
|
...(o.agents
|
|
58
62
|
? { pluginOptions: { subagents: { agents: parseAgents(String(o.agents)) } } }
|
|
59
63
|
: {}),
|
|
@@ -114,7 +118,7 @@ async function run(cmd, prompt, sessionId) {
|
|
|
114
118
|
const trusted = await withProjectTrust(opts);
|
|
115
119
|
return runTui({ ...trusted, ...(sessionId ? { session: sessionId } : {}) });
|
|
116
120
|
}
|
|
117
|
-
const { createApplication } = await import("@alisio/core");
|
|
121
|
+
const { createApplication, formatBytes, KIND_LABELS } = await import("@alisio/core");
|
|
118
122
|
const app = await createApplication({
|
|
119
123
|
...(await cliDefaults()),
|
|
120
124
|
...opts,
|
|
@@ -123,12 +127,40 @@ async function run(cmd, prompt, sessionId) {
|
|
|
123
127
|
process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
124
128
|
else if (event.type === "text_delta")
|
|
125
129
|
process.stdout.write(String(event.data.delta));
|
|
130
|
+
else if (event.type === "request_retry") {
|
|
131
|
+
const retry = event.data;
|
|
132
|
+
process.stderr.write(`\nThe model did not respond; retrying (${retry.attempt}/${retry.of})…\n`);
|
|
133
|
+
}
|
|
134
|
+
else if (event.type === "truncation_recovery") {
|
|
135
|
+
const recovery = event.data;
|
|
136
|
+
process.stderr.write(`\nThe response was cut off by the output limit; retrying in smaller steps (${recovery.attempt}/${recovery.of})…\n`);
|
|
137
|
+
}
|
|
126
138
|
else if (event.type === "tool_started")
|
|
127
139
|
process.stderr.write(`\n→ ${event.data.name}\n`);
|
|
140
|
+
else if (event.type === "artifact_published")
|
|
141
|
+
process.stderr.write(artifactLine(event));
|
|
128
142
|
},
|
|
129
143
|
});
|
|
144
|
+
/** `artifact: Dashboard site.zip (48 KB) → /abs/path` (nothing is opened automatically). */
|
|
145
|
+
function artifactLine(event) {
|
|
146
|
+
const data = event.data;
|
|
147
|
+
return `artifact: ${KIND_LABELS[data.artifact.kind] ?? "File"} ${data.artifact.fileName} (${formatBytes(data.artifact.bytes)}) → ${data.path}\n`;
|
|
148
|
+
}
|
|
130
149
|
for (const failure of app.mcpStartupFailures())
|
|
131
150
|
process.stderr.write(`[startup] ${failure}\n`);
|
|
151
|
+
// The ACTIVE agent (built-in `build`/`plan` or a main-capable definition) drives the main
|
|
152
|
+
// session here too: its system prompt is appended and a read-only agent narrows the run. The
|
|
153
|
+
// TUI adds the picker and the model switch; effort is a TUI feature (validated against the
|
|
154
|
+
// active model's catalog).
|
|
155
|
+
const { agentCatalogFromState, agentRunOptions, resolveActiveAgent } = await import("./tui/agents.js");
|
|
156
|
+
let activeRunOptions = {};
|
|
157
|
+
try {
|
|
158
|
+
const reported = app.plugins.pluginState("subagents", "mainAgents");
|
|
159
|
+
activeRunOptions = agentRunOptions(resolveActiveAgent(agentCatalogFromState(reported), app.config.agents.active));
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
/* agent resolution is best-effort */
|
|
163
|
+
}
|
|
132
164
|
const controller = new AbortController();
|
|
133
165
|
const interrupt = () => controller.abort(new Error("Interrupted"));
|
|
134
166
|
process.on("SIGINT", interrupt);
|
|
@@ -151,7 +183,10 @@ async function run(cmd, prompt, sessionId) {
|
|
|
151
183
|
const match = /^\/skill:([a-z0-9-]+)\s*([\s\S]*)$/.exec(prompt);
|
|
152
184
|
if (match?.[1])
|
|
153
185
|
prompt = `${await app.skills.load(match[1])}\n\nUser request: ${match[2] ?? ""}`;
|
|
154
|
-
await app.runner.run(session, template?.text ?? prompt, controller.signal,
|
|
186
|
+
await app.runner.run(session, template?.text ?? prompt, controller.signal, {
|
|
187
|
+
...activeRunOptions,
|
|
188
|
+
...(template ? { display: template.display } : {}),
|
|
189
|
+
});
|
|
155
190
|
if (!opts.json)
|
|
156
191
|
process.stdout.write(`\nSession: ${session}\n`);
|
|
157
192
|
return;
|
|
@@ -182,7 +217,7 @@ async function run(cmd, prompt, sessionId) {
|
|
|
182
217
|
process.stderr.write(`[startup] ${JSON.stringify(d)}\n`);
|
|
183
218
|
}
|
|
184
219
|
if (!opts.quiet)
|
|
185
|
-
console.log("Alisio · /exit /new /skill:name /command plugin.id:name args");
|
|
220
|
+
console.log("Alisio · /exit /new /btw question /skill:name /command plugin.id:name args");
|
|
186
221
|
process.stdout.write("\nalisio › ");
|
|
187
222
|
try {
|
|
188
223
|
for await (const rawLine of rl) {
|
|
@@ -199,6 +234,22 @@ async function run(cmd, prompt, sessionId) {
|
|
|
199
234
|
process.stdout.write("\nalisio › ");
|
|
200
235
|
continue;
|
|
201
236
|
}
|
|
237
|
+
if (line === "/btw" || line.startsWith("/btw ")) {
|
|
238
|
+
// Side question: printed here only, never added to the conversation.
|
|
239
|
+
try {
|
|
240
|
+
const { CommandCatalog } = await import("@alisio/core");
|
|
241
|
+
const result = await new CommandCatalog(app).execute("btw", line.slice(4), {
|
|
242
|
+
sessionId: session,
|
|
243
|
+
signal: controller.signal,
|
|
244
|
+
});
|
|
245
|
+
console.log(result.text ?? "");
|
|
246
|
+
}
|
|
247
|
+
catch (e) {
|
|
248
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
249
|
+
}
|
|
250
|
+
process.stdout.write("\nalisio › ");
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
202
253
|
if (line.startsWith("/command ")) {
|
|
203
254
|
const [name, ...args] = line.slice(9).split(" ");
|
|
204
255
|
const handler = app.plugins.commands.get(name ?? "");
|
|
@@ -211,7 +262,10 @@ async function run(cmd, prompt, sessionId) {
|
|
|
211
262
|
const input = match?.[1] ? `${await app.skills.load(match[1])}\n\n${match[2] ?? ""}` : line;
|
|
212
263
|
try {
|
|
213
264
|
const template = app.expandPrompt(input);
|
|
214
|
-
await app.runner.run(session, template?.text ?? input, controller.signal,
|
|
265
|
+
await app.runner.run(session, template?.text ?? input, controller.signal, {
|
|
266
|
+
...activeRunOptions,
|
|
267
|
+
...(template ? { display: template.display } : {}),
|
|
268
|
+
});
|
|
215
269
|
}
|
|
216
270
|
catch (e) {
|
|
217
271
|
console.error(String(e));
|
|
@@ -225,7 +279,11 @@ async function run(cmd, prompt, sessionId) {
|
|
|
225
279
|
}
|
|
226
280
|
finally {
|
|
227
281
|
process.off("SIGINT", interrupt);
|
|
282
|
+
// Background tasks end with Alisio: `run` waits for none of them, it stops them and says so.
|
|
283
|
+
const live = app.tasks.liveCount();
|
|
228
284
|
await app.close();
|
|
285
|
+
if (live)
|
|
286
|
+
process.stderr.write(`${live} background task${live === 1 ? " was" : "s were"} still running and ${live === 1 ? "was" : "were"} stopped: background tasks end when Alisio exits.\n`);
|
|
229
287
|
}
|
|
230
288
|
}
|
|
231
289
|
program
|
|
@@ -239,6 +297,19 @@ program
|
|
|
239
297
|
.argument("[prompt]")
|
|
240
298
|
.action((id, prompt, _opts, cmd) => run(cmd, prompt, id));
|
|
241
299
|
program.action((_opts, cmd) => run(cmd));
|
|
300
|
+
program
|
|
301
|
+
.command("serve")
|
|
302
|
+
.description("Start the local web UI server (HTTP + SSE)")
|
|
303
|
+
.option("--port <port>", "Port to listen on (0 picks a free one)", "4317")
|
|
304
|
+
.option("--host <address>", "Address to bind; non-loopback requires --allow-remote", "127.0.0.1")
|
|
305
|
+
.option("--allow-remote", "Allow a non-loopback --host (no TLS; prefer an SSH tunnel)")
|
|
306
|
+
.option("--no-open", "Do not open the browser")
|
|
307
|
+
.option("--max-workspaces <n>", "Maximum workspaces open at once", "4")
|
|
308
|
+
.option("--max-runs <n>", "Maximum concurrent runs across all sessions", "4")
|
|
309
|
+
.action(async (flags, cmd) => {
|
|
310
|
+
const { serve } = await import("./serve.js");
|
|
311
|
+
await serve(flags, { ...(await cliDefaults()), ...options(cmd) }, VERSION);
|
|
312
|
+
});
|
|
242
313
|
program
|
|
243
314
|
.command("setup")
|
|
244
315
|
.description("Write an example configuration without secrets (for AGENTS.md use /init)")
|
|
@@ -255,7 +326,7 @@ program
|
|
|
255
326
|
console.log('To generate AGENTS.md for this project, run /init inside alisio (or: alisio run "/init" --allow-write).');
|
|
256
327
|
});
|
|
257
328
|
program.command("doctor").action(async (_opts, cmd) => {
|
|
258
|
-
const { findWorkspace, loadConfigWithProvenance, overridesSavedProviderProfile, ProviderSettingsStore, RIPGREP_INSTALL_HINT, which, } = await import("@alisio/core");
|
|
329
|
+
const { DEFAULT_MAX_OUTPUT_TOKENS, findWorkspace, loadConfigWithProvenance, MAX_AUTO_OUTPUT_TOKENS, overridesSavedProviderProfile, ProviderSettingsStore, RIPGREP_INSTALL_HINT, which, } = await import("@alisio/core");
|
|
259
330
|
const o = options(cmd);
|
|
260
331
|
const workspace = await findWorkspace(o.cwd ?? process.cwd());
|
|
261
332
|
const { config, provenance } = await loadConfigWithProvenance(workspace, {
|
|
@@ -278,6 +349,14 @@ program.command("doctor").action(async (_opts, cmd) => {
|
|
|
278
349
|
workspace,
|
|
279
350
|
git: (await which("git")) ?? null,
|
|
280
351
|
ripgrep: (await which("rg")) ?? null,
|
|
352
|
+
// The per-request output budget is resolved when a run starts, from the model's catalog;
|
|
353
|
+
// doctor does not contact the provider, so it reports what the configuration decides.
|
|
354
|
+
outputTokens: config.limits.maxOutputTokens !== undefined
|
|
355
|
+
? { limit: config.limits.maxOutputTokens, source: "set by you (limits.maxOutputTokens)" }
|
|
356
|
+
: {
|
|
357
|
+
limit: null,
|
|
358
|
+
source: `the model catalog's declared limit (capped at ${MAX_AUTO_OUTPUT_TOKENS}), else ${DEFAULT_MAX_OUTPUT_TOKENS}`,
|
|
359
|
+
},
|
|
281
360
|
provider: {
|
|
282
361
|
id: useSaved ? saved.profile.provider : "openai-compatible",
|
|
283
362
|
baseURL: useSaved ? saved.profile.values.baseURL : config.provider.baseURL,
|
|
@@ -299,13 +378,61 @@ program.command("doctor").action(async (_opts, cmd) => {
|
|
|
299
378
|
: config.provider.auth === "none" || !!process.env[config.provider.apiKeyEnv],
|
|
300
379
|
},
|
|
301
380
|
};
|
|
302
|
-
|
|
381
|
+
const { analysisState } = await import("./analysis.js");
|
|
382
|
+
const analysisInfo = await analysisState(o);
|
|
383
|
+
const { hints: _hints, ...pythonInfo } = analysisInfo.status.python;
|
|
384
|
+
console.log(JSON.stringify({
|
|
385
|
+
...status,
|
|
386
|
+
python: { mode: analysisInfo.status.mode, ...pythonInfo },
|
|
387
|
+
analysis: {
|
|
388
|
+
mode: analysisInfo.status.mode,
|
|
389
|
+
oci: analysisInfo.status.oci,
|
|
390
|
+
limits: analysisInfo.status.limits,
|
|
391
|
+
retention: analysisInfo.status.retention,
|
|
392
|
+
...(analysisInfo.ignored.length ? { ignored: analysisInfo.ignored } : {}),
|
|
393
|
+
},
|
|
394
|
+
}, null, 2));
|
|
395
|
+
if (analysisInfo.guidance)
|
|
396
|
+
process.stderr.write(`\n${analysisInfo.guidance}\n`);
|
|
397
|
+
for (const key of provenance.ignored ?? [])
|
|
398
|
+
process.stderr.write(`\nIgnored ${key} from the project configuration: only your user configuration decides it.\n`);
|
|
303
399
|
if (!status.ripgrep)
|
|
304
400
|
process.stderr.write(`\nWarning: ripgrep (rg) is not installed; search_text and list_files will not work. ${RIPGREP_INSTALL_HINT}\n`);
|
|
305
401
|
if (!model || model === "YOUR_MODEL_ID")
|
|
306
402
|
console.error("\nNo model configured yet: set provider.model in your config, --model, or ALISIO_MODEL " +
|
|
307
403
|
"before starting a real conversation (it will otherwise fail on the first turn).");
|
|
308
404
|
});
|
|
405
|
+
const analysis = program
|
|
406
|
+
.command("analysis")
|
|
407
|
+
.description("Python analysis runtime (python_run): status, optional extras and containers");
|
|
408
|
+
analysis
|
|
409
|
+
.command("status")
|
|
410
|
+
.description("Show the runtime: Python, extras, container engine, limits and retention")
|
|
411
|
+
.action(async (_opts, cmd) => {
|
|
412
|
+
const { printAnalysisStatus } = await import("./analysis.js");
|
|
413
|
+
await printAnalysisStatus(options(cmd));
|
|
414
|
+
});
|
|
415
|
+
analysis
|
|
416
|
+
.command("setup")
|
|
417
|
+
.description("Optional: install hash-locked Python extras (--extras) or pull the container image (--oci). " +
|
|
418
|
+
"python_run never needs this for the standard library.")
|
|
419
|
+
.option("--extras <set>", "analysis (pandas, numpy, matplotlib…) or science (adds scipy…)")
|
|
420
|
+
.option("--oci", "Pull the container image once and verify its digest (Docker or Podman)")
|
|
421
|
+
.option("--image <name>", "With --oci: the image to pull (default: analysis.oci.image)")
|
|
422
|
+
.action(async (flags, cmd) => {
|
|
423
|
+
const { setupExtras, setupOci } = await import("./analysis.js");
|
|
424
|
+
if (flags.oci)
|
|
425
|
+
return setupOci(options(cmd), flags.image);
|
|
426
|
+
return setupExtras(options(cmd), flags.extras);
|
|
427
|
+
});
|
|
428
|
+
analysis
|
|
429
|
+
.command("sweep")
|
|
430
|
+
.description("Run the retention sweep now (it also runs by itself at most once a day)")
|
|
431
|
+
.option("--force", "Ignore the once-a-day mark")
|
|
432
|
+
.action(async (flags, cmd) => {
|
|
433
|
+
const { sweep } = await import("./analysis.js");
|
|
434
|
+
await sweep(options(cmd), !!flags.force);
|
|
435
|
+
});
|
|
309
436
|
const trust = program.command("trust").description("Inspect or revoke per-directory project trust");
|
|
310
437
|
trust.command("list").action(async () => {
|
|
311
438
|
const { listTrust } = await import("@alisio/core");
|
package/dist/serve.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AppOptions } from "@alisio/core";
|
|
2
|
+
export interface ServeFlags {
|
|
3
|
+
port: string;
|
|
4
|
+
host: string;
|
|
5
|
+
allowRemote?: boolean;
|
|
6
|
+
open: boolean;
|
|
7
|
+
maxWorkspaces: string;
|
|
8
|
+
maxRuns: string;
|
|
9
|
+
}
|
|
10
|
+
/** The operating system's URL opener for `platform`, as `[command, ...args]`. */
|
|
11
|
+
export declare function browserCommand(platform: NodeJS.Platform, url: string): string[];
|
|
12
|
+
/** Opens `url` in the default browser; failures are reported, never fatal. */
|
|
13
|
+
export declare function openBrowser(url: string): void;
|
|
14
|
+
/** Starts the server, prints the launch URL and stops cleanly on SIGINT/SIGTERM. */
|
|
15
|
+
export declare function serve(flags: ServeFlags, app: AppOptions & Record<string, unknown>, version: string): Promise<void>;
|