@wafflebase/cli 0.4.9 → 0.5.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.
Files changed (83) hide show
  1. package/dist/bin.js +99988 -11
  2. package/package.json +7 -6
  3. package/dist/bin.d.ts +0 -2
  4. package/dist/client/dry-run.d.ts +0 -5
  5. package/dist/client/dry-run.js +0 -16
  6. package/dist/client/http-client.d.ts +0 -59
  7. package/dist/client/http-client.js +0 -178
  8. package/dist/commands/api-keys.d.ts +0 -2
  9. package/dist/commands/api-keys.js +0 -54
  10. package/dist/commands/cells.d.ts +0 -2
  11. package/dist/commands/cells.js +0 -108
  12. package/dist/commands/ctx.d.ts +0 -5
  13. package/dist/commands/ctx.js +0 -56
  14. package/dist/commands/docs.d.ts +0 -3
  15. package/dist/commands/docs.js +0 -320
  16. package/dist/commands/login.d.ts +0 -2
  17. package/dist/commands/login.js +0 -220
  18. package/dist/commands/logout.d.ts +0 -2
  19. package/dist/commands/logout.js +0 -10
  20. package/dist/commands/root.d.ts +0 -18
  21. package/dist/commands/root.js +0 -37
  22. package/dist/commands/schema.d.ts +0 -2
  23. package/dist/commands/schema.js +0 -28
  24. package/dist/commands/sheets-export.d.ts +0 -2
  25. package/dist/commands/sheets-export.js +0 -52
  26. package/dist/commands/sheets-import.d.ts +0 -2
  27. package/dist/commands/sheets-import.js +0 -88
  28. package/dist/commands/sheets.d.ts +0 -2
  29. package/dist/commands/sheets.js +0 -17
  30. package/dist/commands/slides.d.ts +0 -3
  31. package/dist/commands/slides.js +0 -227
  32. package/dist/commands/status.d.ts +0 -2
  33. package/dist/commands/status.js +0 -22
  34. package/dist/commands/tabs.d.ts +0 -2
  35. package/dist/commands/tabs.js +0 -20
  36. package/dist/config/config.d.ts +0 -38
  37. package/dist/config/config.js +0 -135
  38. package/dist/config/session.d.ts +0 -49
  39. package/dist/config/session.js +0 -74
  40. package/dist/docs/content.d.ts +0 -37
  41. package/dist/docs/content.js +0 -90
  42. package/dist/docs/docx-export.d.ts +0 -23
  43. package/dist/docs/docx-export.js +0 -14
  44. package/dist/docs/docx-import.d.ts +0 -36
  45. package/dist/docs/docx-import.js +0 -70
  46. package/dist/docs/dom-polyfill.d.ts +0 -1
  47. package/dist/docs/dom-polyfill.js +0 -29
  48. package/dist/docs/fontkit-measurer.d.ts +0 -41
  49. package/dist/docs/fontkit-measurer.js +0 -64
  50. package/dist/docs/image-fetcher.d.ts +0 -37
  51. package/dist/docs/image-fetcher.js +0 -38
  52. package/dist/docs/import.d.ts +0 -72
  53. package/dist/docs/import.js +0 -146
  54. package/dist/docs/page-range.d.ts +0 -29
  55. package/dist/docs/page-range.js +0 -74
  56. package/dist/docs/page-slice.d.ts +0 -45
  57. package/dist/docs/page-slice.js +0 -61
  58. package/dist/docs/paginate.d.ts +0 -13
  59. package/dist/docs/paginate.js +0 -19
  60. package/dist/docs/pdf-export.d.ts +0 -47
  61. package/dist/docs/pdf-export.js +0 -95
  62. package/dist/output/binary.d.ts +0 -37
  63. package/dist/output/binary.js +0 -47
  64. package/dist/output/csv.d.ts +0 -4
  65. package/dist/output/csv.js +0 -25
  66. package/dist/output/formatter.d.ts +0 -4
  67. package/dist/output/formatter.js +0 -41
  68. package/dist/output/json.d.ts +0 -1
  69. package/dist/output/json.js +0 -3
  70. package/dist/output/table.d.ts +0 -4
  71. package/dist/output/table.js +0 -14
  72. package/dist/schema/registry.d.ts +0 -42
  73. package/dist/schema/registry.js +0 -375
  74. package/dist/slides/content.d.ts +0 -41
  75. package/dist/slides/content.js +0 -122
  76. package/dist/slides/import.d.ts +0 -82
  77. package/dist/slides/import.js +0 -170
  78. package/dist/slides/pptx-export.d.ts +0 -6
  79. package/dist/slides/pptx-export.js +0 -10
  80. package/dist/slides/pptx-import.d.ts +0 -35
  81. package/dist/slides/pptx-import.js +0 -49
  82. package/dist/util/csv-parse.d.ts +0 -21
  83. package/dist/util/csv-parse.js +0 -99
@@ -1,52 +0,0 @@
1
- import { writeFileSync } from 'node:fs';
2
- import { extname } from 'node:path';
3
- import { getGlobalOpts, getClient } from './root.js';
4
- import { outputError } from '../output/formatter.js';
5
- import { formatCsv } from '../output/csv.js';
6
- import { formatJson } from '../output/json.js';
7
- const VALID_FORMATS = ['csv', 'json'];
8
- function detectFormat(file, formatFlag) {
9
- if (formatFlag) {
10
- if (!VALID_FORMATS.includes(formatFlag)) {
11
- throw new Error(`Unsupported format "${formatFlag}". Use csv or json.`);
12
- }
13
- return formatFlag;
14
- }
15
- const ext = extname(file).toLowerCase();
16
- if (ext === '.json')
17
- return 'json';
18
- return 'csv';
19
- }
20
- export function registerSheetsExportCommand(parent) {
21
- parent
22
- .command('export <doc-id> <file>')
23
- .description('Export tab data to CSV/JSON')
24
- .option('--tab <tab-id>', 'Source tab', 'tab-1')
25
- .option('--range <range>', 'Cell range to export (e.g. A1:D100)')
26
- .option('--file-format <fmt>', 'File format (csv, json)')
27
- .action(async function (docId, file) {
28
- const opts = getGlobalOpts(this);
29
- const localOpts = this.opts();
30
- try {
31
- const res = await getClient(opts).getCells(docId, localOpts.tab, localOpts.range);
32
- if (!res.ok) {
33
- const msg = res.data?.error?.message;
34
- throw new Error(msg ?? `HTTP ${res.status}`);
35
- }
36
- const fmt = detectFormat(file, localOpts.fileFormat);
37
- const formatted = fmt === 'csv' ? formatCsv(res.data) : formatJson(res.data);
38
- if (file === '-') {
39
- process.stdout.write(formatted + '\n');
40
- }
41
- else {
42
- writeFileSync(file, formatted + '\n', 'utf-8');
43
- if (!opts.quiet) {
44
- console.log(`Exported to ${file}`);
45
- }
46
- }
47
- }
48
- catch (e) {
49
- outputError(e, opts.quiet);
50
- }
51
- });
52
- }
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function registerSheetsImportCommand(parent: Command): void;
@@ -1,88 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { extname } from 'node:path';
3
- import { getGlobalOpts, getClient, getConfig } from './root.js';
4
- import { output, outputError } from '../output/formatter.js';
5
- import { printDryRun } from '../client/dry-run.js';
6
- import { parseCsv, parseStartRef, buildCellMap } from '../util/csv-parse.js';
7
- const VALID_FORMATS = ['csv', 'json'];
8
- function detectFormat(file, formatFlag) {
9
- if (formatFlag) {
10
- if (!VALID_FORMATS.includes(formatFlag)) {
11
- throw new Error(`Unsupported format "${formatFlag}". Use csv or json.`);
12
- }
13
- return formatFlag;
14
- }
15
- const ext = extname(file).toLowerCase();
16
- if (ext === '.json')
17
- return 'json';
18
- return 'csv';
19
- }
20
- function readInput(file) {
21
- if (file === '-') {
22
- return readFileSync(0, 'utf-8');
23
- }
24
- return readFileSync(file, 'utf-8');
25
- }
26
- export function registerSheetsImportCommand(parent) {
27
- parent
28
- .command('import <doc-id> <file>')
29
- .description('Import CSV/JSON into a tab')
30
- .option('--tab <tab-id>', 'Target tab', 'tab-1')
31
- .option('--file-format <fmt>', 'File format (csv, json)')
32
- .option('--start <ref>', 'Top-left cell to start import', 'A1')
33
- .action(async function (docId, file) {
34
- const opts = getGlobalOpts(this);
35
- const localOpts = this.opts();
36
- try {
37
- const fmt = detectFormat(file, localOpts.fileFormat);
38
- const raw = readInput(file);
39
- let rows;
40
- if (fmt === 'json') {
41
- const parsed = JSON.parse(raw);
42
- // Expect either array of arrays or array of objects
43
- if (Array.isArray(parsed) && parsed.length > 0) {
44
- if (Array.isArray(parsed[0])) {
45
- rows = parsed;
46
- }
47
- else if (typeof parsed[0] === 'object') {
48
- // Array of objects → header row + value rows
49
- const objs = parsed;
50
- const keys = Object.keys(objs[0]);
51
- rows = [keys, ...objs.map((o) => keys.map((k) => String(o[k] ?? '')))];
52
- }
53
- else {
54
- throw new Error('JSON must be an array of arrays or array of objects');
55
- }
56
- }
57
- else {
58
- throw new Error('JSON must be a non-empty array');
59
- }
60
- }
61
- else {
62
- rows = parseCsv(raw);
63
- }
64
- if (rows.length === 0) {
65
- throw new Error('No data to import');
66
- }
67
- const { row: startRow, col: startCol } = parseStartRef(localOpts.start);
68
- const cells = buildCellMap(rows, startRow, startCol);
69
- const cellCount = Object.keys(cells).length;
70
- if (opts.dryRun) {
71
- printDryRun(getConfig(opts), 'PATCH', `/documents/${docId}/tabs/${localOpts.tab}/cells`, {
72
- cells,
73
- });
74
- return;
75
- }
76
- const res = await getClient(opts).batchCells(docId, localOpts.tab, cells);
77
- if (!res.ok)
78
- throw new Error(`HTTP ${res.status}`);
79
- const result = typeof res.data === 'object' && res.data !== null
80
- ? { imported: cellCount, ...res.data }
81
- : { imported: cellCount };
82
- output(result, opts.format, opts.quiet);
83
- }
84
- catch (e) {
85
- outputError(e, opts.quiet);
86
- }
87
- });
88
- }
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function registerSheetsCommand(program: Command): Command;
@@ -1,17 +0,0 @@
1
- import { registerCellsCommand } from './cells.js';
2
- import { registerTabsCommand } from './tabs.js';
3
- import { registerSheetsImportCommand } from './sheets-import.js';
4
- import { registerSheetsExportCommand } from './sheets-export.js';
5
- export function registerSheetsCommand(program) {
6
- const sheets = program
7
- .command('sheets')
8
- .alias('sheet')
9
- .alias('spreadsheet')
10
- .alias('spreadsheets')
11
- .description('Spreadsheet commands');
12
- registerTabsCommand(sheets);
13
- registerCellsCommand(sheets);
14
- registerSheetsImportCommand(sheets);
15
- registerSheetsExportCommand(sheets);
16
- return sheets;
17
- }
@@ -1,3 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function registerSlidesCommand(program: Command): void;
3
- export declare function registerSlidesImportCommand(slides: Command): void;
@@ -1,227 +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 { runSlidesImport } from '../slides/import.js';
6
- import { parseSlidesContentFormat, runSlidesContent, } from '../slides/content.js';
7
- import { writeBinary } from '../output/binary.js';
8
- import { createImageFetcher } from '../docs/image-fetcher.js';
9
- import { exportPptxCli } from '../slides/pptx-export.js';
10
- export function registerSlidesCommand(program) {
11
- const slides = program
12
- .command('slides')
13
- .alias('slide')
14
- .alias('deck')
15
- .description('Manage slide decks');
16
- slides
17
- .command('list')
18
- .description('List slide decks in workspace')
19
- .action(async function () {
20
- const opts = getGlobalOpts(this);
21
- try {
22
- const res = await getClient(opts).listDocuments();
23
- if (!res.ok)
24
- throw new Error(`HTTP ${res.status}`);
25
- let data = res.data;
26
- if (Array.isArray(data)) {
27
- data = data.filter((d) => d.type === 'slides');
28
- }
29
- output(data, opts.format, opts.quiet);
30
- }
31
- catch (e) {
32
- outputError(e, opts.quiet);
33
- }
34
- });
35
- slides
36
- .command('create <title>')
37
- .description('Create a new slide deck')
38
- .action(async function (title) {
39
- const opts = getGlobalOpts(this);
40
- try {
41
- if (opts.dryRun) {
42
- printDryRun(getConfig(opts), 'POST', '/documents', {
43
- title,
44
- type: 'slides',
45
- });
46
- return;
47
- }
48
- const res = await getClient(opts).createDocument(title, 'slides');
49
- if (!res.ok)
50
- throw new Error(`HTTP ${res.status}`);
51
- output(res.data, opts.format, opts.quiet);
52
- }
53
- catch (e) {
54
- outputError(e, opts.quiet);
55
- }
56
- });
57
- slides
58
- .command('get <doc-id>')
59
- .description('Show slide deck metadata')
60
- .action(async function (docId) {
61
- const opts = getGlobalOpts(this);
62
- try {
63
- const res = await getClient(opts).getDocument(docId);
64
- if (!res.ok)
65
- throw new Error(`HTTP ${res.status}`);
66
- output(res.data, opts.format, opts.quiet);
67
- }
68
- catch (e) {
69
- outputError(e, opts.quiet);
70
- }
71
- });
72
- slides
73
- .command('rename <doc-id> <title>')
74
- .description('Rename a slide deck')
75
- .action(async function (docId, title) {
76
- const opts = getGlobalOpts(this);
77
- if (opts.dryRun) {
78
- printDryRun(getConfig(opts), 'PATCH', `/documents/${docId}`, { title });
79
- return;
80
- }
81
- try {
82
- const res = await getClient(opts).updateDocument(docId, title);
83
- if (!res.ok)
84
- throw new Error(`HTTP ${res.status}`);
85
- output(res.data, opts.format, opts.quiet);
86
- }
87
- catch (e) {
88
- outputError(e, opts.quiet);
89
- }
90
- });
91
- slides
92
- .command('delete <doc-id>')
93
- .description('Delete a slide deck')
94
- .action(async function (docId) {
95
- const opts = getGlobalOpts(this);
96
- if (opts.dryRun) {
97
- printDryRun(getConfig(opts), 'DELETE', `/documents/${docId}`);
98
- return;
99
- }
100
- try {
101
- const res = await getClient(opts).deleteDocument(docId);
102
- if (!res.ok)
103
- throw new Error(`HTTP ${res.status}`);
104
- output(res.data, opts.format, opts.quiet);
105
- }
106
- catch (e) {
107
- outputError(e, opts.quiet);
108
- }
109
- });
110
- slides
111
- .command('content <doc-id>')
112
- .description('Read deck content as JSON, Markdown, or plain text')
113
- // NOTE: `--format` is intentionally not redeclared here — the global
114
- // `--format` option catches the user's value (see the same comment
115
- // on `docs content`). We read `opts.format` and validate it through
116
- // `parseSlidesContentFormat`.
117
- .option('--notes', 'Include speaker notes (md/text)', false)
118
- .option('--out <file>', 'Output file (- for stdout)')
119
- .option('--force', 'Overwrite existing output file', false)
120
- .action(async function (docId) {
121
- const opts = getGlobalOpts(this);
122
- const local = this.opts();
123
- try {
124
- const format = parseSlidesContentFormat(opts.format);
125
- if (opts.dryRun) {
126
- printDryRun(getConfig(opts), 'GET', `/documents/${docId}/content`);
127
- return;
128
- }
129
- const res = await getClient(opts).getSlidesContent(docId);
130
- if (!res.ok) {
131
- const body = res.data;
132
- if (body?.error) {
133
- // Surface backend-shaped errors (e.g., TYPE_MISMATCH) verbatim
134
- // so agents reading stderr can act on the `code` field.
135
- console.error(JSON.stringify(body, null, 2));
136
- process.exitCode = 1;
137
- return;
138
- }
139
- throw new Error(`HTTP ${res.status}`);
140
- }
141
- runSlidesContent({
142
- deck: res.data,
143
- format,
144
- notes: local.notes,
145
- out: local.out,
146
- force: local.force,
147
- quiet: opts.quiet,
148
- });
149
- }
150
- catch (e) {
151
- outputError(e, opts.quiet);
152
- }
153
- });
154
- slides
155
- .command('export <doc-id> <file>')
156
- .description('Export a slide deck to PPTX')
157
- // NOTE: `--format` is intentionally not redeclared here — the global
158
- // `--format` option catches the user's value (see the same comment on
159
- // `docs export`). We read `opts.format` via `getOptionValueSourceWithGlobals`
160
- // to distinguish an explicit CLI flag from the default, then validate
161
- // that only "pptx" is accepted.
162
- .option('--force', 'Overwrite existing output file', false)
163
- .action(async function (docId, file) {
164
- const opts = getGlobalOpts(this);
165
- const local = this.opts();
166
- try {
167
- const formatSource = this.getOptionValueSourceWithGlobals('format');
168
- // Widen to `string` — `opts.format` is the global `OutputFormat`
169
- // union (json|table|csv|yaml), which has no overlap with the
170
- // export-only `pptx` value, so a direct comparison is a tsc error.
171
- const fmt = formatSource === 'cli' ? opts.format : undefined;
172
- if (fmt && fmt !== 'pptx')
173
- throw new Error(`Invalid --format "${fmt}". Only "pptx" is supported.`);
174
- if (!fmt && extname(file).toLowerCase() !== '.pptx') {
175
- throw new Error(`Cannot infer format from "${file}". Use a .pptx extension or --format pptx.`);
176
- }
177
- if (opts.dryRun) {
178
- printDryRun(getConfig(opts), 'GET', `/documents/${docId}/content`);
179
- return;
180
- }
181
- const res = await getClient(opts).getSlidesContent(docId);
182
- if (!res.ok) {
183
- const body = res.data;
184
- if (body?.error) {
185
- console.error(JSON.stringify(body, null, 2));
186
- process.exitCode = 1;
187
- return;
188
- }
189
- throw new Error(`HTTP ${res.status}`);
190
- }
191
- const imageFetcher = createImageFetcher({ serverBase: getConfig(opts).server });
192
- const bytes = await exportPptxCli(res.data, { imageFetcher });
193
- writeBinary(bytes, file, { force: local.force, quiet: opts.quiet });
194
- }
195
- catch (e) {
196
- outputError(e, opts.quiet);
197
- }
198
- });
199
- registerSlidesImportCommand(slides);
200
- }
201
- export function registerSlidesImportCommand(slides) {
202
- slides
203
- .command('import <file>')
204
- .description('Import a .pptx file as a new (or replacement) slides deck')
205
- .option('--title <title>', 'Deck title (default: file basename)')
206
- .option('--replace <doc-id>', 'Replace content of an existing deck')
207
- .option('--yes', 'Skip --replace confirmation prompt', false)
208
- .action(async function (file) {
209
- const opts = getGlobalOpts(this);
210
- const local = this.opts();
211
- try {
212
- const result = await runSlidesImport({
213
- file,
214
- title: local.title,
215
- replace: local.replace,
216
- yes: local.yes,
217
- quiet: opts.quiet,
218
- dryRun: opts.dryRun,
219
- }, getClient(opts));
220
- if (result.exitCode !== 0)
221
- process.exitCode = result.exitCode;
222
- }
223
- catch (e) {
224
- outputError(e, opts.quiet);
225
- }
226
- });
227
- }
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function registerStatusCommand(program: Command): void;
@@ -1,22 +0,0 @@
1
- import { loadSession, isSessionExpired } from '../config/session.js';
2
- export function registerStatusCommand(program) {
3
- program
4
- .command('status')
5
- .description('Show current auth state')
6
- .action(() => {
7
- const session = loadSession();
8
- if (!session) {
9
- console.log('Not logged in. Run `wafflebase login`.');
10
- return;
11
- }
12
- console.log(`Logged in as ${session.user.username} (${session.user.email})`);
13
- console.log(`Server: ${session.server}`);
14
- const ws = session.workspaces.find((w) => w.id === session.activeWorkspace);
15
- const wsLabel = ws
16
- ? `${ws.name} (${session.activeWorkspace.slice(0, 8)}...)`
17
- : session.activeWorkspace;
18
- console.log(`Workspace: ${wsLabel}`);
19
- const expired = isSessionExpired(session);
20
- console.log(`Session: ${expired ? 'expired' : 'valid'} (expires ${session.expiresAt})`);
21
- });
22
- }
@@ -1,2 +0,0 @@
1
- import { Command } from 'commander';
2
- export declare function registerTabsCommand(parent: Command): void;
@@ -1,20 +0,0 @@
1
- import { getGlobalOpts, getClient } from './root.js';
2
- import { output, outputError } from '../output/formatter.js';
3
- export function registerTabsCommand(parent) {
4
- const tab = parent.command('tabs').alias('tab').description('Manage tabs');
5
- tab
6
- .command('list <doc-id>')
7
- .description('List tabs in a document')
8
- .action(async function (docId) {
9
- const opts = getGlobalOpts(this);
10
- try {
11
- const res = await getClient(opts).listTabs(docId);
12
- if (!res.ok)
13
- throw new Error(`HTTP ${res.status}`);
14
- output(res.data, opts.format, opts.quiet);
15
- }
16
- catch (e) {
17
- outputError(e, opts.quiet);
18
- }
19
- });
20
- }
@@ -1,38 +0,0 @@
1
- export declare const DEFAULT_SERVER = "https://api.wafflebase.io";
2
- export interface CliConfig {
3
- server: string;
4
- apiKey: string;
5
- workspace: string;
6
- authMode: 'api-key' | 'jwt' | 'none';
7
- accessToken?: string;
8
- refreshToken?: string;
9
- }
10
- /**
11
- * Migrate config from old path (~/.config/wafflebase/config.yaml) to new path
12
- * (~/.wafflebase/config.yaml) if needed.
13
- */
14
- export declare function migrateConfigIfNeeded(newPath: string, oldPath?: string): void;
15
- /**
16
- * Return the resolved config file path.
17
- * WAFFLEBASE_CONFIG env var overrides everything; otherwise defaults to
18
- * ~/.wafflebase/config.yaml (with migration from old ~/.config/wafflebase path).
19
- */
20
- export declare function getConfigPath(): string;
21
- /**
22
- * Resolve CLI configuration from flags > env > session > config file.
23
- *
24
- * Auth resolution order:
25
- * 1. Flag/env `--api-key` or `WAFFLEBASE_API_KEY` → API key auth
26
- * 2. Session `~/.wafflebase/session.json` with both tokens → JWT auth
27
- * (the access token may already be past its `expiresAt`; the HTTP
28
- * client refreshes it on the first 401 and falls back to a clear
29
- * SESSION_EXPIRED error when the refresh token is also dead)
30
- * 3. Config profile `api-key` → API key auth
31
- * 4. None → empty (commands will fail)
32
- */
33
- export declare function resolveConfig(flags: {
34
- server?: string;
35
- apiKey?: string;
36
- workspace?: string;
37
- profile?: string;
38
- }): CliConfig;
@@ -1,135 +0,0 @@
1
- import { readFileSync, existsSync, mkdirSync, copyFileSync } from 'node:fs';
2
- import { join, dirname } from 'node:path';
3
- import { homedir } from 'node:os';
4
- import { parse as parseYaml } from 'yaml';
5
- import { loadSession } from './session.js';
6
- export const DEFAULT_SERVER = 'https://api.wafflebase.io';
7
- /**
8
- * Migrate config from old path (~/.config/wafflebase/config.yaml) to new path
9
- * (~/.wafflebase/config.yaml) if needed.
10
- */
11
- export function migrateConfigIfNeeded(newPath, oldPath = join(homedir(), '.config', 'wafflebase', 'config.yaml')) {
12
- if (existsSync(newPath))
13
- return;
14
- if (!existsSync(oldPath))
15
- return;
16
- try {
17
- mkdirSync(dirname(newPath), { recursive: true });
18
- copyFileSync(oldPath, newPath);
19
- process.stderr.write(`[wafflebase] Config migrated from ${oldPath} to ${newPath}\n`);
20
- }
21
- catch {
22
- // Silent failure — migration is best-effort
23
- }
24
- }
25
- /**
26
- * Return the resolved config file path.
27
- * WAFFLEBASE_CONFIG env var overrides everything; otherwise defaults to
28
- * ~/.wafflebase/config.yaml (with migration from old ~/.config/wafflebase path).
29
- */
30
- export function getConfigPath() {
31
- if (process.env.WAFFLEBASE_CONFIG) {
32
- return process.env.WAFFLEBASE_CONFIG;
33
- }
34
- const newPath = join(homedir(), '.wafflebase', 'config.yaml');
35
- migrateConfigIfNeeded(newPath);
36
- return newPath;
37
- }
38
- /**
39
- * Resolve CLI configuration from flags > env > session > config file.
40
- *
41
- * Auth resolution order:
42
- * 1. Flag/env `--api-key` or `WAFFLEBASE_API_KEY` → API key auth
43
- * 2. Session `~/.wafflebase/session.json` with both tokens → JWT auth
44
- * (the access token may already be past its `expiresAt`; the HTTP
45
- * client refreshes it on the first 401 and falls back to a clear
46
- * SESSION_EXPIRED error when the refresh token is also dead)
47
- * 3. Config profile `api-key` → API key auth
48
- * 4. None → empty (commands will fail)
49
- */
50
- export function resolveConfig(flags) {
51
- const profile = loadProfile(flags.profile ?? 'default');
52
- // Step 1: Check flags/env for API key
53
- const flagOrEnvApiKey = flags.apiKey ?? process.env.WAFFLEBASE_API_KEY ?? undefined;
54
- if (flagOrEnvApiKey) {
55
- return {
56
- server: flags.server ??
57
- process.env.WAFFLEBASE_SERVER ??
58
- profile.server ??
59
- DEFAULT_SERVER,
60
- apiKey: flagOrEnvApiKey,
61
- workspace: flags.workspace ??
62
- process.env.WAFFLEBASE_WORKSPACE ??
63
- profile.workspace ??
64
- '',
65
- authMode: 'api-key',
66
- };
67
- }
68
- // Step 2: Try session — accept it whenever both tokens are present.
69
- // Gating on `isSessionExpired` here would silently drop the session
70
- // (and its `activeWorkspace`) the moment the access token expired,
71
- // turning every command into a `/workspaces//...` 404. Letting the
72
- // HTTP client see the expired access token + refresh token lets its
73
- // 401 → refresh → retry path do its job.
74
- const session = loadSession();
75
- if (session && session.accessToken && session.refreshToken) {
76
- return {
77
- server: flags.server ??
78
- process.env.WAFFLEBASE_SERVER ??
79
- session.server ??
80
- profile.server ??
81
- DEFAULT_SERVER,
82
- apiKey: '',
83
- workspace: flags.workspace ??
84
- process.env.WAFFLEBASE_WORKSPACE ??
85
- session.activeWorkspace ??
86
- profile.workspace ??
87
- '',
88
- authMode: 'jwt',
89
- accessToken: session.accessToken,
90
- refreshToken: session.refreshToken,
91
- };
92
- }
93
- // Step 3: Fallback to config profile API key
94
- const profileApiKey = profile['api-key'];
95
- if (profileApiKey) {
96
- return {
97
- server: flags.server ??
98
- process.env.WAFFLEBASE_SERVER ??
99
- profile.server ??
100
- DEFAULT_SERVER,
101
- apiKey: profileApiKey,
102
- workspace: flags.workspace ??
103
- process.env.WAFFLEBASE_WORKSPACE ??
104
- profile.workspace ??
105
- '',
106
- authMode: 'api-key',
107
- };
108
- }
109
- // Step 4: Nothing available
110
- return {
111
- server: flags.server ??
112
- process.env.WAFFLEBASE_SERVER ??
113
- profile.server ??
114
- DEFAULT_SERVER,
115
- apiKey: '',
116
- workspace: flags.workspace ??
117
- process.env.WAFFLEBASE_WORKSPACE ??
118
- profile.workspace ??
119
- '',
120
- authMode: 'none',
121
- };
122
- }
123
- function loadProfile(name) {
124
- const configPath = getConfigPath();
125
- if (!existsSync(configPath))
126
- return {};
127
- try {
128
- const raw = readFileSync(configPath, 'utf-8');
129
- const config = parseYaml(raw);
130
- return config?.profiles?.[name] ?? {};
131
- }
132
- catch {
133
- return {};
134
- }
135
- }
@@ -1,49 +0,0 @@
1
- export interface SessionUser {
2
- id: number;
3
- username: string;
4
- email: string;
5
- photo: string | null;
6
- }
7
- export interface WorkspaceInfo {
8
- id: string;
9
- name: string;
10
- }
11
- export interface Session {
12
- server: string;
13
- user: SessionUser;
14
- accessToken: string;
15
- refreshToken: string;
16
- expiresAt: string;
17
- activeWorkspace: string;
18
- workspaces: WorkspaceInfo[];
19
- }
20
- /**
21
- * Returns the default session file path: ~/.wafflebase/session.json
22
- */
23
- export declare function getSessionPath(): string;
24
- /**
25
- * Loads the session from disk. Returns null if the file does not exist or
26
- * contains invalid JSON.
27
- */
28
- export declare function loadSession(path?: string): Session | null;
29
- /**
30
- * Writes the session to disk with owner-only (0600) permissions.
31
- * Creates the parent directory if it does not exist.
32
- */
33
- export declare function saveSession(session: Session, path?: string): void;
34
- /**
35
- * Deletes the session file if it exists. No-op otherwise.
36
- */
37
- export declare function clearSession(path?: string): void;
38
- /**
39
- * Returns true when the session's expiresAt timestamp is in the past.
40
- */
41
- export declare function isSessionExpired(session: Session): boolean;
42
- /**
43
- * Decodes the `exp` claim from a JWT's payload segment and returns an ISO
44
- * 8601 string.
45
- *
46
- * JWT format: <header>.<payload>.<signature>
47
- * The payload is base64url-encoded JSON.
48
- */
49
- export declare function decodeJwtExpiry(token: string): string;