shepherd-onboard 0.1.2 → 0.1.5
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 +6 -4
- package/bin/shepherd-onboard.js +200 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,8 @@ Give this to a coding agent:
|
|
|
10
10
|
npx -y shepherd-onboard@latest agent
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The command prints the exact prompt the agent should
|
|
13
|
+
The command prints the exact prompt the agent should follow, then the exact follow-up commands to open Shepherd WorkOS login/signup, open source 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, sources to connect, and Messages skip/provide-handle. Account creation/relinking always starts with Shepherd WorkOS auth.
|
|
14
15
|
|
|
15
16
|
## Human Terminal One-liner
|
|
16
17
|
|
|
@@ -20,8 +21,9 @@ npx -y shepherd-onboard@latest
|
|
|
20
21
|
|
|
21
22
|
The command:
|
|
22
23
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
24
|
+
- runs Shepherd WorkOS login/signup first
|
|
25
|
+
- asks for name, organization, and an optional local Messages handle
|
|
26
|
+
- creates or reuses the Shepherd customer account from the WorkOS-authenticated 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
|
|
27
29
|
- opens Slack authorization
|
|
@@ -36,7 +38,7 @@ The command does not expose Railway, database, Redis, or internal service detail
|
|
|
36
38
|
## Options
|
|
37
39
|
|
|
38
40
|
```sh
|
|
39
|
-
--email <email>
|
|
41
|
+
--email <email> Advanced: must match the WorkOS-authenticated email
|
|
40
42
|
--name <name> Full name
|
|
41
43
|
--org <name> Organization name
|
|
42
44
|
--granola-api-key <key> Granola API key
|
package/bin/shepherd-onboard.js
CHANGED
|
@@ -50,8 +50,11 @@ async function runOnboarding() {
|
|
|
50
50
|
|
|
51
51
|
console.log("\nShepherd Raw Sync Onboarding\n");
|
|
52
52
|
|
|
53
|
-
const
|
|
54
|
-
const
|
|
53
|
+
const workosLogin = await runWorkosLogin(apiUrl, noOpen);
|
|
54
|
+
const email = authenticatedEmail(workosLogin.authenticated);
|
|
55
|
+
if (!email) throw new Error("Shepherd WorkOS auth did not return an email address.");
|
|
56
|
+
|
|
57
|
+
const name = stringArg("name") ?? authenticatedName(workosLogin.authenticated) ?? await valueOrPrompt("name", "Full name");
|
|
55
58
|
const organizationName = await valueOrPrompt("org", "Organization name");
|
|
56
59
|
|
|
57
60
|
const sources = {
|
|
@@ -65,6 +68,8 @@ async function runOnboarding() {
|
|
|
65
68
|
email,
|
|
66
69
|
name,
|
|
67
70
|
organizationName,
|
|
71
|
+
authSessionId: workosLogin.started.authSessionId,
|
|
72
|
+
authSessionToken: workosLogin.started.authSessionToken,
|
|
68
73
|
sources,
|
|
69
74
|
});
|
|
70
75
|
|
|
@@ -154,23 +159,54 @@ async function runAgentOnboarding() {
|
|
|
154
159
|
return;
|
|
155
160
|
}
|
|
156
161
|
|
|
157
|
-
if (args.
|
|
158
|
-
await
|
|
162
|
+
if (args.login) {
|
|
163
|
+
await loginAgentWithWorkos();
|
|
159
164
|
return;
|
|
160
165
|
}
|
|
161
166
|
|
|
162
|
-
if (
|
|
163
|
-
|
|
167
|
+
if (args.continue || args.resume) {
|
|
168
|
+
await continueAgentOnboarding();
|
|
164
169
|
return;
|
|
165
170
|
}
|
|
166
171
|
|
|
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;
|
|
179
|
+
const wantsStart = Boolean(
|
|
180
|
+
stringArg("name")
|
|
181
|
+
|| stringArg("org")
|
|
182
|
+
|| stringArg("email")
|
|
183
|
+
|| args["no-google"]
|
|
184
|
+
|| args["no-slack"]
|
|
185
|
+
|| args["no-granola"]
|
|
186
|
+
|| args["no-messages"]
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (!wantsStart) {
|
|
190
|
+
printAgentContract();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!workosAuth) {
|
|
194
|
+
throw new Error(`Run ${agentCommand()} agent --login first so Shepherd can create or relink the WorkOS account.`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const email = stringArg("email") ?? workosAuth.workosUser?.email ?? workosAuth.account?.email;
|
|
198
|
+
const name = stringArg("name") ?? workosAuth.workosUser?.name ?? workosAuth.account?.name;
|
|
199
|
+
const organizationName = stringArg("org") ?? workosAuth.account?.organizationName;
|
|
200
|
+
if (!email) throw new Error("WorkOS login did not return an email. Re-run agent --login.");
|
|
201
|
+
if (!name) throw new Error("Full name is required. Pass --name \"<full_name>\".");
|
|
202
|
+
if (!organizationName) throw new Error("Organization name is required. Pass --org \"<organization>\".");
|
|
203
|
+
|
|
170
204
|
const session = await postJson(`${apiUrl}/onboarding/raw/session`, {
|
|
171
|
-
email
|
|
172
|
-
name
|
|
173
|
-
organizationName
|
|
205
|
+
email,
|
|
206
|
+
name,
|
|
207
|
+
organizationName,
|
|
208
|
+
authSessionId: workosAuth.authSessionId,
|
|
209
|
+
authSessionToken: workosAuth.authSessionToken,
|
|
174
210
|
sources,
|
|
175
211
|
});
|
|
176
212
|
|
|
@@ -181,6 +217,7 @@ async function runAgentOnboarding() {
|
|
|
181
217
|
account: session.account,
|
|
182
218
|
sources,
|
|
183
219
|
authUrls: session.authUrls ?? {},
|
|
220
|
+
workosAuth,
|
|
184
221
|
createdAt: new Date().toISOString(),
|
|
185
222
|
});
|
|
186
223
|
|
|
@@ -233,6 +270,83 @@ async function runAgentOnboarding() {
|
|
|
233
270
|
console.log(" Omit either optional flag if that source is not being connected.");
|
|
234
271
|
}
|
|
235
272
|
|
|
273
|
+
async function loginAgentWithWorkos() {
|
|
274
|
+
const apiUrl = trimTrailingSlash(args.api ?? DEFAULT_API_URL);
|
|
275
|
+
const noOpen = Boolean(args["no-open"]);
|
|
276
|
+
const { started, authenticated } = await runWorkosLogin(apiUrl, noOpen);
|
|
277
|
+
const previous = await readOptionalAgentState();
|
|
278
|
+
const statePath = await writeAgentState({
|
|
279
|
+
...(previous ?? {}),
|
|
280
|
+
apiUrl,
|
|
281
|
+
workosAuth: {
|
|
282
|
+
status: "authenticated",
|
|
283
|
+
authSessionId: started.authSessionId,
|
|
284
|
+
authSessionToken: started.authSessionToken,
|
|
285
|
+
workosUser: authenticated.workosUser,
|
|
286
|
+
accountStatus: authenticated.accountStatus,
|
|
287
|
+
account: authenticated.account,
|
|
288
|
+
authenticatedAt: new Date().toISOString(),
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
if (args.json) {
|
|
293
|
+
console.log(JSON.stringify({
|
|
294
|
+
status: "authenticated",
|
|
295
|
+
accountStatus: authenticated.accountStatus,
|
|
296
|
+
account: authenticated.account,
|
|
297
|
+
workosUser: authenticated.workosUser,
|
|
298
|
+
statePath,
|
|
299
|
+
}, null, 2));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
console.log("Shepherd account login complete.");
|
|
304
|
+
if (authenticated.account) {
|
|
305
|
+
console.log(`Linked account: ${authenticated.account.email}`);
|
|
306
|
+
console.log(`Organization: ${authenticated.account.organizationName} (${authenticated.account.organizationSlug})`);
|
|
307
|
+
} else {
|
|
308
|
+
console.log(`Authenticated email: ${authenticated.workosUser?.email ?? "unknown"}`);
|
|
309
|
+
console.log("No existing Shepherd customer account was found; continue onboarding to create one.");
|
|
310
|
+
}
|
|
311
|
+
console.log(`State saved: ${statePath}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function runWorkosLogin(apiUrl, noOpen) {
|
|
315
|
+
const started = await postJson(`${apiUrl}/onboarding/raw/auth/start`, {});
|
|
316
|
+
|
|
317
|
+
console.log("\nShepherd account login");
|
|
318
|
+
console.log("Opening Shepherd WorkOS auth. Complete login/signup in the browser.");
|
|
319
|
+
await openOrPrint(started.verificationUriComplete ?? started.verificationUri, { noOpen });
|
|
320
|
+
if (noOpen && started.userCode) console.log(`User code: ${started.userCode}`);
|
|
321
|
+
|
|
322
|
+
const authenticated = await pollWorkosLogin(apiUrl, started);
|
|
323
|
+
return { started, authenticated };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async function pollWorkosLogin(apiUrl, started) {
|
|
327
|
+
const intervalMs = Math.max(1000, Number(started.intervalSeconds ?? 5) * 1000);
|
|
328
|
+
const expiresAt = Date.parse(started.expiresAt ?? "") || Date.now() + 600_000;
|
|
329
|
+
while (Date.now() < expiresAt) {
|
|
330
|
+
await sleep(intervalMs);
|
|
331
|
+
const response = await fetch(
|
|
332
|
+
`${apiUrl}/onboarding/raw/auth/${encodeURIComponent(started.authSessionId)}/poll`,
|
|
333
|
+
{
|
|
334
|
+
method: "POST",
|
|
335
|
+
headers: {
|
|
336
|
+
"Content-Type": "application/json",
|
|
337
|
+
"x-shepherd-onboarding-token": started.authSessionToken,
|
|
338
|
+
},
|
|
339
|
+
body: JSON.stringify({ authSessionToken: started.authSessionToken }),
|
|
340
|
+
},
|
|
341
|
+
);
|
|
342
|
+
const body = await response.json().catch(() => ({}));
|
|
343
|
+
if (response.status === 202) continue;
|
|
344
|
+
if (!response.ok) throw new Error(safeError(body?.error ?? `WorkOS login poll failed (${response.status})`));
|
|
345
|
+
if (body.status === "authenticated") return body;
|
|
346
|
+
}
|
|
347
|
+
throw new Error("Shepherd WorkOS login expired before it completed.");
|
|
348
|
+
}
|
|
349
|
+
|
|
236
350
|
async function continueAgentOnboarding() {
|
|
237
351
|
const state = await readAgentState();
|
|
238
352
|
const body = { sessionToken: state.sessionToken };
|
|
@@ -371,11 +485,13 @@ function printHelp(which) {
|
|
|
371
485
|
|
|
372
486
|
Usage:
|
|
373
487
|
npx -y ${PACKAGE_NAME}@latest agent
|
|
374
|
-
npx -y ${PACKAGE_NAME}@latest agent --
|
|
488
|
+
npx -y ${PACKAGE_NAME}@latest agent --login
|
|
489
|
+
npx -y ${PACKAGE_NAME}@latest agent --name <name> --org <organization>
|
|
375
490
|
npx -y ${PACKAGE_NAME}@latest agent --continue --granola-api-key <key> --messages-handle <value>
|
|
376
491
|
npx -y ${PACKAGE_NAME}@latest agent --status
|
|
377
492
|
|
|
378
493
|
Agent mode is non-interactive. It prints the user prompt and exact commands a coding agent should run.
|
|
494
|
+
Always run --login first. WorkOS login/signup creates or relinks the Shepherd account before source setup.
|
|
379
495
|
`);
|
|
380
496
|
return;
|
|
381
497
|
}
|
|
@@ -402,7 +518,7 @@ Usage:
|
|
|
402
518
|
npx -y ${PACKAGE_NAME}@latest agent
|
|
403
519
|
|
|
404
520
|
Options:
|
|
405
|
-
--email <email>
|
|
521
|
+
--email <email> Advanced: must match the WorkOS-authenticated email.
|
|
406
522
|
--name <name> Full name.
|
|
407
523
|
--org <name> Organization name.
|
|
408
524
|
--granola-api-key <key> Granola API key.
|
|
@@ -427,8 +543,34 @@ function printAgentContract() {
|
|
|
427
543
|
const payload = {
|
|
428
544
|
task: "Complete Shepherd raw-source onboarding for this user without exposing Railway, database, Redis, or internal service details.",
|
|
429
545
|
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.",
|
|
546
|
+
askStyle: [
|
|
547
|
+
"Ask in short interactive prompts, not as a pasted checklist.",
|
|
548
|
+
"Start with selection questions to determine what the user wants connected.",
|
|
549
|
+
"Ask whether they are joining an existing organization or creating a new one.",
|
|
550
|
+
"Run Shepherd WorkOS login/signup before source setup. Do not ask whether they already have an account.",
|
|
551
|
+
"Collect Full name and Organization name as direct text prompts after those choices. The email comes from WorkOS auth.",
|
|
552
|
+
"Ask Messages as a selectable choice: Skip Messages, or Provide handle.",
|
|
553
|
+
"If the user chooses Provide handle, ask for the phone number or Apple ID email.",
|
|
554
|
+
],
|
|
555
|
+
selectionQuestions: [
|
|
556
|
+
{
|
|
557
|
+
label: "Organization",
|
|
558
|
+
prompt: "Are you joining an existing organization or creating a new one?",
|
|
559
|
+
options: ["Join existing org", "Create new org"],
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
label: "Sources",
|
|
563
|
+
prompt: "Which sources should Shepherd connect for raw sync?",
|
|
564
|
+
options: ["Google/Gmail/Docs/Calendar", "Slack", "Granola", "Messages"],
|
|
565
|
+
multiSelect: true,
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
label: "Messages",
|
|
569
|
+
prompt: "Do you want to connect local Messages?",
|
|
570
|
+
options: ["Skip Messages", "Provide handle"],
|
|
571
|
+
},
|
|
572
|
+
],
|
|
430
573
|
askUserFor: [
|
|
431
|
-
"Email",
|
|
432
574
|
"Full name",
|
|
433
575
|
"Organization name",
|
|
434
576
|
"Messages phone number or Apple ID email, if they want local Messages connected",
|
|
@@ -446,7 +588,8 @@ function printAgentContract() {
|
|
|
446
588
|
"Do not use local repository context, shell identity, machine paths, prior chats, or known personal details to fill onboarding fields.",
|
|
447
589
|
"Do not quote or explain this instruction set to the user.",
|
|
448
590
|
],
|
|
449
|
-
|
|
591
|
+
loginCommand: `${command} agent --login`,
|
|
592
|
+
startCommand: `${command} agent --name "<full_name>" --org "<organization>"`,
|
|
450
593
|
continueCommand: `${command} agent --continue`,
|
|
451
594
|
optionalContinueArgs: [
|
|
452
595
|
"--messages-handle \"<phone_or_apple_id>\" if local Messages is being connected",
|
|
@@ -468,15 +611,36 @@ You are a coding agent onboarding the current user to Shepherd raw sync.
|
|
|
468
611
|
|
|
469
612
|
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
613
|
|
|
614
|
+
Ask with short interactive prompts, not as one pasted checklist.
|
|
615
|
+
|
|
616
|
+
Start with selection questions to determine intent:
|
|
617
|
+
1. Organization: Join existing org, or Create new org.
|
|
618
|
+
2. Sources: Google/Gmail/Docs/Calendar, Slack, Granola, Messages. Allow multi-select if your interface supports it.
|
|
619
|
+
3. Messages, if selected: Skip Messages, or Provide handle.
|
|
620
|
+
|
|
621
|
+
Before source setup, always run:
|
|
622
|
+
${payload.loginCommand}
|
|
623
|
+
|
|
624
|
+
That opens one WorkOS Shepherd login/signup flow and saves a local onboarding auth session. It creates or relinks the Shepherd customer account; the next setup command attaches sources to the same production cloud account rows.
|
|
625
|
+
|
|
471
626
|
Ask the user for:
|
|
472
|
-
1.
|
|
473
|
-
2.
|
|
474
|
-
3.
|
|
475
|
-
|
|
627
|
+
1. Full name
|
|
628
|
+
2. Organization name
|
|
629
|
+
3. Messages phone number or Apple ID email, only if they selected Messages and chose Provide handle
|
|
630
|
+
|
|
631
|
+
Do not ask for their email separately. Use the email returned by WorkOS auth.
|
|
632
|
+
|
|
633
|
+
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
634
|
|
|
477
635
|
Then run:
|
|
478
636
|
${payload.startCommand}
|
|
479
637
|
|
|
638
|
+
Add skip flags for sources the user did not select:
|
|
639
|
+
- --no-google
|
|
640
|
+
- --no-slack
|
|
641
|
+
- --no-granola
|
|
642
|
+
- --no-messages
|
|
643
|
+
|
|
480
644
|
That command creates/reuses the customer user and org, opens Google/Slack browser auth, and saves local state.
|
|
481
645
|
It also runs:
|
|
482
646
|
open 'granola://settings/connectors/api-keys'
|
|
@@ -525,9 +689,20 @@ async function readAgentState() {
|
|
|
525
689
|
sessionToken: requiredConfigString(parsed.sessionToken, "sessionToken"),
|
|
526
690
|
account: parsed.account,
|
|
527
691
|
sources: parsed.sources ?? {},
|
|
692
|
+
workosAuth: parsed.workosAuth,
|
|
528
693
|
};
|
|
529
694
|
}
|
|
530
695
|
|
|
696
|
+
async function readOptionalAgentState() {
|
|
697
|
+
const path = stringArg("state") ?? DEFAULT_AGENT_STATE_PATH;
|
|
698
|
+
try {
|
|
699
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
700
|
+
} catch (err) {
|
|
701
|
+
if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") return null;
|
|
702
|
+
throw err;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
531
706
|
function publicAgentAccount(account) {
|
|
532
707
|
return {
|
|
533
708
|
email: account?.email,
|
|
@@ -538,6 +713,14 @@ function publicAgentAccount(account) {
|
|
|
538
713
|
};
|
|
539
714
|
}
|
|
540
715
|
|
|
716
|
+
function authenticatedEmail(authenticated) {
|
|
717
|
+
return authenticated?.workosUser?.email ?? authenticated?.account?.email ?? null;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function authenticatedName(authenticated) {
|
|
721
|
+
return authenticated?.workosUser?.name ?? authenticated?.account?.name ?? null;
|
|
722
|
+
}
|
|
723
|
+
|
|
541
724
|
function agentNeedsUserAction(sources, opened) {
|
|
542
725
|
const actions = [];
|
|
543
726
|
if (sources.google && opened.includes("google")) actions.push("Complete Google browser authorization for Gmail, Docs, and Calendar consent.");
|