launch-ih 1.0.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.
Files changed (68) hide show
  1. package/README.md +117 -0
  2. package/dist/browser.d.ts +20 -0
  3. package/dist/browser.js +86 -0
  4. package/dist/browser.js.map +1 -0
  5. package/dist/cli/commands/draft.command.d.ts +10 -0
  6. package/dist/cli/commands/draft.command.js +32 -0
  7. package/dist/cli/commands/draft.command.js.map +1 -0
  8. package/dist/cli/commands/history.command.d.ts +9 -0
  9. package/dist/cli/commands/history.command.js +30 -0
  10. package/dist/cli/commands/history.command.js.map +1 -0
  11. package/dist/cli/commands/login.command.d.ts +10 -0
  12. package/dist/cli/commands/login.command.js +64 -0
  13. package/dist/cli/commands/login.command.js.map +1 -0
  14. package/dist/cli/commands/new.command.d.ts +17 -0
  15. package/dist/cli/commands/new.command.js +151 -0
  16. package/dist/cli/commands/new.command.js.map +1 -0
  17. package/dist/cli/commands/preview.command.d.ts +13 -0
  18. package/dist/cli/commands/preview.command.js +48 -0
  19. package/dist/cli/commands/preview.command.js.map +1 -0
  20. package/dist/cli/commands/publish.command.d.ts +12 -0
  21. package/dist/cli/commands/publish.command.js +70 -0
  22. package/dist/cli/commands/publish.command.js.map +1 -0
  23. package/dist/cli/commands/status.command.d.ts +9 -0
  24. package/dist/cli/commands/status.command.js +47 -0
  25. package/dist/cli/commands/status.command.js.map +1 -0
  26. package/dist/cli/index.d.ts +2 -0
  27. package/dist/cli/index.js +168 -0
  28. package/dist/cli/index.js.map +1 -0
  29. package/dist/cli/ui/editor.d.ts +1 -0
  30. package/dist/cli/ui/editor.js +39 -0
  31. package/dist/cli/ui/editor.js.map +1 -0
  32. package/dist/cli/ui/output.d.ts +23 -0
  33. package/dist/cli/ui/output.js +96 -0
  34. package/dist/cli/ui/output.js.map +1 -0
  35. package/dist/cli/ui/prompts.d.ts +11 -0
  36. package/dist/cli/ui/prompts.js +28 -0
  37. package/dist/cli/ui/prompts.js.map +1 -0
  38. package/dist/cli/utils/errors.d.ts +20 -0
  39. package/dist/cli/utils/errors.js +48 -0
  40. package/dist/cli/utils/errors.js.map +1 -0
  41. package/dist/cli/utils/session-helpers.d.ts +4 -0
  42. package/dist/cli/utils/session-helpers.js +37 -0
  43. package/dist/cli/utils/session-helpers.js.map +1 -0
  44. package/dist/cli.d.ts +2 -0
  45. package/dist/cli.js +6 -0
  46. package/dist/cli.js.map +1 -0
  47. package/dist/env.d.ts +10 -0
  48. package/dist/env.js +27 -0
  49. package/dist/env.js.map +1 -0
  50. package/dist/ih-auth.d.ts +32 -0
  51. package/dist/ih-auth.js +240 -0
  52. package/dist/ih-auth.js.map +1 -0
  53. package/dist/ih-poster.d.ts +21 -0
  54. package/dist/ih-poster.js +217 -0
  55. package/dist/ih-poster.js.map +1 -0
  56. package/dist/launch-workflow.d.ts +17 -0
  57. package/dist/launch-workflow.js +80 -0
  58. package/dist/launch-workflow.js.map +1 -0
  59. package/dist/post-drafter.d.ts +6 -0
  60. package/dist/post-drafter.js +103 -0
  61. package/dist/post-drafter.js.map +1 -0
  62. package/dist/types.d.ts +55 -0
  63. package/dist/types.js +5 -0
  64. package/dist/types.js.map +1 -0
  65. package/dist/utils.d.ts +21 -0
  66. package/dist/utils.js +54 -0
  67. package/dist/utils.js.map +1 -0
  68. package/package.json +52 -0
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # LAUNCH — Ship your product to Indie Hackers from the terminal
2
+
3
+ ![LAUNCH Demo](demo.gif) — *Add a terminal recording here*
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npx launch-ih
9
+ # or
10
+ npm install -g launch-ih
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```
16
+ launch login → Log into Indie Hackers and save session
17
+ launch new → Create a new product launch session
18
+ launch draft → Generate or edit a post draft
19
+ launch preview → Dry-run preview in browser
20
+ launch publish → Submit the approved draft
21
+ ```
22
+
23
+ ## Full Command Reference
24
+
25
+ All commands support `--json` (global flag) for JSON output mode.
26
+
27
+ ### `launch login`
28
+
29
+ Log into Indie Hackers and save session.
30
+
31
+ | Flag | Description |
32
+ |------|-------------|
33
+ | `--yes` | Skip interactive prompts |
34
+
35
+ ### `launch new`
36
+
37
+ Create a new product launch session.
38
+
39
+ | Flag | Description |
40
+ |------|-------------|
41
+ | `--yes` | Skip confirmation prompts |
42
+ | `--name <name>` | Product name |
43
+ | `--tagline <tagline>` | Product tagline |
44
+ | `--problem <problem>` | Problem it solves |
45
+ | `--features <features>` | Comma-separated features |
46
+ | `--link <url>` | Product URL |
47
+ | `--audience <audience>` | Target audience |
48
+ | `--stage <stage>` | `pre-launch`, `just-launched`, or `growing` |
49
+ | `--resume` | Resume existing drafting session |
50
+
51
+ ### `launch draft`
52
+
53
+ Generate or edit a post draft.
54
+
55
+ | Flag | Description |
56
+ |------|-------------|
57
+ | `--yes` | Skip editor prompt |
58
+ | `--session <id>` | Session ID |
59
+
60
+ ### `launch preview`
61
+
62
+ Dry-run preview: fill Indie Hackers form and take a screenshot. Never submits.
63
+
64
+ | Flag | Description |
65
+ |------|-------------|
66
+ | `--yes` | Skip prompts |
67
+ | `--session <id>` | Session ID |
68
+ | `--headful` | Run browser in visible mode |
69
+
70
+ ### `launch publish`
71
+
72
+ Submit the approved draft to Indie Hackers.
73
+
74
+ | Flag | Description |
75
+ |------|-------------|
76
+ | `--yes` | Skip confirmation |
77
+ | `--session <id>` | Session ID |
78
+ | `--headful` | Run browser in visible mode |
79
+
80
+ ### `launch history`
81
+
82
+ View past launch sessions.
83
+
84
+ | Flag | Description |
85
+ |------|-------------|
86
+ | `--limit <n>` | Max results (`0` = all, default `10`) |
87
+
88
+ ### `launch status`
89
+
90
+ Show current session state and next steps.
91
+
92
+ | Flag | Description |
93
+ |------|-------------|
94
+ | `--session <id>` | Session ID |
95
+
96
+ ## How It Works
97
+
98
+ Three key components:
99
+
100
+ - **Orchestrator-driven AI drafting** — Post drafts are generated by an AI orchestrator (not a paid API). Templates and structured prompts produce Indie Hackers–style posts without API costs.
101
+ - **Playwright browser automation** — Preview and publish commands use Playwright to fill Indie Hackers forms, take screenshots, and submit posts. All browser actions are safe — preview never submits.
102
+ - **Session persistence** — Product info and post drafts are saved as JSON session files. Resume mid-flow with `--resume` or `--session`.
103
+
104
+ ## Requirements
105
+
106
+ - Node.js 18+
107
+ - Chrome/Chromium (installed via `npx playwright install`)
108
+ - An Indie Hackers account
109
+
110
+ ## Links
111
+
112
+ - [Indie Hackers](https://www.indiehackers.com/)
113
+ - [CLI Vision](./CLI-VISION.md)
114
+
115
+ ## License
116
+
117
+ MIT
@@ -0,0 +1,20 @@
1
+ import { Browser, BrowserContext } from 'playwright';
2
+ import { LaunchConfig } from './env.js';
3
+ export interface StealthBrowser {
4
+ browser: Browser;
5
+ context: BrowserContext;
6
+ }
7
+ /**
8
+ * Launch a stealth browser using a persistent Chrome profile.
9
+ * The profile stores Cloudflare trust cookies, cookies, etc.
10
+ * so repeat visits don't trigger challenges.
11
+ */
12
+ export declare function launchStealthBrowser(headless: boolean, slowMo?: number): Promise<StealthBrowser>;
13
+ /**
14
+ * Launch a stealth browser with the given config.
15
+ */
16
+ export declare function launchWithConfig(config: LaunchConfig): Promise<StealthBrowser>;
17
+ /**
18
+ * Clear the persistent browser profile (use if Cloudflare challenges persist).
19
+ */
20
+ export declare function clearBrowserProfile(): void;
@@ -0,0 +1,86 @@
1
+ // ============================================================
2
+ // LAUNCH — Stealth Browser Launcher
3
+ // ============================================================
4
+ // Handles Playwright launch with stealth settings to bypass
5
+ // Reddit's bot detection. Uses a persistent browser profile
6
+ // so Cloudflare trust is maintained across runs.
7
+ // ============================================================
8
+ import { chromium } from 'playwright';
9
+ import { ensureDir } from './utils.js';
10
+ import { join } from 'path';
11
+ const REALISTIC_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' +
12
+ 'AppleWebKit/537.36 (KHTML, like Gecko) ' +
13
+ 'Chrome/148.0.0.0 Safari/537.36';
14
+ const PROFILE_DIR = join(process.cwd(), '.browser-profile');
15
+ /**
16
+ * Launch a stealth browser using a persistent Chrome profile.
17
+ * The profile stores Cloudflare trust cookies, cookies, etc.
18
+ * so repeat visits don't trigger challenges.
19
+ */
20
+ export async function launchStealthBrowser(headless, slowMo = 100) {
21
+ ensureDir(PROFILE_DIR);
22
+ // Standard headless mode
23
+ const commonArgs = [
24
+ '--disable-blink-features=AutomationControlled',
25
+ '--disable-infobars',
26
+ '--window-size=1280,900',
27
+ '--no-first-run',
28
+ '--disable-extensions',
29
+ '--disable-sync',
30
+ '--password-store=basic',
31
+ '--use-mock-keychain',
32
+ `--user-data-dir=${PROFILE_DIR}`,
33
+ ];
34
+ // Use launchPersistentContext so Playwright manages the profile directory.
35
+ // This preserves Cloudflare trust cookies across runs.
36
+ const persistentOpts = {
37
+ headless,
38
+ slowMo,
39
+ channel: 'chrome',
40
+ viewport: { width: 1280, height: 900 },
41
+ userAgent: REALISTIC_USER_AGENT,
42
+ locale: 'en-US',
43
+ timezoneId: 'America/New_York',
44
+ };
45
+ let context;
46
+ try {
47
+ context = await chromium.launchPersistentContext(PROFILE_DIR, persistentOpts);
48
+ }
49
+ catch {
50
+ // Fallback without Chrome channel
51
+ const { channel, ...noChannelOpts } = persistentOpts;
52
+ context = await chromium.launchPersistentContext(PROFILE_DIR, noChannelOpts);
53
+ }
54
+ // Hide automation fingerprints
55
+ await context.addInitScript(() => {
56
+ Object.defineProperty(navigator, 'webdriver', {
57
+ get: () => undefined,
58
+ configurable: true,
59
+ });
60
+ const origQuery = navigator.permissions.query.bind(navigator.permissions);
61
+ navigator.permissions.query = ((p) => {
62
+ if (p?.name === 'notifications')
63
+ return Promise.resolve({ state: 'denied', onchange: null });
64
+ return origQuery(p);
65
+ });
66
+ });
67
+ // launchPersistentContext doesn't expose the Browser directly via return.
68
+ // We need to get it from context.
69
+ const browser = context.browser();
70
+ if (!browser)
71
+ throw new Error('Failed to get browser from persistent context');
72
+ return { browser, context };
73
+ }
74
+ /**
75
+ * Launch a stealth browser with the given config.
76
+ */
77
+ export async function launchWithConfig(config) {
78
+ return launchStealthBrowser(config.browserHeadless, config.browserSlowMo);
79
+ }
80
+ /**
81
+ * Clear the persistent browser profile (use if Cloudflare challenges persist).
82
+ */
83
+ export function clearBrowserProfile() {
84
+ // Just a utility — actual clearing would remove PROFILE_DIR
85
+ }
86
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { CliResponse, IHPostDraft } from '../../types.js';
2
+ export interface DraftOptions {
3
+ json?: boolean;
4
+ yes?: boolean;
5
+ session?: string;
6
+ }
7
+ export declare function handleDraft(opts: DraftOptions): Promise<CliResponse<{
8
+ sessionId: string;
9
+ draft: IHPostDraft;
10
+ }>>;
@@ -0,0 +1,32 @@
1
+ import { fallbackIHDraft, createIHDraft } from '../../post-drafter.js';
2
+ import { updateSession } from '../../launch-workflow.js';
3
+ import { promptConfirm } from '../ui/prompts.js';
4
+ import { editInEditor } from '../ui/editor.js';
5
+ import { resolveSession, assertState } from '../utils/session-helpers.js';
6
+ export async function handleDraft(opts) {
7
+ const session = resolveSession(opts.session);
8
+ assertState(session, 'drafting');
9
+ let draft = session.draft ?? fallbackIHDraft(session.product);
10
+ if (!opts.yes) {
11
+ const editRequested = await promptConfirm('Edit draft in editor?');
12
+ if (editRequested) {
13
+ const formattedContent = `# ${draft.title}\n\n${draft.body}`;
14
+ const editedContent = await editInEditor(formattedContent);
15
+ if (editedContent && editedContent !== formattedContent) {
16
+ const editedLines = editedContent.split('\n');
17
+ const newTitle = editedLines[0].replace(/^#\s*/, '').trim();
18
+ const newBody = editedLines.slice(1).join('\n').trim();
19
+ if (newTitle && newBody) {
20
+ draft = createIHDraft(session.product, newTitle, newBody);
21
+ }
22
+ }
23
+ }
24
+ }
25
+ const updated = updateSession(session, { draft, status: 'awaiting_approval' });
26
+ return {
27
+ success: true,
28
+ data: { sessionId: updated.sessionId, draft },
29
+ error: null,
30
+ };
31
+ }
32
+ //# sourceMappingURL=draft.command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft.command.js","sourceRoot":"","sources":["../../../src/cli/commands/draft.command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAQ1E,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAkB;IAClD,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,WAAW,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAEjC,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,eAAe,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9D,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACd,MAAM,aAAa,GAAG,MAAM,aAAa,CAAC,uBAAuB,CAAC,CAAC;QACnE,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,gBAAgB,GAAG,KAAK,KAAK,CAAC,KAAK,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;YAC7D,MAAM,aAAa,GAAG,MAAM,YAAY,CAAC,gBAAgB,CAAC,CAAC;YAC3D,IAAI,aAAa,IAAI,aAAa,KAAK,gBAAgB,EAAE,CAAC;gBACxD,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC9C,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBAC5D,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;gBACvD,IAAI,QAAQ,IAAI,OAAO,EAAE,CAAC;oBACxB,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAC5D,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAE/E,OAAO;QACL,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE;QAC7C,KAAK,EAAE,IAAI;KACZ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { CliResponse, LaunchSession } from '../../types.js';
2
+ export interface HistoryOptions {
3
+ json?: boolean;
4
+ limit?: number;
5
+ }
6
+ export declare function handleHistory(opts: HistoryOptions): Promise<CliResponse<{
7
+ sessions: LaunchSession[];
8
+ total: number;
9
+ }>>;
@@ -0,0 +1,30 @@
1
+ // ============================================================
2
+ // LAUNCH — ih history Command (non-browser CLI)
3
+ // ============================================================
4
+ // Lists all saved launch sessions, newest first.
5
+ // ============================================================
6
+ import { listSessions } from '../../launch-workflow.js';
7
+ export async function handleHistory(opts) {
8
+ try {
9
+ const allSessions = listSessions();
10
+ const limit = opts.limit ?? 10;
11
+ if (!Number.isFinite(limit) || limit < 0) {
12
+ return {
13
+ success: false,
14
+ data: null,
15
+ error: `Invalid --limit value '${limit}'. Must be a non-negative integer.`,
16
+ };
17
+ }
18
+ const sessions = limit > 0 ? allSessions.slice(0, limit) : allSessions;
19
+ return {
20
+ success: true,
21
+ data: { sessions, total: allSessions.length },
22
+ error: null,
23
+ };
24
+ }
25
+ catch (err) {
26
+ const message = err instanceof Error ? err.message : String(err);
27
+ return { success: false, data: null, error: message };
28
+ }
29
+ }
30
+ //# sourceMappingURL=history.command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.command.js","sourceRoot":"","sources":["../../../src/cli/commands/history.command.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,gDAAgD;AAChD,+DAA+D;AAC/D,iDAAiD;AACjD,+DAA+D;AAG/D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAOxD,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAoB;IAEpB,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,YAAY,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAE/B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACzC,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,IAAI,EAAE,IAAI;gBACV,KAAK,EAAE,0BAA0B,KAAK,oCAAoC;aAC3E,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QAEvE,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE;YAC7C,KAAK,EAAE,IAAI;SACZ,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IACxD,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { CliResponse } from '../../types.js';
2
+ export interface LoginOptions {
3
+ json?: boolean;
4
+ yes?: boolean;
5
+ }
6
+ export declare function handleLogin(opts: LoginOptions): Promise<CliResponse<{
7
+ email: string;
8
+ username: string;
9
+ cookieFile: string;
10
+ }>>;
@@ -0,0 +1,64 @@
1
+ // ============================================================
2
+ // LAUNCH — ih login Command (non-browser CLI)
3
+ // ============================================================
4
+ // Authenticates with Indie Hackers via browser automation.
5
+ // Prompts for credentials if env vars are missing.
6
+ // ============================================================
7
+ import { resolve } from 'path';
8
+ import { loadConfig } from '../../env.js';
9
+ import { launchStealthBrowser } from '../../browser.js';
10
+ import { login } from '../../ih-auth.js';
11
+ import { promptText, promptPassword } from '../ui/prompts.js';
12
+ export async function handleLogin(opts) {
13
+ let config;
14
+ try {
15
+ config = loadConfig();
16
+ }
17
+ catch {
18
+ if (opts.yes) {
19
+ return {
20
+ success: false,
21
+ data: null,
22
+ error: 'Missing IH_EMAIL or IH_PASSWORD in environment. Set them or omit --yes to be prompted.',
23
+ };
24
+ }
25
+ const email = await promptText('Indie Hackers email');
26
+ const password = await promptPassword('Indie Hackers password');
27
+ config = {
28
+ ihEmail: email,
29
+ ihPassword: password,
30
+ browserHeadless: false,
31
+ browserSlowMo: 100,
32
+ cookiePath: resolve(process.cwd(), './cookies/ih-cookies.json'),
33
+ screenshotDir: resolve(process.cwd(), './screenshots'),
34
+ };
35
+ }
36
+ const { browser, context } = await launchStealthBrowser(false, config.browserSlowMo);
37
+ try {
38
+ const authSession = await login(context, {
39
+ email: config.ihEmail,
40
+ password: config.ihPassword,
41
+ }, config);
42
+ return {
43
+ success: true,
44
+ data: {
45
+ email: authSession.email,
46
+ username: authSession.username,
47
+ cookieFile: authSession.cookieFile,
48
+ },
49
+ error: null,
50
+ };
51
+ }
52
+ catch (err) {
53
+ const message = err instanceof Error ? err.message : String(err);
54
+ return {
55
+ success: false,
56
+ data: null,
57
+ error: `Login failed: ${message}`,
58
+ };
59
+ }
60
+ finally {
61
+ await browser.close().catch(() => { });
62
+ }
63
+ }
64
+ //# sourceMappingURL=login.command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.command.js","sourceRoot":"","sources":["../../../src/cli/commands/login.command.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,8CAA8C;AAC9C,+DAA+D;AAC/D,2DAA2D;AAC3D,mDAAmD;AACnD,+DAA+D;AAE/D,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAO9D,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAkB;IAElB,IAAI,MAAqC,CAAC;IAE1C,IAAI,CAAC;QACH,MAAM,GAAG,UAAU,EAAE,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,IAAI,EAAE,IAAI;gBACV,KAAK,EAAE,wFAAwF;aAChG,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,qBAAqB,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,wBAAwB,CAAC,CAAC;QAEhE,MAAM,GAAG;YACP,OAAO,EAAE,KAAK;YACd,UAAU,EAAE,QAAQ;YACpB,eAAe,EAAE,KAAK;YACtB,aAAa,EAAE,GAAG;YAClB,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,2BAA2B,CAAC;YAC/D,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,eAAe,CAAC;SACvD,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;IAErF,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE;YACvC,KAAK,EAAE,MAAM,CAAC,OAAO;YACrB,QAAQ,EAAE,MAAM,CAAC,UAAU;SAC5B,EAAE,MAAM,CAAC,CAAC;QAEX,OAAO;YACL,OAAO,EAAE,IAAI;YACb,IAAI,EAAE;gBACJ,KAAK,EAAE,WAAW,CAAC,KAAK;gBACxB,QAAQ,EAAE,WAAW,CAAC,QAAQ;gBAC9B,UAAU,EAAE,WAAW,CAAC,UAAU;aACnC;YACD,KAAK,EAAE,IAAI;SACZ,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO;YACL,OAAO,EAAE,KAAK;YACd,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,iBAAiB,OAAO,EAAE;SAClC,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACxC,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { ProductInfo, CliResponse } from '../../types.js';
2
+ export interface NewOptions {
3
+ json?: boolean;
4
+ yes?: boolean;
5
+ name?: string;
6
+ tagline?: string;
7
+ problem?: string;
8
+ features?: string;
9
+ link?: string;
10
+ audience?: string;
11
+ stage?: string;
12
+ resume?: boolean;
13
+ }
14
+ export declare function handleNew(opts: NewOptions): Promise<CliResponse<{
15
+ sessionId: string;
16
+ product: ProductInfo;
17
+ }>>;
@@ -0,0 +1,151 @@
1
+ // ============================================================
2
+ // LAUNCH — ih new Command (non-browser CLI)
3
+ // ============================================================
4
+ // Creates a new product launch session. Supports interactive
5
+ // step-through and non-interactive flag-based modes.
6
+ // ============================================================
7
+ import { createSession, loadLatestSession } from '../../launch-workflow.js';
8
+ import { promptText, promptSelect, promptConfirm, promptMultiInput, } from '../ui/prompts.js';
9
+ const VALID_STAGES = ['pre-launch', 'just-launched', 'growing'];
10
+ export async function handleNew(opts) {
11
+ try {
12
+ // --yes without --name or --resume is invalid in non-interactive mode
13
+ // Also detect if --name swallowed the next flag (shell dropped empty value).
14
+ const nameMissing = opts.name === undefined || opts.name === null || opts.name === '';
15
+ const nameIsFlag = !nameMissing && opts.name.startsWith('--');
16
+ if (opts.yes && (nameMissing || nameIsFlag) && !opts.resume) {
17
+ return {
18
+ success: false,
19
+ data: null,
20
+ error: '--name is required when using --yes in non-interactive mode. Provide --name <name> or use --resume.',
21
+ };
22
+ }
23
+ // ---- Non-interactive mode (all flags provided) ----
24
+ if (opts.name !== undefined && opts.name !== null && opts.name !== '') {
25
+ return handleNonInteractive(opts);
26
+ }
27
+ // ---- Resume mode ----
28
+ if (opts.resume) {
29
+ return handleResume();
30
+ }
31
+ // ---- Interactive mode ----
32
+ return handleInteractive(opts);
33
+ }
34
+ catch (err) {
35
+ const message = err instanceof Error ? err.message : String(err);
36
+ return { success: false, data: null, error: message };
37
+ }
38
+ }
39
+ // ============================================================
40
+ // Non-interactive: all flags must be present
41
+ // ============================================================
42
+ function handleNonInteractive(opts) {
43
+ const features = opts.features
44
+ ? opts.features.split(',').map((f) => f.trim()).filter(Boolean)
45
+ : [];
46
+ if (!VALID_STAGES.includes(opts.stage)) {
47
+ return {
48
+ success: false,
49
+ data: null,
50
+ error: `Invalid stage '${opts.stage}'. Valid values: ${VALID_STAGES.join(', ')}`,
51
+ };
52
+ }
53
+ const product = {
54
+ name: opts.name,
55
+ tagline: opts.tagline || '',
56
+ problem: opts.problem || '',
57
+ features,
58
+ link: opts.link || '',
59
+ targetAudience: opts.audience || '',
60
+ stage: opts.stage,
61
+ };
62
+ const session = createSession(product);
63
+ return {
64
+ success: true,
65
+ data: { sessionId: session.sessionId, product: session.product },
66
+ error: null,
67
+ };
68
+ }
69
+ // ============================================================
70
+ // Resume: return the latest drafting session
71
+ // ============================================================
72
+ function handleResume() {
73
+ const existing = loadLatestSession();
74
+ if (!existing || existing.status !== 'drafting') {
75
+ return {
76
+ success: false,
77
+ data: null,
78
+ error: 'No existing draft session found to resume. Start a new one with `launch new`.',
79
+ };
80
+ }
81
+ return {
82
+ success: true,
83
+ data: { sessionId: existing.sessionId, product: existing.product },
84
+ error: null,
85
+ };
86
+ }
87
+ // ============================================================
88
+ // Interactive: step through product info with prompts
89
+ // ============================================================
90
+ async function handleInteractive(opts) {
91
+ const existing = loadLatestSession();
92
+ if (existing && existing.status === 'drafting') {
93
+ const action = await promptSelect('An existing draft session was found. What would you like to do?', [
94
+ { name: 'Resume existing draft', value: 'resume' },
95
+ { name: 'Start fresh', value: 'fresh' },
96
+ ]);
97
+ if (action === 'resume') {
98
+ return {
99
+ success: true,
100
+ data: { sessionId: existing.sessionId, product: existing.product },
101
+ error: null,
102
+ };
103
+ }
104
+ }
105
+ const name = await promptText('Product name', {
106
+ validate: (v) => v.length > 0 || 'Required',
107
+ });
108
+ const tagline = await promptText('Tagline');
109
+ const problem = await promptText('Problem it solves');
110
+ const features = await promptMultiInput('Feature', 'Add another feature?');
111
+ const link = await promptText('Product URL');
112
+ const audience = await promptText('Target audience');
113
+ const stage = await promptSelect('Stage', [
114
+ { name: 'Pre-launch', value: 'pre-launch' },
115
+ { name: 'Just launched', value: 'just-launched' },
116
+ { name: 'Growing', value: 'growing' },
117
+ ]);
118
+ const product = {
119
+ name,
120
+ tagline,
121
+ problem,
122
+ features,
123
+ link,
124
+ targetAudience: audience,
125
+ stage: stage,
126
+ };
127
+ // Show summary
128
+ const lines = [
129
+ `Name: ${product.name}`,
130
+ `Tagline: ${product.tagline}`,
131
+ `Problem: ${product.problem}`,
132
+ `Features: ${product.features.join(', ') || '(none)'}`,
133
+ `URL: ${product.link}`,
134
+ `Audience: ${product.targetAudience}`,
135
+ `Stage: ${product.stage}`,
136
+ ];
137
+ console.log(`\n${'─'.repeat(40)}\n${lines.join('\n')}\n${'─'.repeat(40)}\n`);
138
+ if (!opts.yes) {
139
+ const confirmed = await promptConfirm('Save this product?', true);
140
+ if (!confirmed) {
141
+ return { success: false, data: null, error: 'Cancelled by user' };
142
+ }
143
+ }
144
+ const session = createSession(product);
145
+ return {
146
+ success: true,
147
+ data: { sessionId: session.sessionId, product: session.product },
148
+ error: null,
149
+ };
150
+ }
151
+ //# sourceMappingURL=new.command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"new.command.js","sourceRoot":"","sources":["../../../src/cli/commands/new.command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { CliResponse, IHPostDraft } from '../../types.js';
2
+ export interface PreviewOptions {
3
+ json?: boolean;
4
+ yes?: boolean;
5
+ session?: string;
6
+ headful?: boolean;
7
+ }
8
+ export declare function handlePreview(opts: PreviewOptions): Promise<CliResponse<{
9
+ sessionId: string;
10
+ screenshotPath: string;
11
+ draft: IHPostDraft;
12
+ dryRun: boolean;
13
+ }>>;
@@ -0,0 +1,48 @@
1
+ import { loadConfig } from '../../env.js';
2
+ import { launchStealthBrowser } from '../../browser.js';
3
+ import { loadCookies } from '../../ih-auth.js';
4
+ import { postToIH } from '../../ih-poster.js';
5
+ import { resolveSession, assertState, assertHasDraft } from '../utils/session-helpers.js';
6
+ export async function handlePreview(opts) {
7
+ const session = resolveSession(opts.session);
8
+ assertState(session, 'awaiting_approval');
9
+ const draft = assertHasDraft(session);
10
+ const config = loadConfig();
11
+ try {
12
+ const { browser, context } = await launchStealthBrowser(!opts.headful, config.browserSlowMo);
13
+ try {
14
+ await loadCookies(context, config.cookiePath);
15
+ const result = await postToIH(context, draft, config, { dryRun: true });
16
+ if (!result.success) {
17
+ return {
18
+ success: false,
19
+ data: result.screenshotPath
20
+ ? { sessionId: session.sessionId, screenshotPath: result.screenshotPath, draft, dryRun: result.dryRun }
21
+ : null,
22
+ error: result.error || 'Preview failed',
23
+ };
24
+ }
25
+ return {
26
+ success: true,
27
+ data: {
28
+ sessionId: session.sessionId,
29
+ screenshotPath: result.screenshotPath,
30
+ draft,
31
+ dryRun: result.dryRun,
32
+ },
33
+ error: null,
34
+ };
35
+ }
36
+ finally {
37
+ await browser.close().catch(() => { });
38
+ }
39
+ }
40
+ catch (err) {
41
+ return {
42
+ success: false,
43
+ data: null,
44
+ error: `Preview failed: ${err instanceof Error ? err.message : String(err)}`,
45
+ };
46
+ }
47
+ }
48
+ //# sourceMappingURL=preview.command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.command.js","sourceRoot":"","sources":["../../../src/cli/commands/preview.command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAS1F,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAoB;IACtD,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,WAAW,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAE5B,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,oBAAoB,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC;QAC7F,IAAI,CAAC;YACH,MAAM,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;YAC9C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAExE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,IAAI,EAAE,MAAM,CAAC,cAAc;wBACzB,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,cAAc,EAAE,MAAM,CAAC,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;wBACvG,CAAC,CAAC,IAAI;oBACR,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,gBAAgB;iBACxC,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE;oBACJ,SAAS,EAAE,OAAO,CAAC,SAAS;oBAC5B,cAAc,EAAE,MAAM,CAAC,cAAe;oBACtC,KAAK;oBACL,MAAM,EAAE,MAAM,CAAC,MAAM;iBACtB;gBACD,KAAK,EAAE,IAAI;aACZ,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,KAAK;YACd,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,mBAAmB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;SAC7E,CAAC;IACJ,CAAC;AACH,CAAC"}