@genspark/cli 1.5.1 → 1.5.3

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 (52) hide show
  1. package/README.md +22 -14
  2. package/dist/client.d.ts +5 -0
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +107 -0
  5. package/dist/client.js.map +1 -1
  6. package/dist/commands/aidrive-upload.d.ts +10 -7
  7. package/dist/commands/aidrive-upload.d.ts.map +1 -1
  8. package/dist/commands/aidrive-upload.js +35 -8
  9. package/dist/commands/aidrive-upload.js.map +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +187 -34
  12. package/dist/index.js.map +1 -1
  13. package/dist/localFiles.d.ts +25 -0
  14. package/dist/localFiles.d.ts.map +1 -1
  15. package/dist/localFiles.js +40 -0
  16. package/dist/localFiles.js.map +1 -1
  17. package/dist/mesh/manifest.d.ts +4 -4
  18. package/dist/mesh/manifest.d.ts.map +1 -1
  19. package/dist/mesh/manifest.js +9 -9
  20. package/dist/mesh/manifest.js.map +1 -1
  21. package/dist/task-shim.d.ts +13 -0
  22. package/dist/task-shim.d.ts.map +1 -0
  23. package/dist/task-shim.js +40 -0
  24. package/dist/task-shim.js.map +1 -0
  25. package/docs/skills.md +8 -5
  26. package/package.json +1 -1
  27. package/skills/gsk-acp-agents/SKILL.md +2 -2
  28. package/skills/gsk-audio-processing/SKILL.md +5 -3
  29. package/skills/gsk-create-task/SKILL.md +7 -40
  30. package/skills/gsk-github/SKILL.md +4 -0
  31. package/skills/gsk-gmail/SKILL.md +4 -0
  32. package/skills/gsk-google-calendar/SKILL.md +5 -0
  33. package/skills/gsk-google-chat/SKILL.md +6 -2
  34. package/skills/gsk-google-docs/SKILL.md +4 -0
  35. package/skills/gsk-google-drive/SKILL.md +4 -0
  36. package/skills/gsk-google-sheets/SKILL.md +4 -0
  37. package/skills/gsk-google-slides/SKILL.md +4 -0
  38. package/skills/gsk-image-generation/SKILL.md +1 -1
  39. package/skills/gsk-microsoft-teams/SKILL.md +18 -5
  40. package/skills/gsk-notion/SKILL.md +4 -0
  41. package/skills/gsk-onedrive/SKILL.md +4 -0
  42. package/skills/gsk-outlook-calendar/SKILL.md +5 -0
  43. package/skills/gsk-outlook-email/SKILL.md +4 -0
  44. package/skills/gsk-shared/SKILL.md +5 -5
  45. package/skills/gsk-sharepoint/SKILL.md +4 -0
  46. package/skills/gsk-slack/SKILL.md +7 -1
  47. package/skills/gsk-task-artifacts/SKILL.md +35 -0
  48. package/skills/gsk-task-continue/SKILL.md +13 -10
  49. package/skills/gsk-task-info/SKILL.md +36 -0
  50. package/skills/gsk-task-status/SKILL.md +6 -6
  51. package/skills/gsk-task-stop/SKILL.md +35 -0
  52. package/skills/gsk-video-generation/SKILL.md +8 -5
package/dist/index.js CHANGED
@@ -27,13 +27,14 @@ import * as pathModule from 'path';
27
27
  import { createRequire } from 'module';
28
28
  import { spawn } from 'child_process';
29
29
  import { ApiClient, isAuthError, unauthenticatedRequest, } from './client.js';
30
- import { classifyFileArg, awaitLocalFileReady, decideAfterWait, localFileNotReadyError, } from './localFiles.js';
30
+ import { classifyFileArg, awaitLocalFileReady, decideAfterWait, localFileNotReadyError, passthroughWarning, urlsParamItems, } from './localFiles.js';
31
31
  import { setDebugEnabled, setOutputFormat, debug, info, warn, error as logError, output, } from './logger.js';
32
32
  import { loadConfigFile, getConfigPath, loadToolsCache, normalizeBaseUrl, saveToolsCache, setConfigPathOverride, updateConfigFile, } from './config.js';
33
33
  import { checkForUpdates } from './updater.js';
34
+ import { shimLegacyTaskInvocation } from './task-shim.js';
34
35
  import { getSandboxRuntime } from './runtime.js';
35
36
  import { registerDesignCommand } from './commands/design.js';
36
- import { pickExistingFileOperand } from './commands/aidrive-upload.js';
37
+ import { collectUploadFiles, pickExistingUploadOperand, } from './commands/aidrive-upload.js';
37
38
  import { parseLastSeenIndex, eventStdoutLine } from './commands/chat-events.js';
38
39
  import { mediaItemsKeyFor, mediaSummaryLines } from './mediaSummary.js';
39
40
  import { registerMeshCommand, getMeshInvocation, runMesh, } from './commands/mesh.js';
@@ -351,7 +352,7 @@ async function uploadLocalFile(filePath, client) {
351
352
  *
352
353
  * This avoids the roundabout blob-upload + download_file two-step approach.
353
354
  */
354
- async function uploadToAiDrive(filePath, uploadPath, override = false) {
355
+ async function uploadToAiDrive(filePath, uploadPath, override = false, workspace = 'personal') {
355
356
  const fileName = pathModule.basename(filePath);
356
357
  const ext = pathModule.extname(filePath).toLowerCase();
357
358
  const contentType = CONTENT_TYPE_MAP[ext] || 'application/octet-stream';
@@ -359,7 +360,7 @@ async function uploadToAiDrive(filePath, uploadPath, override = false) {
359
360
  info(`Uploading ${fileName} (${formatFileSize(fileSize)}, ${contentType}) directly to AI Drive...`);
360
361
  const globalOpts = getGlobalOptions();
361
362
  const baseUrl = globalOpts.baseUrl.replace(/\/$/, '');
362
- const uploadUrl = `${baseUrl}/api/tool_cli/aidrive/upload?upload_path=${encodeURIComponent(uploadPath)}${override ? '&override=true' : ''}`;
363
+ const uploadUrl = `${baseUrl}/api/tool_cli/aidrive/upload?upload_path=${encodeURIComponent(uploadPath)}&workspace=${encodeURIComponent(workspace)}${override ? '&override=true' : ''}`;
363
364
  const headers = { 'Content-Type': contentType };
364
365
  if (globalOpts.apiKey)
365
366
  headers['X-Api-Key'] = globalOpts.apiKey;
@@ -417,7 +418,9 @@ async function resolveFileArg(value, client) {
417
418
  case 'passthrough':
418
419
  // No local evidence beyond the parent directory: let the backend try
419
420
  // it as an AI Drive path (its failure mode is a per-URL actionable
420
- // error, not a generic one).
421
+ // error, not a generic one). Warn first so a failed producer in the
422
+ // same batch is diagnosable from this call's own output.
423
+ warn(passthroughWarning(localPath, readiness));
421
424
  return value;
422
425
  }
423
426
  }
@@ -485,23 +488,29 @@ async function resolveLocalFiles(args, client) {
485
488
  }
486
489
  // Other array-of-URLs params (e.g. image_urls) — keep the existing
487
490
  // auto-upload-to-blob behaviour, which is the right thing for image
488
- // generation sources etc.
489
- else if (key.endsWith('_urls') && Array.isArray(value)) {
490
- const resolvedUrls = [];
491
- for (const item of value) {
492
- if (typeof item === 'string') {
493
- resolvedUrls.push(await resolveFileArg(item, client));
494
- }
495
- else {
496
- resolvedUrls.push(item);
491
+ // generation sources etc. urlsParamItems also accepts a plain-string
492
+ // value (an --args-file authored with a string where the schema wants
493
+ // an array) so the local path still gets uploaded instead of leaking
494
+ // to the server; writing the array back restores the schema shape.
495
+ else {
496
+ const urlsItems = urlsParamItems(key, value);
497
+ if (urlsItems) {
498
+ const resolvedUrls = [];
499
+ for (const item of urlsItems) {
500
+ if (typeof item === 'string') {
501
+ resolvedUrls.push(await resolveFileArg(item, client));
502
+ }
503
+ else {
504
+ resolvedUrls.push(item);
505
+ }
497
506
  }
507
+ resolved[key] = resolvedUrls;
508
+ }
509
+ // Handle single URL (e.g., url, audio_url)
510
+ else if ((key === 'url' || key.endsWith('_url')) &&
511
+ typeof value === 'string') {
512
+ resolved[key] = await resolveFileArg(value, client);
498
513
  }
499
- resolved[key] = resolvedUrls;
500
- }
501
- // Handle single URL (e.g., url, audio_url)
502
- else if ((key === 'url' || key.endsWith('_url')) &&
503
- typeof value === 'string') {
504
- resolved[key] = await resolveFileArg(value, client);
505
514
  }
506
515
  }
507
516
  return resolved;
@@ -754,9 +763,17 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
754
763
  if (outputFileKeys.length > 0) {
755
764
  cmd.option('-o, --output-file <path>', 'Download the generated file to a local path');
756
765
  }
766
+ // task ask (task_continue) shares the async-first submit → --follow
767
+ // upgrades the stub into a live SSE follow, same as task create.
768
+ // (Named --follow, not --attach: tools with an attachments array param
769
+ // already own --attach as a file alias.)
770
+ if (tool.name === 'task_continue') {
771
+ cmd.option('--follow', 'After the async submit, follow the durable run live (SSE) and exit with the terminal result', false);
772
+ }
757
773
  // For create_task, add --acp flag to enter ACP stdio bridge mode
758
774
  if (tool.name === 'create_task') {
759
775
  cmd.option('--acp', 'Start as ACP (Agent Client Protocol) stdio agent instead of one-shot execution', false);
776
+ cmd.option('--follow', 'After the async submit, follow the durable run live (SSE) and exit with the terminal result', false);
760
777
  cmd.option('-o, --output-file <path>', 'Export the artifact (pptx/docx/xlsx) to a local file after task completes');
761
778
  cmd.option('--session-id <id>', 'ACP session ID (used with get-project)');
762
779
  cmd.option('--file <values...>', 'Import local files (Design: structured context; other tasks: CSV/Excel file_urls)');
@@ -774,6 +791,9 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
774
791
  }
775
792
  // For the aidrive tool, add client-side options used with --local_file upload
776
793
  if (tool.name === 'aidrive') {
794
+ if (!Object.hasOwn(props, 'workspace')) {
795
+ cmd.option('--workspace <value>', 'AI Drive workspace: personal or public_brain');
796
+ }
777
797
  cmd.option('--local_file <path>', 'Local file path to upload to AI Drive (supports files >5 MB; alternative to --file_content)');
778
798
  cmd.option('--override', 'Overwrite an existing file at the destination path (used with --local_file)', false);
779
799
  }
@@ -908,6 +928,11 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
908
928
  const outputFilePath = opts.outputFile;
909
929
  delete opts.outputFile;
910
930
  delete opts.acp; // ACP flag is client-side only
931
+ // -o exports an artifact of the FINISHED project — an async submit
932
+ // stub has nothing to export yet, so an export request implies the
933
+ // live follow.
934
+ const attachTask = !!opts.follow || !!outputFilePath;
935
+ delete opts.follow; // client-side only
911
936
  delete opts.sessionId; // get-project flag is client-side only
912
937
  const argsFilePath = opts.argsFile;
913
938
  delete opts.argsFile; // client-side only; merged below
@@ -1115,7 +1140,7 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
1115
1140
  const operandCandidates = primaryArgValue !== undefined && primaryArgValue === args.action
1116
1141
  ? cmd.args.slice(1)
1117
1142
  : cmd.args;
1118
- const pathOperand = pickExistingFileOperand(operandCandidates);
1143
+ const pathOperand = pickExistingUploadOperand(operandCandidates);
1119
1144
  if (pathOperand) {
1120
1145
  opts.local_file = pathOperand;
1121
1146
  }
@@ -1135,6 +1160,7 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
1135
1160
  opts.local_file) {
1136
1161
  const localFilePath = opts.local_file;
1137
1162
  const override = !!opts.override;
1163
+ const workspace = args.workspace || 'personal';
1138
1164
  if (!fs.existsSync(localFilePath)) {
1139
1165
  logError(`File not found: ${localFilePath}`);
1140
1166
  process.exit(1);
@@ -1146,9 +1172,35 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
1146
1172
  const uploadPath = rawUploadPath.startsWith('/')
1147
1173
  ? rawUploadPath
1148
1174
  : `/${rawUploadPath}`;
1149
- // Stream directly to AI Drive — single request, no blob middleman
1150
- const result = await uploadToAiDrive(localFilePath, uploadPath, override);
1151
- output(result);
1175
+ const localStat = fs.statSync(localFilePath);
1176
+ const files = collectUploadFiles(localFilePath);
1177
+ if (localStat.isDirectory()) {
1178
+ if (files.length === 0) {
1179
+ logError(`Folder has no files to upload: ${localFilePath}`);
1180
+ process.exit(1);
1181
+ }
1182
+ info(`Uploading folder ${localFilePath} (${files.length} files)...`);
1183
+ const uploaded = [];
1184
+ for (const file of files) {
1185
+ const destination = pathModule.posix.join(uploadPath, file.relativePath);
1186
+ const result = await uploadToAiDrive(file.absolutePath, destination, override, workspace);
1187
+ if (result.status !== 'ok') {
1188
+ output(result);
1189
+ process.exit(1);
1190
+ }
1191
+ uploaded.push(destination);
1192
+ }
1193
+ output({
1194
+ status: 'ok',
1195
+ message: `Uploaded ${uploaded.length} files`,
1196
+ data: { workspace, root: uploadPath, files: uploaded },
1197
+ });
1198
+ }
1199
+ else {
1200
+ // Stream directly to AI Drive — single request, no blob middleman
1201
+ const result = await uploadToAiDrive(localFilePath, uploadPath, override, workspace);
1202
+ output(result);
1203
+ }
1152
1204
  return;
1153
1205
  }
1154
1206
  const designAttachmentArgs = collectDesignAttachmentArgs(process.argv);
@@ -1200,7 +1252,36 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
1200
1252
  }
1201
1253
  // Resolve local file paths to uploaded URLs
1202
1254
  const resolvedArgs = await resolveLocalFiles(args, client);
1203
- const result = await client.executeTool(tool.name, resolvedArgs);
1255
+ // The server owns the async-first default: create_task /
1256
+ // task_continue return a submit stub ({status: "submitted",
1257
+ // run_id, stream_url}) unless `--wait true` was passed. --attach
1258
+ // upgrades the stub into a live SSE follow that exits with the
1259
+ // run's terminal result — the work still executes server-side.
1260
+ let result = await client.executeTool(tool.name, resolvedArgs);
1261
+ const stubData = result.data;
1262
+ if (attachTask &&
1263
+ result.status === 'ok' &&
1264
+ stubData &&
1265
+ typeof stubData.stream_url === 'string' &&
1266
+ (stubData.status === 'submitted' ||
1267
+ stubData.status === 'duplicate_submit_converged')) {
1268
+ const meta = await client.attachGskTaskRun(stubData.stream_url);
1269
+ const runStatus = String(meta.status || '');
1270
+ const summary = meta.result_summary && typeof meta.result_summary === 'object'
1271
+ ? meta.result_summary
1272
+ : {};
1273
+ result = {
1274
+ status: runStatus === 'succeeded' ? 'ok' : 'error',
1275
+ message: runStatus === 'succeeded'
1276
+ ? 'success'
1277
+ : `task ended with status ${runStatus || 'unknown'}`,
1278
+ data: {
1279
+ ...summary,
1280
+ run_id: stubData.run_id,
1281
+ run_status: runStatus,
1282
+ },
1283
+ };
1284
+ }
1204
1285
  const isSbGitCloneUrl = tool.name === 'sb-git' &&
1205
1286
  resolvedArgs.action === 'clone-url';
1206
1287
  if (isSbGitCloneUrl &&
@@ -2105,19 +2186,23 @@ _groupCommands.set('phone-call', phoneCallCmd);
2105
2186
  // gk_tool_cli_conversation) and attach as dynamic subcommands via the
2106
2187
  // seeded _groupCommands entry; this static parent only gives the group a
2107
2188
  // real description instead of the generated "Commands for chat".
2108
- const chatCmd = program
2109
- .command('chat')
2110
- .description('Headless agent conversations: start one and get ids back immediately, ' +
2111
- 'poll status for replies, send follow-ups, stop, delete, rename ' +
2112
- '(see subcommands).');
2113
- _groupCommands.set('chat', chatCmd);
2114
- // `chat events` is static (not server-registered): it needs real-time
2189
+ // The task command tree. Verbs are server-registered (create / status /
2190
+ // info / ask / answer / messages / wait / stop / delete / rename attach as
2191
+ // dynamic subcommands via the seeded group); this static parent gives the
2192
+ // group a description, and hosts the one static verb below.
2193
+ const taskCmd = program
2194
+ .command('task')
2195
+ .description('Agent tasks and conversations: create one (submits and returns in ' +
2196
+ 'seconds), poll status, read outputs, send follow-ups, answer ' +
2197
+ 'agent questions, stop, delete, rename (see subcommands).');
2198
+ _groupCommands.set('task', taskCmd);
2199
+ // `task events` is static (not server-registered): it needs real-time
2115
2200
  // line-by-line output the generic dynamic-command handler cannot provide
2116
2201
  // (that path buffers intermediate NDJSON messages and only prints the
2117
2202
  // final result).
2118
- chatCmd
2203
+ taskCmd
2119
2204
  .command('events <project_id>')
2120
- .description("Attach to the conversation's in-flight run and stream its events " +
2205
+ .description("Attach to the task's in-flight run and stream its events " +
2121
2206
  'live (one JSON line per event on stdout): the first line is a ' +
2122
2207
  'project_data snapshot event, buffered events since run start ' +
2123
2208
  'replay next, then live events follow until the run ends. Every ' +
@@ -2167,10 +2252,78 @@ chatCmd
2167
2252
  process.exit(1);
2168
2253
  }
2169
2254
  });
2255
+ // `task export` is static: it composes two calls (task_status to resolve
2256
+ // the task type, then /export_artifact) plus an optional local download —
2257
+ // the generic dynamic handler maps one verb to one server tool.
2258
+ taskCmd
2259
+ .command('export <project_id>')
2260
+ .description("Convert and download a task's deliverable (docs → docx/pdf, " +
2261
+ 'slides → pptx/pdf, sheets → xlsx). Prints the download URL; with ' +
2262
+ '-o <path> also saves the file locally. The task type is resolved ' +
2263
+ 'automatically from the project; `gsk task artifacts <project_id>` ' +
2264
+ 'lists what a task produced and which formats it can export.')
2265
+ .option('--format <format>', 'export format: auto | docx | pdf | pptx | xlsx (auto picks the ' +
2266
+ "task type's natural format)", 'auto')
2267
+ .option('-o, --output-file <path>', 'save the exported file to this path')
2268
+ .action(async (projectId, opts) => {
2269
+ try {
2270
+ const client = createClient();
2271
+ const statusResult = await client.executeTool('task_status', {
2272
+ id: projectId,
2273
+ });
2274
+ const statusData = statusResult.data;
2275
+ const taskType = statusResult.status === 'ok' && statusData
2276
+ ? statusData.task_type
2277
+ : undefined;
2278
+ if (!taskType) {
2279
+ output(statusResult.status === 'ok'
2280
+ ? {
2281
+ status: 'error',
2282
+ message: `Cannot resolve task type for ${projectId}`,
2283
+ data: null,
2284
+ }
2285
+ : statusResult);
2286
+ process.exit(1);
2287
+ }
2288
+ const exportResult = await client.exportArtifact(projectId, taskType, opts.format || 'auto');
2289
+ const expData = exportResult.data;
2290
+ if (exportResult.status === 'ok' && expData && opts.outputFile) {
2291
+ const downloadUrl = expData.download_url;
2292
+ if (downloadUrl) {
2293
+ expData.local_path = await downloadToFile(downloadUrl, opts.outputFile, client);
2294
+ }
2295
+ else {
2296
+ // The converter can report ok with an empty URL (e.g. the
2297
+ // share-link build failed server-side) — surface it instead
2298
+ // of printing ok with no file on disk.
2299
+ output({
2300
+ status: 'error',
2301
+ message: 'Export produced no download_url — nothing was saved ' +
2302
+ `to ${opts.outputFile}`,
2303
+ data: expData,
2304
+ });
2305
+ process.exit(1);
2306
+ }
2307
+ }
2308
+ output(exportResult);
2309
+ if (exportResult.status === 'error') {
2310
+ process.exit(1);
2311
+ }
2312
+ }
2313
+ catch (err) {
2314
+ logError(err.message);
2315
+ process.exit(1);
2316
+ }
2317
+ });
2170
2318
  // ============================================
2171
2319
  // Dynamic Tool Registration & Startup
2172
2320
  // ============================================
2173
2321
  async function main() {
2322
+ // Pre-tree `gsk task <type>` compat (see shimLegacyTaskInvocation).
2323
+ process.argv = [
2324
+ ...process.argv.slice(0, 2),
2325
+ ...shimLegacyTaskInvocation(process.argv.slice(2)),
2326
+ ];
2174
2327
  // `gsk mesh ...` is a native-binary passthrough — intercept it before the
2175
2328
  // tool-fetch path and npm self-update gating (it needs neither). Handling it
2176
2329
  // outside commander keeps the rest of the CLI's option parsing intact (see