devto-mcp 0.5.3 → 0.7.0

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 CHANGED
@@ -14,12 +14,11 @@ This is a one-time global install. Each project is linked individually via `devt
14
14
 
15
15
  ## Setup
16
16
 
17
- 1. Create an account and get an API key at [devto.ai](https://devto.ai)
17
+ 1. Create an account at [devto.ai](https://devto.ai). Connect Jira from the onboarding screen at [devto.ai/onboard](https://devto.ai/onboard) — click **Connect with Jira** and authorize via OAuth. No tokens to copy.
18
18
 
19
- 2. Log in and configure your Anthropic key:
19
+ 2. Configure your Anthropic key:
20
20
 
21
21
  ```bash
22
- devto login
23
22
  devto config set anthropic-key sk-ant-xxxx
24
23
  ```
25
24
 
@@ -30,6 +29,10 @@ cd your-project
30
29
  devto init
31
30
  ```
32
31
 
32
+ `devto init` opens your browser to `devto.ai/cli/authorize`. A short code is displayed — confirm it matches in the browser, click **Approve**, and the CLI receives your API key silently. No copy-pasting.
33
+
34
+ If you prefer to paste an API key manually, select option **[2]** at the prompt. Get your key from [devto.ai/dashboard/keys](https://devto.ai/dashboard/keys).
35
+
33
36
  This creates:
34
37
  - `.devto.json` — project config (workspace URL, project key, provider)
35
38
  - `.devto/config.json` — credentials (API key, scoped to this project)
@@ -69,7 +72,7 @@ DevTo generates a structured plan. Run `confirm_plan` to push it to your project
69
72
 
70
73
  | Command | Description |
71
74
  |---|---|
72
- | `devto login` | Authenticate with your API key (global) |
75
+ | `devto login` | Authenticate globally (browser flow or paste API key) |
73
76
  | `devto init` | Link this project to DevTo |
74
77
  | `devto unlink` | Unlink this project (removes .devto.json, .devto/, registry entry) |
75
78
  | `devto status` | Show connection and active project config |
package/dist/cli.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * DevTo CLI — devto <command>
3
+ * devto CLI — devto <command>
4
4
  *
5
5
  * Commands:
6
- * devto login Authenticate with your DevTo API key
6
+ * devto login Authenticate with your devto API key
7
7
  * devto logout Clear global credentials
8
8
  * devto status Show current connection status
9
- * devto init Link this project to DevTo
10
- * devto unlink Unlink this project from DevTo
9
+ * devto init Link this project to devto
10
+ * devto unlink Unlink this project from devto
11
11
  * devto doctor Test full connection chain
12
12
  * devto sync Re-sync workspace configuration
13
13
  * devto verbose Toggle verbose mode
package/dist/cli.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  /**
4
- * DevTo CLI — devto <command>
4
+ * devto CLI — devto <command>
5
5
  *
6
6
  * Commands:
7
- * devto login Authenticate with your DevTo API key
7
+ * devto login Authenticate with your devto API key
8
8
  * devto logout Clear global credentials
9
9
  * devto status Show current connection status
10
- * devto init Link this project to DevTo
11
- * devto unlink Unlink this project from DevTo
10
+ * devto init Link this project to devto
11
+ * devto unlink Unlink this project from devto
12
12
  * devto doctor Test full connection chain
13
13
  * devto sync Re-sync workspace configuration
14
14
  * devto verbose Toggle verbose mode
@@ -54,7 +54,7 @@ const path = __importStar(require("path"));
54
54
  const os = __importStar(require("os"));
55
55
  const config_1 = require("./config");
56
56
  const command = process.argv[2];
57
- const NO_PROJECT_MESSAGE = `\nNo project linked in this directory.\nRun \`devto init\` to link this project to a DevTo workspace.\n`;
57
+ const NO_PROJECT_MESSAGE = `\nNo project linked in this directory.\nRun \`devto init\` to link this project to a devto workspace.\n`;
58
58
  /**
59
59
  * Require .devto.json in the directory tree.
60
60
  * If not found, print message and exit. Used by status, doctor, sync.
@@ -180,14 +180,116 @@ async function fetchWorkspaces(apiKey) {
180
180
  return [];
181
181
  }
182
182
  }
183
+ /**
184
+ * Open a URL in the user's default browser (best-effort, non-blocking).
185
+ * Silently swallows failures — the CLI also prints the URL so manual copy-paste works.
186
+ */
187
+ function openBrowser(url) {
188
+ try {
189
+ const { spawn } = require("child_process");
190
+ const platform = process.platform;
191
+ if (platform === "darwin") {
192
+ spawn("open", [url], { stdio: "ignore", detached: true }).unref();
193
+ }
194
+ else if (platform === "win32") {
195
+ spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true, shell: false }).unref();
196
+ }
197
+ else {
198
+ spawn("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
199
+ }
200
+ }
201
+ catch {
202
+ /* ignore — user can click the printed URL */
203
+ }
204
+ }
205
+ /**
206
+ * Device-code authorization: open the browser, display a short code, and poll
207
+ * the API until the user approves. Returns the issued API key. Throws on
208
+ * timeout, denial, or network failure.
209
+ */
210
+ async function browserLogin() {
211
+ const apiUrl = process.env.DEVTO_API_URL ?? "https://api.devto.ai";
212
+ let start;
213
+ try {
214
+ const res = await fetch(`${apiUrl}/api/v1/cli/device/start`, {
215
+ method: "POST",
216
+ headers: { "Content-Type": "application/json", "X-DevTo-Version": config_1.VERSION },
217
+ body: "{}",
218
+ });
219
+ if (!res.ok)
220
+ throw new Error(`${res.status}`);
221
+ start = (await res.json());
222
+ }
223
+ catch {
224
+ throw new Error("Could not reach devto to start browser sign-in. Check your internet connection.");
225
+ }
226
+ const openUrl = start.verification_url_complete ?? start.verification_url ?? "https://devto.ai/cli/authorize";
227
+ console.log("\nOpening browser to sign in...");
228
+ console.log(` ${openUrl}`);
229
+ console.log(`\n Verify the code matches: ${start.user_code}\n`);
230
+ openBrowser(openUrl);
231
+ // ── Poll until approved / denied / expired ──────────────────────────
232
+ const deadline = Date.now() + start.expires_in * 1000;
233
+ const intervalMs = Math.max(2, start.interval) * 1000;
234
+ const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
235
+ let frame = 0;
236
+ const spinner = setInterval(() => {
237
+ process.stdout.write(`\r ${spinnerFrames[frame++ % spinnerFrames.length]} Waiting for you to authorize...`);
238
+ }, 100);
239
+ try {
240
+ while (Date.now() < deadline) {
241
+ await new Promise((r) => setTimeout(r, intervalMs));
242
+ let res;
243
+ try {
244
+ res = await fetch(`${apiUrl}/api/v1/cli/device/poll`, {
245
+ method: "POST",
246
+ headers: { "Content-Type": "application/json", "X-DevTo-Version": config_1.VERSION },
247
+ body: JSON.stringify({ device_code: start.device_code }),
248
+ });
249
+ }
250
+ catch {
251
+ continue; // transient network failure — keep polling
252
+ }
253
+ if (res.status === 200) {
254
+ const data = (await res.json());
255
+ if (data.status === "approved" && data.api_key) {
256
+ clearInterval(spinner);
257
+ process.stdout.write("\r\x1b[K"); // clear spinner line
258
+ console.log(" Authorized.\n");
259
+ return data.api_key;
260
+ }
261
+ }
262
+ else if (res.status === 202) {
263
+ continue; // still pending
264
+ }
265
+ else {
266
+ const data = (await res.json().catch(() => ({})));
267
+ clearInterval(spinner);
268
+ process.stdout.write("\r\x1b[K");
269
+ if (data.code === "CLI_DEVICE_DENIED") {
270
+ throw new Error("Authorization was denied.");
271
+ }
272
+ if (data.code === "CLI_DEVICE_EXPIRED") {
273
+ throw new Error("Authorization expired. Run `devto init` again.");
274
+ }
275
+ throw new Error(data.message ?? "Authorization failed.");
276
+ }
277
+ }
278
+ throw new Error("Timed out waiting for authorization. Run `devto init` again.");
279
+ }
280
+ finally {
281
+ clearInterval(spinner);
282
+ process.stdout.write("\r\x1b[K");
283
+ }
284
+ }
183
285
  async function login() {
184
- console.log("\nDevTo Login (global)\n");
286
+ console.log("\ndevto Login (global)\n");
185
287
  console.log("Note: For per-project isolation, use `devto init` instead.");
186
288
  console.log(" `devto login` sets a global fallback key.\n");
187
289
  console.log("Get your API key from: https://devto.ai/dashboard/keys\n");
188
290
  const apiKey = await prompt("Paste your API key: ", true);
189
291
  if (!apiKey || !apiKey.startsWith("devto_")) {
190
- console.error("\nInvalid key format. DevTo API keys start with 'devto_'.\nGet your key at https://devto.ai/dashboard/keys\n");
292
+ console.error("\nInvalid key format. devto API keys start with 'devto_'.\nGet your key at https://devto.ai/dashboard/keys\n");
191
293
  process.exit(1);
192
294
  }
193
295
  process.stdout.write("Validating... ");
@@ -197,42 +299,28 @@ async function login() {
197
299
  process.exit(1);
198
300
  }
199
301
  if (result === "unreachable") {
200
- console.error("\nCould not reach DevTo API. Check your internet connection.\n");
302
+ console.error("\nCould not reach devto API. Check your internet connection.\n");
201
303
  process.exit(1);
202
304
  }
203
305
  const apiUrl = process.env.DEVTO_API_URL ?? "https://api.devto.ai";
204
306
  (0, config_1.writeConfig)({ api_key: apiKey, api_url: apiUrl });
205
307
  console.log("OK\n");
206
308
  console.log("Logged in! Your API key is saved to ~/.devto/config.json\n");
207
- // Prompt for Anthropic key
208
- console.log("────────────────────────────────────────────────");
209
- console.log("DevTo uses the Anthropic API to generate AI plans locally.");
210
- console.log("You need an Anthropic API key for the `create_plan` feature.");
211
- console.log("Get one at: https://console.anthropic.com/settings/keys\n");
212
- const setupAnthropic = await prompt("Set up Anthropic key now? (y/n): ");
213
- if (setupAnthropic.toLowerCase() === "y" || setupAnthropic.toLowerCase() === "yes") {
214
- const anthropicKey = await prompt("Paste your Anthropic API key: ", true);
215
- if (!anthropicKey || !anthropicKey.startsWith("sk-ant-")) {
216
- console.error("\nInvalid key format. Anthropic keys start with 'sk-ant-'.");
217
- console.log("You can set it later: devto config set anthropic-key sk-ant-xxxx\n");
218
- }
219
- else {
220
- (0, config_1.setAnthropicKey)(anthropicKey);
221
- console.log("Anthropic key saved!\n");
222
- }
223
- }
224
- else {
225
- console.log("\nSkipped. You can set it later: devto config set anthropic-key sk-ant-xxxx\n");
226
- }
227
309
  console.log("Next step: run `devto init` in your project directory.");
228
310
  console.log("Run `devto status` to verify your connection.\n");
229
311
  }
230
312
  async function logout() {
231
313
  const configDir = path.join(os.homedir(), ".devto");
232
- if (fs.existsSync(configDir)) {
233
- fs.rmSync(configDir, { recursive: true, force: true });
234
- console.log("\nLogged out. Removed ~/.devto credentials.");
235
- console.log("Note: Project .devto.json files are untouched — run `devto unlink` per project to remove them.\n");
314
+ const configFile = path.join(configDir, "config.json");
315
+ let removed = false;
316
+ if (fs.existsSync(configFile)) {
317
+ fs.unlinkSync(configFile);
318
+ removed = true;
319
+ }
320
+ if (removed) {
321
+ console.log("\nLogged out. Removed ~/.devto/config.json credentials.");
322
+ console.log("Note: ~/.devto/projects.json and project .devto.json files are untouched.");
323
+ console.log("Run `devto unlink` per project to remove project-level config.\n");
236
324
  }
237
325
  else {
238
326
  console.log("\nNo credentials found. Already logged out.\n");
@@ -247,7 +335,7 @@ async function status() {
247
335
  : globalConfig
248
336
  ? { config: globalConfig, source: "global" }
249
337
  : null;
250
- console.log("\nDevTo Status");
338
+ console.log("\ndevto Status");
251
339
  console.log("────────────");
252
340
  console.log(`Project config: ${path.join(projectJson.dir, ".devto.json")}`);
253
341
  console.log(` Workspace: ${projectJson.config.workspaceUrl}`);
@@ -268,15 +356,18 @@ async function status() {
268
356
  }
269
357
  console.log(`API URL: ${config.api_url}`);
270
358
  console.log(`API Key: ${config.api_key.slice(0, 12)}...`);
271
- // Anthropic key status
272
- const hasAnthropic = config.anthropic_key || (() => { try {
273
- (0, config_1.getAnthropicKey)();
274
- return true;
275
- }
276
- catch {
277
- return false;
278
- } })();
279
- console.log(`Anthropic Key: ${hasAnthropic ? "configured" : "missing (run `devto config set anthropic-key sk-ant-xxxx`)"}`);
359
+ // AI call routing
360
+ const hasLocalAnthropicKey = config.anthropic_key ||
361
+ (() => {
362
+ try {
363
+ (0, config_1.getAnthropicKey)();
364
+ return true;
365
+ }
366
+ catch {
367
+ return false;
368
+ }
369
+ })();
370
+ console.log(`AI calls: ${hasLocalAnthropicKey ? "your own key (Expert Mode)" : "via devto"}`);
280
371
  process.stdout.write("Connection: ");
281
372
  try {
282
373
  const headers = {
@@ -306,25 +397,35 @@ async function status() {
306
397
  }
307
398
  }
308
399
  catch {
309
- console.log("Could not connect to DevTo API");
400
+ console.log("Could not connect to devto API");
310
401
  }
311
402
  console.log();
312
403
  }
313
404
  async function init() {
314
- console.log("\nDevTo Init — Link this project to DevTo\n");
405
+ console.log("\ndevto Init — Link this project to devto\n");
315
406
  const globalConfig = (0, config_1.readConfig)();
316
407
  const existingProject = (0, config_1.readProjectConfig)();
317
408
  const existingJson = (0, config_1.findProjectJson)(process.cwd());
318
409
  const apiUrl = process.env.DEVTO_API_URL ?? globalConfig?.api_url ?? "https://api.devto.ai";
319
410
  // ── Step 1: Get API key ──────────────────────────────────────────────
411
+ // Preferred: browser-based device authorization (no key copy-paste).
412
+ // Fallbacks: reuse an existing project/global key, or paste one manually.
320
413
  let apiKey;
414
+ async function acquireKeyInteractively() {
415
+ const method = await prompt("Sign in? [1] Browser (recommended) [2] Paste API key: ");
416
+ if (method.trim() === "2") {
417
+ console.log("Get your API key from: https://devto.ai/dashboard/keys\n");
418
+ return promptForApiKey();
419
+ }
420
+ return browserLogin();
421
+ }
321
422
  if (existingProject?.api_key) {
322
423
  const reuse = await prompt(`This project already has an API key (${existingProject.api_key.slice(0, 12)}...). Use it? (y/n): `);
323
424
  if (reuse.toLowerCase() === "y" || reuse.toLowerCase() === "yes") {
324
425
  apiKey = existingProject.api_key;
325
426
  }
326
427
  else {
327
- apiKey = await promptForApiKey();
428
+ apiKey = await acquireKeyInteractively();
328
429
  }
329
430
  }
330
431
  else if (globalConfig?.api_key) {
@@ -333,12 +434,11 @@ async function init() {
333
434
  apiKey = globalConfig.api_key;
334
435
  }
335
436
  else {
336
- apiKey = await promptForApiKey();
437
+ apiKey = await acquireKeyInteractively();
337
438
  }
338
439
  }
339
440
  else {
340
- console.log("Get your API key from: https://devto.ai/dashboard/keys\n");
341
- apiKey = await promptForApiKey();
441
+ apiKey = await acquireKeyInteractively();
342
442
  }
343
443
  // Validate the key
344
444
  process.stdout.write("Validating... ");
@@ -348,7 +448,7 @@ async function init() {
348
448
  process.exit(1);
349
449
  }
350
450
  if (validation === "unreachable") {
351
- console.error("\nCould not reach DevTo API. Check your internet connection.\n");
451
+ console.error("\nCould not reach devto API. Check your internet connection.\n");
352
452
  process.exit(1);
353
453
  }
354
454
  console.log("OK");
@@ -388,16 +488,15 @@ async function init() {
388
488
  const projectKey = selected.projectKey;
389
489
  const provider = selected.providerType;
390
490
  const workspaceId = selected.id;
391
- // ── Step 3: Check for Anthropic key ──────────────────────────────────
392
- let anthropicKey = existingProject?.anthropic_key;
393
- if (!anthropicKey) {
491
+ // Preserve any existing Anthropic key for Expert Mode users
492
+ const anthropicKey = existingProject?.anthropic_key ?? (() => {
394
493
  try {
395
- anthropicKey = (0, config_1.getAnthropicKey)();
494
+ return (0, config_1.getAnthropicKey)();
396
495
  }
397
496
  catch {
398
- // No Anthropic key yet — skip
497
+ return undefined;
399
498
  }
400
- }
499
+ })();
401
500
  // ── Step 4: Write .devto.json (project config) ──────────────────────
402
501
  (0, config_1.writeProjectJson)({
403
502
  workspaceUrl,
@@ -456,6 +555,15 @@ async function init() {
456
555
  }
457
556
  if (!mcpConfig.mcpServers)
458
557
  mcpConfig.mcpServers = {};
558
+ // Warn if overwriting a different user's credentials
559
+ const existingMcp = mcpConfig.mcpServers.devto;
560
+ if (existingMcp?.env?.DEVTO_API_KEY && existingMcp.env.DEVTO_API_KEY !== apiKey) {
561
+ const overwrite = await prompt(`\n⚠ .mcp.json already has a different devto API key (${existingMcp.env.DEVTO_API_KEY.slice(0, 12)}...).\nOverwrite with yours? (y/n): `);
562
+ if (overwrite.toLowerCase() !== "y" && overwrite.toLowerCase() !== "yes") {
563
+ console.log("\nAborted. Existing .mcp.json left unchanged.\n");
564
+ process.exit(0);
565
+ }
566
+ }
459
567
  mcpConfig.mcpServers.devto = mcpServerConfig;
460
568
  fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2));
461
569
  console.log("MCP config written to .mcp.json");
@@ -478,25 +586,21 @@ async function init() {
478
586
  console.log(`Project: ${projectKey}`);
479
587
  console.log(`Provider: ${provider}`);
480
588
  console.log(`API key: ${apiKey.slice(0, 12)}...`);
481
- if (!anthropicKey) {
482
- console.log("\nNote: Anthropic API key not configured.");
483
- console.log(" AI plan generation (create_plan) requires an Anthropic key.");
484
- console.log(" Run: devto config set anthropic-key sk-ant-xxxx");
485
- console.log(" Then re-run `devto init` to update the MCP config.");
486
- console.log(" Get one at: https://console.anthropic.com/settings/keys");
589
+ if (anthropicKey) {
590
+ console.log("AI calls: Expert Mode (your own Anthropic key)");
487
591
  }
488
592
  console.log("\nRestart Claude Code to pick up the changes.\n");
489
593
  }
490
594
  async function unlink() {
491
- console.log("\nDevTo Unlink — Remove this project from DevTo\n");
595
+ console.log("\ndevto Unlink — Remove this project from devto\n");
492
596
  const projectJson = (0, config_1.findProjectJson)(process.cwd());
493
597
  const devtoJsonPath = path.join(process.cwd(), ".devto.json");
494
598
  const devtoConfigDir = path.join(process.cwd(), ".devto");
495
599
  if (!projectJson && !fs.existsSync(devtoJsonPath) && !fs.existsSync(devtoConfigDir)) {
496
- console.log("This project is not linked to DevTo.\n");
600
+ console.log("This project is not linked to devto.\n");
497
601
  return;
498
602
  }
499
- const confirm = await prompt("Remove DevTo configuration from this project? (y/n): ");
603
+ const confirm = await prompt("Remove devto configuration from this project? (y/n): ");
500
604
  if (confirm.toLowerCase() !== "y" && confirm.toLowerCase() !== "yes") {
501
605
  console.log("Cancelled.\n");
502
606
  return;
@@ -546,7 +650,7 @@ async function unlink() {
546
650
  async function promptForApiKey() {
547
651
  const apiKey = await prompt("Paste your API key: ", true);
548
652
  if (!apiKey || !apiKey.startsWith("devto_")) {
549
- console.error("\nInvalid key format. DevTo API keys start with 'devto_'.\nGet your key at https://devto.ai/dashboard/keys\n");
653
+ console.error("\nInvalid key format. devto API keys start with 'devto_'.\nGet your key at https://devto.ai/dashboard/keys\n");
550
654
  process.exit(1);
551
655
  }
552
656
  return apiKey;
@@ -556,17 +660,17 @@ function ensureGitignore(entry) {
556
660
  try {
557
661
  if (fs.existsSync(gitignorePath)) {
558
662
  const content = fs.readFileSync(gitignorePath, "utf-8");
559
- if (!content.includes(entry)) {
560
- const label = entry === ".devto.json" ? "DevTo project config"
663
+ if (!content.split('\n').some(line => line.trim() === entry)) {
664
+ const label = entry === ".devto.json" ? "devto project config"
561
665
  : entry === ".mcp.json" ? "MCP config (contains API keys)"
562
- : "DevTo credentials (contains API keys)";
666
+ : "devto credentials (contains API keys)";
563
667
  fs.appendFileSync(gitignorePath, `\n# ${label}\n${entry}\n`);
564
668
  }
565
669
  }
566
670
  else {
567
- const label = entry === ".devto.json" ? "DevTo project config"
671
+ const label = entry === ".devto.json" ? "devto project config"
568
672
  : entry === ".mcp.json" ? "MCP config (contains API keys)"
569
- : "DevTo credentials (contains API keys)";
673
+ : "devto credentials (contains API keys)";
570
674
  fs.writeFileSync(gitignorePath, `# ${label}\n${entry}\n`);
571
675
  }
572
676
  }
@@ -575,7 +679,7 @@ function ensureGitignore(entry) {
575
679
  }
576
680
  }
577
681
  async function doctor() {
578
- console.log("\nDevTo Doctor — Connection Diagnostics\n");
682
+ console.log("\ndevto Doctor — Connection Diagnostics\n");
579
683
  const projectJson = requireProjectJson();
580
684
  const resolved = (0, config_1.resolveConfig)();
581
685
  const config = resolved?.config ?? null;
@@ -583,8 +687,8 @@ async function doctor() {
583
687
  console.log(`OK (${path.join(projectJson.dir, ".devto.json")})`);
584
688
  console.log(` Workspace: ${projectJson.config.workspaceUrl}`);
585
689
  console.log(` Project: ${projectJson.config.projectKey} (${projectJson.config.provider})`);
586
- // 1. Check DevTo API reachable
587
- process.stdout.write("DevTo API reachable ... ");
690
+ // 1. Check devto API reachable
691
+ process.stdout.write("devto API reachable ... ");
588
692
  const apiUrl = config?.api_url ?? (0, config_1.getApiUrl)();
589
693
  try {
590
694
  const res = await fetch(`${apiUrl}/api/v1/status`, {
@@ -665,15 +769,22 @@ async function doctor() {
665
769
  console.log("SKIP (cannot reach API)");
666
770
  }
667
771
  }
668
- // 4. Check Anthropic key
669
- process.stdout.write("Anthropic API key ... ");
670
- try {
671
- (0, config_1.getAnthropicKey)();
672
- console.log("OK");
772
+ // 4. AI call routing
773
+ process.stdout.write("AI calls ... ");
774
+ const hasLocalAnthropicKey = (() => {
775
+ try {
776
+ (0, config_1.getAnthropicKey)();
777
+ return true;
778
+ }
779
+ catch {
780
+ return false;
781
+ }
782
+ })();
783
+ if (hasLocalAnthropicKey) {
784
+ console.log("Expert Mode (your own Anthropic key)");
673
785
  }
674
- catch {
675
- console.log("MISSING");
676
- console.log(" Fix: Run `devto config set anthropic-key sk-ant-xxxx`");
786
+ else {
787
+ console.log("via devto (no local key needed)");
677
788
  }
678
789
  // 5. Check .mcp.json in current directory
679
790
  process.stdout.write("MCP config (.mcp.json) ... ");
@@ -701,7 +812,7 @@ async function doctor() {
701
812
  console.log();
702
813
  }
703
814
  async function sync() {
704
- console.log("\nDevTo Sync — Workspace Configuration Discovery\n");
815
+ console.log("\ndevto Sync — Workspace Configuration Discovery\n");
705
816
  requireProjectJson();
706
817
  const config = (0, config_1.readConfig)();
707
818
  if (!config?.api_key) {
@@ -711,8 +822,8 @@ async function sync() {
711
822
  const apiUrl = config.api_url;
712
823
  process.stdout.write("Discovering workspace configuration... ");
713
824
  try {
714
- const res = await fetch(`${apiUrl}/api/v1/jira/discover`, {
715
- method: "POST",
825
+ const res = await fetch(`${apiUrl}/api/v1/project/summary`, {
826
+ method: "GET",
716
827
  headers: {
717
828
  "Content-Type": "application/json",
718
829
  Authorization: `Bearer ${config.api_key}`,
@@ -767,14 +878,14 @@ async function configSet() {
767
878
  }
768
879
  function printHelp() {
769
880
  console.log(`
770
- DevTo CLI — AI-powered work management
881
+ devto CLI — AI-powered work management
771
882
 
772
883
  Usage:
773
- devto login Authenticate with your DevTo API key
884
+ devto login Authenticate with your devto API key
774
885
  devto logout Clear global credentials (~/.devto)
775
886
  devto status Show connection status and active project
776
- devto init Link this project to DevTo
777
- devto unlink Unlink this project from DevTo
887
+ devto init Link this project to devto
888
+ devto unlink Unlink this project from devto
778
889
  devto doctor Test full connection chain
779
890
  devto sync Re-sync workspace configuration
780
891
  devto verbose Toggle verbose mode