askshepherd 0.1.44 → 0.1.45

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/bin/shepherd.js CHANGED
@@ -12,6 +12,7 @@ const DEFAULT_STATE_PATH = join(homedir(), ".shepherd", "mcp.json");
12
12
  const DEFAULT_AGENT_STATE_PATH = join(homedir(), ".shepherd", "raw-onboarding-agent.json");
13
13
  const PACKAGE_SPEC = "askshepherd@latest";
14
14
  const PUBLIC_COMMAND = `npx -y ${PACKAGE_SPEC}`;
15
+ const SKILL_INSTALL_TARGETS = ["codex", "claude"];
15
16
  const MCP_ENVIRONMENT_TARGETS = {
16
17
  deploy: {
17
18
  label: "Customer deploy",
@@ -94,6 +95,16 @@ async function dispatch() {
94
95
  return;
95
96
  }
96
97
 
98
+ if (command === "guide" || command === "workflow") {
99
+ await runGuide();
100
+ return;
101
+ }
102
+
103
+ if (command === "troubleshoot" || command === "doctor") {
104
+ await runTroubleshoot();
105
+ return;
106
+ }
107
+
97
108
  if (command === "onboard") {
98
109
  await runOnboardCommand();
99
110
  return;
@@ -215,6 +226,468 @@ async function runCall() {
215
226
  await runToolByName(toolName);
216
227
  }
217
228
 
229
+ async function runGuide() {
230
+ const payload = await buildGuidePayload();
231
+ if (args.json) {
232
+ console.log(JSON.stringify(payload, null, 2));
233
+ return;
234
+ }
235
+ console.log(renderGuide(payload));
236
+ }
237
+
238
+ async function runTroubleshoot() {
239
+ const payload = await buildTroubleshootPayload();
240
+ if (args.json) {
241
+ console.log(JSON.stringify(payload, null, 2));
242
+ return;
243
+ }
244
+ console.log(renderTroubleshoot(payload));
245
+ }
246
+
247
+ async function buildGuidePayload() {
248
+ const [agentPrompt, statusResult] = await Promise.all([
249
+ loadEngineAgentPrompt(),
250
+ collectStatusJson(),
251
+ ]);
252
+ const onboardingState = await readOnboardingStateOptional(statusResult.status?.statePath);
253
+ const apiUrl = trimTrailingSlash(stringArg("api") ?? onboardingState?.apiUrl ?? DEFAULT_API_URL);
254
+ const capabilities = await fetchOnboardingCapabilities(apiUrl);
255
+ const sourceRows = onboardingSourceRows(capabilities);
256
+ const workflow = guideWorkflow({ sourceRows });
257
+ return {
258
+ command: `${PUBLIC_COMMAND} guide`,
259
+ apiUrl,
260
+ currentState: summarizeGuideState(statusResult, onboardingState),
261
+ capabilities,
262
+ sourceSelection: {
263
+ instruction: "Use a native multi-select window/control when your agent environment supports it. Otherwise ask one concise multi-select question in chat.",
264
+ options: sourceRows,
265
+ },
266
+ workflow,
267
+ help: {
268
+ command: `${PUBLIC_COMMAND} troubleshoot`,
269
+ instruction: "Run this if onboarding state, source setup, local sync, MCP tools, or wiki readiness is unclear.",
270
+ },
271
+ canonicalEnginePrompt: agentPrompt,
272
+ };
273
+ }
274
+
275
+ async function buildTroubleshootPayload() {
276
+ const guide = await buildGuidePayload();
277
+ return {
278
+ command: `${PUBLIC_COMMAND} troubleshoot`,
279
+ apiUrl: guide.apiUrl,
280
+ currentState: guide.currentState,
281
+ capabilities: guide.capabilities,
282
+ diagnostics: troubleshootDiagnostics(guide.currentState),
283
+ help: guide.help,
284
+ };
285
+ }
286
+
287
+ async function loadEngineAgentPrompt() {
288
+ const argv = ["agent", "--json"];
289
+ appendEngineContextArgs(argv);
290
+ const result = await execEngineCapture(argv);
291
+ if (result.exitCode !== 0) {
292
+ return {
293
+ status: "unavailable",
294
+ error: renderCapturedCommand(result),
295
+ };
296
+ }
297
+ try {
298
+ return {
299
+ status: "available",
300
+ payload: JSON.parse(result.stdout),
301
+ };
302
+ } catch {
303
+ return {
304
+ status: "invalid_json",
305
+ error: "The Shepherd onboarding engine did not return valid JSON for its agent workflow prompt.",
306
+ };
307
+ }
308
+ }
309
+
310
+ async function collectStatusJson() {
311
+ const argv = ["status", "--json"];
312
+ appendEngineContextArgs(argv);
313
+ const result = await execEngineCapture(argv);
314
+ if (result.exitCode !== 0) {
315
+ return {
316
+ status: null,
317
+ error: renderCapturedCommand(result),
318
+ };
319
+ }
320
+ try {
321
+ return {
322
+ status: JSON.parse(result.stdout),
323
+ error: null,
324
+ };
325
+ } catch {
326
+ return {
327
+ status: null,
328
+ error: "The Shepherd status command did not return valid JSON.",
329
+ };
330
+ }
331
+ }
332
+
333
+ function appendEngineContextArgs(argv) {
334
+ // Deliberately excludes --state: the wrapper documents it as the MCP token
335
+ // state path, but the engine would read it as the onboarding agent state.
336
+ for (const key of ["api", "onboarding-state", "channel"]) {
337
+ const value = stringArg(key);
338
+ if (value) argv.push(`--${key}`, value);
339
+ }
340
+ }
341
+
342
+ async function readOnboardingStateOptional(enginePath) {
343
+ const path = typeof enginePath === "string" && enginePath ? enginePath : onboardingStatePathForGuide();
344
+ if (!existsSync(path)) return null;
345
+ try {
346
+ const parsed = JSON.parse(await readFile(path, "utf8"));
347
+ return recordValue(parsed);
348
+ } catch {
349
+ return null;
350
+ }
351
+ }
352
+
353
+ function onboardingStatePathForGuide() {
354
+ const explicit = stringArg("onboarding-state");
355
+ if (explicit) return expandHome(explicit);
356
+ // Mirrors the engine's cliChannel()/agentStatePath(): any non-stable channel
357
+ // gets a suffixed state file, and env channels count.
358
+ const channel = [stringArg("channel"), process.env.SHEPHERD_ONBOARD_CHANNEL, process.env.SHEPHERD_APP_CHANNEL]
359
+ .map((value) => String(value ?? "").trim().toLowerCase())
360
+ .find(Boolean) ?? "stable";
361
+ if (channel !== "stable") {
362
+ return join(homedir(), ".shepherd", `raw-onboarding-agent.${channel}.json`);
363
+ }
364
+ return DEFAULT_AGENT_STATE_PATH;
365
+ }
366
+
367
+ async function fetchOnboardingCapabilities(apiUrl) {
368
+ let endpoint = String(apiUrl);
369
+ try {
370
+ endpoint = new URL("/onboarding/raw/capabilities", `${trimTrailingSlash(apiUrl)}/`).toString();
371
+ const response = await fetch(endpoint, { signal: AbortSignal.timeout(10_000) });
372
+ const body = await response.json().catch(() => ({}));
373
+ if (!response.ok) {
374
+ return {
375
+ status: "unavailable",
376
+ endpoint,
377
+ error: safeError(body?.error ?? `capabilities request failed (${response.status})`),
378
+ sources: {},
379
+ };
380
+ }
381
+ return {
382
+ status: "available",
383
+ endpoint,
384
+ sources: normalizeCapabilities(body),
385
+ };
386
+ } catch (err) {
387
+ return {
388
+ status: "unavailable",
389
+ endpoint,
390
+ error: safeError(err),
391
+ sources: {},
392
+ };
393
+ }
394
+ }
395
+
396
+ function normalizeCapabilities(body) {
397
+ const sources = recordValue(body?.sources) ?? recordValue(body?.capabilities?.sources) ?? {};
398
+ const normalized = {};
399
+ for (const source of onboardingSourceDefinitions()) {
400
+ const raw = sources[source.capabilityKey];
401
+ normalized[source.capabilityKey] = raw === undefined ? source.defaultAvailable === true : raw !== false;
402
+ }
403
+ return normalized;
404
+ }
405
+
406
+ function onboardingSourceDefinitions() {
407
+ return [
408
+ { capabilityKey: "google", sourceId: "google", label: "Google Workspace", detail: "Gmail, Drive, Docs, Calendar, Sheets, Slides, Tasks, Contacts via domain-wide delegation" },
409
+ { capabilityKey: "notion", sourceId: "notion", label: "Notion", detail: "browser OAuth" },
410
+ { capabilityKey: "slack", sourceId: "slack", label: "Slack", detail: "browser OAuth" },
411
+ { capabilityKey: "github", sourceId: "github", label: "GitHub", detail: "PAT + owner/repo for webhook-backed event sync" },
412
+ { capabilityKey: "granola", sourceId: "granola", label: "Granola", detail: "browser OAuth, with legacy API-key fallback" },
413
+ { capabilityKey: "messages", sourceId: "messages", label: "Messages", detail: "local macOS source; requires explicit consent, Full Disk Access, and chat selection", localOnly: true },
414
+ { capabilityKey: "codingSessions", sourceId: "coding-sessions", label: "Coding Sessions", detail: "local Codex/Claude Code metadata sync; requires explicit consent", defaultAvailable: true, localOnly: true },
415
+ ];
416
+ }
417
+
418
+ function onboardingSourceRows(capabilities) {
419
+ const availableSources = capabilities.status === "available" ? capabilities.sources : {};
420
+ return onboardingSourceDefinitions().map((source) => ({
421
+ id: source.sourceId,
422
+ label: source.label,
423
+ // Local-only sources never depend on backend capability reporting; backend
424
+ // OAuth sources stay fail-closed when capabilities are unavailable.
425
+ available: source.localOnly === true
426
+ ? true
427
+ : capabilities.status === "available" && availableSources?.[source.capabilityKey] !== false,
428
+ detail: source.detail,
429
+ }));
430
+ }
431
+
432
+ function summarizeGuideState(statusResult, onboardingState) {
433
+ const status = statusResult.status;
434
+ const wikiReadiness = status ? wikiReadinessPayloadFromStatus(status) : null;
435
+ const sourceRows = status ? statusSourceRowsFromStatus(status) : [];
436
+ return {
437
+ configured: Boolean(status?.configured ?? onboardingState),
438
+ statusError: statusResult.error,
439
+ account: status?.account ?? onboardingState?.account ?? null,
440
+ production: status?.production ?? null,
441
+ productionError: status?.productionError ?? null,
442
+ savedSources: status?.savedSources ?? onboardingState?.sources ?? {},
443
+ sources: sourceRows,
444
+ wikiReadiness,
445
+ local: status?.local ?? null,
446
+ commands: status?.commands ?? {
447
+ login: `${PUBLIC_COMMAND} login`,
448
+ continueSetup: `${PUBLIC_COMMAND} continue`,
449
+ checkStatus: `${PUBLIC_COMMAND} status`,
450
+ messagesChats: `${PUBLIC_COMMAND} messages-chats`,
451
+ },
452
+ };
453
+ }
454
+
455
+ function statusSourceRowsFromStatus(status) {
456
+ const providers = recordValue(status?.providers) ?? {};
457
+ const savedSources = recordValue(status?.savedSources) ?? {};
458
+ const definitions = [
459
+ ["google", "Google Workspace", "google"],
460
+ ["notion", "Notion", "notion"],
461
+ ["slack", "Slack", "slack"],
462
+ ["github", "GitHub", "github"],
463
+ ["granola", "Granola", "granola"],
464
+ ["messages", "Messages", "messages"],
465
+ ["codingSessions", "Coding Sessions", "codingSessions"],
466
+ ];
467
+ return definitions.map(([key, label, sourceKey]) => ({
468
+ key,
469
+ label,
470
+ selected: savedSources[sourceKey] === true,
471
+ seen: Boolean(providers[key]),
472
+ connected: providers[key]?.connected === true,
473
+ }));
474
+ }
475
+
476
+ function guideWorkflow({ sourceRows }) {
477
+ const sourceList = sourceRows
478
+ .filter((source) => source.available)
479
+ .map((source) => source.id)
480
+ .join(",");
481
+ return [
482
+ {
483
+ id: "install_usage_skill",
484
+ title: "Ask where to install the Shepherd usage skill",
485
+ prompt: "Ask the user whether to install the Shepherd usage skill into Codex, Claude Code, both, or skip for now.",
486
+ commands: [`${PUBLIC_COMMAND} skill --install <codex|claude|all>`],
487
+ },
488
+ {
489
+ id: "workos_login",
490
+ title: "Authenticate with WorkOS",
491
+ prompt: "Run one Shepherd WorkOS login/signup flow. Use the returned email as account identity; do not ask for email separately.",
492
+ commands: [`${PUBLIC_COMMAND} login`],
493
+ },
494
+ {
495
+ id: "name_org",
496
+ title: "Collect name and organization",
497
+ prompt: "Ask for full name and organization name. Shepherd verifies existing organization joins from the authenticated account and company email domain; typed org text is not trusted by itself.",
498
+ commands: [],
499
+ },
500
+ {
501
+ id: "source_selection",
502
+ title: "Show source selection",
503
+ prompt: `Put up a native multi-select window/control if available. Offer only connectable sources (${sourceList || "none reported"}). Ask explicit consent before Messages or Coding Sessions, and never default Messages to all chats.`,
504
+ commands: [],
505
+ },
506
+ {
507
+ id: "start_onboarding",
508
+ title: "Start source onboarding",
509
+ prompt: "Run onboarding with only the sources the user selected. Use comma-separated source ids from the guide output.",
510
+ commands: [`${PUBLIC_COMMAND} onboard --name "<full_name>" --org "<organization>" --sources "<selected_sources>"`],
511
+ },
512
+ {
513
+ id: "continue_modalities",
514
+ title: "Complete one setup modality at a time",
515
+ prompt: "After each browser/admin/PAT/local permission step, run continue and follow the current modality it prints. Do not open later source setup surfaces until the command advances.",
516
+ commands: [`${PUBLIC_COMMAND} continue`],
517
+ },
518
+ {
519
+ id: "verify_readiness",
520
+ title: "Verify setup and wiki readiness",
521
+ prompt: "Check local sync, wiki readiness, and the live tool catalog. If wiki status is wiki_not_ready, tell the user Shepherd is still learning and include progress/ETA instead of answering memory questions.",
522
+ commands: [`${PUBLIC_COMMAND} status`, `${PUBLIC_COMMAND} shepherd_wiki_status`, `${PUBLIC_COMMAND} tools --json`],
523
+ },
524
+ {
525
+ id: "install_query_tools",
526
+ title: "Ask where to install Shepherd MCP",
527
+ prompt: "After onboarding completes, ask whether to install Shepherd MCP into Codex, Claude Code, both, or none so Shepherd is queryable from their tools.",
528
+ commands: [`${PUBLIC_COMMAND} mcp-login --install <codex|claude|all>`],
529
+ },
530
+ ];
531
+ }
532
+
533
+ function renderGuide(payload) {
534
+ const lines = ["Shepherd agent onboarding workflow", ""];
535
+ lines.push("Use this workflow in order. Keep prompts short and interactive; do not paste the whole checklist to the user unless they ask.");
536
+ lines.push(`API target: ${payload.apiUrl}`);
537
+ lines.push("");
538
+ lines.push("Current state:");
539
+ lines.push(...renderGuideCurrentState(payload.currentState).map((line) => `- ${line}`));
540
+ lines.push("");
541
+ lines.push("Available source choices:");
542
+ for (const source of payload.sourceSelection.options) {
543
+ lines.push(`- ${source.available ? "[available]" : "[unavailable]"} ${source.label} (${source.id}): ${source.detail}`);
544
+ }
545
+ if (payload.capabilities.status !== "available") {
546
+ lines.push(`- Capability check unavailable from ${payload.capabilities.endpoint}: ${payload.capabilities.error}`);
547
+ }
548
+ lines.push("");
549
+ lines.push("Workflow:");
550
+ payload.workflow.forEach((step, index) => {
551
+ lines.push(`${index + 1}. ${step.title}`);
552
+ lines.push(` ${step.prompt}`);
553
+ for (const command of step.commands) lines.push(` Run: ${command}`);
554
+ });
555
+ lines.push("");
556
+ lines.push("If confused or blocked:");
557
+ lines.push(`- Run: ${payload.help.command}`);
558
+ lines.push(`- ${payload.help.instruction}`);
559
+ lines.push("");
560
+ lines.push("Canonical engine prompt:");
561
+ if (payload.canonicalEnginePrompt.status === "available") {
562
+ lines.push("- The onboarding engine prompt is available in `shepherd guide --json` under `canonicalEnginePrompt.payload`.");
563
+ lines.push("- It is the source of truth for one-modality-at-a-time setup, Messages Full Disk Access guidance, GitHub PAT setup, and MCP install prompts.");
564
+ } else {
565
+ lines.push(`- Unavailable: ${payload.canonicalEnginePrompt.error}`);
566
+ }
567
+ return lines.join("\n");
568
+ }
569
+
570
+ function renderGuideCurrentState(state) {
571
+ if (state.statusError) return [`Status unavailable: ${state.statusError}`];
572
+ const lines = [];
573
+ if (state.account) {
574
+ const email = state.account.email ? ` <${state.account.email}>` : "";
575
+ const org = state.account.organizationName ? ` / ${state.account.organizationName}` : "";
576
+ lines.push(`Account: ${state.account.name ?? "unknown"}${email}${org}`);
577
+ } else {
578
+ lines.push("Account: no saved Shepherd onboarding session");
579
+ }
580
+ if (state.productionError) lines.push(`Backend status: unavailable (${state.productionError})`);
581
+ else if (state.production) lines.push(`Backend status: ${state.production.status ?? "unknown"}`);
582
+ else lines.push("Backend status: not checked");
583
+ if (state.wikiReadiness) {
584
+ const wiki = recordValue(state.wikiReadiness.wiki);
585
+ const progress = wiki?.progress_percent == null ? "" : `, ${wiki.progress_percent}% built`;
586
+ const eta = wiki?.eta ? `, ETA ${wiki.eta}` : "";
587
+ lines.push(`Wiki: ${state.wikiReadiness.status}${progress}${eta}`);
588
+ }
589
+ const selected = state.sources.filter((source) => source.selected);
590
+ if (selected.length) {
591
+ lines.push(`Selected sources: ${selected.map((source) => `${source.label} ${source.connected ? "(connected)" : "(pending)"}`).join(", ")}`);
592
+ }
593
+ return lines;
594
+ }
595
+
596
+ function troubleshootDiagnostics(state) {
597
+ const diagnostics = [];
598
+ if (state.statusError) {
599
+ diagnostics.push({
600
+ symptom: "Status command failed",
601
+ likelyCause: "Local onboarding state is missing, unreadable, or points at an unavailable API.",
602
+ fix: [`Run ${PUBLIC_COMMAND} guide`, `Then retry ${PUBLIC_COMMAND} status --json`],
603
+ });
604
+ return diagnostics;
605
+ }
606
+
607
+ if (!state.configured || !state.account) {
608
+ diagnostics.push({
609
+ symptom: "No saved Shepherd onboarding session",
610
+ likelyCause: "WorkOS login has not completed on this machine or the selected --onboarding-state path is empty.",
611
+ fix: [`Run ${PUBLIC_COMMAND} login`, `Then run ${PUBLIC_COMMAND} guide`],
612
+ });
613
+ }
614
+
615
+ if (state.productionError) {
616
+ diagnostics.push({
617
+ symptom: "Backend status is unavailable",
618
+ likelyCause: "The saved onboarding token/API target is expired, revoked, or temporarily unreachable.",
619
+ fix: [`Run ${PUBLIC_COMMAND} login`, `Then run ${PUBLIC_COMMAND} continue`],
620
+ });
621
+ }
622
+
623
+ for (const source of state.sources.filter((row) => row.selected && !row.connected)) {
624
+ diagnostics.push({
625
+ symptom: `${source.label} is selected but not connected`,
626
+ likelyCause: "That source still has a pending browser/admin/PAT/local permission modality.",
627
+ fix: [`Run ${PUBLIC_COMMAND} continue`, `If it is Messages, run ${PUBLIC_COMMAND} messages-chats and pass selected chat IDs back to continue.`],
628
+ });
629
+ }
630
+
631
+ if (state.local?.messages?.configPath && state.local.messages.launch && state.local.messages.launch.running === false) {
632
+ diagnostics.push({
633
+ symptom: "Messages LaunchAgent is installed but not running",
634
+ likelyCause: "macOS Full Disk Access or launchd startup did not validate.",
635
+ fix: [`Run ${PUBLIC_COMMAND} status`, "Grant Full Disk Access to the onboarding app and Node.js, then rerun continue if status asks for it."],
636
+ });
637
+ }
638
+
639
+ if (state.local?.messages?.configPath && state.local.messages.storage?.readable === false) {
640
+ diagnostics.push({
641
+ symptom: "Messages database is not readable",
642
+ likelyCause: "Full Disk Access is missing for the app running Shepherd onboarding.",
643
+ fix: ["Open System Settings -> Privacy & Security -> Full Disk Access and enable the terminal/agent app plus Node.js.", `Then run ${PUBLIC_COMMAND} messages-chats`],
644
+ });
645
+ }
646
+
647
+ if (state.local?.codingSessions?.configPath && state.local.codingSessions.launch && state.local.codingSessions.launch.running === false) {
648
+ diagnostics.push({
649
+ symptom: "Coding Sessions LaunchAgent is installed but not running",
650
+ likelyCause: "launchd startup failed or macOS denied access to the session folders.",
651
+ fix: [`Run ${PUBLIC_COMMAND} coding-sessions-status`, `Then run ${PUBLIC_COMMAND} continue if status asks for setup repair.`],
652
+ });
653
+ }
654
+
655
+ if (state.wikiReadiness?.status === "wiki_not_ready") {
656
+ diagnostics.push({
657
+ symptom: "Wiki is not ready yet",
658
+ likelyCause: "Initial ingestion/wiki processing is still building. This is not a setup failure.",
659
+ fix: ["Tell the user Shepherd is still learning about them, include progress/ETA from shepherd_wiki_status, and retry the memory question later."],
660
+ });
661
+ }
662
+
663
+ if (diagnostics.length === 0) {
664
+ diagnostics.push({
665
+ symptom: "No obvious local onboarding blocker found",
666
+ likelyCause: "Setup appears coherent from local status.",
667
+ fix: [`Run ${PUBLIC_COMMAND} tools --json to verify query tools, then use exact listed tool names.`],
668
+ });
669
+ }
670
+ return diagnostics;
671
+ }
672
+
673
+ function renderTroubleshoot(payload) {
674
+ const lines = ["Shepherd troubleshooting", ""];
675
+ lines.push(`API target: ${payload.apiUrl}`);
676
+ lines.push("");
677
+ lines.push("Current state:");
678
+ lines.push(...renderGuideCurrentState(payload.currentState).map((line) => `- ${line}`));
679
+ lines.push("");
680
+ lines.push("Diagnostics:");
681
+ payload.diagnostics.forEach((item, index) => {
682
+ lines.push(`${index + 1}. ${item.symptom}`);
683
+ lines.push(` Likely cause: ${item.likelyCause}`);
684
+ for (const fix of item.fix) lines.push(` Fix: ${fix}`);
685
+ });
686
+ lines.push("");
687
+ lines.push(`For the full workflow, run: ${PUBLIC_COMMAND} guide`);
688
+ return lines.join("\n");
689
+ }
690
+
218
691
  async function runToolByName(toolName) {
219
692
  const toolArgs = await readToolArgs();
220
693
  const localNames = new Set(localToolDefinitions({ environmentControls: true }).map((tool) => tool.name));
@@ -323,6 +796,20 @@ function localToolDefinitions(opts = {}) {
323
796
  openWorldHint: false,
324
797
  };
325
798
  const tools = [
799
+ {
800
+ name: "shepherd_onboarding_guide",
801
+ description: "LOCAL agent-facing Shepherd onboarding workflow. Use when setting up Shepherd, choosing sources, deciding skill/MCP install targets, or recovering the next onboarding step.",
802
+ inputSchema: emptyInputSchema,
803
+ annotations: readOnlyAnnotations,
804
+ _meta: { provider: "local_cli", command: "shepherd guide" },
805
+ },
806
+ {
807
+ name: "shepherd_troubleshoot",
808
+ description: "LOCAL state-aware Shepherd onboarding/setup help. Use when confused, blocked, auth expired, source setup is pending, local sync is unhealthy, or the wiki is still building.",
809
+ inputSchema: emptyInputSchema,
810
+ annotations: readOnlyAnnotations,
811
+ _meta: { provider: "local_cli", command: "shepherd troubleshoot" },
812
+ },
326
813
  {
327
814
  name: "shepherd_status",
328
815
  description: "LOCAL Shepherd setup and sync status. Use this first when the user asks what they have enabled, what is connected, whether Shepherd is syncing, or why local Messages/Coding Sessions are not running.",
@@ -394,6 +881,14 @@ function localToolDefinitions(opts = {}) {
394
881
  }
395
882
 
396
883
  async function callLocalTool(name) {
884
+ if (name === "shepherd_onboarding_guide") {
885
+ return textResult(renderGuide(await buildGuidePayload()), false);
886
+ }
887
+
888
+ if (name === "shepherd_troubleshoot") {
889
+ return textResult(renderTroubleshoot(await buildTroubleshootPayload()), false);
890
+ }
891
+
397
892
  if (name === "shepherd_environment_status") {
398
893
  return textResult(await renderCliEnvironmentStatus(), false);
399
894
  }
@@ -492,6 +987,7 @@ function printInstructions() {
492
987
  - Use shepherd tools --json as the source of truth for all available MCP-equivalent tools.
493
988
  - Use exact tool names only: shepherd call <tool_name> --args '<json_object>'.
494
989
  - Unknown commands are treated as exact tool names, so shepherd <tool_name> --args '<json_object>' is also valid.
990
+ - For onboarding, run shepherd guide and follow the returned workflow. If blocked, run shepherd troubleshoot.
495
991
  - Run shepherd onboard to create/select the account, link sources, and mint CLI tool auth.
496
992
  - Use shepherd_status for local setup/sync-health and shepherd_wiki_status before memory/wiki questions when readiness is uncertain. Do not inspect the user's home directory, repositories, ~/.shepherd, ~/.codex, or ~/.claude yourself.
497
993
  - If shepherd_wiki_status or a production Shepherd tool returns status: "wiki_not_ready", do not answer the memory/wiki question yet; report the readiness progress/ETA instead.
@@ -503,18 +999,23 @@ function printInstructions() {
503
999
  async function runSkill() {
504
1000
  const skillText = await readFile(SKILL_PATH, "utf8");
505
1001
  const outputPath = stringArg("output");
506
- const installTarget = stringArg("install");
507
- const destination = outputPath
508
- ? expandHome(outputPath)
509
- : installTarget
510
- ? skillInstallPath(installTarget)
511
- : null;
512
-
513
- if (destination) {
514
- await mkdir(dirname(destination), { recursive: true });
515
- await writeFile(destination, skillText);
516
- if (args.json) console.log(JSON.stringify({ path: destination }, null, 2));
517
- else console.log(`Wrote Shepherd skill: ${destination}`);
1002
+ const installTarget = stringArg("install") ?? (args.install === true ? "codex" : undefined);
1003
+
1004
+ if (outputPath || installTarget) {
1005
+ const installed = installTarget ? await writeSkillInstallTargets(installTarget, skillText) : [];
1006
+ let outputDestination = null;
1007
+ if (outputPath) {
1008
+ outputDestination = expandHome(outputPath);
1009
+ await mkdir(dirname(outputDestination), { recursive: true });
1010
+ await writeFile(outputDestination, skillText);
1011
+ }
1012
+ if (args.json) {
1013
+ console.log(JSON.stringify({ path: outputDestination ?? installed[0]?.path ?? null, installed }, null, 2));
1014
+ } else {
1015
+ if (outputDestination) console.log(`Wrote Shepherd skill: ${outputDestination}`);
1016
+ for (const entry of installed) console.log(`Wrote Shepherd skill for ${entry.target}: ${entry.path}`);
1017
+ if (!outputDestination && installed.length === 0) console.log("Shepherd skill install skipped.");
1018
+ }
518
1019
  return;
519
1020
  }
520
1021
 
@@ -523,12 +1024,12 @@ async function runSkill() {
523
1024
 
524
1025
  async function runAgentSetup() {
525
1026
  const target = args["no-install"] ? null : stringArg("install") ?? "codex";
526
- let installedPath = null;
1027
+ let installed = [];
527
1028
  if (target) {
528
- installedPath = await writeSkill(target);
1029
+ installed = await writeSkill(target);
529
1030
  }
530
1031
 
531
- const payload = agentSetupPayload(installedPath);
1032
+ const payload = agentSetupPayload(installed);
532
1033
  if (args.json) {
533
1034
  console.log(JSON.stringify(payload, null, 2));
534
1035
  return;
@@ -536,7 +1037,7 @@ async function runAgentSetup() {
536
1037
 
537
1038
  console.log(`Shepherd agent setup
538
1039
 
539
- Installed skill: ${installedPath ?? "skipped"}
1040
+ Installed skill: ${installed.length ? installed.map((entry) => `${entry.target}: ${entry.path}`).join(", ") : "skipped"}
540
1041
 
541
1042
  One-line prompt to give an agent:
542
1043
  ${payload.oneLinePrompt}
@@ -545,11 +1046,14 @@ Bootstrap prompt when the agent does not already have the skill:
545
1046
  ${payload.bootstrapPrompt}
546
1047
 
547
1048
  Agent instructions:
548
- 1. Ask the user which sources to connect and ask consent before Messages or Coding Sessions.
549
- 2. Run ${payload.commands.login}.
550
- 3. Run ${payload.commands.onboard}.
551
- 4. Run ${payload.commands.continue} after each browser/admin/local permission step.
552
- 5. Finish with ${payload.commands.status}, ${payload.commands.wikiStatus}, and ${payload.commands.tools}.
1049
+ 1. Run ${payload.commands.guide} and follow the returned workflow.
1050
+ 2. Confirm the usage skill install target (agent-setup installs Codex by default; adjust with ${payload.commands.installSkill}).
1051
+ 3. Ask which sources to connect with a multi-select UI when available; ask consent before Messages or Coding Sessions.
1052
+ 4. Run ${payload.commands.login}.
1053
+ 5. Run ${payload.commands.onboard}.
1054
+ 6. Run ${payload.commands.continue} after each browser/admin/local permission step.
1055
+ 7. Finish with ${payload.commands.status}, ${payload.commands.wikiStatus}, and ${payload.commands.tools}.
1056
+ 8. If blocked, run ${payload.commands.troubleshoot}.
553
1057
 
554
1058
  Customer default:
555
1059
  - Do not pass --env for normal customer setup or tool calls. The CLI defaults to the Production Customer deploy lane.
@@ -564,20 +1068,34 @@ Environment selection changes the tool catalog, tool execution, and backend data
564
1068
 
565
1069
  async function writeSkill(target) {
566
1070
  const skillText = await readFile(SKILL_PATH, "utf8");
567
- const destination = skillInstallPath(target);
568
- await mkdir(dirname(destination), { recursive: true });
569
- await writeFile(destination, skillText);
570
- return destination;
1071
+ return writeSkillInstallTargets(target, skillText);
1072
+ }
1073
+
1074
+ async function writeSkillInstallTargets(target, skillText) {
1075
+ const targets = parseSkillInstallTargets(target);
1076
+ const installed = [];
1077
+ for (const installTarget of targets) {
1078
+ const destination = skillInstallPath(installTarget);
1079
+ await mkdir(dirname(destination), { recursive: true });
1080
+ await writeFile(destination, skillText);
1081
+ installed.push({ target: installTarget, path: destination });
1082
+ }
1083
+ return installed;
571
1084
  }
572
1085
 
573
- function agentSetupPayload(installedPath) {
574
- const bootstrapPrompt = `Set up Shepherd for me: run \`${PUBLIC_COMMAND} agent-setup\`, follow its prompts, ask me before connecting each source, then verify with \`${PUBLIC_COMMAND} status\`, \`${PUBLIC_COMMAND} shepherd_wiki_status\`, and \`${PUBLIC_COMMAND} tools --json\`.`;
1086
+ function agentSetupPayload(installed) {
1087
+ const installedSkillPath = installed[0]?.path ?? null;
1088
+ const bootstrapPrompt = `Set up Shepherd for me: run \`${PUBLIC_COMMAND} agent-setup\` (installs the Codex usage skill by default; pass --install claude|all|none to change), then \`${PUBLIC_COMMAND} guide\`; follow the returned workflow, ask me where to install MCP tools, ask consent before connecting each source, then verify with \`${PUBLIC_COMMAND} status\`, \`${PUBLIC_COMMAND} shepherd_wiki_status\`, and \`${PUBLIC_COMMAND} tools --json\`.`;
575
1089
  return {
576
- installedSkillPath: installedPath,
1090
+ installedSkillPath,
1091
+ installedSkillPaths: installed,
577
1092
  oneLinePrompt: "Use the Shepherd skill to onboard me to this repository before coding.",
578
1093
  bootstrapPrompt,
579
1094
  commands: {
580
1095
  agentSetup: `${PUBLIC_COMMAND} agent-setup`,
1096
+ guide: `${PUBLIC_COMMAND} guide`,
1097
+ troubleshoot: `${PUBLIC_COMMAND} troubleshoot`,
1098
+ installSkill: `${PUBLIC_COMMAND} skill --install <codex|claude|all>`,
581
1099
  login: `${PUBLIC_COMMAND} login`,
582
1100
  onboard: `${PUBLIC_COMMAND} onboard --name "<full_name>" --org "<organization>" --sources "<sources>"`,
583
1101
  continue: `${PUBLIC_COMMAND} continue`,
@@ -602,12 +1120,37 @@ function agentSetupPayload(installedPath) {
602
1120
  };
603
1121
  }
604
1122
 
1123
+ function parseSkillInstallTargets(value) {
1124
+ const raw = String(value ?? "").trim().toLowerCase();
1125
+ if (!raw || raw === "codex") return ["codex"];
1126
+ if (raw === "all" || raw === "both" || raw === "yes" || raw === "true") return [...SKILL_INSTALL_TARGETS];
1127
+ if (raw === "none" || raw === "no" || raw === "false" || raw === "skip") return [];
1128
+ const aliases = new Map([
1129
+ ["codex-cli", "codex"],
1130
+ ["claude-code", "claude"],
1131
+ ["claude", "claude"],
1132
+ ]);
1133
+ const targets = raw
1134
+ .split(/[,\s]+/)
1135
+ .map((target) => aliases.get(target) ?? target)
1136
+ .filter(Boolean);
1137
+ for (const target of targets) {
1138
+ if (!SKILL_INSTALL_TARGETS.includes(target)) {
1139
+ throw new Error(`unknown skill install target "${target}". Use codex, claude, all, none, or --output <path>.`);
1140
+ }
1141
+ }
1142
+ return [...new Set(targets)];
1143
+ }
1144
+
605
1145
  function skillInstallPath(target) {
606
1146
  const normalized = String(target ?? "").trim().toLowerCase();
607
1147
  if (!normalized || normalized === "codex") {
608
1148
  return join(homedir(), ".codex", "skills", "shepherd", "SKILL.md");
609
1149
  }
610
- throw new Error(`unknown skill install target "${target}". Use codex or --output <path>.`);
1150
+ if (normalized === "claude") {
1151
+ return join(homedir(), ".claude", "skills", "shepherd", "SKILL.md");
1152
+ }
1153
+ throw new Error(`unknown skill install target "${target}". Use codex, claude, all, none, or --output <path>.`);
611
1154
  }
612
1155
 
613
1156
  function printHelp() {
@@ -616,6 +1159,8 @@ function printHelp() {
616
1159
  Usage:
617
1160
  npx -y askshepherd@latest
618
1161
  npx -y askshepherd@latest agent-setup
1162
+ shepherd guide
1163
+ shepherd troubleshoot
619
1164
  shepherd login
620
1165
  shepherd onboard
621
1166
  shepherd continue
@@ -625,10 +1170,12 @@ Usage:
625
1170
  shepherd <tool_name> --args '<json_object>'
626
1171
  shepherd status
627
1172
  shepherd instructions
628
- shepherd skill [--install codex|--output <path>]
1173
+ shepherd skill [--install codex|claude|all|--output <path>]
629
1174
 
630
1175
  Commands:
631
1176
  agent-setup Install/print the one-line coding-agent setup handoff.
1177
+ guide Print the agent-facing onboarding workflow.
1178
+ troubleshoot Print state-aware setup/onboarding help.
632
1179
  login Authenticate Shepherd account and save local onboarding auth.
633
1180
  onboard Create/select account, link sources, and prepare tool auth.
634
1181
  continue Resume pending source setup.
@@ -638,7 +1185,7 @@ Commands:
638
1185
  <tool_name> Call one exact Shepherd MCP tool.
639
1186
  status Shorthand for shepherd_status.
640
1187
  instructions Print agent-facing behavior instructions.
641
- skill Print or write the bundled Codex skill.
1188
+ skill Print or write the bundled usage skill.
642
1189
 
643
1190
  Options:
644
1191
  --args <json> JSON object passed to a tool call.
@@ -650,7 +1197,7 @@ Options:
650
1197
  --url <url> Override the MCP endpoint.
651
1198
  --token <token> Override the bearer token.
652
1199
  --env <environment> askshepherd.ai only: select deploy, canary, or customer-facing for tools/calls.
653
- --install codex With skill, write to ~/.codex/skills/shepherd/SKILL.md.
1200
+ --install <targets> With skill, write to codex, claude, all, none, or comma-separated targets.
654
1201
  --output <path> With skill, write SKILL.md to this path.
655
1202
  --json Print machine-readable output where supported.
656
1203
  --help Show this help.