@agendapanda/cli 0.3.2 → 0.4.1
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/README.md +110 -87
- package/dist/bin/ap.js +32 -0
- package/dist/bin/ap.js.map +1 -1
- package/dist/src/commands/alias.d.ts +2 -0
- package/dist/src/commands/alias.js +84 -0
- package/dist/src/commands/alias.js.map +1 -0
- package/dist/src/commands/auth.d.ts +4 -0
- package/dist/src/commands/auth.js +224 -20
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/calendar.js +8 -3
- package/dist/src/commands/calendar.js.map +1 -1
- package/dist/src/commands/completion.d.ts +2 -0
- package/dist/src/commands/completion.js +197 -0
- package/dist/src/commands/completion.js.map +1 -0
- package/dist/src/commands/connections.js +171 -26
- package/dist/src/commands/connections.js.map +1 -1
- package/dist/src/commands/context.js +10 -2
- package/dist/src/commands/context.js.map +1 -1
- package/dist/src/commands/init.d.ts +2 -0
- package/dist/src/commands/init.js +249 -0
- package/dist/src/commands/init.js.map +1 -0
- package/dist/src/commands/post.js +253 -9
- package/dist/src/commands/post.js.map +1 -1
- package/dist/src/commands/projects.js +8 -2
- package/dist/src/commands/projects.js.map +1 -1
- package/dist/src/index.js +24 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/config.d.ts +18 -0
- package/dist/src/lib/config.js +48 -0
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/errors.d.ts +4 -0
- package/dist/src/lib/errors.js +5 -0
- package/dist/src/lib/errors.js.map +1 -0
- package/dist/src/lib/platform-rules.js +2 -0
- package/dist/src/lib/platform-rules.js.map +1 -1
- package/dist/src/lib/poll.d.ts +11 -0
- package/dist/src/lib/poll.js +42 -0
- package/dist/src/lib/poll.js.map +1 -0
- package/dist/src/lib/update-check.d.ts +2 -0
- package/dist/src/lib/update-check.js +66 -0
- package/dist/src/lib/update-check.js.map +1 -0
- package/dist/src/lib/utils.d.ts +2 -0
- package/dist/src/lib/utils.js +27 -0
- package/dist/src/lib/utils.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { createInterface } from 'readline';
|
|
3
|
+
import { api, ApiError } from '../lib/api.js';
|
|
4
|
+
import { getApiKey, setApiKey, getActiveProject, setActiveProject, getActiveConnection, setActiveConnection, getConfigPath, } from '../lib/config.js';
|
|
5
|
+
import { outputError } from '../lib/output.js';
|
|
6
|
+
import { openInBrowser } from '../lib/utils.js';
|
|
7
|
+
import { pollForNewConnection } from '../lib/poll.js';
|
|
8
|
+
const PLATFORMS = [
|
|
9
|
+
{ id: 'twitter', label: 'X (Twitter)' },
|
|
10
|
+
{ id: 'linkedin', label: 'LinkedIn' },
|
|
11
|
+
{ id: 'facebook', label: 'Facebook Pages' },
|
|
12
|
+
{ id: 'instagram', label: 'Instagram' },
|
|
13
|
+
{ id: 'threads', label: 'Threads' },
|
|
14
|
+
{ id: 'bluesky', label: 'Bluesky' },
|
|
15
|
+
{ id: 'tiktok', label: 'TikTok' },
|
|
16
|
+
];
|
|
17
|
+
function prompt(rl, question) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export function registerInitCommand(program) {
|
|
23
|
+
program
|
|
24
|
+
.command('init')
|
|
25
|
+
.description('Guided first-run setup wizard')
|
|
26
|
+
.addHelpText('after', `
|
|
27
|
+
Examples:
|
|
28
|
+
ap init # start the interactive setup wizard`)
|
|
29
|
+
.action(async () => {
|
|
30
|
+
if (!process.stdin.isTTY) {
|
|
31
|
+
outputError('ap init requires an interactive terminal', 'NOT_INTERACTIVE');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
35
|
+
try {
|
|
36
|
+
// Welcome
|
|
37
|
+
console.log('');
|
|
38
|
+
console.log(chalk.bold('Welcome to Agenda Panda CLI'));
|
|
39
|
+
console.log(chalk.dim("Let's get you set up in 4 quick steps."));
|
|
40
|
+
console.log('');
|
|
41
|
+
// ── Step 1/4: Auth ──
|
|
42
|
+
console.log(chalk.bold('Step 1/4 — Authentication'));
|
|
43
|
+
let apiKey = getApiKey();
|
|
44
|
+
let authenticated = false;
|
|
45
|
+
if (apiKey) {
|
|
46
|
+
try {
|
|
47
|
+
const me = await api.get('/api/auth/me');
|
|
48
|
+
console.log(chalk.green('✓') + ` Already logged in as ${chalk.bold(me.user.name)} (${me.user.email})`);
|
|
49
|
+
authenticated = true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
console.log(chalk.yellow('Stored key is invalid. Let\'s re-authenticate.'));
|
|
53
|
+
apiKey = '';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!authenticated) {
|
|
57
|
+
const hasKey = await prompt(rl, 'Do you have an API key? (y/N) ');
|
|
58
|
+
if (hasKey.toLowerCase() !== 'y') {
|
|
59
|
+
console.log(chalk.dim('Opening agendapanda.com to get your API key...'));
|
|
60
|
+
await openInBrowser('https://agendapanda.com/settings?tab=security');
|
|
61
|
+
console.log(chalk.dim('Copy your API key from Settings > Security > API Keys'));
|
|
62
|
+
}
|
|
63
|
+
let valid = false;
|
|
64
|
+
while (!valid) {
|
|
65
|
+
const key = await prompt(rl, 'Enter your API key: ');
|
|
66
|
+
if (!key || !key.startsWith('ap_')) {
|
|
67
|
+
console.log(chalk.red('Invalid key format. Keys start with "ap_". Try again.'));
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const originalKey = getApiKey();
|
|
71
|
+
setApiKey(key);
|
|
72
|
+
try {
|
|
73
|
+
const me = await api.get('/api/auth/me');
|
|
74
|
+
console.log(chalk.green('✓') + ` Authenticated as ${chalk.bold(me.user.name)} (${me.user.email})`);
|
|
75
|
+
valid = true;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
setApiKey(originalKey);
|
|
79
|
+
console.log(chalk.red('Key validation failed. Check your key and try again.'));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
console.log('');
|
|
84
|
+
// ── Step 2/4: Workspace ──
|
|
85
|
+
console.log(chalk.bold('Step 2/4 — Workspace'));
|
|
86
|
+
let activeProject = getActiveProject();
|
|
87
|
+
if (activeProject) {
|
|
88
|
+
try {
|
|
89
|
+
const data = await api.get(`/api/projects/${activeProject}`);
|
|
90
|
+
console.log(chalk.green('✓') + ` Active workspace: ${chalk.bold(data.project.name)}`);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
console.log(chalk.yellow('Stored project not found. Let\'s pick one.'));
|
|
94
|
+
activeProject = '';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!activeProject) {
|
|
98
|
+
let projects = [];
|
|
99
|
+
try {
|
|
100
|
+
const data = await api.get('/api/projects');
|
|
101
|
+
projects = data.projects || [];
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
const msg = err instanceof ApiError ? err.message : String(err);
|
|
105
|
+
console.log(chalk.red(`Could not fetch workspaces: ${msg}`));
|
|
106
|
+
}
|
|
107
|
+
if (projects.length === 0) {
|
|
108
|
+
console.log(chalk.dim('No workspaces found. Opening browser to create one...'));
|
|
109
|
+
await openInBrowser('https://agendapanda.com');
|
|
110
|
+
console.log(chalk.dim('Create a workspace, then run `ap init` again.'));
|
|
111
|
+
rl.close();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
console.log('Your workspaces:');
|
|
115
|
+
projects.forEach((p, i) => {
|
|
116
|
+
console.log(` ${i + 1}) ${p.name} ${chalk.dim(`(${p.id.slice(0, 8)}...)`)}`);
|
|
117
|
+
});
|
|
118
|
+
let chosen;
|
|
119
|
+
while (!chosen) {
|
|
120
|
+
const answer = await prompt(rl, 'Select a workspace (number): ');
|
|
121
|
+
const idx = parseInt(answer, 10) - 1;
|
|
122
|
+
if (idx >= 0 && idx < projects.length) {
|
|
123
|
+
chosen = projects[idx];
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
console.log(chalk.red('Invalid selection. Try again.'));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
setActiveProject(chosen.id);
|
|
130
|
+
console.log(chalk.green('✓') + ` Active workspace set to ${chalk.bold(chosen.name)}`);
|
|
131
|
+
}
|
|
132
|
+
console.log('');
|
|
133
|
+
// ── Step 3/4: Connect Platform ──
|
|
134
|
+
console.log(chalk.bold('Step 3/4 — Connect a Platform'));
|
|
135
|
+
console.log('Which platform would you like to connect?');
|
|
136
|
+
PLATFORMS.forEach((p, i) => {
|
|
137
|
+
console.log(` ${i + 1}) ${p.label}`);
|
|
138
|
+
});
|
|
139
|
+
console.log(` ${PLATFORMS.length + 1}) ${chalk.dim('Skip for now')}`);
|
|
140
|
+
const platformAnswer = await prompt(rl, 'Select (number): ');
|
|
141
|
+
const platformIdx = parseInt(platformAnswer, 10) - 1;
|
|
142
|
+
let newConnection;
|
|
143
|
+
if (platformIdx >= 0 && platformIdx < PLATFORMS.length) {
|
|
144
|
+
const platform = PLATFORMS[platformIdx];
|
|
145
|
+
try {
|
|
146
|
+
// Snapshot existing connections
|
|
147
|
+
let existingIds;
|
|
148
|
+
try {
|
|
149
|
+
const before = await api.get('/api/connections');
|
|
150
|
+
existingIds = new Set(before.connections.filter((c) => c.platform === platform.id).map((c) => c.id));
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
existingIds = new Set();
|
|
154
|
+
}
|
|
155
|
+
const authUrl = await api.getRedirect(`/api/connections/connect/${encodeURIComponent(platform.id)}`);
|
|
156
|
+
await openInBrowser(authUrl);
|
|
157
|
+
console.log(chalk.dim(`Opened ${platform.label} authorization in browser...`));
|
|
158
|
+
console.log(chalk.dim('Waiting for connection... (press Ctrl+C to skip)'));
|
|
159
|
+
const { connection: conn, cancelled } = await pollForNewConnection(platform.id, existingIds);
|
|
160
|
+
if (cancelled) {
|
|
161
|
+
console.log(chalk.yellow('\nPolling cancelled. Run `ap connections add` later.'));
|
|
162
|
+
}
|
|
163
|
+
else if (conn) {
|
|
164
|
+
newConnection = conn;
|
|
165
|
+
const username = conn.platform_username || conn.platform_display_name || platform.id;
|
|
166
|
+
console.log(chalk.green('✓') + ` Connected as @${username} on ${platform.label}`);
|
|
167
|
+
setActiveConnection(conn.id);
|
|
168
|
+
console.log(chalk.dim(` Set as default connection (${conn.id.slice(0, 8)}...)`));
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
console.log(chalk.yellow('Connection not detected within 60s. Run `ap connections add` later.'));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
const msg = err instanceof ApiError ? err.message : String(err);
|
|
176
|
+
console.log(chalk.red(`Could not start OAuth: ${msg}`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
console.log(chalk.dim('Skipped. Run `ap connections add <platform>` when ready.'));
|
|
181
|
+
}
|
|
182
|
+
console.log('');
|
|
183
|
+
// ── Step 4/4: Test Post ──
|
|
184
|
+
console.log(chalk.bold('Step 4/4 — Test Post'));
|
|
185
|
+
const connectionId = getActiveConnection();
|
|
186
|
+
if (newConnection && connectionId) {
|
|
187
|
+
const sendTest = await prompt(rl, 'Send a test post to the connected platform? (y/N) ');
|
|
188
|
+
if (sendTest.toLowerCase() === 'y') {
|
|
189
|
+
const projectId = getActiveProject();
|
|
190
|
+
if (!projectId) {
|
|
191
|
+
console.log(chalk.yellow('No active project. Skipping test post.'));
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
try {
|
|
195
|
+
const data = await api.post(`/api/projects/${projectId}/posts`, {
|
|
196
|
+
content: 'Hello from Agenda Panda CLI! This is a test post.',
|
|
197
|
+
scheduled_at: new Date().toISOString(),
|
|
198
|
+
social_connection_id: connectionId,
|
|
199
|
+
post_now: true,
|
|
200
|
+
});
|
|
201
|
+
if (data.post.posted) {
|
|
202
|
+
console.log(chalk.green('✓') + ' Test post published successfully!');
|
|
203
|
+
}
|
|
204
|
+
else if (data.post.error) {
|
|
205
|
+
console.log(chalk.yellow('⚠') + ` Post created but failed: ${data.post.error}`);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
console.log(chalk.green('✓') + ' Test post scheduled.');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
const msg = err instanceof ApiError ? err.message : String(err);
|
|
213
|
+
console.log(chalk.red(`Test post failed: ${msg}`));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
console.log(chalk.dim('Skipped.'));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
console.log(chalk.dim('No connection set — skipping test post.'));
|
|
223
|
+
}
|
|
224
|
+
console.log('');
|
|
225
|
+
// ── Summary ──
|
|
226
|
+
console.log(chalk.bold('Setup complete! Here are some next steps:'));
|
|
227
|
+
console.log('');
|
|
228
|
+
console.log(` ${chalk.dim('$')} ap whoami ${chalk.dim('# check your auth status')}`);
|
|
229
|
+
console.log(` ${chalk.dim('$')} ap connections list ${chalk.dim('# see connected platforms')}`);
|
|
230
|
+
console.log(` ${chalk.dim('$')} ap post create --content "..." ${chalk.dim('# create a post')}`);
|
|
231
|
+
console.log(` ${chalk.dim('$')} ap calendar import --file cal.json ${chalk.dim('# bulk import posts')}`);
|
|
232
|
+
console.log(` ${chalk.dim('$')} ap context | claude "Write posts" ${chalk.dim('# AI-powered content')}`);
|
|
233
|
+
console.log('');
|
|
234
|
+
console.log(chalk.dim(`Config stored at ${getConfigPath()}`));
|
|
235
|
+
rl.close();
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
rl.close();
|
|
239
|
+
if (err instanceof ApiError) {
|
|
240
|
+
outputError(err.message, err.code);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
outputError(String(err));
|
|
244
|
+
}
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACN,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,GACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAEtD,MAAM,SAAS,GAAG;IACjB,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE;IACvC,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE;IACrC,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,gBAAgB,EAAE;IAC3C,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE;IACvC,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;IACnC,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;IACnC,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;CACxB,CAAC;AAEX,SAAS,MAAM,CAAC,EAAsC,EAAE,QAAgB;IACvE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IACnD,OAAO;SACL,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,+BAA+B,CAAC;SAC5C,WAAW,CAAC,OAAO,EAAE;;2EAEmD,CAAC;SACzE,MAAM,CAAC,KAAK,IAAI,EAAE;QAClB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YAC1B,WAAW,CAAC,0CAA0C,EAAE,iBAAiB,CAAC,CAAC;YAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;QAED,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAE7E,IAAI,CAAC;YACJ,UAAU;YACV,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC,CAAC;YACjE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAEhB,uBAAuB;YACvB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC;YACrD,IAAI,MAAM,GAAG,SAAS,EAAE,CAAC;YACzB,IAAI,aAAa,GAAG,KAAK,CAAC;YAE1B,IAAI,MAAM,EAAE,CAAC;gBACZ,IAAI,CAAC;oBACJ,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,CAAa,cAAc,CAAC,CAAC;oBACrD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,yBAAyB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;oBACvG,aAAa,GAAG,IAAI,CAAC;gBACtB,CAAC;gBAAC,MAAM,CAAC;oBACR,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,gDAAgD,CAAC,CAAC,CAAC;oBAC5E,MAAM,GAAG,EAAE,CAAC;gBACb,CAAC;YACF,CAAC;YAED,IAAI,CAAC,aAAa,EAAE,CAAC;gBACpB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,EAAE,EAAE,gCAAgC,CAAC,CAAC;gBAClE,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,EAAE,CAAC;oBAClC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC,CAAC;oBACzE,MAAM,aAAa,CAAC,+CAA+C,CAAC,CAAC;oBACrE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC,CAAC;gBACjF,CAAC;gBAED,IAAI,KAAK,GAAG,KAAK,CAAC;gBAClB,OAAO,CAAC,KAAK,EAAE,CAAC;oBACf,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,EAAE,EAAE,sBAAsB,CAAC,CAAC;oBACrD,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;wBACpC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC,CAAC;wBAChF,SAAS;oBACV,CAAC;oBAED,MAAM,WAAW,GAAG,SAAS,EAAE,CAAC;oBAChC,SAAS,CAAC,GAAG,CAAC,CAAC;oBACf,IAAI,CAAC;wBACJ,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,CAAa,cAAc,CAAC,CAAC;wBACrD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,qBAAqB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;wBACnG,KAAK,GAAG,IAAI,CAAC;oBACd,CAAC;oBAAC,MAAM,CAAC;wBACR,SAAS,CAAC,WAAW,CAAC,CAAC;wBACvB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,sDAAsD,CAAC,CAAC,CAAC;oBAChF,CAAC;gBACF,CAAC;YACF,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAEhB,4BAA4B;YAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC;YAChD,IAAI,aAAa,GAAG,gBAAgB,EAAE,CAAC;YAEvC,IAAI,aAAa,EAAE,CAAC;gBACnB,IAAI,CAAC;oBACJ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,GAAG,CAAuB,iBAAiB,aAAa,EAAE,CAAC,CAAC;oBACnF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,sBAAsB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACvF,CAAC;gBAAC,MAAM,CAAC;oBACR,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,4CAA4C,CAAC,CAAC,CAAC;oBACxE,aAAa,GAAG,EAAE,CAAC;gBACpB,CAAC;YACF,CAAC;YAED,IAAI,CAAC,aAAa,EAAE,CAAC;gBACpB,IAAI,QAAQ,GAAc,EAAE,CAAC;gBAC7B,IAAI,CAAC;oBACJ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,GAAG,CAA0B,eAAe,CAAC,CAAC;oBACrE,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;gBAChC,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,MAAM,GAAG,GAAG,GAAG,YAAY,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBAChE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,+BAA+B,GAAG,EAAE,CAAC,CAAC,CAAC;gBAC9D,CAAC;gBAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC3B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC,CAAC;oBAChF,MAAM,aAAa,CAAC,yBAAyB,CAAC,CAAC;oBAC/C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC,CAAC;oBACxE,EAAE,CAAC,KAAK,EAAE,CAAC;oBACX,OAAO;gBACR,CAAC;gBAED,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;gBAChC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;oBACzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBAC/E,CAAC,CAAC,CAAC;gBAEH,IAAI,MAA2B,CAAC;gBAChC,OAAO,CAAC,MAAM,EAAE,CAAC;oBAChB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,EAAE,EAAE,+BAA+B,CAAC,CAAC;oBACjE,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;oBACrC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;wBACvC,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;oBACxB,CAAC;yBAAM,CAAC;wBACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,CAAC;oBACzD,CAAC;gBACF,CAAC;gBAED,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,4BAA4B,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACvF,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAEhB,mCAAmC;YACnC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC,CAAC;YACzD,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;YACzD,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;gBAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;YACvC,CAAC,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,MAAM,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;YAEvE,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,EAAE,EAAE,mBAAmB,CAAC,CAAC;YAC7D,MAAM,WAAW,GAAG,QAAQ,CAAC,cAAc,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;YAErD,IAAI,aAAqC,CAAC;YAE1C,IAAI,WAAW,IAAI,CAAC,IAAI,WAAW,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC;gBACxD,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;gBAExC,IAAI,CAAC;oBACJ,gCAAgC;oBAChC,IAAI,WAAwB,CAAC;oBAC7B,IAAI,CAAC;wBACJ,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,CAAgC,kBAAkB,CAAC,CAAC;wBAChF,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;oBACtG,CAAC;oBAAC,MAAM,CAAC;wBACR,WAAW,GAAG,IAAI,GAAG,EAAE,CAAC;oBACzB,CAAC;oBAED,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,4BAA4B,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;oBACrG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;oBAC7B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,QAAQ,CAAC,KAAK,8BAA8B,CAAC,CAAC,CAAC;oBAC/E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC,CAAC;oBAE3E,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,MAAM,oBAAoB,CAAC,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;oBAE7F,IAAI,SAAS,EAAE,CAAC;wBACf,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sDAAsD,CAAC,CAAC,CAAC;oBACnF,CAAC;yBAAM,IAAI,IAAI,EAAE,CAAC;wBACjB,aAAa,GAAG,IAAI,CAAC;wBACrB,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,qBAAqB,IAAI,QAAQ,CAAC,EAAE,CAAC;wBACrF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,kBAAkB,QAAQ,OAAO,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;wBAClF,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;wBAC7B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,gCAAgC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;oBACnF,CAAC;yBAAM,CAAC;wBACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,qEAAqE,CAAC,CAAC,CAAC;oBAClG,CAAC;gBACF,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,MAAM,GAAG,GAAG,GAAG,YAAY,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;oBAChE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,0BAA0B,GAAG,EAAE,CAAC,CAAC,CAAC;gBACzD,CAAC;YACF,CAAC;iBAAM,CAAC;gBACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC,CAAC;YACpF,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAEhB,4BAA4B;YAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC;YAEhD,MAAM,YAAY,GAAG,mBAAmB,EAAE,CAAC;YAC3C,IAAI,aAAa,IAAI,YAAY,EAAE,CAAC;gBACnC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,EAAE,EAAE,oDAAoD,CAAC,CAAC;gBACxF,IAAI,QAAQ,CAAC,WAAW,EAAE,KAAK,GAAG,EAAE,CAAC;oBACpC,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;oBACrC,IAAI,CAAC,SAAS,EAAE,CAAC;wBAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,wCAAwC,CAAC,CAAC,CAAC;oBACrE,CAAC;yBAAM,CAAC;wBACP,IAAI,CAAC;4BACJ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAC1B,iBAAiB,SAAS,QAAQ,EAClC;gCACC,OAAO,EAAE,mDAAmD;gCAC5D,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gCACtC,oBAAoB,EAAE,YAAY;gCAClC,QAAQ,EAAE,IAAI;6BACd,CACD,CAAC;4BACF,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;gCACtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,oCAAoC,CAAC,CAAC;4BACtE,CAAC;iCAAM,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;gCAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,6BAA6B,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;4BACjF,CAAC;iCAAM,CAAC;gCACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,uBAAuB,CAAC,CAAC;4BACzD,CAAC;wBACF,CAAC;wBAAC,OAAO,GAAG,EAAE,CAAC;4BACd,MAAM,GAAG,GAAG,GAAG,YAAY,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;4BAChE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC,CAAC;wBACpD,CAAC;oBACF,CAAC;gBACF,CAAC;qBAAM,CAAC;oBACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;gBACpC,CAAC;YACF,CAAC;iBAAM,CAAC;gBACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC,CAAC;YACnE,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAEhB,gBAAgB;YAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC,CAAC;YACrE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,oCAAoC,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC,EAAE,CAAC,CAAC;YAC5G,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,qCAAqC,KAAK,CAAC,GAAG,CAAC,2BAA2B,CAAC,EAAE,CAAC,CAAC;YAC9G,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,qCAAqC,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;YACpG,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,wCAAwC,KAAK,CAAC,GAAG,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC;YAC3G,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,wCAAwC,KAAK,CAAC,GAAG,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC;YAC5G,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,oBAAoB,aAAa,EAAE,EAAE,CAAC,CAAC,CAAC;YAE9D,EAAE,CAAC,KAAK,EAAE,CAAC;QACZ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;gBAC7B,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACP,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1B,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC;IACF,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,10 +1,219 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { readFile } from 'fs/promises';
|
|
3
|
+
import { createInterface } from 'readline';
|
|
3
4
|
import { api, ApiError } from '../lib/api.js';
|
|
4
|
-
import { getActiveProject } from '../lib/config.js';
|
|
5
|
+
import { getActiveProject, getActiveConnection } from '../lib/config.js';
|
|
5
6
|
import { readStdin } from '../lib/input.js';
|
|
6
7
|
import { filenameFromPath, guessMimeTypeFromPath } from '../lib/media.js';
|
|
8
|
+
import { PLATFORM_RULES } from '../lib/platform-rules.js';
|
|
7
9
|
import { initJsonMode, isJsonMode, outputJson, outputTable, outputError, outputItems, outputMutation, } from '../lib/output.js';
|
|
10
|
+
import { API_KEY_URL_SUFFIX, MISSING_PROJECT_MSG, MISSING_CONNECTION_SUFFIX } from '../lib/errors.js';
|
|
11
|
+
function promptLine(rl, question) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function promptMultiline(rl, maxChars) {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const lines = [];
|
|
19
|
+
let currentLen = 0;
|
|
20
|
+
console.log(chalk.dim(` (max ${maxChars} chars · Ctrl+D when done)`));
|
|
21
|
+
const remaining = () => maxChars - currentLen;
|
|
22
|
+
const onLine = (line) => {
|
|
23
|
+
lines.push(line);
|
|
24
|
+
currentLen = lines.join('\n').length;
|
|
25
|
+
const rem = remaining();
|
|
26
|
+
process.stdout.write(chalk.dim(` [${rem} chars left] `) + '> ');
|
|
27
|
+
};
|
|
28
|
+
const onClose = () => {
|
|
29
|
+
rl.removeListener('line', onLine);
|
|
30
|
+
resolve(lines.join('\n').trim());
|
|
31
|
+
};
|
|
32
|
+
process.stdout.write('> ');
|
|
33
|
+
rl.on('line', onLine);
|
|
34
|
+
rl.once('close', onClose);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function interactivePostCreate(options) {
|
|
38
|
+
const openInterfaces = [];
|
|
39
|
+
const closeAll = () => {
|
|
40
|
+
for (const iface of openInterfaces) {
|
|
41
|
+
iface.close();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
45
|
+
openInterfaces.push(rl);
|
|
46
|
+
try {
|
|
47
|
+
// Resolve project first
|
|
48
|
+
const projectId = options.project || getActiveProject();
|
|
49
|
+
if (!projectId) {
|
|
50
|
+
outputError(MISSING_PROJECT_MSG, 'MISSING_PROJECT');
|
|
51
|
+
closeAll();
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
// Fetch connections
|
|
55
|
+
const data = await api.get('/api/connections');
|
|
56
|
+
const connectionsList = data.connections;
|
|
57
|
+
if (connectionsList.length === 0) {
|
|
58
|
+
outputError('No connections found. Run: ap connections add <platform>', 'NO_CONNECTIONS');
|
|
59
|
+
closeAll();
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
// 1. Select connection
|
|
63
|
+
console.log('');
|
|
64
|
+
console.log(chalk.bold('? Select connection:'));
|
|
65
|
+
connectionsList.forEach((c, i) => {
|
|
66
|
+
const name = c.platform_username
|
|
67
|
+
? `@${c.platform_username}`
|
|
68
|
+
: c.platform_display_name || c.platform;
|
|
69
|
+
const rule = PLATFORM_RULES[c.platform];
|
|
70
|
+
const label = rule ? rule.label : c.platform;
|
|
71
|
+
console.log(` ${i + 1}. ${label} (${name})`);
|
|
72
|
+
});
|
|
73
|
+
let selectedConnection;
|
|
74
|
+
while (!selectedConnection) {
|
|
75
|
+
const answer = await promptLine(rl, '> ');
|
|
76
|
+
const idx = parseInt(answer, 10) - 1;
|
|
77
|
+
if (idx >= 0 && idx < connectionsList.length) {
|
|
78
|
+
selectedConnection = connectionsList[idx];
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
console.log(chalk.red('Invalid selection. Try again.'));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const platform = selectedConnection.platform;
|
|
85
|
+
const rule = PLATFORM_RULES[platform];
|
|
86
|
+
const maxChars = rule ? rule.max_chars : 5000;
|
|
87
|
+
// 2. Content input
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log(chalk.bold('? Content:'));
|
|
90
|
+
const contentRl = createInterface({ input: process.stdin, output: process.stderr });
|
|
91
|
+
openInterfaces.push(contentRl);
|
|
92
|
+
const content = await promptMultiline(contentRl, maxChars);
|
|
93
|
+
contentRl.close();
|
|
94
|
+
if (!content) {
|
|
95
|
+
outputError('Post content cannot be empty.', 'MISSING_CONTENT');
|
|
96
|
+
closeAll();
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
if (content.length > maxChars) {
|
|
100
|
+
outputError(`Content exceeds ${maxChars} char limit for ${platform} (${content.length} chars).`, 'CONTENT_TOO_LONG');
|
|
101
|
+
closeAll();
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
// Re-create readline after multiline consumed the previous one
|
|
105
|
+
const rl2 = createInterface({ input: process.stdin, output: process.stderr });
|
|
106
|
+
openInterfaces.push(rl2);
|
|
107
|
+
// 3. When to post
|
|
108
|
+
console.log('');
|
|
109
|
+
console.log(chalk.bold('? When to post?'));
|
|
110
|
+
console.log(' 1. Now');
|
|
111
|
+
console.log(' 2. Schedule for later');
|
|
112
|
+
let postNow = true;
|
|
113
|
+
let scheduledAt = new Date().toISOString();
|
|
114
|
+
let validWhen = false;
|
|
115
|
+
while (!validWhen) {
|
|
116
|
+
const answer = await promptLine(rl2, '> ');
|
|
117
|
+
if (answer === '1') {
|
|
118
|
+
postNow = true;
|
|
119
|
+
validWhen = true;
|
|
120
|
+
}
|
|
121
|
+
else if (answer === '2') {
|
|
122
|
+
postNow = false;
|
|
123
|
+
validWhen = true;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
console.log(chalk.red('Enter 1 or 2.'));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (!postNow) {
|
|
130
|
+
let validDate = false;
|
|
131
|
+
while (!validDate) {
|
|
132
|
+
const dateStr = await promptLine(rl2, chalk.bold('? Date (YYYY-MM-DD): '));
|
|
133
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
|
134
|
+
// Validate the date is a real calendar date
|
|
135
|
+
const parsedDate = new Date(`${dateStr}T00:00:00Z`);
|
|
136
|
+
if (isNaN(parsedDate.getTime()) || parsedDate.toISOString().slice(0, 10) !== dateStr) {
|
|
137
|
+
console.log(chalk.red('Invalid date. Use a valid calendar date (e.g. 2026-03-01).'));
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
let validTime = false;
|
|
141
|
+
while (!validTime) {
|
|
142
|
+
const timeStr = await promptLine(rl2, chalk.bold('? Time (HH:MM UTC): '));
|
|
143
|
+
if (/^\d{2}:\d{2}$/.test(timeStr)) {
|
|
144
|
+
const [hours, minutes] = timeStr.split(':').map(Number);
|
|
145
|
+
if (hours > 23 || minutes > 59) {
|
|
146
|
+
console.log(chalk.red('Invalid time. Hours must be 00-23, minutes 00-59.'));
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
scheduledAt = `${dateStr}T${timeStr}:00Z`;
|
|
150
|
+
// Validate the scheduled time is in the future
|
|
151
|
+
if (new Date(scheduledAt) <= new Date()) {
|
|
152
|
+
console.log(chalk.red('Scheduled time must be in the future.'));
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
validTime = true;
|
|
156
|
+
validDate = true;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
console.log(chalk.red('Invalid time format. Use HH:MM (e.g. 14:00).'));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
console.log(chalk.red('Invalid date format. Use YYYY-MM-DD (e.g. 2026-03-01).'));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
rl2.close();
|
|
169
|
+
// Upload media if provided
|
|
170
|
+
let mediaUrl;
|
|
171
|
+
const mediaPath = options.media || options.image;
|
|
172
|
+
if (mediaPath) {
|
|
173
|
+
const mimeType = guessMimeTypeFromPath(mediaPath);
|
|
174
|
+
if (!mimeType) {
|
|
175
|
+
outputError('Unsupported media extension. Use jpg/jpeg/png/gif/webp/mp4/mov/webm/m4v.', 'UNSUPPORTED_MEDIA_TYPE');
|
|
176
|
+
closeAll();
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
const mediaData = await readFile(mediaPath);
|
|
180
|
+
const filename = filenameFromPath(mediaPath);
|
|
181
|
+
const blob = new Blob([mediaData], { type: mimeType });
|
|
182
|
+
const formData = new FormData();
|
|
183
|
+
formData.append('file', blob, filename);
|
|
184
|
+
const uploadResult = await api.upload('/api/media/upload', formData);
|
|
185
|
+
mediaUrl = uploadResult.url;
|
|
186
|
+
}
|
|
187
|
+
// Create post
|
|
188
|
+
const result = await api.post(`/api/projects/${projectId}/posts`, {
|
|
189
|
+
content,
|
|
190
|
+
scheduled_at: scheduledAt,
|
|
191
|
+
social_connection_id: selectedConnection.id,
|
|
192
|
+
media_url: mediaUrl,
|
|
193
|
+
post_now: postNow,
|
|
194
|
+
});
|
|
195
|
+
const connName = selectedConnection.platform_username
|
|
196
|
+
? `@${selectedConnection.platform_username}`
|
|
197
|
+
: selectedConnection.platform_display_name || selectedConnection.platform;
|
|
198
|
+
const platformLabel = rule ? rule.label : selectedConnection.platform;
|
|
199
|
+
if (result.post.posted) {
|
|
200
|
+
console.log('');
|
|
201
|
+
console.log(chalk.green('✓') + ` Posted to ${platformLabel} (${connName})`);
|
|
202
|
+
}
|
|
203
|
+
else if (result.post.error) {
|
|
204
|
+
console.log('');
|
|
205
|
+
console.log(chalk.yellow('⚠') + ` Post created but failed to publish: ${result.post.error}`);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
console.log('');
|
|
209
|
+
console.log(chalk.green('✓') + ` Scheduled for ${scheduledAt}`);
|
|
210
|
+
}
|
|
211
|
+
console.log(chalk.dim(` ID: ${result.post.id}`));
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
closeAll();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
8
217
|
export function registerPostCommands(program) {
|
|
9
218
|
const post = program.command('post').description('Create and manage posts');
|
|
10
219
|
// ap post create
|
|
@@ -13,13 +222,36 @@ export function registerPostCommands(program) {
|
|
|
13
222
|
.description('Create a new post')
|
|
14
223
|
.option('--content <text>', 'Post content')
|
|
15
224
|
.option('--connection <id>', 'Social connection ID (required)')
|
|
16
|
-
.option('--schedule <datetime>', 'Schedule
|
|
225
|
+
.option('--schedule <datetime>', 'Schedule time in UTC ISO 8601 (e.g. 2026-03-01T14:00:00Z)')
|
|
17
226
|
.option('--now', 'Post immediately (default if no --schedule)')
|
|
18
227
|
.option('--media <path>', 'Upload and attach media (image/video)')
|
|
19
228
|
.option('--image <path>', 'Deprecated alias for --media')
|
|
20
229
|
.option('--project <id>', 'Override active project')
|
|
21
230
|
.option('--json', 'Output as JSON')
|
|
231
|
+
.addHelpText('after', `
|
|
232
|
+
Examples:
|
|
233
|
+
ap post create --content "Hello world" --now # post immediately
|
|
234
|
+
ap post create --content "Scheduled!" --schedule 2026-03-01T14:00:00Z
|
|
235
|
+
ap post create --content "With image" --media photo.jpg --now
|
|
236
|
+
echo "Piped content" | ap post create --now # pipe from stdin`)
|
|
22
237
|
.action(async (options) => {
|
|
238
|
+
// Interactive mode: TTY + no content/connection/schedule flags
|
|
239
|
+
const hasInteractiveFlags = options.content || options.connection || options.schedule || options.now;
|
|
240
|
+
if (process.stdout.isTTY && process.stdin.isTTY && !hasInteractiveFlags && !options.json) {
|
|
241
|
+
try {
|
|
242
|
+
await interactivePostCreate(options);
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
if (err instanceof ApiError) {
|
|
246
|
+
outputError(err.message, err.code);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
outputError(String(err));
|
|
250
|
+
}
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
23
255
|
initJsonMode(options);
|
|
24
256
|
try {
|
|
25
257
|
// Resolve content
|
|
@@ -34,15 +266,16 @@ export function registerPostCommands(program) {
|
|
|
34
266
|
outputError('Post content is required. Use --content or pipe via stdin.', 'MISSING_CONTENT');
|
|
35
267
|
process.exit(1);
|
|
36
268
|
}
|
|
37
|
-
// Resolve connection
|
|
38
|
-
|
|
39
|
-
|
|
269
|
+
// Resolve connection — flag → config/env → error
|
|
270
|
+
const connectionId = options.connection || getActiveConnection();
|
|
271
|
+
if (!connectionId) {
|
|
272
|
+
outputError('--connection is required' + MISSING_CONNECTION_SUFFIX, 'MISSING_CONNECTION');
|
|
40
273
|
process.exit(1);
|
|
41
274
|
}
|
|
42
275
|
// Resolve project
|
|
43
276
|
const projectId = options.project || getActiveProject();
|
|
44
277
|
if (!projectId) {
|
|
45
|
-
outputError(
|
|
278
|
+
outputError(MISSING_PROJECT_MSG, 'MISSING_PROJECT');
|
|
46
279
|
process.exit(1);
|
|
47
280
|
}
|
|
48
281
|
// Upload media if provided
|
|
@@ -68,7 +301,7 @@ export function registerPostCommands(program) {
|
|
|
68
301
|
const data = await api.post(`/api/projects/${projectId}/posts`, {
|
|
69
302
|
content,
|
|
70
303
|
scheduled_at: scheduledAt,
|
|
71
|
-
social_connection_id:
|
|
304
|
+
social_connection_id: connectionId,
|
|
72
305
|
media_url: mediaUrl,
|
|
73
306
|
post_now: postNow,
|
|
74
307
|
});
|
|
@@ -90,7 +323,14 @@ export function registerPostCommands(program) {
|
|
|
90
323
|
}
|
|
91
324
|
catch (err) {
|
|
92
325
|
if (err instanceof ApiError) {
|
|
93
|
-
|
|
326
|
+
let msg = err.message;
|
|
327
|
+
if (err.code === 'MISSING_PROJECT' || msg.includes('No project set')) {
|
|
328
|
+
msg = MISSING_PROJECT_MSG;
|
|
329
|
+
}
|
|
330
|
+
if (err.code === 'INVALID_KEY' || msg.includes('Invalid API key')) {
|
|
331
|
+
msg += API_KEY_URL_SUFFIX;
|
|
332
|
+
}
|
|
333
|
+
outputError(msg, err.code);
|
|
94
334
|
}
|
|
95
335
|
else {
|
|
96
336
|
outputError(String(err));
|
|
@@ -103,6 +343,10 @@ export function registerPostCommands(program) {
|
|
|
103
343
|
.command('list')
|
|
104
344
|
.description('List all posts')
|
|
105
345
|
.option('--json', 'Output as JSON')
|
|
346
|
+
.addHelpText('after', `
|
|
347
|
+
Examples:
|
|
348
|
+
ap post list # show all posts in a table
|
|
349
|
+
ap post list --json # JSON output for scripting`)
|
|
106
350
|
.action(async (options) => {
|
|
107
351
|
initJsonMode(options);
|
|
108
352
|
try {
|
|
@@ -244,7 +488,7 @@ export function registerPostCommands(program) {
|
|
|
244
488
|
.command('update <id>')
|
|
245
489
|
.description('Update a post')
|
|
246
490
|
.option('--content <text>', 'New content')
|
|
247
|
-
.option('--schedule <datetime>', '
|
|
491
|
+
.option('--schedule <datetime>', 'Schedule time in UTC ISO 8601 (e.g. 2026-03-01T14:00:00Z)')
|
|
248
492
|
.option('--media <path>', 'Upload and attach media (image/video)')
|
|
249
493
|
.option('--image <path>', 'Deprecated alias for --media')
|
|
250
494
|
.option('--remove-media', 'Remove attached media')
|