@@ -6,7 +6,7 @@ import { spawnSync } from "node:child_process";
66import chalk from "chalk" ;
77import { loadConfig } from "../config.js" ;
88import { MiosaClient } from "../client.js" ;
9- import { handleError , parseHostPath } from "./util.js" ;
9+ import { handleError , isJsonMode , parseHostPath } from "./util.js" ;
1010import { ProgressBar } from "../ui/progress.js" ;
1111import { spin } from "../ui/spinner.js" ;
1212import { UserError } from "../errors.js" ;
@@ -18,10 +18,12 @@ export function register(program: Command): void {
1818 "Copy files between local and host (host:/path syntax for remote)" ,
1919 )
2020 . option ( "-r, --recursive" , "Copy directories recursively" )
21- . action ( async ( src : string , dst : string , opts : { recursive ?: boolean } ) => {
21+ . option ( "--json" , "Output as JSON" )
22+ . action ( async ( src : string , dst : string , opts : { recursive ?: boolean ; json ?: boolean } ) => {
2223 try {
2324 const config = loadConfig ( ) ;
2425 const client = new MiosaClient ( config ) ;
26+ const json = isJsonMode ( opts ) ;
2527
2628 const srcIsRemote = src . includes ( ":" ) ;
2729 const dstIsRemote = dst . includes ( ":" ) ;
@@ -45,43 +47,46 @@ export function register(program: Command): void {
4547 if ( ! srcIsRemote && dstIsRemote ) {
4648 // Upload: local → host
4749 const { host : hostName , path : remotePath } = parseHostPath ( dst ) ;
48- const spinner = spin ( `Resolving host ${ hostName } ...` ) ;
50+ const spinner = json ? null : spin ( `Resolving host ${ hostName } ...` ) ;
4951 try {
5052 const host = await client . getHost ( hostName ) ;
51- spinner . stop ( ) ;
53+ spinner ? .stop ( ) ;
5254 await uploadPath (
5355 client ,
5456 host . id ,
5557 src ,
5658 remotePath ,
5759 opts . recursive ?? false ,
60+ json ,
5861 ) ;
5962 } catch ( err ) {
60- spinner . stop ( ) ;
63+ spinner ? .stop ( ) ;
6164 await uploadPathToSandbox (
6265 client ,
6366 hostName ,
6467 src ,
6568 remotePath ,
6669 opts . recursive ?? false ,
70+ json ,
6771 err ,
6872 ) ;
6973 }
7074 } else {
7175 // Download: host → local
7276 const { host : hostName , path : remotePath } = parseHostPath ( src ) ;
73- const spinner = spin ( `Resolving host ${ hostName } ...` ) ;
77+ const spinner = json ? null : spin ( `Resolving host ${ hostName } ...` ) ;
7478 try {
7579 const host = await client . getHost ( hostName ) ;
76- spinner . stop ( ) ;
77- await downloadFile ( client , host . id , remotePath , dst ) ;
80+ spinner ? .stop ( ) ;
81+ await downloadFile ( client , host . id , remotePath , dst , json ) ;
7882 } catch ( err ) {
79- spinner . stop ( ) ;
83+ spinner ? .stop ( ) ;
8084 await downloadPathFromSandbox (
8185 client ,
8286 hostName ,
8387 remotePath ,
8488 dst ,
89+ json ,
8590 err ,
8691 ) ;
8792 }
@@ -98,6 +103,7 @@ async function uploadPath(
98103 localPath : string ,
99104 remotePath : string ,
100105 recursive : boolean ,
106+ json : boolean ,
101107) : Promise < void > {
102108 const stat = fs . statSync ( localPath ) ;
103109
@@ -112,7 +118,7 @@ async function uploadPath(
112118 for ( const entry of entries ) {
113119 const entryLocal = path . join ( localPath , entry ) ;
114120 const entryRemote = remotePath . replace ( / \/ $ / , "" ) + "/" + entry ;
115- await uploadPath ( client , hostId , entryLocal , entryRemote , recursive ) ;
121+ await uploadPath ( client , hostId , entryLocal , entryRemote , recursive , json ) ;
116122 }
117123 return ;
118124 }
@@ -123,8 +129,8 @@ async function uploadPath(
123129 : remotePath ;
124130
125131 const data = fs . readFileSync ( localPath ) ;
126- const bar = new ProgressBar ( `Uploading ${ filename } ` ) ;
127- bar . update ( 0 , data . length ) ;
132+ const bar = json ? null : new ProgressBar ( `Uploading ${ filename } ` ) ;
133+ bar ? .update ( 0 , data . length ) ;
128134
129135 await client . uploadFile (
130136 hostId as Parameters < typeof client . uploadFile > [ 0 ] ,
@@ -133,9 +139,13 @@ async function uploadPath(
133139 filename ,
134140 ) ;
135141
136- bar . update ( data . length , data . length ) ;
137- bar . done ( ) ;
138- console . log ( chalk . green ( `Uploaded ${ localPath } → ${ remotePath } ` ) ) ;
142+ bar ?. update ( data . length , data . length ) ;
143+ bar ?. done ( ) ;
144+ if ( json ) {
145+ console . log ( JSON . stringify ( { ok : true , data : { source : localPath , target : remotePath } } , null , 2 ) ) ;
146+ } else {
147+ console . log ( chalk . green ( `Uploaded ${ localPath } → ${ remotePath } ` ) ) ;
148+ }
139149}
140150
141151async function uploadPathToSandbox (
@@ -144,6 +154,7 @@ async function uploadPathToSandbox(
144154 localPath : string ,
145155 remotePath : string ,
146156 recursive : boolean ,
157+ json : boolean ,
147158 hostError : unknown ,
148159) : Promise < void > {
149160 await assertSandboxExists ( client , sandboxId , hostError ) ;
@@ -169,22 +180,43 @@ async function uploadPathToSandbox(
169180 } finally {
170181 fs . rmSync ( archivePath , { force : true } ) ;
171182 }
172- console . log ( chalk . green ( `Uploaded ${ sourceDir } → ${ sandboxId } :${ remotePath } ` ) ) ;
183+ if ( json ) {
184+ console . log (
185+ JSON . stringify (
186+ { ok : true , data : { sandbox_id : sandboxId , source : sourceDir , target : remotePath , type : "directory" } } ,
187+ null ,
188+ 2 ,
189+ ) ,
190+ ) ;
191+ } else {
192+ console . log ( chalk . green ( `Uploaded ${ sourceDir } → ${ sandboxId } :${ remotePath } ` ) ) ;
193+ }
173194 return ;
174195 }
175196
176197 const finalRemotePath = remotePath . endsWith ( "/" )
177198 ? `${ remotePath } ${ path . basename ( localPath ) } `
178199 : remotePath ;
179200 await writeSandboxFile ( client , sandboxId , finalRemotePath , localPath ) ;
180- console . log ( chalk . green ( `Uploaded ${ localPath } → ${ sandboxId } :${ finalRemotePath } ` ) ) ;
201+ if ( json ) {
202+ console . log (
203+ JSON . stringify (
204+ { ok : true , data : { sandbox_id : sandboxId , source : localPath , target : finalRemotePath , type : "file" } } ,
205+ null ,
206+ 2 ,
207+ ) ,
208+ ) ;
209+ } else {
210+ console . log ( chalk . green ( `Uploaded ${ localPath } → ${ sandboxId } :${ finalRemotePath } ` ) ) ;
211+ }
181212}
182213
183214async function downloadPathFromSandbox (
184215 client : MiosaClient ,
185216 sandboxId : string ,
186217 remotePath : string ,
187218 localDst : string ,
219+ json : boolean ,
188220 hostError : unknown ,
189221) : Promise < void > {
190222 await assertSandboxExists ( client , sandboxId , hostError ) ;
@@ -216,7 +248,17 @@ async function downloadPathFromSandbox(
216248 ( ) => ( { } ) ,
217249 ) ;
218250 }
219- console . log ( chalk . green ( `Downloaded ${ sandboxId } :${ remotePath } → ${ localDir } ` ) ) ;
251+ if ( json ) {
252+ console . log (
253+ JSON . stringify (
254+ { ok : true , data : { sandbox_id : sandboxId , source : remotePath , target : localDir , type : "directory" } } ,
255+ null ,
256+ 2 ,
257+ ) ,
258+ ) ;
259+ } else {
260+ console . log ( chalk . green ( `Downloaded ${ sandboxId } :${ remotePath } → ${ localDir } ` ) ) ;
261+ }
220262 return ;
221263 }
222264
@@ -227,7 +269,17 @@ async function downloadPathFromSandbox(
227269 : localDst ;
228270 fs . mkdirSync ( path . dirname ( path . resolve ( localPath ) ) , { recursive : true } ) ;
229271 fs . writeFileSync ( localPath , bytes ) ;
230- console . log ( chalk . green ( `Downloaded ${ sandboxId } :${ remotePath } → ${ localPath } ` ) ) ;
272+ if ( json ) {
273+ console . log (
274+ JSON . stringify (
275+ { ok : true , data : { sandbox_id : sandboxId , source : remotePath , target : localPath , type : "file" } } ,
276+ null ,
277+ 2 ,
278+ ) ,
279+ ) ;
280+ } else {
281+ console . log ( chalk . green ( `Downloaded ${ sandboxId } :${ remotePath } → ${ localPath } ` ) ) ;
282+ }
231283}
232284
233285async function assertSandboxExists (
@@ -358,6 +410,7 @@ async function downloadFile(
358410 hostId : string ,
359411 remotePath : string ,
360412 localDst : string ,
413+ json : boolean ,
361414) : Promise < void > {
362415 const filename = path . basename ( remotePath ) ;
363416 let localPath = localDst ;
@@ -376,7 +429,7 @@ async function downloadFile(
376429 10 ,
377430 ) ;
378431
379- const bar = new ProgressBar ( `Downloading ${ filename } ` ) ;
432+ const bar = json ? null : new ProgressBar ( `Downloading ${ filename } ` ) ;
380433 const out = fs . createWriteStream ( localPath ) ;
381434 let received = 0 ;
382435
@@ -385,14 +438,18 @@ async function downloadFile(
385438 out . write ( buf ) ;
386439 received += buf . length ;
387440 if ( contentLength > 0 ) {
388- bar . update ( received , contentLength ) ;
441+ bar ? .update ( received , contentLength ) ;
389442 }
390443 }
391444
392445 await new Promise < void > ( ( resolve , reject ) => {
393446 out . end ( ( err ?: Error | null ) => ( err ? reject ( err ) : resolve ( ) ) ) ;
394447 } ) ;
395448
396- bar . done ( ) ;
397- console . log ( chalk . green ( `Downloaded ${ remotePath } → ${ localPath } ` ) ) ;
449+ bar ?. done ( ) ;
450+ if ( json ) {
451+ console . log ( JSON . stringify ( { ok : true , data : { source : remotePath , target : localPath } } , null , 2 ) ) ;
452+ } else {
453+ console . log ( chalk . green ( `Downloaded ${ remotePath } → ${ localPath } ` ) ) ;
454+ }
398455}
0 commit comments