@headways/cli 0.3.0 → 0.4.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.
Files changed (2) hide show
  1. package/dist/index.js +104 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -79,10 +79,7 @@ function registerAuthCommands(program2) {
79
79
  });
80
80
  configure.command("clear").description("Clear credentials and reset setup state (keeps API and app URLs)").action(() => {
81
81
  const cfg = readConfig();
82
- const cleared = {};
83
- if (cfg.apiUrl) cleared.apiUrl = cfg.apiUrl;
84
- if (cfg.appUrl) cleared.appUrl = cfg.appUrl;
85
- writeConfig(cleared);
82
+ writeConfig({ apiUrl: cfg.apiUrl, appUrl: cfg.appUrl });
86
83
  console.log("Credentials cleared. The desktop app will show onboarding on next launch.");
87
84
  });
88
85
  configure.command("status").description("Show current API key and org").action(() => {
@@ -282,6 +279,13 @@ runtimes: [claude-code]
282
279
  kind: outcome
283
280
  description: 'Captures the main output artifact'
284
281
  schema: {}
282
+ `
283
+ );
284
+ await fs.writeFile(
285
+ path.join(dir, "connections.yaml"),
286
+ `# connections:
287
+ # - connector: slack
288
+ # purpose: "Post skill output to a Slack channel"
285
289
  `
286
290
  );
287
291
  await fs.mkdir(path.join(dir, "fixtures"), { recursive: true });
@@ -397,16 +401,47 @@ async function readSkillDir(dir) {
397
401
  capabilities = { raw: capYaml };
398
402
  } catch {
399
403
  }
400
- return { body, headline, capabilities };
404
+ let connections;
405
+ const connYamlPath = path3.join(dir, "connections.yaml");
406
+ try {
407
+ const connYaml = await fs3.readFile(connYamlPath, "utf-8");
408
+ const items = parseConnectionsYaml(connYaml);
409
+ if (items.length > 0) connections = items;
410
+ } catch {
411
+ }
412
+ return { body, headline, capabilities, connections };
413
+ }
414
+ function parseConnectionsYaml(yaml) {
415
+ const items = [];
416
+ const connectorRe = /^\s*-\s+connector:\s*(.+)$/;
417
+ const purposeRe = /^\s+purpose:\s*["']?(.+?)["']?\s*$/;
418
+ const lines = yaml.split("\n");
419
+ let current = null;
420
+ for (const line of lines) {
421
+ if (line.trimStart().startsWith("#")) continue;
422
+ const connMatch = connectorRe.exec(line);
423
+ if (connMatch) {
424
+ if (current?.connector && current.purpose) items.push(current);
425
+ current = { connector: (connMatch[1] ?? "").trim() };
426
+ continue;
427
+ }
428
+ if (current) {
429
+ const purposeMatch = purposeRe.exec(line);
430
+ if (purposeMatch) current.purpose = (purposeMatch[1] ?? "").trim();
431
+ }
432
+ }
433
+ if (current?.connector && current.purpose) items.push(current);
434
+ return items;
401
435
  }
402
436
  async function pushSkill(slug, dir) {
403
- const { body, headline, capabilities } = await readSkillDir(dir);
437
+ const { body, headline, capabilities, connections } = await readSkillDir(dir);
404
438
  await apiRequest(`/v1/skills/${slug}/draft`, {
405
439
  method: "PUT",
406
440
  body: JSON.stringify({
407
441
  body,
408
442
  ...headline ? { headline } : {},
409
- ...capabilities ? { capabilities } : {}
443
+ ...capabilities ? { capabilities } : {},
444
+ ...connections ? { connections } : {}
410
445
  })
411
446
  });
412
447
  console.log(`Pushed '${slug}' draft`);
@@ -469,6 +504,7 @@ headways skills push <slug> # push local edits as a draft
469
504
  SKILL.md # skill body \u2014 instructions for the AI agent
470
505
  headways.yaml # metadata: slug, name, headline, channel, runtimes
471
506
  capabilities.yaml # what the skill is allowed to do
507
+ connections.yaml # MCP connectors this skill requires (optional)
472
508
  hooks.yaml # structured hooks the skill exposes (optional)
473
509
  \`\`\`
474
510
 
@@ -510,6 +546,20 @@ data_classes: none # none | pii | phi | pci
510
546
  auto_send: false # true = skill may act without user confirmation
511
547
  \`\`\`
512
548
 
549
+ ### connections.yaml (required for any skill that uses MCP connector tools)
550
+
551
+ Declare every MCP connector the skill depends on. Users see this list on \`headways skills accept\`
552
+ and the Headways app gates installation on the connectors being configured.
553
+
554
+ \`\`\`yaml
555
+ - connector: slack # connector identifier (e.g. slack, github, jira, linear, notion, google-drive)
556
+ purpose: Read channel messages and threads via Slack MCP tools
557
+ - connector: github
558
+ purpose: Read pull requests and issues
559
+ \`\`\`
560
+
561
+ Omit the file entirely if the skill has no connector dependencies.
562
+
513
563
  ### hooks.yaml (omit if unused)
514
564
 
515
565
  \`\`\`yaml
@@ -565,6 +615,7 @@ local edits with \`headways skills push <slug>\`.
565
615
  - Headline > 90 chars \u2192 submit blocked with 422. Shorten before pushing.
566
616
  - Uppercase or special chars in slug \u2192 rejected at creation. Use \`a-z\`, \`0-9\`, \`-\` only.
567
617
  - Missing \`capabilities.yaml\` entries \u2192 skill silently blocked at runtime.
618
+ - Missing \`connections.yaml\` for MCP-dependent skills \u2192 users install the skill but hit tool-not-found errors at runtime with no explanation. Always create this file when the skill calls MCP tools.
568
619
  - Passive or noun-phrase headline \u2192 poor discoverability; rewrite as a verb phrase.
569
620
  `.trim();
570
621
  function registerSkillsCommands(program2) {
@@ -591,6 +642,23 @@ function registerSkillsCommands(program2) {
591
642
  skills.command("accept <slug>").description("Accept a pending skill update and install it locally").action(async (slug) => {
592
643
  const { acceptSkill } = await import("./sync-6PKI35ZY.js");
593
644
  await acceptSkill(slug);
645
+ try {
646
+ const { apiRequest: apiRequest2 } = await import("./api-2BK6MGZB.js");
647
+ const metadata = await apiRequest2(`/v1/skills/${slug}/bundle/metadata`);
648
+ const reqs = metadata.connectionRequirements ?? [];
649
+ if (reqs.length > 0) {
650
+ console.log("");
651
+ console.log("This skill requires the following connectors:");
652
+ console.log("");
653
+ for (const req of reqs) {
654
+ console.log(` - ${req.connector.padEnd(20)} ${req.purpose}`);
655
+ }
656
+ console.log("");
657
+ console.log("To authorize these connectors, use the Headways desktop app");
658
+ console.log("or run: headways connections add <provider>");
659
+ }
660
+ } catch {
661
+ }
594
662
  });
595
663
  skills.command("feedback <slug>").description("Submit feedback about a skill").option(
596
664
  "--reaction <type>",
@@ -616,6 +684,28 @@ function registerSkillsCommands(program2) {
616
684
  });
617
685
  }
618
686
 
687
+ // src/commands/connections/index.ts
688
+ import "commander";
689
+ function registerConnectionsCommands(program2) {
690
+ const connections = program2.command("connections").description("Manage connector authorizations");
691
+ connections.command("add [provider]").description("Authorize a connector (opens the desktop app)").action((provider) => {
692
+ const target = provider ? `the ${provider} connector` : "connectors";
693
+ console.log(`To authorize ${target}, open the Headways desktop app.`);
694
+ console.log("");
695
+ console.log(" 1. Open Headways");
696
+ console.log(" 2. Go to Settings \u2192 Connections");
697
+ console.log(' 3. Click "Connect" next to the connector you want to authorize');
698
+ console.log("");
699
+ console.log(
700
+ "Alternatively, install a skill that requires the connector and it will be set up automatically."
701
+ );
702
+ });
703
+ connections.command("list").description("List authorized connections (opens the desktop app)").action(() => {
704
+ console.log("To view your connections, open the Headways desktop app.");
705
+ console.log("Go to Settings \u2192 Connections to see and manage your authorized connectors.");
706
+ });
707
+ }
708
+
619
709
  // src/sdk/emit.ts
620
710
  import "commander";
621
711
  function registerEmitCommand(program2) {
@@ -720,6 +810,10 @@ function registerPrimeCommand(program2) {
720
810
  for (const skill of skills) {
721
811
  const runLine = skill.lastRunAt ? `last run ${new Date(skill.lastRunAt).toLocaleDateString()}` : "never run";
722
812
  lines.push(`- **${skill.slug}** v${skill.version} (${skill.runtime}, ${runLine})`);
813
+ if (skill.connectionRequirements.length > 0) {
814
+ const connectors = skill.connectionRequirements.map((r) => r.connector).join(", ");
815
+ lines.push(` - Requires connectors: ${connectors}`);
816
+ }
723
817
  }
724
818
  }
725
819
  lines.push("", "## Skill Files", "");
@@ -739,7 +833,8 @@ function getInstalledSkills() {
739
833
  slug,
740
834
  version: String(raw.version ?? ""),
741
835
  runtime: String(raw.runtime ?? "claude-code"),
742
- lastRunAt: raw.last_run_at ?? null
836
+ lastRunAt: raw.last_run_at ?? null,
837
+ connectionRequirements: Array.isArray(raw.connection_requirements) ? raw.connection_requirements : []
743
838
  };
744
839
  } catch {
745
840
  return null;
@@ -754,6 +849,7 @@ function getInstalledSkills() {
754
849
  program.name("headways").description("Headways CLI \u2014 skill authoring, sync, and runtime SDK").version("0.2.1");
755
850
  registerAuthCommands(program);
756
851
  registerSkillsCommands(program);
852
+ registerConnectionsCommands(program);
757
853
  registerSyncCommands(program);
758
854
  registerEmitCommand(program);
759
855
  registerPrimeCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headways/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Headways CLI — authoring, sync, and runtime SDK",
6
6
  "license": "MIT",