artemys 0.3.1 → 0.3.3

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.
Files changed (144) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +14 -20
  3. package/dist/cli/config.d.ts +21 -0
  4. package/dist/cli/config.d.ts.map +1 -0
  5. package/dist/cli/config.js +97 -0
  6. package/dist/cli/config.js.map +1 -0
  7. package/dist/cli/index.d.ts +5 -0
  8. package/dist/cli/index.d.ts.map +1 -1
  9. package/dist/cli/index.js +310 -106
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/cli/init.d.ts +3 -0
  12. package/dist/cli/init.d.ts.map +1 -0
  13. package/dist/cli/init.js +177 -0
  14. package/dist/cli/init.js.map +1 -0
  15. package/dist/coffeeshop/client.d.ts +5 -10
  16. package/dist/coffeeshop/client.d.ts.map +1 -1
  17. package/dist/coffeeshop/client.js +43 -15
  18. package/dist/coffeeshop/client.js.map +1 -1
  19. package/dist/coffeeshop/index.d.ts +1 -1
  20. package/dist/coffeeshop/index.d.ts.map +1 -1
  21. package/dist/coffeeshop/index.js +1 -1
  22. package/dist/coffeeshop/index.js.map +1 -1
  23. package/dist/coffeeshop/schemas.d.ts +22 -16
  24. package/dist/coffeeshop/schemas.d.ts.map +1 -1
  25. package/dist/coffeeshop/schemas.js +27 -30
  26. package/dist/coffeeshop/schemas.js.map +1 -1
  27. package/dist/discovery/agent-card.schema.d.ts +4 -2
  28. package/dist/discovery/agent-card.schema.d.ts.map +1 -1
  29. package/dist/discovery/agent-card.schema.js +11 -3
  30. package/dist/discovery/agent-card.schema.js.map +1 -1
  31. package/dist/discovery/index.d.ts +2 -1
  32. package/dist/discovery/index.d.ts.map +1 -1
  33. package/dist/discovery/index.js +2 -1
  34. package/dist/discovery/index.js.map +1 -1
  35. package/dist/discovery/reserved-handles.d.ts +2 -0
  36. package/dist/discovery/reserved-handles.d.ts.map +1 -0
  37. package/dist/discovery/reserved-handles.js +18 -0
  38. package/dist/discovery/reserved-handles.js.map +1 -0
  39. package/dist/integrations/job-boards/http.d.ts +4 -1
  40. package/dist/integrations/job-boards/http.d.ts.map +1 -1
  41. package/dist/integrations/job-boards/http.js +4 -3
  42. package/dist/integrations/job-boards/http.js.map +1 -1
  43. package/dist/integrations/job-boards/jsonld.d.ts.map +1 -1
  44. package/dist/integrations/job-boards/jsonld.js +28 -0
  45. package/dist/integrations/job-boards/jsonld.js.map +1 -1
  46. package/dist/integrations/job-boards/remotive.d.ts.map +1 -1
  47. package/dist/integrations/job-boards/remotive.js +6 -35
  48. package/dist/integrations/job-boards/remotive.js.map +1 -1
  49. package/dist/mcp-server/index.d.ts +1 -1
  50. package/dist/mcp-server/index.d.ts.map +1 -1
  51. package/dist/mcp-server/index.js +1 -1
  52. package/dist/mcp-server/index.js.map +1 -1
  53. package/dist/mcp-server/persistence.d.ts +0 -4
  54. package/dist/mcp-server/persistence.d.ts.map +1 -1
  55. package/dist/mcp-server/persistence.js +25 -30
  56. package/dist/mcp-server/persistence.js.map +1 -1
  57. package/dist/mcp-server/prompts.d.ts +20 -0
  58. package/dist/mcp-server/prompts.d.ts.map +1 -0
  59. package/dist/mcp-server/prompts.js +44 -0
  60. package/dist/mcp-server/prompts.js.map +1 -0
  61. package/dist/mcp-server/resources.d.ts +2 -1
  62. package/dist/mcp-server/resources.d.ts.map +1 -1
  63. package/dist/mcp-server/resources.js +15 -0
  64. package/dist/mcp-server/resources.js.map +1 -1
  65. package/dist/mcp-server/server.d.ts +3 -0
  66. package/dist/mcp-server/server.d.ts.map +1 -1
  67. package/dist/mcp-server/server.js +40 -3
  68. package/dist/mcp-server/server.js.map +1 -1
  69. package/dist/mcp-server/state.d.ts +0 -1
  70. package/dist/mcp-server/state.d.ts.map +1 -1
  71. package/dist/mcp-server/state.js +0 -4
  72. package/dist/mcp-server/state.js.map +1 -1
  73. package/dist/mcp-server/tools/common.d.ts +1 -219
  74. package/dist/mcp-server/tools/common.d.ts.map +1 -1
  75. package/dist/mcp-server/tools/common.js +1 -3
  76. package/dist/mcp-server/tools/common.js.map +1 -1
  77. package/dist/mcp-server/tools/index.d.ts.map +1 -1
  78. package/dist/mcp-server/tools/index.js +2 -0
  79. package/dist/mcp-server/tools/index.js.map +1 -1
  80. package/dist/mcp-server/tools/messaging.d.ts +11 -0
  81. package/dist/mcp-server/tools/messaging.d.ts.map +1 -1
  82. package/dist/mcp-server/tools/messaging.js +14 -4
  83. package/dist/mcp-server/tools/messaging.js.map +1 -1
  84. package/dist/mcp-server/tools/protocol.js.map +1 -1
  85. package/dist/mcp-server/tools/resume.d.ts +3 -0
  86. package/dist/mcp-server/tools/resume.d.ts.map +1 -0
  87. package/dist/mcp-server/tools/resume.js +61 -0
  88. package/dist/mcp-server/tools/resume.js.map +1 -0
  89. package/dist/mcp-server/tools/talent.d.ts +25 -0
  90. package/dist/mcp-server/tools/talent.d.ts.map +1 -1
  91. package/dist/mcp-server/tools/talent.js +123 -43
  92. package/dist/mcp-server/tools/talent.js.map +1 -1
  93. package/dist/protocol/envelope.schema.d.ts +1 -1
  94. package/dist/protocol/envelope.schema.d.ts.map +1 -1
  95. package/dist/protocol/envelope.schema.js +2 -1
  96. package/dist/protocol/envelope.schema.js.map +1 -1
  97. package/dist/protocol/index.d.ts +1 -2
  98. package/dist/protocol/index.d.ts.map +1 -1
  99. package/dist/protocol/index.js +1 -2
  100. package/dist/protocol/index.js.map +1 -1
  101. package/dist/protocol/talent.schema.d.ts +115 -0
  102. package/dist/protocol/talent.schema.d.ts.map +1 -1
  103. package/dist/protocol/talent.schema.js +61 -3
  104. package/dist/protocol/talent.schema.js.map +1 -1
  105. package/dist/resume/index.d.ts +4 -0
  106. package/dist/resume/index.d.ts.map +1 -0
  107. package/dist/resume/index.js +4 -0
  108. package/dist/resume/index.js.map +1 -0
  109. package/dist/resume/parse-resume.d.ts +8 -0
  110. package/dist/resume/parse-resume.d.ts.map +1 -0
  111. package/dist/resume/parse-resume.js +124 -0
  112. package/dist/resume/parse-resume.js.map +1 -0
  113. package/dist/resume/providers/anthropic.d.ts +2 -0
  114. package/dist/resume/providers/anthropic.d.ts.map +1 -0
  115. package/dist/resume/providers/anthropic.js +26 -0
  116. package/dist/resume/providers/anthropic.js.map +1 -0
  117. package/dist/resume/providers/google.d.ts +2 -0
  118. package/dist/resume/providers/google.d.ts.map +1 -0
  119. package/dist/resume/providers/google.js +24 -0
  120. package/dist/resume/providers/google.js.map +1 -0
  121. package/dist/resume/providers/openai.d.ts +2 -0
  122. package/dist/resume/providers/openai.d.ts.map +1 -0
  123. package/dist/resume/providers/openai.js +25 -0
  124. package/dist/resume/providers/openai.js.map +1 -0
  125. package/dist/resume/to-card.d.ts +7 -0
  126. package/dist/resume/to-card.d.ts.map +1 -0
  127. package/dist/resume/to-card.js +63 -0
  128. package/dist/resume/to-card.js.map +1 -0
  129. package/package.json +10 -1
  130. package/skills/artemys-talent/SKILL.md +258 -0
  131. package/skills/artemys-talent/references/TOOLS.md +479 -0
  132. package/skills/artemys-talent/scripts/setup.sh +88 -0
  133. package/dist/discovery/intro-decision.schema.d.ts +0 -17
  134. package/dist/discovery/intro-decision.schema.d.ts.map +0 -1
  135. package/dist/discovery/intro-decision.schema.js +0 -11
  136. package/dist/discovery/intro-decision.schema.js.map +0 -1
  137. package/dist/discovery/intro-request.schema.d.ts +0 -11
  138. package/dist/discovery/intro-request.schema.d.ts.map +0 -1
  139. package/dist/discovery/intro-request.schema.js +0 -12
  140. package/dist/discovery/intro-request.schema.js.map +0 -1
  141. package/dist/protocol/transport.schema.d.ts +0 -27
  142. package/dist/protocol/transport.schema.d.ts.map +0 -1
  143. package/dist/protocol/transport.schema.js +0 -18
  144. package/dist/protocol/transport.schema.js.map +0 -1
package/dist/cli/index.js CHANGED
@@ -1,15 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { readFile } from "node:fs/promises";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
3
  import process from "node:process";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { CoffeeShopClient } from "../coffeeshop/index.js";
6
6
  import { AgentCardSchema, DiscoveryQuerySchema, } from "../discovery/index.js";
7
- import { ConversationTracker, ProfileStore, SqliteMcpPersistence, createPersistentConversationTracker, createPersistentProfileStore, startArtemysMcpServer, } from "../mcp-server/index.js";
8
- import { registerMessagingTools } from "../mcp-server/tools/messaging.js";
9
- import { registerTalentTools } from "../mcp-server/tools/talent.js";
10
- const DEFAULT_COFFEESHOP_URL = "https://coffeeshop.artemys.ai";
11
- const DEFAULT_MAX_TRACKED_CONVERSATIONS = 1_000;
12
- const DEFAULT_CONVERSATION_TTL_MS = 24 * 60 * 60 * 1000;
7
+ import { ConversationTracker, DEFAULT_CONVERSATION_TTL_MS, DEFAULT_MAX_TRACKED_CONVERSATIONS, ProfileStore, SqliteMcpPersistence, createPersistentConversationTracker, createPersistentProfileStore, startArtemysMcpServer, } from "../mcp-server/index.js";
8
+ import { CandidateSnapshotSchema } from "../protocol/index.js";
9
+ import { checkInbox, respondToMessage } from "../mcp-server/tools/messaging.js";
10
+ import { searchOpportunities, expressInterest, updateProfile, getMyApplications } from "../mcp-server/tools/talent.js";
11
+ import { getConfigPath, getFlagString, loadConfig, resolveAgentCardPath, resolveApiKey, resolveCoffeeShopUrl, saveConfig, } from "./config.js";
13
12
  function parseArgs(argv) {
14
13
  const positionals = [];
15
14
  const flags = {};
@@ -44,10 +43,6 @@ function parseArgs(argv) {
44
43
  }
45
44
  return { positionals, flags };
46
45
  }
47
- function getFlagString(flags, key) {
48
- const value = flags[key];
49
- return typeof value === "string" ? value : undefined;
50
- }
51
46
  function getFlagBoolean(flags, key) {
52
47
  const value = flags[key];
53
48
  if (typeof value === "boolean") {
@@ -85,39 +80,51 @@ function getPersistOption(flags) {
85
80
  }
86
81
  return undefined;
87
82
  }
88
- function getCsvFlagList(flags, key) {
89
- const value = getFlagString(flags, key);
90
- if (!value) {
91
- return undefined;
92
- }
93
- const items = value
94
- .split(",")
95
- .map((entry) => entry.trim())
96
- .filter((entry) => entry.length > 0);
97
- return items.length > 0 ? items : undefined;
83
+ async function outputJson(data, outputPath) {
84
+ const json = JSON.stringify(data, null, 2);
85
+ if (outputPath) {
86
+ await writeFile(outputPath, json + "\n", "utf-8");
87
+ console.log(`Written to ${outputPath}`);
88
+ }
89
+ else {
90
+ console.log(json);
91
+ }
98
92
  }
99
93
  function printHelp() {
100
94
  console.log(`
101
95
  artemys - Protocol + Coffee Shop + MCP Server SDK CLI
102
96
 
103
97
  Commands:
98
+ start Initialize agent identity and register with Coffee Shop
99
+ whoami Show current agent identity and hub status
100
+ doctor Run diagnostics on agent setup
104
101
  mcp-server Start Artemys MCP server
105
102
  talent Candidate talent workflow commands
106
103
  register Register an agent card with Coffee Shop
107
104
  discover Discover agents via Coffee Shop
108
105
  rotate-key Rotate Coffee Shop API key
106
+ parse-resume Parse a resume file (PDF or text) into structured JSON via LLM
107
+ to-card Convert a FullResume JSON to an anonymous CandidateCard
109
108
  version Show CLI version
110
109
  help Show this help message
111
110
 
112
111
  Examples:
113
- artemys mcp-server --agent-card ./agent-card.json
112
+ artemys start
113
+ artemys start --display-name "My Agent" --role candidate --handle my-agent
114
+ artemys whoami
115
+ artemys doctor
116
+ artemys mcp-server
114
117
  artemys mcp-server --agent-card ./agent-card.json --persist
118
+ artemys mcp-server --transport sse --mcp-token my-secret
115
119
  artemys register --agent-card ./agent-card.json
116
- artemys discover --requester-agent-id agent-123 --role talent_agent
117
- artemys talent search --agent-card ./agent-card.json --limit 20
118
- artemys talent apply --agent-card ./agent-card.json --job-id job-123 --persist
119
- artemys talent profile --agent-card ./agent-card.json --profile-file ./candidate-profile.json --persist
120
- artemys rotate-key --agent-id agent-123 --api-key old-key
120
+ artemys discover --requester-agent-id @my-agent --role talent_agent
121
+ artemys talent search --skills TypeScript,React --remote --location "San Francisco"
122
+ artemys talent apply --job-id job-123 --persist
123
+ artemys talent profile --profile-file ./candidate-profile.json --persist
124
+ artemys talent respond --message-id msg-456 --content '{"text":"Sounds great"}'
125
+ artemys rotate-key --agent-id @my-agent --api-key old-key
126
+ artemys parse-resume ./resume.pdf --provider anthropic --api-key sk-... # or set ANTHROPIC_API_KEY
127
+ artemys to-card ./resume.json --output ./card.json
121
128
  `);
122
129
  }
123
130
  function printTalentHelp() {
@@ -125,20 +132,26 @@ function printTalentHelp() {
125
132
  artemys talent - Candidate talent workflow commands
126
133
 
127
134
  Subcommands:
128
- search Search for matching job opportunities
129
- apply Submit an application for a job posting
130
- status Check inbox messages
131
- profile Update stored candidate profile
132
- help Show this help message
135
+ whoami Show current candidate profile summary
136
+ search Search for matching job opportunities
137
+ apply Submit an application for a job posting
138
+ applications List your submitted applications
139
+ status Check inbox messages
140
+ respond Reply to a message in your inbox
141
+ profile Update stored candidate profile
142
+ help Show this help message
133
143
 
134
144
  Examples:
135
- artemys talent search --agent-card ./agent-card.json --limit 20
145
+ artemys talent whoami --agent-card ./agent-card.json --persist
146
+ artemys talent search --agent-card ./agent-card.json --skills TypeScript,React --remote --limit 20
136
147
  artemys talent apply --agent-card ./agent-card.json --job-id job-123 --match-reasoning "Strong backend fit"
148
+ artemys talent applications --status pending --agent-card ./agent-card.json
137
149
  artemys talent status --agent-card ./agent-card.json --unread-only --persist
150
+ artemys talent respond --agent-card ./agent-card.json --message-id msg-456 --content '{"text":"Interested, available next week"}'
138
151
  artemys talent profile --agent-card ./agent-card.json --profile-file ./candidate-profile.json --sync-agent-card --persist ~/.artemys/state.db
139
152
 
140
153
  Common flags:
141
- --agent-card <path> Required for all talent subcommands except help
154
+ --agent-card <path> Path to agent card (optional if 'artemys start' was run)
142
155
  --coffeeshop-url <url> Override Coffee Shop base URL
143
156
  --persist [path] Persist profile + conversations to SQLite (default: ~/.artemys/state.db)
144
157
  `);
@@ -148,13 +161,21 @@ async function readAgentCardFromPath(pathValue) {
148
161
  const parsed = JSON.parse(raw);
149
162
  return AgentCardSchema.parse(parsed);
150
163
  }
164
+ async function resolveAndReadAgentCard(flags) {
165
+ const cardPath = resolveAgentCardPath(flags);
166
+ if (!cardPath) {
167
+ throw new Error("No agent card found. Run 'artemys start' to get started, or use --agent-card <path>.");
168
+ }
169
+ return readAgentCardFromPath(cardPath);
170
+ }
151
171
  function buildNetworkDeps(args, agentCard) {
152
- const coffeeShopUrl = getFlagString(args.flags, "coffeeshop-url") ?? DEFAULT_COFFEESHOP_URL;
153
- const hasCoffeeShopApiKey = typeof process.env.ARTEMYS_COFFEESHOP_API_KEY === "string" &&
154
- process.env.ARTEMYS_COFFEESHOP_API_KEY.length > 0;
172
+ const config = loadConfig();
173
+ const coffeeShopUrl = resolveCoffeeShopUrl(args.flags, config);
174
+ const apiKey = resolveApiKey(config);
175
+ const hasCoffeeShopApiKey = typeof apiKey === "string" && apiKey.length > 0;
155
176
  const coffeeShopClient = new CoffeeShopClient({
156
177
  baseUrl: coffeeShopUrl,
157
- apiKey: process.env.ARTEMYS_COFFEESHOP_API_KEY,
178
+ apiKey,
158
179
  agentId: agentCard.agent_id,
159
180
  });
160
181
  return {
@@ -188,48 +209,100 @@ function buildTalentState(flags) {
188
209
  },
189
210
  };
190
211
  }
191
- function buildTalentToolHandlers(deps) {
192
- const handlers = new Map();
193
- const mockServer = {
194
- registerTool: (name, _config, handler) => {
195
- handlers.set(name, handler);
196
- },
197
- };
198
- registerTalentTools(mockServer, deps);
199
- registerMessagingTools(mockServer, deps);
200
- return handlers;
201
- }
202
- function extractToolResponseData(response) {
203
- const structuredData = response.structuredContent?.data;
204
- if (structuredData !== undefined) {
205
- return structuredData;
206
- }
207
- const textContent = response.content[0]?.text;
208
- if (typeof textContent !== "string" || textContent.length === 0) {
209
- return null;
212
+ async function whoamiCommand(args) {
213
+ const config = loadConfig();
214
+ if (!config) {
215
+ console.error("Not initialized. Run 'artemys start'.");
216
+ process.exitCode = 1;
217
+ return;
210
218
  }
219
+ const agentCard = await resolveAndReadAgentCard(args.flags);
220
+ const { coffeeShopClient, hasCoffeeShopApiKey } = buildNetworkDeps(args, agentCard);
221
+ let hubStatus = "unknown";
211
222
  try {
212
- return JSON.parse(textContent);
223
+ const remote = await coffeeShopClient.getCard(agentCard.agent_id);
224
+ hubStatus = remote ? "connected" : "not found";
213
225
  }
214
226
  catch {
215
- return textContent;
227
+ hubStatus = "unreachable";
216
228
  }
229
+ const cardPath = resolveAgentCardPath(args.flags);
230
+ const configPath = getConfigPath();
231
+ console.log(`
232
+ Agent Identity
233
+ Handle: ${agentCard.agent_id}
234
+ Display name: ${agentCard.display_name}
235
+ Role: ${agentCard.role}
236
+ Coffee Shop: ${hubStatus}
237
+ API key: ${hasCoffeeShopApiKey ? "configured" : "not set"}
238
+ Agent card: ${cardPath ?? "not found"}
239
+ Config: ${configPath}
240
+ `);
217
241
  }
218
- async function invokeTalentTool(handlers, toolName, args) {
219
- const handler = handlers.get(toolName);
220
- if (!handler) {
221
- throw new Error(`Talent tool '${toolName}' is unavailable`);
242
+ async function doctorCommand(args) {
243
+ const checks = [];
244
+ // 1. Node.js version
245
+ const major = Number.parseInt(process.version.slice(1).split(".")[0], 10);
246
+ checks.push({
247
+ label: "Node.js >= 22",
248
+ ok: major >= 22,
249
+ detail: process.version,
250
+ });
251
+ // 2. Config exists
252
+ const config = loadConfig();
253
+ checks.push({
254
+ label: "Config exists",
255
+ ok: config !== null,
256
+ detail: config ? getConfigPath() : "Run 'artemys start'",
257
+ });
258
+ // 3. Agent card
259
+ let agentCard = null;
260
+ try {
261
+ agentCard = await resolveAndReadAgentCard(args.flags);
262
+ checks.push({ label: "Agent card valid", ok: true });
263
+ }
264
+ catch (err) {
265
+ checks.push({
266
+ label: "Agent card valid",
267
+ ok: false,
268
+ detail: err instanceof Error ? err.message : "Failed to read",
269
+ });
270
+ }
271
+ // 4 & 5. Coffee Shop reachable + API key valid (run in parallel)
272
+ if (config) {
273
+ const coffeeShopUrl = resolveCoffeeShopUrl(args.flags, config);
274
+ const healthCheck = fetch(`${coffeeShopUrl}/health`, {
275
+ signal: AbortSignal.timeout(5_000),
276
+ })
277
+ .then((response) => ({ ok: response.ok, detail: coffeeShopUrl }))
278
+ .catch(() => ({ ok: false, detail: coffeeShopUrl }));
279
+ const apiKeyCheck = agentCard
280
+ ? buildNetworkDeps(args, agentCard)
281
+ .coffeeShopClient.getCard(agentCard.agent_id)
282
+ .then((remote) => ({ ok: remote !== null, detail: undefined }))
283
+ .catch(() => ({ ok: false, detail: "getCard failed" }))
284
+ : Promise.resolve({ ok: false, detail: "Skipped (no agent card)" });
285
+ const [healthResult, apiKeyResult] = await Promise.all([healthCheck, apiKeyCheck]);
286
+ checks.push({ label: "Coffee Shop reachable", ...healthResult });
287
+ checks.push({ label: "API key valid", ...apiKeyResult });
288
+ }
289
+ else {
290
+ checks.push({ label: "Coffee Shop reachable", ok: false, detail: "No config" });
291
+ checks.push({ label: "API key valid", ok: false, detail: "Skipped (no config or card)" });
292
+ }
293
+ for (const check of checks) {
294
+ const status = check.ok ? "[OK]" : "[FAIL]";
295
+ const detail = check.detail ? ` (${check.detail})` : "";
296
+ console.log(` ${status} ${check.label}${detail}`);
297
+ }
298
+ const allOk = checks.every((c) => c.ok);
299
+ if (!allOk) {
300
+ process.exitCode = 1;
222
301
  }
223
- const response = await handler(args);
224
- return extractToolResponseData(response);
225
302
  }
226
303
  async function registerCommand(args) {
227
- const cardPath = getFlagString(args.flags, "agent-card");
228
- if (!cardPath) {
229
- throw new Error("Missing required --agent-card <path>");
230
- }
231
- const card = await readAgentCardFromPath(cardPath);
232
- const coffeeShopUrl = getFlagString(args.flags, "coffeeshop-url") ?? DEFAULT_COFFEESHOP_URL;
304
+ const card = await resolveAndReadAgentCard(args.flags);
305
+ const coffeeShopUrl = resolveCoffeeShopUrl(args.flags, loadConfig());
233
306
  const client = new CoffeeShopClient({ baseUrl: coffeeShopUrl });
234
307
  const result = await client.register(card);
235
308
  console.log(JSON.stringify(result, null, 2));
@@ -256,21 +329,22 @@ async function discoverCommand(args) {
256
329
  ...(capabilities ? { capabilities_any: capabilities } : {}),
257
330
  ...(limitRaw ? { limit: Number.parseInt(limitRaw, 10) } : {}),
258
331
  });
259
- const coffeeShopUrl = getFlagString(args.flags, "coffeeshop-url") ?? DEFAULT_COFFEESHOP_URL;
332
+ const coffeeShopUrl = resolveCoffeeShopUrl(args.flags, loadConfig());
260
333
  const client = new CoffeeShopClient({ baseUrl: coffeeShopUrl });
261
334
  const results = await client.discover(query);
262
335
  console.log(JSON.stringify(results, null, 2));
263
336
  }
264
337
  async function rotateKeyCommand(args) {
265
- const agentId = getFlagString(args.flags, "agent-id");
338
+ const config = loadConfig();
339
+ const agentId = getFlagString(args.flags, "agent-id") ?? config?.agent_id;
266
340
  if (!agentId) {
267
- throw new Error("Missing required --agent-id <agent-id>");
341
+ throw new Error("Missing required --agent-id <agent-id> (or run 'artemys start')");
268
342
  }
269
- const apiKey = getFlagString(args.flags, "api-key");
343
+ const apiKey = getFlagString(args.flags, "api-key") ?? resolveApiKey(config);
270
344
  if (!apiKey) {
271
- throw new Error("Missing required --api-key <api-key>");
345
+ throw new Error("Missing required --api-key <api-key> (or run 'artemys start')");
272
346
  }
273
- const coffeeShopUrl = getFlagString(args.flags, "coffeeshop-url") ?? DEFAULT_COFFEESHOP_URL;
347
+ const coffeeShopUrl = resolveCoffeeShopUrl(args.flags, config);
274
348
  const client = new CoffeeShopClient({
275
349
  baseUrl: coffeeShopUrl,
276
350
  apiKey,
@@ -278,25 +352,30 @@ async function rotateKeyCommand(args) {
278
352
  });
279
353
  const result = await client.rotateApiKey();
280
354
  console.log(JSON.stringify(result, null, 2));
281
- console.log("Save api_key now. It is only returned at rotation time.");
355
+ // Update config.json if it exists and agent_id matches
356
+ if (config && config.agent_id === agentId) {
357
+ saveConfig({ ...config, api_key: result.api_key });
358
+ console.log("Config updated with new API key.");
359
+ }
360
+ else {
361
+ console.log("Save api_key now. It is only returned at rotation time.");
362
+ }
282
363
  }
283
364
  async function mcpServerCommand(args) {
284
- const cardPath = getFlagString(args.flags, "agent-card");
285
- if (!cardPath) {
286
- throw new Error("Missing required --agent-card <path>");
287
- }
288
365
  const transport = getFlagString(args.flags, "transport") === "sse" ? "sse" : "stdio";
289
366
  const portRaw = getFlagString(args.flags, "port");
290
367
  const port = portRaw ? Number.parseInt(portRaw, 10) : 3100;
291
368
  const persist = getPersistOption(args.flags);
292
- const agentCard = await readAgentCardFromPath(cardPath);
369
+ const agentCard = await resolveAndReadAgentCard(args.flags);
293
370
  const { coffeeShopClient } = buildNetworkDeps(args, agentCard);
371
+ const token = getFlagString(args.flags, "mcp-token") || process.env.ARTEMYS_MCP_TOKEN || undefined;
294
372
  const started = await startArtemysMcpServer({
295
373
  agentCard,
296
374
  coffeeShopClient,
297
375
  transport,
298
376
  port,
299
377
  ...(persist ? { persist } : {}),
378
+ ...(token ? { token } : {}),
300
379
  });
301
380
  if (transport === "sse") {
302
381
  console.error(`Artemys MCP server listening on http://127.0.0.1:${port}/mcp`);
@@ -314,25 +393,42 @@ async function talentCommand(args) {
314
393
  printTalentHelp();
315
394
  return;
316
395
  }
317
- const cardPath = getFlagString(args.flags, "agent-card");
318
- if (!cardPath) {
319
- throw new Error("Missing required --agent-card <path>");
320
- }
321
- const agentCard = await readAgentCardFromPath(cardPath);
396
+ const agentCard = await resolveAndReadAgentCard(args.flags);
322
397
  const { coffeeShopClient } = buildNetworkDeps(args, agentCard);
323
398
  const state = buildTalentState(args.flags);
324
399
  try {
325
- const handlers = buildTalentToolHandlers({
326
- coffeeShopClient,
327
- tracker: state.tracker,
328
- profileStore: state.profileStore,
329
- agentCard,
330
- });
400
+ if (subcommand === "whoami") {
401
+ const profile = state.profileStore.getProfile();
402
+ console.log(JSON.stringify({
403
+ agent_id: agentCard.agent_id,
404
+ display_name: agentCard.display_name,
405
+ has_profile: profile !== null,
406
+ profile,
407
+ }, null, 2));
408
+ return;
409
+ }
331
410
  if (subcommand === "search") {
411
+ const skillsRaw = getFlagString(args.flags, "skills");
412
+ const location = getFlagString(args.flags, "location");
413
+ const remote = getFlagBoolean(args.flags, "remote");
414
+ const minCompRaw = getFlagString(args.flags, "min-compensation");
415
+ const maxCompRaw = getFlagString(args.flags, "max-compensation");
332
416
  const limitRaw = getFlagString(args.flags, "limit");
333
- const data = await invokeTalentTool(handlers, "search_opportunities", {
334
- ...(limitRaw ? { limit: Number.parseInt(limitRaw, 10) } : {}),
335
- });
417
+ const filters = {};
418
+ if (skillsRaw)
419
+ filters.skills = skillsRaw.split(",").map((s) => s.trim());
420
+ if (location)
421
+ filters.location = location;
422
+ if (typeof remote === "boolean")
423
+ filters.remote = remote;
424
+ if (minCompRaw)
425
+ filters.min_compensation = Number(minCompRaw);
426
+ if (maxCompRaw)
427
+ filters.max_compensation = Number(maxCompRaw);
428
+ if (limitRaw)
429
+ filters.limit = Number(limitRaw);
430
+ const hasFilters = Object.keys(filters).length > 0;
431
+ const data = await searchOpportunities(coffeeShopClient, hasFilters ? filters : undefined);
336
432
  console.log(JSON.stringify(data, null, 2));
337
433
  return;
338
434
  }
@@ -342,21 +438,47 @@ async function talentCommand(args) {
342
438
  throw new Error("Missing required --job-id <job-id>");
343
439
  }
344
440
  const matchReasoning = getFlagString(args.flags, "match-reasoning");
345
- const data = await invokeTalentTool(handlers, "express_interest", {
346
- job_id: jobId,
347
- ...(matchReasoning ? { match_reasoning: matchReasoning } : {}),
348
- });
441
+ const storedProfile = state.profileStore.getProfile();
442
+ const snapshot = CandidateSnapshotSchema.parse(storedProfile ?? { display_name: agentCard.display_name });
443
+ const data = await expressInterest(coffeeShopClient, jobId, snapshot, matchReasoning ?? undefined);
349
444
  console.log(JSON.stringify(data, null, 2));
350
445
  return;
351
446
  }
352
447
  if (subcommand === "status") {
353
448
  const unreadOnly = getFlagBoolean(args.flags, "unread-only");
354
- const data = await invokeTalentTool(handlers, "check_inbox", {
355
- ...(typeof unreadOnly === "boolean" ? { unread_only: unreadOnly } : {}),
449
+ const data = await checkInbox(coffeeShopClient, {
450
+ unreadOnly: typeof unreadOnly === "boolean" ? unreadOnly : false,
356
451
  });
357
452
  console.log(JSON.stringify(data, null, 2));
358
453
  return;
359
454
  }
455
+ if (subcommand === "respond") {
456
+ const messageId = getFlagString(args.flags, "message-id");
457
+ if (!messageId) {
458
+ throw new Error("Missing required --message-id <id>");
459
+ }
460
+ const contentRaw = getFlagString(args.flags, "content");
461
+ if (!contentRaw) {
462
+ throw new Error("Missing required --content <json-string>");
463
+ }
464
+ let content;
465
+ try {
466
+ content = JSON.parse(contentRaw);
467
+ if (!content || typeof content !== "object" || Array.isArray(content)) {
468
+ throw new Error("--content must be a JSON object");
469
+ }
470
+ }
471
+ catch (err) {
472
+ if (err instanceof SyntaxError) {
473
+ throw new Error("--content must be valid JSON");
474
+ }
475
+ throw err;
476
+ }
477
+ const messageType = getFlagString(args.flags, "message-type");
478
+ const data = await respondToMessage(coffeeShopClient, messageId, content, messageType ?? undefined);
479
+ console.log(JSON.stringify(data, null, 2));
480
+ return;
481
+ }
360
482
  if (subcommand === "profile") {
361
483
  const profilePath = getFlagString(args.flags, "profile-file");
362
484
  if (!profilePath) {
@@ -368,13 +490,21 @@ async function talentCommand(args) {
368
490
  throw new Error("Profile file must contain a JSON object");
369
491
  }
370
492
  const syncAgentCard = getFlagBoolean(args.flags, "sync-agent-card");
371
- const data = await invokeTalentTool(handlers, "update_profile", {
372
- ...parsedProfile,
373
- ...(typeof syncAgentCard === "boolean" ? { sync_agent_card: syncAgentCard } : {}),
493
+ const snapshot = CandidateSnapshotSchema.parse(parsedProfile);
494
+ const data = await updateProfile(coffeeShopClient, snapshot, {
495
+ profileStore: state.profileStore,
496
+ agentCard,
497
+ syncAgentCard: typeof syncAgentCard === "boolean" ? syncAgentCard : false,
374
498
  });
375
499
  console.log(JSON.stringify(data, null, 2));
376
500
  return;
377
501
  }
502
+ if (subcommand === "applications") {
503
+ const status = getFlagString(args.flags, "status");
504
+ const data = await getMyApplications(coffeeShopClient, status ? { status } : undefined);
505
+ console.log(JSON.stringify(data, null, 2));
506
+ return;
507
+ }
378
508
  throw new Error(`Unknown talent subcommand: ${subcommand}`);
379
509
  }
380
510
  finally {
@@ -398,6 +528,19 @@ export async function runCli(argv = process.argv.slice(2)) {
398
528
  await versionCommand();
399
529
  return;
400
530
  }
531
+ if (command === "start" || command === "init") {
532
+ const { initCommand } = await import("./init.js");
533
+ await initCommand(args);
534
+ return;
535
+ }
536
+ if (command === "whoami") {
537
+ await whoamiCommand(args);
538
+ return;
539
+ }
540
+ if (command === "doctor") {
541
+ await doctorCommand(args);
542
+ return;
543
+ }
401
544
  if (command === "register") {
402
545
  await registerCommand(args);
403
546
  return;
@@ -418,6 +561,67 @@ export async function runCli(argv = process.argv.slice(2)) {
418
561
  await talentCommand(args);
419
562
  return;
420
563
  }
564
+ if (command === "parse-resume") {
565
+ const filePath = args.positionals[1];
566
+ if (!filePath) {
567
+ throw new Error("Missing required <file> argument");
568
+ }
569
+ const provider = getFlagString(args.flags, "provider");
570
+ if (!provider) {
571
+ throw new Error("Missing required --provider <provider>");
572
+ }
573
+ const validProviders = ["anthropic", "openai", "google"];
574
+ if (!validProviders.includes(provider)) {
575
+ throw new Error(`Unsupported provider "${provider}". Use: ${validProviders.join(", ")}`);
576
+ }
577
+ const envVarMap = {
578
+ anthropic: "ANTHROPIC_API_KEY",
579
+ openai: "OPENAI_API_KEY",
580
+ google: "GOOGLE_AI_API_KEY",
581
+ };
582
+ const envVar = envVarMap[provider];
583
+ const apiKey = getFlagString(args.flags, "api-key")
584
+ || (envVar ? process.env[envVar] : undefined);
585
+ if (!apiKey) {
586
+ throw new Error(`Missing API key. Set ${envVar} or pass --api-key`);
587
+ }
588
+ const model = getFlagString(args.flags, "model");
589
+ let text;
590
+ if (filePath.toLowerCase().endsWith(".pdf")) {
591
+ const [buffer, { default: pdf }] = await Promise.all([
592
+ readFile(filePath),
593
+ import("pdf-parse"),
594
+ ]);
595
+ try {
596
+ text = (await pdf(buffer)).text;
597
+ }
598
+ catch (err) {
599
+ throw new Error(`Failed to parse PDF "${filePath}": ${err instanceof Error ? err.message : String(err)}`);
600
+ }
601
+ }
602
+ else {
603
+ text = await readFile(filePath, "utf-8");
604
+ }
605
+ const { parseResume } = await import("../resume/index.js");
606
+ const result = await parseResume(text, {
607
+ provider: provider,
608
+ apiKey,
609
+ ...(model ? { model } : {}),
610
+ });
611
+ await outputJson(result, getFlagString(args.flags, "output"));
612
+ return;
613
+ }
614
+ if (command === "to-card") {
615
+ const filePath = args.positionals[1];
616
+ if (!filePath) {
617
+ throw new Error("Missing required <file> argument");
618
+ }
619
+ const raw = await readFile(filePath, "utf-8");
620
+ const { toCard, FullResumeSchema } = await import("../resume/index.js");
621
+ const resume = FullResumeSchema.parse(JSON.parse(raw));
622
+ await outputJson(toCard(resume), getFlagString(args.flags, "output"));
623
+ return;
624
+ }
421
625
  throw new Error(`Unknown command: ${command}`);
422
626
  }
423
627
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {