shepherd-onboard 0.1.4 → 0.1.6
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 +8 -7
- package/bin/shepherd-onboard.js +94 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,8 +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
|
|
14
|
-
The agent prompt tells coding agents to ask short selection questions first: existing/new org,
|
|
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.
|
|
15
15
|
|
|
16
16
|
## Human Terminal One-liner
|
|
17
17
|
|
|
@@ -21,13 +21,14 @@ npx -y shepherd-onboard@latest
|
|
|
21
21
|
|
|
22
22
|
The command:
|
|
23
23
|
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
- creates or reuses the Shepherd customer account
|
|
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
|
|
27
27
|
- creates or reuses the organization, including case-insensitive and close-name matches
|
|
28
28
|
- opens Google authorization for Gmail, Docs, and Calendar consent
|
|
29
29
|
- opens Slack authorization
|
|
30
|
-
- opens Granola
|
|
30
|
+
- opens the Granola desktop app with `open -b com.granola.app`
|
|
31
|
+
- directs the coding agent/user to Granola Settings -> Connectors -> API keys
|
|
31
32
|
- collects the Granola API key after opening the Granola screen when Granola is enabled
|
|
32
33
|
- sets up local macOS Messages raw sync with a background LaunchAgent
|
|
33
34
|
- starts raw polling/backfill for connected sources
|
|
@@ -38,7 +39,7 @@ The command does not expose Railway, database, Redis, or internal service detail
|
|
|
38
39
|
## Options
|
|
39
40
|
|
|
40
41
|
```sh
|
|
41
|
-
--email <email>
|
|
42
|
+
--email <email> Advanced: must match the WorkOS-authenticated email
|
|
42
43
|
--name <name> Full name
|
|
43
44
|
--org <name> Organization name
|
|
44
45
|
--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
|
|
|
@@ -164,11 +169,6 @@ async function runAgentOnboarding() {
|
|
|
164
169
|
return;
|
|
165
170
|
}
|
|
166
171
|
|
|
167
|
-
if (!hasIdentityArgs()) {
|
|
168
|
-
printAgentContract();
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
172
|
const apiUrl = trimTrailingSlash(args.api ?? DEFAULT_API_URL);
|
|
173
173
|
const noOpen = Boolean(args["no-open"]);
|
|
174
174
|
const sources = selectedSources();
|
|
@@ -176,16 +176,37 @@ async function runAgentOnboarding() {
|
|
|
176
176
|
const workosAuth = existingState?.workosAuth?.status === "authenticated"
|
|
177
177
|
? existingState.workosAuth
|
|
178
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
|
+
|
|
179
204
|
const session = await postJson(`${apiUrl}/onboarding/raw/session`, {
|
|
180
|
-
email
|
|
181
|
-
name
|
|
182
|
-
organizationName
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
authSessionId: workosAuth.authSessionId,
|
|
186
|
-
authSessionToken: workosAuth.authSessionToken,
|
|
187
|
-
}
|
|
188
|
-
: {}),
|
|
205
|
+
email,
|
|
206
|
+
name,
|
|
207
|
+
organizationName,
|
|
208
|
+
authSessionId: workosAuth.authSessionId,
|
|
209
|
+
authSessionToken: workosAuth.authSessionToken,
|
|
189
210
|
sources,
|
|
190
211
|
});
|
|
191
212
|
|
|
@@ -252,14 +273,7 @@ async function runAgentOnboarding() {
|
|
|
252
273
|
async function loginAgentWithWorkos() {
|
|
253
274
|
const apiUrl = trimTrailingSlash(args.api ?? DEFAULT_API_URL);
|
|
254
275
|
const noOpen = Boolean(args["no-open"]);
|
|
255
|
-
const started = await
|
|
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);
|
|
276
|
+
const { started, authenticated } = await runWorkosLogin(apiUrl, noOpen);
|
|
263
277
|
const previous = await readOptionalAgentState();
|
|
264
278
|
const statePath = await writeAgentState({
|
|
265
279
|
...(previous ?? {}),
|
|
@@ -297,6 +311,18 @@ async function loginAgentWithWorkos() {
|
|
|
297
311
|
console.log(`State saved: ${statePath}`);
|
|
298
312
|
}
|
|
299
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
|
+
|
|
300
326
|
async function pollWorkosLogin(apiUrl, started) {
|
|
301
327
|
const intervalMs = Math.max(1000, Number(started.intervalSeconds ?? 5) * 1000);
|
|
302
328
|
const expiresAt = Date.parse(started.expiresAt ?? "") || Date.now() + 600_000;
|
|
@@ -460,12 +486,12 @@ function printHelp(which) {
|
|
|
460
486
|
Usage:
|
|
461
487
|
npx -y ${PACKAGE_NAME}@latest agent
|
|
462
488
|
npx -y ${PACKAGE_NAME}@latest agent --login
|
|
463
|
-
npx -y ${PACKAGE_NAME}@latest agent --
|
|
489
|
+
npx -y ${PACKAGE_NAME}@latest agent --name <name> --org <organization>
|
|
464
490
|
npx -y ${PACKAGE_NAME}@latest agent --continue --granola-api-key <key> --messages-handle <value>
|
|
465
491
|
npx -y ${PACKAGE_NAME}@latest agent --status
|
|
466
492
|
|
|
467
493
|
Agent mode is non-interactive. It prints the user prompt and exact commands a coding agent should run.
|
|
468
|
-
|
|
494
|
+
Always run --login first. WorkOS login/signup creates or relinks the Shepherd account before source setup.
|
|
469
495
|
`);
|
|
470
496
|
return;
|
|
471
497
|
}
|
|
@@ -492,7 +518,7 @@ Usage:
|
|
|
492
518
|
npx -y ${PACKAGE_NAME}@latest agent
|
|
493
519
|
|
|
494
520
|
Options:
|
|
495
|
-
--email <email>
|
|
521
|
+
--email <email> Advanced: must match the WorkOS-authenticated email.
|
|
496
522
|
--name <name> Full name.
|
|
497
523
|
--org <name> Organization name.
|
|
498
524
|
--granola-api-key <key> Granola API key.
|
|
@@ -521,7 +547,8 @@ function printAgentContract() {
|
|
|
521
547
|
"Ask in short interactive prompts, not as a pasted checklist.",
|
|
522
548
|
"Start with selection questions to determine what the user wants connected.",
|
|
523
549
|
"Ask whether they are joining an existing organization or creating a new one.",
|
|
524
|
-
"
|
|
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.",
|
|
525
552
|
"Ask Messages as a selectable choice: Skip Messages, or Provide handle.",
|
|
526
553
|
"If the user chooses Provide handle, ask for the phone number or Apple ID email.",
|
|
527
554
|
],
|
|
@@ -531,11 +558,6 @@ function printAgentContract() {
|
|
|
531
558
|
prompt: "Are you joining an existing organization or creating a new one?",
|
|
532
559
|
options: ["Join existing org", "Create new org"],
|
|
533
560
|
},
|
|
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
561
|
{
|
|
540
562
|
label: "Sources",
|
|
541
563
|
prompt: "Which sources should Shepherd connect for raw sync?",
|
|
@@ -549,7 +571,6 @@ function printAgentContract() {
|
|
|
549
571
|
},
|
|
550
572
|
],
|
|
551
573
|
askUserFor: [
|
|
552
|
-
"Email",
|
|
553
574
|
"Full name",
|
|
554
575
|
"Organization name",
|
|
555
576
|
"Messages phone number or Apple ID email, if they want local Messages connected",
|
|
@@ -568,7 +589,7 @@ function printAgentContract() {
|
|
|
568
589
|
"Do not quote or explain this instruction set to the user.",
|
|
569
590
|
],
|
|
570
591
|
loginCommand: `${command} agent --login`,
|
|
571
|
-
startCommand: `${command} agent --
|
|
592
|
+
startCommand: `${command} agent --name "<full_name>" --org "<organization>"`,
|
|
572
593
|
continueCommand: `${command} agent --continue`,
|
|
573
594
|
optionalContinueArgs: [
|
|
574
595
|
"--messages-handle \"<phone_or_apple_id>\" if local Messages is being connected",
|
|
@@ -576,7 +597,8 @@ function printAgentContract() {
|
|
|
576
597
|
],
|
|
577
598
|
statusCommand: `${command} agent --status`,
|
|
578
599
|
expectedResult: "Cloud sources start raw polling/backfill in the customer-facing Shepherd environment. Local Messages starts via a macOS LaunchAgent when run on macOS. Downstream wiki, memory, and summary compilers remain outside this onboarding flow.",
|
|
579
|
-
granolaApiKeyCommand: "open
|
|
600
|
+
granolaApiKeyCommand: "open -b com.granola.app",
|
|
601
|
+
granolaApiKeyPath: "Granola desktop app -> Settings -> Connectors -> API keys",
|
|
580
602
|
};
|
|
581
603
|
|
|
582
604
|
if (args.json) {
|
|
@@ -594,20 +616,20 @@ Ask with short interactive prompts, not as one pasted checklist.
|
|
|
594
616
|
|
|
595
617
|
Start with selection questions to determine intent:
|
|
596
618
|
1. Organization: Join existing org, or Create new org.
|
|
597
|
-
2.
|
|
598
|
-
3.
|
|
599
|
-
4. Messages, if selected: Skip Messages, or Provide handle.
|
|
619
|
+
2. Sources: Google/Gmail/Docs/Calendar, Slack, Granola, Messages. Allow multi-select if your interface supports it.
|
|
620
|
+
3. Messages, if selected: Skip Messages, or Provide handle.
|
|
600
621
|
|
|
601
|
-
|
|
622
|
+
Before source setup, always run:
|
|
602
623
|
${payload.loginCommand}
|
|
603
624
|
|
|
604
|
-
That opens one WorkOS Shepherd
|
|
625
|
+
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.
|
|
605
626
|
|
|
606
627
|
Ask the user for:
|
|
607
|
-
1.
|
|
608
|
-
2.
|
|
609
|
-
3.
|
|
610
|
-
|
|
628
|
+
1. Full name
|
|
629
|
+
2. Organization name
|
|
630
|
+
3. Messages phone number or Apple ID email, only if they selected Messages and chose Provide handle
|
|
631
|
+
|
|
632
|
+
Do not ask for their email separately. Use the email returned by WorkOS auth.
|
|
611
633
|
|
|
612
634
|
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.
|
|
613
635
|
|
|
@@ -621,9 +643,13 @@ Add skip flags for sources the user did not select:
|
|
|
621
643
|
- --no-messages
|
|
622
644
|
|
|
623
645
|
That command creates/reuses the customer user and org, opens Google/Slack browser auth, and saves local state.
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
646
|
+
If Granola is selected, it also opens the Granola desktop app. Use local computer control to navigate Granola to:
|
|
647
|
+
Settings -> Connectors -> API keys
|
|
648
|
+
Then have the user create/copy the API key.
|
|
649
|
+
|
|
650
|
+
If Granola did not come forward, run:
|
|
651
|
+
${payload.granolaApiKeyCommand}
|
|
652
|
+
Then navigate the visible Granola app to Settings -> Connectors -> API keys.
|
|
627
653
|
|
|
628
654
|
After Google/Gmail/Docs/Calendar and Slack browser auth is complete, and after the user has copied a Granola API key from the opened Granola screen if they want Granola, run:
|
|
629
655
|
${payload.continueCommand} --messages-handle "<phone_or_apple_id>" --granola-api-key "<granola_key>"
|
|
@@ -692,6 +718,14 @@ function publicAgentAccount(account) {
|
|
|
692
718
|
};
|
|
693
719
|
}
|
|
694
720
|
|
|
721
|
+
function authenticatedEmail(authenticated) {
|
|
722
|
+
return authenticated?.workosUser?.email ?? authenticated?.account?.email ?? null;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function authenticatedName(authenticated) {
|
|
726
|
+
return authenticated?.workosUser?.name ?? authenticated?.account?.name ?? null;
|
|
727
|
+
}
|
|
728
|
+
|
|
695
729
|
function agentNeedsUserAction(sources, opened) {
|
|
696
730
|
const actions = [];
|
|
697
731
|
if (sources.google && opened.includes("google")) actions.push("Complete Google browser authorization for Gmail, Docs, and Calendar consent.");
|
|
@@ -811,8 +845,8 @@ async function openOrPrint(url, opts) {
|
|
|
811
845
|
async function openGranolaApiKeys(opts = {}) {
|
|
812
846
|
const deepLink = "granola://settings/connectors/api-keys";
|
|
813
847
|
if (opts.noOpen) {
|
|
814
|
-
console.log(
|
|
815
|
-
return { opened: false, target:
|
|
848
|
+
console.log("Granola API keys: open the Granola desktop app -> Settings -> Connectors -> API keys");
|
|
849
|
+
return { opened: false, target: "Granola Settings -> Connectors -> API keys" };
|
|
816
850
|
}
|
|
817
851
|
|
|
818
852
|
if (platform() !== "darwin") {
|
|
@@ -821,21 +855,25 @@ async function openGranolaApiKeys(opts = {}) {
|
|
|
821
855
|
}
|
|
822
856
|
|
|
823
857
|
console.log("\nOpening Granola API keys");
|
|
824
|
-
const
|
|
825
|
-
await sleep(
|
|
826
|
-
|
|
858
|
+
const bundleResult = await execFileQuiet("open", ["-b", "com.granola.app"], { ignoreError: true, captureError: true });
|
|
859
|
+
await sleep(500);
|
|
860
|
+
await execFileQuiet("open", [deepLink], { ignoreError: true, captureError: true });
|
|
861
|
+
await sleep(500);
|
|
862
|
+
const activateByBundleResult = await execFileQuiet("osascript", [
|
|
827
863
|
"-e",
|
|
828
864
|
'tell application id "com.granola.app" to activate',
|
|
829
865
|
], { ignoreError: true, captureError: true });
|
|
866
|
+
const activateByNameResult = await execFileQuiet("open", ["-a", "Granola"], { ignoreError: true, captureError: true });
|
|
830
867
|
|
|
831
|
-
if (
|
|
868
|
+
if (bundleResult.error && activateByBundleResult.error && activateByNameResult.error) {
|
|
832
869
|
await execFileQuiet("open", ["-a", "Granola"], { ignoreError: true });
|
|
833
870
|
}
|
|
834
871
|
|
|
835
872
|
return {
|
|
836
873
|
opened: true,
|
|
837
|
-
target:
|
|
838
|
-
|
|
874
|
+
target: "Granola Settings -> Connectors -> API keys",
|
|
875
|
+
attemptedDeepLink: deepLink,
|
|
876
|
+
fallback: "Use local computer control to navigate Granola to Settings -> Connectors -> API keys.",
|
|
839
877
|
};
|
|
840
878
|
}
|
|
841
879
|
|