@yoyo-bot/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 (58) hide show
  1. package/README.md +160 -0
  2. package/dist/commands/auth.d.ts +5 -0
  3. package/dist/commands/auth.d.ts.map +1 -0
  4. package/dist/commands/auth.js +26 -0
  5. package/dist/commands/auth.js.map +1 -0
  6. package/dist/commands/bind.d.ts +9 -0
  7. package/dist/commands/bind.d.ts.map +1 -0
  8. package/dist/commands/bind.js +132 -0
  9. package/dist/commands/bind.js.map +1 -0
  10. package/dist/commands/init.d.ts +5 -0
  11. package/dist/commands/init.d.ts.map +1 -0
  12. package/dist/commands/init.js +57 -0
  13. package/dist/commands/init.js.map +1 -0
  14. package/dist/commands/list.d.ts +6 -0
  15. package/dist/commands/list.d.ts.map +1 -0
  16. package/dist/commands/list.js +38 -0
  17. package/dist/commands/list.js.map +1 -0
  18. package/dist/commands/publish.d.ts +8 -0
  19. package/dist/commands/publish.d.ts.map +1 -0
  20. package/dist/commands/publish.js +96 -0
  21. package/dist/commands/publish.js.map +1 -0
  22. package/dist/commands/search.d.ts +5 -0
  23. package/dist/commands/search.d.ts.map +1 -0
  24. package/dist/commands/search.js +36 -0
  25. package/dist/commands/search.js.map +1 -0
  26. package/dist/commands/unbind.d.ts +7 -0
  27. package/dist/commands/unbind.d.ts.map +1 -0
  28. package/dist/commands/unbind.js +26 -0
  29. package/dist/commands/unbind.js.map +1 -0
  30. package/dist/commands/whoami.d.ts +4 -0
  31. package/dist/commands/whoami.d.ts.map +1 -0
  32. package/dist/commands/whoami.js +34 -0
  33. package/dist/commands/whoami.js.map +1 -0
  34. package/dist/index.d.ts +3 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +67 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/lib/api.d.ts +60 -0
  39. package/dist/lib/api.d.ts.map +1 -0
  40. package/dist/lib/api.js +54 -0
  41. package/dist/lib/api.js.map +1 -0
  42. package/dist/lib/config.d.ts +11 -0
  43. package/dist/lib/config.d.ts.map +1 -0
  44. package/dist/lib/config.js +38 -0
  45. package/dist/lib/config.js.map +1 -0
  46. package/package.json +26 -0
  47. package/src/commands/auth.ts +30 -0
  48. package/src/commands/bind.ts +147 -0
  49. package/src/commands/init.ts +69 -0
  50. package/src/commands/list.ts +43 -0
  51. package/src/commands/publish.ts +117 -0
  52. package/src/commands/search.ts +40 -0
  53. package/src/commands/unbind.ts +34 -0
  54. package/src/commands/whoami.ts +34 -0
  55. package/src/index.ts +76 -0
  56. package/src/lib/api.ts +118 -0
  57. package/src/lib/config.ts +45 -0
  58. package/tsconfig.json +10 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEpC,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAa,EAAE,IAAwC;IACzF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAExD,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,iBAAiB,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO;IACT,CAAC;IAED,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,mBAAmB,KAAK,GAAG,CAAC,CAAC,CAAC;QACjD,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,yBAAyB,KAAK,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC;IAE/F,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,MAAM,KAAK,GAAG,CAAC,CAAC,YAAY,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,KAAK,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpH,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,KAAK,KAAK,GAAG,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,CAAC,GAAG,EAAE,CAAC;IAChB,CAAC;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ interface UnbindOptions {
2
+ dir?: string;
3
+ force?: boolean;
4
+ }
5
+ export declare function unbindCommand(ref: string, opts: UnbindOptions): Promise<void>;
6
+ export {};
7
+ //# sourceMappingURL=unbind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unbind.d.ts","sourceRoot":"","sources":["../../src/commands/unbind.ts"],"names":[],"mappings":"AAMA,UAAU,aAAa;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAMD,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBnF"}
@@ -0,0 +1,26 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { existsSync } from "node:fs";
5
+ import pc from "picocolors";
6
+ function defaultBindRoot() {
7
+ return join(homedir(), ".yoyo", "binders");
8
+ }
9
+ export async function unbindCommand(ref, opts) {
10
+ const parts = ref.split("/");
11
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
12
+ console.error(pc.red(`Invalid ref "${ref}". Expected <owner>/<slug>`));
13
+ process.exit(1);
14
+ }
15
+ const [owner, slug] = parts;
16
+ const bindRoot = opts.dir ? resolve(opts.dir) : defaultBindRoot();
17
+ const bindDir = join(bindRoot, owner, slug);
18
+ if (!existsSync(bindDir)) {
19
+ console.error(pc.red(`Not bound: ${ref} (${bindDir} not found)`));
20
+ process.exit(1);
21
+ }
22
+ console.log(pc.dim(`Removing ${bindDir}…`));
23
+ await rm(bindDir, { recursive: true, force: true });
24
+ console.log(pc.green(`Unbound ${ref}`));
25
+ }
26
+ //# sourceMappingURL=unbind.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unbind.js","sourceRoot":"","sources":["../../src/commands/unbind.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,MAAM,YAAY,CAAC;AAO5B,SAAS,eAAe;IACtB,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW,EAAE,IAAmB;IAClE,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,gBAAgB,GAAG,4BAA4B,CAAC,CAAC,CAAC;QACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC;IAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC;IAClE,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,KAAM,EAAE,IAAK,CAAC,CAAC;IAE9C,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,cAAc,GAAG,KAAK,OAAO,aAAa,CAAC,CAAC,CAAC;QAClE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,YAAY,OAAO,GAAG,CAAC,CAAC,CAAC;IAC5C,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,GAAG,EAAE,CAAC,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function whoamiCommand(opts: {
2
+ json?: boolean;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=whoami.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whoami.d.ts","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAIA,wBAAsB,aAAa,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA6B3E"}
@@ -0,0 +1,34 @@
1
+ import pc from "picocolors";
2
+ import { api, ApiError } from "../lib/api.js";
3
+ import { getApiKey } from "../lib/config.js";
4
+ export async function whoamiCommand(opts) {
5
+ const key = await getApiKey();
6
+ if (!key) {
7
+ console.error(pc.red("Not authenticated. Run: yoyo auth"));
8
+ process.exit(1);
9
+ }
10
+ let user;
11
+ try {
12
+ user = await api.whoami();
13
+ }
14
+ catch (err) {
15
+ if (err instanceof ApiError && err.status === 401) {
16
+ console.error(pc.red("Invalid or expired API key. Run: yoyo auth"));
17
+ }
18
+ else {
19
+ console.error(pc.red(`Error: ${String(err)}`));
20
+ }
21
+ process.exit(1);
22
+ }
23
+ if (opts.json) {
24
+ console.log(JSON.stringify(user, null, 2));
25
+ return;
26
+ }
27
+ console.log();
28
+ console.log(` ${pc.bold(user.displayName ?? user.slug)}`);
29
+ console.log(` ${pc.dim("Slug")} ${user.slug}`);
30
+ if (user.email)
31
+ console.log(` ${pc.dim("Email")} ${user.email}`);
32
+ console.log();
33
+ }
34
+ //# sourceMappingURL=whoami.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAwB;IAC1D,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC,CAAC;QAC3D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,IAAI,CAAC;IACT,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAClD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC,CAAC;QACtE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3C,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAClD,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,EAAE,CAAC;AAChB,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ import { program } from "commander";
3
+ import { searchCommand } from "./commands/search.js";
4
+ import { bindCommand } from "./commands/bind.js";
5
+ import { unbindCommand } from "./commands/unbind.js";
6
+ import { publishCommand } from "./commands/publish.js";
7
+ import { initCommand } from "./commands/init.js";
8
+ import { authCommand } from "./commands/auth.js";
9
+ import { whoamiCommand } from "./commands/whoami.js";
10
+ import { listCommand } from "./commands/list.js";
11
+ program
12
+ .name("yoyo")
13
+ .description("Yoyo — the AI agent binder registry CLI")
14
+ .version("0.0.1");
15
+ program
16
+ .command("search <query>")
17
+ .description("Search for binders in the registry")
18
+ .option("-l, --limit <n>", "max results", "10")
19
+ .option("--json", "output JSON")
20
+ .action((query, opts) => searchCommand(query, opts));
21
+ program
22
+ .command("bind <owner/slug>")
23
+ .description("Bind (install) a binder to ~/.yoyo/binders/")
24
+ .option("-v, --version <ver>", "specific version")
25
+ .option("-d, --dir <path>", "bind root directory")
26
+ .option("--dry-run", "preview without writing")
27
+ .option("--json", "output JSON")
28
+ .action((ref, opts) => bindCommand(ref, opts));
29
+ program
30
+ .command("unbind <owner/slug>")
31
+ .description("Remove a bound binder")
32
+ .option("-d, --dir <path>", "bind root directory")
33
+ .option("-f, --force", "skip confirmation")
34
+ .action((ref, opts) => unbindCommand(ref, opts));
35
+ program
36
+ .command("list")
37
+ .description("List binders in the registry")
38
+ .option("--owner <slug>", "filter by owner")
39
+ .option("--page <n>", "page number", "1")
40
+ .option("--json", "output JSON")
41
+ .action((opts) => listCommand(opts));
42
+ program
43
+ .command("publish")
44
+ .description("Publish binder from current directory")
45
+ .option("-d, --dir <path>", "binder directory (default: cwd)")
46
+ .option("--dry-run", "preview without uploading")
47
+ .option("--json", "output JSON")
48
+ .action((opts) => publishCommand(opts));
49
+ program
50
+ .command("init")
51
+ .description("Scaffold a new binder in the current directory")
52
+ .option("-d, --dir <path>", "target directory (default: cwd)")
53
+ .option("-y, --yes", "skip prompts, use defaults")
54
+ .action((opts) => initCommand(opts));
55
+ program
56
+ .command("auth")
57
+ .description("Save your Yoyo API key to ~/.yoyo/config.json")
58
+ .option("-k, --key <apiKey>", "API key (omit to prompt)")
59
+ .option("--url <apiUrl>", "custom API URL")
60
+ .action((opts) => authCommand(opts));
61
+ program
62
+ .command("whoami")
63
+ .description("Show the currently authenticated user")
64
+ .option("--json", "output JSON")
65
+ .action((opts) => whoamiCommand(opts));
66
+ program.parseAsync(process.argv);
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ export declare class ApiError extends Error {
2
+ status: number;
3
+ body?: unknown | undefined;
4
+ constructor(status: number, message: string, body?: unknown | undefined);
5
+ }
6
+ export interface BinderSummary {
7
+ owner: string;
8
+ slug: string;
9
+ displayName?: string;
10
+ description?: string;
11
+ version?: string;
12
+ downloads?: number;
13
+ tab?: string;
14
+ verification?: "official" | "verified" | "none";
15
+ tags?: string[];
16
+ }
17
+ export interface BinderDetail extends BinderSummary {
18
+ readme?: string;
19
+ latestVersion?: {
20
+ version: string;
21
+ tarballUrl?: string;
22
+ tarballSha256?: string;
23
+ platforms?: string[];
24
+ };
25
+ }
26
+ export interface SearchResult {
27
+ items: BinderSummary[];
28
+ total: number;
29
+ page: number;
30
+ pageSize: number;
31
+ }
32
+ export interface WhoamiResult {
33
+ id: string;
34
+ slug: string;
35
+ displayName?: string;
36
+ email?: string;
37
+ }
38
+ export declare const api: {
39
+ search: (q: string, page?: number, pageSize?: number) => Promise<SearchResult>;
40
+ getBinder: (owner: string, slug: string) => Promise<BinderDetail>;
41
+ listBinders: (owner?: string, page?: number, pageSize?: number) => Promise<SearchResult>;
42
+ whoami: () => Promise<WhoamiResult>;
43
+ publish: (data: {
44
+ slug: string;
45
+ displayName?: string;
46
+ description?: string;
47
+ version: string;
48
+ readme?: string;
49
+ tags?: string[];
50
+ tab?: string;
51
+ license?: string;
52
+ platforms?: string[];
53
+ dependencies?: Record<string, string>;
54
+ }) => Promise<{
55
+ id: string;
56
+ slug: string;
57
+ version: string;
58
+ }>;
59
+ };
60
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAEA,qBAAa,QAAS,SAAQ,KAAK;IAExB,MAAM,EAAE,MAAM;IAEd,IAAI,CAAC,EAAE,OAAO;gBAFd,MAAM,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACR,IAAI,CAAC,EAAE,OAAO,YAAA;CAKxB;AAoCD,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;IAChD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,eAAO,MAAM,GAAG;gBACF,MAAM;uBAKC,MAAM,QAAQ,MAAM;0BAKjB,MAAM;;oBASZ;QACd,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACvC;YAAmB,MAAM;cAAQ,MAAM;iBAAW,MAAM;;CAC1D,CAAC"}
@@ -0,0 +1,54 @@
1
+ import { getApiKey, getApiUrl } from "./config.js";
2
+ export class ApiError extends Error {
3
+ status;
4
+ body;
5
+ constructor(status, message, body) {
6
+ super(message);
7
+ this.status = status;
8
+ this.body = body;
9
+ this.name = "ApiError";
10
+ }
11
+ }
12
+ async function request(method, path, opts = {}) {
13
+ const base = await getApiUrl();
14
+ const url = `${base}${path}`;
15
+ const headers = { "Content-Type": "application/json" };
16
+ if (opts.auth !== false) {
17
+ const key = await getApiKey();
18
+ if (key)
19
+ headers["Authorization"] = `Bearer ${key}`;
20
+ }
21
+ const res = await fetch(url, {
22
+ method,
23
+ headers,
24
+ body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
25
+ });
26
+ if (!res.ok) {
27
+ let detail = "";
28
+ try {
29
+ const j = (await res.json());
30
+ detail = j.message ?? j.error ?? "";
31
+ }
32
+ catch { }
33
+ throw new ApiError(res.status, detail || res.statusText);
34
+ }
35
+ return res.json();
36
+ }
37
+ // ── API calls ──────────────────────────────────────────────────────────────
38
+ export const api = {
39
+ search: (q, page = 1, pageSize = 20) => request("GET", `/binders?q=${encodeURIComponent(q)}&page=${page}&pageSize=${pageSize}`, {
40
+ auth: false,
41
+ }),
42
+ getBinder: (owner, slug) => request("GET", `/binders/${encodeURIComponent(owner)}/${encodeURIComponent(slug)}`, {
43
+ auth: false,
44
+ }),
45
+ listBinders: (owner, page = 1, pageSize = 20) => {
46
+ const ownerQ = owner ? `&owner=${encodeURIComponent(owner)}` : "";
47
+ return request("GET", `/binders?page=${page}&pageSize=${pageSize}${ownerQ}`, {
48
+ auth: false,
49
+ });
50
+ },
51
+ whoami: () => request("GET", "/auth/me"),
52
+ publish: (data) => request("POST", "/binders", { body: data }),
53
+ };
54
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface YoyoConfig {
2
+ apiKey?: string;
3
+ apiUrl?: string;
4
+ }
5
+ export declare function configPath(): string;
6
+ export declare function defaultApiUrl(): string;
7
+ export declare function loadConfig(): Promise<YoyoConfig>;
8
+ export declare function saveConfig(cfg: YoyoConfig): Promise<void>;
9
+ export declare function getApiKey(): Promise<string | undefined>;
10
+ export declare function getApiUrl(): Promise<string>;
11
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,CAStD;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/D;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAI7D;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAGjD"}
@@ -0,0 +1,38 @@
1
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
2
+ import { join, dirname } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { existsSync } from "node:fs";
5
+ export function configPath() {
6
+ return join(homedir(), ".yoyo", "config.json");
7
+ }
8
+ export function defaultApiUrl() {
9
+ return process.env.YOYO_API_URL ?? "https://api.yoyo.bot/v1";
10
+ }
11
+ export async function loadConfig() {
12
+ const p = configPath();
13
+ if (!existsSync(p))
14
+ return {};
15
+ try {
16
+ const raw = await readFile(p, "utf8");
17
+ return JSON.parse(raw);
18
+ }
19
+ catch {
20
+ return {};
21
+ }
22
+ }
23
+ export async function saveConfig(cfg) {
24
+ const p = configPath();
25
+ await mkdir(dirname(p), { recursive: true });
26
+ await writeFile(p, JSON.stringify(cfg, null, 2) + "\n", "utf8");
27
+ }
28
+ export async function getApiKey() {
29
+ if (process.env.YOYO_API_KEY)
30
+ return process.env.YOYO_API_KEY;
31
+ const cfg = await loadConfig();
32
+ return cfg.apiKey;
33
+ }
34
+ export async function getApiUrl() {
35
+ const cfg = await loadConfig();
36
+ return process.env.YOYO_API_URL ?? cfg.apiUrl ?? "https://api.yoyo.bot/v1";
37
+ }
38
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAOrC,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,yBAAyB,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,MAAM,CAAC,GAAG,UAAU,EAAE,CAAC;IACvB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAe;IAC9C,MAAM,CAAC,GAAG,UAAU,EAAE,CAAC;IACvB,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,MAAM,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAC9D,MAAM,GAAG,GAAG,MAAM,UAAU,EAAE,CAAC;IAC/B,OAAO,GAAG,CAAC,MAAM,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,GAAG,GAAG,MAAM,UAAU,EAAE,CAAC;IAC/B,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,MAAM,IAAI,yBAAyB,CAAC;AAC7E,CAAC"}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@yoyo-bot/cli",
3
+ "version": "0.1.0",
4
+ "description": "YoYo CLI — search, install, fork, and publish binders for AI agents. The universal binder registry.",
5
+ "type": "module",
6
+ "bin": {
7
+ "yoyo": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsc --watch",
12
+ "typecheck": "tsc --noEmit",
13
+ "start": "node dist/index.js"
14
+ },
15
+ "dependencies": {
16
+ "commander": "^12.1.0",
17
+ "picocolors": "^1.1.1"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^20.10.0",
21
+ "typescript": "^5.3.0"
22
+ },
23
+ "engines": {
24
+ "node": ">=18.0.0"
25
+ }
26
+ }
@@ -0,0 +1,30 @@
1
+ import { createInterface } from "node:readline";
2
+ import pc from "picocolors";
3
+ import { loadConfig, saveConfig, getApiUrl } from "../lib/config.js";
4
+ import { api } from "../lib/api.js";
5
+
6
+ export async function authCommand(opts: { key?: string; url?: string }): Promise<void> {
7
+ let apiKey = opts.key;
8
+
9
+ if (!apiKey) {
10
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
11
+ apiKey = await new Promise<string>((resolve) => {
12
+ rl.question(pc.dim("Enter your Yoyo API key: "), (ans) => {
13
+ rl.close();
14
+ resolve(ans.trim());
15
+ });
16
+ });
17
+ }
18
+
19
+ if (!apiKey) {
20
+ console.error(pc.red("No API key provided."));
21
+ process.exit(1);
22
+ }
23
+
24
+ const cfg = await loadConfig();
25
+ cfg.apiKey = apiKey;
26
+ if (opts.url) cfg.apiUrl = opts.url;
27
+ await saveConfig(cfg);
28
+
29
+ console.log(pc.green("API key saved to ~/.yoyo/config.json"));
30
+ }
@@ -0,0 +1,147 @@
1
+ import { writeFile, mkdir } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { createHash } from "node:crypto";
5
+ import { unlink } from "node:fs/promises";
6
+ import pc from "picocolors";
7
+ import { api, ApiError } from "../lib/api.js";
8
+
9
+ interface BindOptions {
10
+ version?: string;
11
+ dir?: string;
12
+ json?: boolean;
13
+ dryRun?: boolean;
14
+ }
15
+
16
+ function defaultBindRoot(): string {
17
+ return join(homedir(), ".yoyo", "binders");
18
+ }
19
+
20
+ function parseRef(ref: string): { owner: string; slug: string } {
21
+ const parts = ref.split("/");
22
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
23
+ throw new Error(`Invalid binder ref "${ref}". Expected <owner>/<slug>`);
24
+ }
25
+ return { owner: parts[0], slug: parts[1] };
26
+ }
27
+
28
+ function formatBytes(n: number): string {
29
+ if (n < 1024) return `${n} B`;
30
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
31
+ return `${(n / (1024 * 1024)).toFixed(1)} MB`;
32
+ }
33
+
34
+ export async function bindCommand(ref: string, opts: BindOptions): Promise<void> {
35
+ let owner: string, slug: string;
36
+ try {
37
+ ({ owner, slug } = parseRef(ref));
38
+ } catch (err) {
39
+ console.error(pc.red(String(err)));
40
+ process.exit(1);
41
+ }
42
+
43
+ if (!opts.json) process.stdout.write(pc.dim(`Fetching ${ref}…\n`));
44
+
45
+ let binder;
46
+ try {
47
+ binder = await api.getBinder(owner, slug);
48
+ } catch (err) {
49
+ if (err instanceof ApiError && err.status === 404) {
50
+ console.error(pc.red(`Binder not found: ${ref}`));
51
+ } else {
52
+ console.error(pc.red(`API error: ${String(err)}`));
53
+ }
54
+ process.exit(1);
55
+ }
56
+
57
+ const spine = binder.latestVersion;
58
+ if (!spine) {
59
+ console.error(pc.red(`Binder ${ref} has no published versions.`));
60
+ process.exit(1);
61
+ }
62
+
63
+ const bindDir = join(opts.dir ? resolve(opts.dir) : defaultBindRoot(), owner, slug);
64
+
65
+ if (opts.json) {
66
+ console.log(JSON.stringify({ ref, bindDir, dryRun: opts.dryRun ?? false, binder }, null, 2));
67
+ return;
68
+ }
69
+
70
+ const badge =
71
+ binder.verification === "official"
72
+ ? pc.yellow(" ✦ Official")
73
+ : binder.verification === "verified"
74
+ ? pc.green(" ✓ Verified")
75
+ : "";
76
+
77
+ console.log(`\n${pc.bold(binder.displayName ?? ref)}${badge} ${pc.dim(`v${spine.version}`)}`);
78
+ if (binder.description) console.log(pc.dim(binder.description.slice(0, 80)));
79
+ console.log(pc.dim("─".repeat(50)));
80
+ console.log(` ${pc.dim("Bind to")} ${pc.dim(bindDir)}`);
81
+ console.log();
82
+
83
+ if (opts.dryRun) {
84
+ console.log(pc.yellow("Dry run — nothing written."));
85
+ return;
86
+ }
87
+
88
+ await mkdir(bindDir, { recursive: true });
89
+
90
+ // Write manifest
91
+ const manifest = {
92
+ name: slug,
93
+ owner,
94
+ fullName: `${owner}/${slug}`,
95
+ version: spine.version,
96
+ description: binder.description,
97
+ tab: binder.tab,
98
+ tags: binder.tags ?? [],
99
+ boundAt: new Date().toISOString(),
100
+ tarballUrl: spine.tarballUrl,
101
+ tarballSha256: spine.tarballSha256,
102
+ };
103
+ await writeFile(join(bindDir, "binder.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
104
+ console.log(` ${pc.green("✓")} binder.json`);
105
+
106
+ // Download tarball with SHA256 verification
107
+ if (spine.tarballUrl) {
108
+ const tarPath = join(bindDir, `${slug}-${spine.version}.tar.gz`);
109
+ process.stdout.write(` ${pc.dim("↓ tarball…")}`);
110
+ try {
111
+ const res = await fetch(spine.tarballUrl);
112
+ if (res.ok && res.body) {
113
+ const reader = res.body.getReader();
114
+ const chunks: Uint8Array[] = [];
115
+ let total = 0;
116
+ for (;;) {
117
+ const { done, value } = await reader.read();
118
+ if (done) break;
119
+ chunks.push(value);
120
+ total += value.byteLength;
121
+ }
122
+ const buf = Buffer.concat(chunks, total);
123
+
124
+ if (spine.tarballSha256) {
125
+ const actual = createHash("sha256").update(buf).digest("hex");
126
+ if (actual !== spine.tarballSha256) {
127
+ process.stdout.write(`\r ${pc.red("✗")} tarball SHA256 mismatch — aborting\n`);
128
+ console.error(pc.dim(` expected: ${spine.tarballSha256}\n got: ${actual}`));
129
+ await unlink(tarPath).catch(() => {});
130
+ process.exit(1);
131
+ }
132
+ }
133
+
134
+ await writeFile(tarPath, buf);
135
+ process.stdout.write(`\r ${pc.green("✓")} tarball (${pc.dim(formatBytes(total))})\n`);
136
+ } else {
137
+ process.stdout.write(`\r ${pc.dim("~ tarball skipped")}\n`);
138
+ }
139
+ } catch {
140
+ process.stdout.write(`\r ${pc.dim("~ tarball unavailable")}\n`);
141
+ }
142
+ }
143
+
144
+ console.log();
145
+ console.log(pc.green(`Bound ${ref}@${spine.version}`));
146
+ console.log(pc.dim(`Location: ${bindDir}`));
147
+ }
@@ -0,0 +1,69 @@
1
+ import { writeFile, mkdir } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { existsSync } from "node:fs";
4
+ import { createInterface } from "node:readline";
5
+ import pc from "picocolors";
6
+
7
+ async function prompt(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {
8
+ return new Promise((res) => rl.question(question, (a) => res(a.trim())));
9
+ }
10
+
11
+ export async function initCommand(opts: { dir?: string; yes?: boolean }): Promise<void> {
12
+ const cwd = opts.dir ? resolve(opts.dir) : process.cwd();
13
+ const manifestPath = join(cwd, "binder.json");
14
+
15
+ if (existsSync(manifestPath) && !opts.yes) {
16
+ console.error(pc.yellow("binder.json already exists. Use --yes to overwrite."));
17
+ process.exit(1);
18
+ }
19
+
20
+ let slug = "", displayName = "", description = "";
21
+
22
+ if (opts.yes) {
23
+ const dirName = cwd.split("/").pop() ?? "my-binder";
24
+ slug = dirName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
25
+ displayName = dirName;
26
+ } else {
27
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
28
+ slug = await prompt(rl, pc.dim("Binder slug (e.g. my-tools): "));
29
+ displayName = await prompt(rl, pc.dim("Display name: "));
30
+ description = await prompt(rl, pc.dim("Description: "));
31
+ rl.close();
32
+ }
33
+
34
+ if (!slug) {
35
+ console.error(pc.red("Slug is required."));
36
+ process.exit(1);
37
+ }
38
+
39
+ const manifest = {
40
+ slug,
41
+ displayName: displayName || slug,
42
+ description: description || "",
43
+ version: "0.1.0",
44
+ tab: "tools",
45
+ tags: [],
46
+ platforms: [],
47
+ license: "MIT",
48
+ dependencies: {},
49
+ };
50
+
51
+ await mkdir(cwd, { recursive: true });
52
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
53
+
54
+ // Scaffold a basic page.md if it doesn't exist
55
+ const pagePath = join(cwd, "page.md");
56
+ if (!existsSync(pagePath)) {
57
+ await writeFile(
58
+ pagePath,
59
+ `# ${manifest.displayName}\n\n${manifest.description || "Describe what this binder provides to AI agents."}\n`,
60
+ "utf8"
61
+ );
62
+ }
63
+
64
+ console.log(pc.green(`\nInitialised binder in ${cwd}`));
65
+ console.log(` ${pc.dim("✓")} binder.json`);
66
+ console.log(` ${pc.dim("✓")} page.md`);
67
+ console.log();
68
+ console.log(pc.dim("Next: yoyo publish"));
69
+ }