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.
- package/README.md +117 -0
- package/dist/browser.d.ts +20 -0
- package/dist/browser.js +86 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli/commands/draft.command.d.ts +10 -0
- package/dist/cli/commands/draft.command.js +32 -0
- package/dist/cli/commands/draft.command.js.map +1 -0
- package/dist/cli/commands/history.command.d.ts +9 -0
- package/dist/cli/commands/history.command.js +30 -0
- package/dist/cli/commands/history.command.js.map +1 -0
- package/dist/cli/commands/login.command.d.ts +10 -0
- package/dist/cli/commands/login.command.js +64 -0
- package/dist/cli/commands/login.command.js.map +1 -0
- package/dist/cli/commands/new.command.d.ts +17 -0
- package/dist/cli/commands/new.command.js +151 -0
- package/dist/cli/commands/new.command.js.map +1 -0
- package/dist/cli/commands/preview.command.d.ts +13 -0
- package/dist/cli/commands/preview.command.js +48 -0
- package/dist/cli/commands/preview.command.js.map +1 -0
- package/dist/cli/commands/publish.command.d.ts +12 -0
- package/dist/cli/commands/publish.command.js +70 -0
- package/dist/cli/commands/publish.command.js.map +1 -0
- package/dist/cli/commands/status.command.d.ts +9 -0
- package/dist/cli/commands/status.command.js +47 -0
- package/dist/cli/commands/status.command.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +168 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ui/editor.d.ts +1 -0
- package/dist/cli/ui/editor.js +39 -0
- package/dist/cli/ui/editor.js.map +1 -0
- package/dist/cli/ui/output.d.ts +23 -0
- package/dist/cli/ui/output.js +96 -0
- package/dist/cli/ui/output.js.map +1 -0
- package/dist/cli/ui/prompts.d.ts +11 -0
- package/dist/cli/ui/prompts.js +28 -0
- package/dist/cli/ui/prompts.js.map +1 -0
- package/dist/cli/utils/errors.d.ts +20 -0
- package/dist/cli/utils/errors.js +48 -0
- package/dist/cli/utils/errors.js.map +1 -0
- package/dist/cli/utils/session-helpers.d.ts +4 -0
- package/dist/cli/utils/session-helpers.js +37 -0
- package/dist/cli/utils/session-helpers.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.js +27 -0
- package/dist/env.js.map +1 -0
- package/dist/ih-auth.d.ts +32 -0
- package/dist/ih-auth.js +240 -0
- package/dist/ih-auth.js.map +1 -0
- package/dist/ih-poster.d.ts +21 -0
- package/dist/ih-poster.js +217 -0
- package/dist/ih-poster.js.map +1 -0
- package/dist/launch-workflow.d.ts +17 -0
- package/dist/launch-workflow.js +80 -0
- package/dist/launch-workflow.js.map +1 -0
- package/dist/post-drafter.d.ts +6 -0
- package/dist/post-drafter.js +103 -0
- package/dist/post-drafter.js.map +1 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +21 -0
- package/dist/utils.js +54 -0
- package/dist/utils.js.map +1 -0
- 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
|
+
 — *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;
|
package/dist/browser.js
ADDED
|
@@ -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"}
|