@wafflebase/cli 0.4.9 → 0.5.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/dist/bin.js +99693 -11
- package/package.json +7 -6
- package/dist/bin.d.ts +0 -2
- package/dist/client/dry-run.d.ts +0 -5
- package/dist/client/dry-run.js +0 -16
- package/dist/client/http-client.d.ts +0 -59
- package/dist/client/http-client.js +0 -178
- package/dist/commands/api-keys.d.ts +0 -2
- package/dist/commands/api-keys.js +0 -54
- package/dist/commands/cells.d.ts +0 -2
- package/dist/commands/cells.js +0 -108
- package/dist/commands/ctx.d.ts +0 -5
- package/dist/commands/ctx.js +0 -56
- package/dist/commands/docs.d.ts +0 -3
- package/dist/commands/docs.js +0 -320
- package/dist/commands/login.d.ts +0 -2
- package/dist/commands/login.js +0 -220
- package/dist/commands/logout.d.ts +0 -2
- package/dist/commands/logout.js +0 -10
- package/dist/commands/root.d.ts +0 -18
- package/dist/commands/root.js +0 -37
- package/dist/commands/schema.d.ts +0 -2
- package/dist/commands/schema.js +0 -28
- package/dist/commands/sheets-export.d.ts +0 -2
- package/dist/commands/sheets-export.js +0 -52
- package/dist/commands/sheets-import.d.ts +0 -2
- package/dist/commands/sheets-import.js +0 -88
- package/dist/commands/sheets.d.ts +0 -2
- package/dist/commands/sheets.js +0 -17
- package/dist/commands/slides.d.ts +0 -3
- package/dist/commands/slides.js +0 -227
- package/dist/commands/status.d.ts +0 -2
- package/dist/commands/status.js +0 -22
- package/dist/commands/tabs.d.ts +0 -2
- package/dist/commands/tabs.js +0 -20
- package/dist/config/config.d.ts +0 -38
- package/dist/config/config.js +0 -135
- package/dist/config/session.d.ts +0 -49
- package/dist/config/session.js +0 -74
- package/dist/docs/content.d.ts +0 -37
- package/dist/docs/content.js +0 -90
- package/dist/docs/docx-export.d.ts +0 -23
- package/dist/docs/docx-export.js +0 -14
- package/dist/docs/docx-import.d.ts +0 -36
- package/dist/docs/docx-import.js +0 -70
- package/dist/docs/dom-polyfill.d.ts +0 -1
- package/dist/docs/dom-polyfill.js +0 -29
- package/dist/docs/fontkit-measurer.d.ts +0 -41
- package/dist/docs/fontkit-measurer.js +0 -64
- package/dist/docs/image-fetcher.d.ts +0 -37
- package/dist/docs/image-fetcher.js +0 -38
- package/dist/docs/import.d.ts +0 -72
- package/dist/docs/import.js +0 -146
- package/dist/docs/page-range.d.ts +0 -29
- package/dist/docs/page-range.js +0 -74
- package/dist/docs/page-slice.d.ts +0 -45
- package/dist/docs/page-slice.js +0 -61
- package/dist/docs/paginate.d.ts +0 -13
- package/dist/docs/paginate.js +0 -19
- package/dist/docs/pdf-export.d.ts +0 -47
- package/dist/docs/pdf-export.js +0 -95
- package/dist/output/binary.d.ts +0 -37
- package/dist/output/binary.js +0 -47
- package/dist/output/csv.d.ts +0 -4
- package/dist/output/csv.js +0 -25
- package/dist/output/formatter.d.ts +0 -4
- package/dist/output/formatter.js +0 -41
- package/dist/output/json.d.ts +0 -1
- package/dist/output/json.js +0 -3
- package/dist/output/table.d.ts +0 -4
- package/dist/output/table.js +0 -14
- package/dist/schema/registry.d.ts +0 -42
- package/dist/schema/registry.js +0 -375
- package/dist/slides/content.d.ts +0 -41
- package/dist/slides/content.js +0 -122
- package/dist/slides/import.d.ts +0 -82
- package/dist/slides/import.js +0 -170
- package/dist/slides/pptx-export.d.ts +0 -6
- package/dist/slides/pptx-export.js +0 -10
- package/dist/slides/pptx-import.d.ts +0 -35
- package/dist/slides/pptx-import.js +0 -49
- package/dist/util/csv-parse.d.ts +0 -21
- package/dist/util/csv-parse.js +0 -99
package/dist/commands/docs.js
DELETED
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
import { extname } from 'node:path';
|
|
2
|
-
import { getGlobalOpts, getClient, getConfig } from './root.js';
|
|
3
|
-
import { output, outputError } from '../output/formatter.js';
|
|
4
|
-
import { printDryRun } from '../client/dry-run.js';
|
|
5
|
-
import { parseContentFormat, runDocsContent } from '../docs/content.js';
|
|
6
|
-
import { exportPdf } from '../docs/pdf-export.js';
|
|
7
|
-
import { exportDocx } from '../docs/docx-export.js';
|
|
8
|
-
import { createImageFetcher } from '../docs/image-fetcher.js';
|
|
9
|
-
import { parsePageRange } from '../docs/page-range.js';
|
|
10
|
-
import { writeBinary } from '../output/binary.js';
|
|
11
|
-
import { runDocsImport } from '../docs/import.js';
|
|
12
|
-
function parseType(value) {
|
|
13
|
-
if (value === undefined)
|
|
14
|
-
return undefined;
|
|
15
|
-
if (value !== 'doc' && value !== 'sheet') {
|
|
16
|
-
throw new Error(`Invalid --type "${value}". Use "doc" or "sheet".`);
|
|
17
|
-
}
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
const VALID_EXPORT_FORMATS = ['pdf', 'docx'];
|
|
21
|
-
function detectExportFormat(file, formatFlag) {
|
|
22
|
-
if (formatFlag) {
|
|
23
|
-
if (!VALID_EXPORT_FORMATS.includes(formatFlag)) {
|
|
24
|
-
throw new Error(`Invalid --format "${formatFlag}". Use one of: ${VALID_EXPORT_FORMATS.join(', ')}.`);
|
|
25
|
-
}
|
|
26
|
-
return formatFlag;
|
|
27
|
-
}
|
|
28
|
-
const ext = extname(file).toLowerCase();
|
|
29
|
-
if (ext === '.pdf')
|
|
30
|
-
return 'pdf';
|
|
31
|
-
if (ext === '.docx')
|
|
32
|
-
return 'docx';
|
|
33
|
-
throw new Error(`Cannot infer format from "${file}". Pass --format pdf|docx, or use a .pdf/.docx extension.`);
|
|
34
|
-
}
|
|
35
|
-
export function registerDocsCommand(program) {
|
|
36
|
-
const doc = program
|
|
37
|
-
.command('docs')
|
|
38
|
-
.alias('doc')
|
|
39
|
-
.alias('document')
|
|
40
|
-
.alias('documents')
|
|
41
|
-
.description('Manage documents');
|
|
42
|
-
doc
|
|
43
|
-
.command('list')
|
|
44
|
-
.description('List documents in workspace')
|
|
45
|
-
.option('--type <type>', 'Filter by document type (doc|sheet)')
|
|
46
|
-
.action(async function () {
|
|
47
|
-
const opts = getGlobalOpts(this);
|
|
48
|
-
const { type: typeStr } = this.opts();
|
|
49
|
-
try {
|
|
50
|
-
const filterType = parseType(typeStr);
|
|
51
|
-
const res = await getClient(opts).listDocuments();
|
|
52
|
-
if (!res.ok)
|
|
53
|
-
throw new Error(`HTTP ${res.status}`);
|
|
54
|
-
let data = res.data;
|
|
55
|
-
if (filterType && Array.isArray(data)) {
|
|
56
|
-
data = data.filter((d) => (d.type ?? 'sheet') === filterType);
|
|
57
|
-
}
|
|
58
|
-
output(data, opts.format, opts.quiet);
|
|
59
|
-
}
|
|
60
|
-
catch (e) {
|
|
61
|
-
outputError(e, opts.quiet);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
doc
|
|
65
|
-
.command('create <title>')
|
|
66
|
-
.description('Create a new document')
|
|
67
|
-
.option('--type <type>', 'Document type (doc|sheet)', 'sheet')
|
|
68
|
-
.action(async function (title) {
|
|
69
|
-
const opts = getGlobalOpts(this);
|
|
70
|
-
const { type: typeStr } = this.opts();
|
|
71
|
-
try {
|
|
72
|
-
const type = parseType(typeStr) ?? 'sheet';
|
|
73
|
-
if (opts.dryRun) {
|
|
74
|
-
printDryRun(getConfig(opts), 'POST', '/documents', { title, type });
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const res = await getClient(opts).createDocument(title, type);
|
|
78
|
-
if (!res.ok)
|
|
79
|
-
throw new Error(`HTTP ${res.status}`);
|
|
80
|
-
output(res.data, opts.format, opts.quiet);
|
|
81
|
-
}
|
|
82
|
-
catch (e) {
|
|
83
|
-
outputError(e, opts.quiet);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
doc
|
|
87
|
-
.command('get <doc-id>')
|
|
88
|
-
.description('Show document metadata')
|
|
89
|
-
.action(async function (docId) {
|
|
90
|
-
const opts = getGlobalOpts(this);
|
|
91
|
-
try {
|
|
92
|
-
const res = await getClient(opts).getDocument(docId);
|
|
93
|
-
if (!res.ok)
|
|
94
|
-
throw new Error(`HTTP ${res.status}`);
|
|
95
|
-
output(res.data, opts.format, opts.quiet);
|
|
96
|
-
}
|
|
97
|
-
catch (e) {
|
|
98
|
-
outputError(e, opts.quiet);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
doc
|
|
102
|
-
.command('rename <doc-id> <title>')
|
|
103
|
-
.description('Rename a document')
|
|
104
|
-
.action(async function (docId, title) {
|
|
105
|
-
const opts = getGlobalOpts(this);
|
|
106
|
-
if (opts.dryRun) {
|
|
107
|
-
printDryRun(getConfig(opts), 'PATCH', `/documents/${docId}`, { title });
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
const res = await getClient(opts).updateDocument(docId, title);
|
|
112
|
-
if (!res.ok)
|
|
113
|
-
throw new Error(`HTTP ${res.status}`);
|
|
114
|
-
output(res.data, opts.format, opts.quiet);
|
|
115
|
-
}
|
|
116
|
-
catch (e) {
|
|
117
|
-
outputError(e, opts.quiet);
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
doc
|
|
121
|
-
.command('delete <doc-id>')
|
|
122
|
-
.description('Delete a document')
|
|
123
|
-
.action(async function (docId) {
|
|
124
|
-
const opts = getGlobalOpts(this);
|
|
125
|
-
if (opts.dryRun) {
|
|
126
|
-
printDryRun(getConfig(opts), 'DELETE', `/documents/${docId}`);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
try {
|
|
130
|
-
const res = await getClient(opts).deleteDocument(docId);
|
|
131
|
-
if (!res.ok)
|
|
132
|
-
throw new Error(`HTTP ${res.status}`);
|
|
133
|
-
output(res.data, opts.format, opts.quiet);
|
|
134
|
-
}
|
|
135
|
-
catch (e) {
|
|
136
|
-
outputError(e, opts.quiet);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
doc
|
|
140
|
-
.command('content <doc-id>')
|
|
141
|
-
.description('Read document content as JSON, Markdown, or plain text')
|
|
142
|
-
// NOTE: `--format` is intentionally not redeclared here. The global
|
|
143
|
-
// `--format` option (declared in `createProgram`) catches the user's
|
|
144
|
-
// value because commander resolves duplicate-named flags through the
|
|
145
|
-
// earliest parent that owns them. Reading via `opts.format` (the
|
|
146
|
-
// merged form) keeps a single source of truth and lets
|
|
147
|
-
// `parseContentFormat` validate that the value is one of json|md|text.
|
|
148
|
-
.option('--pages <range>', 'Page range to include (e.g. 1-3,5)')
|
|
149
|
-
.option('--include-header-footer', 'Include header/footer (md/text)', false)
|
|
150
|
-
.option('--inline-images', 'Inline data: image URLs (md only)', false)
|
|
151
|
-
.option('--out <file>', 'Output file (- for stdout)')
|
|
152
|
-
.option('--force', 'Overwrite existing output file', false)
|
|
153
|
-
.action(async function (docId) {
|
|
154
|
-
const opts = getGlobalOpts(this);
|
|
155
|
-
const local = this.opts();
|
|
156
|
-
try {
|
|
157
|
-
const format = parseContentFormat(opts.format);
|
|
158
|
-
if (opts.dryRun) {
|
|
159
|
-
printDryRun(getConfig(opts), 'GET', `/documents/${docId}/content`);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const res = await getClient(opts).getDocContent(docId);
|
|
163
|
-
if (!res.ok) {
|
|
164
|
-
const body = res.data;
|
|
165
|
-
if (body?.error) {
|
|
166
|
-
// Surface backend-shaped errors (e.g., TYPE_MISMATCH) verbatim
|
|
167
|
-
// so agents reading stderr can act on the `code` field.
|
|
168
|
-
console.error(JSON.stringify(body, null, 2));
|
|
169
|
-
process.exitCode = 1;
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
throw new Error(`HTTP ${res.status}`);
|
|
173
|
-
}
|
|
174
|
-
runDocsContent({
|
|
175
|
-
doc: res.data,
|
|
176
|
-
format,
|
|
177
|
-
pages: local.pages,
|
|
178
|
-
includeHeaderFooter: local.includeHeaderFooter,
|
|
179
|
-
inlineImages: local.inlineImages,
|
|
180
|
-
out: local.out,
|
|
181
|
-
force: local.force,
|
|
182
|
-
quiet: opts.quiet,
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
catch (e) {
|
|
186
|
-
outputError(e, opts.quiet);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
doc
|
|
190
|
-
.command('export <doc-id> <file>')
|
|
191
|
-
.description('Export a document to PDF or DOCX')
|
|
192
|
-
// NOTE: `--format` reuses the global option for the same reason
|
|
193
|
-
// documented on `docs content` above; we route through `opts.format`
|
|
194
|
-
// and use commander's `getOptionValueSourceWithGlobals` to tell a
|
|
195
|
-
// user-supplied value from the implicit default. That keeps an
|
|
196
|
-
// explicit `--format json` from silently falling through to the
|
|
197
|
-
// extension — it now surfaces as the documented "invalid format"
|
|
198
|
-
// error.
|
|
199
|
-
.option('--pages <range>', 'Page range to export (PDF only)')
|
|
200
|
-
.option('--include-header-footer', 'Include header/footer regions', true)
|
|
201
|
-
.option('--force', 'Overwrite existing output file', false)
|
|
202
|
-
.action(async function (docId, file) {
|
|
203
|
-
const opts = getGlobalOpts(this);
|
|
204
|
-
const local = this.opts();
|
|
205
|
-
try {
|
|
206
|
-
// `'cli'` source means the user typed the flag (or its alias)
|
|
207
|
-
// on the command line; anything else (`'default'`, `'env'`,
|
|
208
|
-
// `'config'`) is treated as "no override intended" so the
|
|
209
|
-
// file extension wins.
|
|
210
|
-
const formatSource = this.getOptionValueSourceWithGlobals('format');
|
|
211
|
-
const formatOverride = formatSource === 'cli' ? opts.format : undefined;
|
|
212
|
-
const format = detectExportFormat(file, formatOverride);
|
|
213
|
-
if (opts.dryRun) {
|
|
214
|
-
printDryRun(getConfig(opts), 'GET', `/documents/${docId}/content`);
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
const res = await getClient(opts).getDocContent(docId);
|
|
218
|
-
if (!res.ok) {
|
|
219
|
-
const body = res.data;
|
|
220
|
-
if (body?.error) {
|
|
221
|
-
console.error(JSON.stringify(body, null, 2));
|
|
222
|
-
process.exitCode = 1;
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
throw new Error(`HTTP ${res.status}`);
|
|
226
|
-
}
|
|
227
|
-
const fetchedDoc = res.data;
|
|
228
|
-
// Image inlines reference URLs that PdfExporter and DocxExporter
|
|
229
|
-
// can't resolve on their own. The fetcher walks each unique
|
|
230
|
-
// `src`, GETs it (via `globalThis.fetch`), and hands back a
|
|
231
|
-
// Blob. Relative URLs (`/images/<id>`) resolve against the
|
|
232
|
-
// configured server base so the same doc round-trips between
|
|
233
|
-
// the editor (browser-relative) and the CLI.
|
|
234
|
-
const imageFetcher = createImageFetcher({
|
|
235
|
-
serverBase: getConfig(opts).server,
|
|
236
|
-
});
|
|
237
|
-
let bytes;
|
|
238
|
-
if (format === 'pdf') {
|
|
239
|
-
// Pagination uses the FontkitMeasurer's coarse Latin estimate
|
|
240
|
-
// when no Korean fonts are needed; we still pre-page to know
|
|
241
|
-
// the document's page count for `parsePageRange`. PdfExporter
|
|
242
|
-
// does the authoritative paint-time pagination internally.
|
|
243
|
-
let pageRange = undefined;
|
|
244
|
-
if (local.pages) {
|
|
245
|
-
// Resolve pages against the rendered PDF's page count
|
|
246
|
-
// (rather than running our own paginator twice). We render
|
|
247
|
-
// the full PDF first, then strip non-selected pages —
|
|
248
|
-
// see `extractPages` in pdf-export.ts.
|
|
249
|
-
const fullPdf = await exportPdf(fetchedDoc, {
|
|
250
|
-
includeHeaderFooter: local.includeHeaderFooter,
|
|
251
|
-
imageFetcher,
|
|
252
|
-
});
|
|
253
|
-
const total = await pdfPageCount(fullPdf);
|
|
254
|
-
pageRange = parsePageRange(local.pages, total);
|
|
255
|
-
if (!opts.quiet) {
|
|
256
|
-
for (const w of pageRange.warnings)
|
|
257
|
-
console.error(w);
|
|
258
|
-
}
|
|
259
|
-
bytes = await exportPdf(fetchedDoc, {
|
|
260
|
-
includeHeaderFooter: local.includeHeaderFooter,
|
|
261
|
-
pages: pageRange,
|
|
262
|
-
imageFetcher,
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
bytes = await exportPdf(fetchedDoc, {
|
|
267
|
-
includeHeaderFooter: local.includeHeaderFooter,
|
|
268
|
-
imageFetcher,
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
if (local.pages && !opts.quiet) {
|
|
274
|
-
console.error('DOCX has no page concept — exporting full document, --pages ignored.');
|
|
275
|
-
}
|
|
276
|
-
bytes = await exportDocx(fetchedDoc, {
|
|
277
|
-
includeHeaderFooter: local.includeHeaderFooter,
|
|
278
|
-
imageFetcher,
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
writeBinary(bytes, file, { force: local.force, quiet: opts.quiet });
|
|
282
|
-
}
|
|
283
|
-
catch (e) {
|
|
284
|
-
outputError(e, opts.quiet);
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
registerDocsImportCommand(doc);
|
|
288
|
-
}
|
|
289
|
-
async function pdfPageCount(bytes) {
|
|
290
|
-
const { PDFDocument } = await import('pdf-lib');
|
|
291
|
-
const pdf = await PDFDocument.load(bytes);
|
|
292
|
-
return pdf.getPageCount();
|
|
293
|
-
}
|
|
294
|
-
export function registerDocsImportCommand(doc) {
|
|
295
|
-
doc
|
|
296
|
-
.command('import <file>')
|
|
297
|
-
.description('Import a .docx file as a new (or replacement) document')
|
|
298
|
-
.option('--title <title>', 'Document title (default: file basename)')
|
|
299
|
-
.option('--replace <doc-id>', 'Replace content of an existing document')
|
|
300
|
-
.option('--yes', 'Skip --replace confirmation prompt', false)
|
|
301
|
-
.action(async function (file) {
|
|
302
|
-
const opts = getGlobalOpts(this);
|
|
303
|
-
const local = this.opts();
|
|
304
|
-
try {
|
|
305
|
-
const result = await runDocsImport({
|
|
306
|
-
file,
|
|
307
|
-
title: local.title,
|
|
308
|
-
replace: local.replace,
|
|
309
|
-
yes: local.yes,
|
|
310
|
-
quiet: opts.quiet,
|
|
311
|
-
dryRun: opts.dryRun,
|
|
312
|
-
}, getClient(opts));
|
|
313
|
-
if (result.exitCode !== 0)
|
|
314
|
-
process.exitCode = result.exitCode;
|
|
315
|
-
}
|
|
316
|
-
catch (e) {
|
|
317
|
-
outputError(e, opts.quiet);
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
}
|
package/dist/commands/login.d.ts
DELETED
package/dist/commands/login.js
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
import { createServer } from 'node:http';
|
|
2
|
-
import { createInterface } from 'node:readline';
|
|
3
|
-
import { loadSession, saveSession, decodeJwtExpiry, } from '../config/session.js';
|
|
4
|
-
import { DEFAULT_SERVER } from '../config/config.js';
|
|
5
|
-
export function registerLoginCommand(program) {
|
|
6
|
-
program
|
|
7
|
-
.command('login')
|
|
8
|
-
.description('Log in via GitHub OAuth in the browser')
|
|
9
|
-
.action(async function () {
|
|
10
|
-
const parentOpts = this.optsWithGlobals();
|
|
11
|
-
const server = (parentOpts.server ?? DEFAULT_SERVER).replace(/\/$/, '');
|
|
12
|
-
// 1. Check existing session
|
|
13
|
-
const existing = loadSession();
|
|
14
|
-
if (existing) {
|
|
15
|
-
const answer = await ask(`Logged in as ${existing.user.username}. Continue? [Y/n] `);
|
|
16
|
-
if (answer.toLowerCase() === 'n') {
|
|
17
|
-
console.log('Cancelled.');
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
// 2. Start local HTTP server
|
|
22
|
-
const { port, waitForCallback, close } = await startCallbackServer();
|
|
23
|
-
// 3. Build OAuth URL and open browser
|
|
24
|
-
const oauthUrl = `${server}/auth/github?mode=cli&port=${port}`;
|
|
25
|
-
console.error(`Opening browser: ${oauthUrl}`);
|
|
26
|
-
console.error('If the browser does not open, visit the URL above.');
|
|
27
|
-
try {
|
|
28
|
-
const open = (await import('open')).default;
|
|
29
|
-
await open(oauthUrl);
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
// Browser open failed — URL was already printed
|
|
33
|
-
}
|
|
34
|
-
// 4. Wait for callback
|
|
35
|
-
let code;
|
|
36
|
-
try {
|
|
37
|
-
code = await waitForCallback();
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
40
|
-
close();
|
|
41
|
-
}
|
|
42
|
-
// 5. Exchange code for tokens
|
|
43
|
-
const exchangeRes = await fetch(`${server}/auth/cli/exchange`, {
|
|
44
|
-
method: 'POST',
|
|
45
|
-
headers: { 'Content-Type': 'application/json' },
|
|
46
|
-
body: JSON.stringify({ code }),
|
|
47
|
-
});
|
|
48
|
-
if (!exchangeRes.ok) {
|
|
49
|
-
console.error('Token exchange failed. Try again with `wafflebase login`.');
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
const tokens = (await exchangeRes.json());
|
|
53
|
-
// 6. Get user info
|
|
54
|
-
const meRes = await fetch(`${server}/auth/me`, {
|
|
55
|
-
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
|
56
|
-
});
|
|
57
|
-
if (!meRes.ok) {
|
|
58
|
-
console.error('Failed to fetch user info.');
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
const user = (await meRes.json());
|
|
62
|
-
// 7. Get workspace list
|
|
63
|
-
const wsRes = await fetch(`${server}/workspaces`, {
|
|
64
|
-
headers: { Authorization: `Bearer ${tokens.accessToken}` },
|
|
65
|
-
});
|
|
66
|
-
if (!wsRes.ok) {
|
|
67
|
-
console.error(`Failed to fetch workspaces (HTTP ${wsRes.status}). Try again with \`wafflebase login\`.`);
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
const workspaces = (await wsRes.json());
|
|
71
|
-
// 8. Select workspace
|
|
72
|
-
let activeWorkspace = '';
|
|
73
|
-
if (workspaces.length === 0) {
|
|
74
|
-
console.log('No workspaces found.');
|
|
75
|
-
}
|
|
76
|
-
else if (workspaces.length === 1) {
|
|
77
|
-
activeWorkspace = workspaces[0].id;
|
|
78
|
-
console.log(`Workspace: ${workspaces[0].name}`);
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
console.log('Select a workspace:');
|
|
82
|
-
workspaces.forEach((ws, i) => {
|
|
83
|
-
console.log(` ${i + 1}. ${ws.name} (${ws.id.slice(0, 8)})`);
|
|
84
|
-
});
|
|
85
|
-
const choice = await ask('Enter number: ');
|
|
86
|
-
const idx = parseInt(choice, 10) - 1;
|
|
87
|
-
if (idx >= 0 && idx < workspaces.length) {
|
|
88
|
-
activeWorkspace = workspaces[idx].id;
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
activeWorkspace = workspaces[0].id;
|
|
92
|
-
console.log(`Invalid choice, using ${workspaces[0].name}.`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// 9. Save session
|
|
96
|
-
const session = {
|
|
97
|
-
server,
|
|
98
|
-
user,
|
|
99
|
-
accessToken: tokens.accessToken,
|
|
100
|
-
refreshToken: tokens.refreshToken,
|
|
101
|
-
expiresAt: decodeJwtExpiry(tokens.accessToken),
|
|
102
|
-
activeWorkspace,
|
|
103
|
-
workspaces,
|
|
104
|
-
};
|
|
105
|
-
saveSession(session);
|
|
106
|
-
console.log(`Logged in as ${user.username}.`);
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
function ask(prompt) {
|
|
110
|
-
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
111
|
-
return new Promise((resolve) => {
|
|
112
|
-
rl.question(prompt, (answer) => {
|
|
113
|
-
rl.close();
|
|
114
|
-
resolve(answer.trim());
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
function startCallbackServer() {
|
|
119
|
-
return new Promise((resolve, reject) => {
|
|
120
|
-
let settled = false;
|
|
121
|
-
let callbackResolve;
|
|
122
|
-
let callbackReject;
|
|
123
|
-
const callbackPromise = new Promise((res, rej) => {
|
|
124
|
-
callbackResolve = res;
|
|
125
|
-
callbackReject = rej;
|
|
126
|
-
});
|
|
127
|
-
const srv = createServer((req, res) => {
|
|
128
|
-
const url = new URL(req.url ?? '/', `http://127.0.0.1`);
|
|
129
|
-
if (url.pathname !== '/callback') {
|
|
130
|
-
res.writeHead(404);
|
|
131
|
-
res.end('Not found');
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
const code = url.searchParams.get('code');
|
|
135
|
-
if (!code) {
|
|
136
|
-
res.writeHead(400);
|
|
137
|
-
res.end('Missing code');
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
141
|
-
res.end(`<!DOCTYPE html>
|
|
142
|
-
<html lang="en">
|
|
143
|
-
<head>
|
|
144
|
-
<meta charset="utf-8">
|
|
145
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
146
|
-
<title>Wafflebase CLI</title>
|
|
147
|
-
<style>
|
|
148
|
-
body {
|
|
149
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
150
|
-
display: flex;
|
|
151
|
-
justify-content: center;
|
|
152
|
-
align-items: center;
|
|
153
|
-
min-height: 100vh;
|
|
154
|
-
margin: 0;
|
|
155
|
-
background: #fafafa;
|
|
156
|
-
color: #1a1a1a;
|
|
157
|
-
}
|
|
158
|
-
.card {
|
|
159
|
-
text-align: center;
|
|
160
|
-
padding: 3rem 2.5rem;
|
|
161
|
-
background: #fff;
|
|
162
|
-
border-radius: 12px;
|
|
163
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.04);
|
|
164
|
-
}
|
|
165
|
-
.icon { font-size: 2.5rem; margin-bottom: 0.5rem; }
|
|
166
|
-
h2 { margin: 0 0 0.5rem; font-size: 1.25rem; font-weight: 600; }
|
|
167
|
-
p { margin: 0; color: #666; font-size: 0.95rem; }
|
|
168
|
-
</style>
|
|
169
|
-
</head>
|
|
170
|
-
<body>
|
|
171
|
-
<div class="card">
|
|
172
|
-
<div class="icon">✓</div>
|
|
173
|
-
<h2>Login successful!</h2>
|
|
174
|
-
<p>You can close this tab and return to the terminal.</p>
|
|
175
|
-
</div>
|
|
176
|
-
</body>
|
|
177
|
-
</html>`);
|
|
178
|
-
if (!settled) {
|
|
179
|
-
settled = true;
|
|
180
|
-
callbackResolve(code);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
const timeout = setTimeout(() => {
|
|
184
|
-
if (!settled) {
|
|
185
|
-
settled = true;
|
|
186
|
-
callbackReject(new Error('Login timed out. Try again with `wafflebase login`.'));
|
|
187
|
-
}
|
|
188
|
-
srv.close();
|
|
189
|
-
}, 30_000);
|
|
190
|
-
// Try to listen on a random port (up to 3 attempts)
|
|
191
|
-
let attempts = 0;
|
|
192
|
-
const tryListen = () => {
|
|
193
|
-
attempts++;
|
|
194
|
-
srv.listen(0, '127.0.0.1', () => {
|
|
195
|
-
const addr = srv.address();
|
|
196
|
-
if (!addr || typeof addr === 'string') {
|
|
197
|
-
reject(new Error('Failed to start callback server'));
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
resolve({
|
|
201
|
-
port: addr.port,
|
|
202
|
-
waitForCallback: () => callbackPromise,
|
|
203
|
-
close: () => {
|
|
204
|
-
clearTimeout(timeout);
|
|
205
|
-
srv.close();
|
|
206
|
-
},
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
};
|
|
210
|
-
srv.on('error', (err) => {
|
|
211
|
-
if (err.code === 'EADDRINUSE' && attempts < 3) {
|
|
212
|
-
tryListen();
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
reject(err);
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
tryListen();
|
|
219
|
-
});
|
|
220
|
-
}
|
package/dist/commands/logout.js
DELETED
package/dist/commands/root.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { type CliConfig } from '../config/config.js';
|
|
3
|
-
import { HttpClient } from '../client/http-client.js';
|
|
4
|
-
import type { OutputFormat } from '../output/formatter.js';
|
|
5
|
-
export interface GlobalOpts {
|
|
6
|
-
server?: string;
|
|
7
|
-
apiKey?: string;
|
|
8
|
-
workspace?: string;
|
|
9
|
-
profile?: string;
|
|
10
|
-
format: OutputFormat;
|
|
11
|
-
quiet: boolean;
|
|
12
|
-
verbose: boolean;
|
|
13
|
-
dryRun: boolean;
|
|
14
|
-
}
|
|
15
|
-
export declare function getGlobalOpts(cmd: Command): GlobalOpts;
|
|
16
|
-
export declare function getConfig(opts: GlobalOpts): CliConfig;
|
|
17
|
-
export declare function getClient(opts: GlobalOpts): HttpClient;
|
|
18
|
-
export declare function createProgram(): Command;
|
package/dist/commands/root.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { createRequire } from 'node:module';
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { resolveConfig } from '../config/config.js';
|
|
4
|
-
import { HttpClient } from '../client/http-client.js';
|
|
5
|
-
const require = createRequire(import.meta.url);
|
|
6
|
-
const { version } = require('../../package.json');
|
|
7
|
-
export function getGlobalOpts(cmd) {
|
|
8
|
-
const root = cmd.optsWithGlobals();
|
|
9
|
-
return root;
|
|
10
|
-
}
|
|
11
|
-
export function getConfig(opts) {
|
|
12
|
-
return resolveConfig({
|
|
13
|
-
server: opts.server,
|
|
14
|
-
apiKey: opts.apiKey,
|
|
15
|
-
workspace: opts.workspace,
|
|
16
|
-
profile: opts.profile,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
export function getClient(opts) {
|
|
20
|
-
return new HttpClient(getConfig(opts));
|
|
21
|
-
}
|
|
22
|
-
export function createProgram() {
|
|
23
|
-
const program = new Command();
|
|
24
|
-
program
|
|
25
|
-
.name('wafflebase')
|
|
26
|
-
.description('CLI for Wafflebase spreadsheet API')
|
|
27
|
-
.version(version)
|
|
28
|
-
.option('--server <url>', 'Server URL')
|
|
29
|
-
.option('--api-key <key>', 'API key')
|
|
30
|
-
.option('--workspace <id>', 'Workspace ID')
|
|
31
|
-
.option('--profile <name>', 'Config profile', 'default')
|
|
32
|
-
.option('--format <fmt>', 'Output format (json, table, csv)', 'json')
|
|
33
|
-
.option('--quiet', 'Suppress output', false)
|
|
34
|
-
.option('--verbose', 'Verbose output', false)
|
|
35
|
-
.option('--dry-run', 'Show request without executing', false);
|
|
36
|
-
return program;
|
|
37
|
-
}
|
package/dist/commands/schema.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { getGlobalOpts } from './root.js';
|
|
2
|
-
import { output } from '../output/formatter.js';
|
|
3
|
-
import { getCommandSchema, getAllCommandSchemas } from '../schema/registry.js';
|
|
4
|
-
export function registerSchemaCommand(program) {
|
|
5
|
-
program
|
|
6
|
-
.command('schema [command]')
|
|
7
|
-
.description('Describe command parameters and response shape')
|
|
8
|
-
.action(function (commandName) {
|
|
9
|
-
const opts = getGlobalOpts(this);
|
|
10
|
-
if (commandName) {
|
|
11
|
-
const schema = getCommandSchema(commandName);
|
|
12
|
-
if (!schema) {
|
|
13
|
-
console.error(JSON.stringify({ error: { code: 'NOT_FOUND', message: `Unknown command: ${commandName}` } }, null, 2));
|
|
14
|
-
process.exitCode = 1;
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
output(schema, opts.format, opts.quiet);
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
const commands = getAllCommandSchemas().map((c) => ({
|
|
21
|
-
name: c.name,
|
|
22
|
-
description: c.description,
|
|
23
|
-
safety: c.safety,
|
|
24
|
-
}));
|
|
25
|
-
output({ commands }, opts.format, opts.quiet);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
}
|