@genspark/cli 1.5.0 → 1.5.2
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/README.md +22 -14
- package/dist/argsFile.d.ts +17 -0
- package/dist/argsFile.d.ts.map +1 -0
- package/dist/argsFile.js +38 -0
- package/dist/argsFile.js.map +1 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +126 -1
- package/dist/client.js.map +1 -1
- package/dist/commands/aidrive-upload.d.ts +10 -7
- package/dist/commands/aidrive-upload.d.ts.map +1 -1
- package/dist/commands/aidrive-upload.js +35 -8
- package/dist/commands/aidrive-upload.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +370 -34
- package/dist/index.js.map +1 -1
- package/dist/localFiles.d.ts +10 -0
- package/dist/localFiles.d.ts.map +1 -1
- package/dist/localFiles.js +17 -0
- package/dist/localFiles.js.map +1 -1
- package/dist/mesh/manifest.d.ts +4 -4
- package/dist/mesh/manifest.d.ts.map +1 -1
- package/dist/mesh/manifest.js +9 -9
- package/dist/mesh/manifest.js.map +1 -1
- package/dist/task-shim.d.ts +13 -0
- package/dist/task-shim.d.ts.map +1 -0
- package/dist/task-shim.js +40 -0
- package/dist/task-shim.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/skills.md +9 -5
- package/package.json +1 -1
- package/skills/gsk-acp-agents/SKILL.md +2 -2
- package/skills/gsk-create-task/SKILL.md +6 -6
- package/skills/gsk-github/SKILL.md +4 -0
- package/skills/gsk-gmail/SKILL.md +4 -0
- package/skills/gsk-google-calendar/SKILL.md +5 -0
- package/skills/gsk-google-chat/SKILL.md +6 -2
- package/skills/gsk-google-docs/SKILL.md +4 -0
- package/skills/gsk-google-drive/SKILL.md +4 -0
- package/skills/gsk-google-sheets/SKILL.md +4 -0
- package/skills/gsk-google-slides/SKILL.md +51 -0
- package/skills/gsk-image-generation/SKILL.md +1 -1
- package/skills/gsk-microsoft-teams/SKILL.md +25 -9
- package/skills/gsk-notion/SKILL.md +4 -0
- package/skills/gsk-onedrive/SKILL.md +4 -0
- package/skills/gsk-outlook-calendar/SKILL.md +5 -0
- package/skills/gsk-outlook-email/SKILL.md +4 -0
- package/skills/gsk-shared/SKILL.md +5 -5
- package/skills/gsk-sharepoint/SKILL.md +4 -0
- package/skills/gsk-slack/SKILL.md +7 -1
- package/skills/gsk-task-artifacts/SKILL.md +35 -0
- package/skills/gsk-task-continue/SKILL.md +13 -10
- package/skills/gsk-task-info/SKILL.md +36 -0
- package/skills/gsk-task-status/SKILL.md +6 -6
- package/skills/gsk-task-stop/SKILL.md +35 -0
- package/skills/gsk-video-generation/SKILL.md +9 -6
- package/skills/gsk-youtube/SKILL.md +5 -4
package/dist/index.js
CHANGED
|
@@ -26,14 +26,15 @@ import * as os from 'os';
|
|
|
26
26
|
import * as pathModule from 'path';
|
|
27
27
|
import { createRequire } from 'module';
|
|
28
28
|
import { spawn } from 'child_process';
|
|
29
|
-
import { ApiClient, unauthenticatedRequest } from './client.js';
|
|
30
|
-
import { classifyFileArg, awaitLocalFileReady, decideAfterWait, localFileNotReadyError, } from './localFiles.js';
|
|
31
|
-
import { setDebugEnabled, setOutputFormat, debug, info, error as logError, output, } from './logger.js';
|
|
29
|
+
import { ApiClient, isAuthError, unauthenticatedRequest, } from './client.js';
|
|
30
|
+
import { classifyFileArg, awaitLocalFileReady, decideAfterWait, localFileNotReadyError, passthroughWarning, } from './localFiles.js';
|
|
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 {
|
|
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';
|
|
@@ -157,6 +158,7 @@ const CONTENT_TYPE_MAP = {
|
|
|
157
158
|
'.png': 'image/png',
|
|
158
159
|
'.gif': 'image/gif',
|
|
159
160
|
'.webp': 'image/webp',
|
|
161
|
+
'.svg': 'image/svg+xml',
|
|
160
162
|
'.pdf': 'application/pdf',
|
|
161
163
|
'.txt': 'text/plain',
|
|
162
164
|
'.json': 'application/json',
|
|
@@ -164,6 +166,20 @@ const CONTENT_TYPE_MAP = {
|
|
|
164
166
|
'.css': 'text/css',
|
|
165
167
|
'.js': 'application/javascript',
|
|
166
168
|
'.ts': 'application/typescript',
|
|
169
|
+
'.jsx': 'text/javascript',
|
|
170
|
+
'.tsx': 'text/typescript',
|
|
171
|
+
'.vue': 'text/html',
|
|
172
|
+
'.py': 'text/x-python',
|
|
173
|
+
'.java': 'text/x-java-source',
|
|
174
|
+
'.go': 'text/x-go',
|
|
175
|
+
'.rs': 'text/x-rust',
|
|
176
|
+
'.c': 'text/x-c',
|
|
177
|
+
'.cc': 'text/x-c++',
|
|
178
|
+
'.cpp': 'text/x-c++',
|
|
179
|
+
'.h': 'text/x-c',
|
|
180
|
+
'.hpp': 'text/x-c++',
|
|
181
|
+
'.yaml': 'application/x-yaml',
|
|
182
|
+
'.yml': 'application/x-yaml',
|
|
167
183
|
'.md': 'text/markdown',
|
|
168
184
|
'.xml': 'application/xml',
|
|
169
185
|
'.csv': 'text/csv',
|
|
@@ -173,6 +189,46 @@ const CONTENT_TYPE_MAP = {
|
|
|
173
189
|
'.wav': 'audio/wav',
|
|
174
190
|
'.webm': 'video/webm',
|
|
175
191
|
};
|
|
192
|
+
/** Preserve interleaved --image/--file order; Commander groups options by key. */
|
|
193
|
+
export function collectDesignAttachmentArgs(argv) {
|
|
194
|
+
const result = [];
|
|
195
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
196
|
+
const token = argv[index];
|
|
197
|
+
const equalsMatch = token.match(/^--(image|file)=(.+)$/);
|
|
198
|
+
if (equalsMatch) {
|
|
199
|
+
result.push({
|
|
200
|
+
path: equalsMatch[2],
|
|
201
|
+
role: equalsMatch[1] === 'image' ? 'visual_reference' : 'context',
|
|
202
|
+
});
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (token !== '--image' && token !== '--file')
|
|
206
|
+
continue;
|
|
207
|
+
const role = token === '--image' ? 'visual_reference' : 'context';
|
|
208
|
+
while (index + 1 < argv.length && !argv[index + 1].startsWith('-')) {
|
|
209
|
+
result.push({ path: argv[index + 1], role });
|
|
210
|
+
index += 1;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
async function uploadDesignAttachments(values, client) {
|
|
216
|
+
const attachments = [];
|
|
217
|
+
// Upload the complete batch before executeTool: any failure aborts project
|
|
218
|
+
// creation/continuation rather than leaving a half-bound Design project.
|
|
219
|
+
for (const value of values) {
|
|
220
|
+
const localPath = expandHome(value.path);
|
|
221
|
+
if (!isLocalFilePath(localPath)) {
|
|
222
|
+
throw new Error(`File not found: ${value.path}`);
|
|
223
|
+
}
|
|
224
|
+
const name = pathModule.basename(localPath);
|
|
225
|
+
const ext = pathModule.extname(localPath).toLowerCase();
|
|
226
|
+
const mimeType = CONTENT_TYPE_MAP[ext] || 'application/octet-stream';
|
|
227
|
+
const url = await uploadLocalFile(localPath, client);
|
|
228
|
+
attachments.push({ url, name, mime_type: mimeType, role: value.role });
|
|
229
|
+
}
|
|
230
|
+
return attachments;
|
|
231
|
+
}
|
|
176
232
|
/**
|
|
177
233
|
* Stream a local file to a URL using Node.js http/https.request +
|
|
178
234
|
* fs.createReadStream so the file is never fully buffered in memory.
|
|
@@ -296,7 +352,7 @@ async function uploadLocalFile(filePath, client) {
|
|
|
296
352
|
*
|
|
297
353
|
* This avoids the roundabout blob-upload + download_file two-step approach.
|
|
298
354
|
*/
|
|
299
|
-
async function uploadToAiDrive(filePath, uploadPath, override = false) {
|
|
355
|
+
async function uploadToAiDrive(filePath, uploadPath, override = false, workspace = 'personal') {
|
|
300
356
|
const fileName = pathModule.basename(filePath);
|
|
301
357
|
const ext = pathModule.extname(filePath).toLowerCase();
|
|
302
358
|
const contentType = CONTENT_TYPE_MAP[ext] || 'application/octet-stream';
|
|
@@ -304,7 +360,7 @@ async function uploadToAiDrive(filePath, uploadPath, override = false) {
|
|
|
304
360
|
info(`Uploading ${fileName} (${formatFileSize(fileSize)}, ${contentType}) directly to AI Drive...`);
|
|
305
361
|
const globalOpts = getGlobalOptions();
|
|
306
362
|
const baseUrl = globalOpts.baseUrl.replace(/\/$/, '');
|
|
307
|
-
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' : ''}`;
|
|
308
364
|
const headers = { 'Content-Type': contentType };
|
|
309
365
|
if (globalOpts.apiKey)
|
|
310
366
|
headers['X-Api-Key'] = globalOpts.apiKey;
|
|
@@ -362,7 +418,9 @@ async function resolveFileArg(value, client) {
|
|
|
362
418
|
case 'passthrough':
|
|
363
419
|
// No local evidence beyond the parent directory: let the backend try
|
|
364
420
|
// it as an AI Drive path (its failure mode is a per-URL actionable
|
|
365
|
-
// 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));
|
|
366
424
|
return value;
|
|
367
425
|
}
|
|
368
426
|
}
|
|
@@ -586,6 +644,12 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
586
644
|
}
|
|
587
645
|
const props = tool.parameters?.properties || {};
|
|
588
646
|
const required = tool.parameters?.required || [];
|
|
647
|
+
// When --args-file is on the command line, required params may live in
|
|
648
|
+
// the file, which Commander cannot see — its parse-time required checks
|
|
649
|
+
// would reject the invocation before the file is ever read. Register
|
|
650
|
+
// everything optional in that case; the action handler re-validates the
|
|
651
|
+
// required set AFTER the file merge (#46011).
|
|
652
|
+
const argsFileInvoked = process.argv.some(a => a === '--args-file' || a.startsWith('--args-file='));
|
|
589
653
|
// If primary_arg is set, make it a positional argument
|
|
590
654
|
if (tool.cli.primary_arg && props[tool.cli.primary_arg]) {
|
|
591
655
|
const param = props[tool.cli.primary_arg];
|
|
@@ -599,7 +663,9 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
599
663
|
// usage say e.g. "missing required argument 'deck'" instead of 'value'
|
|
600
664
|
// (#43556: the anonymous form sent agents guessing).
|
|
601
665
|
const argName = tool.cli.primary_arg;
|
|
602
|
-
const bracket = isRequired && !isServiceAction
|
|
666
|
+
const bracket = isRequired && !isServiceAction && !argsFileInvoked
|
|
667
|
+
? `<${argName}>`
|
|
668
|
+
: `[${argName}]`;
|
|
603
669
|
cmd.argument(bracket, param.description || argName);
|
|
604
670
|
}
|
|
605
671
|
// Add remaining params as --options
|
|
@@ -614,7 +680,8 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
614
680
|
// requiredOption would block every invocation before the action handler
|
|
615
681
|
// (and its collector) ever runs.
|
|
616
682
|
const isRequired = tool.name === 'create_task' ||
|
|
617
|
-
(tool.name === 'skills_save' && name === 'files')
|
|
683
|
+
(tool.name === 'skills_save' && name === 'files') ||
|
|
684
|
+
argsFileInvoked
|
|
618
685
|
? false
|
|
619
686
|
: required.includes(name);
|
|
620
687
|
const paramType = Array.isArray(param.type) ? param.type[0] : param.type;
|
|
@@ -677,17 +744,34 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
677
744
|
cmd.option('--attach <values...>', 'Attach local files or URLs (alias for --attachments; repeatable)');
|
|
678
745
|
cmd.option('--attachment <values...>', 'Attach local files or URLs (alias for --attachments; repeatable)');
|
|
679
746
|
}
|
|
747
|
+
// Quoting-hazard-free input path (#46011): a shell-invoked agent passing
|
|
748
|
+
// rich text in double quotes gets `$`-tokens expanded before gsk sees
|
|
749
|
+
// argv ("$150k" → "50k"). A JSON file / stdin never crosses shell quoting.
|
|
750
|
+
cmd.option('--args-file <path>', 'Read arguments from a JSON object file ("-" = stdin). File keys ' +
|
|
751
|
+
'override flag values. Use for rich text (strings containing $, ' +
|
|
752
|
+
'quotes, newlines) instead of double-quoted flags. Server ' +
|
|
753
|
+
'parameters only — CLI-only options (e.g. --local_file) must ' +
|
|
754
|
+
'still be passed as flags.');
|
|
680
755
|
// Add -o/--output-file option for tools that produce downloadable files
|
|
681
756
|
const outputFileKeys = tool.cli.output_file_keys || [];
|
|
682
757
|
if (outputFileKeys.length > 0) {
|
|
683
758
|
cmd.option('-o, --output-file <path>', 'Download the generated file to a local path');
|
|
684
759
|
}
|
|
760
|
+
// task ask (task_continue) shares the async-first submit → --follow
|
|
761
|
+
// upgrades the stub into a live SSE follow, same as task create.
|
|
762
|
+
// (Named --follow, not --attach: tools with an attachments array param
|
|
763
|
+
// already own --attach as a file alias.)
|
|
764
|
+
if (tool.name === 'task_continue') {
|
|
765
|
+
cmd.option('--follow', 'After the async submit, follow the durable run live (SSE) and exit with the terminal result', false);
|
|
766
|
+
}
|
|
685
767
|
// For create_task, add --acp flag to enter ACP stdio bridge mode
|
|
686
768
|
if (tool.name === 'create_task') {
|
|
687
769
|
cmd.option('--acp', 'Start as ACP (Agent Client Protocol) stdio agent instead of one-shot execution', false);
|
|
770
|
+
cmd.option('--follow', 'After the async submit, follow the durable run live (SSE) and exit with the terminal result', false);
|
|
688
771
|
cmd.option('-o, --output-file <path>', 'Export the artifact (pptx/docx/xlsx) to a local file after task completes');
|
|
689
772
|
cmd.option('--session-id <id>', 'ACP session ID (used with get-project)');
|
|
690
|
-
cmd.option('--file <values...>', 'Import local
|
|
773
|
+
cmd.option('--file <values...>', 'Import local files (Design: structured context; other tasks: CSV/Excel file_urls)');
|
|
774
|
+
cmd.option('--image <values...>', 'Design only: upload ordered local visual references');
|
|
691
775
|
cmd.addHelpText('after', '\nVirtual subcommands:\n' +
|
|
692
776
|
' get-project Get Genspark project info for an ACP session (local file lookup, no API call)\n' +
|
|
693
777
|
' --session-id <id> ACP session ID (required)\n' +
|
|
@@ -695,8 +779,15 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
695
779
|
'Example:\n' +
|
|
696
780
|
' gsk task get-project --session-id <acp_session_id>\n');
|
|
697
781
|
}
|
|
782
|
+
if (tool.name === 'task_continue') {
|
|
783
|
+
cmd.option('--image <values...>', 'Design only: append ordered local visual references');
|
|
784
|
+
cmd.option('--file <values...>', 'Design only: append ordered local context files');
|
|
785
|
+
}
|
|
698
786
|
// For the aidrive tool, add client-side options used with --local_file upload
|
|
699
787
|
if (tool.name === 'aidrive') {
|
|
788
|
+
if (!Object.hasOwn(props, 'workspace')) {
|
|
789
|
+
cmd.option('--workspace <value>', 'AI Drive workspace: personal or public_brain');
|
|
790
|
+
}
|
|
700
791
|
cmd.option('--local_file <path>', 'Local file path to upload to AI Drive (supports files >5 MB; alternative to --file_content)');
|
|
701
792
|
cmd.option('--override', 'Overwrite an existing file at the destination path (used with --local_file)', false);
|
|
702
793
|
}
|
|
@@ -744,7 +835,10 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
744
835
|
: undefined;
|
|
745
836
|
if (tool.cli.primary_arg === 'action' &&
|
|
746
837
|
Array.isArray(actionEnum) &&
|
|
747
|
-
primaryArgValue === undefined
|
|
838
|
+
primaryArgValue === undefined &&
|
|
839
|
+
// The action may arrive via --args-file (read later in the
|
|
840
|
+
// handler) — don't short-circuit to the action listing then.
|
|
841
|
+
!opts.argsFile) {
|
|
748
842
|
logError(tool.description);
|
|
749
843
|
logError(`Available actions: ${actionEnum.join(', ')}`);
|
|
750
844
|
logError(`Usage: gsk ${cmd.name()} <action> [options] ` +
|
|
@@ -790,8 +884,10 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
790
884
|
await startAcpBridge(taskType, clientFactory().getOptions());
|
|
791
885
|
return;
|
|
792
886
|
}
|
|
793
|
-
// For create_task (non-ACP): validate required params manually
|
|
794
|
-
|
|
887
|
+
// For create_task (non-ACP): validate required params manually.
|
|
888
|
+
// Skipped when --args-file is present — required params may live in
|
|
889
|
+
// the file, and the post-merge revalidation below enforces them.
|
|
890
|
+
if (tool.name === 'create_task' && !opts.argsFile) {
|
|
795
891
|
for (const reqParam of required) {
|
|
796
892
|
if (reqParam === tool.cli.primary_arg)
|
|
797
893
|
continue;
|
|
@@ -826,7 +922,25 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
826
922
|
const outputFilePath = opts.outputFile;
|
|
827
923
|
delete opts.outputFile;
|
|
828
924
|
delete opts.acp; // ACP flag is client-side only
|
|
925
|
+
// -o exports an artifact of the FINISHED project — an async submit
|
|
926
|
+
// stub has nothing to export yet, so an export request implies the
|
|
927
|
+
// live follow.
|
|
928
|
+
const attachTask = !!opts.follow || !!outputFilePath;
|
|
929
|
+
delete opts.follow; // client-side only
|
|
829
930
|
delete opts.sessionId; // get-project flag is client-side only
|
|
931
|
+
const argsFilePath = opts.argsFile;
|
|
932
|
+
delete opts.argsFile; // client-side only; merged below
|
|
933
|
+
// Required checks were relaxed because --args-file appeared in argv;
|
|
934
|
+
// an explicitly EMPTY value (`--args-file=` / `--args-file ""`,
|
|
935
|
+
// parsed as '') would skip the merge AND the revalidation below — a
|
|
936
|
+
// validation bypass, not a degenerate no-op. Refuse it. undefined is
|
|
937
|
+
// deliberately allowed: argv detection can false-positive on a
|
|
938
|
+
// literal "--args-file" VALUE of another option, and the server-side
|
|
939
|
+
// required validation still covers that arm.
|
|
940
|
+
if (argsFilePath !== undefined && !argsFilePath) {
|
|
941
|
+
logError('--args-file requires a non-empty path (or "-" for stdin)');
|
|
942
|
+
process.exit(1);
|
|
943
|
+
}
|
|
830
944
|
const args = { ...opts };
|
|
831
945
|
// Set primary arg
|
|
832
946
|
if (tool.cli.primary_arg && primaryArgValue !== undefined) {
|
|
@@ -857,6 +971,54 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
857
971
|
delete args.attach;
|
|
858
972
|
delete args.attachment;
|
|
859
973
|
}
|
|
974
|
+
// Merge --args-file after every flag-derived write (positional,
|
|
975
|
+
// attachment aliases) so file keys genuinely win: the file is the
|
|
976
|
+
// deliberate rich payload, flags are the convenience layer (#46011).
|
|
977
|
+
// It sits before the numeric coercion below so file-provided
|
|
978
|
+
// numeric strings get the same schema-driven coercion flags do.
|
|
979
|
+
if (argsFilePath) {
|
|
980
|
+
let fileArgs;
|
|
981
|
+
try {
|
|
982
|
+
const { readArgsFile } = await import('./argsFile.js');
|
|
983
|
+
fileArgs = readArgsFile(argsFilePath);
|
|
984
|
+
}
|
|
985
|
+
catch (err) {
|
|
986
|
+
logError(err.message);
|
|
987
|
+
process.exit(1);
|
|
988
|
+
}
|
|
989
|
+
// The file carries SERVER parameters only. CLI-only options
|
|
990
|
+
// (--local_file, --file, --out, ...) are processed from flags by
|
|
991
|
+
// client-side blocks below that never see the file — honoring
|
|
992
|
+
// them here would silently do nothing, so warn and drop instead.
|
|
993
|
+
for (const key of Object.keys(fileArgs)) {
|
|
994
|
+
if (!(key in props)) {
|
|
995
|
+
warn(`--args-file: "${key}" is not a server parameter of ` +
|
|
996
|
+
`${tool.name} and was ignored — CLI-only options must be ` +
|
|
997
|
+
'passed as flags.');
|
|
998
|
+
delete fileArgs[key];
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
Object.assign(args, fileArgs);
|
|
1002
|
+
// Required params were registered optional because the file may
|
|
1003
|
+
// carry them (Commander validates before the file is readable) —
|
|
1004
|
+
// re-validate the merged set now. CLI-populated params are
|
|
1005
|
+
// exempt: skills_save's `files` is schema-required but filled by
|
|
1006
|
+
// the directory collector further down, which deliberately owns
|
|
1007
|
+
// the value (file-wins does not apply to collector-owned params).
|
|
1008
|
+
for (const reqParam of required) {
|
|
1009
|
+
if (tool.name === 'skills_save' && reqParam === 'files')
|
|
1010
|
+
continue;
|
|
1011
|
+
if (args[reqParam] === undefined || args[reqParam] === null) {
|
|
1012
|
+
// The primary arg is a positional — there is no --<name>
|
|
1013
|
+
// flag for it, so the recovery hint must not invent one.
|
|
1014
|
+
const hint = reqParam === tool.cli.primary_arg
|
|
1015
|
+
? `pass it as the positional argument or include it in --args-file`
|
|
1016
|
+
: `pass --${reqParam} or include it in --args-file`;
|
|
1017
|
+
logError(`Missing required parameter: ${reqParam} (${hint})`);
|
|
1018
|
+
process.exit(1);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
860
1022
|
// Coerce numeric strings to numbers based on schema
|
|
861
1023
|
for (const [name, param] of Object.entries(props)) {
|
|
862
1024
|
if (args[name] !== undefined) {
|
|
@@ -941,6 +1103,10 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
941
1103
|
info(`Warning: ${dir} carries provenance for ${collected.sidecarOwner} — ` +
|
|
942
1104
|
`saving will fork this skill into your own catalog.`);
|
|
943
1105
|
}
|
|
1106
|
+
// Collector-owned: `files` must be {path, content} read from the
|
|
1107
|
+
// local directory, so this assignment intentionally overrides any
|
|
1108
|
+
// --args-file value (the file-wins rule covers flag-shaped params
|
|
1109
|
+
// only, not CLI-populated ones).
|
|
944
1110
|
args.files = collected.files;
|
|
945
1111
|
}
|
|
946
1112
|
// aidrive upload ergonomics: `gsk aidrive upload <path>` is the natural
|
|
@@ -954,10 +1120,21 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
954
1120
|
// no usable source, fail with an actionable message that names
|
|
955
1121
|
// --local_file rather than the opaque backend error.
|
|
956
1122
|
if (tool.name === 'aidrive' &&
|
|
957
|
-
|
|
1123
|
+
// args.action, not the positional: the action may arrive via
|
|
1124
|
+
// --args-file (sb-git's clone-url gate uses the same pattern).
|
|
1125
|
+
args.action === 'upload' &&
|
|
958
1126
|
!opts.local_file &&
|
|
959
|
-
|
|
960
|
-
|
|
1127
|
+
// args, not opts: file_content is a server param and may have
|
|
1128
|
+
// arrived via --args-file (merged into args above).
|
|
1129
|
+
!args.file_content) {
|
|
1130
|
+
// Skip the first operand only when it really is the action: with
|
|
1131
|
+
// the action arriving via --args-file, Commander binds a bare
|
|
1132
|
+
// path to the [action] positional slot, so cmd.args[0] is itself
|
|
1133
|
+
// a path candidate.
|
|
1134
|
+
const operandCandidates = primaryArgValue !== undefined && primaryArgValue === args.action
|
|
1135
|
+
? cmd.args.slice(1)
|
|
1136
|
+
: cmd.args;
|
|
1137
|
+
const pathOperand = pickExistingUploadOperand(operandCandidates);
|
|
961
1138
|
if (pathOperand) {
|
|
962
1139
|
opts.local_file = pathOperand;
|
|
963
1140
|
}
|
|
@@ -973,10 +1150,11 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
973
1150
|
// Stream the file directly to AI Drive via the dedicated tool_cli
|
|
974
1151
|
// endpoint (POST /api/tool_cli/aidrive/upload) — no blob middleman.
|
|
975
1152
|
if (tool.name === 'aidrive' &&
|
|
976
|
-
|
|
1153
|
+
args.action === 'upload' &&
|
|
977
1154
|
opts.local_file) {
|
|
978
1155
|
const localFilePath = opts.local_file;
|
|
979
1156
|
const override = !!opts.override;
|
|
1157
|
+
const workspace = args.workspace || 'personal';
|
|
980
1158
|
if (!fs.existsSync(localFilePath)) {
|
|
981
1159
|
logError(`File not found: ${localFilePath}`);
|
|
982
1160
|
process.exit(1);
|
|
@@ -988,13 +1166,54 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
988
1166
|
const uploadPath = rawUploadPath.startsWith('/')
|
|
989
1167
|
? rawUploadPath
|
|
990
1168
|
: `/${rawUploadPath}`;
|
|
991
|
-
|
|
992
|
-
const
|
|
993
|
-
|
|
1169
|
+
const localStat = fs.statSync(localFilePath);
|
|
1170
|
+
const files = collectUploadFiles(localFilePath);
|
|
1171
|
+
if (localStat.isDirectory()) {
|
|
1172
|
+
if (files.length === 0) {
|
|
1173
|
+
logError(`Folder has no files to upload: ${localFilePath}`);
|
|
1174
|
+
process.exit(1);
|
|
1175
|
+
}
|
|
1176
|
+
info(`Uploading folder ${localFilePath} (${files.length} files)...`);
|
|
1177
|
+
const uploaded = [];
|
|
1178
|
+
for (const file of files) {
|
|
1179
|
+
const destination = pathModule.posix.join(uploadPath, file.relativePath);
|
|
1180
|
+
const result = await uploadToAiDrive(file.absolutePath, destination, override, workspace);
|
|
1181
|
+
if (result.status !== 'ok') {
|
|
1182
|
+
output(result);
|
|
1183
|
+
process.exit(1);
|
|
1184
|
+
}
|
|
1185
|
+
uploaded.push(destination);
|
|
1186
|
+
}
|
|
1187
|
+
output({
|
|
1188
|
+
status: 'ok',
|
|
1189
|
+
message: `Uploaded ${uploaded.length} files`,
|
|
1190
|
+
data: { workspace, root: uploadPath, files: uploaded },
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
else {
|
|
1194
|
+
// Stream directly to AI Drive — single request, no blob middleman
|
|
1195
|
+
const result = await uploadToAiDrive(localFilePath, uploadPath, override, workspace);
|
|
1196
|
+
output(result);
|
|
1197
|
+
}
|
|
994
1198
|
return;
|
|
995
1199
|
}
|
|
996
|
-
|
|
997
|
-
|
|
1200
|
+
const designAttachmentArgs = collectDesignAttachmentArgs(process.argv);
|
|
1201
|
+
const isDesignAttachmentCommand = designAttachmentArgs.length > 0 &&
|
|
1202
|
+
((tool.name === 'create_task' && args.task_type === 'design') ||
|
|
1203
|
+
tool.name === 'task_continue');
|
|
1204
|
+
if (isDesignAttachmentCommand) {
|
|
1205
|
+
const uploaded = await uploadDesignAttachments(designAttachmentArgs, client);
|
|
1206
|
+
const existing = Array.isArray(args.attachments)
|
|
1207
|
+
? args.attachments
|
|
1208
|
+
: [];
|
|
1209
|
+
args.attachments = [...existing, ...uploaded];
|
|
1210
|
+
delete args.image;
|
|
1211
|
+
delete args.file;
|
|
1212
|
+
}
|
|
1213
|
+
// Non-Design create_task --file keeps its CSV/Excel file_urls contract.
|
|
1214
|
+
if (tool.name === 'create_task' &&
|
|
1215
|
+
args.task_type !== 'design' &&
|
|
1216
|
+
opts.file) {
|
|
998
1217
|
const fileValues = Array.isArray(opts.file)
|
|
999
1218
|
? opts.file
|
|
1000
1219
|
: [opts.file];
|
|
@@ -1027,7 +1246,36 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
1027
1246
|
}
|
|
1028
1247
|
// Resolve local file paths to uploaded URLs
|
|
1029
1248
|
const resolvedArgs = await resolveLocalFiles(args, client);
|
|
1030
|
-
|
|
1249
|
+
// The server owns the async-first default: create_task /
|
|
1250
|
+
// task_continue return a submit stub ({status: "submitted",
|
|
1251
|
+
// run_id, stream_url}) unless `--wait true` was passed. --attach
|
|
1252
|
+
// upgrades the stub into a live SSE follow that exits with the
|
|
1253
|
+
// run's terminal result — the work still executes server-side.
|
|
1254
|
+
let result = await client.executeTool(tool.name, resolvedArgs);
|
|
1255
|
+
const stubData = result.data;
|
|
1256
|
+
if (attachTask &&
|
|
1257
|
+
result.status === 'ok' &&
|
|
1258
|
+
stubData &&
|
|
1259
|
+
typeof stubData.stream_url === 'string' &&
|
|
1260
|
+
(stubData.status === 'submitted' ||
|
|
1261
|
+
stubData.status === 'duplicate_submit_converged')) {
|
|
1262
|
+
const meta = await client.attachGskTaskRun(stubData.stream_url);
|
|
1263
|
+
const runStatus = String(meta.status || '');
|
|
1264
|
+
const summary = meta.result_summary && typeof meta.result_summary === 'object'
|
|
1265
|
+
? meta.result_summary
|
|
1266
|
+
: {};
|
|
1267
|
+
result = {
|
|
1268
|
+
status: runStatus === 'succeeded' ? 'ok' : 'error',
|
|
1269
|
+
message: runStatus === 'succeeded'
|
|
1270
|
+
? 'success'
|
|
1271
|
+
: `task ended with status ${runStatus || 'unknown'}`,
|
|
1272
|
+
data: {
|
|
1273
|
+
...summary,
|
|
1274
|
+
run_id: stubData.run_id,
|
|
1275
|
+
run_status: runStatus,
|
|
1276
|
+
},
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1031
1279
|
const isSbGitCloneUrl = tool.name === 'sb-git' &&
|
|
1032
1280
|
resolvedArgs.action === 'clone-url';
|
|
1033
1281
|
if (isSbGitCloneUrl &&
|
|
@@ -1179,7 +1427,10 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
1179
1427
|
typeof result.data === 'object') {
|
|
1180
1428
|
const data = result.data;
|
|
1181
1429
|
const projectId = data.project_id;
|
|
1182
|
-
|
|
1430
|
+
// args.type is the value actually sent to the API: the positional
|
|
1431
|
+
// is copied into args and --args-file may override it (file wins),
|
|
1432
|
+
// so the export must key on the merged view, never the positional.
|
|
1433
|
+
const taskType = args.type;
|
|
1183
1434
|
if (projectId && taskType) {
|
|
1184
1435
|
info(`Exporting ${taskType} artifact...`);
|
|
1185
1436
|
try {
|
|
@@ -1929,19 +2180,23 @@ _groupCommands.set('phone-call', phoneCallCmd);
|
|
|
1929
2180
|
// gk_tool_cli_conversation) and attach as dynamic subcommands via the
|
|
1930
2181
|
// seeded _groupCommands entry; this static parent only gives the group a
|
|
1931
2182
|
// real description instead of the generated "Commands for chat".
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
2183
|
+
// The task command tree. Verbs are server-registered (create / status /
|
|
2184
|
+
// info / ask / answer / messages / wait / stop / delete / rename attach as
|
|
2185
|
+
// dynamic subcommands via the seeded group); this static parent gives the
|
|
2186
|
+
// group a description, and hosts the one static verb below.
|
|
2187
|
+
const taskCmd = program
|
|
2188
|
+
.command('task')
|
|
2189
|
+
.description('Agent tasks and conversations: create one (submits and returns in ' +
|
|
2190
|
+
'seconds), poll status, read outputs, send follow-ups, answer ' +
|
|
2191
|
+
'agent questions, stop, delete, rename (see subcommands).');
|
|
2192
|
+
_groupCommands.set('task', taskCmd);
|
|
2193
|
+
// `task events` is static (not server-registered): it needs real-time
|
|
1939
2194
|
// line-by-line output the generic dynamic-command handler cannot provide
|
|
1940
2195
|
// (that path buffers intermediate NDJSON messages and only prints the
|
|
1941
2196
|
// final result).
|
|
1942
|
-
|
|
2197
|
+
taskCmd
|
|
1943
2198
|
.command('events <project_id>')
|
|
1944
|
-
.description("Attach to the
|
|
2199
|
+
.description("Attach to the task's in-flight run and stream its events " +
|
|
1945
2200
|
'live (one JSON line per event on stdout): the first line is a ' +
|
|
1946
2201
|
'project_data snapshot event, buffered events since run start ' +
|
|
1947
2202
|
'replay next, then live events follow until the run ends. Every ' +
|
|
@@ -1991,10 +2246,78 @@ chatCmd
|
|
|
1991
2246
|
process.exit(1);
|
|
1992
2247
|
}
|
|
1993
2248
|
});
|
|
2249
|
+
// `task export` is static: it composes two calls (task_status to resolve
|
|
2250
|
+
// the task type, then /export_artifact) plus an optional local download —
|
|
2251
|
+
// the generic dynamic handler maps one verb to one server tool.
|
|
2252
|
+
taskCmd
|
|
2253
|
+
.command('export <project_id>')
|
|
2254
|
+
.description("Convert and download a task's deliverable (docs → docx/pdf, " +
|
|
2255
|
+
'slides → pptx/pdf, sheets → xlsx). Prints the download URL; with ' +
|
|
2256
|
+
'-o <path> also saves the file locally. The task type is resolved ' +
|
|
2257
|
+
'automatically from the project; `gsk task artifacts <project_id>` ' +
|
|
2258
|
+
'lists what a task produced and which formats it can export.')
|
|
2259
|
+
.option('--format <format>', 'export format: auto | docx | pdf | pptx | xlsx (auto picks the ' +
|
|
2260
|
+
"task type's natural format)", 'auto')
|
|
2261
|
+
.option('-o, --output-file <path>', 'save the exported file to this path')
|
|
2262
|
+
.action(async (projectId, opts) => {
|
|
2263
|
+
try {
|
|
2264
|
+
const client = createClient();
|
|
2265
|
+
const statusResult = await client.executeTool('task_status', {
|
|
2266
|
+
id: projectId,
|
|
2267
|
+
});
|
|
2268
|
+
const statusData = statusResult.data;
|
|
2269
|
+
const taskType = statusResult.status === 'ok' && statusData
|
|
2270
|
+
? statusData.task_type
|
|
2271
|
+
: undefined;
|
|
2272
|
+
if (!taskType) {
|
|
2273
|
+
output(statusResult.status === 'ok'
|
|
2274
|
+
? {
|
|
2275
|
+
status: 'error',
|
|
2276
|
+
message: `Cannot resolve task type for ${projectId}`,
|
|
2277
|
+
data: null,
|
|
2278
|
+
}
|
|
2279
|
+
: statusResult);
|
|
2280
|
+
process.exit(1);
|
|
2281
|
+
}
|
|
2282
|
+
const exportResult = await client.exportArtifact(projectId, taskType, opts.format || 'auto');
|
|
2283
|
+
const expData = exportResult.data;
|
|
2284
|
+
if (exportResult.status === 'ok' && expData && opts.outputFile) {
|
|
2285
|
+
const downloadUrl = expData.download_url;
|
|
2286
|
+
if (downloadUrl) {
|
|
2287
|
+
expData.local_path = await downloadToFile(downloadUrl, opts.outputFile, client);
|
|
2288
|
+
}
|
|
2289
|
+
else {
|
|
2290
|
+
// The converter can report ok with an empty URL (e.g. the
|
|
2291
|
+
// share-link build failed server-side) — surface it instead
|
|
2292
|
+
// of printing ok with no file on disk.
|
|
2293
|
+
output({
|
|
2294
|
+
status: 'error',
|
|
2295
|
+
message: 'Export produced no download_url — nothing was saved ' +
|
|
2296
|
+
`to ${opts.outputFile}`,
|
|
2297
|
+
data: expData,
|
|
2298
|
+
});
|
|
2299
|
+
process.exit(1);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
output(exportResult);
|
|
2303
|
+
if (exportResult.status === 'error') {
|
|
2304
|
+
process.exit(1);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
catch (err) {
|
|
2308
|
+
logError(err.message);
|
|
2309
|
+
process.exit(1);
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
1994
2312
|
// ============================================
|
|
1995
2313
|
// Dynamic Tool Registration & Startup
|
|
1996
2314
|
// ============================================
|
|
1997
2315
|
async function main() {
|
|
2316
|
+
// Pre-tree `gsk task <type>` compat (see shimLegacyTaskInvocation).
|
|
2317
|
+
process.argv = [
|
|
2318
|
+
...process.argv.slice(0, 2),
|
|
2319
|
+
...shimLegacyTaskInvocation(process.argv.slice(2)),
|
|
2320
|
+
];
|
|
1998
2321
|
// `gsk mesh ...` is a native-binary passthrough — intercept it before the
|
|
1999
2322
|
// tool-fetch path and npm self-update gating (it needs neither). Handling it
|
|
2000
2323
|
// outside commander keeps the rest of the CLI's option parsing intact (see
|
|
@@ -2046,7 +2369,20 @@ async function main() {
|
|
|
2046
2369
|
debug(`Fetched and cached ${tools.length} tools from server`);
|
|
2047
2370
|
}
|
|
2048
2371
|
catch (err) {
|
|
2049
|
-
|
|
2372
|
+
if (isAuthError(err)) {
|
|
2373
|
+
// A definitive auth verdict must be loud: swallowing it registers
|
|
2374
|
+
// zero dynamic commands and the user sees "unknown command" with
|
|
2375
|
+
// no hint that the token is the cause (#45982). Warn-and-continue
|
|
2376
|
+
// (not fail-fast) so built-in commands — `gsk login` above all —
|
|
2377
|
+
// still work with an expired token.
|
|
2378
|
+
warn(`API token rejected (HTTP ${err.httpStatus}) — ` +
|
|
2379
|
+
'invalid or expired. Server-provided commands were NOT loaded; ' +
|
|
2380
|
+
'if the command you need is missing, run `gsk login` to ' +
|
|
2381
|
+
're-authenticate.');
|
|
2382
|
+
}
|
|
2383
|
+
else {
|
|
2384
|
+
debug(`Failed to fetch tools: ${err.message}`);
|
|
2385
|
+
}
|
|
2050
2386
|
// Continue without dynamic commands - built-in commands still work
|
|
2051
2387
|
}
|
|
2052
2388
|
}
|