synomem 0.7.1 → 0.8.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.
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import { type DiscoveredWorkspace } from './discover.js';
3
+ import { type DiscoveredWorkspace, type WorkspaceMembership } from './discover.js';
4
4
  import { type PromptIo } from './prompt.js';
5
5
  import { type CredentialStore } from './credentials.js';
6
6
  import { type OAuthLoginOptions } from './oauth.js';
@@ -31,6 +31,14 @@ export interface CliDependencies {
31
31
  organizationId: string;
32
32
  workspaces: DiscoveredWorkspace[];
33
33
  }>;
34
+ /** Injected so `remote workspace list` can be tested without a network. */
35
+ discoverIdentity?: (options: {
36
+ baseUrl: string;
37
+ accessToken: string;
38
+ }) => Promise<{
39
+ workspaceId: string;
40
+ workspaces: WorkspaceMembership[];
41
+ }>;
34
42
  createImportBundle?: (home: string) => Promise<ImportBundle>;
35
43
  remoteImport?: (options: {
36
44
  baseUrl: string;
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAKA,OAAO,EAAE,OAAO,EAA0B,MAAM,WAAW,CAAC;AAkB5D,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,eAAe,CAAC;AAOvB,OAAO,EAA2B,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAA0C,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAIhG,OAAO,EAA4C,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE9F,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AASrB,OAAO,KAAK,EACV,aAAa,EASd,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAkB,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1E,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE;QACjC,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,aAAa,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,eAAe,CAAC;KAClC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAKpB,2BAA2B,CAAC,EAAE,CAAC,OAAO,EAAE;QACtC,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,mBAAmB,EAAE,CAAA;KAAE,CAAC,CAAC;IAC7E,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7D,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,aAAa,CAAC;QACrB,MAAM,EAAE,YAAY,CAAC;QACrB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,KAAK,OAAO,CAAC,aAAa,GAAG,YAAY,CAAC,CAAC;CAC7C;AAuRD,wBAAgB,SAAS,CACvB,EAAE,GAAE,KAAiB,EACrB,cAAc,GAAE,qBAAgD,EAChE,YAAY,GAAE,eAAoB,EAClC,IAAI,GAAE,MAAM,EAAiB,GAC5B,OAAO,CAkyFT;AAED,wBAAsB,MAAM,CAC1B,IAAI,WAAe,EACnB,EAAE,GAAE,KAAiB,EACrB,cAAc,GAAE,qBAAgD,EAChE,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,MAAM,CAAC,CAqBjB"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAKA,OAAO,EAAE,OAAO,EAA0B,MAAM,WAAW,CAAC;AAkB5D,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACzB,MAAM,eAAe,CAAC;AAOvB,OAAO,EAA2B,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAA0C,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAIhG,OAAO,EAA4C,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE9F,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AASrB,OAAO,KAAK,EACV,aAAa,EASd,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAkB,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1E,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE;QACjC,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,aAAa,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,eAAe,CAAC;KAClC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAKpB,2BAA2B,CAAC,EAAE,CAAC,OAAO,EAAE;QACtC,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,mBAAmB,EAAE,CAAA;KAAE,CAAC,CAAC;IAC7E,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE;QAC3B,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,KAAK,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,mBAAmB,EAAE,CAAA;KAAE,CAAC,CAAC;IAC1E,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7D,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,aAAa,CAAC;QACrB,MAAM,EAAE,YAAY,CAAC;QACrB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,KAAK,OAAO,CAAC,aAAa,GAAG,YAAY,CAAC,CAAC;CAC7C;AAuRD,wBAAgB,SAAS,CACvB,EAAE,GAAE,KAAiB,EACrB,cAAc,GAAE,qBAAgD,EAChE,YAAY,GAAE,eAAoB,EAClC,IAAI,GAAE,MAAM,EAAiB,GAC5B,OAAO,CA81FT;AAED,wBAAsB,MAAM,CAC1B,IAAI,WAAe,EACnB,EAAE,GAAE,KAAiB,EACrB,cAAc,GAAE,qBAAgD,EAChE,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,MAAM,CAAC,CAqBjB"}
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { configuredServiceFactory, readSynomemConfig, writeSynomemBackend } from
8
8
  import { cloudApiUrl } from './cloud.js';
9
9
  import { resolveHome } from './config.js';
10
10
  import { assertInteractive, confirmPlan, credentialFingerprint, credentialStoreChoices, environmentInstructions, readAccessToken, runConfigWizard, writeCredentialFile, } from './configure.js';
11
- import { discoverAccessKeyWorkspaces, discoverOrganizations, workspaceChoices, } from './discover.js';
11
+ import { discoverAccessKeyWorkspaces, discoverIdentity, } from './discover.js';
12
12
  import { DEFAULT_WORKSPACE, listLocalWorkspaces, localWorkspaceHome } from './workspaces.js';
13
13
  import { findProjectSelection, resolveWorkspaceSelection, writeProjectSelection, } from './project.js';
14
14
  import { defaultPromptIo, select } from './prompt.js';
@@ -305,6 +305,7 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
305
305
  const credentialStore = dependencies.credentialStore ?? new OsCredentialStore();
306
306
  const oauthLogin = dependencies.oauthLogin ?? loginWithOAuth;
307
307
  const discoverWorkspaces = dependencies.discoverAccessKeyWorkspaces ?? discoverAccessKeyWorkspaces;
308
+ const discoverAccountIdentity = dependencies.discoverIdentity ?? discoverIdentity;
308
309
  const promptIo = dependencies.promptIo ?? defaultPromptIo();
309
310
  const verifyRemoteCredential = dependencies.verifyRemoteCredential ??
310
311
  (async (options) => {
@@ -410,17 +411,62 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
410
411
  output(io, global.json, { name, home }, `Created workspace ${name} at ${home}.\nAct in it with --workspace ${name}.`);
411
412
  });
412
413
  const remoteCommand = program.command('remote').description('Administer a remote workspace');
414
+ const remoteWorkspaceCommand = remoteCommand
415
+ .command('workspace')
416
+ .description('List or switch the workspace this credential addresses');
417
+ /*
418
+ * Every workspace this credential's account belongs to, and which are
419
+ * reachable right now with it — no new token needed for any workspace
420
+ * flagged reachable, since a human credential authorizes its whole
421
+ * organization, not permanently one workspace (see `workspace use`). Data
422
+ * plane (`/v1/identity`), not `/v1/me`: the latter is a first-party
423
+ * control-plane route gated by a service secret this CLI never holds, and
424
+ * would 401 unconditionally against the real hosted API.
425
+ */
426
+ remoteWorkspaceCommand
427
+ .command('list')
428
+ .description("List this credential's account's workspaces, and which are reachable now")
429
+ .option('--url <url>', 'internal: alternate HTTPS origin')
430
+ .action(async (options, command) => {
431
+ const global = globals(command);
432
+ const config = readSynomemConfig(global.home, env);
433
+ const baseUrl = options.url ??
434
+ (config?.backend.kind === 'remote' ? config.backend.baseUrl : cloudApiUrl(env));
435
+ const accessToken = env.SYNOMEM_ACCESS_TOKEN;
436
+ if (!accessToken) {
437
+ throw new SynomemError('AUTH_REQUIRED', 'Set SYNOMEM_ACCESS_TOKEN, or run `synomem auth login` first.');
438
+ }
439
+ const identity = await discoverAccountIdentity({ baseUrl, accessToken });
440
+ const human = identity.workspaces.length
441
+ ? identity.workspaces
442
+ .map((workspace) => `${workspace.id} ${workspace.displayName}` +
443
+ (workspace.addressableWithThisToken
444
+ ? ''
445
+ : ' (not reachable with this credential)'))
446
+ .join('\n')
447
+ : 'This credential belongs to no workspaces yet.';
448
+ output(io, global.json, identity, human);
449
+ });
413
450
  /*
414
- * The browser counterpart to `discoverAccessKeyWorkspaces` — same purpose,
415
- * different credential: a signed-in account may reach several
416
- * organizations, each with several workspaces, so there is a genuine choice
417
- * to make, and no way to make it without seeing the list. Printing the IDs
418
- * alongside the names is the point: the ID is what `backend use remote
419
- * --workspace` takes.
451
+ * A workspace flagged `addressableWithThisToken` by `workspace list` needs
452
+ * no new token or re-authentication — the credential already covers it.
453
+ * This only ever rewrites local config to point at it; see `backend use
454
+ * remote --workspace`, which this delegates to.
420
455
  */
456
+ remoteWorkspaceCommand
457
+ .command('use <workspace-id>')
458
+ .description('Address a different workspace with the current remote credential')
459
+ .option('--url <url>', 'internal: alternate HTTPS origin')
460
+ .action((workspaceId, options, command) => {
461
+ const global = globals(command);
462
+ const config = writeSynomemBackend({ kind: 'remote', baseUrl: options.url ?? cloudApiUrl(env), workspaceId }, global.home);
463
+ output(io, global.json, { backend: config.backend }, `Now addressing workspace ${workspaceId}.`);
464
+ });
465
+ // Deprecated alias for `remote workspace list`, kept working rather than
466
+ // breaking anyone who already scripted the old name.
421
467
  remoteCommand
422
468
  .command('workspaces')
423
- .description('List the organizations and workspaces this credential can reach')
469
+ .description('Deprecated: use `remote workspace list`')
424
470
  .option('--url <url>', 'internal: alternate HTTPS origin')
425
471
  .action(async (options, command) => {
426
472
  const global = globals(command);
@@ -431,19 +477,16 @@ export function createCli(io = defaultIo, serviceFactory = configuredServiceFact
431
477
  if (!accessToken) {
432
478
  throw new SynomemError('AUTH_REQUIRED', 'Set SYNOMEM_ACCESS_TOKEN, or run `synomem auth login` first.');
433
479
  }
434
- const organizations = await discoverOrganizations({ baseUrl, accessToken });
435
- const choices = workspaceChoices(organizations);
436
- const human = organizations.length
437
- ? organizations
438
- .map((organization) => [
439
- `${organization.displayName} (${organization.slug}) — ${organization.role}`,
440
- ...(organization.workspaces.length
441
- ? organization.workspaces.map((workspace) => ` ${workspace.id} ${workspace.displayName}`)
442
- : [' (no workspaces yet)']),
443
- ].join('\n'))
480
+ const identity = await discoverAccountIdentity({ baseUrl, accessToken });
481
+ const human = identity.workspaces.length
482
+ ? identity.workspaces
483
+ .map((workspace) => `${workspace.id} ${workspace.displayName}` +
484
+ (workspace.addressableWithThisToken
485
+ ? ''
486
+ : ' (not reachable with this credential)'))
444
487
  .join('\n')
445
- : 'This account belongs to no organizations yet.';
446
- output(io, global.json, { organizations, choices }, human);
488
+ : 'This credential belongs to no workspaces yet.';
489
+ output(io, global.json, identity, human);
447
490
  });
448
491
  remoteCommand
449
492
  .command('import')