Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 11 additions & 3 deletions LAZURIO.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Lazurio build of the Composio CLI

This is Lazurio's public fork of [ComposioHQ/composio](https://github.com/ComposioHQ/composio). It
exists to ship a build of the Composio CLI (`ts/packages/cli`) with three changes that no upstream
exists to ship a build of the Composio CLI (`ts/packages/cli`) with four changes that no upstream
release contains yet. Lazurio installs this build in the Environments it runs and reads the CLI's
output in its Launchpad. Everything else is upstream code, unchanged.

Expand All @@ -25,7 +25,15 @@ output in its Launchpad. Everything else is upstream code, unchanged.
minutes.
Upstream `next` still does (`waitForLinkedSession` in
`ts/packages/cli/src/effects/browser-login.ts`). No upstream issue or pull request yet.
4. **Release source.** The update check, `composio upgrade`, skill installation, and
4. **`composio orgs list`** prints each organization's `created_at` in its JSON, exactly as
Composio's API returns it (an ISO 8601 date-time string), next to `id`, `name`, and
`is_selected_global_org`. The API lists organizations newest first and marks none as the
account's default, so the creation time is how Lazurio finds the organization Composio created
when the account signed up: the oldest one. Upstream `next` still prints only the other three
fields (`formatOrganizationsJson` in
`ts/packages/cli/src/commands/orgs/list/orgs.list.format.ts`). No upstream issue or pull
request yet.
5. **Release source.** The update check, `composio upgrade`, skill installation, and
companion-module self-repair fetch releases from `Lazurio/composio` instead of
`ComposioHQ/composio` (`GITHUB_REPO` in `ts/packages/cli/src/constants.ts`). The
`COMPOSIO_GITHUB_*` overrides, and `GITHUB_*` for self-repair, work as upstream. The update
Expand Down Expand Up @@ -86,7 +94,7 @@ missing SHA-256 utility or any other digest stops the install right after the do

## Retirement

When upstream ships equivalents of all three changes in a stable release, Lazurio returns to the
When upstream ships equivalents of all four changes in a stable release, Lazurio returns to the
official build and these branches get no new releases. An installed Lazurio build keeps fetching
releases from this fork until it is reinstalled from the official installer, or upgraded with
`COMPOSIO_GITHUB_OWNER=ComposioHQ composio upgrade`.
7 changes: 7 additions & 0 deletions ts/packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

### Patch Changes

- `composio orgs list` prints each organization's `created_at` in its JSON
output, exactly as Composio's API returns it (an ISO 8601 date-time string),
next to `id`, `name`, and `is_selected_global_org`. The API lists
organizations newest first and marks none as the account's default, so the
creation time is how a script finds the organization Composio created when
the account signed up: the oldest one. The human-readable list is unchanged.

- `composio login --poll`, and the browser wait of `composio login` and
`composio init`, check every 5 seconds whether the login is complete, for up
to 10 minutes. From the sixth check on, the pause between checks used to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const orgsCmd$List = Command.make('list', { limit }, ({ limit }) =>
organizations.data.map(org => ({
id: org.id,
name: org.name,
created_at: org.created_at,
is_selected_global_org: defaultOrgId === org.id,
}))
)
Expand Down
16 changes: 14 additions & 2 deletions ts/packages/cli/src/services/composio-clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,16 @@ export interface OrganizationSummary {
readonly name: string;
}

/**
* A single organization entry returned by GET /api/v3.1/org/list.
*/
export interface OrganizationListItem extends OrganizationSummary {
/** When the organization was created, exactly as the API returns it. */
readonly created_at: string;
}

export interface OrganizationListResponse {
readonly data: ReadonlyArray<OrganizationSummary>;
readonly data: ReadonlyArray<OrganizationListItem>;
readonly total_items: number;
}

Expand Down Expand Up @@ -684,7 +692,11 @@ export const listOrganizations = (params: {
);
const organizations = response.organizations.map(
(organization: OrgListResponse.Organization) =>
({ id: organization.id, name: organization.name }) satisfies OrganizationSummary
({
id: organization.id,
name: organization.name,
created_at: organization.created_at,
}) satisfies OrganizationListItem
);

return {
Expand Down
5 changes: 3 additions & 2 deletions ts/packages/cli/test/__utils__/services/test-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ export interface TestLiveInput {
| ((
scope: MockRequestScope
) => SessionRetrieveInfoResponse | Promise<SessionRetrieveInfoResponse>);
organizations?: ReadonlyArray<OrganizationSummary>;
/** An organization without `created_at` is served with `TIMESTAMP`. */
organizations?: ReadonlyArray<OrganizationSummary & { readonly created_at?: string }>;
projects?: ReadonlyArray<OrgProject>;
connectedToolkits?: ReadonlyArray<string>;
latestToolVersion?: { readonly tool_slug: string; readonly version: string };
Expand Down Expand Up @@ -1383,7 +1384,7 @@ export const TestLayer = (input?: TestLiveInput) =>
list: async (query?: { limit?: number } | null, options?: RequestOptions) => {
record('org.list', options, query);
if (!accountData.organizations) throw noTestData('org.list');
const organizations = accountData.organizations.map(org => ({ ...org, ...timestamps }));
const organizations = accountData.organizations.map(org => ({ ...timestamps, ...org }));
return {
organizations,
next_cursor: null,
Expand Down
46 changes: 46 additions & 0 deletions ts/packages/cli/test/src/commands/orgs.list.cmd.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, expect, layer } from '@effect/vitest';
import { Effect } from 'effect';
import { cli, MockConsole, TestLive } from 'test/__utils__';

describe('CLI: composio orgs list', () => {
layer(
TestLive({
// Stores `org_1` as the current global org.
fixture: 'user-config-with-global-context',
accountData: {
// Newest first, as the API lists them.
organizations: [
{ id: 'org_1', name: 'acme', created_at: '2026-09-14T08:30:00.000Z' },
{ id: 'org_2', name: 'personal_workspace', created_at: '2025-03-02T17:05:41.123Z' },
],
},
})
)(it => {
it.effect('[Then] prints each organization with its created_at as JSON on stdout', () =>
Effect.gen(function* () {
yield* cli(['orgs', 'list']);

const stdout = yield* MockConsole.getLines({ stream: 'stdout' });
expect(stdout.map(line => JSON.parse(line) as unknown)).toEqual([
[
{
id: 'org_1',
name: 'acme',
created_at: '2026-09-14T08:30:00.000Z',
is_selected_global_org: true,
},
{
id: 'org_2',
name: 'personal_workspace',
created_at: '2025-03-02T17:05:41.123Z',
is_selected_global_org: false,
},
],
]);

const stderr = yield* MockConsole.getLines({ stripAnsi: true, stream: 'stderr' });
expect(stderr).toContain('✓ acme (org_1)\n personal_workspace (org_2)');
})
);
});
});
20 changes: 15 additions & 5 deletions ts/packages/cli/test/src/services/composio-clients.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,23 @@ describe('composio-clients', () => {
}).pipe(Effect.provide(withSingleton))
);

it.effect('listOrganizations returns id and name for each organization', () =>
it.effect('listOrganizations returns id, name, and created_at for each organization', () =>
Effect.gen(function* () {
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
jsonResponse({
organizations: [
{ id: 'org_a', name: 'A', created_at: 'x', updated_at: 'x' },
{ id: 'org_b', name: 'B', created_at: 'x', updated_at: 'x' },
{
id: 'org_a',
name: 'A',
created_at: '2026-09-14T08:30:00.000Z',
updated_at: '2026-10-01T12:00:00.000Z',
},
{
id: 'org_b',
name: 'B',
created_at: '2025-03-02T17:05:41.123Z',
updated_at: '2026-10-02T12:00:00.000Z',
},
],
total_pages: 1,
current_page: 1,
Expand All @@ -164,8 +174,8 @@ describe('composio-clients', () => {

expect(result).toEqual({
data: [
{ id: 'org_a', name: 'A' },
{ id: 'org_b', name: 'B' },
{ id: 'org_a', name: 'A', created_at: '2026-09-14T08:30:00.000Z' },
{ id: 'org_b', name: 'B', created_at: '2025-03-02T17:05:41.123Z' },
],
total_items: 2,
});
Expand Down
Loading