@headways/cli 0.3.0 → 0.4.1
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/dist/{api-2BK6MGZB.js → api-5EKGGFQ6.js} +2 -2
- package/dist/{chunk-HYEL7L5Z.js → chunk-2INXZHRG.js} +1 -1
- package/dist/{chunk-T2H7EXOV.js → chunk-UUFIIGTZ.js} +2 -0
- package/dist/{chunk-OZULVVQC.js → chunk-XTEQBKIN.js} +6 -8
- package/dist/{config-SHMIVRAP.js → config-XQHAXREA.js} +3 -1
- package/dist/index.js +139 -32
- package/dist/{sync-6PKI35ZY.js → sync-Q3OQUWOD.js} +2 -2
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ var HEADWAYS_DIR = join(homedir(), ".headways");
|
|
|
8
8
|
var CONFIG_FILE = join(HEADWAYS_DIR, "config.json");
|
|
9
9
|
var CATALOG_FILE = join(HEADWAYS_DIR, "catalog.json");
|
|
10
10
|
var INSTALLED_DIR = join(HEADWAYS_DIR, "installed");
|
|
11
|
+
var CLAUDE_SKILLS_DIR = join(homedir(), ".claude", "skills");
|
|
11
12
|
function readConfig() {
|
|
12
13
|
if (!existsSync(CONFIG_FILE)) return {};
|
|
13
14
|
try {
|
|
@@ -42,6 +43,7 @@ export {
|
|
|
42
43
|
CONFIG_FILE,
|
|
43
44
|
CATALOG_FILE,
|
|
44
45
|
INSTALLED_DIR,
|
|
46
|
+
CLAUDE_SKILLS_DIR,
|
|
45
47
|
readConfig,
|
|
46
48
|
writeConfig,
|
|
47
49
|
getApiUrl,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
CLAUDE_SKILLS_DIR,
|
|
3
4
|
HEADWAYS_DIR,
|
|
4
5
|
getApiUrl,
|
|
5
6
|
readConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-UUFIIGTZ.js";
|
|
7
8
|
|
|
8
9
|
// src/commands/sync/index.ts
|
|
9
10
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, rmSync as rmSync2 } from "fs";
|
|
10
|
-
import { homedir as homedir2 } from "os";
|
|
11
11
|
import { join as join2 } from "path";
|
|
12
12
|
import { createGunzip } from "zlib";
|
|
13
13
|
import { Readable } from "stream";
|
|
@@ -53,9 +53,8 @@ function registerUninstallCommand(program) {
|
|
|
53
53
|
removeHooks(CLAUDE_SETTINGS_GLOBAL);
|
|
54
54
|
removeHooks(CLAUDE_SETTINGS_LOCAL);
|
|
55
55
|
console.log("\u2713 Removed Claude Code hooks");
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
rmSync(skillsDir, { recursive: true, force: true });
|
|
56
|
+
if (existsSync(CLAUDE_SKILLS_DIR)) {
|
|
57
|
+
rmSync(CLAUDE_SKILLS_DIR, { recursive: true, force: true });
|
|
59
58
|
console.log("\u2713 Removed ~/.claude/skills/");
|
|
60
59
|
}
|
|
61
60
|
const headwaysDir = join(homedir(), ".headways");
|
|
@@ -232,9 +231,8 @@ async function downloadAndMaterialize(slug, version, state, apiUrl) {
|
|
|
232
231
|
});
|
|
233
232
|
if (!res.ok) throw new Error(`Bundle fetch failed: ${res.status}`);
|
|
234
233
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
const staging = join2(skillsDir, `.${slug}-staging`);
|
|
234
|
+
const dest = join2(CLAUDE_SKILLS_DIR, slug);
|
|
235
|
+
const staging = join2(CLAUDE_SKILLS_DIR, `.${slug}-staging`);
|
|
238
236
|
mkdirSync2(staging, { recursive: true });
|
|
239
237
|
await extractTarGz(buf, staging);
|
|
240
238
|
if (existsSync2(dest)) rmSync2(dest, { recursive: true });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
CATALOG_FILE,
|
|
4
|
+
CLAUDE_SKILLS_DIR,
|
|
4
5
|
CONFIG_FILE,
|
|
5
6
|
HEADWAYS_DIR,
|
|
6
7
|
INSTALLED_DIR,
|
|
@@ -9,9 +10,10 @@ import {
|
|
|
9
10
|
readConfig,
|
|
10
11
|
requireAuth,
|
|
11
12
|
writeConfig
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-UUFIIGTZ.js";
|
|
13
14
|
export {
|
|
14
15
|
CATALOG_FILE,
|
|
16
|
+
CLAUDE_SKILLS_DIR,
|
|
15
17
|
CONFIG_FILE,
|
|
16
18
|
HEADWAYS_DIR,
|
|
17
19
|
INSTALLED_DIR,
|
package/dist/index.js
CHANGED
|
@@ -2,23 +2,25 @@
|
|
|
2
2
|
import {
|
|
3
3
|
apiRequest,
|
|
4
4
|
rawRequest
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2INXZHRG.js";
|
|
6
6
|
import {
|
|
7
7
|
registerSetupCommand,
|
|
8
8
|
registerSyncCommands,
|
|
9
9
|
registerUninstallCommand
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-XTEQBKIN.js";
|
|
11
11
|
import {
|
|
12
|
+
CLAUDE_SKILLS_DIR,
|
|
12
13
|
INSTALLED_DIR,
|
|
13
14
|
getApiUrl,
|
|
14
15
|
getAppUrl,
|
|
15
16
|
readConfig,
|
|
16
17
|
requireAuth,
|
|
17
18
|
writeConfig
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-UUFIIGTZ.js";
|
|
19
20
|
|
|
20
21
|
// src/index.ts
|
|
21
22
|
import "dotenv/config";
|
|
23
|
+
import { createRequire } from "module";
|
|
22
24
|
import { program } from "commander";
|
|
23
25
|
|
|
24
26
|
// src/commands/auth.ts
|
|
@@ -79,10 +81,7 @@ function registerAuthCommands(program2) {
|
|
|
79
81
|
});
|
|
80
82
|
configure.command("clear").description("Clear credentials and reset setup state (keeps API and app URLs)").action(() => {
|
|
81
83
|
const cfg = readConfig();
|
|
82
|
-
|
|
83
|
-
if (cfg.apiUrl) cleared.apiUrl = cfg.apiUrl;
|
|
84
|
-
if (cfg.appUrl) cleared.appUrl = cfg.appUrl;
|
|
85
|
-
writeConfig(cleared);
|
|
84
|
+
writeConfig({ apiUrl: cfg.apiUrl, appUrl: cfg.appUrl });
|
|
86
85
|
console.log("Credentials cleared. The desktop app will show onboarding on next launch.");
|
|
87
86
|
});
|
|
88
87
|
configure.command("status").description("Show current API key and org").action(() => {
|
|
@@ -282,6 +281,13 @@ runtimes: [claude-code]
|
|
|
282
281
|
kind: outcome
|
|
283
282
|
description: 'Captures the main output artifact'
|
|
284
283
|
schema: {}
|
|
284
|
+
`
|
|
285
|
+
);
|
|
286
|
+
await fs.writeFile(
|
|
287
|
+
path.join(dir, "connections.yaml"),
|
|
288
|
+
`# connections:
|
|
289
|
+
# - connector: slack
|
|
290
|
+
# purpose: "Post skill output to a Slack channel"
|
|
285
291
|
`
|
|
286
292
|
);
|
|
287
293
|
await fs.mkdir(path.join(dir, "fixtures"), { recursive: true });
|
|
@@ -378,44 +384,82 @@ function registerImportCommand(program2) {
|
|
|
378
384
|
import "commander";
|
|
379
385
|
import * as fs3 from "fs/promises";
|
|
380
386
|
import * as path3 from "path";
|
|
381
|
-
import { watch } from "fs";
|
|
387
|
+
import { watch, existsSync } from "fs";
|
|
388
|
+
var catchMissing = (e) => {
|
|
389
|
+
if (e.code === "ENOENT") return null;
|
|
390
|
+
throw e;
|
|
391
|
+
};
|
|
382
392
|
async function readSkillDir(dir) {
|
|
383
393
|
const skillMdPath = path3.join(dir, "SKILL.md");
|
|
384
|
-
|
|
385
|
-
let headline;
|
|
386
|
-
const headwaysYamlPath = path3.join(dir, "headways.yaml");
|
|
394
|
+
let body;
|
|
387
395
|
try {
|
|
388
|
-
|
|
389
|
-
const match = yaml.match(/headline:\s*['"]?(.+?)['"]?\s*$/m);
|
|
390
|
-
if (match) headline = match[1] ?? void 0;
|
|
396
|
+
body = await fs3.readFile(skillMdPath, "utf-8");
|
|
391
397
|
} catch {
|
|
398
|
+
throw new Error(`SKILL.md not found in ${dir}`);
|
|
392
399
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
400
|
+
const [headwaysYaml, capabilitiesYaml, connectionsYaml] = await Promise.all([
|
|
401
|
+
fs3.readFile(path3.join(dir, "headways.yaml"), "utf-8").catch(catchMissing),
|
|
402
|
+
fs3.readFile(path3.join(dir, "capabilities.yaml"), "utf-8").catch(catchMissing),
|
|
403
|
+
fs3.readFile(path3.join(dir, "connections.yaml"), "utf-8").catch(catchMissing)
|
|
404
|
+
]);
|
|
405
|
+
let headline;
|
|
406
|
+
if (headwaysYaml) {
|
|
407
|
+
const match = headwaysYaml.match(/headline:\s*['"]?(.+?)['"]?\s*$/m);
|
|
408
|
+
if (match) headline = match[1] ?? void 0;
|
|
409
|
+
}
|
|
410
|
+
let connections;
|
|
411
|
+
if (connectionsYaml) {
|
|
412
|
+
const items = parseConnectionsYaml(connectionsYaml);
|
|
413
|
+
if (items.length > 0) connections = items;
|
|
414
|
+
}
|
|
415
|
+
return { body, headline, capabilities: capabilitiesYaml ?? void 0, connections };
|
|
416
|
+
}
|
|
417
|
+
function parseConnectionsYaml(yaml) {
|
|
418
|
+
const items = [];
|
|
419
|
+
const connectorRe = /^\s*-\s+connector:\s*(.+)$/;
|
|
420
|
+
const purposeRe = /^\s+purpose:\s*["']?(.+?)["']?\s*$/;
|
|
421
|
+
const lines = yaml.split("\n");
|
|
422
|
+
let current = null;
|
|
423
|
+
for (const line of lines) {
|
|
424
|
+
if (line.trimStart().startsWith("#")) continue;
|
|
425
|
+
const connMatch = connectorRe.exec(line);
|
|
426
|
+
if (connMatch) {
|
|
427
|
+
if (current?.connector && current.purpose) items.push(current);
|
|
428
|
+
current = { connector: (connMatch[1] ?? "").trim() };
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (current) {
|
|
432
|
+
const purposeMatch = purposeRe.exec(line);
|
|
433
|
+
if (purposeMatch) current.purpose = (purposeMatch[1] ?? "").trim();
|
|
434
|
+
}
|
|
399
435
|
}
|
|
400
|
-
|
|
436
|
+
if (current?.connector && current.purpose) items.push(current);
|
|
437
|
+
return items;
|
|
401
438
|
}
|
|
402
439
|
async function pushSkill(slug, dir) {
|
|
403
|
-
const { body, headline, capabilities } = await readSkillDir(dir);
|
|
440
|
+
const { body, headline, capabilities, connections } = await readSkillDir(dir);
|
|
404
441
|
await apiRequest(`/v1/skills/${slug}/draft`, {
|
|
405
442
|
method: "PUT",
|
|
406
443
|
body: JSON.stringify({
|
|
407
444
|
body,
|
|
408
445
|
...headline ? { headline } : {},
|
|
409
|
-
...capabilities ? { capabilities } : {}
|
|
446
|
+
...capabilities ? { capabilities } : {},
|
|
447
|
+
...connections ? { connections } : {}
|
|
410
448
|
})
|
|
411
449
|
});
|
|
412
450
|
console.log(`Pushed '${slug}' draft`);
|
|
413
451
|
}
|
|
452
|
+
function resolveSkillDir(slug) {
|
|
453
|
+
if (!slug) return process.cwd();
|
|
454
|
+
const installedPath = path3.join(CLAUDE_SKILLS_DIR, slug);
|
|
455
|
+
if (existsSync(installedPath)) return installedPath;
|
|
456
|
+
return path3.join(process.cwd(), slug);
|
|
457
|
+
}
|
|
414
458
|
function registerPushCommand(program2) {
|
|
415
|
-
program2.command("push [slug]").description("Push local skill files as a draft to Headways").option("--watch", "Watch for file changes and auto-push").option("--dir <dir>", "Skill directory (default: ./<slug>
|
|
459
|
+
program2.command("push [slug]").description("Push local skill files as a draft to Headways").option("--watch", "Watch for file changes and auto-push").option("--dir <dir>", "Skill directory (default: installed location, then ./<slug>)").action(async (slug, opts) => {
|
|
416
460
|
requireAuth();
|
|
417
461
|
const resolvedSlug = slug ?? path3.basename(process.cwd());
|
|
418
|
-
const dir = opts.dir ?? (slug
|
|
462
|
+
const dir = opts.dir ?? resolveSkillDir(slug);
|
|
419
463
|
await pushSkill(resolvedSlug, dir);
|
|
420
464
|
if (opts.watch) {
|
|
421
465
|
console.log(`Watching ${dir} for changes...`);
|
|
@@ -469,6 +513,7 @@ headways skills push <slug> # push local edits as a draft
|
|
|
469
513
|
SKILL.md # skill body \u2014 instructions for the AI agent
|
|
470
514
|
headways.yaml # metadata: slug, name, headline, channel, runtimes
|
|
471
515
|
capabilities.yaml # what the skill is allowed to do
|
|
516
|
+
connections.yaml # MCP connectors this skill requires (optional)
|
|
472
517
|
hooks.yaml # structured hooks the skill exposes (optional)
|
|
473
518
|
\`\`\`
|
|
474
519
|
|
|
@@ -510,6 +555,20 @@ data_classes: none # none | pii | phi | pci
|
|
|
510
555
|
auto_send: false # true = skill may act without user confirmation
|
|
511
556
|
\`\`\`
|
|
512
557
|
|
|
558
|
+
### connections.yaml (required for any skill that uses MCP connector tools)
|
|
559
|
+
|
|
560
|
+
Declare every MCP connector the skill depends on. Users see this list on \`headways skills accept\`
|
|
561
|
+
and the Headways app gates installation on the connectors being configured.
|
|
562
|
+
|
|
563
|
+
\`\`\`yaml
|
|
564
|
+
- connector: slack # connector identifier (e.g. slack, github, atlassian, linear, notion, google-drive, stripe, asana, hubspot, datadog)
|
|
565
|
+
purpose: Read channel messages and threads via Slack MCP tools
|
|
566
|
+
- connector: github
|
|
567
|
+
purpose: Read pull requests and issues
|
|
568
|
+
\`\`\`
|
|
569
|
+
|
|
570
|
+
Omit the file entirely if the skill has no connector dependencies.
|
|
571
|
+
|
|
513
572
|
### hooks.yaml (omit if unused)
|
|
514
573
|
|
|
515
574
|
\`\`\`yaml
|
|
@@ -565,6 +624,7 @@ local edits with \`headways skills push <slug>\`.
|
|
|
565
624
|
- Headline > 90 chars \u2192 submit blocked with 422. Shorten before pushing.
|
|
566
625
|
- Uppercase or special chars in slug \u2192 rejected at creation. Use \`a-z\`, \`0-9\`, \`-\` only.
|
|
567
626
|
- Missing \`capabilities.yaml\` entries \u2192 skill silently blocked at runtime.
|
|
627
|
+
- 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
628
|
- Passive or noun-phrase headline \u2192 poor discoverability; rewrite as a verb phrase.
|
|
569
629
|
`.trim();
|
|
570
630
|
function registerSkillsCommands(program2) {
|
|
@@ -576,8 +636,8 @@ function registerSkillsCommands(program2) {
|
|
|
576
636
|
console.log(SKILLS_GUIDE);
|
|
577
637
|
});
|
|
578
638
|
skills.command("list").description("List skills in the active org").action(async () => {
|
|
579
|
-
const { requireAuth: requireAuth2 } = await import("./config-
|
|
580
|
-
const { apiRequest: apiRequest2 } = await import("./api-
|
|
639
|
+
const { requireAuth: requireAuth2 } = await import("./config-XQHAXREA.js");
|
|
640
|
+
const { apiRequest: apiRequest2 } = await import("./api-5EKGGFQ6.js");
|
|
581
641
|
requireAuth2();
|
|
582
642
|
const result = await apiRequest2("/v1/skills");
|
|
583
643
|
if (result.data.length === 0) {
|
|
@@ -589,8 +649,25 @@ function registerSkillsCommands(program2) {
|
|
|
589
649
|
}
|
|
590
650
|
});
|
|
591
651
|
skills.command("accept <slug>").description("Accept a pending skill update and install it locally").action(async (slug) => {
|
|
592
|
-
const { acceptSkill } = await import("./sync-
|
|
652
|
+
const { acceptSkill } = await import("./sync-Q3OQUWOD.js");
|
|
593
653
|
await acceptSkill(slug);
|
|
654
|
+
try {
|
|
655
|
+
const { apiRequest: apiRequest2 } = await import("./api-5EKGGFQ6.js");
|
|
656
|
+
const metadata = await apiRequest2(`/v1/skills/${slug}/bundle/metadata`);
|
|
657
|
+
const reqs = metadata.connectionRequirements ?? [];
|
|
658
|
+
if (reqs.length > 0) {
|
|
659
|
+
console.log("");
|
|
660
|
+
console.log("This skill requires the following connectors:");
|
|
661
|
+
console.log("");
|
|
662
|
+
for (const req of reqs) {
|
|
663
|
+
console.log(` - ${req.connector.padEnd(20)} ${req.purpose}`);
|
|
664
|
+
}
|
|
665
|
+
console.log("");
|
|
666
|
+
console.log("To authorize these connectors, use the Headways desktop app");
|
|
667
|
+
console.log("or run: headways connections add <provider>");
|
|
668
|
+
}
|
|
669
|
+
} catch {
|
|
670
|
+
}
|
|
594
671
|
});
|
|
595
672
|
skills.command("feedback <slug>").description("Submit feedback about a skill").option(
|
|
596
673
|
"--reaction <type>",
|
|
@@ -616,6 +693,28 @@ function registerSkillsCommands(program2) {
|
|
|
616
693
|
});
|
|
617
694
|
}
|
|
618
695
|
|
|
696
|
+
// src/commands/connections/index.ts
|
|
697
|
+
import "commander";
|
|
698
|
+
function registerConnectionsCommands(program2) {
|
|
699
|
+
const connections = program2.command("connections").description("Manage connector authorizations");
|
|
700
|
+
connections.command("add [provider]").description("Authorize a connector (opens the desktop app)").action((provider) => {
|
|
701
|
+
const target = provider ? `the ${provider} connector` : "connectors";
|
|
702
|
+
console.log(`To authorize ${target}, open the Headways desktop app.`);
|
|
703
|
+
console.log("");
|
|
704
|
+
console.log(" 1. Open Headways");
|
|
705
|
+
console.log(" 2. Go to Settings \u2192 Connections");
|
|
706
|
+
console.log(' 3. Click "Connect" next to the connector you want to authorize');
|
|
707
|
+
console.log("");
|
|
708
|
+
console.log(
|
|
709
|
+
"Alternatively, install a skill that requires the connector and it will be set up automatically."
|
|
710
|
+
);
|
|
711
|
+
});
|
|
712
|
+
connections.command("list").description("List authorized connections (opens the desktop app)").action(() => {
|
|
713
|
+
console.log("To view your connections, open the Headways desktop app.");
|
|
714
|
+
console.log("Go to Settings \u2192 Connections to see and manage your authorized connectors.");
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
619
718
|
// src/sdk/emit.ts
|
|
620
719
|
import "commander";
|
|
621
720
|
function registerEmitCommand(program2) {
|
|
@@ -657,7 +756,7 @@ function registerEmitCommand(program2) {
|
|
|
657
756
|
}
|
|
658
757
|
|
|
659
758
|
// src/commands/prime.ts
|
|
660
|
-
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
759
|
+
import { existsSync as existsSync2, readdirSync, readFileSync } from "fs";
|
|
661
760
|
import "commander";
|
|
662
761
|
function registerPrimeCommand(program2) {
|
|
663
762
|
program2.command("prime").description("Output Headways workflow context for AI coding assistants").action(() => {
|
|
@@ -720,6 +819,10 @@ function registerPrimeCommand(program2) {
|
|
|
720
819
|
for (const skill of skills) {
|
|
721
820
|
const runLine = skill.lastRunAt ? `last run ${new Date(skill.lastRunAt).toLocaleDateString()}` : "never run";
|
|
722
821
|
lines.push(`- **${skill.slug}** v${skill.version} (${skill.runtime}, ${runLine})`);
|
|
822
|
+
if (skill.connectionRequirements.length > 0) {
|
|
823
|
+
const connectors = skill.connectionRequirements.map((r) => r.connector).join(", ");
|
|
824
|
+
lines.push(` - Requires connectors: ${connectors}`);
|
|
825
|
+
}
|
|
723
826
|
}
|
|
724
827
|
}
|
|
725
828
|
lines.push("", "## Skill Files", "");
|
|
@@ -729,7 +832,7 @@ function registerPrimeCommand(program2) {
|
|
|
729
832
|
});
|
|
730
833
|
}
|
|
731
834
|
function getInstalledSkills() {
|
|
732
|
-
if (!
|
|
835
|
+
if (!existsSync2(INSTALLED_DIR)) return [];
|
|
733
836
|
try {
|
|
734
837
|
return readdirSync(INSTALLED_DIR).filter((f) => f.endsWith(".json")).map((f) => {
|
|
735
838
|
const slug = f.replace(/\.json$/, "");
|
|
@@ -739,7 +842,8 @@ function getInstalledSkills() {
|
|
|
739
842
|
slug,
|
|
740
843
|
version: String(raw.version ?? ""),
|
|
741
844
|
runtime: String(raw.runtime ?? "claude-code"),
|
|
742
|
-
lastRunAt: raw.last_run_at ?? null
|
|
845
|
+
lastRunAt: raw.last_run_at ?? null,
|
|
846
|
+
connectionRequirements: Array.isArray(raw.connection_requirements) ? raw.connection_requirements : []
|
|
743
847
|
};
|
|
744
848
|
} catch {
|
|
745
849
|
return null;
|
|
@@ -751,9 +855,12 @@ function getInstalledSkills() {
|
|
|
751
855
|
}
|
|
752
856
|
|
|
753
857
|
// src/index.ts
|
|
754
|
-
|
|
858
|
+
var require2 = createRequire(import.meta.url);
|
|
859
|
+
var { version } = require2("../package.json");
|
|
860
|
+
program.name("headways").description("Headways CLI \u2014 skill authoring, sync, and runtime SDK").version(version);
|
|
755
861
|
registerAuthCommands(program);
|
|
756
862
|
registerSkillsCommands(program);
|
|
863
|
+
registerConnectionsCommands(program);
|
|
757
864
|
registerSyncCommands(program);
|
|
758
865
|
registerEmitCommand(program);
|
|
759
866
|
registerPrimeCommand(program);
|