ardent-cli-beta 0.0.12 → 0.0.13

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 CHANGED
@@ -19,7 +19,8 @@ context is retained from login. Project listing, creation, deletion, and local
19
19
  context switching are implemented. Organization member listing, invitation,
20
20
  removal, and pending-invitation cancellation are implemented. Connector listing
21
21
  and registration are implemented; registration follows connector status, while
22
- credential delivery and replication readiness remain owed.
22
+ credential delivery and replication readiness remain owed. Connector-scoped branch
23
+ creation, deletion, listing, and local switching are implemented.
23
24
 
24
25
  Set `ARDENT_API_URL` to point the CLI at a development control plane. Without
25
26
  it, the CLI uses `https://production.tryardent.com`.
@@ -40,7 +41,25 @@ npm run build
40
41
  ./bin/run.js member delete teammate@example.com
41
42
  ./bin/run.js connector list
42
43
  ./bin/run.js connector create postgresql 'postgresql://user:password@database.example.com/app'
44
+ ./bin/run.js branch create "my feature"
45
+ ./bin/run.js branch list
46
+ ./bin/run.js branch switch "my feature"
47
+ ./bin/run.js branch delete "my feature"
43
48
  ARDENT_API_URL=http://localhost:8080 ./bin/run.js login --token <token>
44
49
  ```
45
50
 
51
+ Branch commands operate on a connector. When discovering connectors, the CLI uses
52
+ its project context; one connector is selected automatically. In an interactive
53
+ terminal, multiple connectors appear in a numbered picker with the saved connector
54
+ as the default. Automation uses the saved connector and fails if selection is
55
+ ambiguous. Project changes clear connector and branch context.
56
+
57
+ Creation uses the connector's latest published snapshot. If that snapshot is
58
+ unavailable, creation fails without choosing an older one. Names are unique within
59
+ a connector while the branch is not deleted. `branch delete` sends the connector
60
+ and name to the API, which resolves the target. `branch list` fetches current state
61
+ and marks the locally selected branch with `*`; `branch switch` changes local
62
+ context. Create and delete follow the operation in an interactive terminal;
63
+ non-interactive commands and `--json` return the accepted operation immediately.
64
+
46
65
  Before changing this repository, read [ARDENT_CLI_DEVELOPMENT.md](ARDENT_CLI_DEVELOPMENT.md).
@@ -5,8 +5,22 @@ export interface Session {
5
5
  };
6
6
  projects?: ProjectIdentity[];
7
7
  selectedProject?: ProjectIdentity;
8
+ connectors?: ConnectorIdentity[];
9
+ selectedConnector?: ConnectorIdentity;
10
+ branches?: BranchIdentity[];
11
+ selectedBranch?: BranchIdentity;
8
12
  token: string;
9
13
  }
14
+ export interface ConnectorIdentity {
15
+ id: string;
16
+ name: string;
17
+ project_id: string;
18
+ }
19
+ export interface BranchIdentity {
20
+ id: string;
21
+ name: string;
22
+ connector_id: string;
23
+ }
10
24
  export interface ProjectIdentity {
11
25
  id: string;
12
26
  name: string;
@@ -17,7 +17,7 @@ export async function loadSession(directory) {
17
17
  if (typeof session.token !== 'string' || session.token.length === 0)
18
18
  throw new Error();
19
19
  if (session.organization === undefined) {
20
- if (session.projects !== undefined || session.selectedProject !== undefined)
20
+ if (session.projects !== undefined || session.selectedProject !== undefined || session.connectors !== undefined || session.selectedConnector !== undefined || session.branches !== undefined || session.selectedBranch !== undefined)
21
21
  throw new Error();
22
22
  return { token: session.token };
23
23
  }
@@ -47,7 +47,43 @@ export async function loadSession(directory) {
47
47
  if (!selectedProject)
48
48
  throw new Error();
49
49
  }
50
+ let connectors;
51
+ if (session.connectors !== undefined) {
52
+ if (!selectedProject || !Array.isArray(session.connectors) || session.connectors.some((connector) => !isProjectIdentity(connector) || connector.project_id !== selectedProject.id))
53
+ throw new Error();
54
+ connectors = session.connectors.map(({ id, name, project_id }) => ({ id, name, project_id }));
55
+ if (new Set(connectors.map(({ id }) => id)).size !== connectors.length || new Set(connectors.map(({ name }) => name)).size !== connectors.length)
56
+ throw new Error();
57
+ }
58
+ let selectedConnector;
59
+ if (session.selectedConnector !== undefined) {
60
+ if (!isProjectIdentity(session.selectedConnector) || !connectors)
61
+ throw new Error();
62
+ selectedConnector = connectors.find(({ id, name, project_id }) => id === session.selectedConnector?.id && name === session.selectedConnector.name && project_id === session.selectedConnector.project_id);
63
+ if (!selectedConnector)
64
+ throw new Error();
65
+ }
66
+ let branches;
67
+ if (session.branches !== undefined) {
68
+ if (!selectedConnector || !Array.isArray(session.branches) || session.branches.some((branch) => !isProjectIdentity(branch) || branch.connector_id !== selectedConnector.id))
69
+ throw new Error();
70
+ branches = session.branches.map(({ id, name, connector_id }) => ({ id, name, connector_id }));
71
+ if (new Set(branches.map(({ id }) => id)).size !== branches.length || new Set(branches.map(({ name }) => name)).size !== branches.length)
72
+ throw new Error();
73
+ }
74
+ let selectedBranch;
75
+ if (session.selectedBranch !== undefined) {
76
+ if (!isProjectIdentity(session.selectedBranch) || !branches)
77
+ throw new Error();
78
+ selectedBranch = branches.find(({ id, name, connector_id }) => id === session.selectedBranch?.id && name === session.selectedBranch.name && connector_id === session.selectedBranch.connector_id);
79
+ if (!selectedBranch)
80
+ throw new Error();
81
+ }
50
82
  return {
83
+ ...(connectors === undefined ? {} : { connectors }),
84
+ ...(selectedConnector === undefined ? {} : { selectedConnector }),
85
+ ...(branches === undefined ? {} : { branches }),
86
+ ...(selectedBranch === undefined ? {} : { selectedBranch }),
51
87
  organization: session.organization,
52
88
  ...(projects === undefined ? {} : { projects }),
53
89
  ...(selectedProject === undefined ? {} : { selectedProject }),
@@ -0,0 +1,19 @@
1
+ export interface Branch {
2
+ id: string;
3
+ connector_id: string;
4
+ name: string;
5
+ state: string;
6
+ failure_reason?: string;
7
+ }
8
+ export interface BranchOperation {
9
+ branch: Branch;
10
+ operation: {
11
+ operation_id: string;
12
+ workflow_id: string;
13
+ };
14
+ }
15
+ export declare function listBranches(token: string, connectorID: string): Promise<Branch[]>;
16
+ export declare function createBranch(token: string, connectorID: string, name: string): Promise<BranchOperation>;
17
+ export declare function deleteBranch(token: string, connectorID: string, name: string): Promise<BranchOperation>;
18
+ export declare function waitForBranchOperation(token: string, workflowID: string, onStatus?: (status: string) => void): Promise<void>;
19
+ export declare function branchName(arguments_: unknown[]): string;
@@ -0,0 +1,60 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { request } from '../api/client.js';
3
+ export async function listBranches(token, connectorID) {
4
+ const branches = await request(token, `/branches?${new URLSearchParams({ connector_id: connectorID })}`);
5
+ if (!Array.isArray(branches) || branches.some((branch) => !isBranch(branch, connectorID)) || new Set(branches.map(({ id }) => id)).size !== branches.length || new Set(branches.map(({ name }) => name)).size !== branches.length) {
6
+ throw new Error('Ardent API returned an invalid branches response.');
7
+ }
8
+ return branches;
9
+ }
10
+ export async function createBranch(token, connectorID, name) {
11
+ const result = await request(token, '/branches', {
12
+ method: 'POST', body: JSON.stringify({ connector_id: connectorID, name, request_id: randomUUID() }),
13
+ });
14
+ validateOperation(result, connectorID, name);
15
+ return result;
16
+ }
17
+ export async function deleteBranch(token, connectorID, name) {
18
+ const result = await request(token, '/branches', {
19
+ method: 'DELETE', body: JSON.stringify({ connector_id: connectorID, name }),
20
+ });
21
+ validateOperation(result, connectorID, name);
22
+ return result;
23
+ }
24
+ export async function waitForBranchOperation(token, workflowID, onStatus) {
25
+ const deadline = Date.now() + 30 * 60 * 1000;
26
+ while (Date.now() < deadline) {
27
+ const result = await request(token, `/workflows/${encodeURIComponent(workflowID)}`);
28
+ if (!result || result.id !== workflowID || !['DELAYED', 'ENQUEUED', 'PENDING', 'SUCCESS', 'ERROR', 'CANCELLED', 'MAX_RECOVERY_ATTEMPTS_EXCEEDED'].includes(result.status) || (result.failure !== undefined && typeof result.failure !== 'string')) {
29
+ throw new Error('Ardent API returned an invalid branch operation status.');
30
+ }
31
+ onStatus?.(result.status.toLowerCase());
32
+ if (result.status === 'SUCCESS')
33
+ return;
34
+ if (!['DELAYED', 'ENQUEUED', 'PENDING'].includes(result.status))
35
+ throw new Error(result.failure || `Branch operation ${result.status.toLowerCase()}.`);
36
+ await new Promise((resolve) => setTimeout(resolve, 2000));
37
+ }
38
+ throw new Error('Timed out waiting for the branch operation. Run ardent-beta branch list to check its state.');
39
+ }
40
+ export function branchName(arguments_) {
41
+ const name = arguments_.map(String).join(' ').trim();
42
+ if (!name || [...name].length > 128 || /[\p{Cc}]/u.test(name))
43
+ throw new Error('Branch name must contain 1 to 128 characters without control characters.');
44
+ return name;
45
+ }
46
+ function isBranch(value, connectorID) {
47
+ if (typeof value !== 'object' || value === null)
48
+ return false;
49
+ const branch = value;
50
+ return typeof branch.id === 'string' && branch.id.length > 0 && branch.connector_id === connectorID &&
51
+ typeof branch.name === 'string' && branch.name.length > 0 && typeof branch.state === 'string' && branch.state.length > 0 &&
52
+ (branch.failure_reason === undefined || typeof branch.failure_reason === 'string');
53
+ }
54
+ function validateOperation(result, connectorID, name) {
55
+ if (!result || !isBranch(result.branch, connectorID) || result.branch.name !== name || !result.operation ||
56
+ typeof result.operation.operation_id !== 'string' || !result.operation.operation_id ||
57
+ typeof result.operation.workflow_id !== 'string' || !result.operation.workflow_id) {
58
+ throw new Error('Ardent API returned an invalid branch operation response.');
59
+ }
60
+ }
@@ -0,0 +1,2 @@
1
+ import { type BranchIdentity, type Session } from '../auth/credentials.js';
2
+ export declare function replaceBranchCache(directory: string, session: Session, branches: BranchIdentity[]): Promise<Session>;
@@ -0,0 +1,10 @@
1
+ import { saveSession } from '../auth/credentials.js';
2
+ export async function replaceBranchCache(directory, session, branches) {
3
+ const identities = branches.map(({ id, name, connector_id }) => ({ id, name, connector_id }));
4
+ const selectedBranch = identities.find(({ id }) => id === session.selectedBranch?.id);
5
+ const updated = { ...session, branches: identities, selectedBranch };
6
+ if (!selectedBranch)
7
+ delete updated.selectedBranch;
8
+ await saveSession(directory, updated);
9
+ return updated;
10
+ }
@@ -0,0 +1,11 @@
1
+ import { ArdentCommand } from '../../command.js';
2
+ import { type BranchOperation } from '../../branches/client.js';
3
+ export default class BranchCreate extends ArdentCommand {
4
+ static args: {
5
+ name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static strict: boolean;
8
+ static description: string;
9
+ static helpGroup: string;
10
+ run(): Promise<BranchOperation>;
11
+ }
@@ -0,0 +1,39 @@
1
+ import { Args, ux } from '@oclif/core';
2
+ import { ArdentCommand } from '../../command.js';
3
+ import { branchName, createBranch, waitForBranchOperation } from '../../branches/client.js';
4
+ import { replaceBranchCache } from '../../branches/context.js';
5
+ import { requireConnector } from '../../connectors/context.js';
6
+ import { terminalText } from '../../terminal.js';
7
+ export default class BranchCreate extends ArdentCommand {
8
+ static args = { name: Args.string({ description: 'Branch name', required: true }) };
9
+ static strict = false;
10
+ static description = 'Create a branch in the selected connector';
11
+ static helpGroup = 'Branches';
12
+ async run() {
13
+ const { argv } = await this.parse(BranchCreate);
14
+ const name = branchName(argv);
15
+ const session = await requireConnector(this.config.configDir, !this.jsonEnabled());
16
+ const interactive = !this.jsonEnabled() && ux.action.type === 'spinner';
17
+ const target = `${terminalText(name)} in connector ${terminalText(session.selectedConnector.name)}`;
18
+ if (interactive)
19
+ ux.action.start(`Creating branch ${target}`);
20
+ else if (!this.jsonEnabled())
21
+ this.log(`Creating branch ${target}...`);
22
+ try {
23
+ const result = await createBranch(session.token, session.selectedConnector.id, name);
24
+ await replaceBranchCache(this.config.configDir, session, [...(session.branches ?? []).filter(({ id, name }) => id !== result.branch.id && name !== result.branch.name), result.branch]);
25
+ if (interactive) {
26
+ await waitForBranchOperation(session.token, result.operation.workflow_id, (status) => { ux.action.status = status; });
27
+ ux.action.stop(ux.colorize('green', '✓ created'));
28
+ }
29
+ else if (!this.jsonEnabled())
30
+ this.log(`Branch creation started (${terminalText(result.branch.id)}).`);
31
+ return result;
32
+ }
33
+ catch (error) {
34
+ if (interactive)
35
+ ux.action.stop('failed');
36
+ throw error;
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,11 @@
1
+ import { ArdentCommand } from '../../command.js';
2
+ import { type BranchOperation } from '../../branches/client.js';
3
+ export default class BranchDelete extends ArdentCommand {
4
+ static args: {
5
+ name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static strict: boolean;
8
+ static description: string;
9
+ static helpGroup: string;
10
+ run(): Promise<BranchOperation>;
11
+ }
@@ -0,0 +1,39 @@
1
+ import { Args, ux } from '@oclif/core';
2
+ import { ArdentCommand } from '../../command.js';
3
+ import { branchName, deleteBranch, waitForBranchOperation } from '../../branches/client.js';
4
+ import { replaceBranchCache } from '../../branches/context.js';
5
+ import { requireConnector } from '../../connectors/context.js';
6
+ import { terminalText } from '../../terminal.js';
7
+ export default class BranchDelete extends ArdentCommand {
8
+ static args = { name: Args.string({ description: 'Branch name', required: true }) };
9
+ static strict = false;
10
+ static description = 'Delete a branch in the selected connector';
11
+ static helpGroup = 'Branches';
12
+ async run() {
13
+ const { argv } = await this.parse(BranchDelete);
14
+ const name = branchName(argv);
15
+ const session = await requireConnector(this.config.configDir, !this.jsonEnabled());
16
+ const interactive = !this.jsonEnabled() && ux.action.type === 'spinner';
17
+ const target = `${terminalText(name)} in connector ${terminalText(session.selectedConnector.name)}`;
18
+ if (interactive)
19
+ ux.action.start(`Deleting branch ${target}`);
20
+ else if (!this.jsonEnabled())
21
+ this.log(`Deleting branch ${target}...`);
22
+ try {
23
+ const result = await deleteBranch(session.token, session.selectedConnector.id, name);
24
+ if (interactive) {
25
+ await waitForBranchOperation(session.token, result.operation.workflow_id, (status) => { ux.action.status = status; });
26
+ await replaceBranchCache(this.config.configDir, session, (session.branches ?? []).filter(({ id }) => id !== result.branch.id));
27
+ ux.action.stop(ux.colorize('green', '✓ deleted'));
28
+ }
29
+ else if (!this.jsonEnabled())
30
+ this.log(`Branch deletion started (${terminalText(result.branch.id)}).`);
31
+ return result;
32
+ }
33
+ catch (error) {
34
+ if (interactive)
35
+ ux.action.stop('failed');
36
+ throw error;
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,9 @@
1
+ import { ArdentCommand } from '../../command.js';
2
+ import { type Branch } from '../../branches/client.js';
3
+ export default class BranchList extends ArdentCommand {
4
+ static description: string;
5
+ static helpGroup: string;
6
+ run(): Promise<{
7
+ branches: Branch[];
8
+ }>;
9
+ }
@@ -0,0 +1,22 @@
1
+ import { ArdentCommand } from '../../command.js';
2
+ import { listBranches } from '../../branches/client.js';
3
+ import { replaceBranchCache } from '../../branches/context.js';
4
+ import { requireConnector } from '../../connectors/context.js';
5
+ import { terminalText } from '../../terminal.js';
6
+ export default class BranchList extends ArdentCommand {
7
+ static description = 'List branches in the selected connector';
8
+ static helpGroup = 'Branches';
9
+ async run() {
10
+ const session = await requireConnector(this.config.configDir, !this.jsonEnabled());
11
+ const branches = await listBranches(session.token, session.selectedConnector.id);
12
+ const updated = await replaceBranchCache(this.config.configDir, session, branches);
13
+ if (!this.jsonEnabled()) {
14
+ if (branches.length === 0)
15
+ this.log('No branches found.');
16
+ const width = Math.max(0, ...branches.map(({ name }) => terminalText(name).length));
17
+ for (const branch of branches)
18
+ this.log(`${branch.id === updated.selectedBranch?.id ? '*' : ' '} ${terminalText(branch.name).padEnd(width)} ${terminalText(branch.state)}`);
19
+ }
20
+ return { branches };
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ import { ArdentCommand } from '../../command.js';
2
+ import { type Branch } from '../../branches/client.js';
3
+ export default class BranchSwitch extends ArdentCommand {
4
+ static args: {
5
+ name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static strict: boolean;
8
+ static description: string;
9
+ static helpGroup: string;
10
+ run(): Promise<Branch>;
11
+ }
@@ -0,0 +1,27 @@
1
+ import { Args } from '@oclif/core';
2
+ import { saveSession } from '../../auth/credentials.js';
3
+ import { ArdentCommand } from '../../command.js';
4
+ import { branchName, listBranches } from '../../branches/client.js';
5
+ import { replaceBranchCache } from '../../branches/context.js';
6
+ import { requireConnector } from '../../connectors/context.js';
7
+ import { terminalText } from '../../terminal.js';
8
+ export default class BranchSwitch extends ArdentCommand {
9
+ static args = { name: Args.string({ description: 'Branch name', required: true }) };
10
+ static strict = false;
11
+ static description = 'Switch to a branch in the selected connector';
12
+ static helpGroup = 'Branches';
13
+ async run() {
14
+ const { argv } = await this.parse(BranchSwitch);
15
+ const name = branchName(argv);
16
+ const session = await requireConnector(this.config.configDir, !this.jsonEnabled());
17
+ const branches = await listBranches(session.token, session.selectedConnector.id);
18
+ const updated = await replaceBranchCache(this.config.configDir, session, branches);
19
+ const branch = branches.find((candidate) => candidate.name === name);
20
+ if (!branch)
21
+ this.error(`Branch ${JSON.stringify(name)} not found in connector ${JSON.stringify(session.selectedConnector.name)}.`);
22
+ await saveSession(this.config.configDir, { ...updated, selectedBranch: { id: branch.id, name: branch.name, connector_id: branch.connector_id } });
23
+ if (!this.jsonEnabled())
24
+ this.log(`✓ Switched to branch ${terminalText(name)} in connector ${terminalText(session.selectedConnector.name)}`);
25
+ return branch;
26
+ }
27
+ }
@@ -1,6 +1,7 @@
1
1
  import { loadSession } from '../../auth/credentials.js';
2
2
  import { ArdentCommand } from '../../command.js';
3
3
  import { listConnectors } from '../../connectors/client.js';
4
+ import { replaceConnectorCache } from '../../connectors/context.js';
4
5
  import { terminalText } from '../../terminal.js';
5
6
  export default class ConnectorList extends ArdentCommand {
6
7
  static description = 'List connectors in the selected project';
@@ -12,6 +13,7 @@ export default class ConnectorList extends ArdentCommand {
12
13
  if (!session.selectedProject)
13
14
  this.error('No project selected. Run ardent-beta project list, then ardent-beta project switch <name>.');
14
15
  const connectors = await listConnectors(session.token, session.organization.id, session.selectedProject.id);
16
+ await replaceConnectorCache(this.config.configDir, session, connectors);
15
17
  if (!this.jsonEnabled()) {
16
18
  if (connectors.length === 0)
17
19
  this.log('No connectors found.');
@@ -0,0 +1,6 @@
1
+ import { type ConnectorIdentity, type Session } from '../auth/credentials.js';
2
+ export declare function replaceConnectorCache(directory: string, session: Session, connectors: ConnectorIdentity[]): Promise<Session>;
3
+ export declare function selectConnector(directory: string, session: Session, connector: ConnectorIdentity): Promise<Session>;
4
+ export declare function requireConnector(directory: string, interactive: boolean): Promise<Session & {
5
+ selectedConnector: ConnectorIdentity;
6
+ }>;
@@ -0,0 +1,60 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { loadSession, saveSession } from '../auth/credentials.js';
3
+ import { terminalText } from '../terminal.js';
4
+ import { listConnectors } from './client.js';
5
+ export async function replaceConnectorCache(directory, session, connectors) {
6
+ const identities = connectors.map(({ id, name, project_id }) => ({ id, name, project_id }));
7
+ const selectedConnector = identities.find(({ id }) => id === session.selectedConnector?.id) ?? (identities.length === 1 ? identities[0] : undefined);
8
+ const updated = { ...session, connectors: identities, selectedConnector };
9
+ if (!selectedConnector)
10
+ delete updated.selectedConnector;
11
+ if (session.selectedConnector?.id !== selectedConnector?.id) {
12
+ delete updated.branches;
13
+ delete updated.selectedBranch;
14
+ }
15
+ await saveSession(directory, updated);
16
+ return updated;
17
+ }
18
+ export async function selectConnector(directory, session, connector) {
19
+ const updated = { ...session, selectedConnector: connector };
20
+ if (session.selectedConnector?.id !== connector.id) {
21
+ delete updated.branches;
22
+ delete updated.selectedBranch;
23
+ }
24
+ await saveSession(directory, updated);
25
+ return updated;
26
+ }
27
+ export async function requireConnector(directory, interactive) {
28
+ let session = await loadSession(directory);
29
+ if (!session?.organization)
30
+ throw new Error('You must be logged in to run this command. Run ardent-beta login.');
31
+ if (session.selectedConnector && (!interactive || !process.stdin.isTTY))
32
+ return { ...session, selectedConnector: session.selectedConnector };
33
+ if (!session.selectedProject)
34
+ throw new Error('No project selected for connector discovery. Run ardent-beta project list, then ardent-beta project switch <name>.');
35
+ const connectors = await listConnectors(session.token, session.organization.id, session.selectedProject.id);
36
+ session = await replaceConnectorCache(directory, session, connectors);
37
+ if (session.selectedConnector && connectors.length === 1)
38
+ return { ...session, selectedConnector: session.selectedConnector };
39
+ if (connectors.length === 0)
40
+ throw new Error('No connectors found. Run ardent-beta connector create first.');
41
+ if (!interactive || !process.stdin.isTTY)
42
+ throw new Error('No connector selected. Run this command in a terminal to select a connector first.');
43
+ for (const [index, connector] of connectors.entries())
44
+ process.stderr.write(`${index + 1}. ${terminalText(connector.name)}\n`);
45
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
46
+ let answer;
47
+ const current = session.selectedConnector;
48
+ const question = current ? `Select a connector by number [${terminalText(current.name)}]: ` : 'Select a connector by number: ';
49
+ try {
50
+ answer = (await prompt.question(question)).trim();
51
+ }
52
+ finally {
53
+ prompt.close();
54
+ }
55
+ const connector = !answer && current ? current : /^\d+$/.test(answer) ? connectors[Number(answer) - 1] : undefined;
56
+ if (!connector)
57
+ throw new Error('Invalid connector selection.');
58
+ session = await selectConnector(directory, session, connector);
59
+ return { ...session, selectedConnector: connector };
60
+ }
@@ -6,6 +6,12 @@ export async function replaceProjectCache(directory, session, projects) {
6
6
  const updated = { ...session, projects: identities, selectedProject };
7
7
  if (selectedProject === undefined)
8
8
  delete updated.selectedProject;
9
+ if (!updated.selectedProject || session.selectedProject?.id !== updated.selectedProject.id) {
10
+ delete updated.connectors;
11
+ delete updated.selectedConnector;
12
+ delete updated.branches;
13
+ delete updated.selectedBranch;
14
+ }
9
15
  await saveSession(directory, updated);
10
16
  return updated;
11
17
  }
@@ -18,6 +24,12 @@ export async function selectProject(directory, session, project) {
18
24
  else
19
25
  projects[existingIndex] = selectedProject;
20
26
  const updated = { ...session, projects, selectedProject };
27
+ if (!updated.selectedProject || session.selectedProject?.id !== updated.selectedProject.id) {
28
+ delete updated.connectors;
29
+ delete updated.selectedConnector;
30
+ delete updated.branches;
31
+ delete updated.selectedBranch;
32
+ }
21
33
  await saveSession(directory, updated);
22
34
  return updated;
23
35
  }
@@ -95,6 +95,144 @@
95
95
  "status.js"
96
96
  ]
97
97
  },
98
+ "branch:create": {
99
+ "aliases": [],
100
+ "args": {
101
+ "name": {
102
+ "description": "Branch name",
103
+ "name": "name",
104
+ "required": true
105
+ }
106
+ },
107
+ "description": "Create a branch in the selected connector",
108
+ "flags": {
109
+ "json": {
110
+ "description": "Format output as json.",
111
+ "helpGroup": "GLOBAL",
112
+ "name": "json",
113
+ "allowNo": false,
114
+ "type": "boolean"
115
+ }
116
+ },
117
+ "hasDynamicHelp": false,
118
+ "hiddenAliases": [],
119
+ "id": "branch:create",
120
+ "pluginAlias": "ardent-cli-beta",
121
+ "pluginName": "ardent-cli-beta",
122
+ "pluginType": "core",
123
+ "strict": false,
124
+ "enableJsonFlag": true,
125
+ "helpGroup": "Branches",
126
+ "isESM": true,
127
+ "relativePath": [
128
+ "dist",
129
+ "commands",
130
+ "branch",
131
+ "create.js"
132
+ ]
133
+ },
134
+ "branch:delete": {
135
+ "aliases": [],
136
+ "args": {
137
+ "name": {
138
+ "description": "Branch name",
139
+ "name": "name",
140
+ "required": true
141
+ }
142
+ },
143
+ "description": "Delete a branch in the selected connector",
144
+ "flags": {
145
+ "json": {
146
+ "description": "Format output as json.",
147
+ "helpGroup": "GLOBAL",
148
+ "name": "json",
149
+ "allowNo": false,
150
+ "type": "boolean"
151
+ }
152
+ },
153
+ "hasDynamicHelp": false,
154
+ "hiddenAliases": [],
155
+ "id": "branch:delete",
156
+ "pluginAlias": "ardent-cli-beta",
157
+ "pluginName": "ardent-cli-beta",
158
+ "pluginType": "core",
159
+ "strict": false,
160
+ "enableJsonFlag": true,
161
+ "helpGroup": "Branches",
162
+ "isESM": true,
163
+ "relativePath": [
164
+ "dist",
165
+ "commands",
166
+ "branch",
167
+ "delete.js"
168
+ ]
169
+ },
170
+ "branch:list": {
171
+ "aliases": [],
172
+ "args": {},
173
+ "description": "List branches in the selected connector",
174
+ "flags": {
175
+ "json": {
176
+ "description": "Format output as json.",
177
+ "helpGroup": "GLOBAL",
178
+ "name": "json",
179
+ "allowNo": false,
180
+ "type": "boolean"
181
+ }
182
+ },
183
+ "hasDynamicHelp": false,
184
+ "hiddenAliases": [],
185
+ "id": "branch:list",
186
+ "pluginAlias": "ardent-cli-beta",
187
+ "pluginName": "ardent-cli-beta",
188
+ "pluginType": "core",
189
+ "strict": true,
190
+ "enableJsonFlag": true,
191
+ "helpGroup": "Branches",
192
+ "isESM": true,
193
+ "relativePath": [
194
+ "dist",
195
+ "commands",
196
+ "branch",
197
+ "list.js"
198
+ ]
199
+ },
200
+ "branch:switch": {
201
+ "aliases": [],
202
+ "args": {
203
+ "name": {
204
+ "description": "Branch name",
205
+ "name": "name",
206
+ "required": true
207
+ }
208
+ },
209
+ "description": "Switch to a branch in the selected connector",
210
+ "flags": {
211
+ "json": {
212
+ "description": "Format output as json.",
213
+ "helpGroup": "GLOBAL",
214
+ "name": "json",
215
+ "allowNo": false,
216
+ "type": "boolean"
217
+ }
218
+ },
219
+ "hasDynamicHelp": false,
220
+ "hiddenAliases": [],
221
+ "id": "branch:switch",
222
+ "pluginAlias": "ardent-cli-beta",
223
+ "pluginName": "ardent-cli-beta",
224
+ "pluginType": "core",
225
+ "strict": false,
226
+ "enableJsonFlag": true,
227
+ "helpGroup": "Branches",
228
+ "isESM": true,
229
+ "relativePath": [
230
+ "dist",
231
+ "commands",
232
+ "branch",
233
+ "switch.js"
234
+ ]
235
+ },
98
236
  "connector:create": {
99
237
  "aliases": [],
100
238
  "args": {
@@ -436,5 +574,5 @@
436
574
  ]
437
575
  }
438
576
  },
439
- "version": "0.0.12"
577
+ "version": "0.0.13"
440
578
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli-beta",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "The Ardent command-line interface",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",