@easybits.cloud/cli 0.11.0 → 0.12.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 (3) hide show
  1. package/README.md +7 -0
  2. package/dist/index.js +149 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -119,6 +119,8 @@ easybits docs --open # open the docs in your browser
119
119
  pass its name instead of the id (`easybits sb exec scratch -- ls`, `easybits domains ls shop`).
120
120
  The match is exact and case-insensitive; if two share the name the CLI exits 2 and lists the ids.
121
121
 
122
+ Shell completion: `source <(easybits completion zsh)` (or `bash`, `fish`).
123
+
122
124
  Help and messages follow your locale (`LANG=es_*` → Spanish) or `--lang es|en`.
123
125
 
124
126
  Every command has help with examples: `easybits <command> <subcommand> --help`
@@ -161,6 +163,11 @@ Host *.ghosty
161
163
 
162
164
  Full reference: https://www.easybits.cloud/docs#cli · https://www.easybits.cloud/en/docs/cli.md
163
165
 
166
+ ## Changes in 0.12.0
167
+
168
+ - `easybits completion zsh|bash|fish`: TAB completes commands, subcommands, flags and the names of
169
+ your agents, sandboxes and databases (cached 5 min in `~/.cache/easybits/`).
170
+
164
171
  ## Changes in 0.11.0
165
172
 
166
173
  - **Spanish or English, following your locale** (`LANG=es_*` → Spanish; otherwise English), or
package/dist/index.js CHANGED
@@ -2957,6 +2957,130 @@ var agents = {
2957
2957
  }
2958
2958
  };
2959
2959
 
2960
+ // src/commands/completion.ts
2961
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
2962
+ import { homedir as homedir4 } from "os";
2963
+ import { dirname as dirname3, join as join6 } from "path";
2964
+ var GLOBAL_FLAGS = ["--json", "--token", "--lang", "--help", "--version"];
2965
+ var AGENT_FLAGS = /* @__PURE__ */ new Set(["--agent", "--like"]);
2966
+ var findSub = (c, name) => Object.entries(c.subs ?? {}).find(([n, l]) => n === name || l.aliases?.includes(name));
2967
+ function completionFor(words, current2, commands) {
2968
+ const names = commands.filter((c) => !c.name.startsWith("__")).map((c) => c.name);
2969
+ const prev = words[words.length - 1];
2970
+ if (prev && AGENT_FLAGS.has(prev)) return { kind: "names", ref: "agent" };
2971
+ if (!words.length) return { kind: "words", words: [...names, "help"] };
2972
+ const find = (n2) => commands.find((c) => c.name === n2 || c.aliases?.includes(n2));
2973
+ const cmd = find(words[0]);
2974
+ if (words[0] === "help") return { kind: "words", words: words.length === 1 ? names : Object.keys(find(words[1])?.subs ?? {}) };
2975
+ if (!cmd) return { kind: "words", words: [] };
2976
+ const sub = cmd.subs && words[1] ? findSub(cmd, words[1]) : void 0;
2977
+ const leaf = sub?.[1] ?? cmd.leaf;
2978
+ if (current2.startsWith("-")) {
2979
+ const own = Object.keys(leaf?.options ?? {}).map((n2) => `--${n2}`);
2980
+ return { kind: "words", words: [...own, ...GLOBAL_FLAGS] };
2981
+ }
2982
+ if (cmd.subs && !sub) return { kind: "words", words: words.length === 1 ? Object.keys(cmd.subs) : [] };
2983
+ if (cmd.name === "completion") return { kind: "words", words: words.length === 1 ? ["zsh", "bash", "fish"] : [] };
2984
+ const opts = leaf?.options ?? {};
2985
+ let n = 0;
2986
+ for (let i = sub ? 2 : 1; i < words.length; i++) {
2987
+ const w = words[i];
2988
+ if (w.startsWith("--")) {
2989
+ const o = opts[w.slice(2)];
2990
+ if ((o?.type === "string" || w === "--token" || w === "--lang") && !w.includes("=")) i++;
2991
+ continue;
2992
+ }
2993
+ if (w.startsWith("-") && w !== "-") continue;
2994
+ n++;
2995
+ }
2996
+ const spec = REF_ARGS[sub ? `${cmd.name} ${sub[0]}` : cmd.name];
2997
+ if (spec && spec.index === n) return { kind: "names", ref: spec.kind };
2998
+ return { kind: "words", words: [] };
2999
+ }
3000
+ var cacheFile = () => join6(process.env.XDG_CACHE_HOME || join6(homedir4(), ".cache"), "easybits", "complete.json");
3001
+ async function namesOf(ctx, kind) {
3002
+ let cache2 = {};
3003
+ try {
3004
+ cache2 = JSON.parse(readFileSync10(cacheFile(), "utf8"));
3005
+ const c = cache2[kind];
3006
+ if (c && Date.now() - c.at < 5 * 6e4) return c.names;
3007
+ } catch {
3008
+ }
3009
+ const names = [...new Set((await listRefs(ctx, kind)).map((x) => x.name).filter((x) => !!x))];
3010
+ try {
3011
+ mkdirSync5(dirname3(cacheFile()), { recursive: true });
3012
+ writeFileSync6(cacheFile(), JSON.stringify({ ...cache2, [kind]: { at: Date.now(), names } }));
3013
+ } catch {
3014
+ }
3015
+ return names;
3016
+ }
3017
+ async function completeCmd(args, commands) {
3018
+ const rest = args[0] === "--" ? args.slice(1) : args;
3019
+ const current2 = rest.length ? rest[rest.length - 1] : "";
3020
+ const words = rest.slice(0, -1);
3021
+ let out = [];
3022
+ try {
3023
+ const want = completionFor(words, current2, commands);
3024
+ const ctx = { json: true, args: [], opts: {} };
3025
+ out = want.kind === "words" ? want.words : await namesOf(ctx, want.ref);
3026
+ } catch {
3027
+ out = [];
3028
+ }
3029
+ const cur = current2.toLowerCase();
3030
+ process.stdout.write(out.filter((w) => w.toLowerCase().startsWith(cur)).join("\n") + "\n");
3031
+ }
3032
+ function completionScript(shell) {
3033
+ if (shell === "bash") {
3034
+ return `# easybits \u2014 bash completion: eval "$(easybits completion bash)" en ~/.bashrc
3035
+ _easybits() {
3036
+ # Las palabras ANTES de cambiar IFS: el bash 3.2 de macOS junta \${arr[@]:1:n} con un IFS propio.
3037
+ local words=("\${COMP_WORDS[@]:1:COMP_CWORD-1}") cur="\${COMP_WORDS[COMP_CWORD]}"
3038
+ local IFS=$'\\n'
3039
+ COMPREPLY=( $(easybits __complete -- "\${words[@]}" "$cur" 2>/dev/null) )
3040
+ }
3041
+ complete -o default -F _easybits easybits
3042
+ `;
3043
+ }
3044
+ if (shell === "zsh") {
3045
+ return `#compdef easybits
3046
+ # easybits \u2014 zsh completion: source <(easybits completion zsh) en ~/.zshrc (despu\xE9s de compinit)
3047
+ _easybits() {
3048
+ local -a opts
3049
+ opts=("\${(@f)$(easybits __complete -- "\${(@)words[2,CURRENT-1]}" "\${words[CURRENT]}" 2>/dev/null)}")
3050
+ compadd -a opts
3051
+ }
3052
+ compdef _easybits easybits
3053
+ `;
3054
+ }
3055
+ if (shell === "fish") {
3056
+ return `# easybits \u2014 fish completion: easybits completion fish > ~/.config/fish/completions/easybits.fish
3057
+ complete -c easybits -f -a "(easybits __complete -- (commandline -opc)[2..-1] (commandline -ct) 2>/dev/null)"
3058
+ `;
3059
+ }
3060
+ return null;
3061
+ }
3062
+ var completion = {
3063
+ name: "completion",
3064
+ group: "Help",
3065
+ summary: "Shell completion script (zsh, bash, fish)",
3066
+ synopsis: "completion <shell>",
3067
+ leaf: {
3068
+ summary: "Print the completion script for your shell: commands, flags and your agents, sandboxes and databases",
3069
+ usage: "easybits completion <zsh|bash|fish>",
3070
+ examples: [
3071
+ "source <(easybits completion zsh) # add to ~/.zshrc, after compinit",
3072
+ 'eval "$(easybits completion bash)" # add to ~/.bashrc',
3073
+ "easybits completion fish > ~/.config/fish/completions/easybits.fish"
3074
+ ],
3075
+ async run(ctx) {
3076
+ const shell = need(ctx, 0, "zsh|bash|fish", this.usage);
3077
+ const script = completionScript(shell);
3078
+ if (!script) throw usageError(t(`Unknown shell "${shell}".`, `Shell desconocido "${shell}".`), this.usage);
3079
+ process.stdout.write(script);
3080
+ }
3081
+ }
3082
+ };
3083
+
2960
3084
  // src/commands/docs.ts
2961
3085
  import { spawn as spawn2 } from "child_process";
2962
3086
  import { resolveBaseUrl as resolveBaseUrl5 } from "@easybits.cloud/sdk";
@@ -3005,15 +3129,15 @@ var docs = {
3005
3129
 
3006
3130
  // src/commands/doctor.ts
3007
3131
  import { existsSync as existsSync10, statSync as statSync5 } from "fs";
3008
- import { homedir as homedir5 } from "os";
3009
- import { join as join7 } from "path";
3132
+ import { homedir as homedir6 } from "os";
3133
+ import { join as join8 } from "path";
3010
3134
  import { resolveBaseUrl as resolveBaseUrl6 } from "@easybits.cloud/sdk";
3011
3135
 
3012
3136
  // src/update.ts
3013
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
3014
- import { homedir as homedir4 } from "os";
3015
- import { dirname as dirname3, join as join6 } from "path";
3016
- var FILE = join6(process.env.XDG_CONFIG_HOME || join6(homedir4(), ".config"), "easybits", "update.json");
3137
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
3138
+ import { homedir as homedir5 } from "os";
3139
+ import { dirname as dirname4, join as join7 } from "path";
3140
+ var FILE = join7(process.env.XDG_CONFIG_HOME || join7(homedir5(), ".config"), "easybits", "update.json");
3017
3141
  var DAY = 24 * 60 * 60 * 1e3;
3018
3142
  var UPGRADE = "npm i -g @easybits.cloud/cli@latest";
3019
3143
  function newer(a, b) {
@@ -3025,7 +3149,7 @@ function newer(a, b) {
3025
3149
  var updateCheckDisabled = () => Boolean(process.env.EASYBITS_NO_UPDATE_CHECK || process.env.CI);
3026
3150
  function readCache() {
3027
3151
  try {
3028
- return JSON.parse(readFileSync10(FILE, "utf8"));
3152
+ return JSON.parse(readFileSync11(FILE, "utf8"));
3029
3153
  } catch {
3030
3154
  return {};
3031
3155
  }
@@ -3035,8 +3159,8 @@ async function fetchLatest(timeoutMs = 1500) {
3035
3159
  const r = await fetch("https://registry.npmjs.org/@easybits.cloud/cli/latest", { signal: AbortSignal.timeout(timeoutMs) });
3036
3160
  const latest = (await r.json()).version;
3037
3161
  if (!latest) return null;
3038
- mkdirSync5(dirname3(FILE), { recursive: true });
3039
- writeFileSync6(FILE, JSON.stringify({ latest, checkedAt: Date.now() }));
3162
+ mkdirSync6(dirname4(FILE), { recursive: true });
3163
+ writeFileSync7(FILE, JSON.stringify({ latest, checkedAt: Date.now() }));
3040
3164
  return latest;
3041
3165
  } catch {
3042
3166
  return null;
@@ -3060,7 +3184,7 @@ async function updateNotice(current2) {
3060
3184
  }
3061
3185
 
3062
3186
  // src/commands/doctor.ts
3063
- var VERSION = true ? "0.11.0" : "dev";
3187
+ var VERSION = true ? "0.12.0" : "dev";
3064
3188
  var doctor2 = {
3065
3189
  name: "doctor",
3066
3190
  group: "Account",
@@ -3103,7 +3227,7 @@ var doctor2 = {
3103
3227
  checks.push({ check: "credential-env", ok: true, detail: t("EASYBITS_API_KEY wins over the saved login in ~/.easybitsrc", "EASYBITS_API_KEY le gana a la sesi\xF3n guardada en ~/.easybitsrc") });
3104
3228
  }
3105
3229
  }
3106
- const rcPath = join7(homedir5(), ".easybitsrc");
3230
+ const rcPath = join8(homedir6(), ".easybitsrc");
3107
3231
  if (existsSync10(rcPath)) {
3108
3232
  const mode = statSync5(rcPath).mode & 511;
3109
3233
  const ok2 = (mode & 63) === 0;
@@ -3142,7 +3266,8 @@ var COMMANDS = [
3142
3266
  mcp,
3143
3267
  sshKey,
3144
3268
  sshProxy,
3145
- docs
3269
+ docs,
3270
+ completion
3146
3271
  ];
3147
3272
  function findCommand(name) {
3148
3273
  return COMMANDS.find((c) => c.name === name || c.aliases?.includes(name));
@@ -3337,7 +3462,11 @@ var HELP_ES = {
3337
3462
  "Help": "Ayuda",
3338
3463
  "Print the docs URL, or a docs section as markdown": "Imprime la URL de los docs, o una secci\xF3n de los docs en markdown",
3339
3464
  "English docs": "Docs en ingl\xE9s",
3340
- "Open the docs in your browser": "Abre los docs en tu navegador"
3465
+ "Open the docs in your browser": "Abre los docs en tu navegador",
3466
+ "Shell completion script (zsh, bash, fish)": "Script de autocompletado para tu shell (zsh, bash, fish)",
3467
+ "Print the completion script for your shell: commands, flags and your agents, sandboxes and databases": "Imprime el script de autocompletado de tu shell: comandos, flags y tus agentes, sandboxes y bases",
3468
+ "add to ~/.zshrc, after compinit": "agr\xE9galo a ~/.zshrc, despu\xE9s de compinit",
3469
+ "add to ~/.bashrc": "agr\xE9galo a ~/.bashrc"
3341
3470
  };
3342
3471
 
3343
3472
  // src/help.ts
@@ -3528,7 +3657,7 @@ function normalizeArgs(input) {
3528
3657
  }
3529
3658
 
3530
3659
  // src/index.ts
3531
- var VERSION2 = true ? "0.11.0" : "dev";
3660
+ var VERSION2 = true ? "0.12.0" : "dev";
3532
3661
  var GLOBAL_OPTIONS = {
3533
3662
  json: { type: "boolean" },
3534
3663
  token: { type: "string" },
@@ -3572,7 +3701,7 @@ function parse(argv2, skip, leaf) {
3572
3701
  throw usageError(e.message.split("\n")[0].split(". To specify")[0].replace(/\.?$/, "."), leaf?.usage);
3573
3702
  }
3574
3703
  }
3575
- function findSub(cmd, name) {
3704
+ function findSub2(cmd, name) {
3576
3705
  return Object.entries(cmd.subs ?? {}).find(([n, l]) => n === name || l.aliases?.includes(name));
3577
3706
  }
3578
3707
  function unknownCommand(name) {
@@ -3586,7 +3715,7 @@ ${t("Run", "Corre")}: easybits --help`;
3586
3715
  function unknownSub(cmd, name) {
3587
3716
  const e = usageError(t(`Unknown subcommand "${cmd.name} ${name}".`, `Subcomando desconocido "${cmd.name} ${name}".`), `easybits ${cmd.name} <${Object.keys(cmd.subs ?? {}).join("|")}>`);
3588
3717
  const names = Object.entries(cmd.subs ?? {}).flatMap(([n, l]) => [n, ...l.aliases ?? []]);
3589
- const hint = didYouMean(name, names, `easybits ${cmd.name} `, (n) => findSub(cmd, n)?.[0] ?? n);
3718
+ const hint = didYouMean(name, names, `easybits ${cmd.name} `, (n) => findSub2(cmd, n)?.[0] ?? n);
3590
3719
  if (hint) e.hint = `${localHint(hint)}
3591
3720
  ${e.hint}`;
3592
3721
  return e;
@@ -3600,6 +3729,7 @@ function aliasNote([en, es]) {
3600
3729
  }
3601
3730
  var localHint = (hint) => hint.replace("Did you mean this?", t("Did you mean this?", "\xBFQuisiste decir esto?"));
3602
3731
  async function main(input) {
3732
+ if (input[0] === "__complete") return completeCmd(input.slice(1), COMMANDS);
3603
3733
  const { argv: argv2, notes } = normalizeArgs(input);
3604
3734
  if (!wantsJson(argv2)) notes.forEach(aliasNote);
3605
3735
  const pos = positionalIndexes(argv2);
@@ -3609,7 +3739,7 @@ async function main(input) {
3609
3739
  const topic = topicName ? findCommand(topicName) : void 0;
3610
3740
  if (topicName && !topic) throw unknownCommand(topicName);
3611
3741
  const subName = topic?.subs && pos[2] != null ? argv2[pos[2]] : void 0;
3612
- const sub = topic && subName ? findSub(topic, subName) : void 0;
3742
+ const sub = topic && subName ? findSub2(topic, subName) : void 0;
3613
3743
  if (topic && subName && !sub) throw unknownSub(topic, subName);
3614
3744
  const { values: values2 } = parse(argv2, pos.slice(0, sub ? 3 : 2), void 0);
3615
3745
  if (values2.version) {
@@ -3635,7 +3765,7 @@ async function main(input) {
3635
3765
  const skip = [pos[0]];
3636
3766
  if (cmd.subs) {
3637
3767
  const subName = pos[1] != null ? argv2[pos[1]] : void 0;
3638
- const entry = subName ? findSub(cmd, subName) : void 0;
3768
+ const entry = subName ? findSub2(cmd, subName) : void 0;
3639
3769
  if (entry) {
3640
3770
  leaf = entry[1];
3641
3771
  subKey = entry[0];
@@ -3682,7 +3812,7 @@ async function main(input) {
3682
3812
  }
3683
3813
  var argv = process.argv.slice(2);
3684
3814
  setLang(detectLang(langFlag(argv)));
3685
- var notice = process.stderr.isTTY && !wantsJson(argv) && !argv.includes("doctor") ? updateNotice(VERSION2) : null;
3815
+ var notice = process.stderr.isTTY && !wantsJson(argv) && !argv.includes("doctor") && argv[0] !== "__complete" ? updateNotice(VERSION2) : null;
3686
3816
  main(argv).catch((err) => {
3687
3817
  const e = toCliError(err);
3688
3818
  const latest = e.code === "usage" ? cachedNewer(VERSION2) : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easybits.cloud/cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Official CLI for EasyBits, the cloud for AI agents — sandboxes, hosting, domains, databases, agents and files from your terminal",
5
5
  "type": "module",
6
6
  "bin": {