@vecteur/cli 0.7.1 → 0.9.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 +125 -0
- package/bin/vecteur.js +71 -19
- package/package.json +23 -3
- package/src/blocks.js +40 -0
- package/src/browser.js +18 -0
- package/src/client.js +216 -81
- package/src/commands.js +161 -0
- package/src/contract.js +22 -4
- package/src/credentials.js +56 -63
- package/src/diagnostics.js +39 -0
- package/src/download.js +33 -2
- package/src/folders.js +270 -0
- package/src/format.js +39 -29
- package/src/input.js +148 -0
- package/src/markdown.js +226 -0
- package/src/mcp.js +1 -0
- package/src/preferences.js +29 -0
- package/src/session.js +689 -0
- package/src/store.js +82 -0
- package/src/terminal.js +335 -0
- package/src/tui/view.js +319 -0
- package/src/update.js +313 -0
- package/src/vendors.js +44 -0
package/README.md
CHANGED
|
@@ -6,6 +6,16 @@ calls the OPE, engine or database directly.
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install -g @vecteur/cli
|
|
9
|
+
vecteur
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`vecteur` opens the session, signs in through your browser when needed, and finds the Project
|
|
13
|
+
linked to this folder. A new folder creates its Project only when you send your first ask.
|
|
14
|
+
No `init` command or file in the working directory is required.
|
|
15
|
+
|
|
16
|
+
For scripts and explicit one-shot commands:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
9
19
|
vecteur login
|
|
10
20
|
vecteur whoami
|
|
11
21
|
vecteur project list
|
|
@@ -57,3 +67,118 @@ SHA-256, and refuse to overwrite a local path. Use `--project ID` when no Projec
|
|
|
57
67
|
`vecteur mcp` serves MCP over stdio using the same credentials and public client. It exposes
|
|
58
68
|
`vecteur_project_get`, `vecteur_run_submit`, `vecteur_run_get` and `vecteur_run_events`.
|
|
59
69
|
An MCP host supplies tool arguments; stdout carries only MCP protocol.
|
|
70
|
+
|
|
71
|
+
`vecteur mcp setup claude` (or `codex`) runs that vendor's own `mcp add` once so your Claude Code
|
|
72
|
+
or Codex can call Vecteur. `vecteur handoff claude` (or `codex`, or `/handoff` in a session) opens
|
|
73
|
+
your own Claude Code/Codex in this terminal with Vecteur attached for that session only, and
|
|
74
|
+
returns when it exits. The vendor keeps its own login and billing; Vecteur never reads its
|
|
75
|
+
credentials and the handoff changes no vendor configuration. This is a handoff, not the integrated
|
|
76
|
+
mode in which the vendor session appears inside Vecteur's TUI and web.
|
|
77
|
+
|
|
78
|
+
`vecteur run export <run-id> [--output <dir>]` (or `/export [dir]`) writes a completed Run into a
|
|
79
|
+
new folder: `answer.md` (exact answer bytes), `run.json` (the canonical record) and every artifact,
|
|
80
|
+
each verified against its SHA-256 CAS id. Nothing is overwritten.
|
|
81
|
+
|
|
82
|
+
## Interactive session
|
|
83
|
+
|
|
84
|
+
Enter sends; Ctrl+J or Alt+Enter inserts a line. Bracketed paste is one edit and never sends.
|
|
85
|
+
The rich view uses normal terminal scrollback. `vecteur --plain` gives a linear view;
|
|
86
|
+
`NO_COLOR=1 vecteur` or `--no-color` removes styling. `TERM=dumb` selects the linear view.
|
|
87
|
+
Bare invocation in a pipe or CI keeps the previous status output; `vecteur status` always prints it.
|
|
88
|
+
|
|
89
|
+
- `/project` chooses a Project for this session; `/link` saves the folder association.
|
|
90
|
+
`/unlink` removes that association for this subtree and preserves the draft. `/name` renames
|
|
91
|
+
the current Project or names the one created on first send. `/history` loads older Runs on demand.
|
|
92
|
+
- `/model` selects the next Run's model or route from the matched server catalogue. `/mode`
|
|
93
|
+
explains leadership, execution location and payer. Stage 1 runs in Vecteur cloud.
|
|
94
|
+
- `/attach <path>` previews a selected file and asks before uploading on send. Up to eight
|
|
95
|
+
files, 8 MiB each and 32 MiB total; all available extracts are selected. `/clear-files` removes
|
|
96
|
+
pending attachments. Nothing scans your directory or uploads unrelated files.
|
|
97
|
+
- `/answer` handles clarification, including multiple choices and custom text. `/steer <text>`
|
|
98
|
+
and `/answer-now` submit instructions; acceptance is distinct from execution.
|
|
99
|
+
- `/cancel` or double Esc requests cloud Run cancellation. Ctrl+D on an empty composer or
|
|
100
|
+
`/quit` detaches; the cloud Run continues. Ctrl+C clears a nonempty composer.
|
|
101
|
+
- `/files` lists outputs; `/download <number> [path]` verifies bytes and refuses overwrite.
|
|
102
|
+
`/copy [path]` preserves the exact answer: terminal clipboard when supported, otherwise a file.
|
|
103
|
+
- `/resume` reconciles an interrupted submission using its original identity, or follows the
|
|
104
|
+
same Run from its validated event cursor. A moved head requires an explicit new send.
|
|
105
|
+
Unknown admission remains pending if authority or transport prevents reconciliation.
|
|
106
|
+
- `/details [number]`, `/web`, `/doctor` and `/help` expose details, browser access, diagnostics
|
|
107
|
+
and commands. Updates are manual; an incompatible origin supplies its required install command.
|
|
108
|
+
|
|
109
|
+
Folder links are scoped by origin, authenticated account and canonical directory identity.
|
|
110
|
+
An explicit parent link applies to descendants; a nearer link or unlink overrides it. Linking
|
|
111
|
+
home, a filesystem root or a detected mount root requires typing its full path. A replaced directory or inaccessible Project asks for recovery.
|
|
112
|
+
A link is metadata and never a filesystem grant. Viewer access permits reads and downloads.
|
|
113
|
+
|
|
114
|
+
`folders.json` beside the credential store holds bindings, drafts and unresolved request identities.
|
|
115
|
+
POSIX directories/files use 0700/0600; writes are atomic and protected by a short writer lock.
|
|
116
|
+
A crashed writer leaves an explicit lock recovery message. Drafts are bounded to 256 KiB and
|
|
117
|
+
expire after 30 days on next registry access/write. Up to 20 revisions from concurrent editors remain
|
|
118
|
+
recoverable through `/drafts`; `/clear` removes the selected draft. Unchanged terminals never
|
|
119
|
+
overwrite another editor, and admission consumes only its exact draft revision. Unresolved
|
|
120
|
+
request evidence stays until resolved. Account draft recovery requires the same credential fingerprint
|
|
121
|
+
previously bound by a successful account read. Never-signed-in drafts occupy a separate unsigned
|
|
122
|
+
namespace with no Project authority; only explicit login transfers them into an account. Neither
|
|
123
|
+
recovery path grants server access. Attachments are held in
|
|
124
|
+
memory until send and must be selected again after restart. A newer store version is refused without
|
|
125
|
+
rewriting it. Previous CLI versions ignore the separate folder registry.
|
|
126
|
+
|
|
127
|
+
## Maintainer boundaries and later stages
|
|
128
|
+
|
|
129
|
+
One package, one session owner, two views; stage 1 has no daemon or second application.
|
|
130
|
+
|
|
131
|
+
| Owner | Responsibility |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `bin/vecteur.js` | TTY dispatch and preserved script/MCP commands; lazy terminal loading |
|
|
134
|
+
| `session.js`, `commands.js` | Cloud actions, prompts, recovery and view facts |
|
|
135
|
+
| `client.js` | Public API, auth transport, validated records, sources, controls and cursor replay |
|
|
136
|
+
| `credentials.js`, `folders.js`, `store.js` | Existing login, account/folder metadata and atomic persistence |
|
|
137
|
+
| `input.js`, `terminal.js` | Grapheme-aware composer, paste, signals and the single stdout writer |
|
|
138
|
+
| `tui/view.js`, `blocks.js` | Rich layout and Run presentation; plain view uses the same session facts |
|
|
139
|
+
| `markdown.js`, `contract.js` | Generated parser/contracts; no runtime web or SDK import |
|
|
140
|
+
| `download.js`, `diagnostics.js` | Verified downloader, Run export and shared read-only diagnostics |
|
|
141
|
+
| `vendors.js` | Claude Code/Codex MCP setup and native-terminal handoff argv; no vendor credentials |
|
|
142
|
+
|
|
143
|
+
The renderer uses exact `wrap-ansi`/`string-width` pins and a common display-width implementation.
|
|
144
|
+
Ink failed the frozen startup budget. Completed output is appended; resize abandons old live
|
|
145
|
+
geometry so it never guesses how many reflowed scrollback lines to erase. Neither view authorizes
|
|
146
|
+
operations or issues requests itself.
|
|
147
|
+
|
|
148
|
+
Later computer-use work adds separate device credentials, grants, IPC/relay and cancellation
|
|
149
|
+
subjects. Native Claude/Codex sessions need their own identity and lifecycle; they are not cloud
|
|
150
|
+
Runs. A folder link must never become a local execution grant. External tool results enter existing
|
|
151
|
+
custody with tool/version/hash/trust metadata. No companion/native/driver stubs, vendor credential
|
|
152
|
+
discovery or local execution capability ships in this package.
|
|
153
|
+
|
|
154
|
+
The intended platform matrix includes Linux, macOS and Windows Terminal, plus screen-reader
|
|
155
|
+
qualification. Current evidence and unresolved cases belong in the stage-1 WBS/backlog; Linux
|
|
156
|
+
tests alone do not qualify other platforms. Standalone binaries are a later packaging option using
|
|
157
|
+
the same session owner. This candidate is unpublished until release acceptance. Its package-owned
|
|
158
|
+
`vecteur.channel` is `preview`, visible even offline; release preparation must promote it to
|
|
159
|
+
`stable` before freezing the published bytes. Updates across channels always ask explicitly.
|
|
160
|
+
|
|
161
|
+
## Drafting, recovery and updates
|
|
162
|
+
|
|
163
|
+
`vecteur --offline` opens a saved local draft without a network request or browser. Use `/login`
|
|
164
|
+
when ready; signing in restores the draft for editing and never sends it automatically. `/help`
|
|
165
|
+
searches every command and `/shortcuts` lists editing, choice and Run-control keys. `/settings`
|
|
166
|
+
saves plain/screen-reader display, reduced motion and update preferences separately from tokens.
|
|
167
|
+
|
|
168
|
+
`/update check` (or `vecteur update --check`) identifies the actual launcher, Node, PATH mismatch
|
|
169
|
+
and origin's required version. Default installation asks first. Opted-in automatic upgrades defer
|
|
170
|
+
while a Run, login or unresolved submission is active. Only a user-owned npm symlink launcher with
|
|
171
|
+
verified release integrity and npm provenance qualifies. Existing module trees stay intact; the
|
|
172
|
+
launcher switches to a separately installed npm generation. Recovery instructions name the retained
|
|
173
|
+
previous target. Windows wrappers, custom Node loaders, direct source scripts and packages without
|
|
174
|
+
provenance receive a refusal and exact manual guidance. No elevation, shell setup edits or automatic
|
|
175
|
+
old-generation deletion occurs. Scripts, JSON, MCP, help and version never install an update.
|
|
176
|
+
|
|
177
|
+
For an **unpublished preview**, use its exact installed path with `--offline`, or pass `--origin`
|
|
178
|
+
for the matching ready acceptance backend. Installing a global release does not update an absolute
|
|
179
|
+
preview path. Production's exact contract pin may reject a preview before browser sign-in.
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
/path/to/preview/bin/vecteur --offline
|
|
183
|
+
/path/to/preview/bin/vecteur --origin http://127.0.0.1:PORT --no-browser
|
|
184
|
+
```
|
package/bin/vecteur.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
2
|
import { readFileSync } from "node:fs";
|
|
4
3
|
|
|
5
4
|
import {
|
|
@@ -9,9 +8,10 @@ import {
|
|
|
9
8
|
import {
|
|
10
9
|
clearCredentials, credentialsPath, readCredentials, setCurrentOrigin, writeCredentials, selectProject,
|
|
11
10
|
} from "../src/credentials.js";
|
|
12
|
-
import { downloadArtifact, downloadProjection } from "../src/download.js";
|
|
11
|
+
import { downloadArtifact, downloadProjection, exportRun } from "../src/download.js";
|
|
13
12
|
import { humanProjects, resolveNamed, resolveRunProject, resolveWorkspace } from "../src/projects.js";
|
|
14
13
|
import { canonicalJson, currentTransferTables, human, humanCost, progress } from "../src/format.js";
|
|
14
|
+
import { openBrowser } from "../src/browser.js";
|
|
15
15
|
|
|
16
16
|
function manifest() {
|
|
17
17
|
return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
@@ -43,8 +43,11 @@ function refuseOldNode() {
|
|
|
43
43
|
|
|
44
44
|
function helpText(version) {
|
|
45
45
|
return `vecteur ${version}
|
|
46
|
-
login | logout | whoami | project list | project create <name> [--workspace <name-or-id>] | project use <name-or-id> | project get <id> | run submit [-p <id>] -a <ask> [--parent <state-hash>] | run get <id> | run events <id> | artifact get <id> [--output <path>] [-p <id>] | projection get <run-id> <projection-id> [--output <path>] [-p <id>] | mcp
|
|
47
|
-
|
|
46
|
+
login | logout | whoami | project list | project create <name> [--workspace <name-or-id>] | project use <name-or-id> | project get <id> | run submit [-p <id>] -a <ask> [--parent <state-hash>] | run get <id> | run events <id> | run export <id> [--output <dir>] [-p <id>] | artifact get <id> [--output <path>] [-p <id>] | projection get <run-id> <projection-id> [--output <path>] [-p <id>] | mcp
|
|
47
|
+
mcp setup claude|codex adds Vecteur to your Claude Code/Codex · handoff claude|codex opens it in this terminal with Vecteur attached
|
|
48
|
+
status | doctor | (bare vecteur opens an interactive session)
|
|
49
|
+
--help --version --json --origin <url> --no-browser --offline --plain --no-color --local -p/--project -a/--ask --output <path>
|
|
50
|
+
update [--check] [--yes] [--origin <url>] updates the executing installation
|
|
48
51
|
run submit continues the Project's current head; --parent <state-hash> admits it only if that is still the head
|
|
49
52
|
`;
|
|
50
53
|
}
|
|
@@ -58,19 +61,6 @@ function writeStatus(env = process.env) {
|
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
function openBrowser(url) {
|
|
62
|
-
const command = process.platform === "darwin" ? "open"
|
|
63
|
-
: process.platform === "win32" ? "cmd" : "xdg-open";
|
|
64
|
-
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
65
|
-
try {
|
|
66
|
-
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
67
|
-
child.on("error", () => {});
|
|
68
|
-
child.unref();
|
|
69
|
-
} catch {
|
|
70
|
-
// The address is already on screen; failing to spawn a browser is not a failed login.
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
64
|
function parseCommand(argv) {
|
|
75
65
|
let args = [...argv];
|
|
76
66
|
const value = (candidate) => typeof candidate === "string" && candidate.length > 0 &&
|
|
@@ -97,6 +87,10 @@ function parseCommand(argv) {
|
|
|
97
87
|
const json = takeFlag("--json");
|
|
98
88
|
const origin = takeOption("--origin");
|
|
99
89
|
const noBrowser = takeFlag("--no-browser");
|
|
90
|
+
const offline = takeFlag("--offline");
|
|
91
|
+
const checkUpdate = takeFlag("--check"), yesUpdate = takeFlag("--yes");
|
|
92
|
+
const plain = takeFlag("--plain");
|
|
93
|
+
const noColor = takeFlag("--no-color");
|
|
100
94
|
const local = takeFlag("--local");
|
|
101
95
|
const projectId = takeOption("--project", "-p");
|
|
102
96
|
const ask = takeOption("--ask", "-a");
|
|
@@ -111,10 +105,24 @@ function parseCommand(argv) {
|
|
|
111
105
|
}
|
|
112
106
|
if (version) return { kind: "version" };
|
|
113
107
|
if (help) return { kind: "help" };
|
|
108
|
+
if (args.length === 1 && args[0] === "update" && !json && !local && !offline && !plain && !noColor
|
|
109
|
+
&& !noBrowser && projectId === undefined && ask === undefined && workspace === undefined && output === undefined && parent === undefined) {
|
|
110
|
+
return { kind: "update", origin, check: checkUpdate, yes: yesUpdate };
|
|
111
|
+
}
|
|
112
|
+
if (checkUpdate || yesUpdate) throw new CliError("usage_invalid", "--check and --yes apply only to update");
|
|
113
|
+
if (!args.length && !json && !local && projectId === undefined && ask === undefined
|
|
114
|
+
&& workspace === undefined && output === undefined && parent === undefined) {
|
|
115
|
+
return { kind: "session", origin, noBrowser, plain, noColor, offline };
|
|
116
|
+
}
|
|
117
|
+
if (offline) throw new CliError("usage_invalid", "--offline applies only to an interactive session");
|
|
118
|
+
if (plain || noColor) throw new CliError("usage_invalid", "--plain and --no-color apply to an interactive session");
|
|
114
119
|
const extra = json || origin !== undefined || noBrowser || local
|
|
115
120
|
|| projectId !== undefined || ask !== undefined || workspace !== undefined || output !== undefined;
|
|
116
|
-
if (args.length === 0 && !extra) return { kind:
|
|
121
|
+
if (args.length === 1 && ["status", "doctor"].includes(args[0]) && !extra) return { kind: args[0] };
|
|
117
122
|
if (args.length === 1 && args[0] === "mcp" && !extra) return { kind: "mcp" };
|
|
123
|
+
if (!extra && ((args.length === 3 && args[0] === "mcp" && args[1] === "setup") || (args.length === 2 && args[0] === "handoff"))) {
|
|
124
|
+
return { kind: "vendor", mode: args[0] === "mcp" ? "setup" : "handoff", vendor: args.at(-1) };
|
|
125
|
+
}
|
|
118
126
|
if (args[0] === "project" && projectId === undefined && ask === undefined && output === undefined
|
|
119
127
|
&& !noBrowser && !local) {
|
|
120
128
|
if (args.length === 3 && args[1] === "create" && value(args[2])) {
|
|
@@ -157,6 +165,9 @@ function parseCommand(argv) {
|
|
|
157
165
|
&& projectId === undefined && ask === undefined && output === undefined) {
|
|
158
166
|
return { kind: "run_events", id: args[2], origin, json };
|
|
159
167
|
}
|
|
168
|
+
if (args.length === 3 && args[0] === "run" && args[1] === "export" && value(args[2]) && ask === undefined) {
|
|
169
|
+
return { kind: "run_export", id: args[2], projectId, output, origin, json };
|
|
170
|
+
}
|
|
160
171
|
if (args.length === 3 && args[0] === "artifact" && args[1] === "get" && value(args[2])
|
|
161
172
|
&& ask === undefined) {
|
|
162
173
|
return { kind: "artifact_get", id: args[2], projectId, output, origin, json };
|
|
@@ -203,7 +214,7 @@ async function login(command, env = process.env) {
|
|
|
203
214
|
try {
|
|
204
215
|
await new VecteurClient({ origin, token: previous, timeout }).credentialSelfRevoke();
|
|
205
216
|
} catch (error) {
|
|
206
|
-
if (!(error instanceof CliError &&
|
|
217
|
+
if (!(error instanceof CliError && ["pat_revoked", "pat_expired", "pat_invalid"].includes(error.code))) throw error;
|
|
207
218
|
}
|
|
208
219
|
}
|
|
209
220
|
const path = writeCredentials({ origin, token: created.token }, env);
|
|
@@ -277,6 +288,39 @@ async function main() {
|
|
|
277
288
|
writeStatus();
|
|
278
289
|
return;
|
|
279
290
|
}
|
|
291
|
+
if (command.kind === "doctor") {
|
|
292
|
+
const { diagnose } = await import("../src/diagnostics.js");
|
|
293
|
+
const result = await diagnose();
|
|
294
|
+
process.stdout.write(result.text + "\n");
|
|
295
|
+
if (!result.ok) process.exitCode = 1;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (command.kind === "session") {
|
|
299
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || process.env.CI !== undefined) {
|
|
300
|
+
if (command.plain || command.noColor || command.origin || command.noBrowser || command.offline) {
|
|
301
|
+
throw new CliError("terminal_required", "Interactive options need a terminal; use existing subcommands for scripts");
|
|
302
|
+
}
|
|
303
|
+
writeStatus(); return;
|
|
304
|
+
}
|
|
305
|
+
const { startTerminal } = await import("../src/terminal.js");
|
|
306
|
+
await startTerminal(command);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (command.kind === "update") {
|
|
310
|
+
const { updateCommand } = await import("../src/update.js");
|
|
311
|
+
await updateCommand(command);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (command.kind === "vendor") {
|
|
315
|
+
const { VENDORS, executingServer, runVendor, shellLine, vendorArgv } = await import("../src/vendors.js");
|
|
316
|
+
const argv = vendorArgv(command.vendor, command.mode, command.mode === "handoff" ? executingServer() : undefined);
|
|
317
|
+
process.stderr.write(command.mode === "setup"
|
|
318
|
+
? `Adding Vecteur to ${VENDORS[command.vendor]} with its own command:\n ${shellLine(argv)}\n`
|
|
319
|
+
: `Opening your ${VENDORS[command.vendor]} terminal with Vecteur attached for this session only.\n`
|
|
320
|
+
+ `${VENDORS[command.vendor]} keeps its own login and billing; exit it to return here.\n`);
|
|
321
|
+
process.exitCode = await runVendor(argv);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
280
324
|
if (command.kind === "mcp") {
|
|
281
325
|
const { serveVecteurMcp } = await import("../src/mcp.js");
|
|
282
326
|
serveVecteurMcp();
|
|
@@ -328,6 +372,14 @@ async function main() {
|
|
|
328
372
|
: `Downloaded ${value.bytes} bytes to ${value.path} (${value.content_type})\n`);
|
|
329
373
|
return;
|
|
330
374
|
}
|
|
375
|
+
else if (command.kind === "run_export") {
|
|
376
|
+
const projectId = await resolveRunProject(client, command.projectId);
|
|
377
|
+
value = await exportRun(client, await client.runGet(command.id), projectId,
|
|
378
|
+
command.output ?? `vecteur-run-${command.id.replace(/[^A-Za-z0-9_-]/g, "").slice(0, 12)}`);
|
|
379
|
+
process.stdout.write(command.json ? canonicalJson(value) : `Exported Run ${value.run_id} (${value.terminal}) to ${value.directory}\n`
|
|
380
|
+
+ value.files.map(file => ` ${file.path} · ${file.bytes} bytes${file.cas_ref ? " · verified" : ""}\n`).join(""));
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
331
383
|
else if (command.kind === "projection_get") {
|
|
332
384
|
const projectId = await resolveRunProject(client, command.projectId);
|
|
333
385
|
const run = await client.runGet(command.runId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vecteur/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"vecteur": "bin/vecteur.js"
|
|
@@ -15,7 +15,21 @@
|
|
|
15
15
|
"src/errors.js",
|
|
16
16
|
"src/format.js",
|
|
17
17
|
"src/mcp.js",
|
|
18
|
-
"src/projects.js"
|
|
18
|
+
"src/projects.js",
|
|
19
|
+
"src/store.js",
|
|
20
|
+
"src/folders.js",
|
|
21
|
+
"src/browser.js",
|
|
22
|
+
"src/blocks.js",
|
|
23
|
+
"src/session.js",
|
|
24
|
+
"src/input.js",
|
|
25
|
+
"src/commands.js",
|
|
26
|
+
"src/terminal.js",
|
|
27
|
+
"src/markdown.js",
|
|
28
|
+
"src/tui/view.js",
|
|
29
|
+
"src/diagnostics.js",
|
|
30
|
+
"src/preferences.js",
|
|
31
|
+
"src/update.js",
|
|
32
|
+
"src/vendors.js"
|
|
19
33
|
],
|
|
20
34
|
"scripts": {
|
|
21
35
|
"test": "node --test tests/*.test.js",
|
|
@@ -28,14 +42,20 @@
|
|
|
28
42
|
},
|
|
29
43
|
"dependencies": {
|
|
30
44
|
"@modelcontextprotocol/server": "2.0.0",
|
|
45
|
+
"string-width": "7.2.0",
|
|
46
|
+
"wrap-ansi": "9.0.2",
|
|
31
47
|
"zod": "4.4.3"
|
|
32
48
|
},
|
|
33
49
|
"devDependencies": {
|
|
34
50
|
"@modelcontextprotocol/client": "2.0.0"
|
|
35
51
|
},
|
|
36
|
-
"description": "The Vecteur command line
|
|
52
|
+
"description": "The Vecteur command line \u2014 one login, the same product as the web app, from any directory.",
|
|
37
53
|
"license": "UNLICENSED",
|
|
38
54
|
"publishConfig": {
|
|
39
55
|
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"vecteur": {
|
|
58
|
+
"updateProtocol": 1,
|
|
59
|
+
"channel": "stable"
|
|
40
60
|
}
|
|
41
61
|
}
|
package/src/blocks.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { humanRun, progress } from "./format.js";
|
|
2
|
+
|
|
3
|
+
function elapsed(run, now) {
|
|
4
|
+
if (!run) return null;
|
|
5
|
+
const accepted = run.accepted_at;
|
|
6
|
+
// Both owner duration and live age start at admission, never at local follow/restart.
|
|
7
|
+
// Live age uses the client wall clock; invalid/future anchors are unavailable.
|
|
8
|
+
const seconds = run.terminal ? run.duration_seconds
|
|
9
|
+
: Number.isSafeInteger(accepted) && accepted >= 0 && accepted <= now / 1000
|
|
10
|
+
? Math.floor(now / 1000 - accepted) : null;
|
|
11
|
+
if (!Number.isSafeInteger(seconds) || seconds < 0) return "Elapsed unavailable";
|
|
12
|
+
const pad = value => String(value).padStart(2, "0");
|
|
13
|
+
return "Elapsed " + (seconds >= 3600 ? `${Math.floor(seconds / 3600)}:` : "")
|
|
14
|
+
+ `${pad(Math.floor(seconds / 60) % 60)}:${pad(seconds % 60)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** RunWire owns the facts. Optional session cache projects only newly appended events. */
|
|
18
|
+
export function runView(run, cache, now = Date.now()) {
|
|
19
|
+
const events = run?.events ?? [];
|
|
20
|
+
const same = cache && cache.run === run && cache.count <= events.length && cache.view;
|
|
21
|
+
if (same && cache.count === events.length && cache.terminal === run?.terminal) return { ...cache.view, elapsed: elapsed(run, now) };
|
|
22
|
+
const view = same ? cache.view : {
|
|
23
|
+
subject: run ? { kind: "vecteur_run", id: run.run_id } : null,
|
|
24
|
+
answer: "", question: null, artifacts: [], steps: [], progress: "Waiting for Vecteur",
|
|
25
|
+
terminal: "", transcript: "",
|
|
26
|
+
};
|
|
27
|
+
for (let index = same ? cache.count : 0; index < events.length; index++) {
|
|
28
|
+
const event = events[index];
|
|
29
|
+
if (event.kind === "synthesis") view.answer = event.payload.answer ?? "";
|
|
30
|
+
if (event.kind === "clarification") view.question = event.payload.state === "requested" ? event.payload : null;
|
|
31
|
+
if (event.kind === "artifact") view.artifacts.push(event.payload);
|
|
32
|
+
if (["step_started", "step_finished", "task"].includes(event.kind)) view.steps.push(event);
|
|
33
|
+
const line = progress(event);
|
|
34
|
+
if (line) view.progress = line.trim();
|
|
35
|
+
}
|
|
36
|
+
view.terminal = run?.terminal ?? "";
|
|
37
|
+
view.transcript = run?.terminal ? humanRun(run) : "";
|
|
38
|
+
if (cache) Object.assign(cache, { run, count: events.length, terminal: run?.terminal, view });
|
|
39
|
+
return { ...view, elapsed: elapsed(run, now) };
|
|
40
|
+
}
|
package/src/browser.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/** Only an explicit user action calls this; the visible URL always remains usable. */
|
|
4
|
+
export async function openBrowser(url) {
|
|
5
|
+
try {
|
|
6
|
+
const parsed = new URL(url);
|
|
7
|
+
if (!["https:", "http:"].includes(parsed.protocol)) return false;
|
|
8
|
+
const command = process.platform === "darwin" ? "open"
|
|
9
|
+
: process.platform === "win32" ? "rundll32.exe" : "xdg-open";
|
|
10
|
+
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", parsed.href] : [parsed.href];
|
|
11
|
+
return await new Promise(resolve => {
|
|
12
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
13
|
+
const timer = setTimeout(() => { child.unref(); resolve(true); }, 1_500);
|
|
14
|
+
child.once("error", () => { clearTimeout(timer); resolve(false); });
|
|
15
|
+
child.once("exit", code => { clearTimeout(timer); resolve(code === 0); });
|
|
16
|
+
});
|
|
17
|
+
} catch { return false; }
|
|
18
|
+
}
|