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/PROVIDER_SETTINGS.md +183 -7
- package/README.md +87 -9
- package/SKILL.md +83 -30
- package/dist/index.js +435 -332
- package/dist/index.js.map +1 -1
- package/package.json +23 -7
- package/skills/postemi/SKILL.md +1121 -0
- package/skills/postemi-growth/SKILL.md +221 -0
- package/skills/postemi-growth/reference/30-angles.md +74 -0
- package/skills/postemi-growth/reference/review.md +63 -0
- package/skills/postemi-templates/SKILL.md +107 -0
- package/skills/postemi-templates/reference/engagement.md +155 -0
- package/skills/postemi-templates/reference/everyday.md +132 -0
- package/skills/postemi-templates/reference/hook-contrarian.md +227 -0
- package/skills/postemi-templates/reference/hook-growth.md +210 -0
- package/skills/postemi-templates/reference/hook-how-to.md +266 -0
- package/skills/postemi-templates/reference/hook-learning.md +296 -0
- package/skills/postemi-templates/reference/hook-lists.md +195 -0
- package/skills/postemi-templates/reference/hook-stories.md +282 -0
- package/skills/postemi-templates/reference/hook-talk.md +199 -0
- package/skills/postemi-templates/reference/index.md +251 -0
- package/skills/postemi-templates/reference/launch.md +145 -0
- package/skills/postemi-templates/reference/linkedin.md +449 -0
- package/skills/postemi-templates/reference/x.md +139 -0
- package/skills/postemi-writing/SKILL.md +310 -0
- package/skills/postemi-writing/reference/headline-patterns.md +109 -0
- package/skills/postemi-writing/reference/networks.md +113 -0
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
|
|
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)
|
|
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
|
-
|
|
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
|
|
88
|
+
var TIER_ALIASES = {
|
|
118
89
|
basic: "alap",
|
|
119
90
|
team: "csapat"
|
|
120
91
|
};
|
|
121
92
|
function tierForApi(tier) {
|
|
122
93
|
var _a;
|
|
123
|
-
|
|
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
|
|
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
|
|
495
|
-
var
|
|
496
|
-
var
|
|
497
|
-
var
|
|
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:
|
|
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:
|
|
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,
|
|
697
|
-
var CREDENTIALS_FILE = (0,
|
|
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,
|
|
703
|
-
const data = JSON.parse((0,
|
|
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,
|
|
712
|
-
(0,
|
|
974
|
+
if (!(0, import_fs3.existsSync)(CREDENTIALS_DIR)) {
|
|
975
|
+
(0, import_fs3.mkdirSync)(CREDENTIALS_DIR, { recursive: true, mode: 448 });
|
|
713
976
|
}
|
|
714
|
-
(0,
|
|
715
|
-
(0,
|
|
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,
|
|
719
|
-
(0,
|
|
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
|
|
734
|
-
const rl = (0,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
826
|
-
client_name: readEnv("CLIENT_NAME") || BRAND.clientName
|
|
827
|
-
|
|
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
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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:
|
|
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
|
|
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
|
|
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)} (${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1563
|
-
var
|
|
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
|
|
1567
|
-
if (!(0,
|
|
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,
|
|
1573
|
-
const filename = (0,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
2355
|
-
|
|
2459
|
+
"skills <command>",
|
|
2460
|
+
`Optional agent skills: install them into Claude Code, Codex, Cursor or Grok Build`,
|
|
2356
2461
|
(yargs2) => {
|
|
2357
|
-
|
|
2358
|
-
describe: "
|
|
2462
|
+
const target = (y) => y.positional("skills", {
|
|
2463
|
+
describe: "writing (with templates), growth, all, or core",
|
|
2359
2464
|
type: "string",
|
|
2360
|
-
|
|
2361
|
-
})
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
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
|
-
|
|
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 "
|
|
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
|
{},
|