askshepherd 0.1.36 → 0.1.37
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 +34 -0
- package/bin/shepherd-onboard.js +497 -77
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,6 +54,33 @@ The command:
|
|
|
54
54
|
|
|
55
55
|
The command does not expose Railway, database, Redis, or internal service details to the user.
|
|
56
56
|
|
|
57
|
+
## Check Sync Status
|
|
58
|
+
|
|
59
|
+
Use this when the user asks "Check what I've enabled for Shepherd?":
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
npx -y askshepherd@latest status
|
|
63
|
+
npx -y askshepherd@latest status --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
It reports the saved Shepherd account, connected cloud sources, downstream
|
|
67
|
+
processing state, and local background sync health for Messages and Coding
|
|
68
|
+
Sessions.
|
|
69
|
+
|
|
70
|
+
## Set Up Coding Agent Sessions
|
|
71
|
+
|
|
72
|
+
Use this when the user asks "Help me set up coding agent sessions":
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
npx -y askshepherd@latest agent --login
|
|
76
|
+
npx -y askshepherd@latest agent --add-sources coding-sessions --name "<name>" --org "<organization>"
|
|
77
|
+
npx -y askshepherd@latest agent --continue
|
|
78
|
+
npx -y askshepherd@latest status
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The coding agent should ask for consent before enabling this source. The local
|
|
82
|
+
collector syncs redacted Codex and Claude Code summaries, not full transcripts.
|
|
83
|
+
|
|
57
84
|
## Customer MCP Login
|
|
58
85
|
|
|
59
86
|
After raw onboarding creates the Shepherd customer account, customers can
|
|
@@ -82,6 +109,13 @@ The saved MCP state includes:
|
|
|
82
109
|
- `authSource`: `local_onboarding` or `workos`
|
|
83
110
|
- `localAuth`: raw onboarding state reference when local auth was used
|
|
84
111
|
|
|
112
|
+
The installed MCP server is local npm first, remote brain second. For questions
|
|
113
|
+
like "what do I have set up on Shepherd?", "is Shepherd syncing?", or "help me
|
|
114
|
+
set up coding agent sessions", the MCP exposes local tools such as
|
|
115
|
+
`shepherd_status` and `shepherd_setup_coding_sessions` that route agents to the
|
|
116
|
+
local `askshepherd status` / add-source flow. Production memory and wiki tools
|
|
117
|
+
remain remote Railway-backed tools for source recall and company-memory answers.
|
|
118
|
+
|
|
85
119
|
Use `--json` when an agent or setup script needs machine-readable endpoint and
|
|
86
120
|
header details.
|
|
87
121
|
|
package/bin/shepherd-onboard.js
CHANGED
|
@@ -12,7 +12,7 @@ import { fileURLToPath } from "node:url";
|
|
|
12
12
|
const DEFAULT_API_URL = "https://brain-api-customer-facing.up.railway.app";
|
|
13
13
|
const PACKAGE_NAME = "askshepherd";
|
|
14
14
|
const PACKAGE_SPEC = `${PACKAGE_NAME}@latest`;
|
|
15
|
-
const PACKAGE_VERSION = "0.1.
|
|
15
|
+
const PACKAGE_VERSION = "0.1.37";
|
|
16
16
|
const MCP_SERVER_NAME = "shepherd";
|
|
17
17
|
const PACKAGE_DIR = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
18
18
|
const DEFAULT_AGENT_STATE_PATH = join(homedir(), ".shepherd", "raw-onboarding-agent.json");
|
|
@@ -127,6 +127,8 @@ async function dispatch() {
|
|
|
127
127
|
await runMcpInstall();
|
|
128
128
|
} else if (command === "mcp") {
|
|
129
129
|
await runMcpProxy();
|
|
130
|
+
} else if (command === "status" || command === "sync-status" || command === "check") {
|
|
131
|
+
await runStatusCommand();
|
|
130
132
|
} else if (command === "messages-chats") {
|
|
131
133
|
await runMessagesChatsCommand();
|
|
132
134
|
} else if (command === "messages-agent") {
|
|
@@ -720,39 +722,169 @@ async function runMcpProxy() {
|
|
|
720
722
|
{ name: "askshepherd-mcp-proxy", version: PACKAGE_VERSION },
|
|
721
723
|
{ capabilities: {} },
|
|
722
724
|
);
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
725
|
+
let remoteConnected = false;
|
|
726
|
+
let remoteConnectError = null;
|
|
727
|
+
try {
|
|
728
|
+
await remote.connect(new StreamableHTTPClientTransport(new URL(mcpUrl), {
|
|
729
|
+
requestInit: {
|
|
730
|
+
headers: {
|
|
731
|
+
Authorization: `Bearer ${token}`,
|
|
732
|
+
},
|
|
727
733
|
},
|
|
728
|
-
}
|
|
729
|
-
|
|
734
|
+
}));
|
|
735
|
+
remoteConnected = true;
|
|
736
|
+
} catch (err) {
|
|
737
|
+
remoteConnectError = safeError(err);
|
|
738
|
+
}
|
|
730
739
|
|
|
731
740
|
const passthroughResultSchema = typeof ResultSchema.passthrough === "function"
|
|
732
741
|
? ResultSchema.passthrough()
|
|
733
742
|
: ResultSchema;
|
|
743
|
+
const remoteCapabilities = remoteConnected ? remote.getServerCapabilities() ?? {} : {};
|
|
744
|
+
const remoteInstructions = remoteConnected ? remote.getInstructions() ?? "" : "";
|
|
745
|
+
const localTools = localMcpTools();
|
|
746
|
+
const localToolNames = new Set(localTools.map((tool) => tool.name));
|
|
734
747
|
const local = new Server(
|
|
735
748
|
{ name: "askshepherd", version: PACKAGE_VERSION },
|
|
736
749
|
{
|
|
737
|
-
capabilities:
|
|
738
|
-
instructions:
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
750
|
+
capabilities: { ...remoteCapabilities, tools: { listChanged: false } },
|
|
751
|
+
instructions: localMcpInstructions(remoteInstructions, remoteConnectError),
|
|
752
|
+
...(remoteConnected
|
|
753
|
+
? {
|
|
754
|
+
fallbackRequestHandler: async (request, extra) => remote.request(
|
|
755
|
+
request,
|
|
756
|
+
passthroughResultSchema,
|
|
757
|
+
{ ...proxyRequestOptions, signal: extra.signal },
|
|
758
|
+
),
|
|
759
|
+
fallbackNotificationHandler: async (notification) => {
|
|
760
|
+
await remote.notification(notification);
|
|
761
|
+
},
|
|
762
|
+
}
|
|
763
|
+
: {}),
|
|
747
764
|
},
|
|
748
765
|
);
|
|
749
|
-
local.setRequestHandler(ListToolsRequestSchema, async (request, extra) =>
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
766
|
+
local.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
|
|
767
|
+
if (!remoteConnected) return { tools: localTools };
|
|
768
|
+
const remoteTools = await remote
|
|
769
|
+
.listTools(request.params, { ...proxyRequestOptions, signal: extra.signal })
|
|
770
|
+
.catch(() => ({ tools: [] }));
|
|
771
|
+
return {
|
|
772
|
+
...remoteTools,
|
|
773
|
+
tools: [
|
|
774
|
+
...localTools,
|
|
775
|
+
...(remoteTools.tools ?? []).filter((tool) => !localToolNames.has(tool.name)),
|
|
776
|
+
],
|
|
777
|
+
};
|
|
778
|
+
});
|
|
779
|
+
local.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
780
|
+
if (localToolNames.has(request.params.name)) {
|
|
781
|
+
return callLocalMcpTool(request.params.name);
|
|
782
|
+
}
|
|
783
|
+
if (!remoteConnected) {
|
|
784
|
+
return localMcpTextResult(`Production Shepherd MCP is unavailable (${remoteConnectError ?? "not connected"}). For local setup/sync status, use shepherd_status or run ${agentCommand()} status.`, true);
|
|
785
|
+
}
|
|
786
|
+
return remote.callTool(request.params, passthroughResultSchema, { ...proxyRequestOptions, signal: extra.signal });
|
|
787
|
+
});
|
|
753
788
|
await local.connect(new StdioServerTransport());
|
|
754
789
|
}
|
|
755
790
|
|
|
791
|
+
function localMcpTools() {
|
|
792
|
+
const emptyInputSchema = {
|
|
793
|
+
type: "object",
|
|
794
|
+
properties: {},
|
|
795
|
+
additionalProperties: false,
|
|
796
|
+
};
|
|
797
|
+
const readOnlyAnnotations = {
|
|
798
|
+
readOnlyHint: true,
|
|
799
|
+
destructiveHint: false,
|
|
800
|
+
openWorldHint: false,
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
return [
|
|
804
|
+
{
|
|
805
|
+
name: "shepherd_status",
|
|
806
|
+
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. This is backed by the local askshepherd npm CLI; do not use production memory/wiki tools for local setup status.",
|
|
807
|
+
inputSchema: emptyInputSchema,
|
|
808
|
+
annotations: readOnlyAnnotations,
|
|
809
|
+
_meta: { provider: "local_npm", command: `${agentCommand()} status` },
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
name: "shepherd_local_status",
|
|
813
|
+
description: "Explicit local alias for shepherd_status. Returns the authoritative local askshepherd npm status path and current local setup/sync state.",
|
|
814
|
+
inputSchema: emptyInputSchema,
|
|
815
|
+
annotations: readOnlyAnnotations,
|
|
816
|
+
_meta: { provider: "local_npm", command: `${agentCommand()} status` },
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
name: "shepherd_setup_coding_sessions",
|
|
820
|
+
description: "LOCAL setup guide for Codex and Claude Code coding-session sync. Use when the user asks to set up coding agent sessions. Ask for consent, then guide the local askshepherd npm login/add-sources/continue/status commands.",
|
|
821
|
+
inputSchema: emptyInputSchema,
|
|
822
|
+
annotations: readOnlyAnnotations,
|
|
823
|
+
_meta: { provider: "local_npm", command: `${agentCommand()} agent --add-sources coding-sessions` },
|
|
824
|
+
},
|
|
825
|
+
];
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function localMcpInstructions(remoteInstructions, remoteConnectError) {
|
|
829
|
+
return [
|
|
830
|
+
"This MCP server is the local askshepherd npm wrapper plus production Shepherd memory/wiki tools.",
|
|
831
|
+
`For local setup/sync questions like "what do I have set up on Shepherd", "what have I enabled", "is Shepherd syncing", "help me set up coding agent sessions", or "enable coding sessions", use shepherd_status or shepherd_setup_coding_sessions first. These local tools route to the local askshepherd npm status/setup flow and can inspect ~/.shepherd, LaunchAgents, and local Codex/Claude paths.`,
|
|
832
|
+
`If the user asks for raw local status outside MCP, tell them to run ${agentCommand()} status. For setup of coding agent sessions, ask consent, then use ${agentCommand()} agent --login if needed, ${agentCommand()} agent --add-sources coding-sessions --name "<full_name>" --org "<organization>", ${agentCommand()} agent --continue, then ${agentCommand()} status.`,
|
|
833
|
+
"Use production memory/wiki tools only for company-memory questions, source recall, wiki lookup, messages/meetings retrieval, or coding-session work history that has already synced to Shepherd.",
|
|
834
|
+
"Important override: any production instruction saying not to use local shell commands applies only to production memory/wiki answers. It does not apply to local Shepherd setup, source enablement, or sync health.",
|
|
835
|
+
remoteConnectError ? `Production Shepherd MCP connection failed at startup: ${remoteConnectError}. Local setup/status tools are still available.` : "",
|
|
836
|
+
remoteInstructions ? `Production memory/wiki instructions: ${remoteInstructions}` : "",
|
|
837
|
+
].filter(Boolean).join(" ");
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
async function callLocalMcpTool(name) {
|
|
841
|
+
if (name === "shepherd_status" || name === "shepherd_local_status") {
|
|
842
|
+
const status = await collectShepherdStatus();
|
|
843
|
+
return localMcpTextResult([
|
|
844
|
+
`Authoritative local status path: ${agentCommand()} status`,
|
|
845
|
+
"Use this result for setup/source/sync-health questions. Do not use production memory/wiki tools to answer what is enabled locally.",
|
|
846
|
+
renderShepherdStatus(status),
|
|
847
|
+
].join("\n\n"));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (name === "shepherd_setup_coding_sessions") {
|
|
851
|
+
const status = await collectShepherdStatus();
|
|
852
|
+
return localMcpTextResult(renderCodingSessionsSetupMcpResult(status));
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
return localMcpTextResult(`Unknown local Shepherd MCP tool: ${name}`, true);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function renderCodingSessionsSetupMcpResult(status) {
|
|
859
|
+
const command = status.commands.addCodingSessions;
|
|
860
|
+
const alreadyConfigured = Boolean(status.local.codingSessions.configPath);
|
|
861
|
+
return [
|
|
862
|
+
"Local Shepherd coding-session setup",
|
|
863
|
+
"",
|
|
864
|
+
"Use this when the user asks to set up coding agent sessions. Ask for explicit consent before enabling this source: Shepherd will read local Codex and Claude Code session logs, redact sensitive strings locally, and sync bounded summaries plus repo/command/file metadata, not full raw transcripts.",
|
|
865
|
+
"",
|
|
866
|
+
alreadyConfigured
|
|
867
|
+
? "Current state: Coding Sessions already has a local config. Check whether the LaunchAgent is running and whether the last sync is healthy below."
|
|
868
|
+
: "Current state: Coding Sessions is not configured locally yet.",
|
|
869
|
+
"",
|
|
870
|
+
"Commands to run locally:",
|
|
871
|
+
`1. If there is no saved Shepherd login, run: ${status.commands.login}`,
|
|
872
|
+
`2. Add only this source: ${command}`,
|
|
873
|
+
`3. Finish/install the local agent: ${status.commands.continueSetup}`,
|
|
874
|
+
`4. Verify: ${status.commands.checkStatus}`,
|
|
875
|
+
"",
|
|
876
|
+
"Current local status:",
|
|
877
|
+
renderLocalCodingSessionsStatus(status.local.codingSessions).join("\n"),
|
|
878
|
+
].join("\n");
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function localMcpTextResult(text, isError = false) {
|
|
882
|
+
return {
|
|
883
|
+
content: [{ type: "text", text }],
|
|
884
|
+
isError,
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
756
888
|
async function pollMcpLogin(apiUrl, started) {
|
|
757
889
|
const intervalMs = Math.max(1000, Number(started.intervalSeconds ?? 5) * 1000);
|
|
758
890
|
const expiresAt = Date.parse(started.expiresAt ?? "") || Date.now() + 600_000;
|
|
@@ -1021,20 +1153,186 @@ async function continueAgentOnboarding() {
|
|
|
1021
1153
|
}
|
|
1022
1154
|
|
|
1023
1155
|
async function printAgentStatus() {
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
);
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1156
|
+
await runStatusCommand();
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
async function runStatusCommand() {
|
|
1160
|
+
const status = await collectShepherdStatus();
|
|
1161
|
+
if (args.json) {
|
|
1162
|
+
console.log(JSON.stringify(status, null, 2));
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
printShepherdStatus(status);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
async function collectShepherdStatus() {
|
|
1169
|
+
const statePath = agentStatePath();
|
|
1170
|
+
const state = await readOptionalAgentState();
|
|
1171
|
+
let production = null;
|
|
1172
|
+
let productionError = null;
|
|
1173
|
+
|
|
1174
|
+
if (state?.apiUrl && state?.sessionId && state?.sessionToken) {
|
|
1175
|
+
try {
|
|
1176
|
+
production = await getJson(
|
|
1177
|
+
`${trimTrailingSlash(state.apiUrl)}/onboarding/raw/session/${encodeURIComponent(state.sessionId)}/status`,
|
|
1178
|
+
{ token: state.sessionToken },
|
|
1179
|
+
);
|
|
1180
|
+
await updateAgentStateFromOnboardingResponse(state, production);
|
|
1181
|
+
} catch (err) {
|
|
1182
|
+
productionError = safeError(err);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const userId = production?.sessionId ?? state?.sessionId ?? null;
|
|
1187
|
+
const providers = production?.providers ?? state?.providers ?? {};
|
|
1188
|
+
const messagesLocal = await collectMessagesLocalStatus(userId);
|
|
1189
|
+
const codingSessionsLocal = await collectCodingSessionsLocalStatus(userId);
|
|
1190
|
+
|
|
1191
|
+
return {
|
|
1192
|
+
statePath,
|
|
1193
|
+
configured: Boolean(state),
|
|
1194
|
+
account: production?.account ?? state?.account ?? null,
|
|
1195
|
+
savedSources: state?.sources ?? {},
|
|
1196
|
+
providers,
|
|
1197
|
+
production: production
|
|
1198
|
+
? {
|
|
1199
|
+
status: production.status,
|
|
1200
|
+
providers,
|
|
1201
|
+
rawOnly: production.rawOnly === true,
|
|
1202
|
+
processingEnabled: production.processingEnabled === true,
|
|
1203
|
+
processing: production.processing,
|
|
1204
|
+
}
|
|
1205
|
+
: null,
|
|
1206
|
+
productionError,
|
|
1207
|
+
local: {
|
|
1208
|
+
messages: messagesLocal,
|
|
1209
|
+
codingSessions: codingSessionsLocal,
|
|
1210
|
+
},
|
|
1211
|
+
commands: {
|
|
1212
|
+
login: `${agentCommand()} agent --login`,
|
|
1213
|
+
checkStatus: `${agentCommand()} status`,
|
|
1214
|
+
addCodingSessions: `${agentCommand()} agent --add-sources coding-sessions --name "<full_name>" --org "<organization>"`,
|
|
1215
|
+
continueSetup: `${agentCommand()} agent --continue`,
|
|
1216
|
+
codingSessionsStatus: `${agentCommand()} coding-sessions-status`,
|
|
1217
|
+
messagesChats: `${agentCommand()} messages-chats`,
|
|
1218
|
+
},
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
function printShepherdStatus(status) {
|
|
1223
|
+
console.log(renderShepherdStatus(status));
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function renderShepherdStatus(status) {
|
|
1227
|
+
const lines = ["", "Shepherd sync status", ""];
|
|
1228
|
+
|
|
1229
|
+
if (status.account) {
|
|
1230
|
+
const email = status.account.email ? ` <${status.account.email}>` : "";
|
|
1231
|
+
const org = status.account.organizationName ? ` / ${status.account.organizationName}` : "";
|
|
1232
|
+
lines.push(`Account: ${status.account.name ?? "unknown"}${email}${org}`);
|
|
1233
|
+
} else {
|
|
1234
|
+
lines.push("Account: no saved Shepherd onboarding session found");
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
if (status.productionError) {
|
|
1238
|
+
lines.push(`Production status: unavailable (${status.productionError})`);
|
|
1239
|
+
} else if (status.production) {
|
|
1240
|
+
lines.push(`Production status: ${status.production.status ?? "unknown"}`);
|
|
1241
|
+
lines.push(`Downstream processing: ${status.production.processingEnabled ? "enabled" : "not enabled"}`);
|
|
1242
|
+
} else {
|
|
1243
|
+
lines.push("Production status: not checked");
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
lines.push("", "Sources:");
|
|
1247
|
+
for (const source of statusSourceRows(status.providers, status.savedSources)) {
|
|
1248
|
+
const label = source.connected
|
|
1249
|
+
? "connected"
|
|
1250
|
+
: source.seen
|
|
1251
|
+
? "not connected"
|
|
1252
|
+
: source.selected
|
|
1253
|
+
? "selected in saved setup; connection unknown"
|
|
1254
|
+
: "not enabled in saved setup";
|
|
1255
|
+
lines.push(`- ${source.label}: ${label}`);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
lines.push("", "Local sync:");
|
|
1259
|
+
lines.push(...renderLocalMessagesStatus(status.local.messages));
|
|
1260
|
+
lines.push(...renderLocalCodingSessionsStatus(status.local.codingSessions));
|
|
1261
|
+
|
|
1262
|
+
lines.push("", "Useful commands:");
|
|
1263
|
+
if (!status.configured) lines.push(`- Sign in: ${status.commands.login}`);
|
|
1264
|
+
lines.push(`- Check again: ${status.commands.checkStatus}`);
|
|
1265
|
+
lines.push(`- Add coding sessions: ${status.commands.addCodingSessions}`);
|
|
1266
|
+
lines.push(`- Continue pending setup: ${status.commands.continueSetup}`);
|
|
1267
|
+
return lines.join("\n");
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
function statusSourceRows(providers, savedSources = {}) {
|
|
1271
|
+
const definitions = [
|
|
1272
|
+
["google", "Google Workspace", "google"],
|
|
1273
|
+
["slack", "Slack", "slack"],
|
|
1274
|
+
["granola", "Granola", "granola"],
|
|
1275
|
+
["messages", "Messages", "messages"],
|
|
1276
|
+
["codingSessions", "Coding Sessions", "codingSessions"],
|
|
1277
|
+
];
|
|
1278
|
+
return definitions.map(([key, label, sourceKey]) => ({
|
|
1279
|
+
key,
|
|
1280
|
+
label,
|
|
1281
|
+
seen: Boolean(providers?.[key]),
|
|
1282
|
+
selected: savedSources?.[sourceKey] === true,
|
|
1283
|
+
connected: providers?.[key]?.connected === true,
|
|
1284
|
+
}));
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
function printLocalMessagesStatus(status) {
|
|
1288
|
+
console.log(renderLocalMessagesStatus(status).join("\n"));
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
function renderLocalMessagesStatus(status) {
|
|
1292
|
+
const lines = [];
|
|
1293
|
+
const prefix = "- Messages local agent";
|
|
1294
|
+
if (!status.configPath) {
|
|
1295
|
+
lines.push(`${prefix}: not configured`);
|
|
1296
|
+
} else {
|
|
1297
|
+
lines.push(`${prefix}: configured at ${status.configPath}`);
|
|
1298
|
+
}
|
|
1299
|
+
if (status.launch) {
|
|
1300
|
+
lines.push(` LaunchAgent: ${status.launch.label} ${status.launch.running ? "running" : "not running or unknown"}`);
|
|
1301
|
+
} else {
|
|
1302
|
+
lines.push(" LaunchAgent: not installed or unavailable");
|
|
1303
|
+
}
|
|
1304
|
+
lines.push(` Messages database: ${status.storage.readable ? "readable" : `not readable (${status.storage.reason})`}`);
|
|
1305
|
+
lines.push(` Queued unsent messages: ${status.queueDepth}`);
|
|
1306
|
+
return lines;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
function printLocalCodingSessionsStatus(status) {
|
|
1310
|
+
console.log(renderLocalCodingSessionsStatus(status).join("\n"));
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
function renderLocalCodingSessionsStatus(status) {
|
|
1314
|
+
const lines = [];
|
|
1315
|
+
const prefix = "- Coding Sessions local agent";
|
|
1316
|
+
if (!status.configPath) {
|
|
1317
|
+
lines.push(`${prefix}: not configured`);
|
|
1318
|
+
} else {
|
|
1319
|
+
lines.push(`${prefix}: configured at ${status.configPath}`);
|
|
1320
|
+
}
|
|
1321
|
+
if (status.launch) {
|
|
1322
|
+
lines.push(` LaunchAgent: ${status.launch.label} ${status.launch.running ? "running" : "not running or unknown"}`);
|
|
1323
|
+
} else {
|
|
1324
|
+
lines.push(" LaunchAgent: not installed or unavailable");
|
|
1325
|
+
}
|
|
1326
|
+
for (const probe of status.localFolders) {
|
|
1327
|
+
lines.push(` ${probe.provider}: ${probe.path} ${probe.readable ? "readable" : `not readable (${probe.reason})`}`);
|
|
1328
|
+
}
|
|
1329
|
+
if (status.lastSync) {
|
|
1330
|
+
lines.push(` Last sync: ${status.lastSync.finishedAt ?? "unknown"} (${status.lastSync.scanned ?? 0} scanned, ${status.lastSync.changed ?? 0} changed)`);
|
|
1331
|
+
} else {
|
|
1332
|
+
lines.push(" Last sync: none recorded");
|
|
1333
|
+
}
|
|
1334
|
+
lines.push(` Queued unsent sessions: ${status.queueDepth}`);
|
|
1335
|
+
return lines;
|
|
1038
1336
|
}
|
|
1039
1337
|
|
|
1040
1338
|
async function runMessagesChatsCommand() {
|
|
@@ -1173,55 +1471,85 @@ async function runCodingSessionsAgent() {
|
|
|
1173
1471
|
}
|
|
1174
1472
|
}
|
|
1175
1473
|
|
|
1176
|
-
async function
|
|
1177
|
-
const configPath =
|
|
1178
|
-
const config = configPath ?
|
|
1179
|
-
const userId = config?.userId ?? null;
|
|
1474
|
+
async function collectMessagesLocalStatus(preferredUserId = null) {
|
|
1475
|
+
const configPath = await messagesConfigPathForUser(preferredUserId) ?? await latestMessagesConfigPath();
|
|
1476
|
+
const config = configPath ? readJsonOptional(configPath) : null;
|
|
1477
|
+
const userId = config?.userId ?? preferredUserId ?? null;
|
|
1478
|
+
const safeId = userId ? safeFileId(userId) : null;
|
|
1479
|
+
const label = safeId ? `ai.shepherd.raw-messages.${safeId}` : null;
|
|
1480
|
+
const queue = safeId ? readJsonOptional(join(homedir(), ".shepherd", "raw-messages", `${safeId}-queue.json`)) : null;
|
|
1481
|
+
|
|
1482
|
+
return {
|
|
1483
|
+
configPath: configPath ?? null,
|
|
1484
|
+
userId,
|
|
1485
|
+
allChats: config?.allChats === true,
|
|
1486
|
+
selectedChatCount: Array.isArray(config?.allowedChatIds) ? config.allowedChatIds.length : 0,
|
|
1487
|
+
storage: await probePath("messages", MESSAGES_CHAT_DB_PATH),
|
|
1488
|
+
launch: localLaunchStatus(label),
|
|
1489
|
+
queueDepth: Array.isArray(queue) ? queue.length : 0,
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
async function collectCodingSessionsLocalStatus(preferredUserId = null, explicitConfigPath = null) {
|
|
1494
|
+
const configPath = explicitConfigPath ?? await codingSessionsConfigPathForUser(preferredUserId) ?? await latestCodingSessionsConfigPath();
|
|
1495
|
+
const config = configPath ? readJsonOptional(configPath) : null;
|
|
1496
|
+
const userId = config?.userId ?? preferredUserId ?? null;
|
|
1180
1497
|
const safeId = userId ? safeFileId(userId) : null;
|
|
1181
1498
|
const label = safeId ? `ai.shepherd.coding-sessions.${safeId}` : null;
|
|
1182
|
-
const
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
const launch = label && platform() === "darwin"
|
|
1186
|
-
? {
|
|
1187
|
-
label,
|
|
1188
|
-
state: readLaunchctlPrint(`gui/${process.getuid?.() ?? 501}/${label}`),
|
|
1189
|
-
}
|
|
1190
|
-
: null;
|
|
1191
|
-
const production = await productionOnboardingStatusForCodingSessions(config).catch((err) => ({ error: safeError(err) }));
|
|
1192
|
-
const status = {
|
|
1499
|
+
const queue = safeId ? readJsonOptional(join(homedir(), ".shepherd", "coding-sessions", `${safeId}-queue.json`)) : null;
|
|
1500
|
+
|
|
1501
|
+
return {
|
|
1193
1502
|
configPath: configPath ?? null,
|
|
1194
1503
|
userId,
|
|
1195
|
-
localFolders:
|
|
1196
|
-
launch,
|
|
1197
|
-
lastSync,
|
|
1504
|
+
localFolders: await probeCodingSessionPaths(config ?? {}),
|
|
1505
|
+
launch: localLaunchStatus(label),
|
|
1506
|
+
lastSync: userId ? readJsonOptional(codingSessionsStatusFile(userId)) : null,
|
|
1198
1507
|
queueDepth: Array.isArray(queue) ? queue.length : 0,
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
function localLaunchStatus(label) {
|
|
1512
|
+
if (!label || platform() !== "darwin") return null;
|
|
1513
|
+
const state = readLaunchctlPrint(`gui/${process.getuid?.() ?? 501}/${label}`);
|
|
1514
|
+
return {
|
|
1515
|
+
label,
|
|
1516
|
+
running: /state = running|job state = running/i.test(state),
|
|
1517
|
+
state,
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
async function runCodingSessionsStatus() {
|
|
1522
|
+
const configPath = stringArg("config") ?? await latestCodingSessionsConfigPath();
|
|
1523
|
+
const status = await collectCodingSessionsLocalStatus(null, configPath);
|
|
1524
|
+
const config = configPath ? readJsonOptional(configPath) : null;
|
|
1525
|
+
const production = await productionOnboardingStatusForCodingSessions(config).catch((err) => ({ error: safeError(err) }));
|
|
1526
|
+
const detailedStatus = {
|
|
1527
|
+
...status,
|
|
1199
1528
|
production,
|
|
1200
1529
|
};
|
|
1201
1530
|
|
|
1202
1531
|
if (args.json) {
|
|
1203
|
-
console.log(JSON.stringify(
|
|
1532
|
+
console.log(JSON.stringify(detailedStatus, null, 2));
|
|
1204
1533
|
return;
|
|
1205
1534
|
}
|
|
1206
1535
|
|
|
1207
1536
|
console.log("\nShepherd coding-session sync status\n");
|
|
1208
|
-
console.log(`Config: ${configPath ?? "not found"}`);
|
|
1209
|
-
if (userId) console.log(`User: ${userId}`);
|
|
1210
|
-
for (const probe of
|
|
1537
|
+
console.log(`Config: ${detailedStatus.configPath ?? "not found"}`);
|
|
1538
|
+
if (detailedStatus.userId) console.log(`User: ${detailedStatus.userId}`);
|
|
1539
|
+
for (const probe of detailedStatus.localFolders) {
|
|
1211
1540
|
console.log(`- ${probe.provider}: ${probe.path} ${probe.readable ? "readable" : `not readable (${probe.reason})`}`);
|
|
1212
1541
|
}
|
|
1213
|
-
if (launch) {
|
|
1214
|
-
|
|
1215
|
-
console.log(`LaunchAgent: ${launch.label} ${running ? "running" : "not running or unknown"}`);
|
|
1542
|
+
if (detailedStatus.launch) {
|
|
1543
|
+
console.log(`LaunchAgent: ${detailedStatus.launch.label} ${detailedStatus.launch.running ? "running" : "not running or unknown"}`);
|
|
1216
1544
|
} else {
|
|
1217
1545
|
console.log("LaunchAgent: not installed or unavailable");
|
|
1218
1546
|
}
|
|
1219
|
-
if (lastSync) {
|
|
1220
|
-
console.log(`Last sync: ${lastSync.finishedAt ?? "unknown"} (${lastSync.scanned ?? 0} scanned, ${lastSync.changed ?? 0} changed)`);
|
|
1547
|
+
if (detailedStatus.lastSync) {
|
|
1548
|
+
console.log(`Last sync: ${detailedStatus.lastSync.finishedAt ?? "unknown"} (${detailedStatus.lastSync.scanned ?? 0} scanned, ${detailedStatus.lastSync.changed ?? 0} changed)`);
|
|
1221
1549
|
} else {
|
|
1222
1550
|
console.log("Last sync: none recorded");
|
|
1223
1551
|
}
|
|
1224
|
-
console.log(`Queued unsent sessions: ${
|
|
1552
|
+
console.log(`Queued unsent sessions: ${detailedStatus.queueDepth}`);
|
|
1225
1553
|
if (production?.providers?.codingSessions) {
|
|
1226
1554
|
console.log(`Production provider: ${production.providers.codingSessions.connected ? "connected" : "not connected"}`);
|
|
1227
1555
|
} else if (production?.error) {
|
|
@@ -1264,6 +1592,7 @@ Usage:
|
|
|
1264
1592
|
npx -y ${PACKAGE_NAME}@latest agent --continue --granola-api-key <key> --messages-handle <value> --messages-chat-ids <ids|all>
|
|
1265
1593
|
npx -y ${PACKAGE_NAME}@latest agent --add-sources coding-sessions --name <name> --org <organization>
|
|
1266
1594
|
npx -y ${PACKAGE_NAME}@latest agent --status
|
|
1595
|
+
npx -y ${PACKAGE_NAME}@latest status
|
|
1267
1596
|
npx -y ${PACKAGE_NAME}@latest coding-sessions-status
|
|
1268
1597
|
npx -y ${PACKAGE_NAME}@latest messages-chats
|
|
1269
1598
|
npx -y ${PACKAGE_NAME}@latest granola-api-keys
|
|
@@ -1274,6 +1603,28 @@ The bare agent command is intended for coding-agent shells. For direct terminal
|
|
|
1274
1603
|
return;
|
|
1275
1604
|
}
|
|
1276
1605
|
|
|
1606
|
+
if (which === "status" || which === "sync-status" || which === "check") {
|
|
1607
|
+
console.log(`Shepherd sync status
|
|
1608
|
+
|
|
1609
|
+
Usage:
|
|
1610
|
+
npx -y ${PACKAGE_NAME}@latest status
|
|
1611
|
+
npx -y ${PACKAGE_NAME}@latest status --json
|
|
1612
|
+
|
|
1613
|
+
Shows the saved Shepherd account, connected cloud sources, production processing
|
|
1614
|
+
state, and local background sync health for Messages and Coding Sessions.
|
|
1615
|
+
|
|
1616
|
+
Aliases:
|
|
1617
|
+
sync-status
|
|
1618
|
+
check
|
|
1619
|
+
|
|
1620
|
+
Options:
|
|
1621
|
+
--json Print machine-readable status.
|
|
1622
|
+
--state <path> Local onboarding state file. Defaults to ~/.shepherd/raw-onboarding-agent.json.
|
|
1623
|
+
--help Show this help.
|
|
1624
|
+
`);
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1277
1628
|
if (which === "coding-sessions-agent") {
|
|
1278
1629
|
console.log(`Shepherd coding-session sync agent
|
|
1279
1630
|
|
|
@@ -1406,6 +1757,9 @@ Usage:
|
|
|
1406
1757
|
|
|
1407
1758
|
This command is installed into MCP clients. It reads ~/.shepherd/mcp.json and
|
|
1408
1759
|
proxies stdio MCP traffic to the authenticated production Shepherd MCP endpoint.
|
|
1760
|
+
It also exposes local npm-backed setup/status tools such as shepherd_status and
|
|
1761
|
+
shepherd_setup_coding_sessions so agents can answer what is enabled locally and
|
|
1762
|
+
guide coding-session setup.
|
|
1409
1763
|
|
|
1410
1764
|
Options:
|
|
1411
1765
|
--state <path> Token state file. Defaults to ~/.shepherd/mcp.json.
|
|
@@ -1420,6 +1774,7 @@ Options:
|
|
|
1420
1774
|
Usage:
|
|
1421
1775
|
npx -y ${PACKAGE_NAME}@latest
|
|
1422
1776
|
npx -y ${PACKAGE_NAME}@latest agent
|
|
1777
|
+
npx -y ${PACKAGE_NAME}@latest status
|
|
1423
1778
|
npx -y ${PACKAGE_NAME}@latest mcp-login
|
|
1424
1779
|
npx -y ${PACKAGE_NAME}@latest mcp-install
|
|
1425
1780
|
npx -y ${PACKAGE_NAME}@latest messages-chats
|
|
@@ -1479,6 +1834,16 @@ function printAgentContract() {
|
|
|
1479
1834
|
const payload = {
|
|
1480
1835
|
task: "Complete Shepherd customer-facing production raw-source onboarding for this user with the public customer setup flow.",
|
|
1481
1836
|
instruction: "Use this as a public onboarding checklist. Confirm required onboarding choices and fields directly with the user instead of assuming them from local repo context, shell identity, machine paths, or earlier conversations.",
|
|
1837
|
+
commonUserRequests: [
|
|
1838
|
+
{
|
|
1839
|
+
userSays: "Check what I've enabled for Shepherd.",
|
|
1840
|
+
agentShould: `Run ${command} status and summarize connected sources, local background agents, unreadable folders, queued items, and any next command it prints.`,
|
|
1841
|
+
},
|
|
1842
|
+
{
|
|
1843
|
+
userSays: "Help me set up coding agent sessions.",
|
|
1844
|
+
agentShould: `Ask for consent to sync Codex and Claude Code summaries, then run ${command} agent --login if there is no saved Shepherd login, ${command} agent --add-sources coding-sessions --name "<full_name>" --org "<organization>", ${command} agent --continue, and finally ${command} status.`,
|
|
1845
|
+
},
|
|
1846
|
+
],
|
|
1482
1847
|
askStyle: [
|
|
1483
1848
|
"Ask in short interactive prompts, not as a pasted checklist.",
|
|
1484
1849
|
"Start with selection questions to determine what the user wants connected.",
|
|
@@ -1547,13 +1912,15 @@ function printAgentContract() {
|
|
|
1547
1912
|
startCommand: `${command} agent --name "<full_name>" --org "<organization>"`,
|
|
1548
1913
|
addSourcesCommand: `${command} agent --add-sources coding-sessions --name "<full_name>" --org "<organization>"`,
|
|
1549
1914
|
continueCommand: `${command} agent --continue`,
|
|
1915
|
+
checkCommand: `${command} status`,
|
|
1550
1916
|
mcpLoginCommand: `${command} mcp-login`,
|
|
1551
1917
|
optionalContinueArgs: [
|
|
1552
1918
|
"--messages-handle \"<phone_or_apple_id>\" if local Messages is being connected",
|
|
1553
1919
|
"--messages-chat-ids \"<comma_separated_chat_ids>\" if local Messages is being connected, or --messages-chat-ids all when the user explicitly wants every current and future Messages chat watched",
|
|
1554
1920
|
"--granola-api-key \"<granola_key>\" if Granola is being connected",
|
|
1555
1921
|
],
|
|
1556
|
-
statusCommand: `${command}
|
|
1922
|
+
statusCommand: `${command} status`,
|
|
1923
|
+
agentStatusCommand: `${command} agent --status`,
|
|
1557
1924
|
messagesChatsCommand: `${command} messages-chats`,
|
|
1558
1925
|
messagesPermissions: {
|
|
1559
1926
|
macOS: "Local Messages raw sync needs Full Disk Access for the app running onboarding and for Node.js used by the background LaunchAgent. The Messages selector command validates local chat.db access, opens Full Disk Access settings if needed, and keeps checking until access works in interactive onboarding. Background sync install also checks that launchd can start the Messages agent. Contacts permission may also appear when resolving local contact names. The background Messages agent reloads Contacts on startup, watches AddressBook changes when available, and runs fallback contact sync so renamed contacts can hydrate prior ingested Messages rows for the token-bound customer account.",
|
|
@@ -1585,6 +1952,16 @@ Confirm onboarding choices and fields directly with the user instead of assuming
|
|
|
1585
1952
|
|
|
1586
1953
|
Ask with short interactive prompts, not as one pasted checklist. Do not paste this whole checklist into the chat unless the user explicitly asks to see it.
|
|
1587
1954
|
|
|
1955
|
+
Common user requests:
|
|
1956
|
+
- If the user asks "Check what I've enabled for Shepherd?", run:
|
|
1957
|
+
${payload.checkCommand}
|
|
1958
|
+
Then summarize connected sources, local background agents, unreadable folders, queued items, and the next command to fix anything missing.
|
|
1959
|
+
- If the user asks "Help me set up coding agent sessions", ask for consent to sync Codex and Claude Code summaries, then run login if needed, add only the coding-sessions source, continue setup, and finish by checking status:
|
|
1960
|
+
${payload.loginCommand}
|
|
1961
|
+
${payload.addSourcesCommand}
|
|
1962
|
+
${payload.continueCommand}
|
|
1963
|
+
${payload.checkCommand}
|
|
1964
|
+
|
|
1588
1965
|
Start with selection questions to determine intent:
|
|
1589
1966
|
1. Organization: Join existing org, or Create new org.
|
|
1590
1967
|
2. Sources: Google Workspace (Gmail/Drive/Docs/Calendar/Sheets/Slides/Tasks/Contacts), Slack, Granola, Messages, Coding Sessions (Codex/Claude Code summaries). Allow multi-select if your interface supports it.
|
|
@@ -2509,6 +2886,7 @@ function readLaunchctlPrint(domainLabel) {
|
|
|
2509
2886
|
try {
|
|
2510
2887
|
return execFileSync("launchctl", ["print", domainLabel], {
|
|
2511
2888
|
encoding: "utf8",
|
|
2889
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2512
2890
|
timeout: 5_000,
|
|
2513
2891
|
});
|
|
2514
2892
|
} catch (err) {
|
|
@@ -4317,15 +4695,36 @@ function extractCodexCommands(payloads) {
|
|
|
4317
4695
|
function extractClaudeCommands(lines) {
|
|
4318
4696
|
const commands = [];
|
|
4319
4697
|
for (const line of lines) {
|
|
4320
|
-
const
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4698
|
+
for (const command of extractCommandStrings(line.toolUseResult ?? line.message?.content ?? line)) {
|
|
4699
|
+
if (isLikelyShellCommand(command)) {
|
|
4700
|
+
commands.push({ command: redactText(command, 600), exitCode: null, summary: null });
|
|
4701
|
+
}
|
|
4324
4702
|
}
|
|
4325
4703
|
}
|
|
4326
4704
|
return commands.slice(-100);
|
|
4327
4705
|
}
|
|
4328
4706
|
|
|
4707
|
+
function extractCommandStrings(value, depth = 0) {
|
|
4708
|
+
if (depth > 6 || value == null) return [];
|
|
4709
|
+
if (typeof value === "string") return [];
|
|
4710
|
+
if (Array.isArray(value)) return value.flatMap((item) => extractCommandStrings(item, depth + 1));
|
|
4711
|
+
if (typeof value !== "object") return [];
|
|
4712
|
+
|
|
4713
|
+
const commands = [];
|
|
4714
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
4715
|
+
if ((key === "command" || key === "input") && typeof nested === "string" && nested.trim()) {
|
|
4716
|
+
commands.push(nested);
|
|
4717
|
+
continue;
|
|
4718
|
+
}
|
|
4719
|
+
commands.push(...extractCommandStrings(nested, depth + 1));
|
|
4720
|
+
}
|
|
4721
|
+
return commands;
|
|
4722
|
+
}
|
|
4723
|
+
|
|
4724
|
+
function isLikelyShellCommand(value) {
|
|
4725
|
+
return /\b(?:git|npm|pnpm|yarn|bun|pytest|vitest|cargo|go|python|node|tsc|ruff|eslint|make)\b/.test(value);
|
|
4726
|
+
}
|
|
4727
|
+
|
|
4329
4728
|
async function repoMetadata(cwd) {
|
|
4330
4729
|
const base = { fullName: null, remote: null, branch: null, commit: null };
|
|
4331
4730
|
if (!cwd) return base;
|
|
@@ -4474,20 +4873,12 @@ function redactText(value, maxLength = 1000) {
|
|
|
4474
4873
|
.replace(/-----BEGIN [^-]+PRIVATE KEY-----[\s\S]*?-----END [^-]+PRIVATE KEY-----/g, "[redacted-private-key]")
|
|
4475
4874
|
.replace(/\b(?:sk|pk|rk|ghp|github_pat|xox[baprs])_[A-Za-z0-9_=-]{12,}\b/g, "[redacted-token]")
|
|
4476
4875
|
.replace(/\b[A-Za-z0-9._%+-]+:[A-Za-z0-9._%+-]+@/g, "[redacted-credentials]@")
|
|
4477
|
-
.replace(/\b(
|
|
4876
|
+
.replace(/\b(authorization|x-api-key|api[_-]?key|token|secret|password)(\s*[:=]\s*)['"]?[^'"\s]+/gi, "$1$2[redacted]")
|
|
4478
4877
|
.replace(/(OPENAI_API_KEY|FIREWORKS_API_KEY|ANTHROPIC_API_KEY|DATABASE_URL|REDIS_URL)=\S+/g, "$1=[redacted]")
|
|
4479
4878
|
.replace(new RegExp(homedir().replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "~")
|
|
4480
4879
|
.slice(0, maxLength);
|
|
4481
4880
|
}
|
|
4482
4881
|
|
|
4483
|
-
function unescapeJsonString(value) {
|
|
4484
|
-
try {
|
|
4485
|
-
return JSON.parse(`"${value.replace(/"/g, '\\"')}"`);
|
|
4486
|
-
} catch {
|
|
4487
|
-
return value;
|
|
4488
|
-
}
|
|
4489
|
-
}
|
|
4490
|
-
|
|
4491
4882
|
function hashString(value) {
|
|
4492
4883
|
return createHash("sha256").update(String(value ?? "")).digest("hex");
|
|
4493
4884
|
}
|
|
@@ -4515,6 +4906,35 @@ function codingSessionsStatusFile(userId) {
|
|
|
4515
4906
|
return path;
|
|
4516
4907
|
}
|
|
4517
4908
|
|
|
4909
|
+
async function messagesConfigPathForUser(userId) {
|
|
4910
|
+
if (!userId) return null;
|
|
4911
|
+
const path = join(homedir(), ".shepherd", "raw-messages", `${userId}.json`);
|
|
4912
|
+
return existsSync(path) ? path : null;
|
|
4913
|
+
}
|
|
4914
|
+
|
|
4915
|
+
async function codingSessionsConfigPathForUser(userId) {
|
|
4916
|
+
if (!userId) return null;
|
|
4917
|
+
const path = join(homedir(), ".shepherd", "coding-sessions", `${userId}.json`);
|
|
4918
|
+
return existsSync(path) ? path : null;
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4921
|
+
async function latestMessagesConfigPath() {
|
|
4922
|
+
const dir = join(homedir(), ".shepherd", "raw-messages");
|
|
4923
|
+
try {
|
|
4924
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
4925
|
+
const files = [];
|
|
4926
|
+
for (const entry of entries) {
|
|
4927
|
+
if (!entry.isFile() || !entry.name.endsWith(".json") || entry.name.includes("-queue")) continue;
|
|
4928
|
+
const path = join(dir, entry.name);
|
|
4929
|
+
const info = await stat(path).catch(() => null);
|
|
4930
|
+
if (info) files.push({ path, mtimeMs: info.mtimeMs });
|
|
4931
|
+
}
|
|
4932
|
+
return files.sort((a, b) => b.mtimeMs - a.mtimeMs)[0]?.path ?? null;
|
|
4933
|
+
} catch {
|
|
4934
|
+
return null;
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
|
|
4518
4938
|
async function latestCodingSessionsConfigPath() {
|
|
4519
4939
|
const dir = join(homedir(), ".shepherd", "coding-sessions");
|
|
4520
4940
|
try {
|