@sellable/install 0.1.216 → 0.1.217

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
@@ -35,8 +35,9 @@ Campaign creation, foundation memory, content capture/ideation, and post
35
35
  drafting run inside Claude Code or Codex, where the Sellable MCP tools and
36
36
  approval flows are available.
37
37
 
38
- Install is auth-free by default. If you do not pass a token, the agent handles
39
- Sellable sign-in on the first campaign run with a magic-link handoff.
38
+ Install is auth-free by default. The normal path is first-run login: launch a
39
+ Sellable workflow in Claude Code or Codex and the agent handles Sellable
40
+ sign-in with a browser magic-link handoff.
40
41
 
41
42
  The installer uses package stdio MCP by default:
42
43
 
@@ -64,14 +65,15 @@ verification in one path:
64
65
  curl -fsSL "https://app.sellable.dev/api/v2/cli/install" | sh
65
66
  ```
66
67
 
67
- For CI/scripted installs, get a Sellable API token from:
68
+ For scripted fallback after browser login, paste the command shown by Sellable:
68
69
 
69
- ```text
70
- https://app.sellable.dev/settings
70
+ ```bash
71
+ sellable auth set <token> --workspace-id <workspace_id>
71
72
  ```
72
73
 
73
- Then pass it with `--token` / `SELLABLE_TOKEN` plus `--workspace-id` /
74
- `SELLABLE_WORKSPACE_ID`.
74
+ For CI/env-only installs, operators can still pass `--token` / `SELLABLE_TOKEN`
75
+ plus `--workspace-id` / `SELLABLE_WORKSPACE_ID`. Do not use env vars as the
76
+ primary human setup path.
75
77
 
76
78
  Auth is stored once at:
77
79
 
@@ -419,13 +419,26 @@ async function loadAuthIfPresent(opts) {
419
419
  return opts;
420
420
  }
421
421
 
422
+ function redactTokensForLog(value) {
423
+ if (Array.isArray(value)) {
424
+ return value.map((item) => redactTokensForLog(item));
425
+ }
426
+ if (value && typeof value === "object") {
427
+ return Object.fromEntries(
428
+ Object.entries(value).map(([key, item]) => [
429
+ key,
430
+ key.toLowerCase().includes("token") && typeof item === "string"
431
+ ? redact(item)
432
+ : redactTokensForLog(item),
433
+ ])
434
+ );
435
+ }
436
+ return value;
437
+ }
438
+
422
439
  function writeJson(path, data, opts) {
423
440
  if (VERBOSE) {
424
- const redacted = JSON.stringify(
425
- { ...data, token: redact(data.token) },
426
- null,
427
- 2
428
- );
441
+ const redacted = JSON.stringify(redactTokensForLog(data), null, 2);
429
442
  logVerbose(`${C.grey}Writing ${path}: ${redacted}${C.reset}`);
430
443
  }
431
444
  if (opts.dryRun) return;
@@ -442,6 +455,56 @@ function readExisting(path) {
442
455
  }
443
456
  }
444
457
 
458
+ function mergeAuthConfig(raw, auth) {
459
+ const existing =
460
+ raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
461
+ const authFields = {
462
+ token: auth.token,
463
+ activeWorkspaceId: auth.activeWorkspaceId,
464
+ apiUrl: auth.apiUrl,
465
+ };
466
+
467
+ if (existing.activeEnv && existing.environments) {
468
+ const activeEnv = existing.activeEnv;
469
+ const envConfig = existing.environments[activeEnv];
470
+ if (
471
+ !envConfig ||
472
+ typeof envConfig !== "object" ||
473
+ Array.isArray(envConfig)
474
+ ) {
475
+ throw new Error(
476
+ `Unknown active environment '${activeEnv}' in ${authPath()}`
477
+ );
478
+ }
479
+ return {
480
+ ...existing,
481
+ environments: {
482
+ ...existing.environments,
483
+ [activeEnv]: {
484
+ ...envConfig,
485
+ ...authFields,
486
+ },
487
+ },
488
+ };
489
+ }
490
+
491
+ return {
492
+ ...existing,
493
+ ...authFields,
494
+ };
495
+ }
496
+
497
+ function writeAuthSetConfig({ token, workspaceId, apiUrl, dryRun }) {
498
+ const configPath = authPath();
499
+ const raw = readExisting(configPath) || {};
500
+ const config = mergeAuthConfig(raw, {
501
+ token,
502
+ activeWorkspaceId: workspaceId || null,
503
+ apiUrl,
504
+ });
505
+ writeJson(configPath, config, { dryRun });
506
+ }
507
+
445
508
  function sellableHostEnvPath() {
446
509
  return join(homedir(), ".local", "sellable", "app-sellable-dev", ".env");
447
510
  }
@@ -3665,18 +3728,31 @@ async function main() {
3665
3728
  }
3666
3729
  const token = rawArgs[2];
3667
3730
  const authSetFlags = rawArgs.slice(3);
3668
- const dryRun = authSetFlags.includes("--dry-run");
3669
- const unknownAuthSetFlag = authSetFlags.find(
3670
- (arg) => arg !== "--dry-run"
3671
- );
3672
- if (unknownAuthSetFlag) {
3673
- console.error(`Unknown auth set option: ${unknownAuthSetFlag}`);
3731
+ let dryRun = false;
3732
+ let workspaceId = "";
3733
+ for (let i = 0; i < authSetFlags.length; i += 1) {
3734
+ const flag = authSetFlags[i];
3735
+ if (flag === "--dry-run") {
3736
+ dryRun = true;
3737
+ continue;
3738
+ }
3739
+ if (flag === "--workspace-id") {
3740
+ const value = authSetFlags[i + 1];
3741
+ if (!value || value.startsWith("--")) {
3742
+ console.error("Missing value for --workspace-id");
3743
+ process.exit(2);
3744
+ }
3745
+ workspaceId = value;
3746
+ i += 1;
3747
+ continue;
3748
+ }
3749
+ console.error(`Unknown auth set option: ${flag}`);
3674
3750
  process.exit(2);
3675
3751
  }
3676
3752
  if (!token) {
3677
3753
  console.error(
3678
- "Usage: sellable auth set <token>\n" +
3679
- "Get the token from the Sellable browser confirm page (after clicking the magic link)."
3754
+ "Usage: sellable auth set <token> [--workspace-id <id>]\n" +
3755
+ "Use this only when the Sellable browser login page shows a manual fallback command."
3680
3756
  );
3681
3757
  process.exit(2);
3682
3758
  }
@@ -3688,23 +3764,22 @@ async function main() {
3688
3764
  );
3689
3765
  process.exit(2);
3690
3766
  }
3691
- // Bypass writeAuth() its workspaceId guard early-returns on missing
3692
- // workspaceId, but on the auth-set path we don't have one yet (next MCP
3693
- // call hydrates it). Write the same shape directly.
3767
+ // Bypass writeAuth() because auth-set is a first-run browser fallback:
3768
+ // the workspace id is optional for legacy fallback pages, and existing
3769
+ // config metadata must be preserved.
3694
3770
  // Skip installSelfShim() — by definition the user has the shim already
3695
3771
  // (they invoked `sellable auth set` from it).
3696
3772
  const apiUrl = process.env.SELLABLE_API_URL || DEFAULT_API_URL;
3697
- writeJson(
3698
- authPath(),
3699
- { token, activeWorkspaceId: null, apiUrl },
3700
- { dryRun }
3701
- );
3773
+ writeAuthSetConfig({ token, workspaceId, apiUrl, dryRun });
3702
3774
  if (dryRun) {
3703
3775
  console.log(`Dry run: token would be saved to ${authPath()}`);
3704
3776
  } else {
3705
3777
  console.log(`✓ Token saved to ${authPath()}`);
3706
3778
  }
3707
3779
  console.log(` apiUrl: ${apiUrl}`);
3780
+ if (workspaceId) {
3781
+ console.log(` activeWorkspaceId: ${workspaceId}`);
3782
+ }
3708
3783
  console.log(` Continue in your agent:`);
3709
3784
  console.log(` Claude Code: /sellable:create-campaign`);
3710
3785
  console.log(` Claude Code: /sellable:foundation`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.216",
3
+ "version": "0.1.217",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code and Codex",
6
6
  "bin": {
@@ -893,7 +893,11 @@ updates.
893
893
 
894
894
  1. Call `mcp__sellable__get_auth_status({})`.
895
895
  2. If auth is not OK with `error.type === "config"` or `error.type === "auth"`,
896
- the user has not signed in yet. Run the FTUX magic-link handoff:
896
+ the user has not signed in yet. Run first-run login through the FTUX
897
+ magic-link handoff. If a browser page or tool guidance gives the user a
898
+ manual fallback, it must be
899
+ `sellable auth set <token> --workspace-id <workspace_id>`. Do not instruct
900
+ the user to hand-edit JSON auth config.
897
901
 
898
902
  a. Say to the user verbatim:
899
903