flipstream 0.4.0

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.
Files changed (102) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +659 -0
  3. package/bin/dev.js +9 -0
  4. package/bin/run.js +7 -0
  5. package/dist/commands/auth/clear-all.d.ts +10 -0
  6. package/dist/commands/auth/clear-all.js +50 -0
  7. package/dist/commands/auth/login.d.ts +11 -0
  8. package/dist/commands/auth/login.js +49 -0
  9. package/dist/commands/auth/logout.d.ts +10 -0
  10. package/dist/commands/auth/logout.js +51 -0
  11. package/dist/commands/auth/status.d.ts +8 -0
  12. package/dist/commands/auth/status.js +47 -0
  13. package/dist/commands/connections/list.d.ts +19 -0
  14. package/dist/commands/connections/list.js +43 -0
  15. package/dist/commands/query.d.ts +13 -0
  16. package/dist/commands/query.js +76 -0
  17. package/dist/commands/version.d.ts +15 -0
  18. package/dist/commands/version.js +17 -0
  19. package/dist/commands/workspaces/connections.d.ts +21 -0
  20. package/dist/commands/workspaces/connections.js +41 -0
  21. package/dist/commands/workspaces/get.d.ts +15 -0
  22. package/dist/commands/workspaces/get.js +65 -0
  23. package/dist/commands/workspaces/list.d.ts +20 -0
  24. package/dist/commands/workspaces/list.js +87 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +1 -0
  27. package/dist/lib/api/admin-client.d.ts +19 -0
  28. package/dist/lib/api/admin-client.js +44 -0
  29. package/dist/lib/api/client.d.ts +13 -0
  30. package/dist/lib/api/client.js +21 -0
  31. package/dist/lib/api/connections.d.ts +14 -0
  32. package/dist/lib/api/connections.js +44 -0
  33. package/dist/lib/api/errors.d.ts +7 -0
  34. package/dist/lib/api/errors.js +68 -0
  35. package/dist/lib/api/http.d.ts +9 -0
  36. package/dist/lib/api/http.js +55 -0
  37. package/dist/lib/api/ids.d.ts +1 -0
  38. package/dist/lib/api/ids.js +8 -0
  39. package/dist/lib/api/list.d.ts +12 -0
  40. package/dist/lib/api/list.js +31 -0
  41. package/dist/lib/api/projections.d.ts +2 -0
  42. package/dist/lib/api/projections.js +34 -0
  43. package/dist/lib/api/retry.d.ts +13 -0
  44. package/dist/lib/api/retry.js +37 -0
  45. package/dist/lib/auth/claims.d.ts +3 -0
  46. package/dist/lib/auth/claims.js +20 -0
  47. package/dist/lib/auth/discovery.d.ts +13 -0
  48. package/dist/lib/auth/discovery.js +62 -0
  49. package/dist/lib/auth/flow.d.ts +22 -0
  50. package/dist/lib/auth/flow.js +135 -0
  51. package/dist/lib/auth/headless.d.ts +12 -0
  52. package/dist/lib/auth/headless.js +61 -0
  53. package/dist/lib/auth/loopback.d.ts +15 -0
  54. package/dist/lib/auth/loopback.js +198 -0
  55. package/dist/lib/auth/pkce.d.ts +6 -0
  56. package/dist/lib/auth/pkce.js +17 -0
  57. package/dist/lib/auth/provider.d.ts +15 -0
  58. package/dist/lib/auth/provider.js +23 -0
  59. package/dist/lib/auth/refresh.d.ts +6 -0
  60. package/dist/lib/auth/refresh.js +69 -0
  61. package/dist/lib/auth/register.d.ts +4 -0
  62. package/dist/lib/auth/register.js +43 -0
  63. package/dist/lib/auth/revoke.d.ts +8 -0
  64. package/dist/lib/auth/revoke.js +28 -0
  65. package/dist/lib/auth/session.d.ts +6 -0
  66. package/dist/lib/auth/session.js +13 -0
  67. package/dist/lib/command/admin.d.ts +4 -0
  68. package/dist/lib/command/admin.js +30 -0
  69. package/dist/lib/command/base.d.ts +28 -0
  70. package/dist/lib/command/base.js +75 -0
  71. package/dist/lib/command/crash.d.ts +2 -0
  72. package/dist/lib/command/crash.js +37 -0
  73. package/dist/lib/command/flags.d.ts +7 -0
  74. package/dist/lib/command/flags.js +10 -0
  75. package/dist/lib/config/constants.d.ts +5 -0
  76. package/dist/lib/config/constants.js +20 -0
  77. package/dist/lib/config/xdg.d.ts +21 -0
  78. package/dist/lib/config/xdg.js +97 -0
  79. package/dist/lib/errors.d.ts +38 -0
  80. package/dist/lib/errors.js +122 -0
  81. package/dist/lib/exit-codes.d.ts +10 -0
  82. package/dist/lib/exit-codes.js +13 -0
  83. package/dist/lib/help.d.ts +4 -0
  84. package/dist/lib/help.js +14 -0
  85. package/dist/lib/output/ndjson.d.ts +1 -0
  86. package/dist/lib/output/ndjson.js +16 -0
  87. package/dist/lib/output/redact.d.ts +1 -0
  88. package/dist/lib/output/redact.js +47 -0
  89. package/dist/lib/output/render.d.ts +2 -0
  90. package/dist/lib/output/render.js +8 -0
  91. package/dist/lib/output/table.d.ts +3 -0
  92. package/dist/lib/output/table.js +19 -0
  93. package/dist/lib/store/credentials.d.ts +12 -0
  94. package/dist/lib/store/credentials.js +40 -0
  95. package/dist/lib/store/index.d.ts +5 -0
  96. package/dist/lib/store/index.js +21 -0
  97. package/dist/lib/store/keyring.d.ts +16 -0
  98. package/dist/lib/store/keyring.js +45 -0
  99. package/dist/lib/store/memory-store.d.ts +10 -0
  100. package/dist/lib/store/memory-store.js +21 -0
  101. package/oclif.manifest.json +851 -0
  102. package/package.json +79 -0
@@ -0,0 +1,50 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { fetchMetadata } from '../../lib/auth/discovery.js';
3
+ import { revokeToken } from '../../lib/auth/revoke.js';
4
+ import { BaseCommand } from '../../lib/command/base.js';
5
+ import { knownHosts, removeConfigDir } from '../../lib/config/xdg.js';
6
+ import { createStore } from '../../lib/store/index.js';
7
+ export default class AuthClearAll extends BaseCommand {
8
+ static description = 'Purge ALL Flipstream credentials: every keychain entry and the local config directory.';
9
+ static examples = ['<%= config.bin %> auth clear-all', '<%= config.bin %> auth clear-all --no-revoke'];
10
+ static flags = {
11
+ 'no-revoke': Flags.boolean({ description: 'Clear local credentials only; skip the per-host server revocation calls.' }),
12
+ };
13
+ static summary = 'Purge ALL credentials: every keychain entry and the local config dir.';
14
+ async run() {
15
+ const store = createStore();
16
+ const hosts = knownHosts();
17
+ const timeoutMs = this.flags.timeout ?? 10_000;
18
+ // Best-effort revoke each host's refresh token (in parallel — no cross-host
19
+ // dependency), then clear its keychain entry regardless of the outcome.
20
+ await Promise.all(hosts.map(async (host) => {
21
+ const creds = store.load(host);
22
+ if (!this.flags['no-revoke'] && creds?.refreshToken) {
23
+ try {
24
+ const meta = await fetchMetadata(host, { timeoutMs });
25
+ await revokeToken({
26
+ clientId: creds.clientId,
27
+ revocationEndpoint: meta.revocationEndpoint,
28
+ timeoutMs,
29
+ token: creds.refreshToken,
30
+ tokenTypeHint: 'refresh_token',
31
+ });
32
+ }
33
+ catch {
34
+ // Best-effort — proceed to clear the keychain entry anyway.
35
+ }
36
+ }
37
+ store.clear(host);
38
+ }));
39
+ const configRemoved = removeConfigDir();
40
+ return this.respond({ clearedHosts: hosts, configRemoved }, () => {
41
+ if (hosts.length === 0 && !configRemoved) {
42
+ this.log('Nothing to clear.');
43
+ }
44
+ else {
45
+ this.log(`Cleared ${hosts.length} host(s): ${hosts.join(', ') || '(none)'}`);
46
+ this.log(configRemoved ? 'Removed the local config directory.' : 'No local config directory to remove.');
47
+ }
48
+ });
49
+ }
50
+ }
@@ -0,0 +1,11 @@
1
+ import { BaseCommand } from '../../lib/command/base.js';
2
+ export default class AuthLogin extends BaseCommand<typeof AuthLogin> {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'no-browser': import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ scope: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ };
9
+ static summary: string;
10
+ run(): Promise<null | Record<string, unknown>>;
11
+ }
@@ -0,0 +1,49 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { login } from '../../lib/auth/flow.js';
3
+ import { BaseCommand } from '../../lib/command/base.js';
4
+ import { rememberHost } from '../../lib/config/xdg.js';
5
+ import { redact } from '../../lib/output/redact.js';
6
+ import { createStore } from '../../lib/store/index.js';
7
+ export default class AuthLogin extends BaseCommand {
8
+ static description = 'Log in to Flipstream in the browser (OAuth2 Authorization Code + PKCE).';
9
+ static examples = ['<%= config.bin %> auth login', '<%= config.bin %> auth login --no-browser'];
10
+ static flags = {
11
+ 'no-browser': Flags.boolean({
12
+ description: 'Headless mode: print the authorization URL, then paste the redirected URL back (no browser).',
13
+ }),
14
+ scope: Flags.string({ description: 'OAuth scope (repeatable).', multiple: true }),
15
+ };
16
+ static summary = 'Log in via the browser (OAuth2 + PKCE).';
17
+ async run() {
18
+ const result = await login({
19
+ host: this.resolvedHost(),
20
+ openBrowser: !this.flags['no-browser'],
21
+ scopes: this.flags.scope,
22
+ timeoutMs: this.flags.timeout,
23
+ });
24
+ const expiresAt = Math.floor(Date.now() / 1000) + result.expiresIn;
25
+ try {
26
+ createStore().save(result.host, {
27
+ accessToken: result.accessToken,
28
+ clientId: result.clientId,
29
+ expiresAt,
30
+ redirectUri: result.redirectUri,
31
+ refreshToken: result.refreshToken,
32
+ scopes: result.scopes,
33
+ });
34
+ // Record the host in the NON-secret index so logout/clear-all can find it.
35
+ rememberHost(result.host, {
36
+ clientId: result.clientId,
37
+ expiresAt,
38
+ redirectUri: result.redirectUri,
39
+ scopes: result.scopes,
40
+ });
41
+ }
42
+ catch {
43
+ this.warn('Could not save credentials to the OS keychain; you may need to re-login next time.');
44
+ }
45
+ // Emit only the (redacted) decoded claims — never the raw token.
46
+ const claims = redact(result.claims);
47
+ return this.respond(claims, (data) => this.log(JSON.stringify(data)));
48
+ }
49
+ }
@@ -0,0 +1,10 @@
1
+ import { BaseCommand } from '../../lib/command/base.js';
2
+ export default class AuthLogout extends BaseCommand<typeof AuthLogout> {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'no-revoke': import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ };
8
+ static summary: string;
9
+ run(): Promise<Record<string, unknown>>;
10
+ }
@@ -0,0 +1,51 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { fetchMetadata } from '../../lib/auth/discovery.js';
3
+ import { revokeToken } from '../../lib/auth/revoke.js';
4
+ import { BaseCommand } from '../../lib/command/base.js';
5
+ import { forgetHost } from '../../lib/config/xdg.js';
6
+ import { createStore } from '../../lib/store/index.js';
7
+ export default class AuthLogout extends BaseCommand {
8
+ static description = 'Log out of Flipstream: revoke the token (best-effort) and clear the keychain.';
9
+ static examples = ['<%= config.bin %> auth logout', '<%= config.bin %> auth logout --no-revoke'];
10
+ static flags = {
11
+ 'no-revoke': Flags.boolean({ description: 'Clear local credentials only; skip the server revocation call.' }),
12
+ };
13
+ static summary = 'Log out: revoke the token (best-effort) and clear the keychain.';
14
+ async run() {
15
+ const host = this.resolvedHost();
16
+ const store = createStore();
17
+ const creds = store.load(host);
18
+ if (!creds) {
19
+ return this.respond({ host, loggedOut: true, revoked: false }, () => this.log(`Not logged in to ${host}; nothing to do.`));
20
+ }
21
+ let revoked = false;
22
+ if (!this.flags['no-revoke']) {
23
+ const timeoutMs = this.flags.timeout ?? 10_000;
24
+ try {
25
+ const meta = await fetchMetadata(host, { timeoutMs });
26
+ const token = creds.refreshToken || creds.accessToken;
27
+ if (token) {
28
+ revoked = await revokeToken({
29
+ clientId: creds.clientId,
30
+ revocationEndpoint: meta.revocationEndpoint,
31
+ timeoutMs,
32
+ token,
33
+ tokenTypeHint: creds.refreshToken ? 'refresh_token' : 'access_token',
34
+ });
35
+ }
36
+ }
37
+ catch {
38
+ // Discovery/network failure — clear local credentials anyway so the user
39
+ // is logged out locally even when the server cannot be reached.
40
+ }
41
+ }
42
+ // Always clear local credentials, even if revocation could not be confirmed.
43
+ store.clear(host);
44
+ forgetHost(host);
45
+ return this.respond({ host, loggedOut: true, revoked }, () => {
46
+ this.log(`Logged out of ${host}.`);
47
+ if (!this.flags['no-revoke'] && !revoked)
48
+ this.log(' (server token revocation could not be confirmed)');
49
+ });
50
+ }
51
+ }
@@ -0,0 +1,8 @@
1
+ import { BaseCommand } from '../../lib/command/base.js';
2
+ export default class AuthStatus extends BaseCommand<typeof AuthStatus> {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {};
6
+ static summary: string;
7
+ run(): Promise<Record<string, unknown>>;
8
+ }
@@ -0,0 +1,47 @@
1
+ import { tokenClaims } from '../../lib/auth/claims.js';
2
+ import { BaseCommand } from '../../lib/command/base.js';
3
+ import { ExitCode } from '../../lib/exit-codes.js';
4
+ import { redact } from '../../lib/output/redact.js';
5
+ import { createStore } from '../../lib/store/index.js';
6
+ export default class AuthStatus extends BaseCommand {
7
+ static description = 'Show the current Flipstream authentication status for a host.';
8
+ static examples = ['<%= config.bin %> auth status', '<%= config.bin %> auth status --json'];
9
+ static flags = {};
10
+ static summary = 'Show whether you are logged in, and your account, scopes, and expiry.';
11
+ async run() {
12
+ const host = this.resolvedHost();
13
+ const store = createStore();
14
+ if (!store.available()) {
15
+ process.stderr.write('The OS keychain is unavailable (e.g. a headless/SSH session). Browser login needs it; ' +
16
+ 'a headless client_credentials / service-account path is PLANNED.\n');
17
+ return this.exit(ExitCode.NETWORK);
18
+ }
19
+ const creds = store.load(host);
20
+ if (!creds) {
21
+ return this.respond({ host, loggedIn: false }, () => this.log(`Not logged in to ${host}. Run \`flipstream auth login\`.`));
22
+ }
23
+ const claims = tokenClaims(host, store) ?? {};
24
+ // JSON keeps the stable subject id (agent-friendly); the human line prefers
25
+ // the friendlier email when present.
26
+ const account = [claims.sub, claims.email].find((value) => typeof value === 'string') ?? null;
27
+ const displayAccount = [claims.email, claims.sub].find((value) => typeof value === 'string') ?? account;
28
+ const nowSeconds = Math.floor(Date.now() / 1000);
29
+ const expired = creds.expiresAt <= nowSeconds;
30
+ const expiresAtIso = new Date(creds.expiresAt * 1000).toISOString();
31
+ const status = redact({
32
+ account,
33
+ expired,
34
+ expiresAt: expiresAtIso,
35
+ host,
36
+ loggedIn: true,
37
+ scopes: creds.scopes,
38
+ });
39
+ return this.respond(status, () => {
40
+ const relative = expired ? 'expired' : `expires in ${Math.round((creds.expiresAt - nowSeconds) / 3600)}h`;
41
+ this.log(`Logged in to ${host}`);
42
+ this.log(` account: ${typeof displayAccount === 'string' ? displayAccount : 'unknown'}`);
43
+ this.log(` expires: ${expiresAtIso} (${relative})`);
44
+ this.log(` scopes: ${creds.scopes.join(' ')}`);
45
+ });
46
+ }
47
+ }
@@ -0,0 +1,19 @@
1
+ import { BaseCommand } from '../../lib/command/base.js';
2
+ export default class ConnectionsList extends BaseCommand<typeof ConnectionsList> {
3
+ static aliases: string[];
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
10
+ offset: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
+ q: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ sort: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ };
14
+ static summary: string;
15
+ run(): Promise<{
16
+ count: number;
17
+ records: unknown[];
18
+ }>;
19
+ }
@@ -0,0 +1,43 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { fetchConnections, resolveWorkspaceNames } from '../../lib/api/connections.js';
3
+ import { assertUuid } from '../../lib/api/ids.js';
4
+ import { authedAdminClient, renderConnectionsTable } from '../../lib/command/admin.js';
5
+ import { BaseCommand } from '../../lib/command/base.js';
6
+ import { paginationFlags } from '../../lib/command/flags.js';
7
+ import { AuthRequiredError } from '../../lib/errors.js';
8
+ import { createStore } from '../../lib/store/index.js';
9
+ export default class ConnectionsList extends BaseCommand {
10
+ static aliases = ['conn:list'];
11
+ static description = 'List connections (data sources) — org-wide, or for one workspace via --workspace (or `ws connections <id>`). ' +
12
+ 'Returns each connection id + source metadata; secret_id / parameters are redacted. Targets the OAuth/admin host.';
13
+ static examples = [
14
+ '<%= config.bin %> connections list',
15
+ '<%= config.bin %> connections list --workspace <id> --json',
16
+ '<%= config.bin %> conn list --all',
17
+ ];
18
+ static flags = {
19
+ ...paginationFlags,
20
+ workspace: Flags.string({ description: "List only this workspace's connections (UUID)." }),
21
+ };
22
+ static summary = 'List connections, org-wide or for one workspace.';
23
+ async run() {
24
+ const { workspace } = this.flags;
25
+ if (workspace !== undefined)
26
+ assertUuid(workspace, 'workspace id'); // exit 2, no request
27
+ const host = this.resolvedHost();
28
+ const store = createStore();
29
+ if (!store.load(host))
30
+ throw AuthRequiredError.notLoggedIn();
31
+ const client = authedAdminClient(host, store, this.flags.timeout);
32
+ const projected = await fetchConnections(client, {
33
+ all: this.flags.all,
34
+ limit: this.flags.limit,
35
+ offset: this.flags.offset,
36
+ q: this.flags.q,
37
+ sort: this.flags.sort,
38
+ workspace,
39
+ });
40
+ await resolveWorkspaceNames(client, projected.records);
41
+ return this.respondList(projected, (records) => renderConnectionsTable(records));
42
+ }
43
+ }
@@ -0,0 +1,13 @@
1
+ import { BaseCommand } from '../lib/command/base.js';
2
+ export default class Query extends BaseCommand<typeof Query> {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'auth-host': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ body: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ 'body-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ static summary: string;
11
+ run(): Promise<unknown>;
12
+ private parseBody;
13
+ }
@@ -0,0 +1,76 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { readFileSync } from 'node:fs';
3
+ import { mapDataError } from '../lib/api/errors.js';
4
+ import { createAuthedDataClient } from '../lib/api/retry.js';
5
+ import { refresh } from '../lib/auth/refresh.js';
6
+ import { BaseCommand } from '../lib/command/base.js';
7
+ import { DATA_HOST } from '../lib/config/constants.js';
8
+ import { resolveHost } from '../lib/config/xdg.js';
9
+ import { AuthRequiredError, UsageError } from '../lib/errors.js';
10
+ import { redact } from '../lib/output/redact.js';
11
+ import { createStore } from '../lib/store/index.js';
12
+ export default class Query extends BaseCommand {
13
+ static description = 'Send an OPAQUE JSON body (--body, --body-file, or piped stdin) to the Flipstream data API and print the ' +
14
+ 'structured JSON response. The CLI never constructs, validates, or transforms the query payload — the body is ' +
15
+ 'forwarded verbatim (your agent/Skill builds it).';
16
+ static examples = [
17
+ '<%= config.bin %> query --body \'{"opaque":true}\'',
18
+ 'echo \'{"opaque":true}\' | <%= config.bin %> query',
19
+ '<%= config.bin %> query --body-file query.json --json',
20
+ ];
21
+ static flags = {
22
+ 'auth-host': Flags.string({
23
+ description: 'OAuth issuer host for credentials/refresh (defaults to the prod issuer).',
24
+ }),
25
+ body: Flags.string({ description: 'Opaque JSON request body as a string.', exclusive: ['body-file'] }),
26
+ 'body-file': Flags.string({ description: 'Path to a file holding the opaque JSON request body.' }),
27
+ };
28
+ static summary = 'Run an opaque query against the Flipstream data API.';
29
+ async run() {
30
+ // --host overrides the DATA query target; --auth-host the issuer holding creds.
31
+ const dataHost = this.flags.host ?? DATA_HOST;
32
+ const authHost = resolveHost({ hostFlag: this.flags['auth-host'] });
33
+ const store = createStore();
34
+ // Distinguish never-logged-in (no creds) from a later expired/refresh-rejected
35
+ // session (the wrapper raises session_expired on a null refresh).
36
+ if (!store.load(authHost))
37
+ throw AuthRequiredError.notLoggedIn();
38
+ const body = this.parseBody();
39
+ const client = createAuthedDataClient({
40
+ accessTokenIfFresh: (host) => store.accessTokenIfFresh(host),
41
+ authHost,
42
+ host: dataHost,
43
+ refresh: (host) => refresh(host, { store, timeoutMs: this.flags.timeout }),
44
+ timeoutMs: this.flags.timeout,
45
+ });
46
+ let response;
47
+ try {
48
+ response = await client.postQuery(body, { timeoutMs: this.flags.timeout });
49
+ }
50
+ catch (error) {
51
+ throw mapDataError(error);
52
+ }
53
+ // Redact known secret KEY NAMES (E3-4) so a token-like key echoed by the DATA
54
+ // host never reaches --json/NDJSON/human output.
55
+ const safe = redact(response);
56
+ return this.respond(safe, (data) => this.log(JSON.stringify(data, null, 2)));
57
+ }
58
+ // Forward the body VERBATIM: parse-only, no schema and no field handling.
59
+ parseBody() {
60
+ let raw;
61
+ if (this.flags.body !== undefined)
62
+ raw = this.flags.body;
63
+ else if (this.flags['body-file'] !== undefined)
64
+ raw = readFileSync(this.flags['body-file'], 'utf8');
65
+ else if (process.stdin.isTTY)
66
+ throw new UsageError('Provide a body via --body, --body-file <path>, or piped stdin.');
67
+ else
68
+ raw = readFileSync(0, 'utf8');
69
+ try {
70
+ return JSON.parse(raw);
71
+ }
72
+ catch {
73
+ throw new UsageError('Request body is not valid JSON.');
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,15 @@
1
+ import { BaseCommand } from '../lib/command/base.js';
2
+ interface VersionInfo {
3
+ contractVersion: string;
4
+ node: string;
5
+ platform: string;
6
+ version: string;
7
+ }
8
+ export default class Version extends BaseCommand<typeof Version> {
9
+ static description: string;
10
+ static examples: string[];
11
+ static flags: {};
12
+ static summary: string;
13
+ run(): Promise<VersionInfo>;
14
+ }
15
+ export {};
@@ -0,0 +1,17 @@
1
+ import { BaseCommand } from '../lib/command/base.js';
2
+ import { CONTRACT_VERSION } from '../lib/config/constants.js';
3
+ export default class Version extends BaseCommand {
4
+ static description = 'Print the CLI version, runtime, and agent-contract version as JSON.';
5
+ static examples = ['<%= config.bin %> version', '<%= config.bin %> version --json'];
6
+ static flags = {};
7
+ static summary = 'Print version, runtime, and agent-contract version.';
8
+ async run() {
9
+ const info = {
10
+ contractVersion: CONTRACT_VERSION,
11
+ node: process.versions.node,
12
+ platform: process.platform,
13
+ version: this.config.version,
14
+ };
15
+ return this.respond(info, (data) => this.log(JSON.stringify(data)));
16
+ }
17
+ }
@@ -0,0 +1,21 @@
1
+ import { BaseCommand } from '../../lib/command/base.js';
2
+ export default class WorkspacesConnections extends BaseCommand<typeof WorkspacesConnections> {
3
+ static aliases: string[];
4
+ static args: {
5
+ id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static description: string;
8
+ static examples: string[];
9
+ static flags: {
10
+ all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
12
+ offset: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
13
+ q: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ sort: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ };
16
+ static summary: string;
17
+ run(): Promise<{
18
+ count: number;
19
+ records: unknown[];
20
+ }>;
21
+ }
@@ -0,0 +1,41 @@
1
+ import { Args } from '@oclif/core';
2
+ import { fetchConnections, resolveWorkspaceNames } from '../../lib/api/connections.js';
3
+ import { assertUuid } from '../../lib/api/ids.js';
4
+ import { authedAdminClient, renderConnectionsTable } from '../../lib/command/admin.js';
5
+ import { BaseCommand } from '../../lib/command/base.js';
6
+ import { paginationFlags } from '../../lib/command/flags.js';
7
+ import { AuthRequiredError } from '../../lib/errors.js';
8
+ import { createStore } from '../../lib/store/index.js';
9
+ export default class WorkspacesConnections extends BaseCommand {
10
+ static aliases = ['ws:connections'];
11
+ static args = {
12
+ id: Args.string({ description: 'Workspace id (UUID).', required: true }),
13
+ };
14
+ static description = "List one workspace's connections — the ergonomic form of `connections list --workspace <id>`. " +
15
+ 'Targets the OAuth/admin host.';
16
+ static examples = [
17
+ '<%= config.bin %> workspaces connections <id>',
18
+ '<%= config.bin %> ws connections <id> --json',
19
+ '<%= config.bin %> ws connections <id> --all',
20
+ ];
21
+ static flags = { ...paginationFlags };
22
+ static summary = "List one workspace's connections.";
23
+ async run() {
24
+ assertUuid(this.args.id, 'workspace id'); // exit 2, no request
25
+ const host = this.resolvedHost();
26
+ const store = createStore();
27
+ if (!store.load(host))
28
+ throw AuthRequiredError.notLoggedIn();
29
+ const client = authedAdminClient(host, store, this.flags.timeout);
30
+ const projected = await fetchConnections(client, {
31
+ all: this.flags.all,
32
+ limit: this.flags.limit,
33
+ offset: this.flags.offset,
34
+ q: this.flags.q,
35
+ sort: this.flags.sort,
36
+ workspace: this.args.id,
37
+ });
38
+ await resolveWorkspaceNames(client, projected.records);
39
+ return this.respondList(projected, (records) => renderConnectionsTable(records));
40
+ }
41
+ }
@@ -0,0 +1,15 @@
1
+ import { BaseCommand } from '../../lib/command/base.js';
2
+ export default class WorkspacesGet extends BaseCommand<typeof WorkspacesGet> {
3
+ static aliases: string[];
4
+ static args: {
5
+ id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static description: string;
8
+ static examples: string[];
9
+ static flags: {
10
+ refresh: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ 'with-connection-count': import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
+ };
13
+ static summary: string;
14
+ run(): Promise<unknown>;
15
+ }
@@ -0,0 +1,65 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { createAuthedAdminClient } from '../../lib/api/admin-client.js';
3
+ import { mapDataError, notFoundError } from '../../lib/api/errors.js';
4
+ import { assertUuid } from '../../lib/api/ids.js';
5
+ import { normalizeList } from '../../lib/api/list.js';
6
+ import { projectWorkspace } from '../../lib/api/projections.js';
7
+ import { refresh } from '../../lib/auth/refresh.js';
8
+ import { BaseCommand } from '../../lib/command/base.js';
9
+ import { AuthRequiredError, DataHttpError } from '../../lib/errors.js';
10
+ import { createStore } from '../../lib/store/index.js';
11
+ export default class WorkspacesGet extends BaseCommand {
12
+ static aliases = ['ws:get'];
13
+ static args = {
14
+ id: Args.string({ description: 'Workspace id (UUID).', required: true }),
15
+ };
16
+ static description = 'Fetch one workspace by id (the admin API "Client"). A foreign or missing id collapses to a single ' +
17
+ "not-found message (no existence leak). Targets the OAuth/admin host.";
18
+ static examples = [
19
+ '<%= config.bin %> workspaces get <id>',
20
+ '<%= config.bin %> ws get <id> --json',
21
+ '<%= config.bin %> ws get <id> --with-connection-count',
22
+ ];
23
+ static flags = {
24
+ refresh: Flags.boolean({ description: 'Ask the server to refresh derived data (refresh=true).' }),
25
+ 'with-connection-count': Flags.boolean({
26
+ description: 'Include connection_count (one extra request to the connections endpoint).',
27
+ }),
28
+ };
29
+ static summary = 'Show one workspace by id.';
30
+ async run() {
31
+ assertUuid(this.args.id, 'workspace id'); // exit 2, no request issued
32
+ const host = this.resolvedHost();
33
+ const store = createStore();
34
+ if (!store.load(host))
35
+ throw AuthRequiredError.notLoggedIn();
36
+ const client = createAuthedAdminClient({
37
+ accessTokenIfFresh: (authHost) => store.accessTokenIfFresh(authHost),
38
+ authHost: host,
39
+ host,
40
+ refresh: (authHost) => refresh(authHost, { store, timeoutMs: this.flags.timeout }),
41
+ timeoutMs: this.flags.timeout,
42
+ });
43
+ let raw;
44
+ try {
45
+ raw = await client.get(`/clients/${this.args.id}`, { query: { refresh: this.flags.refresh || undefined } });
46
+ }
47
+ catch (error) {
48
+ // Collapse a foreign-id 403 and a 404 to ONE message so neither leaks existence.
49
+ if (error instanceof DataHttpError && (error.status === 403 || error.status === 404))
50
+ throw notFoundError();
51
+ throw mapDataError(error);
52
+ }
53
+ const projected = projectWorkspace(raw);
54
+ if (this.flags['with-connection-count']) {
55
+ try {
56
+ const conns = normalizeList(await client.get(`/clients/${this.args.id}/connections`, { query: { limit: 1 } }));
57
+ projected.connection_count = conns.count;
58
+ }
59
+ catch {
60
+ projected.connection_count = null; // best-effort; never fails the get
61
+ }
62
+ }
63
+ return this.respond(projected, (data) => this.log(JSON.stringify(data, null, 2)));
64
+ }
65
+ }
@@ -0,0 +1,20 @@
1
+ import { BaseCommand } from '../../lib/command/base.js';
2
+ export default class WorkspacesList extends BaseCommand<typeof WorkspacesList> {
3
+ static aliases: string[];
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ 'with-connection-counts': import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
10
+ offset: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
11
+ q: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ sort: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ };
14
+ static summary: string;
15
+ run(): Promise<{
16
+ count: number;
17
+ records: unknown[];
18
+ }>;
19
+ private addConnectionCounts;
20
+ }