@cargo-ai/cli 1.0.87 → 1.0.89
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 +1 -1
- package/build/commands/ask/claudeConfig.d.ts +2 -0
- package/build/commands/ask/claudeConfig.d.ts.map +1 -1
- package/build/commands/ask/claudeConfig.js +67 -1
- package/build/commands/ask/index.js +1 -1
- package/build/commands/ask/localHarness.d.ts +15 -1
- package/build/commands/ask/localHarness.d.ts.map +1 -1
- package/build/commands/ask/localHarness.js +127 -42
- package/build/commands/ask/messageStream.js +1 -1
- package/build/commands/ask/prompt.d.ts +7 -1
- package/build/commands/ask/prompt.d.ts.map +1 -1
- package/build/commands/ask/prompt.js +9 -3
- package/build/commands/ask/session.d.ts +8 -1
- package/build/commands/ask/session.d.ts.map +1 -1
- package/build/commands/ask/session.js +364 -62
- package/build/commands/ask/thinking.d.ts +21 -0
- package/build/commands/ask/thinking.d.ts.map +1 -0
- package/build/commands/ask/thinking.js +57 -0
- package/build/commands/ask/typeAhead.d.ts +67 -0
- package/build/commands/ask/typeAhead.d.ts.map +1 -0
- package/build/commands/ask/typeAhead.js +169 -0
- package/build/commands/auth/workspace.js +1 -1
- package/build/commands/runHandler.d.ts +7 -0
- package/build/commands/runHandler.d.ts.map +1 -1
- package/build/commands/runHandler.js +39 -4
- package/build/commands/workspaceManagement/envVar.d.ts +4 -0
- package/build/commands/workspaceManagement/envVar.d.ts.map +1 -0
- package/build/commands/workspaceManagement/envVar.js +71 -0
- package/build/commands/workspaceManagement/index.d.ts.map +1 -1
- package/build/commands/workspaceManagement/index.js +3 -1
- package/package.json +8 -6
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the spinner says while a turn is thinking rather than running a tool.
|
|
3
|
+
*
|
|
4
|
+
* One fixed word for every wait in every turn reads as a progress bar that is
|
|
5
|
+
* not moving — the same three syllables whether the answer took a second or a
|
|
6
|
+
* minute, and no way to tell a turn that is working from one that is stuck.
|
|
7
|
+
* A word that changes as the turn moves through its steps says the session is
|
|
8
|
+
* alive, which is the only thing a spinner can honestly report.
|
|
9
|
+
*
|
|
10
|
+
* Short, gerunds, and unambiguous about the fact that nothing is being done to
|
|
11
|
+
* anything yet. A few are freighted on purpose.
|
|
12
|
+
*/
|
|
13
|
+
export declare const ASK_THINKING_WORDS: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* A word for the next wait, never the one just used.
|
|
16
|
+
*
|
|
17
|
+
* Repeating straight after itself is the one draw that looks like the spinner
|
|
18
|
+
* is stuck rather than moving on, which is the whole thing this avoids.
|
|
19
|
+
*/
|
|
20
|
+
export declare function pickThinkingWord(previous?: string): string;
|
|
21
|
+
//# sourceMappingURL=thinking.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thinking.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/thinking.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAyB/C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAc1D"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the spinner says while a turn is thinking rather than running a tool.
|
|
3
|
+
*
|
|
4
|
+
* One fixed word for every wait in every turn reads as a progress bar that is
|
|
5
|
+
* not moving — the same three syllables whether the answer took a second or a
|
|
6
|
+
* minute, and no way to tell a turn that is working from one that is stuck.
|
|
7
|
+
* A word that changes as the turn moves through its steps says the session is
|
|
8
|
+
* alive, which is the only thing a spinner can honestly report.
|
|
9
|
+
*
|
|
10
|
+
* Short, gerunds, and unambiguous about the fact that nothing is being done to
|
|
11
|
+
* anything yet. A few are freighted on purpose.
|
|
12
|
+
*/
|
|
13
|
+
export const ASK_THINKING_WORDS = [
|
|
14
|
+
"Thinking",
|
|
15
|
+
"Pondering",
|
|
16
|
+
"Mulling",
|
|
17
|
+
"Musing",
|
|
18
|
+
"Noodling",
|
|
19
|
+
"Percolating",
|
|
20
|
+
"Ruminating",
|
|
21
|
+
"Puzzling",
|
|
22
|
+
"Deliberating",
|
|
23
|
+
"Considering",
|
|
24
|
+
"Weighing",
|
|
25
|
+
"Untangling",
|
|
26
|
+
"Wrangling",
|
|
27
|
+
"Churning",
|
|
28
|
+
"Brewing",
|
|
29
|
+
"Digging",
|
|
30
|
+
"Rummaging",
|
|
31
|
+
"Scheming",
|
|
32
|
+
"Plotting",
|
|
33
|
+
"Marshalling",
|
|
34
|
+
"Manifesting",
|
|
35
|
+
"Stowing",
|
|
36
|
+
"Loading",
|
|
37
|
+
"Hauling",
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* A word for the next wait, never the one just used.
|
|
41
|
+
*
|
|
42
|
+
* Repeating straight after itself is the one draw that looks like the spinner
|
|
43
|
+
* is stuck rather than moving on, which is the whole thing this avoids.
|
|
44
|
+
*/
|
|
45
|
+
export function pickThinkingWord(previous) {
|
|
46
|
+
// Callers hand back what the spinner was showing, ellipsis and all, while
|
|
47
|
+
// the list holds bare words. Comparing the two as they come never matches,
|
|
48
|
+
// and the word repeats as often as chance allows.
|
|
49
|
+
const last = previous === undefined ? undefined : previous.replace(/…$/, "");
|
|
50
|
+
const choices = last === undefined
|
|
51
|
+
? ASK_THINKING_WORDS
|
|
52
|
+
: ASK_THINKING_WORDS.filter((word) => word !== last);
|
|
53
|
+
const pool = choices.length === 0 ? ASK_THINKING_WORDS : choices;
|
|
54
|
+
const index = Math.floor(Math.random() * pool.length);
|
|
55
|
+
const word = pool[index];
|
|
56
|
+
return `${word === undefined ? "Thinking" : word}…`;
|
|
57
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/**
|
|
3
|
+
* What the user typed while a turn was running.
|
|
4
|
+
*
|
|
5
|
+
* `submitted` is a line they finished with Enter, meant to be sent as the next
|
|
6
|
+
* question without stopping at the prompt. `pending` is everything typed
|
|
7
|
+
* behind it — a line they were still in the middle of, or a second one they
|
|
8
|
+
* did finish, which is one question too many to queue — and prefills the
|
|
9
|
+
* prompt instead.
|
|
10
|
+
*/
|
|
11
|
+
export type TypeAhead = {
|
|
12
|
+
submitted: string | undefined;
|
|
13
|
+
pending: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const EMPTY_TYPE_AHEAD: TypeAhead;
|
|
16
|
+
export type TypeAheadCollector = {
|
|
17
|
+
/**
|
|
18
|
+
* Hand stdin back, so a prompt inside the turn can read it itself.
|
|
19
|
+
*
|
|
20
|
+
* Resolves after a short settle during which keystrokes are read and thrown
|
|
21
|
+
* away, rather than releasing the terminal on the spot. A tool approval
|
|
22
|
+
* appears without warning under whatever the user is already typing, and it
|
|
23
|
+
* defaults to Yes — so an Enter meant to send the queued question would
|
|
24
|
+
* otherwise land on the picker and approve a shell command nobody read.
|
|
25
|
+
* Anything typed during the settle is discarded, which is the right reading
|
|
26
|
+
* of a keystroke aimed at a prompt that was not on screen yet.
|
|
27
|
+
*/
|
|
28
|
+
pause: () => Promise<void>;
|
|
29
|
+
/** Take it back once that prompt is done. */
|
|
30
|
+
resume: () => void;
|
|
31
|
+
/** What has been typed so far, for the caller to draw under the spinner. */
|
|
32
|
+
current: () => string;
|
|
33
|
+
/** Stop collecting and report what was typed. */
|
|
34
|
+
stop: () => TypeAhead;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Collect keystrokes while a turn is running, instead of letting the terminal
|
|
38
|
+
* queue them and then lose them.
|
|
39
|
+
*
|
|
40
|
+
* Left alone, the tty stays in canonical mode through a turn: it echoes what
|
|
41
|
+
* is typed, buffers the line, and whether any of it survives the switch to raw
|
|
42
|
+
* mode when the Ask prompt reopens is a race — sometimes the line arrived,
|
|
43
|
+
* sometimes it vanished after being echoed, which is the worst of both. Owning
|
|
44
|
+
* the stream for the whole turn makes it deterministic, and lets a question
|
|
45
|
+
* typed during a reply be waiting when the reply ends.
|
|
46
|
+
*
|
|
47
|
+
* Raw mode is also what makes Ctrl-C reliable here. In canonical mode the
|
|
48
|
+
* terminal turns it into a SIGINT for the whole foreground process group,
|
|
49
|
+
* which is delivered to whatever child happens to own the terminal — during a
|
|
50
|
+
* Claude Code start that is Claude Code, which dies without this process ever
|
|
51
|
+
* hearing about it. As a keypress it always arrives, and `onInterrupt` can
|
|
52
|
+
* unwind the turn properly.
|
|
53
|
+
*/
|
|
54
|
+
export declare function collectTypeAhead(input: {
|
|
55
|
+
stream: NodeJS.ReadableStream;
|
|
56
|
+
/** Ctrl-C. Called on the keypress; the caller decides what it means. */
|
|
57
|
+
onInterrupt: () => void;
|
|
58
|
+
/** Called whenever the typed text changes, so it can be redrawn. */
|
|
59
|
+
onChange?: (text: string) => void;
|
|
60
|
+
/**
|
|
61
|
+
* The tail of an earlier type-ahead whose first line was already submitted.
|
|
62
|
+
* Starting from it keeps what was typed behind that question alive across
|
|
63
|
+
* the turn its own Enter kicked off.
|
|
64
|
+
*/
|
|
65
|
+
initialLine?: string;
|
|
66
|
+
}): TypeAheadCollector;
|
|
67
|
+
//# sourceMappingURL=typeAhead.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeAhead.d.ts","sourceRoot":"","sources":["../../../src/commands/ask/typeAhead.ts"],"names":[],"mappings":";AAGA;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,SAG9B,CAAC;AA4BF,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;;;;;;;OAUG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,6CAA6C;IAC7C,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,iDAAiD;IACjD,IAAI,EAAE,MAAM,SAAS,CAAC;CACvB,CAAC;AAgBF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B,wEAAwE;IACxE,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,kBAAkB,CAqJrB"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import * as readline from "node:readline";
|
|
2
|
+
export const EMPTY_TYPE_AHEAD = {
|
|
3
|
+
submitted: undefined,
|
|
4
|
+
pending: "",
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* How long keystrokes are swallowed before a prompt inside a turn takes the
|
|
8
|
+
* keyboard.
|
|
9
|
+
*
|
|
10
|
+
* Long enough to catch the key already travelling when the prompt was decided
|
|
11
|
+
* on, short enough that nobody waiting for the question notices. The whole
|
|
12
|
+
* budget is spent before a human could have read what they are being asked, so
|
|
13
|
+
* nothing legitimate is lost.
|
|
14
|
+
*
|
|
15
|
+
* `CARGO_ASK_APPROVAL_SETTLE_MS` widens it for the end-to-end test that has to
|
|
16
|
+
* type into the window on purpose.
|
|
17
|
+
*/
|
|
18
|
+
const APPROVAL_SETTLE_MS = readSettleMs();
|
|
19
|
+
function readSettleMs() {
|
|
20
|
+
const raw = process.env["CARGO_ASK_APPROVAL_SETTLE_MS"];
|
|
21
|
+
if (raw === undefined) {
|
|
22
|
+
return 250;
|
|
23
|
+
}
|
|
24
|
+
const parsed = Number(raw);
|
|
25
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 250;
|
|
26
|
+
}
|
|
27
|
+
function isTTYStream(stream) {
|
|
28
|
+
return (typeof stream.setRawMode === "function" &&
|
|
29
|
+
stream.isTTY === true);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Collect keystrokes while a turn is running, instead of letting the terminal
|
|
33
|
+
* queue them and then lose them.
|
|
34
|
+
*
|
|
35
|
+
* Left alone, the tty stays in canonical mode through a turn: it echoes what
|
|
36
|
+
* is typed, buffers the line, and whether any of it survives the switch to raw
|
|
37
|
+
* mode when the Ask prompt reopens is a race — sometimes the line arrived,
|
|
38
|
+
* sometimes it vanished after being echoed, which is the worst of both. Owning
|
|
39
|
+
* the stream for the whole turn makes it deterministic, and lets a question
|
|
40
|
+
* typed during a reply be waiting when the reply ends.
|
|
41
|
+
*
|
|
42
|
+
* Raw mode is also what makes Ctrl-C reliable here. In canonical mode the
|
|
43
|
+
* terminal turns it into a SIGINT for the whole foreground process group,
|
|
44
|
+
* which is delivered to whatever child happens to own the terminal — during a
|
|
45
|
+
* Claude Code start that is Claude Code, which dies without this process ever
|
|
46
|
+
* hearing about it. As a keypress it always arrives, and `onInterrupt` can
|
|
47
|
+
* unwind the turn properly.
|
|
48
|
+
*/
|
|
49
|
+
export function collectTypeAhead(input) {
|
|
50
|
+
const { stream } = input;
|
|
51
|
+
let line = input.initialLine === undefined ? "" : input.initialLine;
|
|
52
|
+
let submitted;
|
|
53
|
+
let stopped = false;
|
|
54
|
+
let listening = false;
|
|
55
|
+
let discarding = false;
|
|
56
|
+
const wasRaw = isTTYStream(stream) ? stream.isRaw === true : false;
|
|
57
|
+
const onKeypress = (str, key) => {
|
|
58
|
+
if (stopped === true) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Mid-settle: the terminal is about to belong to a prompt that is not up
|
|
62
|
+
// yet, so nothing typed now is aimed at anything. Ctrl-C included — the
|
|
63
|
+
// prompt taking over has its own, and cancelling the turn out from under
|
|
64
|
+
// a question already being asked is not what the press meant either.
|
|
65
|
+
if (discarding === true) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (key.ctrl === true && key.name === "c") {
|
|
69
|
+
input.onInterrupt();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (key.name === "return" || key.name === "enter") {
|
|
73
|
+
// Only the first finished line is carried forward as a question. A
|
|
74
|
+
// second Enter would mean two turns queued behind one reply, which is
|
|
75
|
+
// more than the user can be shown before it happens.
|
|
76
|
+
//
|
|
77
|
+
// Left alone rather than swallowed, though: the line behind a submitted
|
|
78
|
+
// question is the tail that prefills the next prompt, and it is drawn
|
|
79
|
+
// under a ◆ like any other, so Enter is the obvious way to finish it.
|
|
80
|
+
// Clearing it here destroyed the one thing that is meant to survive the
|
|
81
|
+
// turn its own Enter started.
|
|
82
|
+
if (submitted !== undefined) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const trimmed = line.trim();
|
|
86
|
+
if (trimmed.length > 0) {
|
|
87
|
+
submitted = trimmed;
|
|
88
|
+
}
|
|
89
|
+
line = "";
|
|
90
|
+
report();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (key.name === "backspace") {
|
|
94
|
+
if (line.length > 0) {
|
|
95
|
+
line = line.slice(0, -1);
|
|
96
|
+
report();
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (str === undefined || str.length === 0) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (key.ctrl === true || key.meta === true) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const code = str.codePointAt(0);
|
|
107
|
+
if (code === undefined || code < 32 || code === 127) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
line = `${line}${str}`;
|
|
111
|
+
report();
|
|
112
|
+
};
|
|
113
|
+
function report() {
|
|
114
|
+
if (input.onChange !== undefined) {
|
|
115
|
+
input.onChange(line);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function listen() {
|
|
119
|
+
if (listening === true || stopped === true) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (isTTYStream(stream) === true) {
|
|
123
|
+
stream.setRawMode(true);
|
|
124
|
+
}
|
|
125
|
+
// An interface closed by a prompt earlier in the turn leaves the stream
|
|
126
|
+
// paused, and a paused stream does not start flowing again just because a
|
|
127
|
+
// listener was added.
|
|
128
|
+
stream.resume();
|
|
129
|
+
readline.emitKeypressEvents(stream);
|
|
130
|
+
stream.on("keypress", onKeypress);
|
|
131
|
+
listening = true;
|
|
132
|
+
}
|
|
133
|
+
function unlisten() {
|
|
134
|
+
if (listening === false) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
stream.off("keypress", onKeypress);
|
|
138
|
+
if (isTTYStream(stream) === true) {
|
|
139
|
+
stream.setRawMode(wasRaw);
|
|
140
|
+
}
|
|
141
|
+
listening = false;
|
|
142
|
+
}
|
|
143
|
+
listen();
|
|
144
|
+
return {
|
|
145
|
+
pause: async () => {
|
|
146
|
+
// Keeps reading throughout, so the keystroke already on its way in when
|
|
147
|
+
// the prompt was decided on is swallowed here rather than queued in the
|
|
148
|
+
// terminal for whatever reads next.
|
|
149
|
+
discarding = true;
|
|
150
|
+
await new Promise((resolve) => {
|
|
151
|
+
setTimeout(resolve, APPROVAL_SETTLE_MS);
|
|
152
|
+
});
|
|
153
|
+
unlisten();
|
|
154
|
+
},
|
|
155
|
+
resume: () => {
|
|
156
|
+
discarding = false;
|
|
157
|
+
listen();
|
|
158
|
+
},
|
|
159
|
+
current: () => line,
|
|
160
|
+
stop: () => {
|
|
161
|
+
if (stopped === true) {
|
|
162
|
+
return { submitted, pending: line };
|
|
163
|
+
}
|
|
164
|
+
stopped = true;
|
|
165
|
+
unlisten();
|
|
166
|
+
return { submitted, pending: line };
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -62,7 +62,7 @@ export async function resolveWorkspace(opts) {
|
|
|
62
62
|
}
|
|
63
63
|
const promptInput = openPromptInput();
|
|
64
64
|
if (promptInput === undefined) {
|
|
65
|
-
failWith("Multiple workspaces available and no interactive terminal; pass --workspace-uuid <uuid> (or set CARGO_WORKSPACE_UUID
|
|
65
|
+
failWith("Multiple workspaces available and no interactive terminal; pass --workspace-uuid <uuid> (or set CARGO_WORKSPACE_UUID) to select one.", { code: ExitCodes.InvalidUsage });
|
|
66
66
|
}
|
|
67
67
|
let selectedWorkspace;
|
|
68
68
|
try {
|
|
@@ -24,9 +24,16 @@ type FailWithOpts = {
|
|
|
24
24
|
extra?: Record<string, unknown>;
|
|
25
25
|
};
|
|
26
26
|
export declare function failWith(message: string, opts?: FailWithOpts): never;
|
|
27
|
+
/**
|
|
28
|
+
* Cut a plain line down to one screen row, with an ellipsis where it was cut.
|
|
29
|
+
*
|
|
30
|
+
* Exported for tests.
|
|
31
|
+
*/
|
|
32
|
+
export declare function clipToWidth(text: string, columns?: number): string;
|
|
27
33
|
export type Spinner = {
|
|
28
34
|
update: (message: string) => void;
|
|
29
35
|
log: (line: string) => void;
|
|
36
|
+
footer: (text: string) => void;
|
|
30
37
|
stop: () => void;
|
|
31
38
|
};
|
|
32
39
|
export declare function startSpinner(message: string): Spinner;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runHandler.d.ts","sourceRoot":"","sources":["../../src/commands/runHandler.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS;;;;;;;CAOZ,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAclE,eAAO,MAAM,MAAM;aACR,MAAM,KAAG,MAAM;eACb,MAAM,KAAG,MAAM;gBACd,MAAM,KAAG,MAAM;cACjB,MAAM,KAAG,MAAM;aAChB,MAAM,KAAG,MAAM;cACd,MAAM,KAAG,MAAM;CAC1B,CAAC;AAEF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAM/C;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAID,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUhE;AAED,KAAK,YAAY,GAAG;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,KAAK,CAgCpE;
|
|
1
|
+
{"version":3,"file":"runHandler.d.ts","sourceRoot":"","sources":["../../src/commands/runHandler.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS;;;;;;;CAOZ,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAclE,eAAO,MAAM,MAAM;aACR,MAAM,KAAG,MAAM;eACb,MAAM,KAAG,MAAM;gBACd,MAAM,KAAG,MAAM;cACjB,MAAM,KAAG,MAAM;aAChB,MAAM,KAAG,MAAM;cACd,MAAM,KAAG,MAAM;CAC1B,CAAC;AAEF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAM/C;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAID,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUhE;AAED,KAAK,YAAY,GAAG;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,KAAK,CAgCpE;AAQD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAiBlE;AAED,MAAM,MAAM,OAAO,GAAG;IAEpB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAGlC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAU5B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAIF,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAwBrD;AAgED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,GAAG,CAQhE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAQ1E;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,OAAO,EAAE,CAAC,EAAE,EACZ,KAAK,EAAE,MAAM,GACZ;IAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CASxD;AAKD,KAAK,iBAAiB,GAAG;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACzB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,wBAAsB,aAAa,CAAC,CAAC,EACnC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,IAAI,CAAC,EAAE,iBAAiB,GACvB,OAAO,CAAC,CAAC,CAAC,CAgEZ;AA0ED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,aAAa,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EAC9D,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,CAAC,CAAC,CAyBZ"}
|
|
@@ -83,6 +83,27 @@ export function failWith(message, opts) {
|
|
|
83
83
|
}
|
|
84
84
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
85
85
|
const SPINNER_INTERVAL_MS = 80;
|
|
86
|
+
/** Assumed width when the terminal will not say — the conventional default. */
|
|
87
|
+
const FALLBACK_COLUMNS = 80;
|
|
88
|
+
/**
|
|
89
|
+
* Cut a plain line down to one screen row, with an ellipsis where it was cut.
|
|
90
|
+
*
|
|
91
|
+
* Exported for tests.
|
|
92
|
+
*/
|
|
93
|
+
export function clipToWidth(text, columns) {
|
|
94
|
+
const width = columns !== undefined
|
|
95
|
+
? columns
|
|
96
|
+
: typeof process.stderr.columns === "number" && process.stderr.columns > 0
|
|
97
|
+
? process.stderr.columns
|
|
98
|
+
: FALLBACK_COLUMNS;
|
|
99
|
+
// One column short of the edge: writing into the last cell makes some
|
|
100
|
+
// terminals wrap to the next row, which is the thing being avoided.
|
|
101
|
+
const limit = width - 1;
|
|
102
|
+
if (limit <= 1 || text.length <= limit) {
|
|
103
|
+
return text;
|
|
104
|
+
}
|
|
105
|
+
return `${text.slice(0, limit - 1)}…`;
|
|
106
|
+
}
|
|
86
107
|
let activeSpinner;
|
|
87
108
|
export function startSpinner(message) {
|
|
88
109
|
// Only one spinner can own the line, so retire any predecessor: otherwise its
|
|
@@ -94,6 +115,7 @@ export function startSpinner(message) {
|
|
|
94
115
|
const tracked = {
|
|
95
116
|
update: spinner.update,
|
|
96
117
|
log: spinner.log,
|
|
118
|
+
footer: spinner.footer,
|
|
97
119
|
stop: () => {
|
|
98
120
|
if (activeSpinner === tracked) {
|
|
99
121
|
activeSpinner = undefined;
|
|
@@ -112,17 +134,27 @@ function createSpinner(message) {
|
|
|
112
134
|
return {
|
|
113
135
|
update: (m) => process.stderr.write(`${m}\n`),
|
|
114
136
|
log: (line) => process.stderr.write(`${line}\n`),
|
|
137
|
+
// Nothing is being redrawn, so there is no line to keep in step.
|
|
138
|
+
footer: () => undefined,
|
|
115
139
|
stop: () => undefined,
|
|
116
140
|
};
|
|
117
141
|
}
|
|
118
142
|
process.stderr.write("\x1B[?25l");
|
|
119
143
|
let current = message;
|
|
120
144
|
let frame = 0;
|
|
145
|
+
let footerLine = "";
|
|
121
146
|
const render = () => {
|
|
122
147
|
const ch = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
|
|
123
148
|
if (ch !== undefined) {
|
|
124
|
-
// \x1B[
|
|
125
|
-
|
|
149
|
+
// \x1B[J clears the spinner line *and* any footer under it, so a footer
|
|
150
|
+
// that just shrank or went away leaves nothing behind.
|
|
151
|
+
process.stderr.write(`\r\x1B[J${colors.cyan(ch)} ${colors.dim(current)}`);
|
|
152
|
+
// Drawn below, then the cursor comes back up: the next frame's \r has to
|
|
153
|
+
// land on the spinner line, not the footer. Clipped first, because that
|
|
154
|
+
// \x1B[1A is only right while the footer fits on one row.
|
|
155
|
+
if (footerLine !== "") {
|
|
156
|
+
process.stderr.write(`\n${colors.dim(clipToWidth(footerLine))}\x1B[1A`);
|
|
157
|
+
}
|
|
126
158
|
}
|
|
127
159
|
frame += 1;
|
|
128
160
|
};
|
|
@@ -135,7 +167,10 @@ function createSpinner(message) {
|
|
|
135
167
|
log: (line) => {
|
|
136
168
|
// Wipe the spinner line, drop a permanent line, let the next frame redraw
|
|
137
169
|
// the spinner beneath it.
|
|
138
|
-
process.stderr.write(`\r\x1B[
|
|
170
|
+
process.stderr.write(`\r\x1B[J${line}\n`);
|
|
171
|
+
},
|
|
172
|
+
footer: (line) => {
|
|
173
|
+
footerLine = line;
|
|
139
174
|
},
|
|
140
175
|
stop: () => {
|
|
141
176
|
if (stopped) {
|
|
@@ -143,7 +178,7 @@ function createSpinner(message) {
|
|
|
143
178
|
}
|
|
144
179
|
stopped = true;
|
|
145
180
|
clearInterval(id);
|
|
146
|
-
process.stderr.write("\r\x1B[
|
|
181
|
+
process.stderr.write("\r\x1B[J\x1B[?25h");
|
|
147
182
|
},
|
|
148
183
|
};
|
|
149
184
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envVar.d.ts","sourceRoot":"","sources":["../../../src/commands/workspaceManagement/envVar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAGxC,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAmFN"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { handleApiCall, outputJson } from "../runHandler.js";
|
|
2
|
+
export function registerEnvVarCommands(parent, getApi) {
|
|
3
|
+
const envVar = parent
|
|
4
|
+
.command("envVar")
|
|
5
|
+
.description("Workspace environment variables, injected into every worker, app and agent");
|
|
6
|
+
envVar
|
|
7
|
+
.command("list")
|
|
8
|
+
.description("List all workspace environment variables")
|
|
9
|
+
.action(async () => {
|
|
10
|
+
const api = getApi();
|
|
11
|
+
const result = await handleApiCall(() => api.workspaceManagement.envVar.all());
|
|
12
|
+
outputJson(result);
|
|
13
|
+
});
|
|
14
|
+
envVar
|
|
15
|
+
.command("create")
|
|
16
|
+
.description("Create a workspace environment variable")
|
|
17
|
+
.requiredOption("--key <key>", "Environment variable name")
|
|
18
|
+
.option("--value <value>", "Value. Omit to read it from the environment variable of the same name")
|
|
19
|
+
.option("--secret", "Encrypt the value at rest and never return it")
|
|
20
|
+
.option("--description <description>", "What this variable is for")
|
|
21
|
+
.action(async (opts) => {
|
|
22
|
+
const api = getApi();
|
|
23
|
+
const value = resolveValue(opts.key, opts.value);
|
|
24
|
+
const result = await handleApiCall(() => api.workspaceManagement.envVar.create({
|
|
25
|
+
key: opts.key,
|
|
26
|
+
value,
|
|
27
|
+
isSecret: opts.secret === true,
|
|
28
|
+
description: opts.description === undefined ? null : opts.description,
|
|
29
|
+
}));
|
|
30
|
+
outputJson(result);
|
|
31
|
+
});
|
|
32
|
+
envVar
|
|
33
|
+
.command("update <uuid>")
|
|
34
|
+
.description("Update a workspace environment variable")
|
|
35
|
+
.option("--value <value>", "New value. Omit to keep the stored one")
|
|
36
|
+
.option("--secret", "Encrypt the value at rest and never return it")
|
|
37
|
+
.option("--no-secret", "Store the value in plain text")
|
|
38
|
+
.option("--description <description>", "What this variable is for")
|
|
39
|
+
.action(async (uuid, opts) => {
|
|
40
|
+
const api = getApi();
|
|
41
|
+
const result = await handleApiCall(() => api.workspaceManagement.envVar.update({
|
|
42
|
+
uuid,
|
|
43
|
+
value: opts.value,
|
|
44
|
+
isSecret: opts.secret,
|
|
45
|
+
description: opts.description,
|
|
46
|
+
}));
|
|
47
|
+
outputJson(result);
|
|
48
|
+
});
|
|
49
|
+
envVar
|
|
50
|
+
.command("remove <uuid>")
|
|
51
|
+
.description("Remove a workspace environment variable")
|
|
52
|
+
.action(async (uuid) => {
|
|
53
|
+
const api = getApi();
|
|
54
|
+
await handleApiCall(() => api.workspaceManagement.envVar.remove(uuid));
|
|
55
|
+
outputJson({ ok: true });
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Falling back to the local environment keeps a secret out of argv (and out of
|
|
60
|
+
* the shell history), which is the whole point of `envVar create --secret`.
|
|
61
|
+
*/
|
|
62
|
+
const resolveValue = (key, value) => {
|
|
63
|
+
if (value !== undefined) {
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
const fromEnvironment = process.env[key];
|
|
67
|
+
if (fromEnvironment === undefined) {
|
|
68
|
+
throw new Error(`no value given — pass --value, or export ${key} before running this command.`);
|
|
69
|
+
}
|
|
70
|
+
return fromEnvironment;
|
|
71
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/workspaceManagement/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/workspaceManagement/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAWxC,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAgBN"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { registerEnvVarCommands } from "./envVar.js";
|
|
1
2
|
import { registerFileCommands } from "./file.js";
|
|
2
3
|
import { registerFolderCommands } from "./folder.js";
|
|
3
4
|
import { registerReportCommands } from "./report.js";
|
|
@@ -9,10 +10,11 @@ import { registerWorkspacesCommands } from "./workspaces.js";
|
|
|
9
10
|
export function registerWorkspaceManagementCommands(parent, getApi) {
|
|
10
11
|
const workspaceManagement = parent
|
|
11
12
|
.command("workspaceManagement")
|
|
12
|
-
.description("Workspaces, users, tokens, roles, folders, reports, sessions");
|
|
13
|
+
.description("Workspaces, users, tokens, environment variables, roles, folders, reports, sessions");
|
|
13
14
|
registerWorkspacesCommands(workspaceManagement, getApi);
|
|
14
15
|
registerUserCommands(workspaceManagement, getApi);
|
|
15
16
|
registerTokenCommands(workspaceManagement, getApi);
|
|
17
|
+
registerEnvVarCommands(workspaceManagement, getApi);
|
|
16
18
|
registerRoleCommands(workspaceManagement, getApi);
|
|
17
19
|
registerFolderCommands(workspaceManagement, getApi);
|
|
18
20
|
registerFileCommands(workspaceManagement, getApi);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cargo-ai/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.89",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://getcargo.ai",
|
|
@@ -55,8 +55,9 @@
|
|
|
55
55
|
"build": "tsc --build",
|
|
56
56
|
"dev": "tsx watch src/index.ts",
|
|
57
57
|
"start": "node build/index.js",
|
|
58
|
-
"test": "npm run build && node --test \"test
|
|
59
|
-
"test:watch": "node --test --watch \"test
|
|
58
|
+
"test": "npm run build && node --test \"test/*.test.mjs\"",
|
|
59
|
+
"test:watch": "node --test --watch \"test/*.test.mjs\"",
|
|
60
|
+
"test:e2e": "npm run build && node --test --test-concurrency=1 \"test/e2e/*.e2e.mjs\"",
|
|
60
61
|
"type:check": "tsc --noEmit",
|
|
61
62
|
"lint": "eslint ./ --fix",
|
|
62
63
|
"lint:check": "eslint ./ --max-warnings=0",
|
|
@@ -66,10 +67,10 @@
|
|
|
66
67
|
"dependencies": {
|
|
67
68
|
"@ai-sdk/harness": "^1.0.95",
|
|
68
69
|
"@ai-sdk/harness-claude-code": "^1.0.99",
|
|
69
|
-
"@cargo-ai/api": "^1.0.
|
|
70
|
+
"@cargo-ai/api": "^1.0.77",
|
|
70
71
|
"@cargo-ai/app-sdk": "^1.0.10",
|
|
71
|
-
"@cargo-ai/cdk": "^1.0.
|
|
72
|
-
"@cargo-ai/types": "^1.0.
|
|
72
|
+
"@cargo-ai/cdk": "^1.0.72",
|
|
73
|
+
"@cargo-ai/types": "^1.0.74",
|
|
73
74
|
"@cargo-ai/utils": "^1.0.28",
|
|
74
75
|
"@cargo-ai/worker-sdk": "^1.0.17",
|
|
75
76
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
@@ -85,6 +86,7 @@
|
|
|
85
86
|
"@cargo-ai/ts-config": "*",
|
|
86
87
|
"@types/node": "^20.10.8",
|
|
87
88
|
"eslint": "9.26.0",
|
|
89
|
+
"node-pty": "^1.0.0",
|
|
88
90
|
"prettier": "3.3.3",
|
|
89
91
|
"typescript": "5.3.2"
|
|
90
92
|
}
|