@vecteur/cli 0.2.4 → 0.3.1

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/dist/ui/App.js DELETED
@@ -1,175 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from "react";
3
- import { Box, Static, Text, useApp, useInput } from "ink";
4
- import { buildLocalContextQuery, openBrowser, streamTurn, webBase } from "../runner.js";
5
- import { handleSlashCommand, parseMentions, renameProject, SLASH_COMMANDS, titleFromPrompt } from "../session.js";
6
- import { markdownToAnsi } from "./markdown.js";
7
- import { Header } from "./Header.js";
8
- import { Logo } from "./logo.js";
9
- import { Prompt } from "./Prompt.js";
10
- import { RunStatus } from "./RunStatus.js";
11
- function highlightMentions(line) {
12
- const parts = line.split(/(@\S+)/g);
13
- return (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "\u203A " }), parts.map((part, index) => part.startsWith("@") ? (_jsx(Text, { color: "green", children: part }, index)) : (_jsx(Text, { children: part }, index)))] }));
14
- }
15
- function TranscriptTurn({ item, project }) {
16
- const color = item.tone === "error" ? "red" : item.tone === "warning" ? "yellow" : undefined;
17
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [highlightMentions(item.user), _jsx(Text, { color: color, children: markdownToAnsi(item.answer) }), item.sawVisual ? (_jsxs(Text, { dimColor: true, children: ["\u21B3 visual artifacts \u2014 open in the web app: ", webBase(), "/projects/", project] })) : null] }));
18
- }
19
- /** Commands whose name starts with the typed `/prefix` (empty when not in slash mode). */
20
- function slashMatches(value) {
21
- if (!value.startsWith("/"))
22
- return [];
23
- const prefix = value.slice(1).toLowerCase();
24
- return SLASH_COMMANDS.filter((cmd) => cmd.name.startsWith(prefix));
25
- }
26
- function SlashMenu({ value, selected }) {
27
- const matches = slashMatches(value);
28
- if (matches.length === 0)
29
- return null;
30
- const sel = Math.min(selected, matches.length - 1);
31
- return (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: matches.map((cmd, i) => (_jsxs(Text, { children: [_jsxs(Text, { color: "cyan", bold: i === sel, children: [i === sel ? "❯ " : " ", "/", cmd.name] }), _jsxs(Text, { dimColor: true, children: [" ", cmd.desc] })] }, cmd.name))) }));
32
- }
33
- export function App({ project, cwd, created, updateNotice }) {
34
- const { exit } = useApp();
35
- const [input, setInput] = useState("");
36
- const [history, setHistory] = useState([]);
37
- const [historyIndex, setHistoryIndex] = useState(undefined);
38
- const [items, setItems] = useState([]);
39
- const [streaming, setStreaming] = useState(false);
40
- const [stages, setStages] = useState([]);
41
- const [turns, setTurns] = useState(0);
42
- const [lastTaskId, setLastTaskId] = useState(undefined);
43
- const [tokenTotal, setTokenTotal] = useState(0);
44
- const [selected, setSelected] = useState(0); // highlighted row in the slash-command menu
45
- const [notice, setNotice] = useState(created ? `workspace bound to ${project}` : undefined);
46
- const pushItem = (item) => {
47
- setItems((prev) => [...prev, { id: prev.length + 1, ...item }]);
48
- };
49
- // Reset the slash-menu highlight whenever the input text changes.
50
- const onInputChange = (value) => {
51
- setInput(value);
52
- setSelected(0);
53
- };
54
- const submit = async (submitted) => {
55
- const raw = submitted.trim();
56
- if (!raw || streaming)
57
- return;
58
- setInput("");
59
- setHistory((prev) => [...prev, raw]);
60
- setHistoryIndex(undefined);
61
- setNotice(undefined);
62
- if (raw.startsWith("/")) {
63
- // Enter runs the HIGHLIGHTED action even when only a prefix was typed (no Tab needed):
64
- // exact command → itself; otherwise the currently-selected match.
65
- const token = raw.slice(1).split(/\s+/)[0] ?? "";
66
- const isExact = SLASH_COMMANDS.some((c) => c.name === token);
67
- const matches = slashMatches(raw);
68
- const name = isExact ? token : matches.length ? matches[Math.min(selected, matches.length - 1)].name : token;
69
- const result = await handleSlashCommand(`/${name}`, { project, cwd });
70
- if (result.clear)
71
- setItems([]);
72
- if (result.reset) {
73
- setTurns(0);
74
- setLastTaskId(undefined);
75
- }
76
- if (result.open)
77
- void openBrowser(result.open);
78
- if (result.output)
79
- pushItem({ user: raw, answer: result.output });
80
- if (result.exit)
81
- exit();
82
- return;
83
- }
84
- const { text, files } = parseMentions(raw);
85
- let query;
86
- try {
87
- query = buildLocalContextQuery(text, files.length ? files : undefined);
88
- }
89
- catch (e) {
90
- pushItem({ user: raw, answer: `✗ ${e.message}`, tone: "error" });
91
- return;
92
- }
93
- setStreaming(true);
94
- setStages([]);
95
- try {
96
- const result = await streamTurn({
97
- project,
98
- query,
99
- followUp: turns > 0,
100
- contextTaskId: lastTaskId,
101
- onStep: (label) => {
102
- setStages((prev) => (prev[prev.length - 1] === label ? prev : [...prev, label]));
103
- },
104
- });
105
- if (result.quotaExceeded) {
106
- pushItem({ user: raw, answer: result.failed ?? "Quota exceeded.", tone: "warning" });
107
- }
108
- else if (result.failed) {
109
- pushItem({ user: raw, answer: `✗ ${result.failed}`, tone: "error" });
110
- }
111
- else {
112
- pushItem({ user: raw, answer: result.answer ?? "(no answer)", sawVisual: result.sawVisual });
113
- // First prompt in a freshly-created project becomes its title (self-describing in the web app).
114
- if (created && turns === 0)
115
- void renameProject(project, titleFromPrompt(raw));
116
- setLastTaskId(result.taskId);
117
- setTurns((prev) => prev + 1);
118
- setTokenTotal((prev) => prev + (result.tokens?.total ?? 0));
119
- }
120
- }
121
- finally {
122
- setStreaming(false);
123
- setStages([]);
124
- }
125
- };
126
- useInput((value, key) => {
127
- if ((key.ctrl && (value === "c" || value === "d")) || value === "\u0003" || value === "\u0004") {
128
- if (streaming) {
129
- setNotice("finishing current turn...");
130
- return;
131
- }
132
- exit();
133
- return;
134
- }
135
- if (streaming)
136
- return;
137
- // Slash-menu navigation takes over the arrows/Tab while typing a `/command`.
138
- const matches = slashMatches(input);
139
- if (matches.length > 0) {
140
- if (key.upArrow) {
141
- setSelected((i) => Math.max(0, i - 1));
142
- return;
143
- }
144
- if (key.downArrow) {
145
- setSelected((i) => Math.min(matches.length - 1, i + 1));
146
- return;
147
- }
148
- if (key.tab) {
149
- const pick = matches[Math.min(selected, matches.length - 1)];
150
- setInput(`/${pick.name} `);
151
- setSelected(0);
152
- return;
153
- }
154
- }
155
- if (key.upArrow && history.length > 0) {
156
- const index = historyIndex === undefined ? history.length - 1 : Math.max(0, historyIndex - 1);
157
- setHistoryIndex(index);
158
- setInput(history[index] ?? "");
159
- }
160
- else if (key.downArrow && history.length > 0) {
161
- if (historyIndex === undefined)
162
- return;
163
- const index = historyIndex + 1;
164
- if (index >= history.length) {
165
- setHistoryIndex(undefined);
166
- setInput("");
167
- }
168
- else {
169
- setHistoryIndex(index);
170
- setInput(history[index] ?? "");
171
- }
172
- }
173
- });
174
- return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsx(Header, { cwd: cwd, project: project }), updateNotice ? _jsxs(Text, { color: "yellow", children: ["\u2191 ", updateNotice] }) : null, items.length === 0 && turns === 0 ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Logo, {}), _jsx(Text, { dimColor: true, children: "Ask space-engineering questions in plain language \u2014 the Vecteur oracle and its" }), _jsx(Text, { dimColor: true, children: "subagents run on our servers and answer with cited results." }), _jsx(Text, { children: " " }), _jsxs(Text, { dimColor: true, children: [" ", _jsx(Text, { color: "cyan", children: "@file" }), " attach a local file", " ", _jsx(Text, { color: "cyan", children: "/help" }), " commands", " ", _jsx(Text, { color: "cyan", children: "\u2191\u2193" }), " history", " ", _jsx(Text, { color: "cyan", children: "ctrl-d" }), " exit"] }), _jsxs(Text, { dimColor: true, children: [" ", "Try ", _jsx(Text, { color: "white", children: "\"design a sun-synchronous orbit at 550 km\"" }), " \u2014 or @mention a file to analyze."] })] })) : null, _jsx(Static, { items: items, children: (item) => _jsx(TranscriptTurn, { item: item, project: project }, item.id) }), streaming ? _jsx(RunStatus, { stages: stages.length ? stages : ["starting run"] }) : null, _jsx(Prompt, { value: input, onChange: onInputChange, onSubmit: submit, disabled: streaming }), _jsx(SlashMenu, { value: input, selected: selected }), notice ? _jsx(Text, { dimColor: true, children: notice }) : null, _jsxs(Box, { justifyContent: "space-between", width: "100%", children: [_jsx(Text, { dimColor: true, children: "enter send \u00B7 /help \u00B7 ctrl-d exit" }), _jsxs(Text, { dimColor: true, children: ["tokens: ", tokenTotal] })] })] }));
175
- }
package/dist/ui/Header.js DELETED
@@ -1,7 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { basename } from "node:path";
3
- import { Box, Text } from "ink";
4
- export function Header({ cwd, project }) {
5
- const shortProject = project.length > 10 ? `${project.slice(0, 8)}…` : project;
6
- return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsxs(Box, { justifyContent: "space-between", width: "100%", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Vecteur" }), _jsx(Text, { dimColor: true, children: " \u00B7 space-engineering agent" })] }), _jsxs(Text, { dimColor: true, children: [basename(cwd) || cwd, " \u00B7 ", shortProject] })] }), _jsxs(Text, { dimColor: true, children: ["workspace: ", cwd] })] }));
7
- }
package/dist/ui/Prompt.js DELETED
@@ -1,7 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from "ink";
3
- import TextInput from "ink-text-input";
4
- const PLACEHOLDER = "Ask anything — /help for commands, @file to attach";
5
- export function Prompt({ value, disabled, onChange, onSubmit, }) {
6
- return (_jsxs(Box, { borderStyle: "round", paddingX: 1, width: "100%", children: [_jsx(Text, { color: "cyan", children: "\u203A " }), _jsx(TextInput, { value: value, onChange: onChange, onSubmit: onSubmit, placeholder: PLACEHOLDER, focus: !disabled, showCursor: !disabled })] }));
7
- }
@@ -1,12 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from "ink";
3
- import Spinner from "ink-spinner";
4
- const MAX_VISIBLE = 8;
5
- export function RunStatus({ stages }) {
6
- const hidden = Math.max(0, stages.length - MAX_VISIBLE);
7
- const shown = stages.slice(-MAX_VISIBLE);
8
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "\u25B8 oracle agent working\u2026" }), hidden > 0 ? _jsx(Text, { dimColor: true, children: ` ✓ ${hidden} earlier step${hidden > 1 ? "s" : ""}` }) : null, shown.map((stage, index) => {
9
- const active = index === shown.length - 1;
10
- return (_jsx(Text, { dimColor: !active, children: active ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { type: "dots" }), " ", stage] })) : (_jsxs(_Fragment, { children: ["\u2713 ", stage] })) }, `${stage}-${index}`));
11
- })] }));
12
- }
package/dist/ui/logo.js DELETED
@@ -1,24 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text, useStdout } from "ink";
3
- import { VERSION } from "../version.js";
4
- const BRAND_BLUE = "#4a9eff";
5
- // "VECTEUR" in an ANSI-shadow block font — the brand wordmark for a wide terminal.
6
- const WORDMARK = [
7
- "██╗ ██╗███████╗ ██████╗████████╗███████╗██╗ ██╗██████╗",
8
- "██║ ██║██╔════╝██╔════╝╚══██╔══╝██╔════╝██║ ██║██╔══██╗",
9
- "██║ ██║█████╗ ██║ ██║ █████╗ ██║ ██║██████╔╝",
10
- "╚██╗ ██╔╝██╔══╝ ██║ ██║ ██╔══╝ ██║ ██║██╔══██╗",
11
- " ╚████╔╝ ███████╗╚██████╗ ██║ ███████╗╚██████╔╝██║ ██║",
12
- " ╚═══╝ ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝",
13
- ];
14
- const WORDMARK_WIDTH = 58;
15
- /** Brand banner for the interactive welcome. Full wordmark when it fits; a compact mark otherwise. */
16
- export function Logo() {
17
- const { stdout } = useStdout();
18
- const cols = stdout?.columns ?? 80;
19
- if (cols >= WORDMARK_WIDTH + 6) {
20
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [WORDMARK.map((line, i) => (_jsx(Text, { color: BRAND_BLUE, children: line }, i))), _jsx(Text, { dimColor: true, children: ` space-engineering agent · v${VERSION}` })] }));
21
- }
22
- // Compact mark for narrow (but ink-capable) terminals — a downward-triangle "V" + wordmark.
23
- return (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: BRAND_BLUE, bold: true, children: "▽ " }), _jsx(Text, { bold: true, color: "white", children: "VECTEUR" }), _jsx(Text, { dimColor: true, children: ` space-engineering agent · v${VERSION}` })] }));
24
- }
@@ -1,38 +0,0 @@
1
- const BOLD = "\x1b[1m";
2
- const DIM = "\x1b[2m";
3
- const CYAN = "\x1b[36m";
4
- const RESET = "\x1b[0m";
5
- /** Server answers sometimes carry web-UI HTML entities — render the literal characters. */
6
- function decodeEntities(s) {
7
- return s
8
- .replace(/&lt;/g, "<")
9
- .replace(/&gt;/g, ">")
10
- .replace(/&quot;/g, '"')
11
- .replace(/&#0?39;/g, "'")
12
- .replace(/&nbsp;/g, " ")
13
- .replace(/&amp;/g, "&"); // last, so we don't double-decode
14
- }
15
- /**
16
- * Answers can include web-UI HTML — notably `<details><summary>…</summary>…</details>`
17
- * collapsible blocks (subagent synthesis). A terminal can't collapse, so render the summary
18
- * as a dim section header and keep the body; drop the wrapper and any other stray tags.
19
- */
20
- function stripHtml(s) {
21
- return s
22
- .replace(/<summary[^>]*>([\s\S]*?)<\/summary>/gi, (_m, inner) => `${DIM}▸ ${inner.replace(/<[^>]+>/g, "").trim()}${RESET}`)
23
- .replace(/<\/?details[^>]*>/gi, "")
24
- .replace(/<[^>]+>/g, "");
25
- }
26
- export function markdownToAnsi(md) {
27
- return decodeEntities(stripHtml(md))
28
- .split(/\r?\n/)
29
- .filter((line) => !/^\s*---\s*$/.test(line))
30
- .map((line) => {
31
- const heading = line.match(/^\s*#{1,6}\s+(.+)$/);
32
- const normalized = heading ? `${BOLD}${heading[1]}${RESET}` : line.replace(/^(\s*)[-*]\s+/, "$1• ");
33
- return normalized
34
- .replace(/\*\*([^*]+)\*\*/g, `${BOLD}$1${RESET}`)
35
- .replace(/`([^`]+)`/g, `${DIM}${CYAN}$1${RESET}`);
36
- })
37
- .join("\n");
38
- }
package/dist/update.js DELETED
@@ -1,75 +0,0 @@
1
- /**
2
- * Auto-update MVP (npm channel). Three pieces:
3
- * - a NON-blocking notice: on start we print an offline-safe banner from cache and, at most
4
- * once/day, refresh the cache from the npm registry in the background.
5
- * - `vecteur update`: runs `npm i -g @vecteur/cli@latest`.
6
- * - the `User-Agent: vecteur-cli/<version>` header (in api.ts) lets the server return 426 to
7
- * hard-gate clients below a minimum supported version.
8
- * It never blocks or breaks the CLI: every network/FS path is best-effort and fails silent.
9
- */
10
- import { VERSION } from "./version.js";
11
- import { getUpdateCache, saveUpdateCache } from "./config.js";
12
- const PKG = "@vecteur/cli";
13
- const REGISTRY = `https://registry.npmjs.org/${PKG}/latest`;
14
- const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // once per day
15
- /** true if `latest` is a strictly higher x.y.z than `current` (prerelease suffix ignored). */
16
- export function isNewer(latest, current) {
17
- const parse = (v) => v.replace(/^v/, "").split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
18
- const a = parse(latest);
19
- const b = parse(current);
20
- for (let i = 0; i < 3; i++) {
21
- if ((a[i] ?? 0) > (b[i] ?? 0))
22
- return true;
23
- if ((a[i] ?? 0) < (b[i] ?? 0))
24
- return false;
25
- }
26
- return false;
27
- }
28
- /** Instant, offline banner from the cached registry version (undefined if up to date). */
29
- export function updateNoticeFromCache() {
30
- const { latestKnownVersion } = getUpdateCache();
31
- if (latestKnownVersion && isNewer(latestKnownVersion, VERSION)) {
32
- return `A new Vecteur CLI is available: ${VERSION} → ${latestKnownVersion}. Run \`vecteur update\`.`;
33
- }
34
- return undefined;
35
- }
36
- /** Refresh the cached latest version from the registry, throttled to once/day. Fire-and-forget. */
37
- export async function refreshUpdateCache() {
38
- try {
39
- const { lastUpdateCheck } = getUpdateCache();
40
- if (lastUpdateCheck && Date.now() - lastUpdateCheck < CHECK_INTERVAL_MS)
41
- return;
42
- const res = await fetch(REGISTRY, {
43
- headers: { Accept: "application/json" },
44
- signal: AbortSignal.timeout(3000),
45
- });
46
- if (!res.ok)
47
- return;
48
- const data = (await res.json());
49
- if (data.version)
50
- saveUpdateCache(data.version);
51
- }
52
- catch {
53
- /* offline / registry down / timeout — silent, try again tomorrow */
54
- }
55
- }
56
- /** `vecteur update` — self-update via the global npm install. */
57
- export async function runUpdate() {
58
- const { spawn } = await import("node:child_process");
59
- console.log(`Updating ${PKG} to the latest version…`);
60
- await new Promise((resolve) => {
61
- const child = spawn("npm", ["install", "-g", `${PKG}@latest`], { stdio: "inherit" });
62
- child.on("error", (err) => {
63
- console.error(`Couldn't run npm (${err.message}). Update manually: npm i -g ${PKG}@latest` +
64
- `\n(or, for a standalone binary, grab the latest release: https://github.com/vecteurspace/vecteur-cli/releases)`);
65
- resolve();
66
- });
67
- child.on("close", (code) => {
68
- if (code === 0)
69
- console.log("Done. Run `vecteur --version` to confirm.");
70
- else
71
- console.error(`npm exited with code ${code}. Try: npm i -g ${PKG}@latest`);
72
- resolve();
73
- });
74
- });
75
- }
package/dist/version.js DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * Single source of truth for the CLI version. Kept in sync with package.json by
3
- * `version.test.ts` (the build/test fails if they drift). Used for `--version`, the
4
- * `User-Agent` header (lets the server gate old clients with 426), and the update check.
5
- */
6
- export const VERSION = "0.2.4";