@alisio/alisio-code 0.1.0-alpha.10
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/LICENSE +21 -0
- package/README.md +23 -0
- package/dist/banner.d.ts +56 -0
- package/dist/banner.js +69 -0
- package/dist/builtin.d.ts +8 -0
- package/dist/builtin.js +49 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +508 -0
- package/dist/prompts/index.d.ts +5 -0
- package/dist/prompts/index.js +3 -0
- package/dist/prompts/init.d.ts +2 -0
- package/dist/prompts/init.js +57 -0
- package/dist/tui/app.d.ts +8 -0
- package/dist/tui/app.js +1412 -0
- package/dist/tui/attachments.d.ts +68 -0
- package/dist/tui/attachments.js +132 -0
- package/dist/tui/clipboard.d.ts +30 -0
- package/dist/tui/clipboard.js +57 -0
- package/dist/tui/components.d.ts +159 -0
- package/dist/tui/components.js +487 -0
- package/dist/tui/connect-input.d.ts +35 -0
- package/dist/tui/connect-input.js +104 -0
- package/dist/tui/panel.d.ts +54 -0
- package/dist/tui/panel.js +140 -0
- package/dist/tui/questions.d.ts +56 -0
- package/dist/tui/questions.js +113 -0
- package/dist/tui/queue.d.ts +35 -0
- package/dist/tui/queue.js +79 -0
- package/dist/tui/skills-manager.d.ts +67 -0
- package/dist/tui/skills-manager.js +200 -0
- package/dist/tui/state.d.ts +199 -0
- package/dist/tui/state.js +566 -0
- package/dist/tui/theme.d.ts +23 -0
- package/dist/tui/theme.js +49 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +28 -0
- package/package.json +66 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { loadVersion } from "./version.js";
|
|
4
|
+
const VERSION = loadVersion(import.meta.url);
|
|
5
|
+
function parseAgents(json) {
|
|
6
|
+
const value = JSON.parse(json);
|
|
7
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
8
|
+
throw new Error('--agents expects a JSON object: {"name":{"description":"...","prompt":"..."}}');
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
/** Built-in plugins and prompt templates, and the slash names templates may not take. */
|
|
12
|
+
async function cliDefaults() {
|
|
13
|
+
const [{ BUILTIN_PLUGINS }, { BUILTIN_PROMPTS }, { reservedCommandNames }] = await Promise.all([
|
|
14
|
+
import("./builtin.js"),
|
|
15
|
+
import("./prompts/index.js"),
|
|
16
|
+
import("./tui/state.js"),
|
|
17
|
+
]);
|
|
18
|
+
return {
|
|
19
|
+
builtins: BUILTIN_PLUGINS,
|
|
20
|
+
builtinPrompts: BUILTIN_PROMPTS,
|
|
21
|
+
reservedPromptNames: reservedCommandNames(),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const program = new Command();
|
|
25
|
+
program
|
|
26
|
+
.name("alisio")
|
|
27
|
+
.description("Velocidad y eficiencia para construir — extensible coding harness")
|
|
28
|
+
.version(VERSION)
|
|
29
|
+
.option("--cwd <path>", "Working directory")
|
|
30
|
+
.option("--config <path>", "Explicit trusted configuration file")
|
|
31
|
+
.option("--trust-project", "Load project config and executable plugins (full process privileges)")
|
|
32
|
+
.option("--plugin <path...>", "Load explicitly trusted local plugins")
|
|
33
|
+
.option("--model <id>", "Provider model ID")
|
|
34
|
+
.option("--base-url <url>", "OpenAI-compatible API base URL, including /v1 if needed")
|
|
35
|
+
.option("--api-mode <mode>", "chat or responses")
|
|
36
|
+
.option("--allow-write", "Allow file writes")
|
|
37
|
+
.option("--allow-process", "Allow arbitrary subprocesses; not sandboxed")
|
|
38
|
+
.option("--allow-external", "Allow network tools: webfetch, websearch and provider-native search")
|
|
39
|
+
.option("--allow-mcp", "Allow configured MCP servers and remote tool calls")
|
|
40
|
+
.option("--allow-agents", "Allow messaging neighboring agents through Herdr")
|
|
41
|
+
.option("--no-herdr", "Disable automatic Herdr lifecycle reports")
|
|
42
|
+
.option("--read-only", "Disable writes, arbitrary processes, network tools, executable plugins and MCP")
|
|
43
|
+
.option("--db <path>", "Session database")
|
|
44
|
+
.option("--json", "Emit versioned JSONL events")
|
|
45
|
+
.option("--no-tui", "Use the plain readline interactive mode instead of the TUI")
|
|
46
|
+
.option("--disable-plugin <ids...>", "Disable built-in plugins (for example: memory)")
|
|
47
|
+
.option("--no-banner", "Do not show the startup screen")
|
|
48
|
+
.option("--agents <json>", 'Extra subagent definitions as JSON: {"name":{"description":"...","prompt":"..."}}')
|
|
49
|
+
.option("--quiet", "Suppress non-essential output (startup screen, hints)");
|
|
50
|
+
const options = (cmd) => {
|
|
51
|
+
const o = cmd.optsWithGlobals();
|
|
52
|
+
return {
|
|
53
|
+
...o,
|
|
54
|
+
baseURL: o.baseUrl,
|
|
55
|
+
noHerdr: o.herdr === false,
|
|
56
|
+
disablePlugins: o.disablePlugin,
|
|
57
|
+
...(o.agents
|
|
58
|
+
? { pluginOptions: { subagents: { agents: parseAgents(String(o.agents)) } } }
|
|
59
|
+
: {}),
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* A one-time, plain (pre-alt-screen) yes/no prompt. It must run before the TUI (and before
|
|
64
|
+
* `createApplication`, which is what actually reads `.alisio/config.json`) exists, since the
|
|
65
|
+
* decision made here controls whether that read happens at all — the TUI's own interactive
|
|
66
|
+
* question queue is built from an already-created Application, too late for this.
|
|
67
|
+
*/
|
|
68
|
+
async function promptTrust(workspace) {
|
|
69
|
+
const { createInterface } = await import("node:readline/promises");
|
|
70
|
+
process.stderr.write(`\nThis directory has Alisio project configuration: ${workspace}\n` +
|
|
71
|
+
"Trusting it lets Alisio load that configuration for this and future runs — including a " +
|
|
72
|
+
"possibly different provider endpoint or API key — plus its plugins, agents, skills and " +
|
|
73
|
+
"prompt templates. Declining uses Alisio's own defaults instead; nothing here is read.\n");
|
|
74
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
75
|
+
try {
|
|
76
|
+
const answer = (await rl.question("Trust this project's Alisio configuration? [y/N] "))
|
|
77
|
+
.trim()
|
|
78
|
+
.toLowerCase();
|
|
79
|
+
return answer === "y" || answer === "yes";
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
rl.close();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolves the effective `trustProject` for an interactive TUI run: an explicit
|
|
87
|
+
* `--trust-project`/`--config` is untouched (that is already one-run, explicit trust, never
|
|
88
|
+
* persisted here as though it were an interactive grant). Otherwise, a workspace with project
|
|
89
|
+
* resources to trust gets a one-time prompt (re-asked only when `.alisio/config.json` changes),
|
|
90
|
+
* persisted in the trust store; a workspace with nothing to trust is never prompted at all.
|
|
91
|
+
*/
|
|
92
|
+
async function withProjectTrust(opts) {
|
|
93
|
+
if (opts.trustProject || opts.config)
|
|
94
|
+
return opts;
|
|
95
|
+
const { findWorkspace, resolveTrust, setTrust } = await import("@alisio/core");
|
|
96
|
+
const workspace = await findWorkspace(opts.cwd ?? process.cwd());
|
|
97
|
+
const resolution = await resolveTrust(workspace);
|
|
98
|
+
if (!resolution.hasProjectResources)
|
|
99
|
+
return opts;
|
|
100
|
+
if (!resolution.needsPrompt)
|
|
101
|
+
return { ...opts, trustProject: resolution.trusted };
|
|
102
|
+
const trusted = await promptTrust(workspace);
|
|
103
|
+
await setTrust(workspace, trusted, resolution.configHash);
|
|
104
|
+
return { ...opts, trustProject: trusted };
|
|
105
|
+
}
|
|
106
|
+
async function run(cmd, prompt, sessionId) {
|
|
107
|
+
const opts = options(cmd);
|
|
108
|
+
if (!prompt &&
|
|
109
|
+
!opts.json &&
|
|
110
|
+
opts.tui !== false &&
|
|
111
|
+
process.stdin.isTTY &&
|
|
112
|
+
process.stdout.isTTY) {
|
|
113
|
+
const { runTui } = await import("./tui/app.js");
|
|
114
|
+
const trusted = await withProjectTrust(opts);
|
|
115
|
+
return runTui({ ...trusted, ...(sessionId ? { session: sessionId } : {}) });
|
|
116
|
+
}
|
|
117
|
+
const { createApplication } = await import("@alisio/core");
|
|
118
|
+
const app = await createApplication({
|
|
119
|
+
...(await cliDefaults()),
|
|
120
|
+
...opts,
|
|
121
|
+
onEvent: (event) => {
|
|
122
|
+
if (opts.json)
|
|
123
|
+
process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
124
|
+
else if (event.type === "text_delta")
|
|
125
|
+
process.stdout.write(String(event.data.delta));
|
|
126
|
+
else if (event.type === "tool_started")
|
|
127
|
+
process.stderr.write(`\n→ ${event.data.name}\n`);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
for (const failure of app.mcpStartupFailures())
|
|
131
|
+
process.stderr.write(`[startup] ${failure}\n`);
|
|
132
|
+
const controller = new AbortController();
|
|
133
|
+
const interrupt = () => controller.abort(new Error("Interrupted"));
|
|
134
|
+
process.on("SIGINT", interrupt);
|
|
135
|
+
// `/name args` runs a prompt template (same syntax as the TUI); refuse before creating a session.
|
|
136
|
+
let template;
|
|
137
|
+
try {
|
|
138
|
+
template = prompt ? app.expandPrompt(prompt) : undefined;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
await app.close();
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
let session = sessionId ?? app.store.create(app.workspace, app.provider.id, app.provider.model).id;
|
|
145
|
+
try {
|
|
146
|
+
// Sessions record their model; an explicit --model switches it for the next turns.
|
|
147
|
+
if (sessionId && opts.model && app.store.get(session).model !== opts.model)
|
|
148
|
+
app.runner.setModel(session, opts.model);
|
|
149
|
+
await app.herdr.report("idle", session);
|
|
150
|
+
if (prompt) {
|
|
151
|
+
const match = /^\/skill:([a-z0-9-]+)\s*([\s\S]*)$/.exec(prompt);
|
|
152
|
+
if (match?.[1])
|
|
153
|
+
prompt = `${await app.skills.load(match[1])}\n\nUser request: ${match[2] ?? ""}`;
|
|
154
|
+
await app.runner.run(session, template?.text ?? prompt, controller.signal, template ? { display: template.display } : {});
|
|
155
|
+
if (!opts.json)
|
|
156
|
+
process.stdout.write(`\nSession: ${session}\n`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (opts.json || !process.stdin.isTTY)
|
|
160
|
+
throw new Error("Provide a prompt with run, or use an interactive terminal");
|
|
161
|
+
const { createInterface } = await import("node:readline/promises");
|
|
162
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
163
|
+
rl.on("SIGINT", interrupt);
|
|
164
|
+
controller.signal.addEventListener("abort", () => rl.close(), { once: true });
|
|
165
|
+
const { bannerPolicy, startupInput, terminalCapabilities } = await import("./banner.js");
|
|
166
|
+
if (bannerPolicy({
|
|
167
|
+
mode: "readline",
|
|
168
|
+
...opts,
|
|
169
|
+
stdoutTTY: !!process.stdout.isTTY,
|
|
170
|
+
stderrTTY: !!process.stderr.isTTY,
|
|
171
|
+
env: process.env,
|
|
172
|
+
})) {
|
|
173
|
+
const { renderStartup } = await import("@alisio/core");
|
|
174
|
+
const terminal = terminalCapabilities({
|
|
175
|
+
env: process.env,
|
|
176
|
+
columns: process.stderr.columns ?? 80,
|
|
177
|
+
tty: true,
|
|
178
|
+
});
|
|
179
|
+
const banner = renderStartup(app.plugins, startupInput(app, { version: VERSION, readOnly: !!opts.readOnly, terminal }));
|
|
180
|
+
process.stderr.write(`${banner.lines.join("\n")}\n`);
|
|
181
|
+
for (const d of banner.diagnostics)
|
|
182
|
+
process.stderr.write(`[startup] ${JSON.stringify(d)}\n`);
|
|
183
|
+
}
|
|
184
|
+
if (!opts.quiet)
|
|
185
|
+
console.log("Alisio · /exit /new /skill:name /command plugin.id:name args");
|
|
186
|
+
process.stdout.write("\nalisio › ");
|
|
187
|
+
try {
|
|
188
|
+
for await (const rawLine of rl) {
|
|
189
|
+
if (controller.signal.aborted)
|
|
190
|
+
break;
|
|
191
|
+
const line = rawLine.trim();
|
|
192
|
+
if (!line)
|
|
193
|
+
continue;
|
|
194
|
+
if (line === "/exit")
|
|
195
|
+
break;
|
|
196
|
+
if (line === "/new") {
|
|
197
|
+
session = app.store.create(app.workspace, app.provider.id, app.provider.model).id;
|
|
198
|
+
await app.herdr.report("idle", session);
|
|
199
|
+
process.stdout.write("\nalisio › ");
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (line.startsWith("/command ")) {
|
|
203
|
+
const [name, ...args] = line.slice(9).split(" ");
|
|
204
|
+
const handler = app.plugins.commands.get(name ?? "");
|
|
205
|
+
if (!handler)
|
|
206
|
+
throw new Error("Unknown plugin command");
|
|
207
|
+
console.log(await handler(args.join(" ")));
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const match = /^\/skill:([a-z0-9-]+)\s*([\s\S]*)$/.exec(line);
|
|
211
|
+
const input = match?.[1] ? `${await app.skills.load(match[1])}\n\n${match[2] ?? ""}` : line;
|
|
212
|
+
try {
|
|
213
|
+
const template = app.expandPrompt(input);
|
|
214
|
+
await app.runner.run(session, template?.text ?? input, controller.signal, template ? { display: template.display } : {});
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
console.error(String(e));
|
|
218
|
+
}
|
|
219
|
+
process.stdout.write("\nalisio › ");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
rl.close();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
process.off("SIGINT", interrupt);
|
|
228
|
+
await app.close();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
program
|
|
232
|
+
.command("run")
|
|
233
|
+
.description('Run one prompt headless; "/name args" runs a prompt template (e.g. "/init")')
|
|
234
|
+
.argument("<prompt>")
|
|
235
|
+
.action((prompt, _options, cmd) => run(cmd, prompt));
|
|
236
|
+
program
|
|
237
|
+
.command("resume")
|
|
238
|
+
.argument("<session>")
|
|
239
|
+
.argument("[prompt]")
|
|
240
|
+
.action((id, prompt, _opts, cmd) => run(cmd, prompt, id));
|
|
241
|
+
program.action((_opts, cmd) => run(cmd));
|
|
242
|
+
program
|
|
243
|
+
.command("setup")
|
|
244
|
+
.description("Write an example configuration without secrets (for AGENTS.md use /init)")
|
|
245
|
+
.action(async (_opts, cmd) => {
|
|
246
|
+
const { resolve, join } = await import("node:path");
|
|
247
|
+
const { mkdir, writeFile } = await import("node:fs/promises");
|
|
248
|
+
const { exists } = await import("@alisio/core");
|
|
249
|
+
const path = join(resolve(options(cmd).cwd ?? process.cwd()), ".alisio", "config.json");
|
|
250
|
+
if (await exists(path))
|
|
251
|
+
throw new Error(`Configuration exists: ${path}`);
|
|
252
|
+
await mkdir(join(path, ".."), { recursive: true });
|
|
253
|
+
await writeFile(path, `${JSON.stringify({ schemaVersion: 1, provider: { baseURL: "https://api.openai.com/v1", apiKeyEnv: "OPENAI_API_KEY", model: "YOUR_MODEL_ID", apiMode: "chat", auth: "bearer", tokenParameter: "max_tokens", streamUsage: false }, plugins: [], skills: [], mcp: { servers: {} } }, null, 2)}\n`);
|
|
254
|
+
console.log(`Created ${path}. Set your model, endpoint and environment key. Use --config ${path}.`);
|
|
255
|
+
console.log('To generate AGENTS.md for this project, run /init inside alisio (or: alisio run "/init" --allow-write).');
|
|
256
|
+
});
|
|
257
|
+
program.command("doctor").action(async (_opts, cmd) => {
|
|
258
|
+
const { findWorkspace, loadConfigWithProvenance, overridesSavedProviderProfile, ProviderSettingsStore, RIPGREP_INSTALL_HINT, which, } = await import("@alisio/core");
|
|
259
|
+
const o = options(cmd);
|
|
260
|
+
const workspace = await findWorkspace(o.cwd ?? process.cwd());
|
|
261
|
+
const { config, provenance } = await loadConfigWithProvenance(workspace, {
|
|
262
|
+
file: o.config,
|
|
263
|
+
trustProject: o.trustProject,
|
|
264
|
+
model: o.model,
|
|
265
|
+
baseURL: o.baseURL,
|
|
266
|
+
apiMode: o.apiMode,
|
|
267
|
+
});
|
|
268
|
+
const saved = await new ProviderSettingsStore().active();
|
|
269
|
+
const useSaved = !!saved &&
|
|
270
|
+
!overridesSavedProviderProfile(provenance, !!o.baseURL || !!o.apiMode || !!process.env.OPENAI_BASE_URL || !!process.env.ALISIO_API_MODE);
|
|
271
|
+
const model = o.model?.trim() ||
|
|
272
|
+
process.env.ALISIO_MODEL?.trim() ||
|
|
273
|
+
(useSaved ? saved.profile.model : config.provider.model);
|
|
274
|
+
const status = {
|
|
275
|
+
version: VERSION,
|
|
276
|
+
runtime: process.versions.bun ? `bun ${process.versions.bun}` : `node ${process.versions.node}`,
|
|
277
|
+
platform: process.platform,
|
|
278
|
+
workspace,
|
|
279
|
+
git: (await which("git")) ?? null,
|
|
280
|
+
ripgrep: (await which("rg")) ?? null,
|
|
281
|
+
provider: {
|
|
282
|
+
id: useSaved ? saved.profile.provider : "openai-compatible",
|
|
283
|
+
baseURL: useSaved ? saved.profile.values.baseURL : config.provider.baseURL,
|
|
284
|
+
apiMode: useSaved ? saved.profile.values.apiMode : config.provider.apiMode,
|
|
285
|
+
model: !model
|
|
286
|
+
? "not configured"
|
|
287
|
+
: model === "YOUR_MODEL_ID"
|
|
288
|
+
? "not configured (placeholder from `alisio setup` — edit .alisio/config.json)"
|
|
289
|
+
: model,
|
|
290
|
+
auth: useSaved ? saved.profile.values.auth : config.provider.auth,
|
|
291
|
+
keyConfigured: useSaved
|
|
292
|
+
? !!saved.credentials.apiKey ||
|
|
293
|
+
saved.profile.values.auth === "none" ||
|
|
294
|
+
!!process.env[typeof saved.profile.values.apiKeyEnv === "string" && saved.profile.values.apiKeyEnv
|
|
295
|
+
? saved.profile.values.apiKeyEnv
|
|
296
|
+
: saved.profile.provider === "deepseek"
|
|
297
|
+
? "DEEPSEEK_API_KEY"
|
|
298
|
+
: "OPENAI_API_KEY"]
|
|
299
|
+
: config.provider.auth === "none" || !!process.env[config.provider.apiKeyEnv],
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
console.log(JSON.stringify(status, null, 2));
|
|
303
|
+
if (!status.ripgrep)
|
|
304
|
+
process.stderr.write(`\nWarning: ripgrep (rg) is not installed; search_text and list_files will not work. ${RIPGREP_INSTALL_HINT}\n`);
|
|
305
|
+
if (!model || model === "YOUR_MODEL_ID")
|
|
306
|
+
console.error("\nNo model configured yet: set provider.model in your config, --model, or ALISIO_MODEL " +
|
|
307
|
+
"before starting a real conversation (it will otherwise fail on the first turn).");
|
|
308
|
+
});
|
|
309
|
+
const trust = program.command("trust").description("Inspect or revoke per-directory project trust");
|
|
310
|
+
trust.command("list").action(async () => {
|
|
311
|
+
const { listTrust } = await import("@alisio/core");
|
|
312
|
+
console.log(JSON.stringify(await listTrust(), null, 2));
|
|
313
|
+
});
|
|
314
|
+
trust
|
|
315
|
+
.command("revoke")
|
|
316
|
+
.argument("<path>")
|
|
317
|
+
.description("Revoke a workspace's stored trust decision (re-prompts next time)")
|
|
318
|
+
.action(async (path) => {
|
|
319
|
+
const { revokeTrust } = await import("@alisio/core");
|
|
320
|
+
const { resolve } = await import("node:path");
|
|
321
|
+
const { realpath } = await import("node:fs/promises");
|
|
322
|
+
const workspace = await realpath(resolve(path)).catch(() => resolve(path));
|
|
323
|
+
const removed = await revokeTrust(workspace);
|
|
324
|
+
console.log(removed ? `Revoked trust for ${workspace}` : `No stored trust decision for ${workspace}`);
|
|
325
|
+
});
|
|
326
|
+
const sessions = program.command("sessions");
|
|
327
|
+
async function openStore(cmd) {
|
|
328
|
+
const { SQLiteStore } = await import("@alisio/core");
|
|
329
|
+
const { stateHome } = await import("@alisio/core");
|
|
330
|
+
const { join } = await import("node:path");
|
|
331
|
+
return new SQLiteStore(options(cmd).db ?? join(stateHome(), "sessions.sqlite"));
|
|
332
|
+
}
|
|
333
|
+
sessions.command("list").action(async (_opts, cmd) => {
|
|
334
|
+
const store = await openStore(cmd);
|
|
335
|
+
try {
|
|
336
|
+
console.log(JSON.stringify(store.list(), null, 2));
|
|
337
|
+
}
|
|
338
|
+
finally {
|
|
339
|
+
store.close();
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
sessions
|
|
343
|
+
.command("recover")
|
|
344
|
+
.argument("<session>")
|
|
345
|
+
.requiredOption("--acknowledge", "Acknowledge uncertain effects after inspecting the workspace")
|
|
346
|
+
.action(async (id, _opts, cmd) => {
|
|
347
|
+
const store = await openStore(cmd);
|
|
348
|
+
try {
|
|
349
|
+
store.acquire(id);
|
|
350
|
+
try {
|
|
351
|
+
store.reconcile(id, true);
|
|
352
|
+
}
|
|
353
|
+
finally {
|
|
354
|
+
store.release(id);
|
|
355
|
+
}
|
|
356
|
+
console.log("Session recovered. Uncertain operations were not replayed.");
|
|
357
|
+
}
|
|
358
|
+
finally {
|
|
359
|
+
store.close();
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
program
|
|
363
|
+
.command("context")
|
|
364
|
+
.command("explain")
|
|
365
|
+
.argument("<path>")
|
|
366
|
+
.action(async (path, _opts, cmd) => {
|
|
367
|
+
const { ProjectContext } = await import("@alisio/core");
|
|
368
|
+
const { findWorkspace } = await import("@alisio/core");
|
|
369
|
+
const { resolve } = await import("node:path");
|
|
370
|
+
const cwd = resolve(options(cmd).cwd ?? process.cwd());
|
|
371
|
+
const root = await findWorkspace(cwd);
|
|
372
|
+
console.log(JSON.stringify(await new ProjectContext(root).explain(resolve(cwd, path)), null, 2));
|
|
373
|
+
});
|
|
374
|
+
const skills = program.command("skills");
|
|
375
|
+
for (const name of ["list", "validate"]) {
|
|
376
|
+
skills
|
|
377
|
+
.command(name)
|
|
378
|
+
.argument("[path]")
|
|
379
|
+
.action(async (path, _opts, cmd) => {
|
|
380
|
+
const { Skills, configHome, findWorkspace, loadConfig, skillRoots } = await import("@alisio/core");
|
|
381
|
+
const { resolve } = await import("node:path");
|
|
382
|
+
const { homedir } = await import("node:os");
|
|
383
|
+
const o = options(cmd), cwd = resolve(o.cwd ?? process.cwd()), root = await findWorkspace(cwd);
|
|
384
|
+
const config = await loadConfig(root, { file: o.config, trustProject: o.trustProject });
|
|
385
|
+
const roots = skillRoots({
|
|
386
|
+
workspace: root,
|
|
387
|
+
cwd,
|
|
388
|
+
home: homedir(),
|
|
389
|
+
configHome: configHome(),
|
|
390
|
+
trusted: !!o.trustProject || !!o.config,
|
|
391
|
+
configSkills: config.skills,
|
|
392
|
+
});
|
|
393
|
+
const catalog = new Skills({ overrides: path ? {} : config.skillOverrides });
|
|
394
|
+
await catalog.discover(path ? [resolve(cwd, path)] : roots);
|
|
395
|
+
console.log(JSON.stringify({ skills: [...catalog.items.values()], diagnostics: catalog.diagnostics }, null, 2));
|
|
396
|
+
if (name === "validate" && catalog.diagnostics.length)
|
|
397
|
+
process.exitCode = 1;
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
const plugins = program.command("plugins");
|
|
401
|
+
plugins.command("list").action(async (_opts, cmd) => {
|
|
402
|
+
const { discoverPlugins, installedNpmPlugins } = await import("@alisio/core");
|
|
403
|
+
const { configHome } = await import("@alisio/core");
|
|
404
|
+
const { join, resolve } = await import("node:path");
|
|
405
|
+
const global = join(configHome(), "plugins");
|
|
406
|
+
console.log(JSON.stringify({
|
|
407
|
+
global: [...(await discoverPlugins(global)), ...(await installedNpmPlugins(global))],
|
|
408
|
+
project: await discoverPlugins(join(resolve(options(cmd).cwd ?? process.cwd()), ".alisio", "plugins")),
|
|
409
|
+
explicit: options(cmd).plugin ?? [],
|
|
410
|
+
}, null, 2));
|
|
411
|
+
});
|
|
412
|
+
plugins.command("doctor").action(async (_opts, cmd) => {
|
|
413
|
+
const { createApplication } = await import("@alisio/core");
|
|
414
|
+
const app = await createApplication({
|
|
415
|
+
...(await cliDefaults()),
|
|
416
|
+
...options(cmd),
|
|
417
|
+
provider: {
|
|
418
|
+
id: "inspection",
|
|
419
|
+
model: "none",
|
|
420
|
+
stream() {
|
|
421
|
+
throw new Error("Inspection provider cannot run prompts");
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
try {
|
|
426
|
+
console.log(JSON.stringify({
|
|
427
|
+
tools: app.registry
|
|
428
|
+
.list()
|
|
429
|
+
.filter((t) => t.name.startsWith("p_"))
|
|
430
|
+
.map((t) => t.name),
|
|
431
|
+
extensions: {
|
|
432
|
+
mascot: app.plugins.extensions.resolve("mascot")?.provider.id ?? "alisio.default",
|
|
433
|
+
startupScreen: app.plugins.extensions.resolve("startup-screen")?.provider.id ?? "alisio.default",
|
|
434
|
+
conflicts: app.plugins.extensions.conflicts(),
|
|
435
|
+
},
|
|
436
|
+
commands: [...app.plugins.commandInfo.entries()]
|
|
437
|
+
.filter(([, info]) => !info.builtin)
|
|
438
|
+
.map(([name]) => name),
|
|
439
|
+
builtin: [...app.plugins.builtins],
|
|
440
|
+
}, null, 2));
|
|
441
|
+
}
|
|
442
|
+
finally {
|
|
443
|
+
await app.close();
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
const mcp = program.command("mcp");
|
|
447
|
+
mcp.command("list").action(async (_opts, cmd) => {
|
|
448
|
+
const { loadConfig } = await import("@alisio/core");
|
|
449
|
+
const o = options(cmd);
|
|
450
|
+
const config = await loadConfig(o.cwd ?? process.cwd(), {
|
|
451
|
+
file: o.config,
|
|
452
|
+
trustProject: o.trustProject,
|
|
453
|
+
});
|
|
454
|
+
console.log(JSON.stringify(Object.keys(config.mcp.servers), null, 2));
|
|
455
|
+
});
|
|
456
|
+
mcp
|
|
457
|
+
.command("doctor")
|
|
458
|
+
.argument("<server>")
|
|
459
|
+
.action(async (server, _opts, cmd) => {
|
|
460
|
+
const o = options(cmd);
|
|
461
|
+
if (!o.allowMcp)
|
|
462
|
+
throw new Error("Use --allow-mcp to start or connect to a configured server");
|
|
463
|
+
const { createApplication } = await import("@alisio/core");
|
|
464
|
+
const app = await createApplication({
|
|
465
|
+
...(await cliDefaults()),
|
|
466
|
+
...o,
|
|
467
|
+
provider: {
|
|
468
|
+
id: "inspection",
|
|
469
|
+
model: "none",
|
|
470
|
+
stream() {
|
|
471
|
+
throw new Error("Inspection only");
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
try {
|
|
476
|
+
console.log(JSON.stringify(await app.mcp.connect(server, AbortSignal.timeout(15000)), null, 2));
|
|
477
|
+
}
|
|
478
|
+
finally {
|
|
479
|
+
await app.close();
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
program
|
|
483
|
+
.command("install")
|
|
484
|
+
.description("Install an npm plugin package into the global plugins directory (~/.config/alisio/plugins)")
|
|
485
|
+
.argument("<spec>", 'npm package spec, e.g. "npm:plugin-openrouter" or "plugin-openrouter@1.2.3"')
|
|
486
|
+
.option("-y, --yes", "Skip the pre-install confirmation (npm may run lifecycle scripts)")
|
|
487
|
+
.option("--trust-plugin", "Explicit trust for this global install (same as --yes)")
|
|
488
|
+
.option("--update", "Refresh an already-installed plugin to the latest version, keeping its name")
|
|
489
|
+
.action(async (spec, _options, cmd) => {
|
|
490
|
+
const { cliInstall, configHome } = await import("@alisio/core");
|
|
491
|
+
const o = options(cmd);
|
|
492
|
+
await cliInstall({
|
|
493
|
+
spec,
|
|
494
|
+
configHome: configHome(),
|
|
495
|
+
yes: !!o.yes || !!o.trustPlugin,
|
|
496
|
+
update: !!o.update,
|
|
497
|
+
readOnly: !!o.readOnly,
|
|
498
|
+
json: !!o.json,
|
|
499
|
+
interactive: !!process.stdin.isTTY && !!process.stdout.isTTY && !o.json,
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
try {
|
|
503
|
+
await program.parseAsync();
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
507
|
+
process.exitCode = 1;
|
|
508
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** Built-in `/init` prompt template (embedded so it ships in dist and in the binary). */
|
|
2
|
+
export declare const INIT_TEMPLATE = "---\ndescription: Analyze this repository and create or update the root AGENTS.md\nargument-hint: \"[focus]\"\nrequires: [write]\n---\nAnalyze this repository and create or update the root `AGENTS.md`: concise, project-specific\ninstructions for coding agents working here.\n\n## 1. Explore efficiently (read-only first)\n- `list_files` on the root and key directories (use `limit`; skip vendored or generated folders).\n- `git_status` to see the state of the tree.\n- `read_file` the manifests that exist: package.json, pnpm-workspace.yaml, pyproject.toml,\n setup.cfg, requirements*.txt, go.mod, Cargo.toml, pom.xml, build.gradle*, Gemfile, composer.json,\n Makefile, justfile, Dockerfile. Nested workspace manifests matter in monorepos.\n- Identify the package manager from lockfiles (pnpm-lock.yaml, package-lock.json, yarn.lock,\n bun.lock/bun.lockb, poetry.lock, uv.lock, Cargo.lock, go.sum).\n- Read build/test/lint/format/typecheck configuration (tsconfig*.json, biome.json, .eslintrc*,\n .prettierrc*, vitest/jest/pytest config, ruff/flake8, rustfmt, golangci) and CI workflows.\n `list_files` hides dotfiles, so probe likely paths such as `.github/workflows/ci.yml` with\n `read_file` (a missing file just returns an error). Use `search_text` only on paths that exist.\n- Read README and CONTRIBUTING, and existing agent instruction files if present: AGENTS.md\n (root and nested), CLAUDE.md, GEMINI.md, .cursorrules, .cursor/rules/*,\n .github/copilot-instructions.md. Probe them with `read_file`; a missing file is not an error.\n- Sample a few representative source and test files to learn the layout and conventions. Use\n `search_text` instead of reading many files.\n\n## 2. Write AGENTS.md (roughly 150 lines or fewer)\nCover, only where the repository gives evidence:\n- Project overview: what it is, main languages and frameworks.\n- Commands: exact setup, build, test, single-test, lint, format and typecheck commands as\n defined in manifests or CI (for example `pnpm test`, not \"run the tests\").\n- Architecture: module or package layout with the important paths.\n- Code style and conventions actually enforced by configuration (formatter, linter rules,\n compiler strictness, import style).\n- Testing approach: framework, where tests live, how fixtures work.\n- Gotchas and constraints: security rules, generated files, files or directories not to touch,\n required tool versions.\n\nRules:\n- Only facts verified from files you read. No generic advice (\"write clean code\"), no invented\n commands or paths. If something important is unknown, say so briefly instead of guessing.\n- Never include secrets, tokens or environment variable values; do not read .env files.\n- Merge useful rules from other agent instruction files and cite the source file, e.g.\n \"(from CLAUDE.md)\".\n\n## 3. Create or update safely\n- If `AGENTS.md` does not exist, create it with `write_file` (`expectedHash: null`).\n- If it exists, `read_file` it first and update it in place: preserve human-written content and\n intent, merge improvements, and keep edits minimal using `edit_file` with the sha256 from\n `read_file` as `expectedHash`. Never blindly overwrite an existing AGENTS.md.\n\n## 4. Finish\nReply with a short summary of what you wrote or changed and anything you could not verify.\n\nAdditional focus from the user (may be empty): $ARGUMENTS\n";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Built-in `/init` prompt template (embedded so it ships in dist and in the binary). */
|
|
2
|
+
export const INIT_TEMPLATE = `---
|
|
3
|
+
description: Analyze this repository and create or update the root AGENTS.md
|
|
4
|
+
argument-hint: "[focus]"
|
|
5
|
+
requires: [write]
|
|
6
|
+
---
|
|
7
|
+
Analyze this repository and create or update the root \`AGENTS.md\`: concise, project-specific
|
|
8
|
+
instructions for coding agents working here.
|
|
9
|
+
|
|
10
|
+
## 1. Explore efficiently (read-only first)
|
|
11
|
+
- \`list_files\` on the root and key directories (use \`limit\`; skip vendored or generated folders).
|
|
12
|
+
- \`git_status\` to see the state of the tree.
|
|
13
|
+
- \`read_file\` the manifests that exist: package.json, pnpm-workspace.yaml, pyproject.toml,
|
|
14
|
+
setup.cfg, requirements*.txt, go.mod, Cargo.toml, pom.xml, build.gradle*, Gemfile, composer.json,
|
|
15
|
+
Makefile, justfile, Dockerfile. Nested workspace manifests matter in monorepos.
|
|
16
|
+
- Identify the package manager from lockfiles (pnpm-lock.yaml, package-lock.json, yarn.lock,
|
|
17
|
+
bun.lock/bun.lockb, poetry.lock, uv.lock, Cargo.lock, go.sum).
|
|
18
|
+
- Read build/test/lint/format/typecheck configuration (tsconfig*.json, biome.json, .eslintrc*,
|
|
19
|
+
.prettierrc*, vitest/jest/pytest config, ruff/flake8, rustfmt, golangci) and CI workflows.
|
|
20
|
+
\`list_files\` hides dotfiles, so probe likely paths such as \`.github/workflows/ci.yml\` with
|
|
21
|
+
\`read_file\` (a missing file just returns an error). Use \`search_text\` only on paths that exist.
|
|
22
|
+
- Read README and CONTRIBUTING, and existing agent instruction files if present: AGENTS.md
|
|
23
|
+
(root and nested), CLAUDE.md, GEMINI.md, .cursorrules, .cursor/rules/*,
|
|
24
|
+
.github/copilot-instructions.md. Probe them with \`read_file\`; a missing file is not an error.
|
|
25
|
+
- Sample a few representative source and test files to learn the layout and conventions. Use
|
|
26
|
+
\`search_text\` instead of reading many files.
|
|
27
|
+
|
|
28
|
+
## 2. Write AGENTS.md (roughly 150 lines or fewer)
|
|
29
|
+
Cover, only where the repository gives evidence:
|
|
30
|
+
- Project overview: what it is, main languages and frameworks.
|
|
31
|
+
- Commands: exact setup, build, test, single-test, lint, format and typecheck commands as
|
|
32
|
+
defined in manifests or CI (for example \`pnpm test\`, not "run the tests").
|
|
33
|
+
- Architecture: module or package layout with the important paths.
|
|
34
|
+
- Code style and conventions actually enforced by configuration (formatter, linter rules,
|
|
35
|
+
compiler strictness, import style).
|
|
36
|
+
- Testing approach: framework, where tests live, how fixtures work.
|
|
37
|
+
- Gotchas and constraints: security rules, generated files, files or directories not to touch,
|
|
38
|
+
required tool versions.
|
|
39
|
+
|
|
40
|
+
Rules:
|
|
41
|
+
- Only facts verified from files you read. No generic advice ("write clean code"), no invented
|
|
42
|
+
commands or paths. If something important is unknown, say so briefly instead of guessing.
|
|
43
|
+
- Never include secrets, tokens or environment variable values; do not read .env files.
|
|
44
|
+
- Merge useful rules from other agent instruction files and cite the source file, e.g.
|
|
45
|
+
"(from CLAUDE.md)".
|
|
46
|
+
|
|
47
|
+
## 3. Create or update safely
|
|
48
|
+
- If \`AGENTS.md\` does not exist, create it with \`write_file\` (\`expectedHash: null\`).
|
|
49
|
+
- If it exists, \`read_file\` it first and update it in place: preserve human-written content and
|
|
50
|
+
intent, merge improvements, and keep edits minimal using \`edit_file\` with the sha256 from
|
|
51
|
+
\`read_file\` as \`expectedHash\`. Never blindly overwrite an existing AGENTS.md.
|
|
52
|
+
|
|
53
|
+
## 4. Finish
|
|
54
|
+
Reply with a short summary of what you wrote or changed and anything you could not verify.
|
|
55
|
+
|
|
56
|
+
Additional focus from the user (may be empty): $ARGUMENTS
|
|
57
|
+
`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppOptions } from "@alisio/core";
|
|
2
|
+
export interface TuiOptions extends AppOptions {
|
|
3
|
+
session?: string;
|
|
4
|
+
quiet?: boolean;
|
|
5
|
+
/** `false` with --no-banner. */
|
|
6
|
+
banner?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function runTui(options: TuiOptions): Promise<void>;
|