@gpdoc/cli 1.2.1 → 1.3.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 (3) hide show
  1. package/README.md +7 -0
  2. package/bin/gpdoc.js +136 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,6 +10,9 @@ npm run cli -- convert notes.html --to gpdoc --output docs/notes.gpdoc.md
10
10
  npm run cli -- convert https://example.com/guide --to markdown --output guide.md
11
11
  npm run cli -- validate docs/notes.gpdoc.md --json
12
12
  npm run cli -- edit docs/notes.gpdoc.md
13
+ npm run cli -- knowledge list --json
14
+ npm run cli -- knowledge search repo_123 "deployment review" --json
15
+ npm run cli -- knowledge github-authorize
13
16
  ```
14
17
 
15
18
  Supported input formats are Markdown, managed GPDoc Markdown, HTML, plain text, GPDoc JSON documents, DOCX, and PPTX text. Supported outputs are `gpdoc`, `markdown`, `html`, `text`, `json`, `docx`, and `pdf`. DOCX/PPTX conversion preserves text and headings, not full presentation layout; PDF output is text-only. PDF input, RTF input, and PPTX output return an explicit unsupported-format error.
@@ -42,6 +45,10 @@ gpdoc repo put repetere/example docs/release-plan.gpdoc.md --path docs/release-p
42
45
 
43
46
  Private Gists and private repositories require the same GPDoc GitHub-source entitlement as the VS Code extension. Google and Microsoft operations use GPDoc's provider API boundary; GitHub actions use the GitHub identity linked to the signed-in GPDoc account.
44
47
 
48
+ ## Git Knowledge
49
+
50
+ Git Knowledge commands call the GPDoc Cloudflare Worker and use `GPDOC_ACCESS_TOKEN`. They never receive GitHub App credentials, Cloudflare credentials, or source-index bindings. Run `gpdoc knowledge github-authorize` to use the GitHub App device flow, then use `connect` with a GitHub repository ID. After authorization, the CLI can list, inspect, search, connect, reindex, manage members, propose reviewable wiki changes, and disconnect repositories. Use `--json` for machine-readable cited search results.
51
+
45
52
  ## Installation
46
53
 
47
54
  After the public npm release, install with `npm install --global @gpdoc/cli`, or run one command without installation through `npx --yes @gpdoc/cli`. The initial distribution target is npm because the CLI requires Node.js 20 or later on every supported platform. A Homebrew formula and signed macOS, Windows, and Linux downloads should follow once release artifacts and signing are in place.
package/bin/gpdoc.js CHANGED
@@ -27,9 +27,115 @@ class CliError extends Error {
27
27
  }
28
28
 
29
29
  function usage() {
30
- return `Usage:\n gpdoc new OUTPUT [--title TITLE] [--json]\n gpdoc inspect INPUT [--json]\n gpdoc validate INPUT [--json]\n gpdoc convert INPUT --to gpdoc|markdown|html|text|json|docx|pdf [--output PATH | --in-place] [--json]\n gpdoc edit INPUT [--json]\n gpdoc login [complete] [--no-browser] [--json]\n gpdoc logout [--json]\n gpdoc whoami [--json]\n gpdoc google upload INPUT [--title TITLE] [--json]\n gpdoc google update INPUT --drive-id ID --item-id ID --revision REVISION [--json]\n gpdoc microsoft upload INPUT [--filename NAME] [--json]\n gpdoc microsoft update INPUT --drive-id ID --item-id ID [--json]\n gpdoc microsoft share --drive-id ID --item-id ID --role view|edit --scope anonymous|organization [--json]\n gpdoc gist create INPUT [--private] [--description TEXT] [--json]\n gpdoc gist update GIST_ID INPUT [--description TEXT] [--json]\n gpdoc repo put OWNER/REPOSITORY INPUT --path REMOTE_PATH [--branch BRANCH] [--message TEXT] [--json]\n`;
30
+ return `Usage:\n gpdoc new OUTPUT [--title TITLE] [--json]\n gpdoc inspect INPUT [--json]\n gpdoc validate INPUT [--json]\n gpdoc convert INPUT --to gpdoc|markdown|html|text|json|docx|pdf [--output PATH | --in-place] [--json]\n gpdoc edit INPUT [--json]\n gpdoc login [complete] [--no-browser] [--json]\n gpdoc logout [--json]\n gpdoc whoami [--json]\n gpdoc google upload INPUT [--title TITLE] [--json]\n gpdoc google update INPUT --drive-id ID --item-id ID --revision REVISION [--json]\n gpdoc microsoft upload INPUT [--filename NAME] [--json]\n gpdoc microsoft update INPUT --drive-id ID --item-id ID [--json]\n gpdoc microsoft share --drive-id ID --item-id ID --role view|edit --scope anonymous|organization [--json]\n gpdoc gist create INPUT [--private] [--description TEXT] [--json]\n gpdoc gist update GIST_ID INPUT [--description TEXT] [--json]\n gpdoc repo put OWNER/REPOSITORY INPUT --path REMOTE_PATH [--branch BRANCH] [--message TEXT] [--json]\n gpdoc knowledge github-authorize [--json]\n gpdoc knowledge list [--json]\n gpdoc knowledge status REPOSITORY_ID [--json]\n gpdoc knowledge search REPOSITORY_ID[,REPOSITORY_ID] QUERY [--json]\n gpdoc knowledge connect GITHUB_REPOSITORY_ID [--json]\n gpdoc knowledge reindex REPOSITORY_ID [--json]\n gpdoc knowledge members REPOSITORY_ID [ACCOUNT_ID [ROLE]] [--json]\n gpdoc knowledge wiki-propose REPOSITORY_ID PATH CONTENT [--json]\n gpdoc knowledge disconnect REPOSITORY_ID --yes [--json]\n`;
31
31
  }
32
32
 
33
+ function knowledgeError(response, payload) {
34
+ const error = new CliError(payload?.code || 'GIT_KNOWLEDGE_REQUEST_FAILED', payload?.error || `Git Knowledge request failed (${response.status}).`);
35
+ error.status = response.status;
36
+ return error;
37
+ }
38
+
39
+ async function knowledgeRequest(pathname, options = {}, runtime = {}) {
40
+ const env = runtime.env || process.env;
41
+ const accessToken = String(env.GPDOC_ACCESS_TOKEN || '').trim();
42
+ if (!accessToken) throw new CliError('AUTH_REQUIRED', 'Set GPDOC_ACCESS_TOKEN from a GPDoc device login before using Git Knowledge.');
43
+ const baseUrl = String(env.GPDOC_API_BASE_URL || 'https://gpdoc.io').trim().replace(/\/+$/, '');
44
+ const response = await fetch(`${baseUrl}/api/git-knowledge${pathname}`, {
45
+ ...options,
46
+ headers: { authorization: `Bearer ${accessToken}`, ...(options.body ? { 'content-type': 'application/json' } : {}), ...(options.headers || {}) },
47
+ });
48
+ const payload = await response.json().catch(() => ({}));
49
+ if (!response.ok) throw knowledgeError(response, payload);
50
+ return payload;
51
+ }
52
+
53
+ async function runKnowledge(argv, runtime = {}) {
54
+ const request = (pathname, options) => knowledgeRequest(pathname, options, runtime);
55
+ const json = argv.includes('--json');
56
+ const args = argv.filter((value) => value !== '--json');
57
+ const [command, ...positionals] = args;
58
+ if (!command || ['help', '--help', '-h'].includes(command)) {
59
+ process.stdout.write(usage());
60
+ return;
61
+ }
62
+ if (command === 'github-authorize') {
63
+ const started = await request('/github/device/authorizations', { method: 'POST', body: '{}' });
64
+ if (json) printResult({ state: started.state, verificationUri: started.verificationUriComplete || started.verificationUri, userCode: started.userCode, expiresAt: started.expiresAt }, true);
65
+ else process.stdout.write(`Open ${started.verificationUriComplete || started.verificationUri} and enter code: ${started.userCode}\n`);
66
+ const deadline = new Date(started.expiresAt).getTime();
67
+ let intervalMs = Math.max(5, Number(started.intervalSeconds || 5)) * 1000;
68
+ while (Date.now() < deadline) {
69
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
70
+ const completed = await request('/github/device/authorizations/complete', { method: 'POST', body: '{}' });
71
+ if (completed.state === 'complete') {
72
+ if (!json) process.stdout.write('GitHub authorization completed. You can now connect a repository.\n');
73
+ return;
74
+ }
75
+ intervalMs = Math.max(5, Number(completed.intervalSeconds || intervalMs / 1000)) * 1000;
76
+ }
77
+ throw new CliError('GITHUB_DEVICE_AUTH_EXPIRED', 'GitHub device authorization expired. Run gpdoc knowledge github-authorize again.');
78
+ }
79
+ if (command === 'list') {
80
+ const result = await request('/repositories');
81
+ printResult(result.repositories || [], json);
82
+ return;
83
+ }
84
+ if (command === 'status') {
85
+ const repositoryId = positionals[0];
86
+ if (!repositoryId) throw new CliError('USAGE', 'gpdoc knowledge status requires a repository id.');
87
+ const result = await request(`/repositories/${encodeURIComponent(repositoryId)}`);
88
+ printResult(result.repository || result, json);
89
+ return;
90
+ }
91
+ if (command === 'search') {
92
+ const [scope, ...queryParts] = positionals;
93
+ if (!scope || !queryParts.length) throw new CliError('USAGE', 'gpdoc knowledge search requires repository ids and a query.');
94
+ const result = await request('/search', { method: 'POST', body: JSON.stringify({ repositoryIds: scope.split(',').map((value) => value.trim()).filter(Boolean), query: queryParts.join(' ') }) });
95
+ printResult(json ? result : (result.results || []).map((entry) => `${entry.citation}\n${entry.excerpt}\n`).join('\n') || 'No cited results.', json);
96
+ return;
97
+ }
98
+ if (command === 'connect') {
99
+ const githubRepositoryId = positionals[0];
100
+ if (!githubRepositoryId) throw new CliError('USAGE', 'gpdoc knowledge connect requires a GitHub repository id.');
101
+ const result = await request('/repositories', { method: 'POST', body: JSON.stringify({ githubRepositoryId }) });
102
+ printResult(result.repository || result, json);
103
+ return;
104
+ }
105
+ if (command === 'reindex') {
106
+ const repositoryId = positionals[0];
107
+ if (!repositoryId) throw new CliError('USAGE', 'gpdoc knowledge reindex requires a repository id.');
108
+ const result = await request(`/repositories/${encodeURIComponent(repositoryId)}/ingest`, { method: 'POST', body: '{}' });
109
+ printResult(result, json);
110
+ return;
111
+ }
112
+ if (command === 'members') {
113
+ const [repositoryId, accountId, role = 'viewer'] = positionals;
114
+ if (!repositoryId) throw new CliError('USAGE', 'gpdoc knowledge members requires a repository id.');
115
+ const result = accountId
116
+ ? await request(`/repositories/${encodeURIComponent(repositoryId)}/members`, { method: 'POST', body: JSON.stringify({ accountId, role }) })
117
+ : await request(`/repositories/${encodeURIComponent(repositoryId)}/members`);
118
+ printResult(result.member || result.members || result, json);
119
+ return;
120
+ }
121
+ if (command === 'wiki-propose') {
122
+ const [repositoryId, pathValue, ...contentParts] = positionals;
123
+ if (!repositoryId || !pathValue || !contentParts.length) throw new CliError('USAGE', 'gpdoc knowledge wiki-propose requires a repository id, wiki path, and Markdown content.');
124
+ const result = await request(`/repositories/${encodeURIComponent(repositoryId)}/wiki-update`, { method: 'POST', body: JSON.stringify({ changes: [{ path: pathValue, content: contentParts.join(' ') }] }) });
125
+ printResult(result, json);
126
+ return;
127
+ }
128
+ if (command === 'disconnect') {
129
+ const repositoryId = positionals[0];
130
+ if (!repositoryId || !argv.includes('--yes')) throw new CliError('USAGE', 'gpdoc knowledge disconnect requires a repository id and --yes.');
131
+ const result = await request(`/repositories/${encodeURIComponent(repositoryId)}`, { method: 'DELETE' });
132
+ printResult(result, json);
133
+ return;
134
+ }
135
+ throw new CliError('USAGE', `Unknown Git Knowledge command: ${command}.`);
136
+ }
137
+
138
+
33
139
  function parseArguments(argv) {
34
140
  const [command, ...rest] = argv;
35
141
  if (!command || ['--help', '-h', 'help'].includes(command)) return { command: 'help', positionals: [], options: {} };
@@ -105,6 +211,29 @@ function printResult(result, json) {
105
211
  else process.stdout.write(`${Object.entries(result).map(([key, value]) => `${key}: ${value}`).join('\n')}\n`);
106
212
  }
107
213
 
214
+ // @spec CLI-029
215
+ function renderWhoami(status) {
216
+ const { identity, pending, ...fields } = status;
217
+ const lines = Object.entries(fields).map(([key, value]) => `${key}: ${value}`);
218
+ if (identity && typeof identity === 'object') {
219
+ const orderedClaims = ['email', 'name', 'nickname', 'sub'];
220
+ const claimEntries = [
221
+ ...orderedClaims.filter((key) => typeof identity[key] === 'string').map((key) => [key, identity[key]]),
222
+ ...Object.entries(identity).filter(([key, value]) => !orderedClaims.includes(key) && typeof value === 'string'),
223
+ ];
224
+ if (claimEntries.length) {
225
+ lines.push('identity:');
226
+ lines.push(...claimEntries.map(([key, value]) => ` ${key}: ${value}`));
227
+ } else {
228
+ lines.push('identity: unavailable');
229
+ }
230
+ } else if (identity === null) {
231
+ lines.push('identity: null');
232
+ }
233
+ if (pending) lines.push('pending: true');
234
+ return lines.join('\n');
235
+ }
236
+
108
237
  function printError(error, json) {
109
238
  const result = { error: error.code || 'ERROR', message: error.message || String(error) };
110
239
  if (json) process.stderr.write(`${JSON.stringify(result)}\n`);
@@ -182,6 +311,10 @@ function renderRemote(result, json) {
182
311
 
183
312
  // @spec CLI-001, CLI-002, CLI-003, CLI-004, CLI-005, CLI-006, CLI-007, CLI-008, CLI-009, CLI-014, CLI-015, CLI-016, CLI-017, CLI-018, CLI-019, CLI-020, CLI-021, CLI-022, CLI-023, CLI-024, CLI-025, CLI-028
184
313
  export async function run(argv, runtime = {}) {
314
+ if (argv[0] === 'knowledge') {
315
+ await runKnowledge(argv.slice(1), runtime);
316
+ return;
317
+ }
185
318
  const parsed = parseArguments(argv);
186
319
  if (parsed.command === 'help') {
187
320
  process.stdout.write(usage());
@@ -218,7 +351,8 @@ export async function run(argv, runtime = {}) {
218
351
  return;
219
352
  }
220
353
  if (parsed.command === 'whoami') {
221
- printResult(await auth.status(), parsed.options.json);
354
+ const status = await auth.status();
355
+ printResult(parsed.options.json ? status : renderWhoami(status), parsed.options.json);
222
356
  return;
223
357
  }
224
358
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gpdoc/cli",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "description": "GPDoc command-line file conversion and validation tools",
6
6
  "repository": "https://github.com/repetere/gpdoc.git",