postemi 1.0.1 → 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 +51 -5
- package/SKILL.md +83 -30
- package/dist/index.js +389 -29
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
- 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
|
@@ -421,10 +421,10 @@ var PostemiAPI = class {
|
|
|
421
421
|
};
|
|
422
422
|
|
|
423
423
|
// src/commands/auth.ts
|
|
424
|
-
var
|
|
425
|
-
var
|
|
426
|
-
var
|
|
427
|
-
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");
|
|
428
428
|
|
|
429
429
|
// src/settings.ts
|
|
430
430
|
var import_fs = require("fs");
|
|
@@ -450,6 +450,9 @@ function getSetting(key) {
|
|
|
450
450
|
return typeof value === "string" && value ? value : void 0;
|
|
451
451
|
}
|
|
452
452
|
function saveSetting(key, value) {
|
|
453
|
+
saveRawSetting(key, value);
|
|
454
|
+
}
|
|
455
|
+
function saveRawSetting(key, value) {
|
|
453
456
|
if (!(0, import_fs.existsSync)(SETTINGS_DIR)) {
|
|
454
457
|
(0, import_fs.mkdirSync)(SETTINGS_DIR, { recursive: true, mode: 448 });
|
|
455
458
|
}
|
|
@@ -622,15 +625,345 @@ function resolveDateInput(input, timezone, flagName = "--date") {
|
|
|
622
625
|
return { iso: instant.toISOString(), appliedTimezone: timezone };
|
|
623
626
|
}
|
|
624
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
|
+
|
|
625
958
|
// src/commands/auth.ts
|
|
626
|
-
var CREDENTIALS_DIR = (0,
|
|
627
|
-
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");
|
|
628
961
|
var DEFAULT_AUTH_SERVER = BRAND.defaultAuthServer;
|
|
629
962
|
var DEFAULT_API_URL = BRAND.defaultApiUrl;
|
|
630
963
|
function loadCredentials() {
|
|
631
964
|
try {
|
|
632
|
-
if (!(0,
|
|
633
|
-
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"));
|
|
634
967
|
if (!data.accessToken) return null;
|
|
635
968
|
return data;
|
|
636
969
|
} catch (e) {
|
|
@@ -638,15 +971,15 @@ function loadCredentials() {
|
|
|
638
971
|
}
|
|
639
972
|
}
|
|
640
973
|
function saveCredentials(credentials) {
|
|
641
|
-
if (!(0,
|
|
642
|
-
(0,
|
|
974
|
+
if (!(0, import_fs3.existsSync)(CREDENTIALS_DIR)) {
|
|
975
|
+
(0, import_fs3.mkdirSync)(CREDENTIALS_DIR, { recursive: true, mode: 448 });
|
|
643
976
|
}
|
|
644
|
-
(0,
|
|
645
|
-
(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);
|
|
646
979
|
}
|
|
647
980
|
function deleteCredentials() {
|
|
648
|
-
if ((0,
|
|
649
|
-
(0,
|
|
981
|
+
if ((0, import_fs3.existsSync)(CREDENTIALS_FILE)) {
|
|
982
|
+
(0, import_fs3.unlinkSync)(CREDENTIALS_FILE);
|
|
650
983
|
}
|
|
651
984
|
}
|
|
652
985
|
function openBrowser(url) {
|
|
@@ -660,8 +993,8 @@ function openBrowser(url) {
|
|
|
660
993
|
function sleep(ms) {
|
|
661
994
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
662
995
|
}
|
|
663
|
-
function
|
|
664
|
-
const rl = (0,
|
|
996
|
+
function ask2(question) {
|
|
997
|
+
const rl = (0, import_readline2.createInterface)({ input: process.stdin, output: process.stderr });
|
|
665
998
|
return new Promise(
|
|
666
999
|
(resolve) => rl.question(question, (answer) => {
|
|
667
1000
|
rl.close();
|
|
@@ -673,11 +1006,11 @@ async function maybeConfigureTimezone() {
|
|
|
673
1006
|
if (getSetting("timezone")) return;
|
|
674
1007
|
if (!process.stdin.isTTY || !process.stderr.isTTY) return;
|
|
675
1008
|
const detected = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
676
|
-
const answer = (await
|
|
1009
|
+
const answer = (await ask2(`
|
|
677
1010
|
Scheduling timezone: ${detected} \u2014 OK? [Y/n] `)).trim().toLowerCase();
|
|
678
1011
|
let timezone = detected;
|
|
679
1012
|
if (answer === "n" || answer === "no") {
|
|
680
|
-
const typed = (await
|
|
1013
|
+
const typed = (await ask2(`Enter an IANA timezone name (e.g. ${BRAND.exampleTimezone}), or leave empty to skip: `)).trim();
|
|
681
1014
|
if (!typed) {
|
|
682
1015
|
console.error(
|
|
683
1016
|
`Skipped. Dates without an explicit offset will be refused until you run: ${BRAND.cli} config:set timezone <IANA name>`
|
|
@@ -728,7 +1061,7 @@ async function chooseWorkspace(creds) {
|
|
|
728
1061
|
);
|
|
729
1062
|
return;
|
|
730
1063
|
}
|
|
731
|
-
const answer = (await
|
|
1064
|
+
const answer = (await ask2(`Work in which one? [1-${workspaces.length}] `)).trim();
|
|
732
1065
|
const index = Number(answer) - 1;
|
|
733
1066
|
const chosen = workspaces[index];
|
|
734
1067
|
if (!chosen) {
|
|
@@ -809,6 +1142,7 @@ async function authLogin(argv) {
|
|
|
809
1142
|
console.log(`\u{1F4C1} Credentials saved to ${CREDENTIALS_FILE}`);
|
|
810
1143
|
await chooseWorkspace(creds);
|
|
811
1144
|
await maybeConfigureTimezone();
|
|
1145
|
+
await offerSkillsAfterLogin(argv);
|
|
812
1146
|
return;
|
|
813
1147
|
}
|
|
814
1148
|
if (data.error === "authorization_pending") {
|
|
@@ -910,7 +1244,7 @@ async function authSignup(argv) {
|
|
|
910
1244
|
});
|
|
911
1245
|
return;
|
|
912
1246
|
}
|
|
913
|
-
code = (await
|
|
1247
|
+
code = (await ask2("Code from the e-mail: ")).trim();
|
|
914
1248
|
if (!code) {
|
|
915
1249
|
console.error("\u274C No code given. Run the command again with --code <code>.");
|
|
916
1250
|
process.exit(1);
|
|
@@ -926,7 +1260,7 @@ async function authSignup(argv) {
|
|
|
926
1260
|
status(` Privacy notice: ${PRIVACY_URL}`);
|
|
927
1261
|
let accepted = !!argv.acceptTerms;
|
|
928
1262
|
if (!accepted && interactive) {
|
|
929
|
-
const answer = (await
|
|
1263
|
+
const answer = (await ask2("Do you agree to both? [y/N] ")).trim().toLowerCase();
|
|
930
1264
|
accepted = answer === "y" || answer === "yes" || answer === "i" || answer === "igen";
|
|
931
1265
|
}
|
|
932
1266
|
if (!accepted) {
|
|
@@ -1150,7 +1484,7 @@ function getConfig() {
|
|
|
1150
1484
|
}
|
|
1151
1485
|
|
|
1152
1486
|
// src/commands/posts.ts
|
|
1153
|
-
var
|
|
1487
|
+
var import_fs4 = require("fs");
|
|
1154
1488
|
var pairContentsWithMedia = (argv) => {
|
|
1155
1489
|
const read = (token, next, long, short) => {
|
|
1156
1490
|
if (token === `--${long}` || token === `-${short}`) {
|
|
@@ -1240,11 +1574,11 @@ async function createPost(args) {
|
|
|
1240
1574
|
if (args.json) {
|
|
1241
1575
|
try {
|
|
1242
1576
|
const jsonPath = args.json;
|
|
1243
|
-
if (!(0,
|
|
1577
|
+
if (!(0, import_fs4.existsSync)(jsonPath)) {
|
|
1244
1578
|
console.error(`\u274C JSON file not found: ${jsonPath}`);
|
|
1245
1579
|
process.exit(1);
|
|
1246
1580
|
}
|
|
1247
|
-
const jsonContent = (0,
|
|
1581
|
+
const jsonContent = (0, import_fs4.readFileSync)(jsonPath, "utf-8");
|
|
1248
1582
|
postData = JSON.parse(jsonContent);
|
|
1249
1583
|
} catch (error) {
|
|
1250
1584
|
console.error(`\u274C Failed to parse JSON file: ${error.message}`);
|
|
@@ -1490,18 +1824,18 @@ async function getPostAnalytics(args) {
|
|
|
1490
1824
|
}
|
|
1491
1825
|
|
|
1492
1826
|
// src/commands/upload.ts
|
|
1493
|
-
var
|
|
1494
|
-
var
|
|
1827
|
+
var import_fs5 = require("fs");
|
|
1828
|
+
var import_path5 = require("path");
|
|
1495
1829
|
async function uploadFile(args) {
|
|
1496
1830
|
const config = getConfig();
|
|
1497
1831
|
const api = new PostemiAPI(config);
|
|
1498
|
-
if (!(0,
|
|
1832
|
+
if (!(0, import_fs5.existsSync)(args.file)) {
|
|
1499
1833
|
console.error(`\u274C File not found: ${args.file}`);
|
|
1500
1834
|
process.exit(1);
|
|
1501
1835
|
}
|
|
1502
1836
|
try {
|
|
1503
|
-
const fileBuffer = (0,
|
|
1504
|
-
const filename = (0,
|
|
1837
|
+
const fileBuffer = (0, import_fs5.readFileSync)(args.file);
|
|
1838
|
+
const filename = (0, import_path5.basename)(args.file) || "file";
|
|
1505
1839
|
const res = await api.upload(fileBuffer, filename);
|
|
1506
1840
|
status("\u2705 File uploaded successfully!");
|
|
1507
1841
|
result(res);
|
|
@@ -2121,6 +2455,28 @@ async function billingManage(args) {
|
|
|
2121
2455
|
});
|
|
2122
2456
|
},
|
|
2123
2457
|
billingManage
|
|
2458
|
+
).command(
|
|
2459
|
+
"skills <command>",
|
|
2460
|
+
`Optional agent skills: install them into Claude Code, Codex, Cursor or Grok Build`,
|
|
2461
|
+
(yargs2) => {
|
|
2462
|
+
const target = (y) => y.positional("skills", {
|
|
2463
|
+
describe: "writing (with templates), growth, all, or core",
|
|
2464
|
+
type: "string",
|
|
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`);
|
|
2477
|
+
},
|
|
2478
|
+
() => {
|
|
2479
|
+
}
|
|
2124
2480
|
).command(
|
|
2125
2481
|
"config:set <key> <value>",
|
|
2126
2482
|
`Save a CLI setting to ~/${BRAND.configDirName}/config.json`,
|
|
@@ -2154,6 +2510,10 @@ async function billingManage(args) {
|
|
|
2154
2510
|
return yargs2.option("auth-server", {
|
|
2155
2511
|
describe: `Auth server URL (default: ${BRAND.defaultApiUrl})`,
|
|
2156
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
|
|
2157
2517
|
}).example(
|
|
2158
2518
|
"$0 auth:login",
|
|
2159
2519
|
"Login via OAuth2 device flow"
|