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.
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/dist/auth.d.ts +53 -0
- package/dist/auth.js +314 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +160 -0
- package/dist/color.d.ts +12 -0
- package/dist/color.js +19 -0
- package/dist/colors.d.ts +6 -0
- package/dist/colors.js +26 -0
- package/dist/host/doctor.d.ts +17 -0
- package/dist/host/doctor.js +54 -0
- package/dist/host/help.d.ts +20 -0
- package/dist/host/help.js +142 -0
- package/dist/host/setup.d.ts +16 -0
- package/dist/host/setup.js +93 -0
- package/dist/http.d.ts +14 -0
- package/dist/http.js +53 -0
- package/dist/ids.d.ts +2 -0
- package/dist/ids.js +21 -0
- package/dist/json-input.d.ts +4 -0
- package/dist/json-input.js +34 -0
- package/dist/lib/format.d.ts +14 -0
- package/dist/lib/format.js +73 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/ops.d.ts +460 -0
- package/dist/lib/ops.js +475 -0
- package/dist/lib/table.d.ts +14 -0
- package/dist/lib/table.js +67 -0
- package/dist/lib/types.d.ts +26 -0
- package/dist/lib/types.js +1 -0
- package/dist/output.d.ts +37 -0
- package/dist/output.js +67 -0
- package/dist/plugins/catalog.d.ts +11 -0
- package/dist/plugins/catalog.js +31 -0
- package/dist/plugins/sheet/plugin.d.ts +2 -0
- package/dist/plugins/sheet/plugin.js +8 -0
- package/dist/plugins/sheet/register.d.ts +2 -0
- package/dist/plugins/sheet/register.js +529 -0
- package/dist/plugins/sheet/specs.d.ts +20 -0
- package/dist/plugins/sheet/specs.js +291 -0
- package/dist/plugins/types.d.ts +7 -0
- package/dist/plugins/types.js +1 -0
- package/dist/ranges.d.ts +20 -0
- package/dist/ranges.js +77 -0
- package/dist/run.d.ts +353 -0
- package/dist/run.js +36 -0
- package/dist/specs.d.ts +1 -0
- package/dist/specs.js +1 -0
- package/package.json +50 -0
- package/skills/clipy/SKILL.md +38 -0
- 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,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;
|