@gpdoc/cli 1.2.0 → 1.2.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
@@ -18,7 +18,7 @@ Conversions write to standard output or an explicit output path. Existing files
18
18
 
19
19
  ## Account and remote providers
20
20
 
21
- Use `gpdoc login` to begin GPDoc's device authorization flow, open the printed verification URL in a browser, and then run `gpdoc login complete`. `gpdoc whoami --json` reports the CLI session without including credentials. `gpdoc logout` clears only the local CLI credential file.
21
+ Use `gpdoc login` to begin GPDoc's device authorization flow. It attempts to open your browser, waits for authorization, and writes the local CLI session before returning. If it cannot launch a browser, use the printed URL and code manually. Use `gpdoc login --no-browser` in a headless session. If you interrupt the command after completing browser authorization, run `gpdoc login complete` before the displayed expiry to finish saving the local session. `gpdoc whoami --json` reports the CLI session without including credentials. `gpdoc logout` clears only the local CLI credential file.
22
22
 
23
23
  For CI, set `GPDOC_ACCESS_TOKEN` instead of signing in interactively. The CLI does not persist that environment value. Interactive credentials are stored outside the current workspace with user-only permissions. Provider tokens are not shown in command output.
24
24
 
package/bin/gpdoc.js CHANGED
@@ -27,7 +27,7 @@ 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] [--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`;
31
31
  }
32
32
 
33
33
  function parseArguments(argv) {
@@ -42,7 +42,7 @@ function parseArguments(argv) {
42
42
  continue;
43
43
  }
44
44
  const key = value.slice(2);
45
- if (['json', 'in-place', 'private'].includes(key)) {
45
+ if (['json', 'in-place', 'private', 'no-browser'].includes(key)) {
46
46
  options[key] = true;
47
47
  continue;
48
48
  }
@@ -119,6 +119,47 @@ function launchEditor(editor, target) {
119
119
  });
120
120
  }
121
121
 
122
+ function delay(milliseconds) {
123
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
124
+ }
125
+
126
+ // The device flow does not send a callback to the local terminal. The foreground
127
+ // CLI process opens the provider page, then polls the token endpoint itself.
128
+ export async function openBrowser(url, { platform = process.platform, spawnImpl = spawn } = {}) {
129
+ const command = platform === 'darwin'
130
+ ? ['open', [url]]
131
+ : platform === 'win32'
132
+ ? ['cmd', ['/c', 'start', '', url]]
133
+ : ['xdg-open', [url]];
134
+ try {
135
+ return await new Promise((resolve) => {
136
+ let settled = false;
137
+ const complete = (opened) => {
138
+ if (settled) return;
139
+ settled = true;
140
+ resolve(opened);
141
+ };
142
+ const child = spawnImpl(command[0], command[1], { detached: true, stdio: 'ignore', windowsHide: true });
143
+ child.once('error', () => complete(false));
144
+ child.once('spawn', () => {
145
+ child.unref?.();
146
+ complete(true);
147
+ });
148
+ });
149
+ } catch {
150
+ return false;
151
+ }
152
+ }
153
+
154
+ async function waitForDeviceAuthorization(auth, sleep = delay) {
155
+ for (;;) {
156
+ const result = await auth.complete();
157
+ if (result.status !== 'pending') return result;
158
+ const retryAfterSeconds = Math.max(1, Number(result.retryAfterSeconds || 1));
159
+ await sleep(retryAfterSeconds * 1000);
160
+ }
161
+ }
162
+
122
163
  function requireOption(options, key, message) {
123
164
  if (!options[key]) throw new CliError('USAGE', message || `--${key} is required.`);
124
165
  return options[key];
@@ -139,7 +180,7 @@ function renderRemote(result, json) {
139
180
  return printResult(result, false);
140
181
  }
141
182
 
142
- // @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
183
+ // @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
143
184
  export async function run(argv, runtime = {}) {
144
185
  const parsed = parseArguments(argv);
145
186
  if (parsed.command === 'help') {
@@ -154,8 +195,22 @@ export async function run(argv, runtime = {}) {
154
195
  const remote = runtime.remote || createRemoteClient({ auth, env: runtime.env || process.env, fetchImpl: runtime.fetchImpl || globalThis.fetch });
155
196
 
156
197
  if (parsed.command === 'login') {
157
- const result = parsed.positionals[0] === 'complete' ? await auth.complete() : await auth.start();
158
- printResult(result, parsed.options.json);
198
+ if (parsed.positionals[0] === 'complete') {
199
+ printResult(await auth.complete(), parsed.options.json);
200
+ return;
201
+ }
202
+ const started = await auth.start();
203
+ const verificationUrl = started.verificationUriComplete || started.verificationUri;
204
+ const browserOpened = parsed.options['no-browser']
205
+ ? false
206
+ : await (runtime.openBrowser || openBrowser)(verificationUrl);
207
+ if (!parsed.options.json) {
208
+ printResult(started, false);
209
+ if (browserOpened) process.stdout.write('Opened your browser. Complete sign-in there; GPDoc will finish this command automatically.\n');
210
+ else process.stdout.write(`Open ${verificationUrl} in a browser, then return here. GPDoc is waiting for authorization.\n`);
211
+ }
212
+ const completed = await waitForDeviceAuthorization(auth, runtime.sleep || delay);
213
+ printResult({ ...started, browserOpened, ...completed }, parsed.options.json);
159
214
  return;
160
215
  }
161
216
  if (parsed.command === 'logout') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gpdoc/cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "description": "GPDoc command-line file conversion and validation tools",
6
6
  "repository": "https://github.com/repetere/gpdoc.git",