postemi 1.0.0 → 1.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.
package/dist/index.js CHANGED
@@ -60,32 +60,12 @@ var import_helpers = require("yargs/helpers");
60
60
  var import_crypto = require("crypto");
61
61
 
62
62
  // src/brand.ts
63
- var BRAND_ID = true ? "postemi" : "posty";
64
- var IS_POSTEMI = BRAND_ID === "postemi";
65
- var POSTY = {
66
- name: "Posty",
67
- cli: "posty",
68
- envPrefix: "POSTY",
69
- configDirName: ".posty",
70
- defaultApiUrl: "https://posty.hu/api",
71
- defaultAuthServer: "https://posty.hu/api",
72
- clientName: "Posty CLI",
73
- homepage: "https://posty.hu",
74
- termsUrl: "https://posty.hu/aszf",
75
- privacyUrl: "https://posty.hu/adatvedelem",
76
- signupUrl: "https://posty.hu/regisztracio",
77
- exampleTimezone: "Europe/Budapest",
78
- exampleTimezoneCity: "Budapest",
79
- tierList: "alap, pro, kreator or csapat",
80
- basicPlanName: "Alap"
81
- };
82
- var POSTEMI = {
63
+ var BRAND = {
83
64
  name: "Postemi",
84
65
  cli: "postemi",
85
66
  envPrefix: "POSTEMI",
86
67
  configDirName: ".postemi",
87
- // api.postemi.com serves the API at the root (its proxy adds /api), the
88
- // same way api.posty.hu does.
68
+ // api.postemi.com serves the API at the root (its proxy adds /api).
89
69
  defaultApiUrl: "https://api.postemi.com",
90
70
  defaultAuthServer: "https://api.postemi.com",
91
71
  clientName: "Postemi CLI",
@@ -98,30 +78,20 @@ var POSTEMI = {
98
78
  tierList: "basic, pro, creator or team",
99
79
  basicPlanName: "Basic"
100
80
  };
101
- var BRAND = IS_POSTEMI ? POSTEMI : POSTY;
102
81
  var envName = (name) => `${BRAND.envPrefix}_${name}`;
103
82
  function readEnv(name) {
104
- const own = process.env[envName(name)];
105
- if (own || !IS_POSTEMI) return own;
106
- return process.env[`POSTY_${name}`];
107
- }
108
- function envSourceName(name) {
109
- if (IS_POSTEMI && !process.env[envName(name)] && process.env[`POSTY_${name}`]) {
110
- return `POSTY_${name}`;
111
- }
112
- return envName(name);
83
+ return process.env[envName(name)];
113
84
  }
114
85
  function writeEnv(name, value) {
115
86
  process.env[envName(name)] = value;
116
87
  }
117
- var POSTEMI_TIER_ALIASES = {
88
+ var TIER_ALIASES = {
118
89
  basic: "alap",
119
90
  team: "csapat"
120
91
  };
121
92
  function tierForApi(tier) {
122
93
  var _a;
123
- if (!IS_POSTEMI) return tier;
124
- return (_a = POSTEMI_TIER_ALIASES[tier.trim().toLowerCase()]) != null ? _a : tier;
94
+ return (_a = TIER_ALIASES[tier.trim().toLowerCase()]) != null ? _a : tier;
125
95
  }
126
96
  var NETWORK_REASONS = {
127
97
  ENOTFOUND: "the host name does not resolve",
@@ -136,7 +106,6 @@ var NETWORK_REASONS = {
136
106
  function networkError(prefix, error, variable = "API_URL") {
137
107
  var _a, _b, _c;
138
108
  const plain = `${prefix}: ${error == null ? void 0 : error.message}`;
139
- if (!IS_POSTEMI) return plain;
140
109
  const code = (_b = (_a = error == null ? void 0 : error.cause) == null ? void 0 : _a.code) != null ? _b : error == null ? void 0 : error.code;
141
110
  const reason = code ? (_c = NETWORK_REASONS[code]) != null ? _c : code : null;
142
111
  return reason ? `${prefix}: ${reason} (${code}). Check the connection, or point ${envName(variable)} at another server.` : plain;
@@ -227,7 +196,7 @@ var ApiError = class extends Error {
227
196
  return this.status === 403 && this.body.includes("spans several workspaces");
228
197
  }
229
198
  };
230
- var PostyAPI = class {
199
+ var PostemiAPI = class {
231
200
  constructor(config) {
232
201
  this.apiKey = config.apiKey;
233
202
  this.workspaceId = config.workspaceId;
@@ -449,52 +418,13 @@ var PostyAPI = class {
449
418
  method: "GET"
450
419
  });
451
420
  }
452
- /*
453
- FOTOAI (fotoai.hu). Generation with the key owner's OWN FotoAI account and
454
- credits; the outputs are saved into this workspace's media library. A key
455
- whose owner has not connected FotoAI gets 409 FOTOAI_NOT_CONNECTED with
456
- `details.connect_url`, the page the person opens once. Needs media:write.
457
- */
458
- async fotoAiStatus() {
459
- return this.request("/public/v1/fotoai/status", { method: "GET" });
460
- }
461
- async fotoAiModels(category) {
462
- const query = category ? `?category=${encodeURIComponent(category)}` : "";
463
- return this.request(`/public/v1/fotoai/models${query}`, { method: "GET" });
464
- }
465
- /** Spends nothing: the price, the balance and a quote_id valid 10 minutes. */
466
- async fotoAiQuote(body) {
467
- return this.request("/public/v1/fotoai/quote", {
468
- method: "POST",
469
- body: JSON.stringify(body)
470
- });
471
- }
472
- /** SPENDS the key owner's FotoAI credits, at most the quoted price. */
473
- async fotoAiGenerate(body) {
474
- return this.request("/public/v1/fotoai/generations", {
475
- method: "POST",
476
- body: JSON.stringify(body)
477
- });
478
- }
479
- async fotoAiGeneration(id) {
480
- return this.request(`/public/v1/fotoai/generations/${encodeURIComponent(id)}`, {
481
- method: "GET"
482
- });
483
- }
484
- /** Imports any FotoAI generation of the key owner into the media library; repeatable. */
485
- async fotoAiImport(id) {
486
- return this.request(
487
- `/public/v1/fotoai/generations/${encodeURIComponent(id)}/import`,
488
- { method: "POST" }
489
- );
490
- }
491
421
  };
492
422
 
493
423
  // src/commands/auth.ts
494
- var import_fs2 = require("fs");
495
- var import_path2 = require("path");
496
- var import_os2 = require("os");
497
- var import_readline = require("readline");
424
+ var import_fs3 = require("fs");
425
+ var import_path4 = require("path");
426
+ var import_os3 = require("os");
427
+ var import_readline2 = require("readline");
498
428
 
499
429
  // src/settings.ts
500
430
  var import_fs = require("fs");
@@ -520,6 +450,9 @@ function getSetting(key) {
520
450
  return typeof value === "string" && value ? value : void 0;
521
451
  }
522
452
  function saveSetting(key, value) {
453
+ saveRawSetting(key, value);
454
+ }
455
+ function saveRawSetting(key, value) {
523
456
  if (!(0, import_fs.existsSync)(SETTINGS_DIR)) {
524
457
  (0, import_fs.mkdirSync)(SETTINGS_DIR, { recursive: true, mode: 448 });
525
458
  }
@@ -600,14 +533,14 @@ function resolveTimezone(flagValue) {
600
533
  if (env) {
601
534
  return {
602
535
  name: assertIanaTimezone(env, envName("TIMEZONE")),
603
- source: envName("TIMEZONE")
536
+ source: "POSTEMI_TIMEZONE"
604
537
  };
605
538
  }
606
539
  const stored = getSetting("timezone");
607
540
  if (stored) {
608
541
  return {
609
542
  name: assertIanaTimezone(stored, `~/${BRAND.configDirName}/config.json`),
610
- source: `config (${BRAND.cli} config:set timezone)`
543
+ source: "config (postemi config:set timezone)"
611
544
  };
612
545
  }
613
546
  return null;
@@ -692,15 +625,345 @@ function resolveDateInput(input, timezone, flagName = "--date") {
692
625
  return { iso: instant.toISOString(), appliedTimezone: timezone };
693
626
  }
694
627
 
628
+ // src/commands/skills.ts
629
+ var import_os2 = require("os");
630
+ var import_path3 = require("path");
631
+ var import_readline = require("readline");
632
+
633
+ // src/skills.ts
634
+ var import_fs2 = require("fs");
635
+ var import_path2 = require("path");
636
+ var OPTIONAL_SETS = ["writing", "growth"];
637
+ var SKILL_SETS = {
638
+ core: ["postemi"],
639
+ writing: ["postemi-writing", "postemi-templates"],
640
+ growth: ["postemi-growth"]
641
+ };
642
+ var SET_SUMMARY = {
643
+ core: "the postemi commands and their rules (always on)",
644
+ writing: "write posts: ideas, hooks, formatting, network rules, templates",
645
+ growth: "grow an audience: niche, cadence, 30-day plan, weekly review"
646
+ };
647
+ var AGENT_IDS = ["claude", "codex", "cursor", "grok"];
648
+ function knownAgents(home, env = {}) {
649
+ const claudeHome = env.CLAUDE_CONFIG_DIR || (0, import_path2.join)(home, ".claude");
650
+ const codexHome = env.CODEX_HOME || (0, import_path2.join)(home, ".codex");
651
+ const cursorHome = (0, import_path2.join)(home, ".cursor");
652
+ const grokHome = (0, import_path2.join)(home, ".grok");
653
+ return [
654
+ { id: "claude", name: "Claude Code", home: claudeHome, skillsDir: (0, import_path2.join)(claudeHome, "skills") },
655
+ { id: "codex", name: "Codex", home: codexHome, skillsDir: (0, import_path2.join)(codexHome, "skills") },
656
+ { id: "cursor", name: "Cursor", home: cursorHome, skillsDir: (0, import_path2.join)(cursorHome, "skills") },
657
+ { id: "grok", name: "Grok Build", home: grokHome, skillsDir: (0, import_path2.join)(grokHome, "skills") }
658
+ ];
659
+ }
660
+ function detectAgents(home, env = {}) {
661
+ return knownAgents(home, env).filter((agent) => isDir(agent.home));
662
+ }
663
+ function parseSetNames(names) {
664
+ const out = /* @__PURE__ */ new Set();
665
+ for (const raw of names) {
666
+ const name = raw.trim().toLowerCase().replace(/^postemi-?/, "") || "core";
667
+ if (name === "all") OPTIONAL_SETS.forEach((s) => out.add(s));
668
+ else if (name === "templates") out.add("writing");
669
+ else if (name === "core" || name === "writing" || name === "growth") out.add(name);
670
+ else throw new Error(`Unknown skill "${raw}". Use writing, growth, all or core.`);
671
+ }
672
+ return [...out];
673
+ }
674
+ function isDir(path) {
675
+ try {
676
+ return (0, import_fs2.statSync)(path).isDirectory();
677
+ } catch (e) {
678
+ return false;
679
+ }
680
+ }
681
+ function listFiles(dir, prefix = "") {
682
+ const out = [];
683
+ for (const name of (0, import_fs2.readdirSync)(dir).sort()) {
684
+ const path = (0, import_path2.join)(dir, name);
685
+ if (isDir(path)) out.push(...listFiles(path, (0, import_path2.join)(prefix, name)));
686
+ else out.push((0, import_path2.join)(prefix, name));
687
+ }
688
+ return out;
689
+ }
690
+ function sameTree(a, b) {
691
+ const fa = listFiles(a);
692
+ const fb = listFiles(b);
693
+ return fa.length === fb.length && fa.every((f, i) => fb[i] === f && (0, import_fs2.readFileSync)((0, import_path2.join)(a, f)).equals((0, import_fs2.readFileSync)((0, import_path2.join)(b, f))));
694
+ }
695
+ function skillName(dir) {
696
+ try {
697
+ const text = (0, import_fs2.readFileSync)((0, import_path2.join)(dir, "SKILL.md"), "utf8");
698
+ const match = /^---\r?\n[\s\S]*?^name:\s*["']?([\w-]+)["']?\s*$/m.exec(text);
699
+ return match ? match[1] : null;
700
+ } catch (e) {
701
+ return null;
702
+ }
703
+ }
704
+ function folderState(source, folder, agent) {
705
+ const target = (0, import_path2.join)(agent.skillsDir, folder);
706
+ if (skillName(target) !== folder) return "missing";
707
+ return sameTree((0, import_path2.join)(source, folder), target) ? "installed" : "outdated";
708
+ }
709
+ function setState(source, set, agent) {
710
+ const states = SKILL_SETS[set].map((f) => folderState(source, f, agent));
711
+ if (states.includes("missing")) return "missing";
712
+ if (states.includes("outdated")) return "outdated";
713
+ return "installed";
714
+ }
715
+ function installSets(source, sets, agent) {
716
+ const folders = foldersFor(sets.includes("core") ? sets : ["core", ...sets]);
717
+ for (const folder of folders) {
718
+ if (skillName((0, import_path2.join)(source, folder)) !== folder) {
719
+ throw new Error(`The package has no ${folder} skill at ${(0, import_path2.join)(source, folder)}`);
720
+ }
721
+ const target = (0, import_path2.join)(agent.skillsDir, folder);
722
+ if ((0, import_fs2.existsSync)(target) && skillName(target) !== folder) {
723
+ throw new Error(`${target} exists and is not the ${folder} skill; move it away first`);
724
+ }
725
+ }
726
+ (0, import_fs2.mkdirSync)(agent.skillsDir, { recursive: true });
727
+ for (const folder of folders) {
728
+ const target = (0, import_path2.join)(agent.skillsDir, folder);
729
+ (0, import_fs2.rmSync)(target, { recursive: true, force: true });
730
+ (0, import_fs2.cpSync)((0, import_path2.join)(source, folder), target, { recursive: true });
731
+ }
732
+ return folders;
733
+ }
734
+ function removeSets(sets, agent) {
735
+ const removed = [];
736
+ for (const folder of foldersFor(sets)) {
737
+ const target = (0, import_path2.join)(agent.skillsDir, folder);
738
+ if (skillName(target) !== folder) continue;
739
+ (0, import_fs2.rmSync)(target, { recursive: true, force: true });
740
+ removed.push(folder);
741
+ }
742
+ return removed;
743
+ }
744
+ function foldersFor(sets) {
745
+ return [...new Set(sets.flatMap((s) => SKILL_SETS[s]))];
746
+ }
747
+ function terminalFrom(argv, env = process.env) {
748
+ return {
749
+ stdinTTY: !!process.stdin.isTTY,
750
+ stderrTTY: !!process.stderr.isTTY,
751
+ json: !!argv.json,
752
+ ci: !!env.CI && env.CI !== "false" && env.CI !== "0"
753
+ };
754
+ }
755
+ function canAsk(t) {
756
+ return t.stdinTTY && t.stderrTTY && !t.json && !t.ci;
757
+ }
758
+ var ANSWER_KEY = {
759
+ writing: "skills.writing",
760
+ growth: "skills.growth"
761
+ };
762
+ var HINT_KEY = "skills.hint";
763
+ var QUESTIONS = {
764
+ writing: "Postemi can also help you write posts. Install the optional writing skill (ideas, hooks, templates)? [y/N] ",
765
+ growth: "Install the optional growth skill (niche, cadence, 30-day plan)? [y/N] "
766
+ };
767
+ var HINT = "Tip: optional agent skills for writing posts and growing an audience: postemi skills list, postemi skills add writing|growth";
768
+ function isYes(answer) {
769
+ return /^(y|yes)$/i.test(answer.trim());
770
+ }
771
+ function alreadyOffered(store) {
772
+ return OPTIONAL_SETS.every((s) => !!store.get(ANSWER_KEY[s]));
773
+ }
774
+ async function offerOptionalSkills(deps, force = false) {
775
+ const { terminal, store, ask: ask3, log } = deps;
776
+ if (!force && alreadyOffered(store)) return [];
777
+ if (!canAsk(terminal)) {
778
+ if (!store.get(HINT_KEY)) {
779
+ log(HINT);
780
+ store.set(HINT_KEY, "shown");
781
+ }
782
+ return [];
783
+ }
784
+ const chosen = [];
785
+ for (const set of OPTIONAL_SETS) {
786
+ const yes = isYes(await ask3(QUESTIONS[set]));
787
+ store.set(ANSWER_KEY[set], yes ? "yes" : "no");
788
+ if (yes) chosen.push(set);
789
+ }
790
+ if (!chosen.length) log("No optional skills. Add them any time with: postemi skills add");
791
+ return chosen;
792
+ }
793
+ async function chooseAgents(opts) {
794
+ const { found, all, requested, yes, terminal, ask: ask3, log } = opts;
795
+ if (requested && requested.length) {
796
+ const ids = requested.flatMap((r) => String(r).split(",")).map((r) => r.trim().toLowerCase()).filter(Boolean);
797
+ if (ids.includes("all")) return found;
798
+ return ids.map((id) => {
799
+ const agent = all.find((a) => a.id === id);
800
+ if (!agent) throw new Error(`Unknown agent "${id}". Use ${AGENT_IDS.join(", ")} or all.`);
801
+ return agent;
802
+ });
803
+ }
804
+ if (found.length <= 1 || yes || !canAsk(terminal)) return found;
805
+ log("Agents found on this machine:");
806
+ found.forEach((a, i) => log(` ${i + 1}. ${a.name} (${a.skillsDir})`));
807
+ const answer = (await ask3(`Install for which? [all, or numbers like 1,3] `)).trim().toLowerCase();
808
+ if (!answer || answer === "all" || answer === "a") return found;
809
+ const picked = answer.split(/[\s,]+/).map((n) => found[Number(n) - 1]).filter((a) => !!a);
810
+ return [...new Set(picked)];
811
+ }
812
+
813
+ // src/commands/skills.ts
814
+ function bundledSkillsDir() {
815
+ return (0, import_path3.join)(__dirname, "..", "skills");
816
+ }
817
+ var settingsStore = {
818
+ get: (key) => {
819
+ const value = loadSettings()[key];
820
+ return typeof value === "string" && value ? value : void 0;
821
+ },
822
+ set: (key, value) => saveRawSetting(key, value)
823
+ };
824
+ function ask(question) {
825
+ const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stderr });
826
+ return new Promise(
827
+ (resolve) => rl.question(question, (answer) => {
828
+ rl.close();
829
+ resolve(answer);
830
+ })
831
+ );
832
+ }
833
+ function die(message) {
834
+ console.error(`\u274C ${message}`);
835
+ process.exit(1);
836
+ }
837
+ async function skillsList() {
838
+ const source = bundledSkillsDir();
839
+ const settings = loadSettings();
840
+ const agents = knownAgents((0, import_os2.homedir)(), process.env);
841
+ const found = new Set(detectAgents((0, import_os2.homedir)(), process.env).map((a) => a.id));
842
+ const sets = ["core", ...OPTIONAL_SETS];
843
+ result({
844
+ agents: agents.map((a) => ({ id: a.id, name: a.name, found: found.has(a.id), skillsDir: a.skillsDir })),
845
+ skills: sets.map((set) => {
846
+ var _a;
847
+ return {
848
+ name: set,
849
+ optional: set !== "core",
850
+ folders: SKILL_SETS[set],
851
+ about: SET_SUMMARY[set],
852
+ answer: set === "core" ? null : (_a = settings[ANSWER_KEY[set]]) != null ? _a : null,
853
+ installed: Object.fromEntries(
854
+ agents.filter((a) => found.has(a.id)).map((a) => [a.id, setState(source, set, a)])
855
+ )
856
+ };
857
+ })
858
+ });
859
+ }
860
+ async function install(sets, argv, terminal) {
861
+ const home = (0, import_os2.homedir)();
862
+ const agents = await chooseAgents({
863
+ found: detectAgents(home, process.env),
864
+ all: knownAgents(home, process.env),
865
+ requested: argv.agent,
866
+ yes: !!argv.yes,
867
+ terminal,
868
+ ask,
869
+ log: status
870
+ });
871
+ if (!agents.length) {
872
+ throw new Error(
873
+ `No agent skill folder found (~/.claude, ~/.codex, ~/.cursor, ~/.grok). Pass --agent claude (or codex, cursor, grok) to create one.`
874
+ );
875
+ }
876
+ const installed = {};
877
+ for (const agent of agents) {
878
+ try {
879
+ installed[agent.id] = installSets(bundledSkillsDir(), sets, agent);
880
+ } catch (error) {
881
+ throw new Error(`${agent.name}: ${error.message}`);
882
+ }
883
+ status(`\u2705 ${agent.name}: ${installed[agent.id].join(", ")} in ${agent.skillsDir}`);
884
+ }
885
+ for (const set of sets) {
886
+ if (set !== "core") settingsStore.set(ANSWER_KEY[set], "yes");
887
+ }
888
+ status("Start a new agent session to load them.");
889
+ return installed;
890
+ }
891
+ async function skillsAdd(argv) {
892
+ var _a;
893
+ const terminal = terminalFrom(argv);
894
+ const names = (_a = argv.skills) != null ? _a : [];
895
+ let sets;
896
+ if (!names.length) {
897
+ if (terminal.json || !terminal.stdinTTY || !terminal.stderrTTY || terminal.ci) {
898
+ die(`Name the skill: ${BRAND.cli} skills add writing|growth|all`);
899
+ }
900
+ sets = await offerOptionalSkills({ terminal, store: settingsStore, ask, log: status }, true);
901
+ if (!sets.length) {
902
+ result({ installed: {} });
903
+ return;
904
+ }
905
+ } else {
906
+ try {
907
+ sets = parseSetNames(names);
908
+ } catch (error) {
909
+ die(error.message);
910
+ }
911
+ }
912
+ try {
913
+ result({ installed: await install(sets, argv, terminal) });
914
+ } catch (error) {
915
+ die(error.message);
916
+ }
917
+ }
918
+ async function skillsRemove(argv) {
919
+ var _a, _b;
920
+ const names = (_a = argv.skills) != null ? _a : [];
921
+ if (!names.length) die(`Name the skill: ${BRAND.cli} skills remove writing|growth|all`);
922
+ let sets;
923
+ let agents;
924
+ try {
925
+ sets = parseSetNames(names);
926
+ agents = await chooseAgents({
927
+ found: detectAgents((0, import_os2.homedir)(), process.env),
928
+ all: knownAgents((0, import_os2.homedir)(), process.env),
929
+ requested: (_b = argv.agent) != null ? _b : ["all"],
930
+ yes: true,
931
+ terminal: terminalFrom(argv),
932
+ ask,
933
+ log: status
934
+ });
935
+ } catch (error) {
936
+ die(error.message);
937
+ }
938
+ const removed = {};
939
+ for (const agent of agents) {
940
+ removed[agent.id] = removeSets(sets, agent);
941
+ if (removed[agent.id].length) status(`\u{1F5D1}\uFE0F ${agent.name}: removed ${removed[agent.id].join(", ")}`);
942
+ }
943
+ for (const set of sets) {
944
+ if (set !== "core") settingsStore.set(ANSWER_KEY[set], "no");
945
+ }
946
+ result({ removed });
947
+ }
948
+ async function offerSkillsAfterLogin(argv) {
949
+ try {
950
+ const terminal = terminalFrom(argv);
951
+ const sets = await offerOptionalSkills({ terminal, store: settingsStore, ask, log: status });
952
+ if (sets.length) await install(sets, {}, terminal);
953
+ } catch (error) {
954
+ status(`Skills not installed: ${(error == null ? void 0 : error.message) || error}. Try: ${BRAND.cli} skills add`);
955
+ }
956
+ }
957
+
695
958
  // src/commands/auth.ts
696
- var CREDENTIALS_DIR = (0, import_path2.join)((0, import_os2.homedir)(), BRAND.configDirName);
697
- var CREDENTIALS_FILE = (0, import_path2.join)(CREDENTIALS_DIR, "credentials.json");
959
+ var CREDENTIALS_DIR = (0, import_path4.join)((0, import_os3.homedir)(), BRAND.configDirName);
960
+ var CREDENTIALS_FILE = (0, import_path4.join)(CREDENTIALS_DIR, "credentials.json");
698
961
  var DEFAULT_AUTH_SERVER = BRAND.defaultAuthServer;
699
962
  var DEFAULT_API_URL = BRAND.defaultApiUrl;
700
963
  function loadCredentials() {
701
964
  try {
702
- if (!(0, import_fs2.existsSync)(CREDENTIALS_FILE)) return null;
703
- const data = JSON.parse((0, import_fs2.readFileSync)(CREDENTIALS_FILE, "utf-8"));
965
+ if (!(0, import_fs3.existsSync)(CREDENTIALS_FILE)) return null;
966
+ const data = JSON.parse((0, import_fs3.readFileSync)(CREDENTIALS_FILE, "utf-8"));
704
967
  if (!data.accessToken) return null;
705
968
  return data;
706
969
  } catch (e) {
@@ -708,15 +971,15 @@ function loadCredentials() {
708
971
  }
709
972
  }
710
973
  function saveCredentials(credentials) {
711
- if (!(0, import_fs2.existsSync)(CREDENTIALS_DIR)) {
712
- (0, import_fs2.mkdirSync)(CREDENTIALS_DIR, { recursive: true, mode: 448 });
974
+ if (!(0, import_fs3.existsSync)(CREDENTIALS_DIR)) {
975
+ (0, import_fs3.mkdirSync)(CREDENTIALS_DIR, { recursive: true, mode: 448 });
713
976
  }
714
- (0, import_fs2.writeFileSync)(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), { encoding: "utf-8", mode: 384 });
715
- (0, import_fs2.chmodSync)(CREDENTIALS_FILE, 384);
977
+ (0, import_fs3.writeFileSync)(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), { encoding: "utf-8", mode: 384 });
978
+ (0, import_fs3.chmodSync)(CREDENTIALS_FILE, 384);
716
979
  }
717
980
  function deleteCredentials() {
718
- if ((0, import_fs2.existsSync)(CREDENTIALS_FILE)) {
719
- (0, import_fs2.unlinkSync)(CREDENTIALS_FILE);
981
+ if ((0, import_fs3.existsSync)(CREDENTIALS_FILE)) {
982
+ (0, import_fs3.unlinkSync)(CREDENTIALS_FILE);
720
983
  }
721
984
  }
722
985
  function openBrowser(url) {
@@ -730,8 +993,8 @@ function openBrowser(url) {
730
993
  function sleep(ms) {
731
994
  return new Promise((resolve) => setTimeout(resolve, ms));
732
995
  }
733
- function ask(question) {
734
- const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stderr });
996
+ function ask2(question) {
997
+ const rl = (0, import_readline2.createInterface)({ input: process.stdin, output: process.stderr });
735
998
  return new Promise(
736
999
  (resolve) => rl.question(question, (answer) => {
737
1000
  rl.close();
@@ -743,11 +1006,11 @@ async function maybeConfigureTimezone() {
743
1006
  if (getSetting("timezone")) return;
744
1007
  if (!process.stdin.isTTY || !process.stderr.isTTY) return;
745
1008
  const detected = Intl.DateTimeFormat().resolvedOptions().timeZone;
746
- const answer = (await ask(`
1009
+ const answer = (await ask2(`
747
1010
  Scheduling timezone: ${detected} \u2014 OK? [Y/n] `)).trim().toLowerCase();
748
1011
  let timezone = detected;
749
1012
  if (answer === "n" || answer === "no") {
750
- const typed = (await ask(`Enter an IANA timezone name (e.g. ${BRAND.exampleTimezone}), or leave empty to skip: `)).trim();
1013
+ const typed = (await ask2(`Enter an IANA timezone name (e.g. ${BRAND.exampleTimezone}), or leave empty to skip: `)).trim();
751
1014
  if (!typed) {
752
1015
  console.error(
753
1016
  `Skipped. Dates without an explicit offset will be refused until you run: ${BRAND.cli} config:set timezone <IANA name>`
@@ -777,7 +1040,7 @@ Scheduling timezone: ${detected} \u2014 OK? [Y/n] `)).trim().toLowerCase();
777
1040
  async function chooseWorkspace(creds) {
778
1041
  let workspaces;
779
1042
  try {
780
- workspaces = await new PostyAPI({
1043
+ workspaces = await new PostemiAPI({
781
1044
  apiKey: creds.accessToken,
782
1045
  apiUrl: creds.apiUrl
783
1046
  }).listWorkspaces();
@@ -798,7 +1061,7 @@ async function chooseWorkspace(creds) {
798
1061
  );
799
1062
  return;
800
1063
  }
801
- const answer = (await ask(`Work in which one? [1-${workspaces.length}] `)).trim();
1064
+ const answer = (await ask2(`Work in which one? [1-${workspaces.length}] `)).trim();
802
1065
  const index = Number(answer) - 1;
803
1066
  const chosen = workspaces[index];
804
1067
  if (!chosen) {
@@ -822,9 +1085,11 @@ async function authLogin(argv) {
822
1085
  headers: { "Content-Type": "application/json" },
823
1086
  // Shown on the approval page, so somebody can tell the login they just
824
1087
  // started from one they did not.
825
- body: JSON.stringify(__spreadValues({
826
- client_name: readEnv("CLIENT_NAME") || BRAND.clientName
827
- }, IS_POSTEMI ? { brand: "postemi" } : {}))
1088
+ body: JSON.stringify({
1089
+ client_name: readEnv("CLIENT_NAME") || BRAND.clientName,
1090
+ // Asks for Postemi's approval page (postemi.com/app/device).
1091
+ brand: "postemi"
1092
+ })
828
1093
  });
829
1094
  if (!response.ok) {
830
1095
  const error = await response.text();
@@ -865,12 +1130,11 @@ async function authLogin(argv) {
865
1130
  const creds = {
866
1131
  accessToken: data.access_token,
867
1132
  /*
868
- Posty stores the API base the server names. The Postemi build
869
- stores the host it logged in on (api.postemi.com): the server
870
- names Posty's base for every brand, and the Postemi host is what
871
- makes the API answer as Postemi.
1133
+ The host the login ran on (api.postemi.com), not the `api_url` in
1134
+ the token response: the Postemi host is what makes the API answer
1135
+ as Postemi.
872
1136
  */
873
- apiUrl: IS_POSTEMI ? String(authServer).replace(/\/+$/, "") : data.api_url || DEFAULT_API_URL,
1137
+ apiUrl: String(authServer).replace(/\/+$/, ""),
874
1138
  organizationId: data.organization_id
875
1139
  };
876
1140
  saveCredentials(creds);
@@ -878,6 +1142,7 @@ async function authLogin(argv) {
878
1142
  console.log(`\u{1F4C1} Credentials saved to ${CREDENTIALS_FILE}`);
879
1143
  await chooseWorkspace(creds);
880
1144
  await maybeConfigureTimezone();
1145
+ await offerSkillsAfterLogin(argv);
881
1146
  return;
882
1147
  }
883
1148
  if (data.error === "authorization_pending") {
@@ -979,7 +1244,7 @@ async function authSignup(argv) {
979
1244
  });
980
1245
  return;
981
1246
  }
982
- code = (await ask("Code from the e-mail: ")).trim();
1247
+ code = (await ask2("Code from the e-mail: ")).trim();
983
1248
  if (!code) {
984
1249
  console.error("\u274C No code given. Run the command again with --code <code>.");
985
1250
  process.exit(1);
@@ -995,7 +1260,7 @@ async function authSignup(argv) {
995
1260
  status(` Privacy notice: ${PRIVACY_URL}`);
996
1261
  let accepted = !!argv.acceptTerms;
997
1262
  if (!accepted && interactive) {
998
- const answer = (await ask("Do you agree to both? [y/N] ")).trim().toLowerCase();
1263
+ const answer = (await ask2("Do you agree to both? [y/N] ")).trim().toLowerCase();
999
1264
  accepted = answer === "y" || answer === "yes" || answer === "i" || answer === "igen";
1000
1265
  }
1001
1266
  if (!accepted) {
@@ -1095,7 +1360,7 @@ async function authStatus() {
1095
1360
  source = "environment";
1096
1361
  apiKey = envKey;
1097
1362
  apiUrl = readEnv("API_URL") || DEFAULT_API_URL;
1098
- status(`\u{1F511} Authentication method: ${label(method)} (${envSourceName("API_KEY")})`);
1363
+ status(`\u{1F511} Authentication method: ${label(method)} (${envName("API_KEY")})`);
1099
1364
  status(`\u{1F511} Credential: ${hint(envKey)}`);
1100
1365
  } else {
1101
1366
  status("\u274C Not authenticated.");
@@ -1113,7 +1378,7 @@ async function authStatus() {
1113
1378
  const base = { method, source, apiUrl, workspace: workspaceId != null ? workspaceId : null };
1114
1379
  let subscription;
1115
1380
  try {
1116
- subscription = await new PostyAPI({
1381
+ subscription = await new PostemiAPI({
1117
1382
  apiKey,
1118
1383
  apiUrl,
1119
1384
  workspaceId
@@ -1163,7 +1428,7 @@ async function authStatus() {
1163
1428
  }
1164
1429
  let integrations = null;
1165
1430
  try {
1166
- const list = await new PostyAPI({
1431
+ const list = await new PostemiAPI({
1167
1432
  apiKey,
1168
1433
  apiUrl,
1169
1434
  workspaceId
@@ -1219,7 +1484,7 @@ function getConfig() {
1219
1484
  }
1220
1485
 
1221
1486
  // src/commands/posts.ts
1222
- var import_fs3 = require("fs");
1487
+ var import_fs4 = require("fs");
1223
1488
  var pairContentsWithMedia = (argv) => {
1224
1489
  const read = (token, next, long, short) => {
1225
1490
  if (token === `--${long}` || token === `-${short}`) {
@@ -1270,7 +1535,7 @@ function echoInterpretation(input, resolved) {
1270
1535
  }
1271
1536
  async function getMissingContent(args) {
1272
1537
  const config = getConfig();
1273
- const api = new PostyAPI(config);
1538
+ const api = new PostemiAPI(config);
1274
1539
  try {
1275
1540
  const res = await api.getMissingContent(args.id);
1276
1541
  result(res);
@@ -1281,7 +1546,7 @@ async function getMissingContent(args) {
1281
1546
  }
1282
1547
  async function connectPost(args) {
1283
1548
  const config = getConfig();
1284
- const api = new PostyAPI(config);
1549
+ const api = new PostemiAPI(config);
1285
1550
  try {
1286
1551
  const res = await api.updateReleaseId(args.id, args.releaseId);
1287
1552
  status(`\u2705 Post ${args.id} connected to release ${args.releaseId}`);
@@ -1293,7 +1558,7 @@ async function connectPost(args) {
1293
1558
  }
1294
1559
  async function findSlot(args) {
1295
1560
  const config = getConfig();
1296
- const api = new PostyAPI(config);
1561
+ const api = new PostemiAPI(config);
1297
1562
  try {
1298
1563
  const res = await api.findSlot(args.id);
1299
1564
  result(res);
@@ -1304,16 +1569,16 @@ async function findSlot(args) {
1304
1569
  }
1305
1570
  async function createPost(args) {
1306
1571
  const config = getConfig();
1307
- const api = new PostyAPI(config);
1572
+ const api = new PostemiAPI(config);
1308
1573
  let postData;
1309
1574
  if (args.json) {
1310
1575
  try {
1311
1576
  const jsonPath = args.json;
1312
- if (!(0, import_fs3.existsSync)(jsonPath)) {
1577
+ if (!(0, import_fs4.existsSync)(jsonPath)) {
1313
1578
  console.error(`\u274C JSON file not found: ${jsonPath}`);
1314
1579
  process.exit(1);
1315
1580
  }
1316
- const jsonContent = (0, import_fs3.readFileSync)(jsonPath, "utf-8");
1581
+ const jsonContent = (0, import_fs4.readFileSync)(jsonPath, "utf-8");
1317
1582
  postData = JSON.parse(jsonContent);
1318
1583
  } catch (error) {
1319
1584
  console.error(`\u274C Failed to parse JSON file: ${error.message}`);
@@ -1401,7 +1666,7 @@ async function createPost(args) {
1401
1666
  }
1402
1667
  async function listPosts(args) {
1403
1668
  const config = getConfig();
1404
- const api = new PostyAPI(config);
1669
+ const api = new PostemiAPI(config);
1405
1670
  const defaultStartDate = /* @__PURE__ */ new Date();
1406
1671
  defaultStartDate.setDate(defaultStartDate.getDate() - 30);
1407
1672
  const defaultEndDate = /* @__PURE__ */ new Date();
@@ -1435,7 +1700,7 @@ async function listPosts(args) {
1435
1700
  }
1436
1701
  async function changePostStatus(args) {
1437
1702
  const config = getConfig();
1438
- const api = new PostyAPI(config);
1703
+ const api = new PostemiAPI(config);
1439
1704
  try {
1440
1705
  const res = await api.changePostStatus(args.id, args.status);
1441
1706
  status(`\u2705 Post ${args.id} status changed to ${args.status}`);
@@ -1447,7 +1712,7 @@ async function changePostStatus(args) {
1447
1712
  }
1448
1713
  async function deletePost(args) {
1449
1714
  const config = getConfig();
1450
- const api = new PostyAPI(config);
1715
+ const api = new PostemiAPI(config);
1451
1716
  try {
1452
1717
  const res = await api.deletePost(args.id);
1453
1718
  status(`\u2705 Post ${args.id} deleted successfully!`);
@@ -1459,7 +1724,7 @@ async function deletePost(args) {
1459
1724
  }
1460
1725
  async function deletePostGroup(args) {
1461
1726
  const config = getConfig();
1462
- const api = new PostyAPI(config);
1727
+ const api = new PostemiAPI(config);
1463
1728
  try {
1464
1729
  const res = await api.deletePostGroup(args.group);
1465
1730
  status(`\u2705 Post group ${args.group} deleted from every channel`);
@@ -1473,7 +1738,7 @@ async function deletePostGroup(args) {
1473
1738
  // src/commands/integrations.ts
1474
1739
  async function listIntegrations(args) {
1475
1740
  const config = getConfig();
1476
- const api = new PostyAPI(config);
1741
+ const api = new PostemiAPI(config);
1477
1742
  try {
1478
1743
  const res = await api.listIntegrations(args == null ? void 0 : args.group);
1479
1744
  result(res);
@@ -1484,7 +1749,7 @@ async function listIntegrations(args) {
1484
1749
  }
1485
1750
  async function listGroups() {
1486
1751
  const config = getConfig();
1487
- const api = new PostyAPI(config);
1752
+ const api = new PostemiAPI(config);
1488
1753
  try {
1489
1754
  const res = await api.listGroups();
1490
1755
  result(res);
@@ -1495,7 +1760,7 @@ async function listGroups() {
1495
1760
  }
1496
1761
  async function getIntegrationSettings(args) {
1497
1762
  const config = getConfig();
1498
- const api = new PostyAPI(config);
1763
+ const api = new PostemiAPI(config);
1499
1764
  try {
1500
1765
  const res = await api.getIntegrationSettings(args.id);
1501
1766
  result(res);
@@ -1506,7 +1771,7 @@ async function getIntegrationSettings(args) {
1506
1771
  }
1507
1772
  async function triggerIntegrationTool(args) {
1508
1773
  const config = getConfig();
1509
- const api = new PostyAPI(config);
1774
+ const api = new PostemiAPI(config);
1510
1775
  let data = {};
1511
1776
  if (args.data) {
1512
1777
  try {
@@ -1537,7 +1802,7 @@ function daysArg(args) {
1537
1802
  }
1538
1803
  async function getAnalytics(args) {
1539
1804
  const config = getConfig();
1540
- const api = new PostyAPI(config);
1805
+ const api = new PostemiAPI(config);
1541
1806
  try {
1542
1807
  const res = await api.getAnalytics(args.id, daysArg(args));
1543
1808
  result(res);
@@ -1548,7 +1813,7 @@ async function getAnalytics(args) {
1548
1813
  }
1549
1814
  async function getPostAnalytics(args) {
1550
1815
  const config = getConfig();
1551
- const api = new PostyAPI(config);
1816
+ const api = new PostemiAPI(config);
1552
1817
  try {
1553
1818
  const res = await api.getPostAnalytics(args.id, daysArg(args));
1554
1819
  result(res);
@@ -1559,18 +1824,18 @@ async function getPostAnalytics(args) {
1559
1824
  }
1560
1825
 
1561
1826
  // src/commands/upload.ts
1562
- var import_fs4 = require("fs");
1563
- var import_path3 = require("path");
1827
+ var import_fs5 = require("fs");
1828
+ var import_path5 = require("path");
1564
1829
  async function uploadFile(args) {
1565
1830
  const config = getConfig();
1566
- const api = new PostyAPI(config);
1567
- if (!(0, import_fs4.existsSync)(args.file)) {
1831
+ const api = new PostemiAPI(config);
1832
+ if (!(0, import_fs5.existsSync)(args.file)) {
1568
1833
  console.error(`\u274C File not found: ${args.file}`);
1569
1834
  process.exit(1);
1570
1835
  }
1571
1836
  try {
1572
- const fileBuffer = (0, import_fs4.readFileSync)(args.file);
1573
- const filename = (0, import_path3.basename)(args.file) || "file";
1837
+ const fileBuffer = (0, import_fs5.readFileSync)(args.file);
1838
+ const filename = (0, import_path5.basename)(args.file) || "file";
1574
1839
  const res = await api.upload(fileBuffer, filename);
1575
1840
  status("\u2705 File uploaded successfully!");
1576
1841
  result(res);
@@ -1581,7 +1846,7 @@ async function uploadFile(args) {
1581
1846
  }
1582
1847
  async function createUploadLink() {
1583
1848
  const config = getConfig();
1584
- const api = new PostyAPI(config);
1849
+ const api = new PostemiAPI(config);
1585
1850
  try {
1586
1851
  const res = await api.createUploadLink();
1587
1852
  status(
@@ -1595,7 +1860,7 @@ async function createUploadLink() {
1595
1860
  }
1596
1861
  async function listUploadLinkFiles(args) {
1597
1862
  const config = getConfig();
1598
- const api = new PostyAPI(config);
1863
+ const api = new PostemiAPI(config);
1599
1864
  try {
1600
1865
  const res = await api.getUploadLinkFiles(args.id);
1601
1866
  if (res.status === "empty") {
@@ -1648,7 +1913,7 @@ async function configGet(args) {
1648
1913
 
1649
1914
  // src/commands/workspaces.ts
1650
1915
  async function listWorkspaces() {
1651
- const api = new PostyAPI(getConfig());
1916
+ const api = new PostemiAPI(getConfig());
1652
1917
  try {
1653
1918
  const res = await api.listWorkspaces();
1654
1919
  result(res);
@@ -1662,7 +1927,7 @@ async function useWorkspace(args) {
1662
1927
  if (!id) {
1663
1928
  fail("Failed to select workspace", new Error(`Give the workspace id, from "${BRAND.cli} workspaces:list"`));
1664
1929
  }
1665
- const api = new PostyAPI(__spreadProps(__spreadValues({}, getConfig()), { workspaceId: void 0 }));
1930
+ const api = new PostemiAPI(__spreadProps(__spreadValues({}, getConfig()), { workspaceId: void 0 }));
1666
1931
  let workspaces;
1667
1932
  try {
1668
1933
  workspaces = await api.listWorkspaces();
@@ -1690,7 +1955,7 @@ async function useWorkspace(args) {
1690
1955
  }
1691
1956
  async function currentWorkspace() {
1692
1957
  const id = getWorkspaceId();
1693
- const api = new PostyAPI(getConfig());
1958
+ const api = new PostemiAPI(getConfig());
1694
1959
  try {
1695
1960
  const workspaces = await api.listWorkspaces();
1696
1961
  const current = workspaces.find((w) => w.current) || null;
@@ -1707,7 +1972,7 @@ async function currentWorkspace() {
1707
1972
 
1708
1973
  // src/commands/billing.ts
1709
1974
  async function billingPlans() {
1710
- const api = new PostyAPI(getConfig());
1975
+ const api = new PostemiAPI(getConfig());
1711
1976
  try {
1712
1977
  result(await api.getPlans());
1713
1978
  } catch (error) {
@@ -1729,7 +1994,7 @@ async function billingSubscribe(args) {
1729
1994
  new Error("Give the period with --period monthly or --period yearly")
1730
1995
  );
1731
1996
  }
1732
- const api = new PostyAPI(getConfig());
1997
+ const api = new PostemiAPI(getConfig());
1733
1998
  try {
1734
1999
  const checkout = await api.createSubscriptionCheckout(tierForApi(tier), period);
1735
2000
  status(
@@ -1754,7 +2019,7 @@ async function billingSubscribe(args) {
1754
2019
  }
1755
2020
  }
1756
2021
  async function billingStatus() {
1757
- const api = new PostyAPI(getConfig());
2022
+ const api = new PostemiAPI(getConfig());
1758
2023
  try {
1759
2024
  const subscription = await api.getSubscription();
1760
2025
  if (subscription.pendingCheckout) {
@@ -1770,7 +2035,7 @@ async function billingStatus() {
1770
2035
  }
1771
2036
  }
1772
2037
  async function billingManage(args) {
1773
- const api = new PostyAPI(getConfig());
2038
+ const api = new PostemiAPI(getConfig());
1774
2039
  try {
1775
2040
  const portal = await api.getSubscriptionPortal();
1776
2041
  const _a = portal, { note } = _a, json = __objRest(_a, ["note"]);
@@ -1785,162 +2050,8 @@ async function billingManage(args) {
1785
2050
  }
1786
2051
  }
1787
2052
 
1788
- // src/commands/fotoai.ts
1789
- var import_crypto2 = require("crypto");
1790
- var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
1791
- var failFotoAi = (context, error) => {
1792
- if (error instanceof ApiError) {
1793
- let body = null;
1794
- try {
1795
- body = JSON.parse(error.body);
1796
- } catch (e) {
1797
- }
1798
- if (error.status === 404 && !(body == null ? void 0 : body.code)) {
1799
- status("\u274C FotoAI is not available on this Posty server yet.");
1800
- process.exit(1);
1801
- }
1802
- if (body == null ? void 0 : body.code) {
1803
- const details = body.details || {};
1804
- status(`\u274C ${context}: ${body.msg || body.code} (${body.code})`);
1805
- if (details.connect_url) {
1806
- status(`Connect FotoAI in Posty once: ${details.connect_url}`);
1807
- }
1808
- if (details.topup_url) {
1809
- status(`FotoAI credits can be bought at ${details.topup_url}`);
1810
- }
1811
- if (typeof details.quote === "number") {
1812
- status(`The new price is ${details.quote / 1e3} credits. Quote again and confirm it.`);
1813
- }
1814
- if (body.retryAfter) {
1815
- status(`Try again in ${body.retryAfter} seconds.`);
1816
- }
1817
- process.exit(1);
1818
- }
1819
- }
1820
- fail(context, error);
1821
- };
1822
- var paramsOf = (args) => {
1823
- const params = {};
1824
- if (args.aspect) params.aspect = String(args.aspect);
1825
- if (args.duration) params.duration = Number(args.duration);
1826
- if (args.resolution) params.resolution = String(args.resolution);
1827
- return params;
1828
- };
1829
- async function fotoAiStatus() {
1830
- const api = new PostyAPI(getConfig());
1831
- try {
1832
- const current = await api.fotoAiStatus();
1833
- if (!current.connected) {
1834
- status(
1835
- current.needsReconnect ? `The FotoAI connection has expired. Connect again in Posty: ${current.connectUrl}` : `FotoAI is not connected. The person connects it once in Posty: ${current.connectUrl}`
1836
- );
1837
- }
1838
- result(current);
1839
- } catch (error) {
1840
- failFotoAi("Failed to read the FotoAI status", error);
1841
- }
1842
- }
1843
- async function fotoAiModels(args) {
1844
- const api = new PostyAPI(getConfig());
1845
- try {
1846
- const catalog = await api.fotoAiModels(args.category);
1847
- const models = (catalog.models || []).map((m) => {
1848
- var _a, _b;
1849
- return {
1850
- id: m.id,
1851
- name: m.name,
1852
- category: m.category,
1853
- can_use: !!m.can_use,
1854
- estimated_seconds: (_a = m.estimated_seconds) != null ? _a : null,
1855
- max_duration_seconds: (_b = m.max_duration_seconds) != null ? _b : null,
1856
- base_price_credits: (() => {
1857
- var _a2;
1858
- const prices = (((_a2 = m.price) == null ? void 0 : _a2.configs) || []).map((c) => Number(c.millicredits)).filter((n) => Number.isFinite(n) && n > 0);
1859
- return prices.length ? Math.min(...prices) / 1e3 : null;
1860
- })(),
1861
- params: (m.params || []).map((p) => {
1862
- var _a2;
1863
- return {
1864
- key: p.key,
1865
- default: (_a2 = p.default) != null ? _a2 : null,
1866
- options: (p.options || []).map((o) => o.value)
1867
- };
1868
- })
1869
- };
1870
- });
1871
- result({ catalog_version: catalog.catalog_version || null, models });
1872
- } catch (error) {
1873
- failFotoAi("Failed to list the FotoAI models", error);
1874
- }
1875
- }
1876
- async function fotoAiGet(args) {
1877
- var _a, _b;
1878
- const api = new PostyAPI(getConfig());
1879
- const deadline = Date.now() + Math.max(0, Number(args.wait) || 0) * 1e3;
1880
- try {
1881
- let current = await api.fotoAiImport(String(args.id));
1882
- while ((["queued", "running"].includes(current.status) || current.status === "completed" && !((_a = current.media) == null ? void 0 : _a.length) && !current.import_error) && Date.now() < deadline) {
1883
- await sleep2(3e3);
1884
- current = await api.fotoAiImport(String(args.id));
1885
- }
1886
- if ((_b = current.media) == null ? void 0 : _b.length) {
1887
- status(`Saved in the media library. Attach each media[].path with posts:create.`);
1888
- } else if (["queued", "running"].includes(current.status)) {
1889
- status(`Still ${current.status}. Run "${BRAND.cli} fotoai:get ${current.id} --wait 60" again.`);
1890
- }
1891
- result(current);
1892
- } catch (error) {
1893
- failFotoAi("Failed to read the FotoAI generation", error);
1894
- }
1895
- }
1896
- async function fotoAiGenerate(args) {
1897
- var _a;
1898
- const api = new PostyAPI(getConfig());
1899
- const request = {
1900
- model: String(args.model),
1901
- prompt: String(args.prompt),
1902
- params: paramsOf(args)
1903
- };
1904
- let quote;
1905
- try {
1906
- quote = await api.fotoAiQuote(request);
1907
- } catch (error) {
1908
- failFotoAi("Failed to price the FotoAI generation", error);
1909
- }
1910
- const balance = typeof quote.available_millicredits === "number" ? `, balance ${quote.available_millicredits / 1e3} credits` : "";
1911
- if (quote.insufficient) {
1912
- status(
1913
- `Not enough FotoAI credits: this costs ${quote.credits} credits${balance}.${quote.topup_url ? ` Buy credits at ${quote.topup_url}` : ""}`
1914
- );
1915
- result({ generated: false, quote });
1916
- process.exit(1);
1917
- }
1918
- if (!args.yes) {
1919
- status(
1920
- `This costs ${quote.credits} FotoAI credits${balance}. Nothing was spent. Run the same command with --yes to generate.`
1921
- );
1922
- result({ generated: false, quote });
1923
- return;
1924
- }
1925
- try {
1926
- const started = await api.fotoAiGenerate(__spreadProps(__spreadValues({}, request), {
1927
- quote_id: quote.quote_id,
1928
- idempotency_key: args.idempotencyKey || (0, import_crypto2.randomUUID)()
1929
- }));
1930
- status(`Generating (about ${(_a = started.estimated_seconds) != null ? _a : "?"} seconds), id ${started.id}.`);
1931
- if (!Number(args.wait)) {
1932
- status(`Run "${BRAND.cli} fotoai:get ${started.id} --wait 60" for the media paths.`);
1933
- result(started);
1934
- return;
1935
- }
1936
- await fotoAiGet({ id: started.id, wait: args.wait });
1937
- } catch (error) {
1938
- failFotoAi("Failed to start the FotoAI generation", error);
1939
- }
1940
- }
1941
-
1942
2053
  // src/index.ts
1943
- var cli = (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).scriptName(BRAND.cli).usage("$0 <command> [options]").option("workspace", {
2054
+ (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).scriptName(BRAND.cli).usage("$0 <command> [options]").option("workspace", {
1944
2055
  alias: "w",
1945
2056
  describe: `Workspace id this command acts on (overrides "${BRAND.cli} workspaces:use" and ${envName("WORKSPACE")})`,
1946
2057
  type: "string",
@@ -2299,7 +2410,7 @@ var cli = (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).s
2299
2410
  currentWorkspace
2300
2411
  ).command(
2301
2412
  "billing:plans",
2302
- IS_POSTEMI ? "Show the Postemi plans: prices, limits, the trial rule and the current tier" : "Show the Posty plans: prices in forint, limits, the trial rule and the current tier",
2413
+ "Show the Postemi plans: prices, limits, the trial rule and the current tier",
2303
2414
  {},
2304
2415
  billingPlans
2305
2416
  ).command(
@@ -2344,39 +2455,29 @@ var cli = (0, import_yargs.default)((0, import_helpers.hideBin)(process.argv)).s
2344
2455
  });
2345
2456
  },
2346
2457
  billingManage
2347
- );
2348
- if (!IS_POSTEMI) cli.command(
2349
- "fotoai:status",
2350
- "Show whether FotoAI (fotoai.hu) is connected for the key owner, as whom, and the FotoAI credit balance",
2351
- {},
2352
- fotoAiStatus
2353
2458
  ).command(
2354
- "fotoai:models",
2355
- "List the FotoAI image and video models with their parameters and prices",
2459
+ "skills <command>",
2460
+ `Optional agent skills: install them into Claude Code, Codex, Cursor or Grok Build`,
2356
2461
  (yargs2) => {
2357
- return yargs2.option("category", {
2358
- describe: "Only image or only video models",
2462
+ const target = (y) => y.positional("skills", {
2463
+ describe: "writing (with templates), growth, all, or core",
2359
2464
  type: "string",
2360
- choices: ["image", "video"]
2361
- });
2362
- },
2363
- fotoAiModels
2364
- ).command(
2365
- "fotoai:generate",
2366
- "Price, and with --yes generate, an image or video with the key owner's own FotoAI credits; the result is saved into the media library",
2367
- (yargs2) => {
2368
- return yargs2.option("model", { alias: "m", describe: `A model id from "${BRAND.cli} fotoai:models"`, type: "string", demandOption: true }).option("prompt", { alias: "p", describe: "What to generate", type: "string", demandOption: true }).option("aspect", { describe: "Aspect ratio, e.g. 1:1, 4:5, 9:16, 16:9", type: "string" }).option("duration", { describe: "Video length in seconds (video models)", type: "number" }).option("resolution", { describe: "Resolution, when the model offers one (e.g. 2K)", type: "string" }).option("yes", { describe: "Spend the credits. Without it only the price is shown", type: "boolean", default: false }).option("wait", { describe: "Wait up to this many seconds for the result", type: "number", default: 0 }).option("idempotency-key", { describe: "Reuse on a retry so nothing is charged twice", type: "string" }).example('$0 fotoai:generate -m image.seedream-5-lite -p "k\xE1v\xE9 a teraszon" --aspect 4:5', "Show the price only").example('$0 fotoai:generate -m image.seedream-5-lite -p "k\xE1v\xE9 a teraszon" --aspect 4:5 --yes --wait 60', "Generate and print the media paths");
2465
+ array: true
2466
+ }).option("agent", {
2467
+ describe: "Only these agents: claude, codex, cursor, grok, or all (skips the question)",
2468
+ type: "string",
2469
+ array: true
2470
+ }).option("yes", { alias: "y", describe: "Ask nothing; use every agent found", type: "boolean", default: false }).option("json", { describe: "Run as an agent or script: never ask", type: "boolean", default: false });
2471
+ return yargs2.command("list", "Show the core and optional skills, and where each is installed", {}, skillsList).command(
2472
+ "add [skills..]",
2473
+ "Install skills (writing, growth, all); asks which when you name none",
2474
+ (y) => target(y).example("$0 skills add writing", "Writing and template skills, plus the core skill").example("$0 skills add all --agent claude --yes", "Both optional skills, Claude Code only, no questions"),
2475
+ skillsAdd
2476
+ ).command("remove [skills..]", "Remove installed skills (writing, growth, all, core)", target, skillsRemove).demandCommand(1, `Use: ${BRAND.cli} skills list | add | remove`);
2369
2477
  },
2370
- fotoAiGenerate
2478
+ () => {
2479
+ }
2371
2480
  ).command(
2372
- "fotoai:get <id>",
2373
- "Show a FotoAI generation; once done, its outputs saved into the media library with paths for posts:create",
2374
- (yargs2) => {
2375
- return yargs2.positional("id", { describe: "The generation id", type: "string" }).option("wait", { describe: "Wait up to this many seconds while it runs", type: "number", default: 0 });
2376
- },
2377
- fotoAiGet
2378
- );
2379
- cli.command(
2380
2481
  "config:set <key> <value>",
2381
2482
  `Save a CLI setting to ~/${BRAND.configDirName}/config.json`,
2382
2483
  (yargs2) => {
@@ -2409,14 +2510,17 @@ cli.command(
2409
2510
  return yargs2.option("auth-server", {
2410
2511
  describe: `Auth server URL (default: ${BRAND.defaultApiUrl})`,
2411
2512
  type: "string"
2513
+ }).option("json", {
2514
+ describe: "Run as an agent or script: ask no setup questions after the login",
2515
+ type: "boolean",
2516
+ default: false
2412
2517
  }).example(
2413
2518
  "$0 auth:login",
2414
2519
  "Login via OAuth2 device flow"
2415
2520
  );
2416
2521
  },
2417
2522
  authLogin
2418
- );
2419
- cli.command(
2523
+ ).command(
2420
2524
  "auth:signup",
2421
2525
  `Create a NEW ${BRAND.name} account with a code sent to the e-mail address, and store its key. No free trial: the next step is billing:subscribe`,
2422
2526
  (yargs2) => {
@@ -2448,13 +2552,12 @@ cli.command(
2448
2552
  "$0 auth:signup --email anna@example.com",
2449
2553
  "Send the code, then ask for it and for the terms on the terminal"
2450
2554
  ).example(
2451
- `$0 auth:signup --email anna@example.com --code 123456 --accept-terms --workspace-name "${IS_POSTEMI ? "Anna's Cafe" : "Anna K\xE1v\xE9z\xF3"}"`,
2555
+ `$0 auth:signup --email anna@example.com --code 123456 --accept-terms --workspace-name "Anna's Cafe"`,
2452
2556
  "Finish in one run (an agent, after the person read the code and agreed)"
2453
2557
  );
2454
2558
  },
2455
2559
  authSignup
2456
- );
2457
- cli.command(
2560
+ ).command(
2458
2561
  "auth:logout",
2459
2562
  "Remove stored OAuth2 credentials",
2460
2563
  {},