@agentuity/cli 0.0.53 → 0.0.54
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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +66 -8
- package/dist/cli.js.map +1 -1
- package/dist/cmd/auth/ssh/add.d.ts.map +1 -1
- package/dist/cmd/auth/ssh/add.js +28 -13
- package/dist/cmd/auth/ssh/add.js.map +1 -1
- package/dist/cmd/auth/ssh/delete.d.ts.map +1 -1
- package/dist/cmd/auth/ssh/delete.js +28 -18
- package/dist/cmd/auth/ssh/delete.js.map +1 -1
- package/dist/cmd/auth/ssh/list.d.ts.map +1 -1
- package/dist/cmd/auth/ssh/list.js +5 -6
- package/dist/cmd/auth/ssh/list.js.map +1 -1
- package/dist/cmd/build/ast.d.ts +34 -0
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +159 -0
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/bundler.d.ts +2 -1
- package/dist/cmd/build/bundler.d.ts.map +1 -1
- package/dist/cmd/build/bundler.js +77 -16
- package/dist/cmd/build/bundler.js.map +1 -1
- package/dist/cmd/build/plugin.d.ts.map +1 -1
- package/dist/cmd/build/plugin.js +74 -4
- package/dist/cmd/build/plugin.js.map +1 -1
- package/dist/cmd/cloud/deployment/list.d.ts.map +1 -1
- package/dist/cmd/cloud/deployment/list.js +28 -23
- package/dist/cmd/cloud/deployment/list.js.map +1 -1
- package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
- package/dist/cmd/cloud/deployment/show.js +50 -47
- package/dist/cmd/cloud/deployment/show.js.map +1 -1
- package/dist/cmd/cloud/env/get.d.ts.map +1 -1
- package/dist/cmd/cloud/env/get.js +16 -14
- package/dist/cmd/cloud/env/get.js.map +1 -1
- package/dist/cmd/cloud/env/list.d.ts.map +1 -1
- package/dist/cmd/cloud/env/list.js +24 -20
- package/dist/cmd/cloud/env/list.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/get.d.ts.map +1 -1
- package/dist/cmd/cloud/keyvalue/get.js +18 -16
- package/dist/cmd/cloud/keyvalue/get.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/keys.d.ts.map +1 -1
- package/dist/cmd/cloud/keyvalue/keys.js +11 -11
- package/dist/cmd/cloud/keyvalue/keys.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/list-namespaces.d.ts.map +1 -1
- package/dist/cmd/cloud/keyvalue/list-namespaces.js +11 -7
- package/dist/cmd/cloud/keyvalue/list-namespaces.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/search.d.ts.map +1 -1
- package/dist/cmd/cloud/keyvalue/search.js +16 -17
- package/dist/cmd/cloud/keyvalue/search.js.map +1 -1
- package/dist/cmd/cloud/keyvalue/stats.d.ts.map +1 -1
- package/dist/cmd/cloud/keyvalue/stats.js +38 -23
- package/dist/cmd/cloud/keyvalue/stats.js.map +1 -1
- package/dist/cmd/cloud/objectstore/get.d.ts.map +1 -1
- package/dist/cmd/cloud/objectstore/get.js +17 -15
- package/dist/cmd/cloud/objectstore/get.js.map +1 -1
- package/dist/cmd/cloud/objectstore/list-buckets.d.ts.map +1 -1
- package/dist/cmd/cloud/objectstore/list-buckets.js +12 -8
- package/dist/cmd/cloud/objectstore/list-buckets.js.map +1 -1
- package/dist/cmd/cloud/objectstore/list-keys.d.ts.map +1 -1
- package/dist/cmd/cloud/objectstore/list-keys.js +13 -10
- package/dist/cmd/cloud/objectstore/list-keys.js.map +1 -1
- package/dist/cmd/cloud/resource/list.d.ts.map +1 -1
- package/dist/cmd/cloud/resource/list.js +38 -27
- package/dist/cmd/cloud/resource/list.js.map +1 -1
- package/dist/cmd/cloud/secret/get.d.ts.map +1 -1
- package/dist/cmd/cloud/secret/get.js +17 -15
- package/dist/cmd/cloud/secret/get.js.map +1 -1
- package/dist/cmd/cloud/secret/list.d.ts.map +1 -1
- package/dist/cmd/cloud/secret/list.js +24 -20
- package/dist/cmd/cloud/secret/list.js.map +1 -1
- package/dist/cmd/cloud/session/logs.d.ts.map +1 -1
- package/dist/cmd/cloud/session/logs.js +18 -15
- package/dist/cmd/cloud/session/logs.js.map +1 -1
- package/dist/cmd/dev/agents.d.ts.map +1 -1
- package/dist/cmd/dev/agents.js +55 -41
- package/dist/cmd/dev/agents.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +2 -0
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/profile/create.js +1 -1
- package/dist/cmd/profile/create.js.map +1 -1
- package/dist/cmd/profile/delete.d.ts.map +1 -1
- package/dist/cmd/profile/delete.js +1 -1
- package/dist/cmd/profile/delete.js.map +1 -1
- package/dist/cmd/profile/list.d.ts.map +1 -1
- package/dist/cmd/profile/list.js +29 -11
- package/dist/cmd/profile/list.js.map +1 -1
- package/dist/cmd/profile/show.d.ts.map +1 -1
- package/dist/cmd/profile/show.js +7 -10
- package/dist/cmd/profile/show.js.map +1 -1
- package/dist/cmd/project/delete.js +1 -1
- package/dist/cmd/project/delete.js.map +1 -1
- package/dist/cmd/version/index.d.ts.map +1 -1
- package/dist/cmd/version/index.js +1 -1
- package/dist/cmd/version/index.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +3 -1
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +32 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +3 -3
- package/src/cli.ts +109 -8
- package/src/cmd/auth/ssh/add.ts +37 -17
- package/src/cmd/auth/ssh/delete.ts +36 -23
- package/src/cmd/auth/ssh/list.ts +8 -6
- package/src/cmd/build/ast.ts +203 -0
- package/src/cmd/build/bundler.ts +81 -15
- package/src/cmd/build/plugin.ts +92 -4
- package/src/cmd/cloud/deployment/list.ts +30 -26
- package/src/cmd/cloud/deployment/show.ts +47 -42
- package/src/cmd/cloud/env/get.ts +14 -12
- package/src/cmd/cloud/env/list.ts +24 -22
- package/src/cmd/cloud/keyvalue/get.ts +19 -14
- package/src/cmd/cloud/keyvalue/keys.ts +10 -12
- package/src/cmd/cloud/keyvalue/list-namespaces.ts +10 -8
- package/src/cmd/cloud/keyvalue/search.ts +14 -17
- package/src/cmd/cloud/keyvalue/stats.ts +52 -28
- package/src/cmd/cloud/objectstore/get.ts +18 -13
- package/src/cmd/cloud/objectstore/list-buckets.ts +11 -9
- package/src/cmd/cloud/objectstore/list-keys.ts +12 -11
- package/src/cmd/cloud/resource/list.ts +33 -23
- package/src/cmd/cloud/secret/get.ts +15 -13
- package/src/cmd/cloud/secret/list.ts +24 -22
- package/src/cmd/cloud/session/logs.ts +18 -17
- package/src/cmd/dev/agents.ts +70 -50
- package/src/cmd/dev/index.ts +2 -0
- package/src/cmd/profile/create.ts +3 -3
- package/src/cmd/profile/delete.ts +5 -2
- package/src/cmd/profile/list.ts +31 -11
- package/src/cmd/profile/show.ts +15 -12
- package/src/cmd/project/delete.ts +1 -1
- package/src/cmd/version/index.ts +5 -1
- package/src/tui.ts +3 -1
- package/src/types.ts +32 -10
package/src/cli.ts
CHANGED
|
@@ -51,7 +51,8 @@ function createAPIClient(baseCtx: CommandContext, config: Config | null): APICli
|
|
|
51
51
|
async function executeOrValidate(
|
|
52
52
|
ctx: CommandContext,
|
|
53
53
|
commandName: string,
|
|
54
|
-
handler?: (ctx: CommandContext) => unknown | Promise<unknown
|
|
54
|
+
handler?: (ctx: CommandContext) => unknown | Promise<unknown>,
|
|
55
|
+
hasResponseSchema?: boolean
|
|
55
56
|
): Promise<void> {
|
|
56
57
|
if (isValidateMode(ctx.options)) {
|
|
57
58
|
// In validate mode, just output success (validation already passed via Zod)
|
|
@@ -62,7 +63,29 @@ async function executeOrValidate(
|
|
|
62
63
|
outputValidation(result, ctx.options);
|
|
63
64
|
} else if (handler) {
|
|
64
65
|
// Normal execution
|
|
65
|
-
await handler(ctx);
|
|
66
|
+
const result = await handler(ctx);
|
|
67
|
+
|
|
68
|
+
// If --json flag is set
|
|
69
|
+
if (ctx.options.json) {
|
|
70
|
+
// If command has a response schema but returned nothing, that's an error
|
|
71
|
+
if (hasResponseSchema && result === undefined) {
|
|
72
|
+
const { createError, exitWithError, ErrorCode } = await import('./errors');
|
|
73
|
+
exitWithError(
|
|
74
|
+
createError(
|
|
75
|
+
ErrorCode.INTERNAL_ERROR,
|
|
76
|
+
`Command '${commandName}' declares a response schema but returned no data. This is a bug in the command implementation.`
|
|
77
|
+
),
|
|
78
|
+
ctx.logger,
|
|
79
|
+
ctx.options.errorFormat
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Output the result as JSON if we have data
|
|
84
|
+
if (result !== undefined) {
|
|
85
|
+
const { outputJSON } = await import('./output');
|
|
86
|
+
outputJSON(result);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
66
89
|
}
|
|
67
90
|
}
|
|
68
91
|
|
|
@@ -593,7 +616,8 @@ async function registerSubcommand(
|
|
|
593
616
|
await executeOrValidate(
|
|
594
617
|
ctx as CommandContext,
|
|
595
618
|
`${parent.name()} ${subcommand.name}`,
|
|
596
|
-
subcommand.handler
|
|
619
|
+
subcommand.handler,
|
|
620
|
+
!!subcommand.schema?.response
|
|
597
621
|
);
|
|
598
622
|
} catch (error) {
|
|
599
623
|
if (error && typeof error === 'object' && 'issues' in error) {
|
|
@@ -653,7 +677,32 @@ async function registerSubcommand(
|
|
|
653
677
|
}
|
|
654
678
|
}
|
|
655
679
|
if (subcommand.handler) {
|
|
656
|
-
await subcommand.handler(ctx as CommandContext);
|
|
680
|
+
const result = await subcommand.handler(ctx as CommandContext);
|
|
681
|
+
|
|
682
|
+
// If --json flag is set
|
|
683
|
+
if (baseCtx.options.json) {
|
|
684
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
685
|
+
const hasResponseSchema = !!(subcommand as any).schema?.response;
|
|
686
|
+
|
|
687
|
+
// If command has a response schema but returned nothing, that's an error
|
|
688
|
+
if (hasResponseSchema && result === undefined) {
|
|
689
|
+
const { createError, exitWithError, ErrorCode } = await import('./errors');
|
|
690
|
+
exitWithError(
|
|
691
|
+
createError(
|
|
692
|
+
ErrorCode.INTERNAL_ERROR,
|
|
693
|
+
`Command '${parent.name()} ${subcommand.name}' declares a response schema but returned no data. This is a bug in the command implementation.`
|
|
694
|
+
),
|
|
695
|
+
baseCtx.logger,
|
|
696
|
+
baseCtx.options.errorFormat
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Output the result as JSON if we have data
|
|
701
|
+
if (result !== undefined) {
|
|
702
|
+
const { outputJSON } = await import('./output');
|
|
703
|
+
outputJSON(result);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
657
706
|
}
|
|
658
707
|
}
|
|
659
708
|
} else if (normalized.optionalAuth) {
|
|
@@ -740,7 +789,8 @@ async function registerSubcommand(
|
|
|
740
789
|
await executeOrValidate(
|
|
741
790
|
ctx as CommandContext,
|
|
742
791
|
`${parent.name()} ${subcommand.name}`,
|
|
743
|
-
subcommand.handler
|
|
792
|
+
subcommand.handler,
|
|
793
|
+
!!subcommand.schema?.response
|
|
744
794
|
);
|
|
745
795
|
} catch (error) {
|
|
746
796
|
if (error && typeof error === 'object' && 'issues' in error) {
|
|
@@ -799,7 +849,32 @@ async function registerSubcommand(
|
|
|
799
849
|
}
|
|
800
850
|
}
|
|
801
851
|
if (subcommand.handler) {
|
|
802
|
-
await subcommand.handler(ctx as CommandContext);
|
|
852
|
+
const result = await subcommand.handler(ctx as CommandContext);
|
|
853
|
+
|
|
854
|
+
// If --json flag is set
|
|
855
|
+
if (baseCtx.options.json) {
|
|
856
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
857
|
+
const hasResponseSchema = !!(subcommand as any).schema?.response;
|
|
858
|
+
|
|
859
|
+
// If command has a response schema but returned nothing, that's an error
|
|
860
|
+
if (hasResponseSchema && result === undefined) {
|
|
861
|
+
const { createError, exitWithError, ErrorCode } = await import('./errors');
|
|
862
|
+
exitWithError(
|
|
863
|
+
createError(
|
|
864
|
+
ErrorCode.INTERNAL_ERROR,
|
|
865
|
+
`Command '${parent.name()} ${subcommand.name}' declares a response schema but returned no data. This is a bug in the command implementation.`
|
|
866
|
+
),
|
|
867
|
+
baseCtx.logger,
|
|
868
|
+
baseCtx.options.errorFormat
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// Output the result as JSON if we have data
|
|
873
|
+
if (result !== undefined) {
|
|
874
|
+
const { outputJSON } = await import('./output');
|
|
875
|
+
outputJSON(result);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
803
878
|
}
|
|
804
879
|
}
|
|
805
880
|
} else {
|
|
@@ -837,7 +912,8 @@ async function registerSubcommand(
|
|
|
837
912
|
await executeOrValidate(
|
|
838
913
|
ctx as CommandContext,
|
|
839
914
|
`${parent.name()} ${subcommand.name}`,
|
|
840
|
-
subcommand.handler
|
|
915
|
+
subcommand.handler,
|
|
916
|
+
!!subcommand.schema?.response
|
|
841
917
|
);
|
|
842
918
|
} catch (error) {
|
|
843
919
|
if (error && typeof error === 'object' && 'issues' in error) {
|
|
@@ -882,7 +958,32 @@ async function registerSubcommand(
|
|
|
882
958
|
}
|
|
883
959
|
}
|
|
884
960
|
if (subcommand.handler) {
|
|
885
|
-
await subcommand.handler(ctx as CommandContext);
|
|
961
|
+
const result = await subcommand.handler(ctx as CommandContext);
|
|
962
|
+
|
|
963
|
+
// If --json flag is set
|
|
964
|
+
if (baseCtx.options.json) {
|
|
965
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
966
|
+
const hasResponseSchema = !!(subcommand as any).schema?.response;
|
|
967
|
+
|
|
968
|
+
// If command has a response schema but returned nothing, that's an error
|
|
969
|
+
if (hasResponseSchema && result === undefined) {
|
|
970
|
+
const { createError, exitWithError, ErrorCode } = await import('./errors');
|
|
971
|
+
exitWithError(
|
|
972
|
+
createError(
|
|
973
|
+
ErrorCode.INTERNAL_ERROR,
|
|
974
|
+
`Command '${parent.name()} ${subcommand.name}' declares a response schema but returned no data. This is a bug in the command implementation.`
|
|
975
|
+
),
|
|
976
|
+
baseCtx.logger,
|
|
977
|
+
baseCtx.options.errorFormat
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Output the result as JSON if we have data
|
|
982
|
+
if (result !== undefined) {
|
|
983
|
+
const { outputJSON } = await import('./output');
|
|
984
|
+
outputJSON(result);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
886
987
|
}
|
|
887
988
|
}
|
|
888
989
|
}
|
package/src/cmd/auth/ssh/add.ts
CHANGED
|
@@ -132,7 +132,7 @@ export const addCommand = createSubcommand({
|
|
|
132
132
|
const { logger, apiClient, opts } = ctx;
|
|
133
133
|
|
|
134
134
|
if (!apiClient) {
|
|
135
|
-
logger.fatal('API client is not available', ErrorCode.INTERNAL_ERROR);
|
|
135
|
+
return logger.fatal('API client is not available', ErrorCode.INTERNAL_ERROR) as never;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
try {
|
|
@@ -143,9 +143,9 @@ export const addCommand = createSubcommand({
|
|
|
143
143
|
try {
|
|
144
144
|
publicKey = readFileSync(opts.file, 'utf-8').trim();
|
|
145
145
|
} catch (error) {
|
|
146
|
-
logger.fatal(
|
|
146
|
+
return logger.fatal(
|
|
147
147
|
`Error reading file: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
148
|
-
);
|
|
148
|
+
) as never;
|
|
149
149
|
}
|
|
150
150
|
} else {
|
|
151
151
|
const stdin = await readStdinIfPiped();
|
|
@@ -157,11 +157,10 @@ export const addCommand = createSubcommand({
|
|
|
157
157
|
const discoveredKeys = discoverSSHKeys();
|
|
158
158
|
|
|
159
159
|
if (discoveredKeys.length === 0) {
|
|
160
|
-
logger.fatal(
|
|
160
|
+
return logger.fatal(
|
|
161
161
|
'No SSH public keys found in ~/.ssh/\n' +
|
|
162
162
|
'Please specify a file with --file or pipe the key via stdin'
|
|
163
|
-
);
|
|
164
|
-
return;
|
|
163
|
+
) as never;
|
|
165
164
|
}
|
|
166
165
|
|
|
167
166
|
// Fetch existing keys from server to filter out already-added ones
|
|
@@ -185,14 +184,13 @@ export const addCommand = createSubcommand({
|
|
|
185
184
|
console.log('To add a different key:');
|
|
186
185
|
tui.bullet(`Use ${tui.bold('--file <path>')} to specify a key file`);
|
|
187
186
|
tui.bullet(`Pipe the key via stdin: ${boldcmd}`);
|
|
188
|
-
return;
|
|
187
|
+
return { success: false, fingerprint: '', keyType: '', added: 0 };
|
|
189
188
|
}
|
|
190
189
|
|
|
191
190
|
if (!process.stdin.isTTY) {
|
|
192
|
-
logger.fatal(
|
|
191
|
+
return logger.fatal(
|
|
193
192
|
'Interactive selection required but cannot prompt in non-TTY environment. Use --file or pipe the key via stdin.'
|
|
194
|
-
);
|
|
195
|
-
return;
|
|
193
|
+
) as never;
|
|
196
194
|
}
|
|
197
195
|
|
|
198
196
|
const response = await enquirer.prompt<{ keys: string[] }>({
|
|
@@ -213,13 +211,17 @@ export const addCommand = createSubcommand({
|
|
|
213
211
|
if (selectedFingerprints.length === 0) {
|
|
214
212
|
tui.newline();
|
|
215
213
|
tui.info('No keys selected');
|
|
216
|
-
return;
|
|
214
|
+
return { success: false, fingerprint: '', keyType: '', added: 0 };
|
|
217
215
|
}
|
|
218
216
|
|
|
219
217
|
// Build Map for O(1) lookups
|
|
220
218
|
const keyMap = new Map(newKeys.map((k) => [k.fingerprint, k]));
|
|
221
219
|
|
|
222
220
|
// Add all selected keys
|
|
221
|
+
let addedCount = 0;
|
|
222
|
+
let lastFingerprint = '';
|
|
223
|
+
let lastKeyType = '';
|
|
224
|
+
|
|
223
225
|
for (const fingerprint of selectedFingerprints) {
|
|
224
226
|
const key = keyMap.get(fingerprint);
|
|
225
227
|
if (!key) continue;
|
|
@@ -232,6 +234,9 @@ export const addCommand = createSubcommand({
|
|
|
232
234
|
clearOnSuccess: true,
|
|
233
235
|
});
|
|
234
236
|
tui.success(`SSH key added: ${tui.muted(result.fingerprint)}`);
|
|
237
|
+
addedCount++;
|
|
238
|
+
lastFingerprint = result.fingerprint;
|
|
239
|
+
lastKeyType = key.publicKey.split(/\s+/)[0] || 'unknown';
|
|
235
240
|
} catch (error) {
|
|
236
241
|
tui.newline();
|
|
237
242
|
if (error instanceof Error) {
|
|
@@ -242,22 +247,27 @@ export const addCommand = createSubcommand({
|
|
|
242
247
|
}
|
|
243
248
|
}
|
|
244
249
|
|
|
245
|
-
return
|
|
250
|
+
return {
|
|
251
|
+
success: addedCount > 0,
|
|
252
|
+
fingerprint: lastFingerprint,
|
|
253
|
+
keyType: lastKeyType,
|
|
254
|
+
added: addedCount,
|
|
255
|
+
};
|
|
246
256
|
}
|
|
247
257
|
}
|
|
248
258
|
|
|
249
259
|
// Only process single key if we got here (from --file or stdin)
|
|
250
260
|
if (!publicKey) {
|
|
251
|
-
logger.fatal('No public key provided');
|
|
261
|
+
return logger.fatal('No public key provided') as never;
|
|
252
262
|
}
|
|
253
263
|
|
|
254
264
|
// Validate key format
|
|
255
265
|
try {
|
|
256
266
|
computeSSHKeyFingerprint(publicKey);
|
|
257
267
|
} catch (error) {
|
|
258
|
-
logger.fatal(
|
|
268
|
+
return logger.fatal(
|
|
259
269
|
`Invalid SSH key format: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
260
|
-
);
|
|
270
|
+
) as never;
|
|
261
271
|
}
|
|
262
272
|
|
|
263
273
|
const result = await tui.spinner({
|
|
@@ -268,12 +278,22 @@ export const addCommand = createSubcommand({
|
|
|
268
278
|
});
|
|
269
279
|
|
|
270
280
|
tui.success(`SSH key added: ${tui.muted(result.fingerprint)}`);
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
success: true,
|
|
284
|
+
fingerprint: result.fingerprint,
|
|
285
|
+
keyType: publicKey.trim().split(/\s+/)[0] || 'unknown',
|
|
286
|
+
added: 1,
|
|
287
|
+
};
|
|
271
288
|
} catch (error) {
|
|
272
289
|
logger.trace(error);
|
|
273
290
|
if (error instanceof Error) {
|
|
274
|
-
logger.fatal(
|
|
291
|
+
return logger.fatal(
|
|
292
|
+
`Failed to add SSH key: ${error.message}`,
|
|
293
|
+
ErrorCode.API_ERROR
|
|
294
|
+
) as never;
|
|
275
295
|
} else {
|
|
276
|
-
logger.fatal('Failed to add SSH key', ErrorCode.API_ERROR);
|
|
296
|
+
return logger.fatal('Failed to add SSH key', ErrorCode.API_ERROR) as never;
|
|
277
297
|
}
|
|
278
298
|
}
|
|
279
299
|
},
|
|
@@ -39,7 +39,7 @@ export const deleteCommand = createSubcommand({
|
|
|
39
39
|
const { logger, apiClient, args, opts, options } = ctx;
|
|
40
40
|
|
|
41
41
|
if (!apiClient) {
|
|
42
|
-
logger.fatal('API client is not available', ErrorCode.INTERNAL_ERROR);
|
|
42
|
+
return logger.fatal('API client is not available', ErrorCode.INTERNAL_ERROR) as never;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const shouldConfirm = process.stdin.isTTY ? opts.confirm : false;
|
|
@@ -53,15 +53,17 @@ export const deleteCommand = createSubcommand({
|
|
|
53
53
|
const keys = await tui.spinner('Fetching SSH keys...', () => listSSHKeys(apiClient));
|
|
54
54
|
|
|
55
55
|
if (keys.length === 0) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
if (!options.json) {
|
|
57
|
+
tui.newline();
|
|
58
|
+
tui.info('No SSH keys found');
|
|
59
|
+
}
|
|
60
|
+
return { success: false, removed: 0, fingerprints: [] };
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
if (!process.stdin.isTTY) {
|
|
62
|
-
logger.fatal(
|
|
64
|
+
return logger.fatal(
|
|
63
65
|
'Interactive selection required but cannot prompt in non-TTY environment. Provide fingerprint as argument.'
|
|
64
|
-
);
|
|
66
|
+
) as never;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
tui.newline();
|
|
@@ -79,9 +81,11 @@ export const deleteCommand = createSubcommand({
|
|
|
79
81
|
fingerprintsToRemove = response.keys;
|
|
80
82
|
|
|
81
83
|
if (fingerprintsToRemove.length === 0) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
if (!options.json) {
|
|
85
|
+
tui.newline();
|
|
86
|
+
tui.info('No keys selected');
|
|
87
|
+
}
|
|
88
|
+
return { success: false, removed: 0, fingerprints: [] };
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
|
|
@@ -98,7 +102,7 @@ export const deleteCommand = createSubcommand({
|
|
|
98
102
|
},
|
|
99
103
|
options
|
|
100
104
|
);
|
|
101
|
-
return;
|
|
105
|
+
return { success: false, removed: 0, fingerprints: [] };
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
if (shouldConfirm) {
|
|
@@ -109,8 +113,10 @@ export const deleteCommand = createSubcommand({
|
|
|
109
113
|
);
|
|
110
114
|
|
|
111
115
|
if (!confirmed) {
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
if (!options.json) {
|
|
117
|
+
tui.info('Cancelled');
|
|
118
|
+
}
|
|
119
|
+
return { success: false, removed: 0, fingerprints: [] };
|
|
114
120
|
}
|
|
115
121
|
}
|
|
116
122
|
|
|
@@ -119,11 +125,13 @@ export const deleteCommand = createSubcommand({
|
|
|
119
125
|
for (const fingerprint of fingerprintsToRemove) {
|
|
120
126
|
outputDryRun(`Would remove SSH key: ${fingerprint}`, options);
|
|
121
127
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
if (!options.json) {
|
|
129
|
+
tui.newline();
|
|
130
|
+
tui.info(
|
|
131
|
+
`[DRY RUN] Would remove ${fingerprintsToRemove.length} SSH key${fingerprintsToRemove.length > 1 ? 's' : ''}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return { success: false, removed: 0, fingerprints: [] };
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
// Actually execute the deletion
|
|
@@ -133,10 +141,12 @@ export const deleteCommand = createSubcommand({
|
|
|
133
141
|
);
|
|
134
142
|
}
|
|
135
143
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
if (!options.json) {
|
|
145
|
+
tui.newline();
|
|
146
|
+
tui.success(
|
|
147
|
+
`Removed ${fingerprintsToRemove.length} SSH key${fingerprintsToRemove.length > 1 ? 's' : ''}`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
140
150
|
|
|
141
151
|
return {
|
|
142
152
|
success: true,
|
|
@@ -146,9 +156,12 @@ export const deleteCommand = createSubcommand({
|
|
|
146
156
|
} catch (error) {
|
|
147
157
|
logger.trace(error);
|
|
148
158
|
if (error instanceof Error) {
|
|
149
|
-
logger.fatal(
|
|
159
|
+
return logger.fatal(
|
|
160
|
+
`Failed to remove SSH key: ${error.message}`,
|
|
161
|
+
ErrorCode.API_ERROR
|
|
162
|
+
) as never;
|
|
150
163
|
} else {
|
|
151
|
-
logger.fatal('Failed to remove SSH key', ErrorCode.API_ERROR);
|
|
164
|
+
return logger.fatal('Failed to remove SSH key', ErrorCode.API_ERROR) as never;
|
|
152
165
|
}
|
|
153
166
|
}
|
|
154
167
|
},
|
package/src/cmd/auth/ssh/list.ts
CHANGED
|
@@ -7,11 +7,10 @@ import { z } from 'zod';
|
|
|
7
7
|
|
|
8
8
|
const SSHListResponseSchema = z.array(
|
|
9
9
|
z.object({
|
|
10
|
-
id: z.string().describe('SSH key ID'),
|
|
11
10
|
fingerprint: z.string().describe('SSH key fingerprint'),
|
|
12
11
|
keyType: z.string().describe('SSH key type (e.g., ssh-rsa, ssh-ed25519)'),
|
|
13
|
-
comment: z.string().
|
|
14
|
-
|
|
12
|
+
comment: z.string().describe('SSH key comment'),
|
|
13
|
+
publicKey: z.string().describe('SSH public key'),
|
|
15
14
|
})
|
|
16
15
|
);
|
|
17
16
|
|
|
@@ -34,7 +33,7 @@ export const listCommand = createSubcommand({
|
|
|
34
33
|
const { logger, apiClient, options } = ctx;
|
|
35
34
|
|
|
36
35
|
if (!apiClient) {
|
|
37
|
-
logger.fatal('API client is not available', ErrorCode.INTERNAL_ERROR);
|
|
36
|
+
return logger.fatal('API client is not available', ErrorCode.INTERNAL_ERROR) as never;
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
try {
|
|
@@ -71,9 +70,12 @@ export const listCommand = createSubcommand({
|
|
|
71
70
|
} catch (error) {
|
|
72
71
|
logger.trace(error);
|
|
73
72
|
if (error instanceof Error) {
|
|
74
|
-
logger.fatal(
|
|
73
|
+
return logger.fatal(
|
|
74
|
+
`Failed to list SSH keys: ${error.message}`,
|
|
75
|
+
ErrorCode.API_ERROR
|
|
76
|
+
) as never;
|
|
75
77
|
} else {
|
|
76
|
-
logger.fatal('Failed to list SSH keys', ErrorCode.API_ERROR);
|
|
78
|
+
return logger.fatal('Failed to list SSH keys', ErrorCode.API_ERROR) as never;
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
},
|
package/src/cmd/build/ast.ts
CHANGED
|
@@ -897,3 +897,206 @@ export async function parseRoute(
|
|
|
897
897
|
}
|
|
898
898
|
return routes;
|
|
899
899
|
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Configuration extracted from createWorkbench call
|
|
903
|
+
*/
|
|
904
|
+
export interface WorkbenchConfig {
|
|
905
|
+
route: string;
|
|
906
|
+
headers?: Record<string, string>;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Result of workbench analysis
|
|
911
|
+
*/
|
|
912
|
+
export interface WorkbenchAnalysis {
|
|
913
|
+
hasWorkbench: boolean;
|
|
914
|
+
config: WorkbenchConfig | null;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Check if a TypeScript file actively uses a specific function
|
|
919
|
+
* (ignores comments and unused imports)
|
|
920
|
+
*
|
|
921
|
+
* @param content - The TypeScript source code
|
|
922
|
+
* @param functionName - The function name to check for (e.g., 'createWorkbench')
|
|
923
|
+
* @returns true if the function is both imported and called
|
|
924
|
+
*/
|
|
925
|
+
export async function checkFunctionUsage(content: string, functionName: string): Promise<boolean> {
|
|
926
|
+
try {
|
|
927
|
+
const ts = await import('typescript');
|
|
928
|
+
const sourceFile = ts.createSourceFile('temp.ts', content, ts.ScriptTarget.Latest, true);
|
|
929
|
+
|
|
930
|
+
let hasImport = false;
|
|
931
|
+
let hasUsage = false;
|
|
932
|
+
|
|
933
|
+
function visitNode(node: import('typescript').Node): void {
|
|
934
|
+
// Check for import declarations with the function
|
|
935
|
+
if (ts.isImportDeclaration(node) && node.importClause?.namedBindings) {
|
|
936
|
+
if (ts.isNamedImports(node.importClause.namedBindings)) {
|
|
937
|
+
for (const element of node.importClause.namedBindings.elements) {
|
|
938
|
+
if (element.name.text === functionName) {
|
|
939
|
+
hasImport = true;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
// Check for function calls
|
|
945
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
946
|
+
if (node.expression.text === functionName) {
|
|
947
|
+
hasUsage = true;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
// Recursively visit child nodes
|
|
951
|
+
ts.forEachChild(node, visitNode);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
visitNode(sourceFile);
|
|
955
|
+
// Only return true if both import and usage are present
|
|
956
|
+
return hasImport && hasUsage;
|
|
957
|
+
} catch (error) {
|
|
958
|
+
// Fallback to string check if AST parsing fails
|
|
959
|
+
console.warn(`AST parsing failed for ${functionName}, falling back to string check:`, error);
|
|
960
|
+
return content.includes(functionName);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Check if app.ts contains conflicting routes for a given endpoint
|
|
966
|
+
*/
|
|
967
|
+
export async function checkRouteConflicts(
|
|
968
|
+
content: string,
|
|
969
|
+
workbenchEndpoint: string
|
|
970
|
+
): Promise<boolean> {
|
|
971
|
+
try {
|
|
972
|
+
const ts = await import('typescript');
|
|
973
|
+
const sourceFile = ts.createSourceFile('app.ts', content, ts.ScriptTarget.Latest, true);
|
|
974
|
+
|
|
975
|
+
let hasConflict = false;
|
|
976
|
+
|
|
977
|
+
function visitNode(node: import('typescript').Node): void {
|
|
978
|
+
// Check for router.get calls
|
|
979
|
+
if (
|
|
980
|
+
ts.isCallExpression(node) &&
|
|
981
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
982
|
+
ts.isIdentifier(node.expression.name) &&
|
|
983
|
+
node.expression.name.text === 'get'
|
|
984
|
+
) {
|
|
985
|
+
// Check if first argument is the workbench endpoint
|
|
986
|
+
if (node.arguments.length > 0 && ts.isStringLiteral(node.arguments[0])) {
|
|
987
|
+
if (node.arguments[0].text === workbenchEndpoint) {
|
|
988
|
+
hasConflict = true;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
ts.forEachChild(node, visitNode);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
visitNode(sourceFile);
|
|
997
|
+
return hasConflict;
|
|
998
|
+
} catch (_error) {
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Analyze workbench usage and extract configuration
|
|
1005
|
+
*
|
|
1006
|
+
* @param content - The TypeScript source code
|
|
1007
|
+
* @returns workbench analysis including usage and config
|
|
1008
|
+
*/
|
|
1009
|
+
export async function analyzeWorkbench(content: string): Promise<WorkbenchAnalysis> {
|
|
1010
|
+
try {
|
|
1011
|
+
const ts = await import('typescript');
|
|
1012
|
+
const sourceFile = ts.createSourceFile('app.ts', content, ts.ScriptTarget.Latest, true);
|
|
1013
|
+
|
|
1014
|
+
let hasImport = false;
|
|
1015
|
+
let hasUsage = false;
|
|
1016
|
+
let config: WorkbenchConfig | null = null;
|
|
1017
|
+
|
|
1018
|
+
function visitNode(node: import('typescript').Node): void {
|
|
1019
|
+
// Check for import declarations with createWorkbench
|
|
1020
|
+
if (ts.isImportDeclaration(node) && node.importClause?.namedBindings) {
|
|
1021
|
+
if (ts.isNamedImports(node.importClause.namedBindings)) {
|
|
1022
|
+
for (const element of node.importClause.namedBindings.elements) {
|
|
1023
|
+
if (element.name.text === 'createWorkbench') {
|
|
1024
|
+
hasImport = true;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// Check for createWorkbench function calls and extract config
|
|
1031
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
1032
|
+
if (node.expression.text === 'createWorkbench') {
|
|
1033
|
+
hasUsage = true;
|
|
1034
|
+
|
|
1035
|
+
// Extract configuration from the first argument (if any)
|
|
1036
|
+
if (node.arguments.length > 0) {
|
|
1037
|
+
const configArg = node.arguments[0];
|
|
1038
|
+
config = parseConfigObject(configArg, ts);
|
|
1039
|
+
} else {
|
|
1040
|
+
// Default config if no arguments provided
|
|
1041
|
+
config = { route: '/workbench' };
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Recursively visit child nodes
|
|
1047
|
+
ts.forEachChild(node, visitNode);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
visitNode(sourceFile);
|
|
1051
|
+
|
|
1052
|
+
// Set default config if workbench is used but no config was parsed
|
|
1053
|
+
if (hasImport && hasUsage && !config) {
|
|
1054
|
+
config = { route: '/workbench' };
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
return {
|
|
1058
|
+
hasWorkbench: hasImport && hasUsage,
|
|
1059
|
+
config: config,
|
|
1060
|
+
};
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
// Fallback to simple check if AST parsing fails
|
|
1063
|
+
console.warn('Workbench AST parsing failed, falling back to string check:', error);
|
|
1064
|
+
const hasWorkbench = content.includes('createWorkbench');
|
|
1065
|
+
return {
|
|
1066
|
+
hasWorkbench,
|
|
1067
|
+
config: hasWorkbench ? { route: '/workbench' } : null,
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Parse a TypeScript object literal to extract configuration
|
|
1074
|
+
*/
|
|
1075
|
+
function parseConfigObject(
|
|
1076
|
+
node: import('typescript').Node,
|
|
1077
|
+
ts: typeof import('typescript')
|
|
1078
|
+
): WorkbenchConfig | null {
|
|
1079
|
+
if (!ts.isObjectLiteralExpression(node)) {
|
|
1080
|
+
return { route: '/workbench' }; // Default config
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
const config: WorkbenchConfig = { route: '/workbench' };
|
|
1084
|
+
|
|
1085
|
+
for (const property of node.properties) {
|
|
1086
|
+
if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name)) {
|
|
1087
|
+
const propertyName = property.name.text;
|
|
1088
|
+
|
|
1089
|
+
if (propertyName === 'route' && ts.isStringLiteral(property.initializer)) {
|
|
1090
|
+
config.route = property.initializer.text;
|
|
1091
|
+
} else if (
|
|
1092
|
+
propertyName === 'headers' &&
|
|
1093
|
+
ts.isObjectLiteralExpression(property.initializer)
|
|
1094
|
+
) {
|
|
1095
|
+
// Parse headers object if needed (not implemented for now)
|
|
1096
|
+
config.headers = {};
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
return config;
|
|
1102
|
+
}
|