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
@@ -0,0 +1,291 @@
1
+ const spreadsheetArg = {
2
+ name: 'spreadsheet',
3
+ required: false,
4
+ description: 'Spreadsheet ID or URL (or --spreadsheet)',
5
+ };
6
+ const tabOpt = { flags: '--tab <name>', description: 'Tab / sheet name' };
7
+ const dry = { flags: '--dry-run', description: 'Preview without applying' };
8
+ const rangeOpt = { flags: '--range <a1>', description: 'A1 range, optionally Sheet!A1:B2' };
9
+ export const COMMANDS = [
10
+ {
11
+ name: 'auth',
12
+ description: 'Login, status, and logout. All login is CLI-triggered — never write OAuth code.',
13
+ subcommands: [
14
+ {
15
+ name: 'login',
16
+ description: 'Start login from the CLI (browser OAuth, service account, token, or ADC)',
17
+ options: [
18
+ { flags: '--credentials <file>', description: 'OAuth desktop client_secret.json' },
19
+ { flags: '--service-account <file>', description: 'Service account JSON key' },
20
+ { flags: '--token <token>', description: 'Pre-issued access token' },
21
+ { flags: '--token-file <file>', description: 'File containing an access token' },
22
+ { flags: '--adc', description: 'Use Application Default Credentials (gcloud)' },
23
+ { flags: '--account <name>', description: 'Named credential profile (default: default)' },
24
+ ],
25
+ },
26
+ { name: 'status', description: 'Show whether this machine is logged in', options: [{ flags: '--account <name>', description: 'Named profile' }] },
27
+ { name: 'logout', description: 'Delete stored credentials for a profile', options: [{ flags: '--account <name>', description: 'Named profile' }] },
28
+ ],
29
+ },
30
+ {
31
+ name: 'find',
32
+ description: 'Search Drive for spreadsheets by name',
33
+ options: [
34
+ { flags: '--name <query>', description: 'Name substring' },
35
+ { flags: '--limit <n>', description: 'Max results', type: 'number' },
36
+ ],
37
+ },
38
+ { name: 'info', description: 'Spreadsheet title, URL, and tabs', args: [spreadsheetArg] },
39
+ {
40
+ name: 'tabs',
41
+ description: 'List tabs',
42
+ subcommands: [{ name: 'list', description: 'List tabs in a spreadsheet', args: [spreadsheetArg] }],
43
+ },
44
+ {
45
+ name: 'read',
46
+ description: 'Read a tab as objects (headers + _row)',
47
+ args: [spreadsheetArg],
48
+ options: [
49
+ tabOpt,
50
+ rangeOpt,
51
+ { flags: '--limit <n>', description: 'Max data rows', type: 'number' },
52
+ { flags: '--header-row <n>', description: '1-based header row (auto-detect if omitted)', type: 'number' },
53
+ { flags: '--raw', description: 'Return a 2D array instead of objects' },
54
+ ],
55
+ subcommands: [
56
+ {
57
+ name: 'range',
58
+ description: 'Read a raw A1 range as a 2D array',
59
+ args: [spreadsheetArg],
60
+ options: [{ flags: '--range <a1>', description: 'A1 range', type: 'string' }],
61
+ },
62
+ ],
63
+ },
64
+ {
65
+ name: 'append',
66
+ description: 'Append object rows (creates a header row on an empty tab)',
67
+ args: [spreadsheetArg],
68
+ options: [tabOpt, { flags: '--rows <json>', description: 'JSON object, array of objects, or @file.json' }, dry],
69
+ },
70
+ {
71
+ name: 'update',
72
+ description: 'Update rows by key column (preferred over row index)',
73
+ args: [spreadsheetArg],
74
+ options: [
75
+ tabOpt,
76
+ { flags: '--key-col <name>', description: 'Column to match' },
77
+ { flags: '--key <value>', description: 'Value to match' },
78
+ { flags: '--set <json>', description: 'JSON object of columns to set, or @file.json' },
79
+ { flags: '--allow-multi', description: 'Update every matching row' },
80
+ dry,
81
+ ],
82
+ },
83
+ {
84
+ name: 'upsert',
85
+ description: 'Update existing rows by key, append the rest',
86
+ args: [spreadsheetArg],
87
+ options: [
88
+ tabOpt,
89
+ { flags: '--key-col <name>', description: 'Key column' },
90
+ { flags: '--rows <json>', description: 'JSON objects or @file.json' },
91
+ dry,
92
+ ],
93
+ },
94
+ {
95
+ name: 'set',
96
+ description: 'Overwrite an A1 range with a 2D JSON array',
97
+ args: [spreadsheetArg],
98
+ options: [rangeOpt, { flags: '--values <json>', description: '2D JSON array or @file.json' }, dry],
99
+ },
100
+ {
101
+ name: 'clear',
102
+ description: 'Clear values in a range (keeps formatting)',
103
+ args: [spreadsheetArg],
104
+ options: [rangeOpt, dry],
105
+ },
106
+ {
107
+ name: 'batch',
108
+ description: 'Run append/updateKey/updateRow/setRange/clear ops',
109
+ args: [spreadsheetArg],
110
+ options: [{ flags: '--ops <json>', description: 'JSON array of ops or @file.json' }, dry],
111
+ },
112
+ {
113
+ name: 'create',
114
+ description: 'Create a spreadsheet',
115
+ options: [
116
+ { flags: '--title <title>', description: 'Spreadsheet title' },
117
+ { flags: '--tabs <names>', description: 'Comma-separated tab names' },
118
+ dry,
119
+ ],
120
+ },
121
+ {
122
+ name: 'tab',
123
+ description: 'Add, rename, delete, or duplicate a tab',
124
+ subcommands: [
125
+ {
126
+ name: 'add',
127
+ description: 'Add a tab',
128
+ args: [spreadsheetArg],
129
+ options: [
130
+ { flags: '--title <name>', description: 'New tab title' },
131
+ { flags: '--index <n>', description: 'Insert index', type: 'number' },
132
+ dry,
133
+ ],
134
+ },
135
+ {
136
+ name: 'rename',
137
+ description: 'Rename a tab',
138
+ args: [spreadsheetArg],
139
+ options: [tabOpt, { flags: '--title <name>', description: 'New title' }, dry],
140
+ },
141
+ {
142
+ name: 'delete',
143
+ description: 'Delete a tab',
144
+ args: [spreadsheetArg],
145
+ options: [tabOpt, dry],
146
+ },
147
+ {
148
+ name: 'duplicate',
149
+ description: 'Duplicate a tab',
150
+ args: [spreadsheetArg],
151
+ options: [tabOpt, { flags: '--title <name>', description: 'Name for the copy' }, dry],
152
+ },
153
+ ],
154
+ },
155
+ {
156
+ name: 'format',
157
+ description: 'Format a range with intent flags (not RepeatCellRequest JSON)',
158
+ args: [spreadsheetArg],
159
+ options: [
160
+ rangeOpt,
161
+ tabOpt,
162
+ { flags: '--bold', description: 'Bold text' },
163
+ { flags: '--italic', description: 'Italic text' },
164
+ { flags: '--bg <color>', description: '#rrggbb or a name (navy, white, …)' },
165
+ { flags: '--fg <color>', description: 'Foreground color' },
166
+ { flags: '--align <dir>', description: 'left | center | right' },
167
+ { flags: '--valign <dir>', description: 'top | middle | bottom' },
168
+ { flags: '--wrap', description: 'Wrap text' },
169
+ dry,
170
+ ],
171
+ subcommands: [
172
+ {
173
+ name: 'header',
174
+ description: 'Bold/contrast header row and freeze it',
175
+ args: [spreadsheetArg],
176
+ options: [tabOpt, { flags: '--bg <color>', description: 'Header background' }, { flags: '--fg <color>', description: 'Header text color' }, { flags: '--no-freeze', description: 'Do not freeze row 1' }, dry],
177
+ },
178
+ ],
179
+ },
180
+ {
181
+ name: 'freeze',
182
+ description: 'Freeze rows and/or columns',
183
+ args: [spreadsheetArg],
184
+ options: [
185
+ tabOpt,
186
+ { flags: '--rows <n>', description: 'Frozen row count (default 1)', type: 'number' },
187
+ { flags: '--columns <n>', description: 'Frozen column count', type: 'number' },
188
+ dry,
189
+ ],
190
+ },
191
+ {
192
+ name: 'number-format',
193
+ description: 'Apply a number pattern to a range',
194
+ args: [spreadsheetArg],
195
+ options: [rangeOpt, tabOpt, { flags: '--pattern <pattern>', description: "e.g. Rp#,##0 or 0.00%" }, dry],
196
+ },
197
+ {
198
+ name: 'formula',
199
+ description: 'Write a formula into a cell',
200
+ args: [spreadsheetArg],
201
+ options: [
202
+ { flags: '--cell <a1>', description: 'Target cell, e.g. Sheet1!D2' },
203
+ { flags: '--expr <formula>', description: 'Formula, with or without leading =' },
204
+ dry,
205
+ ],
206
+ },
207
+ {
208
+ name: 'fill-down',
209
+ description: 'Write a formula down a range',
210
+ args: [spreadsheetArg],
211
+ options: [rangeOpt, { flags: '--expr <formula>', description: 'Formula, with or without leading =' }, dry],
212
+ },
213
+ {
214
+ name: 'share',
215
+ description: 'Share with a user',
216
+ args: [spreadsheetArg],
217
+ options: [
218
+ { flags: '--email <email>', description: 'Google account email' },
219
+ { flags: '--role <role>', description: 'reader or writer' },
220
+ dry,
221
+ ],
222
+ },
223
+ {
224
+ name: 'unshare',
225
+ description: 'Remove a user permission',
226
+ args: [spreadsheetArg],
227
+ options: [{ flags: '--email <email>', description: 'Google account email' }, dry],
228
+ },
229
+ {
230
+ name: 'permissions',
231
+ description: 'List sharing permissions',
232
+ args: [spreadsheetArg],
233
+ },
234
+ {
235
+ name: 'api',
236
+ description: 'Escape hatch for a raw Sheets/Drive method. Use only when no intent command exists.',
237
+ args: [{ name: 'method', required: true, description: 'e.g. batchUpdate, values.get, spreadsheets.create' }],
238
+ options: [
239
+ { flags: '--json <json>', description: 'Request body or @file.json' },
240
+ { flags: '--spreadsheet <id>', description: 'Spreadsheet ID or URL when the method needs one' },
241
+ ],
242
+ },
243
+ ];
244
+ export function findSpec(path) {
245
+ let list = COMMANDS;
246
+ let found;
247
+ for (const part of path) {
248
+ found = list?.find((c) => c.name === part);
249
+ if (!found)
250
+ return undefined;
251
+ list = found.subcommands;
252
+ }
253
+ return found;
254
+ }
255
+ export function helpJson(path = []) {
256
+ if (!path.length) {
257
+ return {
258
+ name: 'sheet',
259
+ description: 'Intent-level Google Sheets plugin. Never write googleapis or OAuth code — run clp sheet commands.',
260
+ globalOptions: [
261
+ { flags: '--pretty', description: 'Pretty-print JSON' },
262
+ { flags: '--account <name>', description: 'Credential profile' },
263
+ { flags: '--spreadsheet <id>', description: 'Default spreadsheet ID or URL' },
264
+ { flags: '--dry-run', description: 'Preview writes' },
265
+ { flags: '--help --json', description: 'Machine-readable command schema' },
266
+ ],
267
+ commands: COMMANDS.map(summarize),
268
+ };
269
+ }
270
+ const spec = findSpec(path);
271
+ if (!spec) {
272
+ return { error: { code: 'UNKNOWN_COMMAND', message: `No command: ${path.join(' ')}`, commands: COMMANDS.map((c) => c.name) } };
273
+ }
274
+ return expand(spec);
275
+ }
276
+ function summarize(spec) {
277
+ return {
278
+ name: spec.name,
279
+ description: spec.description,
280
+ subcommands: spec.subcommands?.map((s) => s.name),
281
+ };
282
+ }
283
+ function expand(spec) {
284
+ return {
285
+ name: spec.name,
286
+ description: spec.description,
287
+ args: spec.args ?? [],
288
+ options: spec.options ?? [],
289
+ subcommands: spec.subcommands?.map(expand),
290
+ };
291
+ }
@@ -0,0 +1,7 @@
1
+ import type { Command } from 'commander';
2
+ export type ClipyPlugin = {
3
+ name: string;
4
+ description: string;
5
+ register(cmd: Command): void;
6
+ helpJson(path: string[]): unknown;
7
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ export type GridRange = {
2
+ sheetId: number;
3
+ startRowIndex?: number;
4
+ endRowIndex?: number;
5
+ startColumnIndex?: number;
6
+ endColumnIndex?: number;
7
+ };
8
+ export type ParsedA1 = {
9
+ sheet?: string;
10
+ startCol: number;
11
+ startRow: number;
12
+ endCol?: number;
13
+ endRow?: number;
14
+ };
15
+ export declare function colLetter(index: number): string;
16
+ export declare function colIndex(letters: string): number;
17
+ export declare function parseA1(range: string): ParsedA1;
18
+ export declare function qualifyRange(range: string, tab?: string): string;
19
+ export declare function toGridRange(parsed: ParsedA1, sheetId: number): GridRange;
20
+ export declare function a1FromParts(sheet: string, startCol: number, startRow: number, endCol?: number, endRow?: number): string;
package/dist/ranges.js ADDED
@@ -0,0 +1,77 @@
1
+ import { CliError } from './output.js';
2
+ export function colLetter(index) {
3
+ let n = index + 1;
4
+ let s = '';
5
+ while (n > 0) {
6
+ const rem = (n - 1) % 26;
7
+ s = String.fromCharCode(65 + rem) + s;
8
+ n = Math.floor((n - 1) / 26);
9
+ }
10
+ return s;
11
+ }
12
+ export function colIndex(letters) {
13
+ let n = 0;
14
+ for (const ch of letters.toUpperCase()) {
15
+ n = n * 26 + (ch.charCodeAt(0) - 64);
16
+ }
17
+ return n - 1;
18
+ }
19
+ const A1_CELL = /([A-Za-z]+)?(\d+)?/;
20
+ export function parseA1(range) {
21
+ const trimmed = range.trim();
22
+ if (!trimmed)
23
+ throw new CliError('VALIDATION_ERROR', 'Range is required');
24
+ let sheet;
25
+ let a1 = trimmed;
26
+ const bang = trimmed.lastIndexOf('!');
27
+ if (bang !== -1) {
28
+ sheet = trimmed.slice(0, bang).replace(/^'|'$/g, '').replace(/''/g, "'");
29
+ a1 = trimmed.slice(bang + 1);
30
+ }
31
+ const [startRaw, endRaw] = a1.split(':');
32
+ const start = parseCorner(startRaw);
33
+ const end = endRaw ? parseCorner(endRaw) : undefined;
34
+ return {
35
+ sheet,
36
+ startCol: start.col ?? 0,
37
+ startRow: start.row ?? 0,
38
+ endCol: end?.col !== undefined ? end.col + 1 : start.col !== undefined && !endRaw ? start.col + 1 : end?.col,
39
+ endRow: end?.row !== undefined ? end.row + 1 : start.row !== undefined && !endRaw ? start.row + 1 : end?.row,
40
+ };
41
+ }
42
+ function parseCorner(raw) {
43
+ const m = raw.trim().match(A1_CELL);
44
+ if (!m)
45
+ throw new CliError('VALIDATION_ERROR', `Invalid A1 notation: ${raw}`);
46
+ const col = m[1] ? colIndex(m[1]) : undefined;
47
+ const row = m[2] ? Number(m[2]) - 1 : undefined;
48
+ return { col, row };
49
+ }
50
+ export function qualifyRange(range, tab) {
51
+ if (range.includes('!'))
52
+ return range;
53
+ if (!tab)
54
+ return range;
55
+ const safe = tab.includes(' ') || /[^A-Za-z0-9_]/.test(tab) ? `'${tab.replace(/'/g, "''")}'` : tab;
56
+ return `${safe}!${range}`;
57
+ }
58
+ export function toGridRange(parsed, sheetId) {
59
+ const range = { sheetId };
60
+ if (parsed.startRow !== undefined)
61
+ range.startRowIndex = parsed.startRow;
62
+ if (parsed.endRow !== undefined)
63
+ range.endRowIndex = parsed.endRow;
64
+ if (parsed.startCol !== undefined)
65
+ range.startColumnIndex = parsed.startCol;
66
+ if (parsed.endCol !== undefined)
67
+ range.endColumnIndex = parsed.endCol;
68
+ return range;
69
+ }
70
+ export function a1FromParts(sheet, startCol, startRow, endCol, endRow) {
71
+ const safe = sheet.includes(' ') || /[^A-Za-z0-9_]/.test(sheet) ? `'${sheet.replace(/'/g, "''")}'` : sheet;
72
+ const start = `${colLetter(startCol)}${startRow + 1}`;
73
+ if (endCol === undefined && endRow === undefined)
74
+ return `${safe}!${start}`;
75
+ const end = `${colLetter((endCol ?? startCol + 1) - 1)}${(endRow ?? startRow + 1)}`;
76
+ return `${safe}!${start}:${end}`;
77
+ }