pmpt-cli 1.3.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,8 +32,9 @@ pmpt plan
32
32
  # 4. Save your progress
33
33
  pmpt save
34
34
 
35
- # 5. Export & share
36
- pmpt export
35
+ # 5. Publish to pmptwiki
36
+ pmpt login
37
+ pmpt publish
37
38
  ```
38
39
 
39
40
  ---
@@ -42,12 +43,15 @@ pmpt export
42
43
 
43
44
  - **5 questions** — Quick product planning with AI-ready prompts
44
45
  - **Version history** — Track every step of your AI-assisted development
45
- - **Share & reproduce** — Export `.pmpt` files for others to learn from
46
+ - **Share & reproduce** — Publish projects for others to learn from and clone
47
+ - **Project hub** — Browse and clone projects at [pmptwiki.com](https://pmptwiki.com/en/explore)
46
48
 
47
49
  ---
48
50
 
49
51
  ## Commands
50
52
 
53
+ ### Local
54
+
51
55
  | Command | Description |
52
56
  |---------|-------------|
53
57
  | `pmpt init` | Initialize project |
@@ -61,6 +65,33 @@ pmpt export
61
65
  | `pmpt import <file>` | Import from `.pmpt` file |
62
66
  | `pmpt status` | Check project status |
63
67
 
68
+ ### Platform
69
+
70
+ | Command | Description |
71
+ |---------|-------------|
72
+ | `pmpt login` | Authenticate with GitHub (one-time setup) |
73
+ | `pmpt publish` | Publish project to pmptwiki |
74
+ | `pmpt clone <slug>` | Clone a published project |
75
+ | `pmpt browse` | Browse and discover projects |
76
+
77
+ ---
78
+
79
+ ## Workflow
80
+
81
+ ```
82
+ [You]
83
+
84
+ ├─ pmpt plan ─────→ 5 questions → AI prompt (clipboard)
85
+
86
+ ├─ Build with AI ──→ Create files, iterate
87
+
88
+ ├─ pmpt save ─────→ Save to .pmpt/.history
89
+
90
+ ├─ pmpt publish ──→ Share on pmptwiki.com
91
+
92
+ └─ pmpt clone ────→ Reproduce someone's project
93
+ ```
94
+
64
95
  ---
65
96
 
66
97
  ## Folder Structure
@@ -79,24 +110,6 @@ pmpt export
79
110
 
80
111
  ---
81
112
 
82
- ## Workflow
83
-
84
- ```
85
- [You]
86
-
87
- ├─ pmpt plan ────→ 5 questions → AI prompt (clipboard)
88
-
89
- ├─ Build with AI ─→ Create files, iterate
90
-
91
- ├─ pmpt save ────→ Save to .pmpt/.history
92
-
93
- ├─ pmpt export ──→ Create .pmpt file (shareable)
94
-
95
- └─ pmpt import ──→ Reproduce someone's project
96
- ```
97
-
98
- ---
99
-
100
113
  ## .pmpt File Format
101
114
 
102
115
  Single JSON file containing your entire development journey:
@@ -104,12 +117,12 @@ Single JSON file containing your entire development journey:
104
117
  ```json
105
118
  {
106
119
  "schemaVersion": "1.0",
107
- "meta": { "projectName", "description", "createdAt" },
108
- "plan": { "productIdea", "coreFeatures", "techStack" },
120
+ "meta": { "projectName": "", "description": "", "createdAt": "" },
121
+ "plan": { "productIdea": "", "coreFeatures": "", "techStack": "" },
109
122
  "docs": { "plan.md": "...", "pmpt.md": "..." },
110
123
  "history": [
111
- { "version": 1, "timestamp": "...", "files": {...} },
112
- { "version": 2, "timestamp": "...", "files": {...} }
124
+ { "version": 1, "timestamp": "...", "files": {} },
125
+ { "version": 2, "timestamp": "...", "files": {} }
113
126
  ]
114
127
  }
115
128
  ```
@@ -121,13 +134,14 @@ Single JSON file containing your entire development journey:
121
134
  - **Side project builders** — Track your AI-assisted development
122
135
  - **Startup founders** — Document MVP creation process
123
136
  - **Content creators** — Share your coding journey
124
- - **Learners** — Study how others build with AI
137
+ - **Learners** — Browse and clone projects to study how others build with AI
125
138
 
126
139
  ---
127
140
 
128
141
  ## Links
129
142
 
130
143
  - [Website](https://pmptwiki.com)
144
+ - [Explore Projects](https://pmptwiki.com/en/explore)
131
145
  - [GitHub](https://github.com/pmptwiki/pmpt-cli)
132
146
  - [npm](https://www.npmjs.com/package/pmpt-cli)
133
147
 
@@ -0,0 +1,73 @@
1
+ import * as p from '@clack/prompts';
2
+ import { fetchProjects } from '../lib/api.js';
3
+ export async function cmdBrowse() {
4
+ p.intro('pmpt browse');
5
+ const s = p.spinner();
6
+ s.start('프로젝트 목록 불러오는 중...');
7
+ let projects;
8
+ try {
9
+ const index = await fetchProjects();
10
+ projects = index.projects;
11
+ }
12
+ catch (err) {
13
+ s.stop('불러오기 실패');
14
+ p.log.error(err instanceof Error ? err.message : '프로젝트 목록을 불러올 수 없습니다.');
15
+ process.exit(1);
16
+ }
17
+ s.stop(`${projects.length}개 프로젝트`);
18
+ if (projects.length === 0) {
19
+ p.log.info('아직 공개된 프로젝트가 없습니다.');
20
+ p.log.message(' pmpt publish — 첫 번째 프로젝트를 공유해보세요!');
21
+ p.outro('');
22
+ return;
23
+ }
24
+ // Select project
25
+ const selected = await p.select({
26
+ message: '프로젝트를 선택하세요:',
27
+ options: projects.map((proj) => ({
28
+ value: proj.slug,
29
+ label: proj.projectName,
30
+ hint: `v${proj.versionCount} · @${proj.author}${proj.description ? ` — ${proj.description.slice(0, 40)}` : ''}`,
31
+ })),
32
+ });
33
+ if (p.isCancel(selected)) {
34
+ p.cancel('');
35
+ process.exit(0);
36
+ }
37
+ const project = projects.find((p) => p.slug === selected);
38
+ // Show details
39
+ p.note([
40
+ `Project: ${project.projectName}`,
41
+ `Author: @${project.author}`,
42
+ `Versions: ${project.versionCount}`,
43
+ project.description ? `Description: ${project.description}` : '',
44
+ project.tags.length ? `Tags: ${project.tags.join(', ')}` : '',
45
+ `Published: ${project.publishedAt.slice(0, 10)}`,
46
+ `Size: ${(project.fileSize / 1024).toFixed(1)} KB`,
47
+ ].filter(Boolean).join('\n'), 'Project Details');
48
+ // Action
49
+ const action = await p.select({
50
+ message: '어떻게 할까요?',
51
+ options: [
52
+ { value: 'clone', label: '이 프로젝트 복제', hint: 'pmpt clone' },
53
+ { value: 'url', label: 'URL 표시', hint: '브라우저에서 보기' },
54
+ { value: 'back', label: '돌아가기' },
55
+ ],
56
+ });
57
+ if (p.isCancel(action) || action === 'back') {
58
+ p.outro('');
59
+ return;
60
+ }
61
+ if (action === 'clone') {
62
+ const { cmdClone } = await import('./clone.js');
63
+ await cmdClone(project.slug);
64
+ return;
65
+ }
66
+ if (action === 'url') {
67
+ const url = `https://pmptwiki.com/ko/p/${project.slug}`;
68
+ p.log.info(`URL: ${url}`);
69
+ p.log.message(`Download: ${project.downloadUrl}`);
70
+ p.log.message(`\npmpt clone ${project.slug} — 터미널에서 복제`);
71
+ p.outro('');
72
+ }
73
+ }
@@ -0,0 +1,127 @@
1
+ import * as p from '@clack/prompts';
2
+ import { join, dirname } from 'path';
3
+ import { existsSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
4
+ import { isInitialized, getConfigDir, getHistoryDir, getDocsDir, initializeProject } from '../lib/config.js';
5
+ import { validatePmptFile } from '../lib/pmptFile.js';
6
+ import { fetchPmptFile } from '../lib/api.js';
7
+ /**
8
+ * Restore history from .pmpt data (shared with import command)
9
+ */
10
+ export function restoreHistory(historyDir, history) {
11
+ mkdirSync(historyDir, { recursive: true });
12
+ for (const version of history) {
13
+ const timestamp = version.timestamp.replace(/[:.]/g, '-').slice(0, 19);
14
+ const snapshotName = `v${version.version}-${timestamp}`;
15
+ const snapshotDir = join(historyDir, snapshotName);
16
+ mkdirSync(snapshotDir, { recursive: true });
17
+ for (const [filename, content] of Object.entries(version.files)) {
18
+ const filePath = join(snapshotDir, filename);
19
+ const fileDir = dirname(filePath);
20
+ if (fileDir !== snapshotDir) {
21
+ mkdirSync(fileDir, { recursive: true });
22
+ }
23
+ writeFileSync(filePath, content, 'utf-8');
24
+ }
25
+ writeFileSync(join(snapshotDir, '.meta.json'), JSON.stringify({
26
+ version: version.version,
27
+ timestamp: version.timestamp,
28
+ files: Object.keys(version.files),
29
+ git: version.git,
30
+ }, null, 2), 'utf-8');
31
+ }
32
+ }
33
+ /**
34
+ * Restore docs from .pmpt data (shared with import command)
35
+ */
36
+ export function restoreDocs(docsDir, docs) {
37
+ mkdirSync(docsDir, { recursive: true });
38
+ for (const [filename, content] of Object.entries(docs)) {
39
+ const filePath = join(docsDir, filename);
40
+ const fileDir = dirname(filePath);
41
+ if (fileDir !== docsDir) {
42
+ mkdirSync(fileDir, { recursive: true });
43
+ }
44
+ writeFileSync(filePath, content, 'utf-8');
45
+ }
46
+ }
47
+ export async function cmdClone(slug) {
48
+ if (!slug) {
49
+ p.log.error('slug를 입력하세요.');
50
+ p.log.info('사용법: pmpt clone <slug>');
51
+ process.exit(1);
52
+ }
53
+ p.intro(`pmpt clone — ${slug}`);
54
+ const s = p.spinner();
55
+ s.start('프로젝트 다운로드 중...');
56
+ let fileContent;
57
+ try {
58
+ fileContent = await fetchPmptFile(slug);
59
+ }
60
+ catch (err) {
61
+ s.stop('다운로드 실패');
62
+ p.log.error(err instanceof Error ? err.message : '프로젝트를 찾을 수 없습니다.');
63
+ process.exit(1);
64
+ }
65
+ s.message('검증 중...');
66
+ const validation = validatePmptFile(fileContent);
67
+ if (!validation.success || !validation.data) {
68
+ s.stop('검증 실패');
69
+ p.log.error(validation.error || '잘못된 .pmpt 파일입니다.');
70
+ process.exit(1);
71
+ }
72
+ const pmptData = validation.data;
73
+ s.stop('다운로드 완료');
74
+ // Show summary
75
+ p.note([
76
+ `Project: ${pmptData.meta.projectName}`,
77
+ `Versions: ${pmptData.history.length}`,
78
+ pmptData.meta.author ? `Author: @${pmptData.meta.author}` : '',
79
+ pmptData.meta.description ? `Description: ${pmptData.meta.description.slice(0, 80)}` : '',
80
+ ].filter(Boolean).join('\n'), 'Project Info');
81
+ const projectPath = process.cwd();
82
+ if (isInitialized(projectPath)) {
83
+ const overwrite = await p.confirm({
84
+ message: '이미 초기화된 프로젝트입니다. 히스토리를 병합하시겠습니까?',
85
+ initialValue: true,
86
+ });
87
+ if (p.isCancel(overwrite) || !overwrite) {
88
+ p.cancel('취소됨');
89
+ process.exit(0);
90
+ }
91
+ }
92
+ const importSpinner = p.spinner();
93
+ importSpinner.start('프로젝트 복원 중...');
94
+ if (!isInitialized(projectPath)) {
95
+ initializeProject(projectPath, { trackGit: true });
96
+ }
97
+ const pmptDir = getConfigDir(projectPath);
98
+ const historyDir = getHistoryDir(projectPath);
99
+ const docsDir = getDocsDir(projectPath);
100
+ restoreHistory(historyDir, pmptData.history);
101
+ if (pmptData.docs) {
102
+ restoreDocs(docsDir, pmptData.docs);
103
+ }
104
+ if (pmptData.plan) {
105
+ writeFileSync(join(pmptDir, 'plan-progress.json'), JSON.stringify({
106
+ completed: true,
107
+ startedAt: pmptData.meta.createdAt,
108
+ updatedAt: pmptData.meta.exportedAt,
109
+ answers: pmptData.plan,
110
+ }, null, 2), 'utf-8');
111
+ }
112
+ let versionCount = 0;
113
+ if (existsSync(historyDir)) {
114
+ versionCount = readdirSync(historyDir).filter((d) => d.startsWith('v')).length;
115
+ }
116
+ importSpinner.stop('복원 완료!');
117
+ p.note([
118
+ `Project: ${pmptData.meta.projectName}`,
119
+ `Versions: ${versionCount}`,
120
+ `Location: ${pmptDir}`,
121
+ ].join('\n'), 'Clone Summary');
122
+ p.log.info('다음 단계:');
123
+ p.log.message(' pmpt history — 버전 히스토리 보기');
124
+ p.log.message(' pmpt plan — AI 프롬프트 보기');
125
+ p.log.message(' pmpt save — 새 스냅샷 저장');
126
+ p.outro('프로젝트가 복제되었습니다!');
127
+ }
@@ -0,0 +1,46 @@
1
+ import * as p from '@clack/prompts';
2
+ import { loadAuth, saveAuth } from '../lib/auth.js';
3
+ import { registerAuth } from '../lib/api.js';
4
+ export async function cmdLogin() {
5
+ p.intro('pmpt login');
6
+ const existing = loadAuth();
7
+ if (existing?.token && existing?.username) {
8
+ p.log.info(`Currently logged in as @${existing.username}`);
9
+ const reauth = await p.confirm({
10
+ message: 'Re-authenticate?',
11
+ initialValue: false,
12
+ });
13
+ if (p.isCancel(reauth) || !reauth) {
14
+ p.outro('');
15
+ return;
16
+ }
17
+ }
18
+ p.log.info('GitHub Personal Access Token이 필요합니다.\n' +
19
+ ' https://github.com/settings/tokens/new\n' +
20
+ ' 필요 권한: read:user');
21
+ const pat = await p.password({
22
+ message: 'GitHub PAT를 입력하세요:',
23
+ validate: (v) => (v.trim().length < 10 ? '올바른 토큰을 입력하세요' : undefined),
24
+ });
25
+ if (p.isCancel(pat)) {
26
+ p.cancel('취소됨');
27
+ process.exit(0);
28
+ }
29
+ const s = p.spinner();
30
+ s.start('인증 중...');
31
+ try {
32
+ const result = await registerAuth(pat);
33
+ saveAuth({
34
+ token: result.token,
35
+ githubToken: pat,
36
+ username: result.username,
37
+ });
38
+ s.stop(`인증 완료 — @${result.username}`);
39
+ }
40
+ catch (err) {
41
+ s.stop('인증 실패');
42
+ p.log.error(err instanceof Error ? err.message : '인증에 실패했습니다.');
43
+ process.exit(1);
44
+ }
45
+ p.outro('로그인 완료! pmpt publish로 프로젝트를 공유하세요.');
46
+ }
@@ -0,0 +1,167 @@
1
+ import * as p from '@clack/prompts';
2
+ import { resolve, basename } from 'path';
3
+ import { readFileSync, existsSync } from 'fs';
4
+ import { isInitialized, loadConfig, saveConfig, getDocsDir } from '../lib/config.js';
5
+ import { getAllSnapshots } from '../lib/history.js';
6
+ import { getPlanProgress } from '../lib/plan.js';
7
+ import { createPmptFile } from '../lib/pmptFile.js';
8
+ import { loadAuth } from '../lib/auth.js';
9
+ import { publishProject } from '../lib/api.js';
10
+ import glob from 'fast-glob';
11
+ import { join } from 'path';
12
+ function readSnapshotFiles(snapshotDir) {
13
+ const files = {};
14
+ if (!existsSync(snapshotDir))
15
+ return files;
16
+ const mdFiles = glob.sync('**/*.md', { cwd: snapshotDir });
17
+ for (const file of mdFiles) {
18
+ try {
19
+ files[file] = readFileSync(join(snapshotDir, file), 'utf-8');
20
+ }
21
+ catch { /* skip */ }
22
+ }
23
+ return files;
24
+ }
25
+ function readDocsFolder(docsDir) {
26
+ const files = {};
27
+ if (!existsSync(docsDir))
28
+ return files;
29
+ const mdFiles = glob.sync('**/*.md', { cwd: docsDir });
30
+ for (const file of mdFiles) {
31
+ try {
32
+ files[file] = readFileSync(join(docsDir, file), 'utf-8');
33
+ }
34
+ catch { /* skip */ }
35
+ }
36
+ return files;
37
+ }
38
+ export async function cmdPublish(path) {
39
+ const projectPath = path ? resolve(path) : process.cwd();
40
+ if (!isInitialized(projectPath)) {
41
+ p.log.error('프로젝트가 초기화되지 않았습니다. `pmpt init`을 먼저 실행하세요.');
42
+ process.exit(1);
43
+ }
44
+ const auth = loadAuth();
45
+ if (!auth?.token || !auth?.username) {
46
+ p.log.error('로그인이 필요합니다. `pmpt login`을 먼저 실행하세요.');
47
+ process.exit(1);
48
+ }
49
+ p.intro('pmpt publish');
50
+ const config = loadConfig(projectPath);
51
+ const snapshots = getAllSnapshots(projectPath);
52
+ const planProgress = getPlanProgress(projectPath);
53
+ if (snapshots.length === 0) {
54
+ p.log.warn('스냅샷이 없습니다. `pmpt save` 또는 `pmpt plan`을 먼저 실행하세요.');
55
+ p.outro('');
56
+ return;
57
+ }
58
+ const projectName = planProgress?.answers?.projectName || basename(projectPath);
59
+ // Collect publish info
60
+ const slug = await p.text({
61
+ message: '프로젝트 slug (URL에 사용될 이름):',
62
+ placeholder: projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-'),
63
+ validate: (v) => {
64
+ if (!/^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$/.test(v)) {
65
+ return '3~50자, 소문자/숫자/하이픈만 사용 가능합니다.';
66
+ }
67
+ },
68
+ });
69
+ if (p.isCancel(slug)) {
70
+ p.cancel('취소됨');
71
+ process.exit(0);
72
+ }
73
+ const description = await p.text({
74
+ message: '프로젝트 설명 (짧게):',
75
+ placeholder: planProgress?.answers?.productIdea?.slice(0, 100) || '',
76
+ defaultValue: planProgress?.answers?.productIdea?.slice(0, 200) || '',
77
+ });
78
+ if (p.isCancel(description)) {
79
+ p.cancel('취소됨');
80
+ process.exit(0);
81
+ }
82
+ const tagsInput = await p.text({
83
+ message: '태그 (쉼표로 구분):',
84
+ placeholder: 'react, saas, mvp',
85
+ defaultValue: '',
86
+ });
87
+ if (p.isCancel(tagsInput)) {
88
+ p.cancel('취소됨');
89
+ process.exit(0);
90
+ }
91
+ const tags = tagsInput
92
+ .split(',')
93
+ .map((t) => t.trim().toLowerCase())
94
+ .filter(Boolean);
95
+ // Build .pmpt content (reuse export logic)
96
+ const history = snapshots.map((snapshot) => ({
97
+ version: snapshot.version,
98
+ timestamp: snapshot.timestamp,
99
+ files: readSnapshotFiles(snapshot.snapshotDir),
100
+ git: snapshot.git,
101
+ }));
102
+ const docsDir = getDocsDir(projectPath);
103
+ const docs = readDocsFolder(docsDir);
104
+ const meta = {
105
+ projectName,
106
+ author: auth.username,
107
+ description: description,
108
+ createdAt: config?.createdAt || new Date().toISOString(),
109
+ exportedAt: new Date().toISOString(),
110
+ };
111
+ const planAnswers = planProgress?.answers
112
+ ? {
113
+ projectName: planProgress.answers.projectName,
114
+ productIdea: planProgress.answers.productIdea,
115
+ additionalContext: planProgress.answers.additionalContext,
116
+ coreFeatures: planProgress.answers.coreFeatures,
117
+ techStack: planProgress.answers.techStack,
118
+ }
119
+ : undefined;
120
+ const pmptContent = createPmptFile(meta, planAnswers, docs, history);
121
+ // Confirm
122
+ p.note([
123
+ `Project: ${projectName}`,
124
+ `Slug: ${slug}`,
125
+ `Versions: ${snapshots.length}`,
126
+ `Size: ${(pmptContent.length / 1024).toFixed(1)} KB`,
127
+ `Author: @${auth.username}`,
128
+ tags.length ? `Tags: ${tags.join(', ')}` : '',
129
+ ].filter(Boolean).join('\n'), 'Publish Preview');
130
+ const confirm = await p.confirm({
131
+ message: '게시하시겠습니까?',
132
+ initialValue: true,
133
+ });
134
+ if (p.isCancel(confirm) || !confirm) {
135
+ p.cancel('취소됨');
136
+ process.exit(0);
137
+ }
138
+ // Upload
139
+ const s = p.spinner();
140
+ s.start('업로드 중...');
141
+ try {
142
+ const result = await publishProject(auth.token, {
143
+ slug: slug,
144
+ pmptContent,
145
+ description: description,
146
+ tags,
147
+ });
148
+ s.stop('게시 완료!');
149
+ // Update config
150
+ if (config) {
151
+ config.lastPublished = new Date().toISOString();
152
+ saveConfig(projectPath, config);
153
+ }
154
+ p.note([
155
+ `URL: ${result.url}`,
156
+ `Download: ${result.downloadUrl}`,
157
+ '',
158
+ `pmpt clone ${slug} — 다른 사람이 이 프로젝트를 복제할 수 있습니다`,
159
+ ].join('\n'), 'Published!');
160
+ }
161
+ catch (err) {
162
+ s.stop('게시 실패');
163
+ p.log.error(err instanceof Error ? err.message : '게시에 실패했습니다.');
164
+ process.exit(1);
165
+ }
166
+ p.outro('');
167
+ }
package/dist/index.js CHANGED
@@ -12,11 +12,15 @@ import { cmdSave } from './commands/save.js';
12
12
  import { cmdSquash } from './commands/squash.js';
13
13
  import { cmdExport } from './commands/export.js';
14
14
  import { cmdImport } from './commands/import.js';
15
+ import { cmdLogin } from './commands/login.js';
16
+ import { cmdPublish } from './commands/publish.js';
17
+ import { cmdClone } from './commands/clone.js';
18
+ import { cmdBrowse } from './commands/browse.js';
15
19
  const program = new Command();
16
20
  program
17
21
  .name('pmpt')
18
22
  .description('pmpt — Record and share your AI-driven product development journey')
19
- .version('1.3.0')
23
+ .version('1.4.1')
20
24
  .addHelpText('after', `
21
25
  Examples:
22
26
  $ pmpt init Initialize project
@@ -24,16 +28,13 @@ Examples:
24
28
  $ pmpt save Save snapshot of docs folder
25
29
  $ pmpt watch Auto-detect file changes
26
30
  $ pmpt history View version history
27
- $ pmpt history --compact Hide minor changes
28
31
  $ pmpt squash v2 v5 Merge versions v2-v5 into v2
29
32
  $ pmpt export Export as .pmpt file (single JSON)
30
33
  $ pmpt import <file.pmpt> Import from .pmpt file
31
-
32
- Folder structure:
33
- .pmpt/
34
- ├── config.json Config file
35
- ├── docs/ Working folder (MD files)
36
- └── .history/ Version history
34
+ $ pmpt login Authenticate with pmptwiki
35
+ $ pmpt publish Publish project to pmptwiki
36
+ $ pmpt clone <slug> Clone a project from pmptwiki
37
+ $ pmpt browse Browse published projects
37
38
 
38
39
  Documentation: https://pmptwiki.com
39
40
  `);
@@ -104,4 +105,21 @@ program
104
105
  clearAuth();
105
106
  console.log('Logged out successfully');
106
107
  });
108
+ // Platform commands
109
+ program
110
+ .command('login')
111
+ .description('Authenticate with pmptwiki platform')
112
+ .action(cmdLogin);
113
+ program
114
+ .command('publish [path]')
115
+ .description('Publish project to pmptwiki platform')
116
+ .action(cmdPublish);
117
+ program
118
+ .command('clone <slug>')
119
+ .description('Clone a project from pmptwiki platform')
120
+ .action(cmdClone);
121
+ program
122
+ .command('browse')
123
+ .description('Browse and search published projects')
124
+ .action(cmdBrowse);
107
125
  program.parse();
@@ -0,0 +1,46 @@
1
+ /**
2
+ * pmptwiki API client
3
+ */
4
+ const API_BASE = 'https://pmptwiki-api.sin2da.workers.dev';
5
+ const R2_PUBLIC_URL = 'https://pub-ce73b2410943490d80b60ddad9243d31.r2.dev';
6
+ export async function registerAuth(githubToken) {
7
+ const res = await fetch(`${API_BASE}/auth/register`, {
8
+ method: 'POST',
9
+ headers: { 'Content-Type': 'application/json' },
10
+ body: JSON.stringify({ githubToken }),
11
+ });
12
+ if (!res.ok) {
13
+ const err = await res.json().catch(() => ({ error: 'Auth failed' }));
14
+ throw new Error(err.error);
15
+ }
16
+ return res.json();
17
+ }
18
+ export async function publishProject(token, data) {
19
+ const res = await fetch(`${API_BASE}/publish`, {
20
+ method: 'POST',
21
+ headers: {
22
+ Authorization: `Bearer ${token}`,
23
+ 'Content-Type': 'application/json',
24
+ },
25
+ body: JSON.stringify(data),
26
+ });
27
+ if (!res.ok) {
28
+ const err = await res.json().catch(() => ({ error: 'Publish failed' }));
29
+ throw new Error(err.error);
30
+ }
31
+ return res.json();
32
+ }
33
+ export async function fetchProjects() {
34
+ const res = await fetch(`${API_BASE}/projects`);
35
+ if (!res.ok) {
36
+ throw new Error('Failed to fetch projects');
37
+ }
38
+ return res.json();
39
+ }
40
+ export async function fetchPmptFile(slug) {
41
+ const res = await fetch(`${R2_PUBLIC_URL}/projects/${slug}.pmpt`);
42
+ if (!res.ok) {
43
+ throw new Error(`Project not found: ${slug}`);
44
+ }
45
+ return res.text();
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmpt-cli",
3
- "version": "1.3.1",
3
+ "version": "1.4.1",
4
4
  "description": "Record and share your AI-driven product development journey",
5
5
  "type": "module",
6
6
  "bin": {