clipy-kit 0.1.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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +65 -0
  3. package/dist/auth.d.ts +53 -0
  4. package/dist/auth.js +314 -0
  5. package/dist/cli.d.ts +6 -0
  6. package/dist/cli.js +160 -0
  7. package/dist/color.d.ts +12 -0
  8. package/dist/color.js +19 -0
  9. package/dist/colors.d.ts +6 -0
  10. package/dist/colors.js +26 -0
  11. package/dist/host/doctor.d.ts +17 -0
  12. package/dist/host/doctor.js +54 -0
  13. package/dist/host/help.d.ts +20 -0
  14. package/dist/host/help.js +142 -0
  15. package/dist/host/setup.d.ts +16 -0
  16. package/dist/host/setup.js +93 -0
  17. package/dist/http.d.ts +14 -0
  18. package/dist/http.js +53 -0
  19. package/dist/ids.d.ts +2 -0
  20. package/dist/ids.js +21 -0
  21. package/dist/json-input.d.ts +4 -0
  22. package/dist/json-input.js +34 -0
  23. package/dist/lib/format.d.ts +14 -0
  24. package/dist/lib/format.js +73 -0
  25. package/dist/lib/index.d.ts +6 -0
  26. package/dist/lib/index.js +6 -0
  27. package/dist/lib/ops.d.ts +460 -0
  28. package/dist/lib/ops.js +475 -0
  29. package/dist/lib/table.d.ts +14 -0
  30. package/dist/lib/table.js +67 -0
  31. package/dist/lib/types.d.ts +26 -0
  32. package/dist/lib/types.js +1 -0
  33. package/dist/output.d.ts +37 -0
  34. package/dist/output.js +67 -0
  35. package/dist/plugins/catalog.d.ts +11 -0
  36. package/dist/plugins/catalog.js +31 -0
  37. package/dist/plugins/sheet/plugin.d.ts +2 -0
  38. package/dist/plugins/sheet/plugin.js +8 -0
  39. package/dist/plugins/sheet/register.d.ts +2 -0
  40. package/dist/plugins/sheet/register.js +529 -0
  41. package/dist/plugins/sheet/specs.d.ts +20 -0
  42. package/dist/plugins/sheet/specs.js +291 -0
  43. package/dist/plugins/types.d.ts +7 -0
  44. package/dist/plugins/types.js +1 -0
  45. package/dist/ranges.d.ts +20 -0
  46. package/dist/ranges.js +77 -0
  47. package/dist/run.d.ts +353 -0
  48. package/dist/run.js +36 -0
  49. package/dist/specs.d.ts +1 -0
  50. package/dist/specs.js +1 -0
  51. package/package.json +50 -0
  52. package/skills/clipy/SKILL.md +38 -0
  53. package/skills/clipy-sheet/SKILL.md +71 -0
package/dist/output.js ADDED
@@ -0,0 +1,67 @@
1
+ export const EXIT = {
2
+ OK: 0,
3
+ VALIDATION: 10,
4
+ AUTH: 20,
5
+ PERMISSION: 30,
6
+ API: 40,
7
+ };
8
+ export class CliError extends Error {
9
+ code;
10
+ details;
11
+ exitCode;
12
+ constructor(code, message, details, exitCode = EXIT.VALIDATION) {
13
+ super(message);
14
+ this.name = 'CliError';
15
+ this.code = code;
16
+ this.details = details;
17
+ this.exitCode = exitCode;
18
+ }
19
+ }
20
+ export function compact(value) {
21
+ if (value === null || value === undefined)
22
+ return undefined;
23
+ if (Array.isArray(value))
24
+ return value.map(compact).filter((v) => v !== undefined);
25
+ if (typeof value === 'object') {
26
+ const out = {};
27
+ for (const [k, v] of Object.entries(value)) {
28
+ const c = compact(v);
29
+ if (c !== undefined)
30
+ out[k] = c;
31
+ }
32
+ return out;
33
+ }
34
+ return value;
35
+ }
36
+ export function envelopeOk(cmd, result, extra = {}) {
37
+ return compact({
38
+ ok: true,
39
+ cmd,
40
+ spreadsheetId: extra.spreadsheetId,
41
+ sheet: extra.sheet,
42
+ result,
43
+ });
44
+ }
45
+ export function envelopeErr(cmd, err) {
46
+ if (err instanceof CliError) {
47
+ return {
48
+ ok: false,
49
+ cmd,
50
+ error: compact({
51
+ code: err.code,
52
+ message: err.message,
53
+ details: err.details,
54
+ }),
55
+ };
56
+ }
57
+ const message = err instanceof Error ? err.message : String(err);
58
+ return { ok: false, cmd, error: { code: 'UNEXPECTED', message } };
59
+ }
60
+ export function exitCodeFor(err) {
61
+ if (err instanceof CliError)
62
+ return err.exitCode;
63
+ return EXIT.API;
64
+ }
65
+ export function stringifyEnvelope(body, pretty) {
66
+ return JSON.stringify(body, null, pretty ? 2 : undefined);
67
+ }
@@ -0,0 +1,11 @@
1
+ export type PluginStatus = 'built-in' | 'planned';
2
+ export type PluginInfo = {
3
+ name: string;
4
+ description: string;
5
+ status: PluginStatus;
6
+ when?: string;
7
+ };
8
+ export declare const BUILTIN: PluginInfo[];
9
+ export declare const PLANNED: PluginInfo[];
10
+ export declare function allPlugins(): PluginInfo[];
11
+ export declare function findPlugin(name: string): PluginInfo | undefined;
@@ -0,0 +1,31 @@
1
+ export const BUILTIN = [
2
+ {
3
+ name: 'sheet',
4
+ description: 'Google Sheets — intent read, upsert, format, share',
5
+ status: 'built-in',
6
+ when: 'v1',
7
+ },
8
+ ];
9
+ export const PLANNED = [
10
+ { name: 'linear', description: 'Issues, projects, comments', status: 'planned', when: 'next' },
11
+ { name: 'github', description: 'PRs, issues, checks', status: 'planned', when: 'next' },
12
+ { name: 'jira', description: 'Enterprise issues and projects', status: 'planned', when: 'next' },
13
+ { name: 'slack', description: 'Post, search, threads', status: 'planned', when: 'later' },
14
+ { name: 'notion', description: 'Docs and databases', status: 'planned', when: 'later' },
15
+ { name: 'confluence', description: 'Enterprise wiki', status: 'planned', when: 'later' },
16
+ { name: 'google-gmail', description: 'Mail next to sheet (extra OAuth scopes)', status: 'planned', when: 'later' },
17
+ { name: 'google-calendar', description: 'Events next to sheet', status: 'planned', when: 'later' },
18
+ { name: 'google-drive', description: 'Files next to sheet', status: 'planned', when: 'later' },
19
+ { name: 'figma', description: 'File metadata and comments', status: 'planned', when: 'later' },
20
+ { name: 'stripe', description: 'Payments and invoices (read-heavy)', status: 'planned', when: 'later' },
21
+ { name: 'sentry', description: 'Error lookup without a full observability MCP', status: 'planned', when: 'later' },
22
+ { name: 'postgres', description: 'Intent SQL, read-only by default', status: 'planned', when: 'later' },
23
+ { name: 'vercel', description: 'Deployments and logs', status: 'planned', when: 'later' },
24
+ { name: 'whatsapp', description: 'Business messaging (only if a real workflow appears)', status: 'planned', when: 'maybe' },
25
+ ];
26
+ export function allPlugins() {
27
+ return [...BUILTIN, ...PLANNED];
28
+ }
29
+ export function findPlugin(name) {
30
+ return allPlugins().find((p) => p.name === name);
31
+ }
@@ -0,0 +1,2 @@
1
+ import type { ClipyPlugin } from '../types.js';
2
+ export declare const sheetPlugin: ClipyPlugin;
@@ -0,0 +1,8 @@
1
+ import { registerSheet } from './register.js';
2
+ import { helpJson } from './specs.js';
3
+ export const sheetPlugin = {
4
+ name: 'sheet',
5
+ description: 'Google Sheets — intent read, upsert, format, share',
6
+ register: registerSheet,
7
+ helpJson,
8
+ };
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerSheet(sheet: Command): void;
@@ -0,0 +1,529 @@
1
+ import { login, logout, status } from '../../auth.js';
2
+ import { resolveSpreadsheetId } from '../../ids.js';
3
+ import { asGrid, asObject, asObjectArray, parseJsonInput } from '../../json-input.js';
4
+ import { CliError } from '../../output.js';
5
+ import { idOf, need, opsFor, run, takeGlobals } from '../../run.js';
6
+ export function registerSheet(sheet) {
7
+ const auth = sheet.command('auth').description('CLI-only login');
8
+ auth
9
+ .command('login')
10
+ .description('Start login from the CLI')
11
+ .option('--credentials <file>', 'OAuth client_secret.json')
12
+ .option('--service-account <file>', 'Service account JSON')
13
+ .option('--token <token>', 'Access token')
14
+ .option('--token-file <file>', 'File containing an access token')
15
+ .option('--adc', 'Use Application Default Credentials')
16
+ .action(async (opts, cmd) => {
17
+ const g = takeGlobals(cmd);
18
+ await run('sheet auth login', g.pretty, async () => ({
19
+ result: await login({
20
+ account: g.account || 'default',
21
+ credentials: opts.credentials,
22
+ serviceAccount: opts.serviceAccount,
23
+ token: opts.token,
24
+ tokenFile: opts.tokenFile,
25
+ adc: Boolean(opts.adc),
26
+ }),
27
+ }));
28
+ });
29
+ auth.command('status').description('Show login status').action(async (_opts, cmd) => {
30
+ const g = takeGlobals(cmd);
31
+ await run('sheet auth status', g.pretty, async () => ({ result: await status(g.account) }));
32
+ });
33
+ auth.command('logout').description('Delete stored credentials').action(async (_opts, cmd) => {
34
+ const g = takeGlobals(cmd);
35
+ await run('sheet auth logout', g.pretty, async () => ({ result: await logout(g.account) }));
36
+ });
37
+ sheet
38
+ .command('find')
39
+ .description('Search Drive for spreadsheets by name')
40
+ .option('--name <query>', 'Name substring')
41
+ .option('--limit <n>', 'Max results', (v) => Number(v))
42
+ .action(async (opts, cmd) => {
43
+ const g = takeGlobals(cmd);
44
+ await run('sheet find', g.pretty, async () => ({
45
+ result: await opsFor(g.account).findSpreadsheets(need(opts.name, '--name'), opts.limit),
46
+ }));
47
+ });
48
+ sheet
49
+ .command('info')
50
+ .description('Spreadsheet metadata')
51
+ .argument('[spreadsheet]', 'ID or URL')
52
+ .action(async (spreadsheet, _opts, cmd) => {
53
+ const g = takeGlobals(cmd);
54
+ await run('sheet info', g.pretty, async () => {
55
+ const spreadsheetId = idOf(spreadsheet, g);
56
+ return { spreadsheetId, result: await opsFor(g.account).info(spreadsheetId) };
57
+ });
58
+ });
59
+ sheet
60
+ .command('tabs')
61
+ .description('List tabs')
62
+ .command('list')
63
+ .description('List tabs')
64
+ .argument('[spreadsheet]', 'ID or URL')
65
+ .action(async (spreadsheet, _opts, cmd) => {
66
+ const g = takeGlobals(cmd);
67
+ await run('sheet tabs list', g.pretty, async () => {
68
+ const spreadsheetId = idOf(spreadsheet, g);
69
+ return { spreadsheetId, result: await opsFor(g.account).listTabs(spreadsheetId) };
70
+ });
71
+ });
72
+ const read = sheet.command('read').description('Read a tab as objects');
73
+ read
74
+ .command('range')
75
+ .description('Read a raw A1 range')
76
+ .argument('[spreadsheet]', 'ID or URL')
77
+ .option('--range <a1>', 'A1 range')
78
+ .action(async (spreadsheet, opts, cmd) => {
79
+ const g = takeGlobals(cmd);
80
+ await run('sheet read range', g.pretty, async () => {
81
+ const spreadsheetId = idOf(spreadsheet, g);
82
+ return {
83
+ spreadsheetId,
84
+ result: await opsFor(g.account).readRange(spreadsheetId, need(opts.range, '--range')),
85
+ };
86
+ });
87
+ });
88
+ read
89
+ .argument('[spreadsheet]', 'ID or URL')
90
+ .option('--tab <name>', 'Tab name')
91
+ .option('--range <a1>', 'A1 range')
92
+ .option('--limit <n>', 'Max data rows', (v) => Number(v))
93
+ .option('--header-row <n>', '1-based header row', (v) => Number(v))
94
+ .option('--raw', 'Return 2D values')
95
+ .action(async (spreadsheet, opts, cmd) => {
96
+ const g = takeGlobals(cmd);
97
+ await run('sheet read', g.pretty, async () => {
98
+ const spreadsheetId = idOf(spreadsheet, g);
99
+ return {
100
+ spreadsheetId,
101
+ sheet: opts.tab,
102
+ result: await opsFor(g.account).readTable(spreadsheetId, {
103
+ tab: opts.tab,
104
+ range: opts.range,
105
+ limit: opts.limit,
106
+ headerRow: opts.headerRow,
107
+ raw: Boolean(opts.raw),
108
+ }),
109
+ };
110
+ });
111
+ });
112
+ sheet
113
+ .command('append')
114
+ .description('Append object rows')
115
+ .argument('[spreadsheet]', 'ID or URL')
116
+ .option('--tab <name>', 'Tab name')
117
+ .option('--rows <json>', 'JSON object(s) or @file.json')
118
+ .action(async (spreadsheet, opts, cmd) => {
119
+ const g = takeGlobals(cmd);
120
+ await run('sheet append', g.pretty, async () => {
121
+ const spreadsheetId = idOf(spreadsheet, g);
122
+ const rows = asObjectArray(parseJsonInput(need(opts.rows, '--rows'), '--rows'), '--rows');
123
+ return {
124
+ spreadsheetId,
125
+ sheet: opts.tab,
126
+ result: await opsFor(g.account).append(spreadsheetId, {
127
+ tab: need(opts.tab, '--tab'),
128
+ rows,
129
+ dryRun: g.dryRun,
130
+ }),
131
+ };
132
+ });
133
+ });
134
+ sheet
135
+ .command('update')
136
+ .description('Update rows by key column')
137
+ .argument('[spreadsheet]', 'ID or URL')
138
+ .option('--tab <name>', 'Tab name')
139
+ .option('--key-col <name>', 'Key column')
140
+ .option('--key <value>', 'Key value')
141
+ .option('--set <json>', 'JSON object or @file.json')
142
+ .option('--allow-multi', 'Update every match')
143
+ .action(async (spreadsheet, opts, cmd) => {
144
+ const g = takeGlobals(cmd);
145
+ await run('sheet update', g.pretty, async () => {
146
+ const spreadsheetId = idOf(spreadsheet, g);
147
+ return {
148
+ spreadsheetId,
149
+ sheet: opts.tab,
150
+ result: await opsFor(g.account).update(spreadsheetId, {
151
+ tab: need(opts.tab, '--tab'),
152
+ keyCol: need(opts.keyCol, '--key-col'),
153
+ key: need(opts.key, '--key'),
154
+ set: asObject(parseJsonInput(need(opts.set, '--set'), '--set'), '--set'),
155
+ allowMulti: Boolean(opts.allowMulti),
156
+ dryRun: g.dryRun,
157
+ }),
158
+ };
159
+ });
160
+ });
161
+ sheet
162
+ .command('upsert')
163
+ .description('Update by key or append')
164
+ .argument('[spreadsheet]', 'ID or URL')
165
+ .option('--tab <name>', 'Tab name')
166
+ .option('--key-col <name>', 'Key column')
167
+ .option('--rows <json>', 'JSON object(s) or @file.json')
168
+ .action(async (spreadsheet, opts, cmd) => {
169
+ const g = takeGlobals(cmd);
170
+ await run('sheet upsert', g.pretty, async () => {
171
+ const spreadsheetId = idOf(spreadsheet, g);
172
+ return {
173
+ spreadsheetId,
174
+ sheet: opts.tab,
175
+ result: await opsFor(g.account).upsert(spreadsheetId, {
176
+ tab: need(opts.tab, '--tab'),
177
+ keyCol: need(opts.keyCol, '--key-col'),
178
+ rows: asObjectArray(parseJsonInput(need(opts.rows, '--rows'), '--rows'), '--rows'),
179
+ dryRun: g.dryRun,
180
+ }),
181
+ };
182
+ });
183
+ });
184
+ sheet
185
+ .command('set')
186
+ .description('Overwrite an A1 range')
187
+ .argument('[spreadsheet]', 'ID or URL')
188
+ .option('--range <a1>', 'A1 range')
189
+ .option('--values <json>', '2D JSON array or @file.json')
190
+ .action(async (spreadsheet, opts, cmd) => {
191
+ const g = takeGlobals(cmd);
192
+ await run('sheet set', g.pretty, async () => {
193
+ const spreadsheetId = idOf(spreadsheet, g);
194
+ return {
195
+ spreadsheetId,
196
+ result: await opsFor(g.account).setRange(spreadsheetId, {
197
+ range: need(opts.range, '--range'),
198
+ values: asGrid(parseJsonInput(need(opts.values, '--values'), '--values'), '--values'),
199
+ dryRun: g.dryRun,
200
+ }),
201
+ };
202
+ });
203
+ });
204
+ sheet
205
+ .command('clear')
206
+ .description('Clear a range')
207
+ .argument('[spreadsheet]', 'ID or URL')
208
+ .option('--range <a1>', 'A1 range')
209
+ .action(async (spreadsheet, opts, cmd) => {
210
+ const g = takeGlobals(cmd);
211
+ await run('sheet clear', g.pretty, async () => {
212
+ const spreadsheetId = idOf(spreadsheet, g);
213
+ return {
214
+ spreadsheetId,
215
+ result: await opsFor(g.account).clear(spreadsheetId, need(opts.range, '--range'), g.dryRun),
216
+ };
217
+ });
218
+ });
219
+ sheet
220
+ .command('batch')
221
+ .description('Run several write ops')
222
+ .argument('[spreadsheet]', 'ID or URL')
223
+ .option('--ops <json>', 'JSON array or @file.json')
224
+ .action(async (spreadsheet, opts, cmd) => {
225
+ const g = takeGlobals(cmd);
226
+ await run('sheet batch', g.pretty, async () => {
227
+ const spreadsheetId = idOf(spreadsheet, g);
228
+ const raw = parseJsonInput(need(opts.ops, '--ops'), '--ops');
229
+ if (!Array.isArray(raw))
230
+ throw new CliError('VALIDATION_ERROR', '--ops must be a JSON array');
231
+ return {
232
+ spreadsheetId,
233
+ result: await opsFor(g.account).batch(spreadsheetId, raw, g.dryRun),
234
+ };
235
+ });
236
+ });
237
+ sheet
238
+ .command('create')
239
+ .description('Create a spreadsheet')
240
+ .option('--title <title>', 'Title')
241
+ .option('--tabs <names>', 'Comma-separated tab names')
242
+ .action(async (opts, cmd) => {
243
+ const g = takeGlobals(cmd);
244
+ await run('sheet create', g.pretty, async () => {
245
+ const result = await opsFor(g.account).create({
246
+ title: need(opts.title, '--title'),
247
+ tabs: opts.tabs ? String(opts.tabs).split(',').map((s) => s.trim()).filter(Boolean) : undefined,
248
+ dryRun: g.dryRun,
249
+ });
250
+ return { spreadsheetId: result.spreadsheetId, result };
251
+ });
252
+ });
253
+ const tab = sheet.command('tab').description('Tab structure');
254
+ tab
255
+ .command('add')
256
+ .argument('[spreadsheet]', 'ID or URL')
257
+ .option('--title <name>', 'New tab title')
258
+ .option('--index <n>', 'Insert index', (v) => Number(v))
259
+ .action(async (spreadsheet, opts, cmd) => {
260
+ const g = takeGlobals(cmd);
261
+ await run('sheet tab add', g.pretty, async () => {
262
+ const spreadsheetId = idOf(spreadsheet, g);
263
+ return {
264
+ spreadsheetId,
265
+ result: await opsFor(g.account).tabAdd(spreadsheetId, {
266
+ title: need(opts.title, '--title'),
267
+ index: opts.index,
268
+ dryRun: g.dryRun,
269
+ }),
270
+ };
271
+ });
272
+ });
273
+ tab
274
+ .command('rename')
275
+ .argument('[spreadsheet]', 'ID or URL')
276
+ .option('--tab <name>', 'Current name')
277
+ .option('--title <name>', 'New name')
278
+ .action(async (spreadsheet, opts, cmd) => {
279
+ const g = takeGlobals(cmd);
280
+ await run('sheet tab rename', g.pretty, async () => {
281
+ const spreadsheetId = idOf(spreadsheet, g);
282
+ return {
283
+ spreadsheetId,
284
+ result: await opsFor(g.account).tabRename(spreadsheetId, {
285
+ tab: need(opts.tab, '--tab'),
286
+ title: need(opts.title, '--title'),
287
+ dryRun: g.dryRun,
288
+ }),
289
+ };
290
+ });
291
+ });
292
+ tab
293
+ .command('delete')
294
+ .argument('[spreadsheet]', 'ID or URL')
295
+ .option('--tab <name>', 'Tab to delete')
296
+ .action(async (spreadsheet, opts, cmd) => {
297
+ const g = takeGlobals(cmd);
298
+ await run('sheet tab delete', g.pretty, async () => {
299
+ const spreadsheetId = idOf(spreadsheet, g);
300
+ return {
301
+ spreadsheetId,
302
+ result: await opsFor(g.account).tabDelete(spreadsheetId, { tab: need(opts.tab, '--tab'), dryRun: g.dryRun }),
303
+ };
304
+ });
305
+ });
306
+ tab
307
+ .command('duplicate')
308
+ .argument('[spreadsheet]', 'ID or URL')
309
+ .option('--tab <name>', 'Tab to copy')
310
+ .option('--title <name>', 'Name for the copy')
311
+ .action(async (spreadsheet, opts, cmd) => {
312
+ const g = takeGlobals(cmd);
313
+ await run('sheet tab duplicate', g.pretty, async () => {
314
+ const spreadsheetId = idOf(spreadsheet, g);
315
+ return {
316
+ spreadsheetId,
317
+ result: await opsFor(g.account).tabDuplicate(spreadsheetId, {
318
+ tab: need(opts.tab, '--tab'),
319
+ title: opts.title,
320
+ dryRun: g.dryRun,
321
+ }),
322
+ };
323
+ });
324
+ });
325
+ const format = sheet.command('format').description('Format a range with intent flags');
326
+ format
327
+ .command('header')
328
+ .argument('[spreadsheet]', 'ID or URL')
329
+ .option('--tab <name>', 'Tab name')
330
+ .option('--bg <color>', 'Header background')
331
+ .option('--fg <color>', 'Header text color')
332
+ .option('--no-freeze', 'Do not freeze row 1')
333
+ .action(async (spreadsheet, opts, cmd) => {
334
+ const g = takeGlobals(cmd);
335
+ await run('sheet format header', g.pretty, async () => {
336
+ const spreadsheetId = idOf(spreadsheet, g);
337
+ return {
338
+ spreadsheetId,
339
+ sheet: opts.tab,
340
+ result: await opsFor(g.account).formatHeader(spreadsheetId, {
341
+ tab: need(opts.tab, '--tab'),
342
+ bg: opts.bg,
343
+ fg: opts.fg,
344
+ freeze: opts.freeze,
345
+ dryRun: g.dryRun,
346
+ }),
347
+ };
348
+ });
349
+ });
350
+ format
351
+ .argument('[spreadsheet]', 'ID or URL')
352
+ .option('--range <a1>', 'A1 range')
353
+ .option('--tab <name>', 'Tab if range has no sheet name')
354
+ .option('--bold', 'Bold')
355
+ .option('--italic', 'Italic')
356
+ .option('--bg <color>', 'Background')
357
+ .option('--fg <color>', 'Foreground')
358
+ .option('--align <dir>', 'left | center | right')
359
+ .option('--valign <dir>', 'top | middle | bottom')
360
+ .option('--wrap', 'Wrap text')
361
+ .action(async (spreadsheet, opts, cmd) => {
362
+ const g = takeGlobals(cmd);
363
+ await run('sheet format', g.pretty, async () => {
364
+ const spreadsheetId = idOf(spreadsheet, g);
365
+ return {
366
+ spreadsheetId,
367
+ result: await opsFor(g.account).format(spreadsheetId, {
368
+ range: need(opts.range, '--range'),
369
+ tab: opts.tab,
370
+ bold: opts.bold ? true : undefined,
371
+ italic: opts.italic ? true : undefined,
372
+ bg: opts.bg,
373
+ fg: opts.fg,
374
+ align: opts.align,
375
+ valign: opts.valign,
376
+ wrap: opts.wrap ? true : undefined,
377
+ dryRun: g.dryRun,
378
+ }),
379
+ };
380
+ });
381
+ });
382
+ sheet
383
+ .command('freeze')
384
+ .description('Freeze rows/columns')
385
+ .argument('[spreadsheet]', 'ID or URL')
386
+ .option('--tab <name>', 'Tab name')
387
+ .option('--rows <n>', 'Frozen rows', (v) => Number(v))
388
+ .option('--columns <n>', 'Frozen columns', (v) => Number(v))
389
+ .action(async (spreadsheet, opts, cmd) => {
390
+ const g = takeGlobals(cmd);
391
+ await run('sheet freeze', g.pretty, async () => {
392
+ const spreadsheetId = idOf(spreadsheet, g);
393
+ return {
394
+ spreadsheetId,
395
+ sheet: opts.tab,
396
+ result: await opsFor(g.account).freeze(spreadsheetId, {
397
+ tab: need(opts.tab, '--tab'),
398
+ rows: opts.rows,
399
+ columns: opts.columns,
400
+ dryRun: g.dryRun,
401
+ }),
402
+ };
403
+ });
404
+ });
405
+ sheet
406
+ .command('number-format')
407
+ .description('Apply a number pattern')
408
+ .argument('[spreadsheet]', 'ID or URL')
409
+ .option('--range <a1>', 'A1 range')
410
+ .option('--tab <name>', 'Tab if range has no sheet name')
411
+ .option('--pattern <pattern>', 'Number pattern')
412
+ .action(async (spreadsheet, opts, cmd) => {
413
+ const g = takeGlobals(cmd);
414
+ await run('sheet number-format', g.pretty, async () => {
415
+ const spreadsheetId = idOf(spreadsheet, g);
416
+ return {
417
+ spreadsheetId,
418
+ result: await opsFor(g.account).numberFormat(spreadsheetId, {
419
+ range: need(opts.range, '--range'),
420
+ pattern: need(opts.pattern, '--pattern'),
421
+ tab: opts.tab,
422
+ dryRun: g.dryRun,
423
+ }),
424
+ };
425
+ });
426
+ });
427
+ sheet
428
+ .command('formula')
429
+ .description('Write a formula into a cell')
430
+ .argument('[spreadsheet]', 'ID or URL')
431
+ .option('--cell <a1>', 'Target cell')
432
+ .option('--expr <formula>', 'Formula')
433
+ .action(async (spreadsheet, opts, cmd) => {
434
+ const g = takeGlobals(cmd);
435
+ await run('sheet formula', g.pretty, async () => {
436
+ const spreadsheetId = idOf(spreadsheet, g);
437
+ return {
438
+ spreadsheetId,
439
+ result: await opsFor(g.account).formula(spreadsheetId, {
440
+ cell: need(opts.cell, '--cell'),
441
+ expr: need(opts.expr, '--expr'),
442
+ dryRun: g.dryRun,
443
+ }),
444
+ };
445
+ });
446
+ });
447
+ sheet
448
+ .command('fill-down')
449
+ .description('Write a formula down a range')
450
+ .argument('[spreadsheet]', 'ID or URL')
451
+ .option('--range <a1>', 'A1 range')
452
+ .option('--expr <formula>', 'Formula')
453
+ .action(async (spreadsheet, opts, cmd) => {
454
+ const g = takeGlobals(cmd);
455
+ await run('sheet fill-down', g.pretty, async () => {
456
+ const spreadsheetId = idOf(spreadsheet, g);
457
+ return {
458
+ spreadsheetId,
459
+ result: await opsFor(g.account).fillDown(spreadsheetId, {
460
+ range: need(opts.range, '--range'),
461
+ expr: need(opts.expr, '--expr'),
462
+ dryRun: g.dryRun,
463
+ }),
464
+ };
465
+ });
466
+ });
467
+ sheet
468
+ .command('share')
469
+ .description('Share with a user')
470
+ .argument('[spreadsheet]', 'ID or URL')
471
+ .option('--email <email>', 'User email')
472
+ .option('--role <role>', 'reader or writer')
473
+ .action(async (spreadsheet, opts, cmd) => {
474
+ const g = takeGlobals(cmd);
475
+ await run('sheet share', g.pretty, async () => {
476
+ const spreadsheetId = idOf(spreadsheet, g);
477
+ return {
478
+ spreadsheetId,
479
+ result: await opsFor(g.account).share(spreadsheetId, {
480
+ email: need(opts.email, '--email'),
481
+ role: need(opts.role, '--role'),
482
+ dryRun: g.dryRun,
483
+ }),
484
+ };
485
+ });
486
+ });
487
+ sheet
488
+ .command('unshare')
489
+ .description('Remove a user permission')
490
+ .argument('[spreadsheet]', 'ID or URL')
491
+ .option('--email <email>', 'User email')
492
+ .action(async (spreadsheet, opts, cmd) => {
493
+ const g = takeGlobals(cmd);
494
+ await run('sheet unshare', g.pretty, async () => {
495
+ const spreadsheetId = idOf(spreadsheet, g);
496
+ return {
497
+ spreadsheetId,
498
+ result: await opsFor(g.account).unshare(spreadsheetId, need(opts.email, '--email'), g.dryRun),
499
+ };
500
+ });
501
+ });
502
+ sheet
503
+ .command('permissions')
504
+ .description('List permissions')
505
+ .argument('[spreadsheet]', 'ID or URL')
506
+ .action(async (spreadsheet, _opts, cmd) => {
507
+ const g = takeGlobals(cmd);
508
+ await run('sheet permissions', g.pretty, async () => {
509
+ const spreadsheetId = idOf(spreadsheet, g);
510
+ return { spreadsheetId, result: await opsFor(g.account).permissions(spreadsheetId) };
511
+ });
512
+ });
513
+ sheet
514
+ .command('api')
515
+ .description('Raw method escape hatch')
516
+ .argument('<method>', 'Method name')
517
+ .option('--json <json>', 'JSON body or @file.json')
518
+ .action(async (method, opts, cmd) => {
519
+ const g = takeGlobals(cmd);
520
+ await run('sheet api', g.pretty, async () => {
521
+ const json = opts.json ? parseJsonInput(opts.json, '--json') : {};
522
+ const spreadsheetId = g.spreadsheet ? resolveSpreadsheetId(g.spreadsheet) : undefined;
523
+ return {
524
+ spreadsheetId,
525
+ result: await opsFor(g.account).api(method, spreadsheetId, json),
526
+ };
527
+ });
528
+ });
529
+ }
@@ -0,0 +1,20 @@
1
+ export type FlagSpec = {
2
+ flags: string;
3
+ description: string;
4
+ type?: string;
5
+ };
6
+ export type ArgSpec = {
7
+ name: string;
8
+ required: boolean;
9
+ description: string;
10
+ };
11
+ export type CommandSpec = {
12
+ name: string;
13
+ description: string;
14
+ args?: ArgSpec[];
15
+ options?: FlagSpec[];
16
+ subcommands?: CommandSpec[];
17
+ };
18
+ export declare const COMMANDS: CommandSpec[];
19
+ export declare function findSpec(path: string[]): CommandSpec | undefined;
20
+ export declare function helpJson(path?: string[]): unknown;