ethos-cli 0.1.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 (55) hide show
  1. package/README.md +121 -0
  2. package/dist/bin/ethos.d.ts +4 -0
  3. package/dist/bin/ethos.js +45 -0
  4. package/dist/bin/ethos.js.map +1 -0
  5. package/dist/commands/agents.d.ts +2 -0
  6. package/dist/commands/agents.js +162 -0
  7. package/dist/commands/agents.js.map +1 -0
  8. package/dist/commands/auth.d.ts +2 -0
  9. package/dist/commands/auth.js +180 -0
  10. package/dist/commands/auth.js.map +1 -0
  11. package/dist/commands/skills.d.ts +2 -0
  12. package/dist/commands/skills.js +90 -0
  13. package/dist/commands/skills.js.map +1 -0
  14. package/dist/commands/tables.d.ts +17 -0
  15. package/dist/commands/tables.js +534 -0
  16. package/dist/commands/tables.js.map +1 -0
  17. package/dist/lib/auth-store.d.ts +13 -0
  18. package/dist/lib/auth-store.js +47 -0
  19. package/dist/lib/auth-store.js.map +1 -0
  20. package/dist/lib/client.d.ts +19 -0
  21. package/dist/lib/client.js +69 -0
  22. package/dist/lib/client.js.map +1 -0
  23. package/dist/lib/command-helpers.d.ts +8 -0
  24. package/dist/lib/command-helpers.js +24 -0
  25. package/dist/lib/command-helpers.js.map +1 -0
  26. package/dist/lib/config.d.ts +12 -0
  27. package/dist/lib/config.js +38 -0
  28. package/dist/lib/config.js.map +1 -0
  29. package/dist/lib/output.d.ts +7 -0
  30. package/dist/lib/output.js +23 -0
  31. package/dist/lib/output.js.map +1 -0
  32. package/dist/lib/table-types.d.ts +138 -0
  33. package/dist/lib/table-types.js +2 -0
  34. package/dist/lib/table-types.js.map +1 -0
  35. package/dist/scripts/install-skills.d.ts +1 -0
  36. package/dist/scripts/install-skills.js +44 -0
  37. package/dist/scripts/install-skills.js.map +1 -0
  38. package/dist/skills-installer/agents.d.ts +9 -0
  39. package/dist/skills-installer/agents.js +31 -0
  40. package/dist/skills-installer/agents.js.map +1 -0
  41. package/dist/skills-installer/index.d.ts +4 -0
  42. package/dist/skills-installer/index.js +5 -0
  43. package/dist/skills-installer/index.js.map +1 -0
  44. package/dist/skills-installer/install.d.ts +15 -0
  45. package/dist/skills-installer/install.js +82 -0
  46. package/dist/skills-installer/install.js.map +1 -0
  47. package/dist/skills-installer/lockfile.d.ts +17 -0
  48. package/dist/skills-installer/lockfile.js +23 -0
  49. package/dist/skills-installer/lockfile.js.map +1 -0
  50. package/dist/skills-installer/skill.d.ts +9 -0
  51. package/dist/skills-installer/skill.js +70 -0
  52. package/dist/skills-installer/skill.js.map +1 -0
  53. package/package.json +48 -0
  54. package/postinstall.cjs +23 -0
  55. package/skills/find-people-at-companies.md +111 -0
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # Ethos CLI
2
+
3
+ Commander-based CLI for Ethos agent and table operations.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g ethos-cli
9
+ ```
10
+
11
+ ## Auth
12
+
13
+ The CLI can connect through your existing browser session:
14
+
15
+ ```bash
16
+ ethos auth login
17
+ ethos auth status
18
+ ethos auth logout
19
+ ```
20
+
21
+ Use `--app-url` if your frontend is not on the default local/prod URL:
22
+
23
+ ```bash
24
+ ethos --api-url http://127.0.0.1:8001 auth login --app-url http://localhost:3001
25
+ ```
26
+
27
+ You can also configure auth with flags or environment variables:
28
+
29
+ ```bash
30
+ export ETHOS_API_URL="https://api.ethos.hello-cluster.com"
31
+ export ETHOS_API_KEY="..."
32
+ export ETHOS_ORG_ID="..."
33
+ ```
34
+
35
+ Every command also accepts `--api-url`, `--api-key`, `--access-token`, `--org-id`, and `--json`.
36
+
37
+ ## Bundled Skills
38
+
39
+ Installing the CLI also installs bundled Ethos skills into supported local agents
40
+ when they are detected: Claude Code, Codex, and Cursor.
41
+
42
+ ```bash
43
+ ethos skills install
44
+ ethos skills install --agents codex
45
+ ethos skills list
46
+ ethos skills remove
47
+ ```
48
+
49
+ Set `ETHOS_SKIP_SKILLS=1` before install to skip the automatic postinstall sync.
50
+
51
+ ## Agent Commands
52
+
53
+ ```bash
54
+ ethos agents list
55
+ ethos agents get <agent-id>
56
+
57
+ ethos agents create \
58
+ --name "Company enricher" \
59
+ --instructions "Enrich this company from the row context" \
60
+ --workflow-type company_enrichment \
61
+ --ability web-search \
62
+ --ability linkedin-company-lookup \
63
+ --tool-preset browser
64
+
65
+ ethos agents configure <agent-id> --workflow-type people_sourcing --clear-abilities
66
+ ethos agents delete <agent-id>
67
+ ```
68
+
69
+ Workflow/table compatibility is shown in `--help`: company tables support company enrichment columns and people sourcing, people tables support person enrichment columns. The CLI sends the configured request and lets backend validation decide the final result.
70
+
71
+ ## Table Commands
72
+
73
+ ```bash
74
+ ethos tables list
75
+ ethos tables get <table-id>
76
+ ethos tables import-csv ./companies.csv --entity-type company
77
+ ethos tables import-csv ./people.csv --entity-type people
78
+
79
+ ethos tables views list <table-id>
80
+ ethos tables views create <table-id> --name "Qualified companies" --filter <column-id>:contains:example
81
+ ethos tables views configure <table-id> <view-id> --clear-filters
82
+ ethos tables views delete <table-id> <view-id>
83
+
84
+ ethos tables columns create-agent <table-id> \
85
+ --name "ICP fit" \
86
+ --agent-id <agent-id> \
87
+ --prompt "Score this account" \
88
+ --output-field score \
89
+ --output-field reason
90
+
91
+ ethos tables columns configure <table-id> <column-id> --name "New name"
92
+ ethos tables columns delete <table-id> <column-id>
93
+ ethos tables columns extract-json <table-id> <column-id>
94
+
95
+ ethos tables rows delete <table-id> <row-id>
96
+ ethos tables rows delete <table-id> 3 --by-index
97
+ ethos tables rows delete <table-id> 1 2 3 --by-index
98
+
99
+ ethos tables agent-edits apply <table-id> \
100
+ --instruction "Delete rows that are no longer relevant" \
101
+ --row-id <focused-row-id>
102
+
103
+ ethos tables cells set <table-id> <row-id> <column-id> --value "Manual note"
104
+ ethos tables cells set <table-id> <row-id> <column-id> --value '{"score":9}' --value-json
105
+
106
+ ethos tables runs start <table-id> <column-id> --scope first_5
107
+ ethos tables runs get <run-id>
108
+ ethos tables runs interrupt <run-id>
109
+
110
+ ethos tables source-people <company-table-id> \
111
+ --view-id <view-id> \
112
+ --agent-id <people-sourcing-agent-id> \
113
+ --input-column <company-domain-column-id> \
114
+ --targeting-brief "Find funding and finance contacts" \
115
+ --created-by-codex
116
+
117
+ ethos tables runs create-people-table <run-id>
118
+ ```
119
+
120
+ When stdout is not a TTY, output is JSON by default so coding agents can parse it reliably.
121
+ Table-detail commands such as `tables get`, `tables import-csv`, and `tables runs create-people-table` include top-level `table_id`, `column_ids`, `row_ids`, and `view_ids` in addition to the full nested objects. `tables source-people` includes top-level `table_id`, `column_id`, and `run_id`.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ export declare function createEthosProgram(): Command;
4
+ export declare function main(argv?: string[]): Promise<void>;
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ import { realpathSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { fileURLToPath } from "node:url";
5
+ import { Command } from "commander";
6
+ import { registerAgents } from "../commands/agents.js";
7
+ import { registerAuth } from "../commands/auth.js";
8
+ import { registerSkills } from "../commands/skills.js";
9
+ import { registerTables } from "../commands/tables.js";
10
+ import { addGlobalOptions } from "../lib/config.js";
11
+ import { printError } from "../lib/output.js";
12
+ const pkg = createRequire(import.meta.url)("../../package.json");
13
+ export function createEthosProgram() {
14
+ const program = new Command();
15
+ addGlobalOptions(program
16
+ .name("ethos")
17
+ .description("CLI for Ethos workspace and table operations")
18
+ .version(pkg.version));
19
+ registerAuth(program);
20
+ registerAgents(program);
21
+ registerSkills(program);
22
+ registerTables(program);
23
+ return program;
24
+ }
25
+ export async function main(argv = process.argv) {
26
+ await createEthosProgram().parseAsync(argv);
27
+ }
28
+ function isDirectInvocation() {
29
+ const entry = process.argv[1];
30
+ if (!entry)
31
+ return false;
32
+ try {
33
+ return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(entry);
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ if (isDirectInvocation()) {
40
+ main().catch((error) => {
41
+ printError(error);
42
+ process.exit(1);
43
+ });
44
+ }
45
+ //# sourceMappingURL=ethos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ethos.js","sourceRoot":"","sources":["../../src/bin/ethos.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,oBAAoB,CAAwB,CAAC;AAExF,MAAM,UAAU,kBAAkB;IAChC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,gBAAgB,CACd,OAAO;SACJ,IAAI,CAAC,OAAO,CAAC;SACb,WAAW,CAAC,8CAA8C,CAAC;SAC3D,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CACxB,CAAC;IAEF,YAAY,CAAC,OAAO,CAAC,CAAC;IACtB,cAAc,CAAC,OAAO,CAAC,CAAC;IACxB,cAAc,CAAC,OAAO,CAAC,CAAC;IACxB,cAAc,CAAC,OAAO,CAAC,CAAC;IACxB,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAiB,OAAO,CAAC,IAAI;IACtD,MAAM,kBAAkB,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,kBAAkB;IACzB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,YAAY,CAAC,KAAK,CAAC,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,IAAI,kBAAkB,EAAE,EAAE,CAAC;IACzB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACrB,UAAU,CAAC,KAAK,CAAC,CAAC;QAClB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerAgents(program: Command): void;
@@ -0,0 +1,162 @@
1
+ import { collect, compactObject, jsonRequest, pathId, withClient } from "../lib/command-helpers.js";
2
+ import { printResult } from "../lib/output.js";
3
+ const workflowTypes = new Set([
4
+ "company_enrichment",
5
+ "person_enrichment",
6
+ "people_sourcing",
7
+ ]);
8
+ const abilities = new Set([
9
+ "web-search",
10
+ "web-fetch",
11
+ "browser-use",
12
+ "ethos-hub",
13
+ "linkedin-profile-lookup",
14
+ "linkedin-company-lookup",
15
+ ]);
16
+ const toolPresets = new Set(["filesystem", "shell", "browser"]);
17
+ const compatibilityHelp = `
18
+
19
+ Workflow/table compatibility:
20
+ company_enrichment agents are intended for company table agent columns.
21
+ person_enrichment agents are intended for people table agent columns.
22
+ people_sourcing agents are intended for company tables via "ethos tables source-people".
23
+ The CLI sends the request as configured; backend validation is the source of truth.`;
24
+ export function registerAgents(program) {
25
+ const agents = program.command("agents").alias("agent").description("Manage Ethos agents").addHelpText("after", compatibilityHelp);
26
+ const listCommand = agents.command("list").description("List agents");
27
+ listCommand.action(async () => {
28
+ await withClient(listCommand, async (client, config) => {
29
+ printResult(config, await client.request("/v1/agents"));
30
+ });
31
+ });
32
+ const getCommand = agents.command("get <agent-id>").description("Fetch an agent");
33
+ getCommand.action(async (agentId) => {
34
+ await withClient(getCommand, async (client, config) => {
35
+ printResult(config, await client.request(`/v1/agents/${pathId(agentId)}`));
36
+ });
37
+ });
38
+ const createCommand = addAgentOptions(agents
39
+ .command("create")
40
+ .description("Create an agent with workflow type, abilities, skills, tool presets, and instructions")
41
+ .requiredOption("--name <name>", "agent name")
42
+ .requiredOption("--instructions <instructions>", "full agent instructions prompt"), { includeClearFlags: false, includeCoreFlags: false });
43
+ createCommand.action(async () => {
44
+ await withClient(createCommand, async (client, config) => {
45
+ const options = createCommand.opts();
46
+ const body = compactObject({
47
+ name: options.name,
48
+ description: options.description,
49
+ instructions: options.instructions,
50
+ model: options.model,
51
+ workflow_type: parseWorkflowType(options.workflowType, "--workflow-type"),
52
+ abilities: parseAbilities(options.ability, "--ability"),
53
+ skills: parseStringList(options.skill),
54
+ tool_presets: parseToolPresets(options.toolPreset, "--tool-preset"),
55
+ });
56
+ printResult(config, await client.request("/v1/agents", jsonRequest("POST", body)));
57
+ });
58
+ });
59
+ const configureCommand = addAgentOptions(agents.command("configure <agent-id>").alias("update").description("Update an agent configuration"), { includeClearFlags: true, includeCoreFlags: true });
60
+ configureCommand.action(async (agentId) => {
61
+ await withClient(configureCommand, async (client, config) => {
62
+ const options = configureCommand.opts();
63
+ const body = compactObject({
64
+ name: options.name,
65
+ description: options.description,
66
+ instructions: options.instructions,
67
+ model: options.model,
68
+ workflow_type: parseUpdateWorkflowType(options),
69
+ abilities: parseUpdateList(options.ability, options.clearAbilities, "--ability", parseAbility),
70
+ skills: parseUpdateStringList(options.skill, options.clearSkills),
71
+ tool_presets: parseUpdateList(options.toolPreset, options.clearToolPresets, "--tool-preset", parseToolPreset),
72
+ });
73
+ if (Object.keys(body).length === 0) {
74
+ throw new Error("Pass at least one agent field to configure.");
75
+ }
76
+ printResult(config, await client.request(`/v1/agents/${pathId(agentId)}`, jsonRequest("PATCH", body)));
77
+ });
78
+ });
79
+ const deleteCommand = agents.command("delete <agent-id>").description("Delete an agent");
80
+ deleteCommand.action(async (agentId) => {
81
+ await withClient(deleteCommand, async (client, config) => {
82
+ printResult(config, await client.request(`/v1/agents/${pathId(agentId)}`, { method: "DELETE" }));
83
+ });
84
+ });
85
+ }
86
+ function addAgentOptions(command, options) {
87
+ if (options.includeCoreFlags) {
88
+ command.option("--name <name>", "agent name").option("--instructions <instructions>", "full agent instructions prompt");
89
+ }
90
+ command
91
+ .option("--description <description>", "agent description")
92
+ .option("--model <model>", "model name")
93
+ .option("--workflow-type <type>", "company_enrichment, person_enrichment, or people_sourcing")
94
+ .option("--ability <ability>", "agent ability; repeatable", collect, [])
95
+ .option("--skill <skill>", "skill name; repeatable", collect, [])
96
+ .option("--tool-preset <preset>", "filesystem, shell, or browser; repeatable", collect, []);
97
+ if (options.includeClearFlags) {
98
+ command
99
+ .option("--clear-abilities", "replace abilities with an empty list")
100
+ .option("--clear-skills", "replace skills with an empty list")
101
+ .option("--clear-tool-presets", "replace tool presets with an empty list")
102
+ .option("--clear-workflow-type", "set workflow_type to null");
103
+ }
104
+ return command.addHelpText("after", compatibilityHelp);
105
+ }
106
+ function parseWorkflowType(value, option) {
107
+ if (value === undefined)
108
+ return undefined;
109
+ if (workflowTypes.has(value))
110
+ return value;
111
+ throw new Error(`${option} must be one of company_enrichment, person_enrichment, people_sourcing.`);
112
+ }
113
+ function parseUpdateWorkflowType(options) {
114
+ if (options.workflowType !== undefined && options.clearWorkflowType) {
115
+ throw new Error("Use either --workflow-type or --clear-workflow-type, not both.");
116
+ }
117
+ if (options.clearWorkflowType)
118
+ return null;
119
+ return parseWorkflowType(options.workflowType, "--workflow-type");
120
+ }
121
+ function parseAbilities(values, option) {
122
+ return parseOptionalList(values, option, parseAbility);
123
+ }
124
+ function parseToolPresets(values, option) {
125
+ return parseOptionalList(values, option, parseToolPreset);
126
+ }
127
+ function parseStringList(values) {
128
+ const parsed = cleanList(values);
129
+ return parsed.length ? parsed : undefined;
130
+ }
131
+ function parseUpdateStringList(values, clear) {
132
+ if (values?.length && clear)
133
+ throw new Error("Use either repeated values or the matching --clear flag, not both.");
134
+ if (clear)
135
+ return [];
136
+ return parseStringList(values);
137
+ }
138
+ function parseUpdateList(values, clear, option, parser) {
139
+ if (values?.length && clear)
140
+ throw new Error(`Use either ${option} or the matching --clear flag, not both.`);
141
+ if (clear)
142
+ return [];
143
+ return parseOptionalList(values, option, parser);
144
+ }
145
+ function parseOptionalList(values, option, parser) {
146
+ const parsed = cleanList(values).map((value) => parser(value, option));
147
+ return parsed.length ? Array.from(new Set(parsed)) : undefined;
148
+ }
149
+ function parseAbility(value, option) {
150
+ if (abilities.has(value))
151
+ return value;
152
+ throw new Error(`${option} must be one of web-search, web-fetch, browser-use, ethos-hub, linkedin-profile-lookup, linkedin-company-lookup.`);
153
+ }
154
+ function parseToolPreset(value, option) {
155
+ if (toolPresets.has(value))
156
+ return value;
157
+ throw new Error(`${option} must be one of filesystem, shell, browser.`);
158
+ }
159
+ function cleanList(values) {
160
+ return (values || []).map((value) => value.trim()).filter(Boolean);
161
+ }
162
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.js","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACpG,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAkB/C,MAAM,aAAa,GAAG,IAAI,GAAG,CAAoB;IAC/C,oBAAoB;IACpB,mBAAmB;IACnB,iBAAiB;CAClB,CAAC,CAAC;AACH,MAAM,SAAS,GAAG,IAAI,GAAG,CAAe;IACtC,YAAY;IACZ,WAAW;IACX,aAAa;IACb,WAAW;IACX,yBAAyB;IACzB,yBAAyB;CAC1B,CAAC,CAAC;AACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAa,CAAC,YAAY,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;AAE5E,MAAM,iBAAiB,GAAG;;;;;;sFAM4D,CAAC;AAEvF,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC,WAAW,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;IAEnI,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;IACtE,WAAW,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;QAC5B,MAAM,UAAU,CAAC,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACrD,WAAW,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC,OAAO,CAAU,YAAY,CAAC,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC;IAClF,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,EAAE;QAC1C,MAAM,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACpD,WAAW,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC,OAAO,CAAQ,cAAc,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QACpF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,aAAa,GAAG,eAAe,CACnC,MAAM;SACH,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,uFAAuF,CAAC;SACpG,cAAc,CAAC,eAAe,EAAE,YAAY,CAAC;SAC7C,cAAc,CAAC,+BAA+B,EAAE,gCAAgC,CAAC,EACpF,EAAE,iBAAiB,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,CACtD,CAAC;IACF,aAAa,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;QAC9B,MAAM,UAAU,CAAC,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACvD,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,EAAgB,CAAC;YACnD,MAAM,IAAI,GAAG,aAAa,CAAC;gBACzB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,aAAa,EAAE,iBAAiB,CAAC,OAAO,CAAC,YAAY,EAAE,iBAAiB,CAAC;gBACzE,SAAS,EAAE,cAAc,CAAC,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC;gBACvD,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC;gBACtC,YAAY,EAAE,gBAAgB,CAAC,OAAO,CAAC,UAAU,EAAE,eAAe,CAAC;aACpE,CAAC,CAAC;YACH,WAAW,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC,OAAO,CAAQ,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5F,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,gBAAgB,GAAG,eAAe,CACtC,MAAM,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,+BAA+B,CAAC,EACnG,EAAE,iBAAiB,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,CACpD,CAAC;IACF,gBAAgB,CAAC,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,EAAE;QAChD,MAAM,UAAU,CAAC,gBAAgB,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YAC1D,MAAM,OAAO,GAAG,gBAAgB,CAAC,IAAI,EAAgB,CAAC;YACtD,MAAM,IAAI,GAAG,aAAa,CAAC;gBACzB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,aAAa,EAAE,uBAAuB,CAAC,OAAO,CAAC;gBAC/C,SAAS,EAAE,eAAe,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,cAAc,EAAE,WAAW,EAAE,YAAY,CAAC;gBAC9F,MAAM,EAAE,qBAAqB,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC;gBACjE,YAAY,EAAE,eAAe,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,gBAAgB,EAAE,eAAe,EAAE,eAAe,CAAC;aAC9G,CAAC,CAAC;YACH,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;YACjE,CAAC;YACD,WAAW,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC,OAAO,CAAQ,cAAc,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QAChH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC;IACzF,aAAa,CAAC,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,EAAE;QAC7C,MAAM,UAAU,CAAC,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACvD,WAAW,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC,OAAO,CAAQ,cAAc,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC1G,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CAAC,OAAgB,EAAE,OAAkE;IAC3G,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;QAC7B,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC,MAAM,CAAC,+BAA+B,EAAE,gCAAgC,CAAC,CAAC;IAC1H,CAAC;IACD,OAAO;SACJ,MAAM,CAAC,6BAA6B,EAAE,mBAAmB,CAAC;SAC1D,MAAM,CAAC,iBAAiB,EAAE,YAAY,CAAC;SACvC,MAAM,CAAC,wBAAwB,EAAE,2DAA2D,CAAC;SAC7F,MAAM,CAAC,qBAAqB,EAAE,2BAA2B,EAAE,OAAO,EAAE,EAAE,CAAC;SACvE,MAAM,CAAC,iBAAiB,EAAE,wBAAwB,EAAE,OAAO,EAAE,EAAE,CAAC;SAChE,MAAM,CAAC,wBAAwB,EAAE,2CAA2C,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;IAC9F,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;QAC9B,OAAO;aACJ,MAAM,CAAC,mBAAmB,EAAE,sCAAsC,CAAC;aACnE,MAAM,CAAC,gBAAgB,EAAE,mCAAmC,CAAC;aAC7D,MAAM,CAAC,sBAAsB,EAAE,yCAAyC,CAAC;aACzE,MAAM,CAAC,uBAAuB,EAAE,2BAA2B,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAyB,EAAE,MAAc;IAClE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,aAAa,CAAC,GAAG,CAAC,KAA0B,CAAC;QAAE,OAAO,KAA0B,CAAC;IACrF,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,yEAAyE,CAAC,CAAC;AACtG,CAAC;AAED,SAAS,uBAAuB,CAAC,OAAqB;IACpD,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;IACpF,CAAC;IACD,IAAI,OAAO,CAAC,iBAAiB;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,iBAAiB,CAAC,OAAO,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,cAAc,CAAC,MAA4B,EAAE,MAAc;IAClE,OAAO,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,gBAAgB,CAAC,MAA4B,EAAE,MAAc;IACpE,OAAO,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,eAAe,CAAC,MAA4B;IACnD,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5C,CAAC;AAED,SAAS,qBAAqB,CAAC,MAA4B,EAAE,KAA0B;IACrF,IAAI,MAAM,EAAE,MAAM,IAAI,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IACnH,IAAI,KAAK;QAAE,OAAO,EAAE,CAAC;IACrB,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,eAAe,CACtB,MAA4B,EAC5B,KAA0B,EAC1B,MAAc,EACd,MAA4C;IAE5C,IAAI,MAAM,EAAE,MAAM,IAAI,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,cAAc,MAAM,0CAA0C,CAAC,CAAC;IAC7G,IAAI,KAAK;QAAE,OAAO,EAAE,CAAC;IACrB,OAAO,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,iBAAiB,CACxB,MAA4B,EAC5B,MAAc,EACd,MAA4C;IAE5C,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IACvE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACjE,CAAC;AAED,SAAS,YAAY,CAAC,KAAa,EAAE,MAAc;IACjD,IAAI,SAAS,CAAC,GAAG,CAAC,KAAqB,CAAC;QAAE,OAAO,KAAqB,CAAC;IACvE,MAAM,IAAI,KAAK,CACb,GAAG,MAAM,kHAAkH,CAC5H,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,KAAa,EAAE,MAAc;IACpD,IAAI,WAAW,CAAC,GAAG,CAAC,KAAmB,CAAC;QAAE,OAAO,KAAmB,CAAC;IACrE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,6CAA6C,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,SAAS,CAAC,MAA4B;IAC7C,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACrE,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerAuth(program: Command): void;
@@ -0,0 +1,180 @@
1
+ import { spawn } from "node:child_process";
2
+ import { hostname } from "node:os";
3
+ import { authConfigPath, clearStoredAuth, readStoredAuth, writeStoredAuth } from "../lib/auth-store.js";
4
+ import { isJsonMode, resolveApiUrl } from "../lib/config.js";
5
+ import { printResult } from "../lib/output.js";
6
+ export function registerAuth(program) {
7
+ const auth = program.command("auth").description("Authenticate the Ethos CLI");
8
+ const loginCommand = auth
9
+ .command("login")
10
+ .description("Connect the CLI through your browser session")
11
+ .option("--app-url <url>", "Ethos web app URL", process.env.ETHOS_APP_URL)
12
+ .option("--device-name <name>", "display name for this CLI session")
13
+ .option("--no-open", "print the approval URL without opening a browser")
14
+ .option("--timeout <seconds>", "seconds to wait for browser approval", "120");
15
+ loginCommand.action(async () => {
16
+ const apiUrl = resolveApiUrl(loginCommand);
17
+ const options = loginCommand.opts();
18
+ const timeoutSeconds = parseTimeout(options.timeout);
19
+ const deviceName = options.deviceName || `Ethos CLI on ${hostname() || "this machine"}`;
20
+ const appUrl = (options.appUrl || inferAppUrl(apiUrl)).replace(/\/+$/, "");
21
+ const registered = await apiRequest(apiUrl, "/v1/auth/cli/register", {
22
+ method: "POST",
23
+ body: {
24
+ app_url: appUrl,
25
+ device_name: deviceName,
26
+ },
27
+ });
28
+ if (isJsonMode(loginCommand)) {
29
+ process.stdout.write(`${JSON.stringify({ success: true, response: { claim_url: registered.claim_url, expires_at: registered.expires_at } }, null, 2)}\n`);
30
+ }
31
+ else {
32
+ process.stdout.write(`Opening browser approval: ${registered.claim_url}\n`);
33
+ }
34
+ if (options.open !== false) {
35
+ openInBrowser(registered.claim_url);
36
+ }
37
+ const claimed = await waitForClaim(apiUrl, registered.claim_token, timeoutSeconds);
38
+ if (!claimed.api_key) {
39
+ throw new Error("Browser approval completed, but the API did not return a CLI token.");
40
+ }
41
+ writeStoredAuth({
42
+ apiUrl,
43
+ apiKey: claimed.api_key,
44
+ orgId: claimed.org_id ?? undefined,
45
+ userEmail: claimed.user_email ?? undefined,
46
+ userId: claimed.user_id ?? undefined,
47
+ apiKeyId: claimed.api_key_id ?? undefined,
48
+ savedAt: new Date().toISOString(),
49
+ });
50
+ printResult({
51
+ apiUrl,
52
+ apiKey: claimed.api_key,
53
+ orgId: claimed.org_id ?? undefined,
54
+ json: isJsonMode(loginCommand),
55
+ }, {
56
+ authenticated: true,
57
+ api_url: apiUrl,
58
+ org_id: claimed.org_id,
59
+ org_name: claimed.org_name,
60
+ user_email: claimed.user_email,
61
+ config_path: authConfigPath(),
62
+ });
63
+ });
64
+ const statusCommand = auth.command("status").description("Show the saved CLI auth state");
65
+ statusCommand.action(async () => {
66
+ const apiUrl = resolveApiUrl(statusCommand);
67
+ const stored = readStoredAuth();
68
+ const storedMatchesApiUrl = stored?.apiUrl.replace(/\/+$/, "") === apiUrl;
69
+ if (!stored || !storedMatchesApiUrl) {
70
+ printAuthResult(statusCommand, {
71
+ authenticated: false,
72
+ api_url: apiUrl,
73
+ config_path: authConfigPath(),
74
+ });
75
+ return;
76
+ }
77
+ const orgs = await apiRequest(apiUrl, "/v1/auth/orgs", {
78
+ method: "GET",
79
+ apiKey: stored.apiKey,
80
+ orgId: stored.orgId,
81
+ });
82
+ printAuthResult(statusCommand, {
83
+ authenticated: true,
84
+ api_url: apiUrl,
85
+ org_id: stored.orgId,
86
+ user_email: stored.userEmail,
87
+ orgs: orgs.orgs,
88
+ config_path: authConfigPath(),
89
+ });
90
+ });
91
+ const logoutCommand = auth.command("logout").description("Remove the saved CLI token from this machine");
92
+ logoutCommand.action(async () => {
93
+ const removed = clearStoredAuth();
94
+ printAuthResult(logoutCommand, {
95
+ authenticated: false,
96
+ removed,
97
+ config_path: authConfigPath(),
98
+ });
99
+ });
100
+ }
101
+ async function waitForClaim(apiUrl, claimToken, timeoutSeconds) {
102
+ const deadline = Date.now() + timeoutSeconds * 1000;
103
+ while (Date.now() <= deadline) {
104
+ const status = await apiRequest(apiUrl, "/v1/auth/cli/status", {
105
+ method: "POST",
106
+ body: {
107
+ claim_token: claimToken,
108
+ reveal: true,
109
+ },
110
+ });
111
+ if (status.status === "claimed")
112
+ return status;
113
+ await new Promise((resolve) => setTimeout(resolve, 2000));
114
+ }
115
+ throw new Error("Still waiting for browser approval. Re-run `ethos auth login` when you are ready to try again.");
116
+ }
117
+ async function apiRequest(apiUrl, path, options = { method: "GET" }) {
118
+ const headers = new Headers();
119
+ if (options.body !== undefined)
120
+ headers.set("Content-Type", "application/json");
121
+ if (options.apiKey)
122
+ headers.set("Authorization", `Bearer ${options.apiKey}`);
123
+ if (options.orgId)
124
+ headers.set("X-Org-Id", options.orgId);
125
+ const response = await fetch(`${apiUrl}${path}`, {
126
+ method: options.method,
127
+ headers,
128
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
129
+ });
130
+ const envelope = (await response.json());
131
+ if (!response.ok || envelope.success === false) {
132
+ throw new Error(getErrorMessage(envelope.error ?? envelope.detail ?? envelope, response.status));
133
+ }
134
+ return envelope.response;
135
+ }
136
+ function parseTimeout(value) {
137
+ const parsed = Number.parseInt(value || "", 10);
138
+ if (!Number.isFinite(parsed) || parsed < 1)
139
+ throw new Error("--timeout must be a positive number of seconds.");
140
+ return Math.min(parsed, 600);
141
+ }
142
+ function inferAppUrl(apiUrl) {
143
+ try {
144
+ const url = new URL(apiUrl);
145
+ if (url.hostname === "localhost" || url.hostname === "127.0.0.1") {
146
+ url.port = url.port === "8001" ? "3001" : "3000";
147
+ return url.toString().replace(/\/+$/, "");
148
+ }
149
+ }
150
+ catch {
151
+ // Fall through to production default.
152
+ }
153
+ return "https://ethos.hello-cluster.com";
154
+ }
155
+ function openInBrowser(url) {
156
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
157
+ const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
158
+ try {
159
+ const child = spawn(command, args, { detached: true, stdio: "ignore" });
160
+ child.unref();
161
+ }
162
+ catch {
163
+ // The approval URL was already printed.
164
+ }
165
+ }
166
+ function printAuthResult(command, response) {
167
+ const apiUrl = resolveApiUrl(command);
168
+ printResult({ apiUrl, apiKey: "", json: isJsonMode(command) }, response);
169
+ }
170
+ function getErrorMessage(error, status) {
171
+ if (typeof error === "string")
172
+ return error;
173
+ if (error && typeof error === "object" && "message" in error) {
174
+ const message = error.message;
175
+ if (typeof message === "string")
176
+ return message;
177
+ }
178
+ return `API request failed: ${status}`;
179
+ }
180
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerSkills(program: Command): void;
@@ -0,0 +1,90 @@
1
+ import { createRequire } from "node:module";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { isJsonMode } from "../lib/config.js";
5
+ import { printResult } from "../lib/output.js";
6
+ import { AGENTS, readLockfile, removeAllSkills, syncSkills, } from "../skills-installer/index.js";
7
+ const here = dirname(fileURLToPath(import.meta.url));
8
+ const SKILLS_DIR = join(here, "..", "..", "skills");
9
+ const pkg = createRequire(import.meta.url)("../../package.json");
10
+ const ALL_AGENTS = Object.keys(AGENTS);
11
+ export function registerSkills(program) {
12
+ const skills = program
13
+ .command("skills")
14
+ .description("Install bundled Ethos skills into supported coding agents. This normally runs during npm install.");
15
+ const installCommand = skills
16
+ .command("install")
17
+ .description("Install bundled Ethos skills into detected agents")
18
+ .option("-a, --agents <list>", `comma-separated agents (default: auto-detect). Supported: ${ALL_AGENTS.join(", ")}`);
19
+ installCommand.action(async () => {
20
+ const result = await syncSkills({
21
+ bundledSkillsDir: SKILLS_DIR,
22
+ ethosVersion: pkg.version,
23
+ agents: parseAgentList(installCommand.opts().agents),
24
+ });
25
+ printSkillsResult(installCommand, {
26
+ label: `Targets: ${result.targetAgents.map((agent) => AGENTS[agent].displayName).join(", ") ||
27
+ "(none detected - pass --agents to force)"}`,
28
+ data: {
29
+ target_agents: result.targetAgents,
30
+ skipped_agents: result.skippedAgents,
31
+ installed: result.installed,
32
+ updated: result.updated,
33
+ removed: result.removed,
34
+ },
35
+ });
36
+ });
37
+ const listCommand = skills
38
+ .command("list")
39
+ .description("Show Ethos-installed skills and where they live on disk");
40
+ listCommand.action(async () => {
41
+ const lock = await readLockfile();
42
+ const installedSkills = Object.entries(lock.skills).map(([name, entry]) => ({
43
+ name,
44
+ installed_at: entry.installedAt,
45
+ agents: Object.fromEntries(Object.entries(entry.agents).map(([agent, info]) => [agent, info?.path])),
46
+ }));
47
+ printSkillsResult(listCommand, {
48
+ label: installedSkills.length
49
+ ? `Installed Ethos skills: ${installedSkills.map((skill) => skill.name).join(", ")}`
50
+ : "No Ethos skills installed. Run `ethos skills install`.",
51
+ data: {
52
+ ethos_version: lock.ethosVersion,
53
+ count: installedSkills.length,
54
+ skills: installedSkills,
55
+ },
56
+ });
57
+ });
58
+ const removeCommand = skills
59
+ .command("remove")
60
+ .description("Remove every Ethos-installed skill from every agent");
61
+ removeCommand.action(async () => {
62
+ const removed = await removeAllSkills();
63
+ printSkillsResult(removeCommand, {
64
+ label: removed.length ? `Removed: ${removed.join(", ")}` : "Nothing to remove.",
65
+ data: { removed },
66
+ });
67
+ });
68
+ }
69
+ function parseAgentList(value) {
70
+ if (!value)
71
+ return undefined;
72
+ const agents = value
73
+ .split(",")
74
+ .map((part) => part.trim())
75
+ .filter(Boolean);
76
+ const parsed = [];
77
+ for (const agent of agents) {
78
+ if (ALL_AGENTS.includes(agent)) {
79
+ parsed.push(agent);
80
+ }
81
+ else {
82
+ throw new Error(`unknown agent: ${agent} (supported: ${ALL_AGENTS.join(", ")})`);
83
+ }
84
+ }
85
+ return parsed;
86
+ }
87
+ function printSkillsResult(command, result) {
88
+ printResult({ apiUrl: "", apiKey: "", json: isJsonMode(command) }, result);
89
+ }
90
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/commands/skills.ts"],"names":[],"mappings":"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"}