Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ec0d4e8
refactor: simplify fetchUsage return type to number
larryrider Jan 14, 2026
340c9eb
refactor: enhance Config command to support extended flags for additi…
larryrider Jan 16, 2026
ce80cf4
feat: add WorkspacesList and WorkspacesUse commands with extended fun…
larryrider Jan 16, 2026
1dc5bbb
feat: enhance authentication flow to include workspace credentials an…
larryrider Jan 16, 2026
bf66142
feat: update getFolderContent to support workspace-specific folder an…
larryrider Jan 16, 2026
b430103
refactor: streamline folder and file UUID retrieval across commands
larryrider Jan 16, 2026
4b4d013
feat: update workspace to decrypt mnemonic key from user creds
larryrider Jan 16, 2026
13f89c7
deps: add crypto deps
larryrider Jan 16, 2026
2d5fa24
feat: add create folder on workspaces
larryrider Jan 19, 2026
5346abe
feat: add workspaces to PrepareNetwork
larryrider Jan 19, 2026
48e99a5
fix: selfsigned generate mock return value
larryrider Jan 19, 2026
25dce00
fix: update timing breakdown logging format for consistency
larryrider Jan 19, 2026
525d562
refactor: update TrashService to handle workspace-specific trash oper…
larryrider Jan 19, 2026
78c3b5c
feat: enhance createFile method in DriveFileService to support worksp…
larryrider Jan 19, 2026
e47436e
fix: update error handling for file and directory validation in Valid…
larryrider Jan 19, 2026
55fdcd9
fix: add await to createFolder calls in UploadFolderService and WebDa…
larryrider Jan 20, 2026
1cc7b29
fix: refactor thumbnail class for better testing
larryrider Jan 20, 2026
cb4b102
refactor: improved and simplified services, WebDAV server and request…
larryrider Jan 20, 2026
3175795
refactor: replace error utility functions with ErrorUtils class metho…
larryrider Jan 20, 2026
ed6283a
refactor: convert methods to arrow functions for consistency across s…
larryrider Jan 20, 2026
4f5997d
refactor: update Webdav command options and descriptions for clarity
larryrider Jan 20, 2026
a908baf
refactor: update network preparation to return bucket and mnemonic
larryrider Jan 20, 2026
3fb361b
refactor: remove aliases for workspaces commands
larryrider Jan 20, 2026
e6f12bc
refactor: add aliases to commands for improved usability
larryrider Jan 20, 2026
2468eba
fix: upload folder spies
larryrider Jan 20, 2026
cbcaaa9
refactor: rename getRootFolderIdIfEmpty to fallbackToRootFolderIdIfEm…
larryrider Jan 21, 2026
f1db720
fix: sonarcloud issues
larryrider Jan 21, 2026
acc5a88
refactor: update test files to mock ConfigService methods for improve…
larryrider Jan 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"/oclif.manifest.json"
],
"dependencies": {
"@dashlane/pqc-kem-kyber512-node": "1.0.0",
"@inquirer/prompts": "8.2.0",
"@internxt/inxt-js": "2.2.9",
"@internxt/lib": "1.4.1",
Expand All @@ -51,6 +52,7 @@
"express": "5.2.1",
"express-async-handler": "1.2.0",
"fast-xml-parser": "5.3.3",
"hash-wasm": "4.12.0",
"mime-types": "3.0.2",
"open": "11.0.0",
"openpgp": "6.3.0",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/add-cert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { WEBDAV_SSL_CERTS_DIR } from '../constants/configs';
export default class AddCert extends Command {
static readonly args = {};
static readonly description = 'Add a self-signed certificate to the trusted store for macOS, Linux, and Windows.';
static readonly aliases = [];
static readonly aliases = ['add:cert'];
static readonly examples = ['<%= config.bin %> <%= command.id %>'];
static readonly flags = {};
static readonly enableJsonFlag = true;
Expand Down
26 changes: 21 additions & 5 deletions src/commands/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Command } from '@oclif/core';
import { Command, Flags } from '@oclif/core';
import { ConfigService } from '../services/config.service';
import { CLIUtils } from '../utils/cli.utils';
import { UsageService } from '../services/usage.service';
Expand All @@ -10,26 +10,42 @@ export default class Config extends Command {
static readonly description = 'Display useful information from the user logged into the Internxt CLI.';
static readonly aliases = [];
static readonly examples = ['<%= config.bin %> <%= command.id %>'];
static readonly flags = {};
static readonly flags = {
...CLIUtils.CommonFlags,
extended: Flags.boolean({
char: 'e',
description: 'Displays additional information in the list.',
required: false,
}),
};
static readonly enableJsonFlag = true;

public run = async () => {
const { flags } = await this.parse(Config);

const userCredentials = await ConfigService.instance.readUser();
if (userCredentials?.user) {
const usedSpace = FormatUtils.humanFileSize((await UsageService.instance.fetchUsage()).total);
const usedSpace = FormatUtils.humanFileSize(await UsageService.instance.fetchUsage());
const availableSpace = FormatUtils.formatLimit(await UsageService.instance.fetchSpaceLimit());

const configList = [
{ key: 'Email', value: userCredentials.user.email },
{ key: 'User name', value: `${userCredentials.user.name} ${userCredentials.user.lastname}` },
{ key: 'Root folder ID', value: userCredentials.user.rootFolderId },
{ key: 'Used space', value: usedSpace },
{ key: 'Available space', value: availableSpace },
];
const header: Header[] = [
if (flags.extended) {
configList.push(
{ key: 'User ID', value: userCredentials.user.uuid },
{ key: 'Created at', value: userCredentials.user.createdAt },
);
}
const headers: Header[] = [
{ value: 'key', alias: 'Key' },
{ value: 'value', alias: 'Value' },
];
CLIUtils.table(this.log.bind(this), header, configList);
CLIUtils.table(this.log.bind(this), headers, configList);

return { success: true, config: Object.fromEntries(configList.map(({ key, value }) => [key, value])) };
} else {
Expand Down
12 changes: 5 additions & 7 deletions src/commands/create-folder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { AsyncUtils } from '../utils/async.utils';
export default class CreateFolder extends Command {
static readonly args = {};
static readonly description = 'Create a folder in your Internxt Drive';
static readonly aliases = [];
static readonly aliases = ['create:folder'];
static readonly examples = ['<%= config.bin %> <%= command.id %>'];
static readonly flags = {
...CLIUtils.CommonFlags,
Expand All @@ -36,14 +36,12 @@ export default class CreateFolder extends Command {
if (!userCredentials) throw new MissingCredentialsError();

const folderName = await this.getFolderName(flags['name'], nonInteractive);
let folderUuid = await this.getFolderUuid(flags['id'], nonInteractive);
if (folderUuid.trim().length === 0) {
// folderId is empty from flags&prompt, which means we should use RootFolderUuid
folderUuid = userCredentials.user.rootFolderId;
}

const folderUuidFromFlag = await this.getFolderUuid(flags['id'], nonInteractive);
const folderUuid = await CLIUtils.fallbackToRootFolderIdIfEmpty(folderUuidFromFlag, userCredentials);

CLIUtils.doing('Creating folder...', flags['json']);
const [createNewFolder, requestCanceler] = DriveFolderService.instance.createFolder({
const [createNewFolder, requestCanceler] = await DriveFolderService.instance.createFolder({
plainName: folderName,
parentFolderUuid: folderUuid,
});
Expand Down
9 changes: 6 additions & 3 deletions src/commands/download-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,16 @@ export default class DownloadFile extends Command {

// Prepare the network
const { user } = await AuthService.instance.getAuthDetails();
const networkFacade = CLIUtils.prepareNetwork({ loginUserDetails: user, jsonFlag: flags['json'] });

CLIUtils.doing('Preparing Network', flags['json']);
const { networkFacade, bucket, mnemonic } = await CLIUtils.prepareNetwork(user);
CLIUtils.done(flags['json']);
// Download the file
const fileWriteStream = createWriteStream(downloadPath);

const [executeDownload, abortable] = await networkFacade.downloadToStream(
driveFile.bucket,
user.mnemonic,
bucket,
mnemonic,
driveFile.fileId,
driveFile.size,
StreamUtils.writeStreamToWritableStream(fileWriteStream),
Expand Down
7 changes: 2 additions & 5 deletions src/commands/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,8 @@ export default class List extends Command {
const userCredentials = await ConfigService.instance.readUser();
if (!userCredentials) throw new MissingCredentialsError();

let folderUuid = await this.getFolderUuid(flags['id'], nonInteractive);
if (folderUuid.trim().length === 0) {
// folderId is empty from flags&prompt, which means we should use RootFolderUuid
folderUuid = userCredentials.user.rootFolderId;
}
const folderUuidFromFlag = await this.getFolderUuid(flags['id'], nonInteractive);
const folderUuid = await CLIUtils.fallbackToRootFolderIdIfEmpty(folderUuidFromFlag, userCredentials);

const { folders, files } = await DriveFolderService.instance.getFolderContent(folderUuid);

Expand Down
44 changes: 12 additions & 32 deletions src/commands/move-file.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Command, Flags } from '@oclif/core';
import { ConfigService } from '../services/config.service';
import { CLIUtils } from '../utils/cli.utils';
import { MissingCredentialsError, NotValidFileUuidError, NotValidFolderUuidError } from '../types/command.types';
import { MissingCredentialsError, NotValidFileUuidError } from '../types/command.types';
import { ValidationService } from '../services/validation.service';
import { DriveFileService } from '../services/drive/drive-file.service';

Expand Down Expand Up @@ -34,11 +34,17 @@ export default class MoveFile extends Command {
if (!userCredentials) throw new MissingCredentialsError();

const fileUuid = await this.getFileUuid(flags['id'], nonInteractive);
let destinationFolderUuid = await this.getDestinationFolderUuid(flags['destination'], nonInteractive);
if (destinationFolderUuid.trim().length === 0) {
// destination id is empty from flags&prompt, which means we should use RootFolderUuid
destinationFolderUuid = userCredentials.user.rootFolderId;
}

const destinationFolderUuidFromFlag = await CLIUtils.getDestinationFolderUuid({
destinationFolderUuidFlag: flags['destination'],
destinationFlagName: MoveFile.flags['destination'].name,
nonInteractive,
reporter: this.log.bind(this),
});
const destinationFolderUuid = await CLIUtils.fallbackToRootFolderIdIfEmpty(
destinationFolderUuidFromFlag,
userCredentials,
);

const newFile = await DriveFileService.instance.moveFile(fileUuid, { destinationFolder: destinationFolderUuid });
const message = `File moved successfully to: ${destinationFolderUuid}`;
Expand Down Expand Up @@ -78,30 +84,4 @@ export default class MoveFile extends Command {
);
return fileUuid;
};

private getDestinationFolderUuid = async (
destinationFolderUuidFlag: string | undefined,
nonInteractive: boolean,
): Promise<string> => {
const destinationFolderUuid = await CLIUtils.getValueFromFlag(
{
value: destinationFolderUuidFlag,
name: MoveFile.flags['destination'].name,
},
{
nonInteractive,
prompt: {
message: 'What is the destination folder id? (leave empty for the root folder)',
options: { type: 'input' },
},
},
{
validate: ValidationService.instance.validateUUIDv4,
error: new NotValidFolderUuidError(),
canBeEmpty: true,
},
this.log.bind(this),
);
return destinationFolderUuid;
};
}
42 changes: 11 additions & 31 deletions src/commands/move-folder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ export default class MoveFolder extends Command {
if (!userCredentials) throw new MissingCredentialsError();

const folderUuid = await this.getFolderUuid(flags['id'], nonInteractive);
let destinationFolderUuid = await this.getDestinationFolderUuid(flags['destination'], nonInteractive);
if (destinationFolderUuid.trim().length === 0) {
// destination id is empty from flags&prompt, which means we should use RootFolderUuid
destinationFolderUuid = userCredentials.user.rootFolderId;
}

const destinationFolderUuidFromFlag = await CLIUtils.getDestinationFolderUuid({
destinationFolderUuidFlag: flags['destination'],
destinationFlagName: MoveFolder.flags['destination'].name,
nonInteractive,
reporter: this.log.bind(this),
});
const destinationFolderUuid = await CLIUtils.fallbackToRootFolderIdIfEmpty(
destinationFolderUuidFromFlag,
userCredentials,
);

const newFolder = await DriveFolderService.instance.moveFolder(folderUuid, {
destinationFolder: destinationFolderUuid,
Expand Down Expand Up @@ -80,30 +86,4 @@ export default class MoveFolder extends Command {
);
return folderUuid;
};

private getDestinationFolderUuid = async (
destinationFolderUuidFlag: string | undefined,
nonInteractive: boolean,
): Promise<string> => {
const destinationFolderUuid = await CLIUtils.getValueFromFlag(
{
value: destinationFolderUuidFlag,
name: MoveFolder.flags['destination'].name,
},
{
nonInteractive,
prompt: {
message: 'What is the destination folder id? (leave empty for the root folder)',
options: { type: 'input' },
},
},
{
validate: ValidationService.instance.validateUUIDv4,
error: new NotValidFolderUuidError(),
canBeEmpty: true,
},
this.log.bind(this),
);
return destinationFolderUuid;
};
}
43 changes: 11 additions & 32 deletions src/commands/trash-restore-file.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Command, Flags } from '@oclif/core';
import { ConfigService } from '../services/config.service';
import { CLIUtils } from '../utils/cli.utils';
import { MissingCredentialsError, NotValidFileUuidError, NotValidFolderUuidError } from '../types/command.types';
import { MissingCredentialsError, NotValidFileUuidError } from '../types/command.types';
import { ValidationService } from '../services/validation.service';
import { DriveFileService } from '../services/drive/drive-file.service';

Expand Down Expand Up @@ -35,12 +35,17 @@ export default class TrashRestoreFile extends Command {
if (!userCredentials) throw new MissingCredentialsError();

const fileUuid = await this.getFileUuid(flags['id'], nonInteractive);
let destinationFolderUuid = await this.getDestinationFolderUuid(flags['destination'], nonInteractive);

if (destinationFolderUuid.trim().length === 0) {
// destinationFolderUuid is empty from flags&prompt, which means we should use RootFolderUuid
destinationFolderUuid = userCredentials.user.rootFolderId;
}
const destinationFolderUuidFromFlag = await CLIUtils.getDestinationFolderUuid({
destinationFolderUuidFlag: flags['destination'],
destinationFlagName: TrashRestoreFile.flags['destination'].name,
nonInteractive,
reporter: this.log.bind(this),
});
const destinationFolderUuid = await CLIUtils.fallbackToRootFolderIdIfEmpty(
destinationFolderUuidFromFlag,
userCredentials,
);

const file = await DriveFileService.instance.moveFile(fileUuid, { destinationFolder: destinationFolderUuid });
const message = `File restored successfully to: ${destinationFolderUuid}`;
Expand Down Expand Up @@ -80,30 +85,4 @@ export default class TrashRestoreFile extends Command {
);
return fileUuid;
};

private getDestinationFolderUuid = async (
destinationFolderUuidFlag: string | undefined,
nonInteractive: boolean,
): Promise<string> => {
const destinationFolderUuid = await CLIUtils.getValueFromFlag(
{
value: destinationFolderUuidFlag,
name: TrashRestoreFile.flags['destination'].name,
},
{
nonInteractive,
prompt: {
message: 'What is the destination folder id? (leave empty for the root folder)',
options: { type: 'input' },
},
},
{
validate: ValidationService.instance.validateUUIDv4,
error: new NotValidFolderUuidError(),
canBeEmpty: true,
},
this.log.bind(this),
);
return destinationFolderUuid;
};
}
41 changes: 10 additions & 31 deletions src/commands/trash-restore-folder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,17 @@ export default class TrashRestoreFolder extends Command {
if (!userCredentials) throw new MissingCredentialsError();

const folderUuid = await this.getFolderUuid(flags['id'], nonInteractive);
let destinationFolderUuid = await this.getDestinationFolderUuid(flags['destination'], nonInteractive);

if (destinationFolderUuid.trim().length === 0) {
// destinationFolderUuid is empty from flags&prompt, which means we should use RootFolderUuid
destinationFolderUuid = userCredentials.user.rootFolderId;
}
const destinationFolderUuidFromFlag = await CLIUtils.getDestinationFolderUuid({
destinationFolderUuidFlag: flags['destination'],
destinationFlagName: TrashRestoreFolder.flags['destination'].name,
nonInteractive,
reporter: this.log.bind(this),
});
const destinationFolderUuid = await CLIUtils.fallbackToRootFolderIdIfEmpty(
destinationFolderUuidFromFlag,
userCredentials,
);

const folder = await DriveFolderService.instance.moveFolder(folderUuid, {
destinationFolder: destinationFolderUuid,
Expand Down Expand Up @@ -82,30 +87,4 @@ export default class TrashRestoreFolder extends Command {
);
return folderUuid;
};

private getDestinationFolderUuid = async (
destinationFolderUuidFlag: string | undefined,
nonInteractive: boolean,
): Promise<string> => {
const destinationFolderUuid = await CLIUtils.getValueFromFlag(
{
value: destinationFolderUuidFlag,
name: TrashRestoreFolder.flags['destination'].name,
},
{
nonInteractive,
prompt: {
message: 'What is the destination folder id? (leave empty for the root folder)',
options: { type: 'input' },
},
},
{
validate: ValidationService.instance.validateUUIDv4,
error: new NotValidFolderUuidError(),
canBeEmpty: true,
},
this.log.bind(this),
);
return destinationFolderUuid;
};
}
Loading