@cargo-ai/cli 1.0.72 → 1.0.73
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/build/commands/auth/email.d.ts +24 -0
- package/build/commands/auth/email.d.ts.map +1 -1
- package/build/commands/auth/email.js +27 -14
- package/build/commands/auth/index.d.ts.map +1 -1
- package/build/commands/auth/index.js +7 -1
- package/build/commands/auth/workspace.d.ts.map +1 -1
- package/build/commands/auth/workspace.js +99 -26
- package/build/commands/connection/connector.d.ts.map +1 -1
- package/build/commands/connection/connector.js +144 -21
- package/build/commands/start/brief.d.ts +107 -0
- package/build/commands/start/brief.d.ts.map +1 -0
- package/build/commands/start/brief.js +222 -0
- package/build/commands/start/index.d.ts +44 -0
- package/build/commands/start/index.d.ts.map +1 -0
- package/build/commands/start/index.js +431 -0
- package/build/commands/start/state.d.ts +50 -0
- package/build/commands/start/state.d.ts.map +1 -0
- package/build/commands/start/state.js +101 -0
- package/build/config.d.ts +1 -0
- package/build/config.d.ts.map +1 -1
- package/build/config.js +1 -0
- package/build/index.js +50 -6
- package/build/paletteLayout.d.ts +16 -0
- package/build/paletteLayout.d.ts.map +1 -0
- package/build/paletteLayout.js +131 -0
- package/build/setupIssues.d.ts +4 -16
- package/build/setupIssues.d.ts.map +1 -1
- package/build/setupIssues.js +3 -52
- package/package.json +3 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ResolvedWorkspace } from "./workspace.js";
|
|
1
2
|
/** `--code -` reads the code from stdin, keeping it out of shell history. */
|
|
2
3
|
export declare const STDIN_CODE = "-";
|
|
3
4
|
export type EmailLoginOptions = {
|
|
@@ -6,11 +7,34 @@ export type EmailLoginOptions = {
|
|
|
6
7
|
workspaceName?: string;
|
|
7
8
|
workspaceUuid?: string;
|
|
8
9
|
baseUrl?: string;
|
|
10
|
+
/**
|
|
11
|
+
* What to re-run when a code was sent but there is no terminal to type it
|
|
12
|
+
* into. Defaults to the `login` form. `start` passes its own, because
|
|
13
|
+
* finishing through `login` would sign the user in and stop there, losing
|
|
14
|
+
* the destination the run was headed for.
|
|
15
|
+
*/
|
|
16
|
+
resumeCommand?: string;
|
|
17
|
+
};
|
|
18
|
+
export type EmailSignInResult = {
|
|
19
|
+
outcome: "signedIn";
|
|
20
|
+
email: string;
|
|
21
|
+
workspace: ResolvedWorkspace;
|
|
22
|
+
}
|
|
23
|
+
/** The code was sent, but there is no terminal to type it back into. */
|
|
24
|
+
| {
|
|
25
|
+
outcome: "codeSent";
|
|
26
|
+
email: string;
|
|
27
|
+
next: string;
|
|
9
28
|
};
|
|
10
29
|
/**
|
|
11
30
|
* Signs in with a code emailed by the identity provider, creating the account
|
|
12
31
|
* on first use. This is the only channel that needs neither a browser nor an
|
|
13
32
|
* existing credential.
|
|
33
|
+
*
|
|
34
|
+
* Returns rather than prints, because two callers need the same exchange and
|
|
35
|
+
* report it differently: `login` writes the JSON result a script reads, while
|
|
36
|
+
* `start` carries on to the next question.
|
|
14
37
|
*/
|
|
38
|
+
export declare function signInWithEmail(opts: EmailLoginOptions): Promise<EmailSignInResult>;
|
|
15
39
|
export declare function runEmailLogin(opts: EmailLoginOptions): Promise<void>;
|
|
16
40
|
//# sourceMappingURL=email.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/email.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/email.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,6EAA6E;AAC7E,eAAO,MAAM,UAAU,MAAM,CAAC;AAE9B,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GACzB;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,iBAAiB,CAAA;CAAE;AACtE,wEAAwE;GACtE;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,iBAAiB,CAAC,CA8B5B;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B1E"}
|
|
@@ -10,24 +10,22 @@ export const STDIN_CODE = "-";
|
|
|
10
10
|
* Signs in with a code emailed by the identity provider, creating the account
|
|
11
11
|
* on first use. This is the only channel that needs neither a browser nor an
|
|
12
12
|
* existing credential.
|
|
13
|
+
*
|
|
14
|
+
* Returns rather than prints, because two callers need the same exchange and
|
|
15
|
+
* report it differently: `login` writes the JSON result a script reads, while
|
|
16
|
+
* `start` carries on to the next question.
|
|
13
17
|
*/
|
|
14
|
-
export async function
|
|
18
|
+
export async function signInWithEmail(opts) {
|
|
15
19
|
const email = opts.email;
|
|
16
20
|
if (email === undefined || email.length === 0) {
|
|
17
21
|
failWith("Must pass --email <email>.", { code: ExitCodes.InvalidUsage });
|
|
18
22
|
}
|
|
19
|
-
const resumeCommand =
|
|
23
|
+
const resumeCommand = opts.resumeCommand !== undefined
|
|
24
|
+
? opts.resumeCommand
|
|
25
|
+
: `cargo-ai login --email ${email} --code <code>`;
|
|
20
26
|
const code = await resolveCode({ email, code: opts.code, resumeCommand });
|
|
21
27
|
if (code === undefined) {
|
|
22
|
-
|
|
23
|
-
// successfully so an agent can relay the code and re-run non-interactively.
|
|
24
|
-
outputJson({
|
|
25
|
-
ok: true,
|
|
26
|
-
status: "codeSent",
|
|
27
|
-
email,
|
|
28
|
-
next: resumeCommand,
|
|
29
|
-
});
|
|
30
|
-
return;
|
|
28
|
+
return { outcome: "codeSent", email, next: resumeCommand };
|
|
31
29
|
}
|
|
32
30
|
const spinner = startSpinner("Verifying code...");
|
|
33
31
|
const tokens = await exchangeEmailOtp({ email, otp: code });
|
|
@@ -38,15 +36,30 @@ export async function runEmailLogin(opts) {
|
|
|
38
36
|
workspaceUuid: opts.workspaceUuid,
|
|
39
37
|
workspaceName: opts.workspaceName,
|
|
40
38
|
});
|
|
41
|
-
|
|
39
|
+
return { outcome: "signedIn", email, workspace };
|
|
40
|
+
}
|
|
41
|
+
export async function runEmailLogin(opts) {
|
|
42
|
+
const result = await signInWithEmail(opts);
|
|
43
|
+
if (result.outcome === "codeSent") {
|
|
44
|
+
// Exit successfully so an agent can relay the code and re-run
|
|
45
|
+
// non-interactively.
|
|
46
|
+
outputJson({
|
|
47
|
+
ok: true,
|
|
48
|
+
status: "codeSent",
|
|
49
|
+
email: result.email,
|
|
50
|
+
next: result.next,
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
success(`Signed in as ${result.email}. Workspace: ${result.workspace.name}.`);
|
|
42
55
|
info(AGENT_SKILLS_NEXT_STEP);
|
|
43
56
|
outputJson({
|
|
44
57
|
ok: true,
|
|
45
58
|
status: "signedIn",
|
|
46
59
|
credentialsFile: getCredentialsPath(),
|
|
47
60
|
baseUrl: resolveBaseUrl(opts.baseUrl),
|
|
48
|
-
email,
|
|
49
|
-
workspace,
|
|
61
|
+
email: result.email,
|
|
62
|
+
workspace: result.workspace,
|
|
50
63
|
tokenType: "session",
|
|
51
64
|
});
|
|
52
65
|
}
|
|
@@ -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;AA2BxD,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,CAsIN;AA2GD,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"}
|
|
@@ -5,6 +5,7 @@ import { getConfig } from "../../config.js";
|
|
|
5
5
|
import { clearCredentials, CredentialsNotClearedError, getCredentialsPath, loadCredentials, saveCredentials, } from "../../credentials.js";
|
|
6
6
|
import { parseBooleanOption } from "../../utils/booleanOption.js";
|
|
7
7
|
import { ExitCodes, failWith, handleApiCall, info, outputJson, success, } from "../runHandler.js";
|
|
8
|
+
import { clearStartState } from "../start/state.js";
|
|
8
9
|
import { runEmailLogin } from "./email.js";
|
|
9
10
|
import { selectLoginChannel } from "./loginChannel.js";
|
|
10
11
|
import { runOAuthDeviceFlow } from "./oauth.js";
|
|
@@ -171,16 +172,21 @@ async function runLogout(opts) {
|
|
|
171
172
|
}
|
|
172
173
|
throw error;
|
|
173
174
|
}
|
|
175
|
+
// Not conditional on `removed`: someone whose credential was an environment
|
|
176
|
+
// variable, or who is signing out a second time, still means "start over".
|
|
177
|
+
const startStateCleared = clearStartState();
|
|
174
178
|
if (removed === true) {
|
|
175
179
|
success(revoked === true ? "Signed out and revoked access." : "Signed out.");
|
|
176
180
|
}
|
|
177
181
|
else {
|
|
178
|
-
info("No saved credentials to remove.");
|
|
182
|
+
info("No saved credentials to remove — nothing was signed in through this CLI.");
|
|
183
|
+
info("If commands still work, the token is coming from CARGO_API_TOKEN or a project .env.");
|
|
179
184
|
}
|
|
180
185
|
outputJson({
|
|
181
186
|
ok: true,
|
|
182
187
|
removed,
|
|
183
188
|
revoked,
|
|
189
|
+
startStateCleared,
|
|
184
190
|
credentialsFile: getCredentialsPath(),
|
|
185
191
|
});
|
|
186
192
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/workspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/workspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAazD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,iBAAiB,CAAC;IAC7B,mFAAmF;IACnF,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAUF,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAkG/B;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,SAAS,mBAAmB,CAAC,SAAS,EAAE,EACpD,IAAI,EAAE,MAAM,GACX,iBAAiB,GAAG,SAAS,CAY/B"}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import * as readline from "node:readline/promises";
|
|
2
|
+
import { askText, isInteractive, selectOne } from "@cargo-ai/cdk/cli";
|
|
2
3
|
import { createApi } from "../../api.js";
|
|
3
4
|
import { askQuestion, openPromptInput } from "../../utils/prompt.js";
|
|
4
5
|
import { ExitCodes, failWith, handleApiCall, info, success, } from "../runHandler.js";
|
|
6
|
+
/**
|
|
7
|
+
* The "make me a new one" row. A leading space keeps it out of the uuid
|
|
8
|
+
* namespace, so it can never collide with a real workspace.
|
|
9
|
+
*/
|
|
10
|
+
const CREATE = " create";
|
|
11
|
+
const DEFAULT_WORKSPACE_NAME = "My workspace";
|
|
5
12
|
export async function resolveWorkspace(opts) {
|
|
6
13
|
const accessToken = opts.accessToken;
|
|
7
14
|
const api = createApi({
|
|
@@ -34,28 +41,6 @@ export async function resolveWorkspace(opts) {
|
|
|
34
41
|
wasCreated: false,
|
|
35
42
|
};
|
|
36
43
|
}
|
|
37
|
-
if (workspaces.length === 0) {
|
|
38
|
-
const promptInput = openPromptInput();
|
|
39
|
-
if (promptInput === undefined) {
|
|
40
|
-
failWith("No workspaces found. Re-run with --workspace-name <name> to create one.", { code: ExitCodes.NotFound });
|
|
41
|
-
}
|
|
42
|
-
let name;
|
|
43
|
-
try {
|
|
44
|
-
info("You don't have any workspaces yet. Let's create one.");
|
|
45
|
-
name = await askQuestion(promptInput.stream, "Workspace name (or press Enter to skip): ");
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
promptInput.close();
|
|
49
|
-
}
|
|
50
|
-
if (name === undefined) {
|
|
51
|
-
failWith("No workspace name provided.", {
|
|
52
|
-
code: ExitCodes.InvalidUsage,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
const workspace = await createWorkspace(api, name);
|
|
56
|
-
success(`Created workspace: ${workspace.name} (${workspace.uuid})`);
|
|
57
|
-
return { workspace, wasCreated: true };
|
|
58
|
-
}
|
|
59
44
|
if (workspaces.length === 1) {
|
|
60
45
|
const [firstWorkspace] = workspaces;
|
|
61
46
|
if (firstWorkspace === undefined) {
|
|
@@ -66,6 +51,15 @@ export async function resolveWorkspace(opts) {
|
|
|
66
51
|
wasCreated: false,
|
|
67
52
|
};
|
|
68
53
|
}
|
|
54
|
+
// Picking an existing workspace and creating one are the same decision, so
|
|
55
|
+
// they are one question rather than two branches with two different pieces
|
|
56
|
+
// of UI. The readline path below stays for the terminals clack cannot use.
|
|
57
|
+
if (isInteractive() === true) {
|
|
58
|
+
return await chooseWorkspace(api, workspaces);
|
|
59
|
+
}
|
|
60
|
+
if (workspaces.length === 0) {
|
|
61
|
+
return await createWorkspaceByAsking(api);
|
|
62
|
+
}
|
|
69
63
|
const promptInput = openPromptInput();
|
|
70
64
|
if (promptInput === undefined) {
|
|
71
65
|
failWith("Multiple workspaces available and no interactive terminal; pass --workspace-uuid <uuid> (or set CARGO_WORKSPACE_UUID when using install.sh) to select one.", { code: ExitCodes.InvalidUsage });
|
|
@@ -82,10 +76,6 @@ export async function resolveWorkspace(opts) {
|
|
|
82
76
|
}
|
|
83
77
|
return { workspace: selectedWorkspace, wasCreated: false };
|
|
84
78
|
}
|
|
85
|
-
async function createWorkspace(api, name) {
|
|
86
|
-
const result = await handleApiCall(() => api.workspaceManagement.workspace.create({ name }), { spinner: "Creating workspace..." });
|
|
87
|
-
return { uuid: result.workspace.uuid, name: result.workspace.name };
|
|
88
|
-
}
|
|
89
79
|
export function findWorkspaceByName(workspaces, name) {
|
|
90
80
|
const wanted = name.trim().toLowerCase();
|
|
91
81
|
const matched = workspaces.find((workspace) => {
|
|
@@ -96,6 +86,89 @@ export function findWorkspaceByName(workspaces, name) {
|
|
|
96
86
|
}
|
|
97
87
|
return { uuid: matched.uuid, name: matched.name };
|
|
98
88
|
}
|
|
89
|
+
/** Pick from the workspaces you have, or name a new one, in one question. */
|
|
90
|
+
async function chooseWorkspace(api, workspaces) {
|
|
91
|
+
if (workspaces.length === 0) {
|
|
92
|
+
info("You don't have any workspaces yet. Let's create one.");
|
|
93
|
+
return {
|
|
94
|
+
workspace: await createWorkspace(api, await askWorkspaceName()),
|
|
95
|
+
wasCreated: true,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const chosen = await selectOne({
|
|
99
|
+
// First, not last: past a dozen workspaces the list scrolls, and a create
|
|
100
|
+
// row at the bottom is off-screen — reachable only by someone who already
|
|
101
|
+
// guessed to type "create". The cursor still starts on the first
|
|
102
|
+
// workspace, so the row is visible without Enter landing on it.
|
|
103
|
+
message: "Which workspace should this CLI work in?",
|
|
104
|
+
recommended: workspaces[0] === undefined ? CREATE : workspaces[0].uuid,
|
|
105
|
+
choices: [
|
|
106
|
+
{
|
|
107
|
+
value: CREATE,
|
|
108
|
+
label: "+ Create a new workspace",
|
|
109
|
+
hint: "you name it",
|
|
110
|
+
fallback: true,
|
|
111
|
+
},
|
|
112
|
+
...workspaces.map((workspace) => ({
|
|
113
|
+
value: workspace.uuid,
|
|
114
|
+
label: workspace.name,
|
|
115
|
+
hint: workspace.uuid,
|
|
116
|
+
})),
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
if (chosen === CREATE) {
|
|
120
|
+
return {
|
|
121
|
+
workspace: await createWorkspace(api, await askWorkspaceName()),
|
|
122
|
+
wasCreated: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const selected = workspaces.find((workspace) => workspace.uuid === chosen);
|
|
126
|
+
if (selected === undefined) {
|
|
127
|
+
failWith("No workspace selected.", { code: ExitCodes.InvalidUsage });
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
workspace: { uuid: selected.uuid, name: selected.name },
|
|
131
|
+
wasCreated: false,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async function askWorkspaceName() {
|
|
135
|
+
return await askText({
|
|
136
|
+
message: "Name it",
|
|
137
|
+
placeholder: DEFAULT_WORKSPACE_NAME,
|
|
138
|
+
defaultValue: DEFAULT_WORKSPACE_NAME,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The zero-workspace path for terminals clack cannot drive — `curl | sh`
|
|
143
|
+
* installers pipe stdin while the controlling terminal stays reachable through
|
|
144
|
+
* /dev/tty, and this is what reads it.
|
|
145
|
+
*/
|
|
146
|
+
async function createWorkspaceByAsking(api) {
|
|
147
|
+
const promptInput = openPromptInput();
|
|
148
|
+
if (promptInput === undefined) {
|
|
149
|
+
failWith("No workspaces found. Re-run with --workspace-name <name> to create one.", { code: ExitCodes.NotFound });
|
|
150
|
+
}
|
|
151
|
+
let name;
|
|
152
|
+
try {
|
|
153
|
+
info("You don't have any workspaces yet. Let's create one.");
|
|
154
|
+
name = await askQuestion(promptInput.stream, "Workspace name (or press Enter to skip): ");
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
promptInput.close();
|
|
158
|
+
}
|
|
159
|
+
if (name === undefined) {
|
|
160
|
+
failWith("No workspace name provided.", {
|
|
161
|
+
code: ExitCodes.InvalidUsage,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const workspace = await createWorkspace(api, name);
|
|
165
|
+
success(`Created workspace: ${workspace.name} (${workspace.uuid})`);
|
|
166
|
+
return { workspace, wasCreated: true };
|
|
167
|
+
}
|
|
168
|
+
async function createWorkspace(api, name) {
|
|
169
|
+
const result = await handleApiCall(() => api.workspaceManagement.workspace.create({ name }), { spinner: "Creating workspace..." });
|
|
170
|
+
return { uuid: result.workspace.uuid, name: result.workspace.name };
|
|
171
|
+
}
|
|
99
172
|
async function promptWorkspaceSelection(workspaces, input) {
|
|
100
173
|
const rl = readline.createInterface({
|
|
101
174
|
input,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../../src/commands/connection/connector.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../../src/commands/connection/connector.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAcxC,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CA6TN"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { awaitBrowserStep, buildCliConnectorCreateUrl, buildCliConnectorEditUrl, isCodingAgent, openBrowser, resolveUserUuid, shouldWaitForPerson, } from "@cargo-ai/cdk/cli";
|
|
2
|
+
import { getConfig } from "../../config.js";
|
|
1
3
|
import { parseBooleanOption } from "../../utils/booleanOption.js";
|
|
2
|
-
import {
|
|
4
|
+
import { openPromptInput } from "../../utils/prompt.js";
|
|
5
|
+
import { colors, ExitCodes, failWith, handleApiCall, info, outputJson, parseJson, } from "../runHandler.js";
|
|
3
6
|
export function registerConnectorCommands(parent, getApi) {
|
|
4
7
|
const connector = parent
|
|
5
8
|
.command("connector")
|
|
@@ -79,39 +82,70 @@ export function registerConnectorCommands(parent, getApi) {
|
|
|
79
82
|
});
|
|
80
83
|
connector
|
|
81
84
|
.command("create")
|
|
82
|
-
.description("Create a connector")
|
|
83
|
-
.
|
|
84
|
-
.
|
|
85
|
+
.description("Create a connector. Without --config, opens the hosted form in a browser and waits.")
|
|
86
|
+
.option("--name <name>", "Connector name (string, required with --config)")
|
|
87
|
+
.option("--slug <slug>", "Connector slug identifier (string, required with --config)")
|
|
85
88
|
.requiredOption("--integration-slug <slug>", "Integration slug to connect to (string, required)")
|
|
86
|
-
.option("--config <json>", "Connector configuration (JSON object, shape depends on integration)")
|
|
89
|
+
.option("--config <json>", "Connector configuration (JSON object, shape depends on integration). Skips the browser.")
|
|
87
90
|
.option("--rate-limit <json>", 'Rate limit configuration (JSON object). Shape: {"maxRequests": integer, "windowMs": integer}')
|
|
88
91
|
.option("--cache-ttl-milliseconds <n>", "Cache TTL in milliseconds (integer)")
|
|
89
92
|
.addHelpText("after", `
|
|
90
93
|
Examples:
|
|
91
|
-
|
|
94
|
+
# Open the hosted form, wait, then write connectors/hubspot.ts and bind it
|
|
95
|
+
$ cargo-ai connection connector create --integration-slug hubspot
|
|
96
|
+
|
|
97
|
+
# Fully non-interactive — no browser
|
|
92
98
|
$ cargo-ai connection connector create --name "HubSpot" --slug hubspot-main --integration-slug hubspot \\
|
|
93
|
-
--
|
|
99
|
+
--config '{"method":"privateApp","accessToken":"pat-..."}'
|
|
100
|
+
$ cargo-ai connection connector create --name "HubSpot" --slug hubspot-main --integration-slug hubspot \\
|
|
101
|
+
--config '{...}' --rate-limit '{"maxRequests":100,"windowMs":60000}' --cache-ttl-milliseconds 300000`)
|
|
94
102
|
.action(async (opts) => {
|
|
95
103
|
const api = getApi();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
integrationSlug: opts.integrationSlug,
|
|
100
|
-
config: opts.config !== undefined
|
|
101
|
-
? parseJson(opts.config, "--config")
|
|
102
|
-
: undefined,
|
|
103
|
-
rateLimit: opts.rateLimit !== undefined
|
|
104
|
+
// `--config` keeps the original, fully scriptable behaviour.
|
|
105
|
+
if (opts.config !== undefined) {
|
|
106
|
+
const rateLimit = opts.rateLimit !== undefined
|
|
104
107
|
? parseJson(opts.rateLimit, "--rate-limit")
|
|
105
|
-
: undefined
|
|
106
|
-
cacheTtlMilliseconds
|
|
108
|
+
: undefined;
|
|
109
|
+
const cacheTtlMilliseconds = opts.cacheTtlMilliseconds !== undefined
|
|
107
110
|
? parseInt(opts.cacheTtlMilliseconds, 10)
|
|
108
|
-
: undefined
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
: undefined;
|
|
112
|
+
const name = opts.name;
|
|
113
|
+
const slug = opts.slug;
|
|
114
|
+
if (name === undefined || slug === undefined) {
|
|
115
|
+
failWith("--name and --slug are required when --config is passed.", { code: ExitCodes.InvalidUsage });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const config = parseJson(opts.config, "--config");
|
|
119
|
+
const result = await handleApiCall(() => api.connection.connector.create({
|
|
120
|
+
name,
|
|
121
|
+
slug,
|
|
122
|
+
integrationSlug: opts.integrationSlug,
|
|
123
|
+
config,
|
|
124
|
+
rateLimit,
|
|
125
|
+
cacheTtlMilliseconds,
|
|
126
|
+
}));
|
|
127
|
+
outputJson(result);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const ignored = [
|
|
131
|
+
opts.name === undefined ? undefined : "--name",
|
|
132
|
+
opts.slug === undefined ? undefined : "--slug",
|
|
133
|
+
opts.rateLimit === undefined ? undefined : "--rate-limit",
|
|
134
|
+
opts.cacheTtlMilliseconds === undefined
|
|
135
|
+
? undefined
|
|
136
|
+
: "--cache-ttl-milliseconds",
|
|
137
|
+
].filter((flag) => flag !== undefined);
|
|
138
|
+
// The hosted form asks for all of these itself, so accepting them here
|
|
139
|
+
// and dropping them would silently produce a connector the caller did
|
|
140
|
+
// not describe.
|
|
141
|
+
if (ignored.length > 0) {
|
|
142
|
+
failWith(`${ignored.join(", ")} only applies with --config — the browser form asks for it.`, { code: ExitCodes.InvalidUsage });
|
|
143
|
+
}
|
|
144
|
+
await runCreateHandoff({ api, integrationSlug: opts.integrationSlug });
|
|
111
145
|
});
|
|
112
146
|
connector
|
|
113
147
|
.command("update")
|
|
114
|
-
.description("Update a connector")
|
|
148
|
+
.description("Update a connector. With no field flags, opens the hosted form in a browser (use it to re-authorize).")
|
|
115
149
|
.requiredOption("--uuid <uuid>", "Connector UUID")
|
|
116
150
|
.option("--name <name>", "Connector name")
|
|
117
151
|
.option("--config <json>", "Configuration (JSON object)")
|
|
@@ -119,6 +153,19 @@ Examples:
|
|
|
119
153
|
.option("--cache-ttl-milliseconds <n>", "Cache TTL in milliseconds")
|
|
120
154
|
.action(async (opts) => {
|
|
121
155
|
const api = getApi();
|
|
156
|
+
// No field to change means "let me edit it in the form" — the only way
|
|
157
|
+
// to re-run an OAuth flow for an existing connector.
|
|
158
|
+
const hasFieldFlag = opts.name !== undefined ||
|
|
159
|
+
opts.config !== undefined ||
|
|
160
|
+
opts.rateLimit !== undefined ||
|
|
161
|
+
opts.cacheTtlMilliseconds !== undefined;
|
|
162
|
+
if (hasFieldFlag === false) {
|
|
163
|
+
// Re-auth changes credentials, not the binding: the CDK file already
|
|
164
|
+
// exists and binds by slug, so there is nothing to write and nothing
|
|
165
|
+
// to wait for. Open the form and get out of the way.
|
|
166
|
+
openConnectorEditForm(opts.uuid);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
122
169
|
const result = await handleApiCall(() => api.connection.connector.update({
|
|
123
170
|
uuid: opts.uuid,
|
|
124
171
|
name: opts.name,
|
|
@@ -144,3 +191,79 @@ Examples:
|
|
|
144
191
|
outputJson(result);
|
|
145
192
|
});
|
|
146
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Send the user to the connectors page, wait for them to confirm, then report
|
|
196
|
+
* the connector they made.
|
|
197
|
+
*
|
|
198
|
+
* Imperative only: it creates a connector and prints it. Writing
|
|
199
|
+
* `connectors/<slug>.ts` and binding cargo.state.json is
|
|
200
|
+
* `cargo-ai cdk add connector/<integration>`, so running this inside a repo
|
|
201
|
+
* never touches the repo.
|
|
202
|
+
*/
|
|
203
|
+
async function runCreateHandoff(input) {
|
|
204
|
+
const { api, integrationSlug } = input;
|
|
205
|
+
const url = buildCliConnectorCreateUrl({
|
|
206
|
+
...resolveAppContext(),
|
|
207
|
+
integrationSlug,
|
|
208
|
+
});
|
|
209
|
+
// Nothing can drive a browser for an agent: hand back the URL and where to
|
|
210
|
+
// look once a human has clicked through. `openPromptInput` still finds
|
|
211
|
+
// `/dev/tty` when stdin is piped; `shouldWaitForPerson` additionally
|
|
212
|
+
// refuses coding agents, whose stdin often looks like a TTY.
|
|
213
|
+
if (shouldWaitForPerson({
|
|
214
|
+
isCodingAgent: isCodingAgent(),
|
|
215
|
+
isCi: process.env["CI"] === "true",
|
|
216
|
+
hasPromptInput: openPromptInput() !== undefined,
|
|
217
|
+
}) === false) {
|
|
218
|
+
outputJson({
|
|
219
|
+
ok: true,
|
|
220
|
+
status: "actionRequired",
|
|
221
|
+
action: "openBrowser",
|
|
222
|
+
integrationSlug,
|
|
223
|
+
url,
|
|
224
|
+
reason: "Creating this connector needs the hosted form, which cannot be driven headlessly.",
|
|
225
|
+
next: `cargo-ai connection connector list --integration-slug ${integrationSlug}`,
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const [before, userUuid] = await Promise.all([
|
|
230
|
+
handleApiCall(() => api.connection.connector.list({ integrationSlug })),
|
|
231
|
+
resolveUserUuid(api),
|
|
232
|
+
]);
|
|
233
|
+
const connector = await awaitBrowserStep({
|
|
234
|
+
api,
|
|
235
|
+
url,
|
|
236
|
+
integrationSlug,
|
|
237
|
+
before: before.connectors,
|
|
238
|
+
userUuid,
|
|
239
|
+
});
|
|
240
|
+
if (connector === undefined) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
outputJson({ ok: true, status: "connected", connector });
|
|
244
|
+
}
|
|
245
|
+
/** Open this connector's edit form so the user can re-run its auth flow. */
|
|
246
|
+
function openConnectorEditForm(connectorUuid) {
|
|
247
|
+
const url = buildCliConnectorEditUrl({
|
|
248
|
+
...resolveAppContext(),
|
|
249
|
+
connectorUuid,
|
|
250
|
+
});
|
|
251
|
+
info(["", "Re-authorize the connector here:", ` ${colors.cyan(url)}`, ""].join("\n"));
|
|
252
|
+
openBrowser(url);
|
|
253
|
+
outputJson({
|
|
254
|
+
ok: true,
|
|
255
|
+
status: "actionRequired",
|
|
256
|
+
action: "openBrowser",
|
|
257
|
+
url,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/** The workspace to act in; the app URL is configuration, not a guess. */
|
|
261
|
+
function resolveAppContext() {
|
|
262
|
+
const { appUrl, workspaceUuid } = getConfig();
|
|
263
|
+
if (workspaceUuid === undefined) {
|
|
264
|
+
return failWith("No workspace selected. Run 'cargo-ai login' first.", {
|
|
265
|
+
code: ExitCodes.NotAuthenticated,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return { appUrl, workspaceUuid };
|
|
269
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { CDK_SKILL, type CodingAgent } from "@cargo-ai/cdk/cli";
|
|
2
|
+
/**
|
|
3
|
+
* Where the agent is being opened, which decides what it is told to do.
|
|
4
|
+
*
|
|
5
|
+
* Three cases, because the right first move is different in each: outside a
|
|
6
|
+
* project there is nothing to read and the work starts with a conversation;
|
|
7
|
+
* inside a CDK repo the workspace is already declared in files worth reading
|
|
8
|
+
* before touching; and inside one carrying a cookbook, that cookbook's own
|
|
9
|
+
* procedure is the authority and re-deriving intent is the wrong move.
|
|
10
|
+
*
|
|
11
|
+
* Naming the same skill in all three, which is what this used to do, sent an
|
|
12
|
+
* agent into a repo full of answers and told it to start asking questions.
|
|
13
|
+
*/
|
|
14
|
+
export type ProjectContext = {
|
|
15
|
+
kind: "none";
|
|
16
|
+
} | {
|
|
17
|
+
kind: "cdk";
|
|
18
|
+
root: string;
|
|
19
|
+
companyContextPath: string | undefined;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "cookbook";
|
|
22
|
+
root: string;
|
|
23
|
+
companyContextPath: string | undefined;
|
|
24
|
+
cookbooks: readonly string[];
|
|
25
|
+
};
|
|
26
|
+
export type StartFacts = {
|
|
27
|
+
workspace: {
|
|
28
|
+
uuid: string;
|
|
29
|
+
name: string;
|
|
30
|
+
};
|
|
31
|
+
/** Whether the Cargo skills bundle is already installed for a coding agent. */
|
|
32
|
+
skillsInstalled: boolean;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The command group "Explore in the CLI" opens the palette on. The action
|
|
36
|
+
* catalog is the shortest path from a menu to something real: search one, run
|
|
37
|
+
* one, see a result.
|
|
38
|
+
*/
|
|
39
|
+
export declare const EXPLORE_PATH: readonly string[];
|
|
40
|
+
/**
|
|
41
|
+
* The skill a handoff loads before the agent's first turn. `cargo` routes; the
|
|
42
|
+
* other two own their task outright.
|
|
43
|
+
*
|
|
44
|
+
* This type is the one place the whole story lives, because everything about
|
|
45
|
+
* the handoff depends on getting it right, and it has been got wrong twice.
|
|
46
|
+
*
|
|
47
|
+
* A skill reaches the agent by two paths at once: the launch passes it as a
|
|
48
|
+
* `/<skill>` command, which only Claude Code expands, and the brief names it in
|
|
49
|
+
* prose, which is all every other agent has. So the two must agree. The first
|
|
50
|
+
* version of the demo brief described its *outcome* instead of naming a skill
|
|
51
|
+
* and let the router choose; the router picked `cargo-gtm`, the general
|
|
52
|
+
* sourcing surface, and the agent spent four of its eight minutes rediscovering
|
|
53
|
+
* action names and dumping integration JSON to find a config schema. The
|
|
54
|
+
* second version named `cargo-quickstart` in the launch but left the brief
|
|
55
|
+
* saying "load `cargo`", which put every non-Claude agent back through the
|
|
56
|
+
* router and made the demo ask for two different skills in two steps.
|
|
57
|
+
*
|
|
58
|
+
* Hence a union rather than `string`: a mistyped or mismatched skill name is
|
|
59
|
+
* not something the CLI can notice at runtime, and the symptom is slow rather
|
|
60
|
+
* than broken, which is the kind of bug that survives.
|
|
61
|
+
*/
|
|
62
|
+
export type HandoffSkill = "cargo" | typeof CDK_SKILL | "cargo-quickstart";
|
|
63
|
+
export declare const ROUTER_SKILL: HandoffSkill;
|
|
64
|
+
export declare const DEMO_SKILL: HandoffSkill;
|
|
65
|
+
export { CDK_SKILL };
|
|
66
|
+
export type Destination = {
|
|
67
|
+
kind: "agent";
|
|
68
|
+
agent: CodingAgent;
|
|
69
|
+
} | {
|
|
70
|
+
kind: "code";
|
|
71
|
+
} | {
|
|
72
|
+
kind: "cli";
|
|
73
|
+
} | {
|
|
74
|
+
kind: "demo";
|
|
75
|
+
} | {
|
|
76
|
+
kind: "none";
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* `--continue <where>` as a destination. Agents are named the way a person
|
|
80
|
+
* refers to them ("cursor"), not the way they are installed ("cursor-agent"),
|
|
81
|
+
* and one that is not on this machine is a usage error rather than a silent
|
|
82
|
+
* fallback to something else.
|
|
83
|
+
*/
|
|
84
|
+
export declare function parseDestination(value: string, agents: readonly CodingAgent[]): Destination | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* The brief a coding agent is opened with. An agent launched in a bare
|
|
87
|
+
* directory has to rediscover the workspace, the skills and the cost rules;
|
|
88
|
+
* this is the whole reason the handoff beats telling someone to open Claude
|
|
89
|
+
* Code themselves.
|
|
90
|
+
*
|
|
91
|
+
* What it says depends on `project` — see `ProjectContext` for why all three
|
|
92
|
+
* cases need different words rather than one brief and a different skill name.
|
|
93
|
+
*/
|
|
94
|
+
export declare function buildBrief(facts: StartFacts, skill: HandoffSkill, project?: ProjectContext): string;
|
|
95
|
+
/**
|
|
96
|
+
* The brief behind the guided demo.
|
|
97
|
+
*
|
|
98
|
+
* It names `cargo-quickstart` rather than describing the demo, because that
|
|
99
|
+
* skill *is* this demo — a persona, ~25 leads, a cost receipt, then saving the
|
|
100
|
+
* pull as a play. Describing the outcome instead is what cost the first version
|
|
101
|
+
* half its runtime (see `HandoffSkill`), so this brief spends its length on
|
|
102
|
+
* pointing at the procedure and forbidding the detour.
|
|
103
|
+
*/
|
|
104
|
+
export declare function buildDemoBrief(facts: StartFacts, icp: string | undefined): string;
|
|
105
|
+
/** The block printed when nobody wanted a handoff. */
|
|
106
|
+
export declare function formatStartNextSteps(facts: StartFacts): string;
|
|
107
|
+
//# sourceMappingURL=brief.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brief.d.ts","sourceRoot":"","sources":["../../../src/commands/start/brief.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,SAAS,EACT,KAAK,WAAW,EAEjB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACrE;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B,CAAC;AAEN,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,+EAA+E;IAC/E,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAAgC,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,SAAS,GAAG,kBAAkB,CAAC;AAE3E,eAAO,MAAM,YAAY,EAAE,YAAsB,CAAC;AAClD,eAAO,MAAM,UAAU,EAAE,YAAiC,CAAC;AAE3D,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,GACrC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,GACf;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,SAAS,WAAW,EAAE,GAC7B,WAAW,GAAG,SAAS,CA8BzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,YAAY,EACnB,OAAO,GAAE,cAAiC,GACzC,MAAM,CAaR;AAyED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,MAAM,CA+BR;AAED,sDAAsD;AACtD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAgB9D"}
|