@cargo-ai/cli 1.0.55 → 1.0.57
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/auth/index.d.ts.map +1 -1
- package/build/commands/auth/index.js +25 -5
- package/build/commands/orchestration/asciiBlocks.d.ts +42 -0
- package/build/commands/orchestration/asciiBlocks.d.ts.map +1 -0
- package/build/commands/orchestration/asciiBlocks.js +170 -0
- package/build/commands/orchestration/node.d.ts.map +1 -1
- package/build/commands/orchestration/node.js +131 -18
- package/build/commands/orchestration/nodeDiagram.d.ts +42 -0
- package/build/commands/orchestration/nodeDiagram.d.ts.map +1 -1
- package/build/commands/orchestration/nodeDiagram.js +123 -40
- package/build/commands/orchestration/nodeDiagramAscii.d.ts +32 -0
- package/build/commands/orchestration/nodeDiagramAscii.d.ts.map +1 -0
- package/build/commands/orchestration/nodeDiagramAscii.js +528 -0
- package/build/credentials.d.ts +1 -1
- package/build/credentials.d.ts.map +1 -1
- package/build/credentials.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ cargo-ai login --token <your-api-token>
|
|
|
42
42
|
|
|
43
43
|
`--email` and `--oauth` save the signed-in session — a short-lived access token plus the refresh token that renews it — to `~/.config/cargo-ai/credentials.json` (file mode `0600`). The CLI renews it in the background as it expires, so you stay signed in and no API token is created per machine. `--token` saves the API token you supplied instead. The same file backs `cargo-cdk`, so one login serves both.
|
|
44
44
|
|
|
45
|
-
`cargo-ai logout` revokes the session at the identity provider, so signing out is final. A token you supplied yourself with `--token` is only forgotten locally, since it may be in use elsewhere; pass `--keep-token` to force that behaviour for a session too.
|
|
45
|
+
`cargo-ai logout` revokes the session at the identity provider, so signing out is final. A token you supplied yourself with `--token` is only forgotten locally, since it may be in use elsewhere; pass `--keep-token` to force that behaviour for a session too. If a credentials file cannot be deleted — a read-only config directory, or one owned by another user — `logout` fails and names the file rather than reporting a sign-out the next command would contradict.
|
|
46
46
|
|
|
47
47
|
Treat the file as you would a password. A saved session acts as you across **every** workspace you belong to — unlike an API token, which is limited to one workspace and to the permissions it was created with — and it stays valid until you run `cargo-ai logout` or the identity provider expires it. On a shared or long-lived machine (CI, a build box), prefer an API token created under **Settings → API** and passed via `CARGO_API_TOKEN`, which you can scope down and revoke from the dashboard.
|
|
48
48
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,WAAW,CAAC;AAGjD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,WAAW,CAAC;AAGjD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAyBxD,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAyGN;AAiGD,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,IAAI,EAAE,aAAa,GAClB,cAAc,CAchB"}
|
|
@@ -2,7 +2,7 @@ import { revokeSession } from "@cargo-ai/cdk/cli";
|
|
|
2
2
|
import { Option } from "commander";
|
|
3
3
|
import { AGENT_SKILLS_NEXT_STEP } from "../../agentSkills.js";
|
|
4
4
|
import { getConfig } from "../../config.js";
|
|
5
|
-
import { clearCredentials, getCredentialsPath, loadCredentials, saveCredentials, } from "../../credentials.js";
|
|
5
|
+
import { clearCredentials, CredentialsNotClearedError, getCredentialsPath, loadCredentials, saveCredentials, } from "../../credentials.js";
|
|
6
6
|
import { ExitCodes, failWith, handleApiCall, info, outputJson, success, } from "../runHandler.js";
|
|
7
7
|
import { runEmailLogin } from "./email.js";
|
|
8
8
|
import { selectLoginChannel } from "./loginChannel.js";
|
|
@@ -40,8 +40,11 @@ that name when one exists, so this is safe to re-run.
|
|
|
40
40
|
|
|
41
41
|
--email and --oauth save the signed-in session to
|
|
42
42
|
~/.config/cargo-ai/credentials.json and renew it automatically, so you stay
|
|
43
|
-
signed in without a token being created for each machine.
|
|
44
|
-
|
|
43
|
+
signed in without a token being created for each machine. CARGO_CONFIG_DIR
|
|
44
|
+
(or XDG_CONFIG_HOME) moves that file; where the home directory is not writable
|
|
45
|
+
— agent sandboxes usually run that way — the session is saved under the system
|
|
46
|
+
temp directory instead, which lasts only as long as the sandbox. Switch
|
|
47
|
+
workspace at any time with CARGO_WORKSPACE_UUID or by signing in again with
|
|
45
48
|
--workspace-uuid <uuid>. An API token passed with --token stays pinned to its
|
|
46
49
|
own workspace, so the override does not apply to it.
|
|
47
50
|
|
|
@@ -59,7 +62,11 @@ Environment variables (CARGO_API_TOKEN, CARGO_WORKSPACE_UUID, CARGO_BASE_URL) ta
|
|
|
59
62
|
.addHelpText("after", `
|
|
60
63
|
A session from "cargo-ai login --email" or "--oauth" is revoked at the identity
|
|
61
64
|
provider, so signing out is final. Tokens you supplied yourself with --token are
|
|
62
|
-
only forgotten locally, since they may be in use elsewhere
|
|
65
|
+
only forgotten locally, since they may be in use elsewhere.
|
|
66
|
+
|
|
67
|
+
Signing out fails, naming the file, when a credentials file cannot be deleted —
|
|
68
|
+
a read-only config directory, or one owned by another user — because the next
|
|
69
|
+
command would otherwise read it and still be signed in.`)
|
|
63
70
|
.action(async (opts) => {
|
|
64
71
|
await runLogout(opts);
|
|
65
72
|
});
|
|
@@ -123,7 +130,20 @@ async function runLogout(opts) {
|
|
|
123
130
|
const credentials = loadCredentials();
|
|
124
131
|
const plan = planRevocation(credentials, opts);
|
|
125
132
|
const revoked = await applyRevocation(plan, credentials);
|
|
126
|
-
|
|
133
|
+
let removed;
|
|
134
|
+
try {
|
|
135
|
+
removed = clearCredentials();
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
if (error instanceof CredentialsNotClearedError) {
|
|
139
|
+
// Revocation already happened, so say so: re-running logout will not
|
|
140
|
+
// revoke a second time, and the file is all that is left to deal with.
|
|
141
|
+
failWith(error.message, {
|
|
142
|
+
extra: { revoked, credentialsFiles: error.paths },
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
127
147
|
if (removed === true) {
|
|
128
148
|
success(revoked === true ? "Signed out and revoked access." : "Signed out.");
|
|
129
149
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export declare function displayWidth(text: string): number;
|
|
2
|
+
/**
|
|
3
|
+
* A rectangle of text plus the column its spine sits on. `spine` is where an
|
|
4
|
+
* incoming `│` attaches at the top and where an outgoing one leaves at the
|
|
5
|
+
* bottom, so stacking is a matter of lining up that column.
|
|
6
|
+
*/
|
|
7
|
+
export type Block = {
|
|
8
|
+
lines: string[];
|
|
9
|
+
width: number;
|
|
10
|
+
spine: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function padTo(line: string, width: number): string;
|
|
13
|
+
export declare function indent(block: Block, by: number): Block;
|
|
14
|
+
/** Text lines centred on a shared spine. No lines is a legitimate answer — an
|
|
15
|
+
* unnamed routing node draws as its split and nothing else. */
|
|
16
|
+
export declare function textBlock(texts: string[]): Block;
|
|
17
|
+
/** One row carrying nothing but the spine. */
|
|
18
|
+
export declare function spineBlock(char?: string): Block;
|
|
19
|
+
export declare function emptyBlock(): Block;
|
|
20
|
+
/** Stack blocks vertically, aligning their spines. */
|
|
21
|
+
export declare function vstack(blocks: Block[]): Block;
|
|
22
|
+
/** Place blocks side by side, returning where each one's spine landed. */
|
|
23
|
+
export declare function hstack(blocks: Block[], gap: number): {
|
|
24
|
+
block: Block;
|
|
25
|
+
spines: number[];
|
|
26
|
+
};
|
|
27
|
+
/** Extend a column downwards so every column in a split ends level. */
|
|
28
|
+
export declare function padColumn(block: Block, toHeight: number, char: string): Block;
|
|
29
|
+
/**
|
|
30
|
+
* The glyph where a rail meets the spine. A tee has to point at the rails it
|
|
31
|
+
* actually carries: drawing `├` while the rail leaves to the left produces a
|
|
32
|
+
* junction with an arm attached to nothing and a rail attached to nothing.
|
|
33
|
+
*/
|
|
34
|
+
export declare function teeAt(spine: number, others: number[], down: boolean): string;
|
|
35
|
+
/** Draw a horizontal rail across `width`, writing one glyph per marked column. */
|
|
36
|
+
export declare function rail(width: number, marks: {
|
|
37
|
+
at: number;
|
|
38
|
+
char: string;
|
|
39
|
+
}[]): string;
|
|
40
|
+
/** Wrap a block in a captioned box, for a sub-flow that runs inside a step. */
|
|
41
|
+
export declare function frame(block: Block, caption: string): Block;
|
|
42
|
+
//# sourceMappingURL=asciiBlocks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asciiBlocks.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/asciiBlocks.ts"],"names":[],"mappings":"AAyBA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKjD;AAMD;;;;GAIG;AACH,MAAM,MAAM,KAAK,GAAG;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAIF,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,GAAG,KAAK,CAQtD;AAED;+DAC+D;AAC/D,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAShD;AAED,8CAA8C;AAC9C,wBAAgB,UAAU,CAAC,IAAI,SAAM,GAAG,KAAK,CAE5C;AAED,wBAAgB,UAAU,IAAI,KAAK,CAElC;AAED,sDAAsD;AACtD,wBAAgB,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAsB7C;AAED,0EAA0E;AAC1E,wBAAgB,MAAM,CACpB,MAAM,EAAE,KAAK,EAAE,EACf,GAAG,EAAE,MAAM,GACV;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CA6BpC;AAED,uEAAuE;AACvE,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAQ7E;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAO5E;AAED,kFAAkF;AAClF,wBAAgB,IAAI,CAClB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,GACpC,MAAM,CAWR;AAED,+EAA+E;AAC/E,wBAAgB,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAU1D"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// Text blocks: the layout algebra the ASCII diagram is assembled from.
|
|
2
|
+
//
|
|
3
|
+
// A Block is a rectangle of text plus the column its spine sits on — the column
|
|
4
|
+
// an incoming `│` attaches to at the top and an outgoing one leaves from at the
|
|
5
|
+
// bottom. Stacking is then a matter of lining that column up, which is what lets
|
|
6
|
+
// the renderer compose a drawing without ever tracking absolute coordinates.
|
|
7
|
+
//
|
|
8
|
+
// Nothing here knows what a workflow is. The invariant every producer maintains
|
|
9
|
+
// is that each line of a Block is exactly `block.width` cells wide, which is why
|
|
10
|
+
// so little of this has to measure anything.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Character width
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// A marker glyph occupies two terminal cells; measuring it as one silently
|
|
15
|
+
// shears every line to its right. `Emoji_Presentation` is the property that
|
|
16
|
+
// means "wide by default" — `Extended_Pictographic` is too broad, matching `◀`,
|
|
17
|
+
// which a terminal draws in a single cell.
|
|
18
|
+
const WIDE = /\p{Emoji_Presentation}/u;
|
|
19
|
+
// Most lines are plain text; the per-character scan is only needed once a line
|
|
20
|
+
// actually carries a marker. One test for the whole string skips it.
|
|
21
|
+
const PLAIN = /^[\x20-\x7E]*$/;
|
|
22
|
+
export function displayWidth(text) {
|
|
23
|
+
if (PLAIN.test(text))
|
|
24
|
+
return text.length;
|
|
25
|
+
let total = 0;
|
|
26
|
+
for (const char of text)
|
|
27
|
+
total += WIDE.test(char) ? 2 : 1;
|
|
28
|
+
return total;
|
|
29
|
+
}
|
|
30
|
+
const blank = (width) => (width > 0 ? " ".repeat(width) : "");
|
|
31
|
+
export function padTo(line, width) {
|
|
32
|
+
const short = width - displayWidth(line);
|
|
33
|
+
return short > 0 ? line + " ".repeat(short) : line;
|
|
34
|
+
}
|
|
35
|
+
export function indent(block, by) {
|
|
36
|
+
if (by <= 0)
|
|
37
|
+
return block;
|
|
38
|
+
const prefix = " ".repeat(by);
|
|
39
|
+
return {
|
|
40
|
+
lines: block.lines.map((line) => prefix + line),
|
|
41
|
+
width: block.width + by,
|
|
42
|
+
spine: block.spine + by,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Text lines centred on a shared spine. No lines is a legitimate answer — an
|
|
46
|
+
* unnamed routing node draws as its split and nothing else. */
|
|
47
|
+
export function textBlock(texts) {
|
|
48
|
+
if (texts.length === 0)
|
|
49
|
+
return emptyBlock();
|
|
50
|
+
const measured = texts.map((text) => ({ text, own: displayWidth(text) }));
|
|
51
|
+
const width = Math.max(...measured.map((entry) => entry.own));
|
|
52
|
+
const lines = measured.map(({ text, own }) => {
|
|
53
|
+
const left = Math.floor((width - own) / 2);
|
|
54
|
+
return " ".repeat(left) + text + blank(width - own - left);
|
|
55
|
+
});
|
|
56
|
+
return { lines, width, spine: Math.floor((width - 1) / 2) };
|
|
57
|
+
}
|
|
58
|
+
/** One row carrying nothing but the spine. */
|
|
59
|
+
export function spineBlock(char = "│") {
|
|
60
|
+
return { lines: [char], width: 1, spine: 0 };
|
|
61
|
+
}
|
|
62
|
+
export function emptyBlock() {
|
|
63
|
+
return { lines: [], width: 0, spine: 0 };
|
|
64
|
+
}
|
|
65
|
+
/** Stack blocks vertically, aligning their spines. */
|
|
66
|
+
export function vstack(blocks) {
|
|
67
|
+
const real = blocks.filter((block) => block.lines.length > 0);
|
|
68
|
+
if (real.length === 0)
|
|
69
|
+
return emptyBlock();
|
|
70
|
+
if (real.length === 1) {
|
|
71
|
+
const only = real[0];
|
|
72
|
+
if (only === undefined)
|
|
73
|
+
return emptyBlock();
|
|
74
|
+
return only;
|
|
75
|
+
}
|
|
76
|
+
const spine = Math.max(...real.map((block) => block.spine));
|
|
77
|
+
const shifted = real.map((block) => indent(block, spine - block.spine));
|
|
78
|
+
const width = Math.max(...shifted.map((block) => block.width));
|
|
79
|
+
return {
|
|
80
|
+
lines: shifted.flatMap((block) => {
|
|
81
|
+
const short = blank(width - block.width);
|
|
82
|
+
return short.length === 0
|
|
83
|
+
? block.lines
|
|
84
|
+
: block.lines.map((line) => line + short);
|
|
85
|
+
}),
|
|
86
|
+
width,
|
|
87
|
+
spine,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Place blocks side by side, returning where each one's spine landed. */
|
|
91
|
+
export function hstack(blocks, gap) {
|
|
92
|
+
const height = Math.max(...blocks.map((block) => block.lines.length));
|
|
93
|
+
const spines = [];
|
|
94
|
+
let offset = 0;
|
|
95
|
+
const rows = Array.from({ length: height }, () => "");
|
|
96
|
+
blocks.forEach((block, index) => {
|
|
97
|
+
if (index > 0)
|
|
98
|
+
offset += gap;
|
|
99
|
+
spines.push(offset + block.spine);
|
|
100
|
+
for (let row = 0; row < height; row += 1) {
|
|
101
|
+
// Every Block keeps `displayWidth(line) === block.width` on every line, so
|
|
102
|
+
// the line needs no measuring here — only the canvas does.
|
|
103
|
+
const canvas = rows[row];
|
|
104
|
+
const line = block.lines[row];
|
|
105
|
+
rows[row] =
|
|
106
|
+
padTo(canvas === undefined ? "" : canvas, offset) +
|
|
107
|
+
(line === undefined ? blank(block.width) : line);
|
|
108
|
+
}
|
|
109
|
+
offset += block.width;
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
block: {
|
|
113
|
+
lines: rows.map((row) => padTo(row, offset)),
|
|
114
|
+
width: offset,
|
|
115
|
+
spine: 0,
|
|
116
|
+
},
|
|
117
|
+
spines,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Extend a column downwards so every column in a split ends level. */
|
|
121
|
+
export function padColumn(block, toHeight, char) {
|
|
122
|
+
const missing = toHeight - block.lines.length;
|
|
123
|
+
if (missing <= 0)
|
|
124
|
+
return block;
|
|
125
|
+
const filler = padTo(" ".repeat(block.spine) + char, block.width);
|
|
126
|
+
return {
|
|
127
|
+
...block,
|
|
128
|
+
lines: [...block.lines, ...Array.from({ length: missing }, () => filler)],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The glyph where a rail meets the spine. A tee has to point at the rails it
|
|
133
|
+
* actually carries: drawing `├` while the rail leaves to the left produces a
|
|
134
|
+
* junction with an arm attached to nothing and a rail attached to nothing.
|
|
135
|
+
*/
|
|
136
|
+
export function teeAt(spine, others, down) {
|
|
137
|
+
const left = others.some((at) => at < spine);
|
|
138
|
+
const right = others.some((at) => at > spine);
|
|
139
|
+
if (left && right)
|
|
140
|
+
return "┼";
|
|
141
|
+
if (left)
|
|
142
|
+
return "┤";
|
|
143
|
+
if (right)
|
|
144
|
+
return "├";
|
|
145
|
+
return down ? "┬" : "┴";
|
|
146
|
+
}
|
|
147
|
+
/** Draw a horizontal rail across `width`, writing one glyph per marked column. */
|
|
148
|
+
export function rail(width, marks) {
|
|
149
|
+
const sorted = [...marks].sort((a, b) => a.at - b.at);
|
|
150
|
+
const first = sorted[0];
|
|
151
|
+
const last = sorted[sorted.length - 1];
|
|
152
|
+
if (first === undefined || last === undefined)
|
|
153
|
+
return " ".repeat(width);
|
|
154
|
+
const cells = Array.from({ length: width }, (_, column) => column > first.at && column < last.at ? "─" : " ");
|
|
155
|
+
for (const mark of sorted)
|
|
156
|
+
cells[mark.at] = mark.char;
|
|
157
|
+
return cells.join("");
|
|
158
|
+
}
|
|
159
|
+
/** Wrap a block in a captioned box, for a sub-flow that runs inside a step. */
|
|
160
|
+
export function frame(block, caption) {
|
|
161
|
+
const inner = Math.max(block.width, displayWidth(caption) + 2);
|
|
162
|
+
const width = inner + 4;
|
|
163
|
+
const label = ` ${caption} `;
|
|
164
|
+
const lines = [
|
|
165
|
+
`┌─${label}${"─".repeat(Math.max(0, width - 3 - displayWidth(label)))}┐`,
|
|
166
|
+
...block.lines.map((line) => `│ ${padTo(line, inner)} │`),
|
|
167
|
+
`└${"─".repeat(width - 2)}┘`,
|
|
168
|
+
];
|
|
169
|
+
return { lines, width, spine: block.spine + 2 };
|
|
170
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/node.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/node.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAgBxC,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,CAoK7E"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { ExitCodes, failWith, handleApiCall, outputJson, parseJson, } from "../runHandler.js";
|
|
3
3
|
import { extractNodes, renderNodeDiagram, toMermaidBlock, } from "./nodeDiagram.js";
|
|
4
|
+
import { renderNodeAscii } from "./nodeDiagramAscii.js";
|
|
4
5
|
export function registerNodeCommands(parent, getApi) {
|
|
5
6
|
const node = parent
|
|
6
7
|
.command("node")
|
|
@@ -105,7 +106,7 @@ Examples:
|
|
|
105
106
|
function registerDiagramCommand(node, getApi) {
|
|
106
107
|
node
|
|
107
108
|
.command("diagram")
|
|
108
|
-
.description("Draw a node graph
|
|
109
|
+
.description("Draw a node graph — ASCII for a terminal, Mermaid to paste elsewhere (free, runs nothing, no credits)")
|
|
109
110
|
.option("--nodes <json>", "Node definitions (JSON array, or '-' to read from stdin)")
|
|
110
111
|
.option("--file <path>", "Read a JSON payload or node array from a file")
|
|
111
112
|
.option("--workflow-uuid <uuid>", "Diagram a workflow's deployed release (add --draft for its draft)")
|
|
@@ -113,37 +114,74 @@ function registerDiagramCommand(node, getApi) {
|
|
|
113
114
|
.option("--release-uuid <uuid>", "Diagram a specific release")
|
|
114
115
|
.option("--run-uuid <uuid>", "Diagram the graph a run executed (follows the run's release when it has one)")
|
|
115
116
|
.option("--title <text>", "Title rendered above the diagram")
|
|
116
|
-
.option("--
|
|
117
|
+
.option("--format <ascii|mermaid>", "ascii = readable in a terminal · mermaid = paste into a PR, docs or a page (default: mermaid)", "mermaid")
|
|
118
|
+
.option("--direction <TD|LR>", "Mermaid flow direction, ignored by --format ascii (default: TD)", "TD")
|
|
117
119
|
.option("--paid <slugs>", "Comma-separated node slugs/uuids that bill credits — marked 💳")
|
|
118
|
-
.option("--highlight <slugs>", "Comma-separated node slugs/uuids to mark red,
|
|
119
|
-
.option("--raw", "Print the
|
|
120
|
+
.option("--highlight <slugs>", "Comma-separated node slugs/uuids to mark (red in Mermaid, ← in ASCII)")
|
|
121
|
+
.option("--raw", "Print the diagram itself instead of JSON")
|
|
120
122
|
.addHelpText("after", `
|
|
121
123
|
Free, runs nothing: turns a node graph into a picture so a reviewer can see the
|
|
122
124
|
routing, the fallback paths and which steps bill before approving or deploying
|
|
123
125
|
it. Pass exactly one source.
|
|
124
126
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
Which format:
|
|
128
|
+
--format ascii SHOWING a graph to someone at a terminal. Renders as a
|
|
129
|
+
centred flow with named steps and labelled branches, so it
|
|
130
|
+
is readable as-is. Use this whenever you are about to
|
|
131
|
+
explain what a workflow, tool or play does.
|
|
132
|
+
--format ascii resolves step names from the integration catalog, so a step
|
|
133
|
+
reads 'Apollo.io / Enrich person'. --nodes and --file skip that lookup and
|
|
134
|
+
keep the slugs, so they work with no credential at all.
|
|
135
|
+
|
|
136
|
+
--format mermaid PASTING the graph somewhere that renders it — a PR body, a
|
|
137
|
+
markdown doc, a published page. In a terminal it is source
|
|
138
|
+
code, not a picture. This is the default, for
|
|
139
|
+
compatibility.
|
|
140
|
+
|
|
141
|
+
Returns {"diagram":"…","format":"ascii"|"mermaid","warnings":[…]}, or the
|
|
142
|
+
diagram itself with --raw (fenced for mermaid, plain text for ascii).
|
|
143
|
+
'warnings' reports structural problems worth saying out loud — nodes
|
|
144
|
+
unreachable from start, dangling childrenUuids.
|
|
128
145
|
|
|
129
146
|
A run created by 'action execute' carries its own graph; a run of a deployed
|
|
130
147
|
tool or play carries only a releaseUuid, and --run-uuid follows it either way.
|
|
131
148
|
|
|
132
149
|
Examples:
|
|
133
|
-
$ cargo-ai orchestration node diagram --workflow-uuid <uuid> --raw
|
|
134
|
-
$ cargo-ai orchestration node diagram --workflow-uuid <uuid> --draft --paid enrich,verify
|
|
135
|
-
$ cargo-ai orchestration node diagram --run-uuid <uuid> --highlight branch_1 --raw
|
|
136
|
-
$ cargo-ai orchestration
|
|
150
|
+
$ cargo-ai orchestration node diagram --workflow-uuid <uuid> --format ascii --raw
|
|
151
|
+
$ cargo-ai orchestration node diagram --workflow-uuid <uuid> --draft --paid enrich,verify --format ascii --raw
|
|
152
|
+
$ cargo-ai orchestration node diagram --run-uuid <uuid> --highlight branch_1 --format ascii --raw
|
|
153
|
+
$ cargo-ai orchestration node diagram --workflow-uuid <uuid> --raw # mermaid, for a PR
|
|
154
|
+
$ cargo-ai orchestration release get-deployed --workflow-uuid <uuid> | cargo-ai orchestration node diagram --nodes - --format ascii --raw`)
|
|
137
155
|
.action(async (opts) => {
|
|
156
|
+
const format = (opts.format === undefined ? "mermaid" : opts.format).toLowerCase();
|
|
157
|
+
if (format !== "ascii" && format !== "mermaid") {
|
|
158
|
+
failWith(`--format must be 'ascii' or 'mermaid', got '${format}'`, {
|
|
159
|
+
code: ExitCodes.InvalidUsage,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
138
162
|
const nodes = await resolveNodes(getApi, opts);
|
|
139
|
-
const result =
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
163
|
+
const result = format === "ascii"
|
|
164
|
+
? renderNodeAscii(nodes, {
|
|
165
|
+
title: opts.title,
|
|
166
|
+
paid: splitList(opts.paid),
|
|
167
|
+
highlight: splitList(opts.highlight),
|
|
168
|
+
// `--nodes` / `--file` are the offline sources: resolving them
|
|
169
|
+
// touched no API, and reaching for one now to prettify a label
|
|
170
|
+
// would make an offline command demand a login.
|
|
171
|
+
names: opts.nodes === undefined && opts.file === undefined
|
|
172
|
+
? await catalogNames(getApi, nodes)
|
|
173
|
+
: undefined,
|
|
174
|
+
})
|
|
175
|
+
: renderNodeDiagram(nodes, {
|
|
176
|
+
title: opts.title,
|
|
177
|
+
direction: (opts.direction === undefined
|
|
178
|
+
? "TD"
|
|
179
|
+
: opts.direction).toUpperCase(),
|
|
180
|
+
paid: splitList(opts.paid),
|
|
181
|
+
highlight: splitList(opts.highlight),
|
|
182
|
+
});
|
|
145
183
|
if (opts.raw === true) {
|
|
146
|
-
console.log(toMermaidBlock(result.diagram));
|
|
184
|
+
console.log(format === "ascii" ? result.diagram : toMermaidBlock(result.diagram));
|
|
147
185
|
for (const warning of result.warnings)
|
|
148
186
|
console.error(`⚠ ${warning}`);
|
|
149
187
|
return;
|
|
@@ -151,6 +189,81 @@ Examples:
|
|
|
151
189
|
outputJson(result);
|
|
152
190
|
});
|
|
153
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Display names for the steps a graph actually uses, so it reads
|
|
194
|
+
* `Apollo.io` / `Enrich person` rather than `apolloio` / `enrichPerson`.
|
|
195
|
+
*
|
|
196
|
+
* Both halves come from the platform's own catalogs — the integration catalog
|
|
197
|
+
* for connector steps, the native-integration catalog for built-in ones — so a
|
|
198
|
+
* step is never labelled with a name this CLI invented. Renaming an action on
|
|
199
|
+
* the platform reaches the drawing without a code change.
|
|
200
|
+
*
|
|
201
|
+
* Best-effort, and only for graphs that came from the API in the first place.
|
|
202
|
+
* `--nodes` and `--file` skip it entirely: those resolve without a credential,
|
|
203
|
+
* and `getApi()` exits the process when there is none, so asking for names
|
|
204
|
+
* there would turn an offline command into one that demands a login. A catalog
|
|
205
|
+
* that is reachable but fails — a token without the scope, a network blip —
|
|
206
|
+
* falls back to slugs rather than losing the drawing.
|
|
207
|
+
*/
|
|
208
|
+
async function catalogNames(getApi, nodes) {
|
|
209
|
+
const used = new Set();
|
|
210
|
+
let hasNative = false;
|
|
211
|
+
// Group bodies live in `config._nodes` and are drawn too, so their steps need
|
|
212
|
+
// names as much as the outer ones — otherwise a loop shows wire slugs beside
|
|
213
|
+
// an outer graph showing product names.
|
|
214
|
+
const collect = (list) => {
|
|
215
|
+
for (const node of list) {
|
|
216
|
+
if (node.kind === "connector" &&
|
|
217
|
+
typeof node.integrationSlug === "string") {
|
|
218
|
+
used.add(node.integrationSlug);
|
|
219
|
+
}
|
|
220
|
+
if (node.kind === "native")
|
|
221
|
+
hasNative = true;
|
|
222
|
+
const config = node.config;
|
|
223
|
+
if (config !== null && config !== undefined) {
|
|
224
|
+
const nested = config["_nodes"];
|
|
225
|
+
if (Array.isArray(nested))
|
|
226
|
+
collect(nested);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
collect(nodes);
|
|
231
|
+
if (used.size === 0 && !hasNative)
|
|
232
|
+
return undefined;
|
|
233
|
+
try {
|
|
234
|
+
const api = getApi();
|
|
235
|
+
const [integrations, native] = await Promise.all([
|
|
236
|
+
used.size === 0
|
|
237
|
+
? undefined
|
|
238
|
+
: api.connection.integration
|
|
239
|
+
.list({ slugs: [...used] })
|
|
240
|
+
.catch(() => undefined),
|
|
241
|
+
hasNative
|
|
242
|
+
? api.connection.nativeIntegration.get().catch(() => undefined)
|
|
243
|
+
: undefined,
|
|
244
|
+
]);
|
|
245
|
+
const byIntegration = {};
|
|
246
|
+
const byAction = {};
|
|
247
|
+
const listed = integrations === undefined ? [] : integrations.integrations;
|
|
248
|
+
for (const integration of listed) {
|
|
249
|
+
const actions = integration.actions === undefined ? {} : integration.actions;
|
|
250
|
+
byIntegration[integration.slug] = integration.name;
|
|
251
|
+
byAction[integration.slug] = Object.fromEntries(Object.entries(actions).map(([slug, action]) => [slug, action.name]));
|
|
252
|
+
}
|
|
253
|
+
const nativeActions = native === undefined ? {} : native.nativeIntegration.actions;
|
|
254
|
+
return {
|
|
255
|
+
integrations: byIntegration,
|
|
256
|
+
actions: byAction,
|
|
257
|
+
native: Object.fromEntries(Object.entries(nativeActions).map(([slug, action]) => [
|
|
258
|
+
slug,
|
|
259
|
+
action.name,
|
|
260
|
+
])),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
154
267
|
function splitList(value) {
|
|
155
268
|
if (value === undefined)
|
|
156
269
|
return [];
|
|
@@ -30,8 +30,50 @@ export type DiagramResult = {
|
|
|
30
30
|
/** Structural problems worth saying out loud rather than drawing over. */
|
|
31
31
|
warnings: string[];
|
|
32
32
|
};
|
|
33
|
+
/** Actions that route rather than do work. Shared, so adding one to the
|
|
34
|
+
* platform does not have to be mirrored in each renderer. */
|
|
35
|
+
export declare const ROUTING_ACTIONS: Set<string>;
|
|
36
|
+
export declare function isStart(node: DiagramNode | undefined): boolean;
|
|
37
|
+
export declare function isEnd(node: DiagramNode | undefined): boolean;
|
|
38
|
+
export declare function indexByUuid(nodes: DiagramNode[]): Map<string, DiagramNode>;
|
|
39
|
+
/** Where the graph begins — the `start` node, or the first node if it has none. */
|
|
40
|
+
export declare function findStart(nodes: DiagramNode[]): DiagramNode | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Whether a user-supplied `--paid` / `--highlight` token names this node.
|
|
43
|
+
* Slugs repeat within a release, so a slug token marks every node carrying it;
|
|
44
|
+
* pass a uuid to mark exactly one.
|
|
45
|
+
*/
|
|
46
|
+
export declare function matchesNode(node: DiagramNode, needles: string[]): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* A node's outgoing edges, in `childrenUuids` order, labelled by what the
|
|
49
|
+
* routing node means. Shared because this encodes routing *semantics* — index
|
|
50
|
+
* order, and the rule that a fallback pointing at the node's own next step is
|
|
51
|
+
* the same arrow rather than a second one. Two copies would let the two formats
|
|
52
|
+
* draw different graphs from one release.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* A fallback pointing at the node's own next step: a failure here does not stop
|
|
56
|
+
* the run. It is not a second arrow — it is the same one — so it shows as a mark
|
|
57
|
+
* on the step rather than an edge. Silently dropping it makes a step that
|
|
58
|
+
* survives a provider outage look like one that dies on it, which is the exact
|
|
59
|
+
* misread this command exists to prevent.
|
|
60
|
+
*/
|
|
61
|
+
export declare function continuesOnFailure(node: DiagramNode): boolean;
|
|
62
|
+
export declare function outgoingEdges(node: DiagramNode, byUuid: Map<string, DiagramNode>): {
|
|
63
|
+
to: string;
|
|
64
|
+
label?: string;
|
|
65
|
+
dashed?: boolean;
|
|
66
|
+
}[];
|
|
67
|
+
/** Structural problems worth saying out loud rather than drawing over. */
|
|
68
|
+
export declare function graphWarnings(nodes: DiagramNode[], byUuid: Map<string, DiagramNode>, reached: Set<string>): string[];
|
|
33
69
|
/** What the node is, in words a user recognises. */
|
|
34
70
|
export declare function labelFor(node: DiagramNode): string;
|
|
71
|
+
/**
|
|
72
|
+
* `childrenUuids` order carries the routing semantics — index 0 of a `branch`
|
|
73
|
+
* is the matched path, index 1 the unmatched one. Getting this backwards
|
|
74
|
+
* inverts the diagram's meaning, so it is table-driven, never inferred.
|
|
75
|
+
*/
|
|
76
|
+
export declare function edgeLabels(node: DiagramNode, count: number): (string | undefined)[];
|
|
35
77
|
export declare function renderNodeDiagram(nodes: DiagramNode[], options?: DiagramOptions): DiagramResult;
|
|
36
78
|
/** Node-shaped means: a uuid plus the fields a graph is walked by. */
|
|
37
79
|
export declare function isNodeArray(value: unknown): value is DiagramNode[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nodeDiagram.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/nodeDiagram.ts"],"names":[],"mappings":"AAgBA;;2CAE2C;AAC3C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;IACzC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;
|
|
1
|
+
{"version":3,"file":"nodeDiagram.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/nodeDiagram.ts"],"names":[],"mappings":"AAgBA;;2CAE2C;AAC3C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,aAAa,CAAC,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;IACzC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC;AAEF;6DAC6D;AAC7D,eAAO,MAAM,eAAe,aAO1B,CAAC;AAOH,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAG9D;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAG5D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAM1E;AAED,mFAAmF;AACnF,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,WAAW,GAAG,SAAS,CAGvE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAGzE;AAED;;;;;;GAMG;AACH;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAI7D;AAQD,wBAAgB,aAAa,CAC3B,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAC/B;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,CAmBpD;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAC3B,KAAK,EAAE,WAAW,EAAE,EACpB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EAChC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,GACnB,MAAM,EAAE,CAyBV;AA4GD,oDAAoD;AACpD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CASlD;AAQD;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,GACZ,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAyBxB;AAoKD,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,GAAE,cAAmB,GAC3B,aAAa,CAkCf;AAMD,sEAAsE;AACtE,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,EAAE,CAYlE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,EAAE,GAAG,SAAS,CAexE;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtD"}
|