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/README.md +31 -6
- package/bin/shepherd-onboard.js +301 -41
- package/bin/shepherd.js +579 -32
- package/package.json +1 -1
- package/skills/shepherd/SKILL.md +37 -58
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
|
-
|
|
508
|
-
|
|
509
|
-
:
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (args.json)
|
|
517
|
-
|
|
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
|
|
1027
|
+
let installed = [];
|
|
527
1028
|
if (target) {
|
|
528
|
-
|
|
1029
|
+
installed = await writeSkill(target);
|
|
529
1030
|
}
|
|
530
1031
|
|
|
531
|
-
const payload = agentSetupPayload(
|
|
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: ${
|
|
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.
|
|
549
|
-
2.
|
|
550
|
-
3.
|
|
551
|
-
4. Run ${payload.commands.
|
|
552
|
-
5.
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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(
|
|
574
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|