@genspark/cli 1.4.2 → 1.5.1
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 +23 -1
- 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 +10 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +19 -1
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +11 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +100 -13
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +290 -41
- package/dist/index.js.map +1 -1
- package/dist/localFiles.d.ts +62 -0
- package/dist/localFiles.d.ts.map +1 -0
- package/dist/localFiles.js +130 -0
- package/dist/localFiles.js.map +1 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +13 -0
- package/dist/logger.js.map +1 -1
- package/dist/mesh/checkupdate.js +3 -3
- package/dist/mesh/checkupdate.js.map +1 -1
- package/dist/runtime.d.ts +44 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +62 -0
- package/dist/runtime.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/updater.d.ts +1 -0
- package/dist/updater.d.ts.map +1 -1
- package/dist/updater.js +10 -0
- package/dist/updater.js.map +1 -1
- package/docs/skills.md +24 -23
- package/package.json +1 -1
- package/skills/gsk-analyze-media/SKILL.md +2 -1
- package/skills/gsk-audio-generation/SKILL.md +1 -1
- package/skills/gsk-connector/SKILL.md +52 -0
- package/skills/gsk-create-task/SKILL.md +34 -1
- package/skills/gsk-github/SKILL.md +2 -0
- package/skills/gsk-gmail/SKILL.md +2 -0
- package/skills/gsk-google-calendar/SKILL.md +4 -1
- package/skills/gsk-google-chat/SKILL.md +4 -1
- package/skills/gsk-google-contacts/SKILL.md +2 -0
- package/skills/gsk-google-docs/SKILL.md +2 -0
- package/skills/gsk-google-drive/SKILL.md +3 -0
- package/skills/gsk-google-sheets/SKILL.md +4 -2
- package/skills/gsk-google-slides/SKILL.md +47 -0
- package/skills/gsk-hub/SKILL.md +15 -5
- package/skills/gsk-image-generation/SKILL.md +2 -2
- package/skills/gsk-microsoft-teams/SKILL.md +16 -11
- package/skills/gsk-notion/SKILL.md +2 -0
- package/skills/gsk-onedrive/SKILL.md +3 -0
- package/skills/gsk-outlook-calendar/SKILL.md +3 -1
- package/skills/gsk-outlook-contacts/SKILL.md +2 -0
- package/skills/gsk-outlook-email/SKILL.md +3 -10
- package/skills/gsk-salesforce/SKILL.md +2 -0
- package/skills/gsk-shared/SKILL.md +12 -1
- package/skills/gsk-sharepoint/SKILL.md +3 -0
- package/skills/gsk-sharepoint-upload/SKILL.md +2 -0
- package/skills/gsk-slack/SKILL.md +12 -7
- package/skills/gsk-task-continue/SKILL.md +4 -3
- package/skills/gsk-video-generation/SKILL.md +8 -7
- package/skills/gsk-youtube/SKILL.md +5 -4
package/dist/index.js
CHANGED
|
@@ -26,13 +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 {
|
|
31
|
-
import {
|
|
29
|
+
import { ApiClient, isAuthError, unauthenticatedRequest, } from './client.js';
|
|
30
|
+
import { classifyFileArg, awaitLocalFileReady, decideAfterWait, localFileNotReadyError, } from './localFiles.js';
|
|
31
|
+
import { setDebugEnabled, setOutputFormat, debug, info, warn, error as logError, output, } from './logger.js';
|
|
32
|
+
import { loadConfigFile, getConfigPath, loadToolsCache, normalizeBaseUrl, saveToolsCache, setConfigPathOverride, updateConfigFile, } from './config.js';
|
|
32
33
|
import { checkForUpdates } from './updater.js';
|
|
34
|
+
import { getSandboxRuntime } from './runtime.js';
|
|
33
35
|
import { registerDesignCommand } from './commands/design.js';
|
|
34
36
|
import { pickExistingFileOperand } from './commands/aidrive-upload.js';
|
|
35
|
-
import { parseLastSeenIndex, eventStdoutLine
|
|
37
|
+
import { parseLastSeenIndex, eventStdoutLine } from './commands/chat-events.js';
|
|
36
38
|
import { mediaItemsKeyFor, mediaSummaryLines } from './mediaSummary.js';
|
|
37
39
|
import { registerMeshCommand, getMeshInvocation, runMesh, } from './commands/mesh.js';
|
|
38
40
|
import { executeCloneUrl, normalizeCloneUrlForGit, } from './commands/sb-git-clone.js';
|
|
@@ -104,9 +106,13 @@ function getGlobalOptions() {
|
|
|
104
106
|
// API key priority: CLI > env > config file
|
|
105
107
|
const apiKey = opts.apiKey || process.env.GSK_API_KEY || fileConfig.api_key;
|
|
106
108
|
// Base URL priority: CLI > env > config file > default
|
|
107
|
-
const
|
|
109
|
+
const explicitBaseUrl = program.getOptionValueSource('baseUrl') === 'cli'
|
|
108
110
|
? opts.baseUrl
|
|
109
|
-
:
|
|
111
|
+
: undefined;
|
|
112
|
+
const baseUrl = normalizeBaseUrl(explicitBaseUrl ||
|
|
113
|
+
process.env.GSK_BASE_URL ||
|
|
114
|
+
fileConfig.base_url ||
|
|
115
|
+
DEFAULT_BASE_URL);
|
|
110
116
|
// Timeout priority: CLI > config file > default
|
|
111
117
|
const timeout = opts.timeout !== String(DEFAULT_TIMEOUT)
|
|
112
118
|
? parseInt(opts.timeout, 10)
|
|
@@ -151,6 +157,7 @@ const CONTENT_TYPE_MAP = {
|
|
|
151
157
|
'.png': 'image/png',
|
|
152
158
|
'.gif': 'image/gif',
|
|
153
159
|
'.webp': 'image/webp',
|
|
160
|
+
'.svg': 'image/svg+xml',
|
|
154
161
|
'.pdf': 'application/pdf',
|
|
155
162
|
'.txt': 'text/plain',
|
|
156
163
|
'.json': 'application/json',
|
|
@@ -158,6 +165,20 @@ const CONTENT_TYPE_MAP = {
|
|
|
158
165
|
'.css': 'text/css',
|
|
159
166
|
'.js': 'application/javascript',
|
|
160
167
|
'.ts': 'application/typescript',
|
|
168
|
+
'.jsx': 'text/javascript',
|
|
169
|
+
'.tsx': 'text/typescript',
|
|
170
|
+
'.vue': 'text/html',
|
|
171
|
+
'.py': 'text/x-python',
|
|
172
|
+
'.java': 'text/x-java-source',
|
|
173
|
+
'.go': 'text/x-go',
|
|
174
|
+
'.rs': 'text/x-rust',
|
|
175
|
+
'.c': 'text/x-c',
|
|
176
|
+
'.cc': 'text/x-c++',
|
|
177
|
+
'.cpp': 'text/x-c++',
|
|
178
|
+
'.h': 'text/x-c',
|
|
179
|
+
'.hpp': 'text/x-c++',
|
|
180
|
+
'.yaml': 'application/x-yaml',
|
|
181
|
+
'.yml': 'application/x-yaml',
|
|
161
182
|
'.md': 'text/markdown',
|
|
162
183
|
'.xml': 'application/xml',
|
|
163
184
|
'.csv': 'text/csv',
|
|
@@ -167,6 +188,46 @@ const CONTENT_TYPE_MAP = {
|
|
|
167
188
|
'.wav': 'audio/wav',
|
|
168
189
|
'.webm': 'video/webm',
|
|
169
190
|
};
|
|
191
|
+
/** Preserve interleaved --image/--file order; Commander groups options by key. */
|
|
192
|
+
export function collectDesignAttachmentArgs(argv) {
|
|
193
|
+
const result = [];
|
|
194
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
195
|
+
const token = argv[index];
|
|
196
|
+
const equalsMatch = token.match(/^--(image|file)=(.+)$/);
|
|
197
|
+
if (equalsMatch) {
|
|
198
|
+
result.push({
|
|
199
|
+
path: equalsMatch[2],
|
|
200
|
+
role: equalsMatch[1] === 'image' ? 'visual_reference' : 'context',
|
|
201
|
+
});
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (token !== '--image' && token !== '--file')
|
|
205
|
+
continue;
|
|
206
|
+
const role = token === '--image' ? 'visual_reference' : 'context';
|
|
207
|
+
while (index + 1 < argv.length && !argv[index + 1].startsWith('-')) {
|
|
208
|
+
result.push({ path: argv[index + 1], role });
|
|
209
|
+
index += 1;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
async function uploadDesignAttachments(values, client) {
|
|
215
|
+
const attachments = [];
|
|
216
|
+
// Upload the complete batch before executeTool: any failure aborts project
|
|
217
|
+
// creation/continuation rather than leaving a half-bound Design project.
|
|
218
|
+
for (const value of values) {
|
|
219
|
+
const localPath = expandHome(value.path);
|
|
220
|
+
if (!isLocalFilePath(localPath)) {
|
|
221
|
+
throw new Error(`File not found: ${value.path}`);
|
|
222
|
+
}
|
|
223
|
+
const name = pathModule.basename(localPath);
|
|
224
|
+
const ext = pathModule.extname(localPath).toLowerCase();
|
|
225
|
+
const mimeType = CONTENT_TYPE_MAP[ext] || 'application/octet-stream';
|
|
226
|
+
const url = await uploadLocalFile(localPath, client);
|
|
227
|
+
attachments.push({ url, name, mime_type: mimeType, role: value.role });
|
|
228
|
+
}
|
|
229
|
+
return attachments;
|
|
230
|
+
}
|
|
170
231
|
/**
|
|
171
232
|
* Stream a local file to a URL using Node.js http/https.request +
|
|
172
233
|
* fs.createReadStream so the file is never fully buffered in memory.
|
|
@@ -338,9 +399,27 @@ function isLocalFilePath(value) {
|
|
|
338
399
|
fs.existsSync(value));
|
|
339
400
|
}
|
|
340
401
|
async function resolveFileArg(value, client) {
|
|
341
|
-
|
|
402
|
+
// Classify the RAW value: a leading '~' is positive local evidence and must
|
|
403
|
+
// hit classifyFileArg's relative-marker branch before any expansion.
|
|
404
|
+
const kind = classifyFileArg(value);
|
|
405
|
+
if (kind === 'remote' || kind === 'aidrive')
|
|
342
406
|
return value;
|
|
343
|
-
|
|
407
|
+
// fs never expands '~' — expand only for the filesystem operations below.
|
|
408
|
+
const localPath = expandHome(value);
|
|
409
|
+
// Tolerate the same-batch create-then-read race (a parallel sibling tool
|
|
410
|
+
// call may still be writing the file) before deciding what the arg is.
|
|
411
|
+
const readiness = await awaitLocalFileReady(localPath);
|
|
412
|
+
switch (decideAfterWait(kind, readiness)) {
|
|
413
|
+
case 'upload':
|
|
414
|
+
return await uploadLocalFile(localPath, client);
|
|
415
|
+
case 'error':
|
|
416
|
+
throw new Error(localFileNotReadyError(localPath, readiness));
|
|
417
|
+
case 'passthrough':
|
|
418
|
+
// No local evidence beyond the parent directory: let the backend try
|
|
419
|
+
// it as an AI Drive path (its failure mode is a per-URL actionable
|
|
420
|
+
// error, not a generic one).
|
|
421
|
+
return value;
|
|
422
|
+
}
|
|
344
423
|
}
|
|
345
424
|
/**
|
|
346
425
|
* Inline-base64 transport limit for email `attachments`. Anything larger
|
|
@@ -547,7 +626,9 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
547
626
|
const prefix = groupName + '_';
|
|
548
627
|
cmdName =
|
|
549
628
|
tool.cli.subcommand ||
|
|
550
|
-
(tool.name.startsWith(prefix)
|
|
629
|
+
(tool.name.startsWith(prefix)
|
|
630
|
+
? tool.name.slice(prefix.length)
|
|
631
|
+
: tool.name);
|
|
551
632
|
}
|
|
552
633
|
const cmd = targetProgram
|
|
553
634
|
.command(cmdName, cmdOpts?.hidden ? { hidden: true } : {})
|
|
@@ -560,6 +641,12 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
560
641
|
}
|
|
561
642
|
const props = tool.parameters?.properties || {};
|
|
562
643
|
const required = tool.parameters?.required || [];
|
|
644
|
+
// When --args-file is on the command line, required params may live in
|
|
645
|
+
// the file, which Commander cannot see — its parse-time required checks
|
|
646
|
+
// would reject the invocation before the file is ever read. Register
|
|
647
|
+
// everything optional in that case; the action handler re-validates the
|
|
648
|
+
// required set AFTER the file merge (#46011).
|
|
649
|
+
const argsFileInvoked = process.argv.some(a => a === '--args-file' || a.startsWith('--args-file='));
|
|
563
650
|
// If primary_arg is set, make it a positional argument
|
|
564
651
|
if (tool.cli.primary_arg && props[tool.cli.primary_arg]) {
|
|
565
652
|
const param = props[tool.cli.primary_arg];
|
|
@@ -573,7 +660,9 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
573
660
|
// usage say e.g. "missing required argument 'deck'" instead of 'value'
|
|
574
661
|
// (#43556: the anonymous form sent agents guessing).
|
|
575
662
|
const argName = tool.cli.primary_arg;
|
|
576
|
-
const bracket = isRequired && !isServiceAction
|
|
663
|
+
const bracket = isRequired && !isServiceAction && !argsFileInvoked
|
|
664
|
+
? `<${argName}>`
|
|
665
|
+
: `[${argName}]`;
|
|
577
666
|
cmd.argument(bracket, param.description || argName);
|
|
578
667
|
}
|
|
579
668
|
// Add remaining params as --options
|
|
@@ -588,7 +677,8 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
588
677
|
// requiredOption would block every invocation before the action handler
|
|
589
678
|
// (and its collector) ever runs.
|
|
590
679
|
const isRequired = tool.name === 'create_task' ||
|
|
591
|
-
(tool.name === 'skills_save' && name === 'files')
|
|
680
|
+
(tool.name === 'skills_save' && name === 'files') ||
|
|
681
|
+
argsFileInvoked
|
|
592
682
|
? false
|
|
593
683
|
: required.includes(name);
|
|
594
684
|
const paramType = Array.isArray(param.type) ? param.type[0] : param.type;
|
|
@@ -651,6 +741,14 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
651
741
|
cmd.option('--attach <values...>', 'Attach local files or URLs (alias for --attachments; repeatable)');
|
|
652
742
|
cmd.option('--attachment <values...>', 'Attach local files or URLs (alias for --attachments; repeatable)');
|
|
653
743
|
}
|
|
744
|
+
// Quoting-hazard-free input path (#46011): a shell-invoked agent passing
|
|
745
|
+
// rich text in double quotes gets `$`-tokens expanded before gsk sees
|
|
746
|
+
// argv ("$150k" → "50k"). A JSON file / stdin never crosses shell quoting.
|
|
747
|
+
cmd.option('--args-file <path>', 'Read arguments from a JSON object file ("-" = stdin). File keys ' +
|
|
748
|
+
'override flag values. Use for rich text (strings containing $, ' +
|
|
749
|
+
'quotes, newlines) instead of double-quoted flags. Server ' +
|
|
750
|
+
'parameters only — CLI-only options (e.g. --local_file) must ' +
|
|
751
|
+
'still be passed as flags.');
|
|
654
752
|
// Add -o/--output-file option for tools that produce downloadable files
|
|
655
753
|
const outputFileKeys = tool.cli.output_file_keys || [];
|
|
656
754
|
if (outputFileKeys.length > 0) {
|
|
@@ -661,7 +759,8 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
661
759
|
cmd.option('--acp', 'Start as ACP (Agent Client Protocol) stdio agent instead of one-shot execution', false);
|
|
662
760
|
cmd.option('-o, --output-file <path>', 'Export the artifact (pptx/docx/xlsx) to a local file after task completes');
|
|
663
761
|
cmd.option('--session-id <id>', 'ACP session ID (used with get-project)');
|
|
664
|
-
cmd.option('--file <values...>', 'Import local
|
|
762
|
+
cmd.option('--file <values...>', 'Import local files (Design: structured context; other tasks: CSV/Excel file_urls)');
|
|
763
|
+
cmd.option('--image <values...>', 'Design only: upload ordered local visual references');
|
|
665
764
|
cmd.addHelpText('after', '\nVirtual subcommands:\n' +
|
|
666
765
|
' get-project Get Genspark project info for an ACP session (local file lookup, no API call)\n' +
|
|
667
766
|
' --session-id <id> ACP session ID (required)\n' +
|
|
@@ -669,6 +768,10 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
669
768
|
'Example:\n' +
|
|
670
769
|
' gsk task get-project --session-id <acp_session_id>\n');
|
|
671
770
|
}
|
|
771
|
+
if (tool.name === 'task_continue') {
|
|
772
|
+
cmd.option('--image <values...>', 'Design only: append ordered local visual references');
|
|
773
|
+
cmd.option('--file <values...>', 'Design only: append ordered local context files');
|
|
774
|
+
}
|
|
672
775
|
// For the aidrive tool, add client-side options used with --local_file upload
|
|
673
776
|
if (tool.name === 'aidrive') {
|
|
674
777
|
cmd.option('--local_file <path>', 'Local file path to upload to AI Drive (supports files >5 MB; alternative to --file_content)');
|
|
@@ -718,7 +821,10 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
718
821
|
: undefined;
|
|
719
822
|
if (tool.cli.primary_arg === 'action' &&
|
|
720
823
|
Array.isArray(actionEnum) &&
|
|
721
|
-
primaryArgValue === undefined
|
|
824
|
+
primaryArgValue === undefined &&
|
|
825
|
+
// The action may arrive via --args-file (read later in the
|
|
826
|
+
// handler) — don't short-circuit to the action listing then.
|
|
827
|
+
!opts.argsFile) {
|
|
722
828
|
logError(tool.description);
|
|
723
829
|
logError(`Available actions: ${actionEnum.join(', ')}`);
|
|
724
830
|
logError(`Usage: gsk ${cmd.name()} <action> [options] ` +
|
|
@@ -764,8 +870,10 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
764
870
|
await startAcpBridge(taskType, clientFactory().getOptions());
|
|
765
871
|
return;
|
|
766
872
|
}
|
|
767
|
-
// For create_task (non-ACP): validate required params manually
|
|
768
|
-
|
|
873
|
+
// For create_task (non-ACP): validate required params manually.
|
|
874
|
+
// Skipped when --args-file is present — required params may live in
|
|
875
|
+
// the file, and the post-merge revalidation below enforces them.
|
|
876
|
+
if (tool.name === 'create_task' && !opts.argsFile) {
|
|
769
877
|
for (const reqParam of required) {
|
|
770
878
|
if (reqParam === tool.cli.primary_arg)
|
|
771
879
|
continue;
|
|
@@ -801,6 +909,19 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
801
909
|
delete opts.outputFile;
|
|
802
910
|
delete opts.acp; // ACP flag is client-side only
|
|
803
911
|
delete opts.sessionId; // get-project flag is client-side only
|
|
912
|
+
const argsFilePath = opts.argsFile;
|
|
913
|
+
delete opts.argsFile; // client-side only; merged below
|
|
914
|
+
// Required checks were relaxed because --args-file appeared in argv;
|
|
915
|
+
// an explicitly EMPTY value (`--args-file=` / `--args-file ""`,
|
|
916
|
+
// parsed as '') would skip the merge AND the revalidation below — a
|
|
917
|
+
// validation bypass, not a degenerate no-op. Refuse it. undefined is
|
|
918
|
+
// deliberately allowed: argv detection can false-positive on a
|
|
919
|
+
// literal "--args-file" VALUE of another option, and the server-side
|
|
920
|
+
// required validation still covers that arm.
|
|
921
|
+
if (argsFilePath !== undefined && !argsFilePath) {
|
|
922
|
+
logError('--args-file requires a non-empty path (or "-" for stdin)');
|
|
923
|
+
process.exit(1);
|
|
924
|
+
}
|
|
804
925
|
const args = { ...opts };
|
|
805
926
|
// Set primary arg
|
|
806
927
|
if (tool.cli.primary_arg && primaryArgValue !== undefined) {
|
|
@@ -831,6 +952,54 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
831
952
|
delete args.attach;
|
|
832
953
|
delete args.attachment;
|
|
833
954
|
}
|
|
955
|
+
// Merge --args-file after every flag-derived write (positional,
|
|
956
|
+
// attachment aliases) so file keys genuinely win: the file is the
|
|
957
|
+
// deliberate rich payload, flags are the convenience layer (#46011).
|
|
958
|
+
// It sits before the numeric coercion below so file-provided
|
|
959
|
+
// numeric strings get the same schema-driven coercion flags do.
|
|
960
|
+
if (argsFilePath) {
|
|
961
|
+
let fileArgs;
|
|
962
|
+
try {
|
|
963
|
+
const { readArgsFile } = await import('./argsFile.js');
|
|
964
|
+
fileArgs = readArgsFile(argsFilePath);
|
|
965
|
+
}
|
|
966
|
+
catch (err) {
|
|
967
|
+
logError(err.message);
|
|
968
|
+
process.exit(1);
|
|
969
|
+
}
|
|
970
|
+
// The file carries SERVER parameters only. CLI-only options
|
|
971
|
+
// (--local_file, --file, --out, ...) are processed from flags by
|
|
972
|
+
// client-side blocks below that never see the file — honoring
|
|
973
|
+
// them here would silently do nothing, so warn and drop instead.
|
|
974
|
+
for (const key of Object.keys(fileArgs)) {
|
|
975
|
+
if (!(key in props)) {
|
|
976
|
+
warn(`--args-file: "${key}" is not a server parameter of ` +
|
|
977
|
+
`${tool.name} and was ignored — CLI-only options must be ` +
|
|
978
|
+
'passed as flags.');
|
|
979
|
+
delete fileArgs[key];
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
Object.assign(args, fileArgs);
|
|
983
|
+
// Required params were registered optional because the file may
|
|
984
|
+
// carry them (Commander validates before the file is readable) —
|
|
985
|
+
// re-validate the merged set now. CLI-populated params are
|
|
986
|
+
// exempt: skills_save's `files` is schema-required but filled by
|
|
987
|
+
// the directory collector further down, which deliberately owns
|
|
988
|
+
// the value (file-wins does not apply to collector-owned params).
|
|
989
|
+
for (const reqParam of required) {
|
|
990
|
+
if (tool.name === 'skills_save' && reqParam === 'files')
|
|
991
|
+
continue;
|
|
992
|
+
if (args[reqParam] === undefined || args[reqParam] === null) {
|
|
993
|
+
// The primary arg is a positional — there is no --<name>
|
|
994
|
+
// flag for it, so the recovery hint must not invent one.
|
|
995
|
+
const hint = reqParam === tool.cli.primary_arg
|
|
996
|
+
? `pass it as the positional argument or include it in --args-file`
|
|
997
|
+
: `pass --${reqParam} or include it in --args-file`;
|
|
998
|
+
logError(`Missing required parameter: ${reqParam} (${hint})`);
|
|
999
|
+
process.exit(1);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
834
1003
|
// Coerce numeric strings to numbers based on schema
|
|
835
1004
|
for (const [name, param] of Object.entries(props)) {
|
|
836
1005
|
if (args[name] !== undefined) {
|
|
@@ -915,6 +1084,10 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
915
1084
|
info(`Warning: ${dir} carries provenance for ${collected.sidecarOwner} — ` +
|
|
916
1085
|
`saving will fork this skill into your own catalog.`);
|
|
917
1086
|
}
|
|
1087
|
+
// Collector-owned: `files` must be {path, content} read from the
|
|
1088
|
+
// local directory, so this assignment intentionally overrides any
|
|
1089
|
+
// --args-file value (the file-wins rule covers flag-shaped params
|
|
1090
|
+
// only, not CLI-populated ones).
|
|
918
1091
|
args.files = collected.files;
|
|
919
1092
|
}
|
|
920
1093
|
// aidrive upload ergonomics: `gsk aidrive upload <path>` is the natural
|
|
@@ -928,10 +1101,21 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
928
1101
|
// no usable source, fail with an actionable message that names
|
|
929
1102
|
// --local_file rather than the opaque backend error.
|
|
930
1103
|
if (tool.name === 'aidrive' &&
|
|
931
|
-
|
|
1104
|
+
// args.action, not the positional: the action may arrive via
|
|
1105
|
+
// --args-file (sb-git's clone-url gate uses the same pattern).
|
|
1106
|
+
args.action === 'upload' &&
|
|
932
1107
|
!opts.local_file &&
|
|
933
|
-
|
|
934
|
-
|
|
1108
|
+
// args, not opts: file_content is a server param and may have
|
|
1109
|
+
// arrived via --args-file (merged into args above).
|
|
1110
|
+
!args.file_content) {
|
|
1111
|
+
// Skip the first operand only when it really is the action: with
|
|
1112
|
+
// the action arriving via --args-file, Commander binds a bare
|
|
1113
|
+
// path to the [action] positional slot, so cmd.args[0] is itself
|
|
1114
|
+
// a path candidate.
|
|
1115
|
+
const operandCandidates = primaryArgValue !== undefined && primaryArgValue === args.action
|
|
1116
|
+
? cmd.args.slice(1)
|
|
1117
|
+
: cmd.args;
|
|
1118
|
+
const pathOperand = pickExistingFileOperand(operandCandidates);
|
|
935
1119
|
if (pathOperand) {
|
|
936
1120
|
opts.local_file = pathOperand;
|
|
937
1121
|
}
|
|
@@ -947,7 +1131,7 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
947
1131
|
// Stream the file directly to AI Drive via the dedicated tool_cli
|
|
948
1132
|
// endpoint (POST /api/tool_cli/aidrive/upload) — no blob middleman.
|
|
949
1133
|
if (tool.name === 'aidrive' &&
|
|
950
|
-
|
|
1134
|
+
args.action === 'upload' &&
|
|
951
1135
|
opts.local_file) {
|
|
952
1136
|
const localFilePath = opts.local_file;
|
|
953
1137
|
const override = !!opts.override;
|
|
@@ -967,8 +1151,23 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
967
1151
|
output(result);
|
|
968
1152
|
return;
|
|
969
1153
|
}
|
|
970
|
-
|
|
971
|
-
|
|
1154
|
+
const designAttachmentArgs = collectDesignAttachmentArgs(process.argv);
|
|
1155
|
+
const isDesignAttachmentCommand = designAttachmentArgs.length > 0 &&
|
|
1156
|
+
((tool.name === 'create_task' && args.task_type === 'design') ||
|
|
1157
|
+
tool.name === 'task_continue');
|
|
1158
|
+
if (isDesignAttachmentCommand) {
|
|
1159
|
+
const uploaded = await uploadDesignAttachments(designAttachmentArgs, client);
|
|
1160
|
+
const existing = Array.isArray(args.attachments)
|
|
1161
|
+
? args.attachments
|
|
1162
|
+
: [];
|
|
1163
|
+
args.attachments = [...existing, ...uploaded];
|
|
1164
|
+
delete args.image;
|
|
1165
|
+
delete args.file;
|
|
1166
|
+
}
|
|
1167
|
+
// Non-Design create_task --file keeps its CSV/Excel file_urls contract.
|
|
1168
|
+
if (tool.name === 'create_task' &&
|
|
1169
|
+
args.task_type !== 'design' &&
|
|
1170
|
+
opts.file) {
|
|
972
1171
|
const fileValues = Array.isArray(opts.file)
|
|
973
1172
|
? opts.file
|
|
974
1173
|
: [opts.file];
|
|
@@ -1153,7 +1352,10 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
1153
1352
|
typeof result.data === 'object') {
|
|
1154
1353
|
const data = result.data;
|
|
1155
1354
|
const projectId = data.project_id;
|
|
1156
|
-
|
|
1355
|
+
// args.type is the value actually sent to the API: the positional
|
|
1356
|
+
// is copied into args and --args-file may override it (file wins),
|
|
1357
|
+
// so the export must key on the merged view, never the positional.
|
|
1358
|
+
const taskType = args.type;
|
|
1157
1359
|
if (projectId && taskType) {
|
|
1158
1360
|
info(`Exporting ${taskType} artifact...`);
|
|
1159
1361
|
try {
|
|
@@ -1186,8 +1388,7 @@ function registerToolCommand(parentProgram, tool, clientFactory, vdProjectIdDefa
|
|
|
1186
1388
|
// web_search payloads are large and agent-consumed — indent-2 here
|
|
1187
1389
|
// is pure token overhead on every SAS/Claw search
|
|
1188
1390
|
output(result, {
|
|
1189
|
-
compact: tool.name === 'web_search' ||
|
|
1190
|
-
Boolean(mediaItemsKeyFor(tool.name)),
|
|
1391
|
+
compact: tool.name === 'web_search' || Boolean(mediaItemsKeyFor(tool.name)),
|
|
1191
1392
|
});
|
|
1192
1393
|
}
|
|
1193
1394
|
catch (err) {
|
|
@@ -1364,10 +1565,10 @@ program
|
|
|
1364
1565
|
const remainingSec = expires_in - elapsedSec;
|
|
1365
1566
|
const tokenResp = await unauthenticatedRequest(baseUrl, `/api/cli_auth/token?code=${encodeURIComponent(device_code)}`, 'GET');
|
|
1366
1567
|
if (tokenResp.status === 'approved' && tokenResp.api_key) {
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1568
|
+
updateConfigFile({
|
|
1569
|
+
api_key: tokenResp.api_key,
|
|
1570
|
+
base_url: baseUrl,
|
|
1571
|
+
});
|
|
1371
1572
|
info('Login successful! API key saved.');
|
|
1372
1573
|
output({
|
|
1373
1574
|
status: 'ok',
|
|
@@ -1405,8 +1606,7 @@ program
|
|
|
1405
1606
|
info('Already logged out (no API key in config).');
|
|
1406
1607
|
return;
|
|
1407
1608
|
}
|
|
1408
|
-
|
|
1409
|
-
saveConfigFile(config);
|
|
1609
|
+
updateConfigFile({}, ['api_key']);
|
|
1410
1610
|
info('Logged out. API key removed.');
|
|
1411
1611
|
output({
|
|
1412
1612
|
status: 'ok',
|
|
@@ -1439,6 +1639,25 @@ program
|
|
|
1439
1639
|
process.exit(1);
|
|
1440
1640
|
}
|
|
1441
1641
|
});
|
|
1642
|
+
// Runtime - report the sandbox environment this CLI process runs in
|
|
1643
|
+
program
|
|
1644
|
+
.command('runtime')
|
|
1645
|
+
.description('Show the Genspark sandbox runtime this CLI is running in ' +
|
|
1646
|
+
'(reads the /opt/genspark template marker; all-null outside a sandbox)')
|
|
1647
|
+
.action(() => {
|
|
1648
|
+
const runtime = getSandboxRuntime();
|
|
1649
|
+
output({
|
|
1650
|
+
status: 'ok',
|
|
1651
|
+
message: 'success',
|
|
1652
|
+
data: {
|
|
1653
|
+
family: runtime.family,
|
|
1654
|
+
version: runtime.version,
|
|
1655
|
+
is_sb_box: runtime.isSbBox,
|
|
1656
|
+
is_super_agent_sandbox: runtime.isSuperAgentSandbox,
|
|
1657
|
+
is_genspark_sandbox: runtime.isGensparkSandbox,
|
|
1658
|
+
},
|
|
1659
|
+
});
|
|
1660
|
+
});
|
|
1442
1661
|
// Init OpenCode - generate opencode.json config
|
|
1443
1662
|
program
|
|
1444
1663
|
.command('init-opencode')
|
|
@@ -1652,6 +1871,13 @@ function _generateContextMd(projectRoot, relSkillsDir, skillDirs) {
|
|
|
1652
1871
|
|
|
1653
1872
|
Read [gsk-shared](${sharedSkill}) for authentication, global flags, and output conventions.
|
|
1654
1873
|
|
|
1874
|
+
## Connectors
|
|
1875
|
+
|
|
1876
|
+
For any third-party integration (Gmail, Slack, Notion, HubSpot, custom MCP
|
|
1877
|
+
servers, ...), start with [gsk-connector](${relSkillsDir}/gsk-connector/SKILL.md) —
|
|
1878
|
+
one fixed \`list\` → \`search\` → \`tools <id>\` → \`call <id>\` flow covers every
|
|
1879
|
+
connector instead of memorizing per-service commands below.
|
|
1880
|
+
|
|
1655
1881
|
## Available Skills
|
|
1656
1882
|
|
|
1657
1883
|
${skillList}
|
|
@@ -1892,12 +2118,16 @@ _groupCommands.set('chat', chatCmd);
|
|
|
1892
2118
|
chatCmd
|
|
1893
2119
|
.command('events <project_id>')
|
|
1894
2120
|
.description("Attach to the conversation's in-flight run and stream its events " +
|
|
1895
|
-
'live (one JSON line per event on stdout):
|
|
1896
|
-
'
|
|
1897
|
-
'
|
|
1898
|
-
'
|
|
1899
|
-
'
|
|
1900
|
-
|
|
2121
|
+
'live (one JSON line per event on stdout): the first line is a ' +
|
|
2122
|
+
'project_data snapshot event, buffered events since run start ' +
|
|
2123
|
+
'replay next, then live events follow until the run ends. Every ' +
|
|
2124
|
+
'event line carries an _event_index. Prints no_running_agent when ' +
|
|
2125
|
+
'the conversation is idle. Upstream segment ends during long runs ' +
|
|
2126
|
+
'are reattached automatically; after a real disconnect, reattach ' +
|
|
2127
|
+
'with --last-seen <highest _event_index already processed> to ' +
|
|
2128
|
+
'skip the replayed prefix.')
|
|
2129
|
+
.option('--last-seen <index>', 'resume cursor: only replay events with _event_index greater than ' +
|
|
2130
|
+
'this (take it from the last event line of the previous stream)')
|
|
1901
2131
|
.action(async (projectId, opts) => {
|
|
1902
2132
|
try {
|
|
1903
2133
|
const lastSeen = parseLastSeenIndex(opts.lastSeen);
|
|
@@ -1921,6 +2151,11 @@ chatCmd
|
|
|
1921
2151
|
else if (msg.heartbeat !== undefined) {
|
|
1922
2152
|
debug(` Heartbeat #${msg.heartbeat}`);
|
|
1923
2153
|
}
|
|
2154
|
+
else if (msg.debug && typeof msg.message === 'string') {
|
|
2155
|
+
// Server-side notices (e.g. transparent segment reattach)
|
|
2156
|
+
// are context, not payload — stderr, --debug only.
|
|
2157
|
+
debug(` ${msg.message}`);
|
|
2158
|
+
}
|
|
1924
2159
|
});
|
|
1925
2160
|
output(result);
|
|
1926
2161
|
if (result.status === 'error') {
|
|
@@ -1944,11 +2179,12 @@ async function main() {
|
|
|
1944
2179
|
if (meshArgs !== null) {
|
|
1945
2180
|
program.parseOptions(process.argv);
|
|
1946
2181
|
setDebugEnabled(getGlobalOptions().debug);
|
|
1947
|
-
// Forward an explicit `--base-url` flag only
|
|
1948
|
-
//
|
|
1949
|
-
// inside the mesh layer so it doesn't inherit gsk's localhost default.
|
|
2182
|
+
// Forward an explicit `--base-url` flag only; env/config/default resolution
|
|
2183
|
+
// happens inside the mesh layer.
|
|
1950
2184
|
const rawBase = program.opts().baseUrl;
|
|
1951
|
-
const baseUrlOverride = rawBase &&
|
|
2185
|
+
const baseUrlOverride = rawBase && program.getOptionValueSource('baseUrl') === 'cli'
|
|
2186
|
+
? normalizeBaseUrl(rawBase)
|
|
2187
|
+
: undefined;
|
|
1952
2188
|
await runMesh(meshArgs, baseUrlOverride);
|
|
1953
2189
|
return;
|
|
1954
2190
|
}
|
|
@@ -1986,7 +2222,20 @@ async function main() {
|
|
|
1986
2222
|
debug(`Fetched and cached ${tools.length} tools from server`);
|
|
1987
2223
|
}
|
|
1988
2224
|
catch (err) {
|
|
1989
|
-
|
|
2225
|
+
if (isAuthError(err)) {
|
|
2226
|
+
// A definitive auth verdict must be loud: swallowing it registers
|
|
2227
|
+
// zero dynamic commands and the user sees "unknown command" with
|
|
2228
|
+
// no hint that the token is the cause (#45982). Warn-and-continue
|
|
2229
|
+
// (not fail-fast) so built-in commands — `gsk login` above all —
|
|
2230
|
+
// still work with an expired token.
|
|
2231
|
+
warn(`API token rejected (HTTP ${err.httpStatus}) — ` +
|
|
2232
|
+
'invalid or expired. Server-provided commands were NOT loaded; ' +
|
|
2233
|
+
'if the command you need is missing, run `gsk login` to ' +
|
|
2234
|
+
're-authenticate.');
|
|
2235
|
+
}
|
|
2236
|
+
else {
|
|
2237
|
+
debug(`Failed to fetch tools: ${err.message}`);
|
|
2238
|
+
}
|
|
1990
2239
|
// Continue without dynamic commands - built-in commands still work
|
|
1991
2240
|
}
|
|
1992
2241
|
}
|