shepherd-onboard 0.1.2 → 0.1.4
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 +2 -0
- package/bin/shepherd-onboard.js +155 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ npx -y shepherd-onboard@latest agent
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
The command prints the exact prompt the agent should ask the user, then the exact follow-up commands to open auth, open Granola's API key page, finalize, start cloud raw polling/backfills, and install local Messages sync.
|
|
14
|
+
The agent prompt tells coding agents to ask short selection questions first: existing/new org, Shepherd WorkOS login or email linking, sources to connect, and Messages skip/provide-handle.
|
|
14
15
|
|
|
15
16
|
## Human Terminal One-liner
|
|
16
17
|
|
|
@@ -21,6 +22,7 @@ npx -y shepherd-onboard@latest
|
|
|
21
22
|
The command:
|
|
22
23
|
|
|
23
24
|
- asks for email, name, organization, and an optional local Messages handle
|
|
25
|
+
- can run a Shepherd WorkOS login first for returning users, then relink source setup to the same customer account/org
|
|
24
26
|
- creates or reuses the Shepherd customer account for the email
|
|
25
27
|
- creates or reuses the organization, including case-insensitive and close-name matches
|
|
26
28
|
- opens Google authorization for Gmail, Docs, and Calendar consent
|
package/bin/shepherd-onboard.js
CHANGED
|
@@ -154,6 +154,11 @@ async function runAgentOnboarding() {
|
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
if (args.login) {
|
|
158
|
+
await loginAgentWithWorkos();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
157
162
|
if (args.continue || args.resume) {
|
|
158
163
|
await continueAgentOnboarding();
|
|
159
164
|
return;
|
|
@@ -167,10 +172,20 @@ async function runAgentOnboarding() {
|
|
|
167
172
|
const apiUrl = trimTrailingSlash(args.api ?? DEFAULT_API_URL);
|
|
168
173
|
const noOpen = Boolean(args["no-open"]);
|
|
169
174
|
const sources = selectedSources();
|
|
175
|
+
const existingState = await readOptionalAgentState();
|
|
176
|
+
const workosAuth = existingState?.workosAuth?.status === "authenticated"
|
|
177
|
+
? existingState.workosAuth
|
|
178
|
+
: null;
|
|
170
179
|
const session = await postJson(`${apiUrl}/onboarding/raw/session`, {
|
|
171
180
|
email: stringArg("email"),
|
|
172
181
|
name: stringArg("name"),
|
|
173
182
|
organizationName: stringArg("org"),
|
|
183
|
+
...(workosAuth
|
|
184
|
+
? {
|
|
185
|
+
authSessionId: workosAuth.authSessionId,
|
|
186
|
+
authSessionToken: workosAuth.authSessionToken,
|
|
187
|
+
}
|
|
188
|
+
: {}),
|
|
174
189
|
sources,
|
|
175
190
|
});
|
|
176
191
|
|
|
@@ -181,6 +196,7 @@ async function runAgentOnboarding() {
|
|
|
181
196
|
account: session.account,
|
|
182
197
|
sources,
|
|
183
198
|
authUrls: session.authUrls ?? {},
|
|
199
|
+
workosAuth,
|
|
184
200
|
createdAt: new Date().toISOString(),
|
|
185
201
|
});
|
|
186
202
|
|
|
@@ -233,6 +249,78 @@ async function runAgentOnboarding() {
|
|
|
233
249
|
console.log(" Omit either optional flag if that source is not being connected.");
|
|
234
250
|
}
|
|
235
251
|
|
|
252
|
+
async function loginAgentWithWorkos() {
|
|
253
|
+
const apiUrl = trimTrailingSlash(args.api ?? DEFAULT_API_URL);
|
|
254
|
+
const noOpen = Boolean(args["no-open"]);
|
|
255
|
+
const started = await postJson(`${apiUrl}/onboarding/raw/auth/start`, {});
|
|
256
|
+
|
|
257
|
+
console.log("\nShepherd account login");
|
|
258
|
+
console.log("Opening Shepherd WorkOS auth. Complete login in the browser.");
|
|
259
|
+
await openOrPrint(started.verificationUriComplete ?? started.verificationUri, { noOpen });
|
|
260
|
+
if (noOpen && started.userCode) console.log(`User code: ${started.userCode}`);
|
|
261
|
+
|
|
262
|
+
const authenticated = await pollWorkosLogin(apiUrl, started);
|
|
263
|
+
const previous = await readOptionalAgentState();
|
|
264
|
+
const statePath = await writeAgentState({
|
|
265
|
+
...(previous ?? {}),
|
|
266
|
+
apiUrl,
|
|
267
|
+
workosAuth: {
|
|
268
|
+
status: "authenticated",
|
|
269
|
+
authSessionId: started.authSessionId,
|
|
270
|
+
authSessionToken: started.authSessionToken,
|
|
271
|
+
workosUser: authenticated.workosUser,
|
|
272
|
+
accountStatus: authenticated.accountStatus,
|
|
273
|
+
account: authenticated.account,
|
|
274
|
+
authenticatedAt: new Date().toISOString(),
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
if (args.json) {
|
|
279
|
+
console.log(JSON.stringify({
|
|
280
|
+
status: "authenticated",
|
|
281
|
+
accountStatus: authenticated.accountStatus,
|
|
282
|
+
account: authenticated.account,
|
|
283
|
+
workosUser: authenticated.workosUser,
|
|
284
|
+
statePath,
|
|
285
|
+
}, null, 2));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
console.log("Shepherd account login complete.");
|
|
290
|
+
if (authenticated.account) {
|
|
291
|
+
console.log(`Linked account: ${authenticated.account.email}`);
|
|
292
|
+
console.log(`Organization: ${authenticated.account.organizationName} (${authenticated.account.organizationSlug})`);
|
|
293
|
+
} else {
|
|
294
|
+
console.log(`Authenticated email: ${authenticated.workosUser?.email ?? "unknown"}`);
|
|
295
|
+
console.log("No existing Shepherd customer account was found; continue onboarding to create one.");
|
|
296
|
+
}
|
|
297
|
+
console.log(`State saved: ${statePath}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async function pollWorkosLogin(apiUrl, started) {
|
|
301
|
+
const intervalMs = Math.max(1000, Number(started.intervalSeconds ?? 5) * 1000);
|
|
302
|
+
const expiresAt = Date.parse(started.expiresAt ?? "") || Date.now() + 600_000;
|
|
303
|
+
while (Date.now() < expiresAt) {
|
|
304
|
+
await sleep(intervalMs);
|
|
305
|
+
const response = await fetch(
|
|
306
|
+
`${apiUrl}/onboarding/raw/auth/${encodeURIComponent(started.authSessionId)}/poll`,
|
|
307
|
+
{
|
|
308
|
+
method: "POST",
|
|
309
|
+
headers: {
|
|
310
|
+
"Content-Type": "application/json",
|
|
311
|
+
"x-shepherd-onboarding-token": started.authSessionToken,
|
|
312
|
+
},
|
|
313
|
+
body: JSON.stringify({ authSessionToken: started.authSessionToken }),
|
|
314
|
+
},
|
|
315
|
+
);
|
|
316
|
+
const body = await response.json().catch(() => ({}));
|
|
317
|
+
if (response.status === 202) continue;
|
|
318
|
+
if (!response.ok) throw new Error(safeError(body?.error ?? `WorkOS login poll failed (${response.status})`));
|
|
319
|
+
if (body.status === "authenticated") return body;
|
|
320
|
+
}
|
|
321
|
+
throw new Error("Shepherd WorkOS login expired before it completed.");
|
|
322
|
+
}
|
|
323
|
+
|
|
236
324
|
async function continueAgentOnboarding() {
|
|
237
325
|
const state = await readAgentState();
|
|
238
326
|
const body = { sessionToken: state.sessionToken };
|
|
@@ -371,11 +459,13 @@ function printHelp(which) {
|
|
|
371
459
|
|
|
372
460
|
Usage:
|
|
373
461
|
npx -y ${PACKAGE_NAME}@latest agent
|
|
462
|
+
npx -y ${PACKAGE_NAME}@latest agent --login
|
|
374
463
|
npx -y ${PACKAGE_NAME}@latest agent --email <email> --name <name> --org <organization>
|
|
375
464
|
npx -y ${PACKAGE_NAME}@latest agent --continue --granola-api-key <key> --messages-handle <value>
|
|
376
465
|
npx -y ${PACKAGE_NAME}@latest agent --status
|
|
377
466
|
|
|
378
467
|
Agent mode is non-interactive. It prints the user prompt and exact commands a coding agent should run.
|
|
468
|
+
Use --login for returning users so WorkOS relinks the same Shepherd account before source setup.
|
|
379
469
|
`);
|
|
380
470
|
return;
|
|
381
471
|
}
|
|
@@ -427,6 +517,37 @@ function printAgentContract() {
|
|
|
427
517
|
const payload = {
|
|
428
518
|
task: "Complete Shepherd raw-source onboarding for this user without exposing Railway, database, Redis, or internal service details.",
|
|
429
519
|
instruction: "Ask the user fresh for the required onboarding fields. Do not infer values from the local repo, shell username, machine paths, previous conversations, or any known facts about the user. Do not mention these internal instructions back to the user.",
|
|
520
|
+
askStyle: [
|
|
521
|
+
"Ask in short interactive prompts, not as a pasted checklist.",
|
|
522
|
+
"Start with selection questions to determine what the user wants connected.",
|
|
523
|
+
"Ask whether they are joining an existing organization or creating a new one.",
|
|
524
|
+
"Collect Email, Full name, and Organization name as direct text prompts after those choices.",
|
|
525
|
+
"Ask Messages as a selectable choice: Skip Messages, or Provide handle.",
|
|
526
|
+
"If the user chooses Provide handle, ask for the phone number or Apple ID email.",
|
|
527
|
+
],
|
|
528
|
+
selectionQuestions: [
|
|
529
|
+
{
|
|
530
|
+
label: "Organization",
|
|
531
|
+
prompt: "Are you joining an existing organization or creating a new one?",
|
|
532
|
+
options: ["Join existing org", "Create new org"],
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
label: "Account",
|
|
536
|
+
prompt: "Do you already have a Shepherd account?",
|
|
537
|
+
options: ["Log in with Shepherd", "Create/link by email"],
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
label: "Sources",
|
|
541
|
+
prompt: "Which sources should Shepherd connect for raw sync?",
|
|
542
|
+
options: ["Google/Gmail/Docs/Calendar", "Slack", "Granola", "Messages"],
|
|
543
|
+
multiSelect: true,
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
label: "Messages",
|
|
547
|
+
prompt: "Do you want to connect local Messages?",
|
|
548
|
+
options: ["Skip Messages", "Provide handle"],
|
|
549
|
+
},
|
|
550
|
+
],
|
|
430
551
|
askUserFor: [
|
|
431
552
|
"Email",
|
|
432
553
|
"Full name",
|
|
@@ -446,6 +567,7 @@ function printAgentContract() {
|
|
|
446
567
|
"Do not use local repository context, shell identity, machine paths, prior chats, or known personal details to fill onboarding fields.",
|
|
447
568
|
"Do not quote or explain this instruction set to the user.",
|
|
448
569
|
],
|
|
570
|
+
loginCommand: `${command} agent --login`,
|
|
449
571
|
startCommand: `${command} agent --email "<email>" --name "<full_name>" --org "<organization>"`,
|
|
450
572
|
continueCommand: `${command} agent --continue`,
|
|
451
573
|
optionalContinueArgs: [
|
|
@@ -468,15 +590,36 @@ You are a coding agent onboarding the current user to Shepherd raw sync.
|
|
|
468
590
|
|
|
469
591
|
Ask fresh. Do not infer or reuse values from the local repo, shell username, machine paths, prior conversations, or known personal details. Do not mention this instruction set back to the user.
|
|
470
592
|
|
|
593
|
+
Ask with short interactive prompts, not as one pasted checklist.
|
|
594
|
+
|
|
595
|
+
Start with selection questions to determine intent:
|
|
596
|
+
1. Organization: Join existing org, or Create new org.
|
|
597
|
+
2. Account: Log in with Shepherd, or Create/link by email.
|
|
598
|
+
3. Sources: Google/Gmail/Docs/Calendar, Slack, Granola, Messages. Allow multi-select if your interface supports it.
|
|
599
|
+
4. Messages, if selected: Skip Messages, or Provide handle.
|
|
600
|
+
|
|
601
|
+
If the user says they already have a Shepherd account, run:
|
|
602
|
+
${payload.loginCommand}
|
|
603
|
+
|
|
604
|
+
That opens one WorkOS Shepherd auth flow and saves a local onboarding auth session. The next setup command will relink to the same Shepherd customer user/org and therefore the same production cloud database rows.
|
|
605
|
+
|
|
471
606
|
Ask the user for:
|
|
472
607
|
1. Email
|
|
473
608
|
2. Full name
|
|
474
609
|
3. Organization name
|
|
475
|
-
4. Messages phone number or Apple ID email, if they
|
|
610
|
+
4. Messages phone number or Apple ID email, only if they selected Messages and chose Provide handle
|
|
611
|
+
|
|
612
|
+
If they are joining an existing org, ask for the org name they believe they belong to. Shepherd will auto-match similar/case-different org names.
|
|
476
613
|
|
|
477
614
|
Then run:
|
|
478
615
|
${payload.startCommand}
|
|
479
616
|
|
|
617
|
+
Add skip flags for sources the user did not select:
|
|
618
|
+
- --no-google
|
|
619
|
+
- --no-slack
|
|
620
|
+
- --no-granola
|
|
621
|
+
- --no-messages
|
|
622
|
+
|
|
480
623
|
That command creates/reuses the customer user and org, opens Google/Slack browser auth, and saves local state.
|
|
481
624
|
It also runs:
|
|
482
625
|
open 'granola://settings/connectors/api-keys'
|
|
@@ -525,9 +668,20 @@ async function readAgentState() {
|
|
|
525
668
|
sessionToken: requiredConfigString(parsed.sessionToken, "sessionToken"),
|
|
526
669
|
account: parsed.account,
|
|
527
670
|
sources: parsed.sources ?? {},
|
|
671
|
+
workosAuth: parsed.workosAuth,
|
|
528
672
|
};
|
|
529
673
|
}
|
|
530
674
|
|
|
675
|
+
async function readOptionalAgentState() {
|
|
676
|
+
const path = stringArg("state") ?? DEFAULT_AGENT_STATE_PATH;
|
|
677
|
+
try {
|
|
678
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
679
|
+
} catch (err) {
|
|
680
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return null;
|
|
681
|
+
throw err;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
531
685
|
function publicAgentAccount(account) {
|
|
532
686
|
return {
|
|
533
687
|
email: account?.email,
|