@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.
Files changed (45) hide show
  1. package/README.md +110 -87
  2. package/dist/bin/ap.js +32 -0
  3. package/dist/bin/ap.js.map +1 -1
  4. package/dist/src/commands/alias.d.ts +2 -0
  5. package/dist/src/commands/alias.js +84 -0
  6. package/dist/src/commands/alias.js.map +1 -0
  7. package/dist/src/commands/auth.d.ts +4 -0
  8. package/dist/src/commands/auth.js +224 -20
  9. package/dist/src/commands/auth.js.map +1 -1
  10. package/dist/src/commands/calendar.js +8 -3
  11. package/dist/src/commands/calendar.js.map +1 -1
  12. package/dist/src/commands/completion.d.ts +2 -0
  13. package/dist/src/commands/completion.js +197 -0
  14. package/dist/src/commands/completion.js.map +1 -0
  15. package/dist/src/commands/connections.js +171 -26
  16. package/dist/src/commands/connections.js.map +1 -1
  17. package/dist/src/commands/context.js +10 -2
  18. package/dist/src/commands/context.js.map +1 -1
  19. package/dist/src/commands/init.d.ts +2 -0
  20. package/dist/src/commands/init.js +249 -0
  21. package/dist/src/commands/init.js.map +1 -0
  22. package/dist/src/commands/post.js +253 -9
  23. package/dist/src/commands/post.js.map +1 -1
  24. package/dist/src/commands/projects.js +8 -2
  25. package/dist/src/commands/projects.js.map +1 -1
  26. package/dist/src/index.js +24 -2
  27. package/dist/src/index.js.map +1 -1
  28. package/dist/src/lib/config.d.ts +18 -0
  29. package/dist/src/lib/config.js +48 -0
  30. package/dist/src/lib/config.js.map +1 -1
  31. package/dist/src/lib/errors.d.ts +4 -0
  32. package/dist/src/lib/errors.js +5 -0
  33. package/dist/src/lib/errors.js.map +1 -0
  34. package/dist/src/lib/platform-rules.js +2 -0
  35. package/dist/src/lib/platform-rules.js.map +1 -1
  36. package/dist/src/lib/poll.d.ts +11 -0
  37. package/dist/src/lib/poll.js +42 -0
  38. package/dist/src/lib/poll.js.map +1 -0
  39. package/dist/src/lib/update-check.d.ts +2 -0
  40. package/dist/src/lib/update-check.js +66 -0
  41. package/dist/src/lib/update-check.js.map +1 -0
  42. package/dist/src/lib/utils.d.ts +2 -0
  43. package/dist/src/lib/utils.js +27 -0
  44. package/dist/src/lib/utils.js.map +1 -0
  45. 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":"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"}
@@ -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 for later (ISO datetime)')
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
- if (!options.connection) {
39
- outputError('--connection is required', 'MISSING_CONNECTION');
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('No project set. Use --project, AP_PROJECT env, or `ap projects use <id>`.', 'MISSING_PROJECT');
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: options.connection,
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
- outputError(err.message, err.code);
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>', 'New schedule (ISO 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')