@namiml/cli 3.4.11-dev.202609222222 → 3.4.11-dev.202609222238

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.
@@ -14,7 +14,13 @@ import { AuthError } from "../auth/errors.js";
14
14
  export function LoginFlow({ auth, store, host, totpCode, org, onDone, onFail, exitWhenDone = true, phase }) {
15
15
  const { exit } = useApp();
16
16
  const [step, setStep] = useState({ kind: "email" });
17
- const [draft, setDraft] = useState("");
17
+ // The draft is scoped to the step that typed it: whatever the ordering of the
18
+ // state updates on a step change, one prompt's text can never seed the next.
19
+ const [draft, setDraft] = useState({ step: "email", text: "" });
20
+ const text = draft.step === step.kind ? draft.text : "";
21
+ const setText = (next) => setDraft({ step: step.kind, text: next });
22
+ /** Drop the typed text immediately — a password must not linger in state. */
23
+ const clearDraft = () => setDraft({ step: "busy", text: "" });
18
24
  const fail = (error) => {
19
25
  const message = error instanceof Error ? error.message : String(error);
20
26
  setStep({ kind: "failed", message });
@@ -56,7 +62,7 @@ export function LoginFlow({ auth, store, host, totpCode, org, onDone, onFail, ex
56
62
  setStep({ kind: "org", signedIn });
57
63
  };
58
64
  const submitPassword = async (email, password) => {
59
- setDraft("");
65
+ clearDraft();
60
66
  setStep({ kind: "busy", label: "Signing in…" });
61
67
  try {
62
68
  const result = await auth.login(email, password);
@@ -74,7 +80,7 @@ export function LoginFlow({ auth, store, host, totpCode, org, onDone, onFail, ex
74
80
  }
75
81
  };
76
82
  const submitCode = async (preAuthToken, code) => {
77
- setDraft("");
83
+ clearDraft();
78
84
  setStep({ kind: "busy", label: "Checking your code…" });
79
85
  try {
80
86
  chooseOrg(await auth.confirmMfa(preAuthToken, code));
@@ -90,10 +96,10 @@ export function LoginFlow({ auth, store, host, totpCode, org, onDone, onFail, ex
90
96
  if (exitWhenDone && (step.kind === "done" || step.kind === "failed"))
91
97
  exit();
92
98
  }, [step.kind, exitWhenDone, exit]);
93
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Sign in to Nami" }), _jsx(Text, { dimColor: true, children: host.baseUrl }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [step.kind === "email" && (_jsxs(Box, { children: [_jsx(Text, { children: "Email: " }), _jsx(TextInput, { value: draft, onChange: setDraft, onSubmit: (v) => { const email = v.trim(); if (email) {
94
- setDraft("");
99
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Sign in to Nami" }), _jsx(Text, { dimColor: true, children: host.baseUrl }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [step.kind === "email" && (_jsxs(Box, { children: [_jsx(Text, { children: "Email: " }), _jsx(TextInput, { value: text, onChange: setText, onSubmit: (v) => { const email = v.trim(); if (email) {
100
+ clearDraft();
95
101
  setStep({ kind: "password", email });
96
- } } })] })), step.kind === "password" && (_jsxs(Box, { flexDirection: "column", children: [step.notice && _jsx(Text, { color: palette.aqua, children: step.notice }), _jsxs(Box, { children: [_jsx(Text, { children: "Password: " }), _jsx(TextInput, { value: draft, onChange: setDraft, mask: "*", onSubmit: (v) => { if (v)
97
- void submitPassword(step.email, v); } })] })] })), step.kind === "mfa" && (_jsxs(Box, { flexDirection: "column", children: [step.notice && _jsx(Text, { color: palette.aqua, children: step.notice }), _jsxs(Box, { children: [_jsx(Text, { children: "Authenticator code: " }), _jsx(TextInput, { value: draft, onChange: setDraft, onSubmit: (v) => { const code = v.trim(); if (code)
102
+ } } })] })), step.kind === "password" && (_jsxs(Box, { flexDirection: "column", children: [step.notice && _jsx(Text, { color: palette.aqua, children: step.notice }), _jsxs(Box, { children: [_jsx(Text, { children: "Password: " }), _jsx(TextInput, { value: text, onChange: setText, mask: "*", onSubmit: (v) => { if (v)
103
+ void submitPassword(step.email, v); } })] })] })), step.kind === "mfa" && (_jsxs(Box, { flexDirection: "column", children: [step.notice && _jsx(Text, { color: palette.aqua, children: step.notice }), _jsxs(Box, { children: [_jsx(Text, { children: "Authenticator code: " }), _jsx(TextInput, { value: text, onChange: setText, onSubmit: (v) => { const code = v.trim(); if (code)
98
104
  void submitCode(step.preAuthToken, code); } })] })] })), step.kind === "org" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Choose an organization:" }), _jsx(SelectInput, { items: step.signedIn.profile.orgs.map((o) => ({ key: o.id, label: o.name, value: o.id })), initialIndex: Math.max(0, step.signedIn.profile.orgs.findIndex((o) => o.id === step.signedIn.profile.defaultOrgId)), onSelect: (item) => { const chosen = step.signedIn.profile.orgs.find((o) => o.id === item.value); void finish(step.signedIn, chosen); } })] })), step.kind === "busy" && _jsx(Thinking, { label: step.label, phase: phase }), step.kind === "done" && (_jsxs(Text, { color: palette.aqua, children: ["Signed in", step.session.orgName ? ` to ${step.session.orgName}` : "", " on ", step.session.host, "."] })), step.kind === "failed" && _jsxs(Text, { color: palette.coral, children: ["Sign-in failed: ", step.message] })] })] }));
99
105
  }
@@ -5,6 +5,7 @@ import { LoginFlow } from "./LoginFlow.js";
5
5
  import { PickApp } from "./PickApp.js";
6
6
  import { PickPlatform } from "./PickPlatform.js";
7
7
  import { PickDevice } from "./PickDevice.js";
8
+ import { SuccessScreen } from "./SuccessScreen.js";
8
9
  import { deviceById, presetsFor, resolveDevice, toSelection } from "../devices/presets.js";
9
10
  import { SelectionHeader } from "./SelectionHeader.js";
10
11
  import { Thinking } from "../components/Thinking.js";
@@ -131,6 +132,8 @@ export function Shell({ ctx, appQuery, platformQuery, deviceQuery, onExit, exitW
131
132
  return void loadPlatforms(state.app, state.platform);
132
133
  if (input === "d" && presetsFor(state.platform.type).length > 1)
133
134
  return setState({ kind: "pick-device", app: state.app, platform: state.platform, current: state.device ?? undefined });
135
+ if (input === "l")
136
+ return setState({ ...state, launchHint: true });
134
137
  }, { isActive: state.kind === "home" });
135
138
  const header = (_jsx(SelectionHeader, { orgName: orgName, appName: "app" in state ? state.app.name : undefined, platformName: "platform" in state ? state.platform.name : undefined, platformType: "platform" in state ? state.platform.typeLabel : undefined, deviceName: "device" in state ? state.device?.displayName : undefined, host: ctx.host.name }));
136
139
  return (_jsxs(Box, { flexDirection: "column", children: [header, _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [state.kind === "boot" && _jsx(Thinking, { label: "Starting\u2026", phase: phase }), state.kind === "signin" && (_jsxs(Box, { flexDirection: "column", children: [state.notice && _jsx(Text, { color: palette.aqua, children: state.notice }), _jsx(LoginFlow, { auth: ctx.auth, store: ctx.store, host: ctx.host, exitWhenDone: false, phase: phase, onDone: (session) => { setOrgName(session.orgName); setOrgId(session.orgId); void loadApps(); }, onFail: (e) => fatal(e.message) })] })), state.kind === "busy" && _jsx(Thinking, { label: state.label, phase: phase }), state.kind === "pick-app" && (_jsx(PickApp, { apps: state.apps, orgName: orgName, baseUrl: ctx.host.baseUrl, currentAppId: state.current?.id, onSelect: (app) => void loadPlatforms(app), onCancel: () => finish(0) })), state.kind === "pick-platform" && (_jsx(PickPlatform, { appName: state.app.name, platforms: state.platforms, currentPlatformId: state.current?.id, onSelect: (platform) => chooseDevice(state.app, platform), onCancel: () => void loadApps(state.app) })), state.kind === "pick-device" && (_jsx(PickDevice, { platformType: state.platform.type, platformName: state.platform.name, currentDeviceId: state.current?.id, onSelect: (device) => { step("configure"); setState({ kind: "configure", app: state.app, platform: state.platform, device }); }, onCancel: () => void loadPlatforms(state.app, state.platform) })), state.kind === "configure" && (_jsx(ConfigureScreen, { host: ctx.host, appPlatformId: state.platform.id, device: state.device ? { platformType: state.platform.type, id: state.device.id } : undefined, phase: phase, verify: verify, configure: configure, onDone: (result) => {
@@ -139,5 +142,5 @@ export function Shell({ ctx, appQuery, platformQuery, deviceQuery, onExit, exitW
139
142
  .then(() => ctx.config.markOnboardingDone())
140
143
  .catch(() => { });
141
144
  setState({ kind: "home", app: state.app, platform: state.platform, device: state.device, result });
142
- }, onBack: () => chooseDevice(state.app, state.platform, state.device ?? undefined) })), state.kind === "home" && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [_jsxs(Text, { bold: true, children: [state.app.name, " \u00B7 ", state.platform.name, " ", _jsxs(Text, { color: palette.secondaryBlue, children: ["[", state.platform.typeLabel, "]"] })] }), _jsxs(Text, { children: ["App Platform ID: ", _jsx(Text, { color: palette.aqua, children: state.platform.id })] }), state.device && _jsxs(Text, { children: ["Device: ", state.device.displayName, " ", _jsxs(Text, { dimColor: true, children: ["(", state.device.type, ", ", state.device.width, "\u00D7", state.device.height, ")"] })] }), _jsxs(Text, { children: [state.result.sdkEnabled ? "SDK enabled" : "SDK disabled", " \u00B7 ", state.result.configureState, state.result.deviceId ? ` · device ${state.result.deviceId}` : ""] }), _jsxs(Text, { children: ["placements ", _jsx(Text, { bold: true, children: state.result.counts.placements }), " \u00B7 products ", _jsx(Text, { bold: true, children: state.result.counts.products }), " \u00B7 paywalls ", _jsx(Text, { bold: true, children: state.result.counts.paywalls })] }), _jsx(Text, { dimColor: true, children: "Launch a placement lands in NAM-4502." })] }), _jsxs(Text, { dimColor: true, children: ["s switch app \u00B7 p switch platform", presetsFor(state.platform.type).length > 1 ? " · d switch device" : "", " \u00B7 q quit"] })] })), state.kind === "fatal" && _jsx(Text, { color: palette.coral, children: state.message })] })] }));
145
+ }, onBack: () => chooseDevice(state.app, state.platform, state.device ?? undefined) })), state.kind === "home" && (_jsx(SuccessScreen, { app: state.app, platform: state.platform, device: state.device, result: state.result, host: ctx.host, canSwitchDevice: presetsFor(state.platform.type).length > 1, launchHint: state.launchHint })), state.kind === "fatal" && _jsx(Text, { color: palette.coral, children: state.message })] })] }));
143
146
  }
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { palette } from "../theme.js";
4
+ export const PLACEMENT_LIST_MAX = 8;
5
+ /** The payoff after onboarding: what the SDK found, and what to do next. */
6
+ export function SuccessScreen({ app, platform, device, result, host, canSwitchDevice, launchHint }) {
7
+ const { counts, placements } = result;
8
+ const shown = placements.slice(0, PLACEMENT_LIST_MAX);
9
+ const more = placements.length - shown.length;
10
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: palette.aqua, paddingX: 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, color: palette.aqua, children: "Connected" }), " \u00B7 ", app.name, " \u00B7 ", platform.name, " ", _jsxs(Text, { color: palette.secondaryBlue, children: ["[", platform.typeLabel, "]"] }), device && _jsxs(Text, { children: [" \u00B7 ", device.displayName, " ", _jsxs(Text, { dimColor: true, children: ["(", device.width, "\u00D7", device.height, ")"] })] })] }), _jsxs(Text, { dimColor: true, children: ["App Platform ID ", result.appPlatformId, " \u00B7 ", host.name] }), _jsxs(Text, { children: ["SDK ", result.configureState, " \u00B7 pinned to @namiml/sdk-core ", result.sdkVersion, " \u00B7 ", result.sdkEnabled ? "SDK enabled" : _jsx(Text, { color: palette.coral, children: "SDK disabled" })] }), result.deviceId && _jsxs(Text, { dimColor: true, children: ["device ", result.deviceId] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: ["placements ", _jsx(Text, { bold: true, children: counts.placements }), " \u00B7 products ", _jsx(Text, { bold: true, children: counts.products }), " \u00B7 paywalls ", _jsx(Text, { bold: true, children: counts.paywalls })] }) }), shown.length > 0 && (_jsxs(Text, { dimColor: true, children: [shown.join(" · "), more > 0 ? ` · +${more} more` : ""] })), launchHint && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: palette.aqua, children: ["Launching a placement lands in the next release.", placements.length > 0 ? ` The SDK found: ${shown.join(", ")}${more > 0 ? ` and ${more} more` : ""}.` : " This platform has no placements yet — add a campaign in the Control Center."] }) }))] }), _jsxs(Text, { dimColor: true, children: ["l launch a placement \u00B7 s switch app \u00B7 p switch platform", canSwitchDevice ? " · d switch device" : "", " \u00B7 q quit"] })] }));
11
+ }
@@ -1,4 +1,5 @@
1
- import { Nami, NamiCampaignManager, NamiPaywallManager, storageService } from "@namiml/sdk-core";
1
+ import { Nami, NamiCampaignManager, NamiPaywallManager, NAMI_SDK_VERSION, storageService } from "@namiml/sdk-core";
2
+ import { summarizePlacements } from "./summary.js";
2
3
  import { registerTerminalAdapters } from "../adapters/index.js";
3
4
  import { FileStorageAdapter, storagePathFor } from "../adapters/file-storage.adapter.js";
4
5
  import { configDir } from "../paths.js";
@@ -38,6 +39,8 @@ export async function configureSdk(input, deps = {}) {
38
39
  products: safeCount(() => NamiCampaignManager.getAllProducts().length),
39
40
  paywalls: safeCount(() => NamiPaywallManager.allPaywalls().length),
40
41
  },
42
+ placements: safeList(() => summarizePlacements(NamiCampaignManager.allCampaigns())),
43
+ sdkVersion: NAMI_SDK_VERSION,
41
44
  deviceId: safeDeviceId(),
42
45
  storagePath,
43
46
  device: preset ? toSelection(preset.device) : null,
@@ -62,6 +65,14 @@ function safeCount(read) {
62
65
  return 0;
63
66
  }
64
67
  }
68
+ function safeList(read) {
69
+ try {
70
+ return read();
71
+ }
72
+ catch {
73
+ return [];
74
+ }
75
+ }
65
76
  function safeDeviceId() {
66
77
  try {
67
78
  const device = storageService.getDevice();
@@ -0,0 +1,12 @@
1
+ /** One human label per campaign rule: the placement label for label rules, else the rule name. */
2
+ export function summarizePlacements(campaigns) {
3
+ if (!Array.isArray(campaigns))
4
+ return [];
5
+ const labels = new Set();
6
+ for (const c of campaigns) {
7
+ const label = c.type === "label" && typeof c.value === "string" && c.value.trim() ? c.value.trim() : c.name?.trim();
8
+ if (label)
9
+ labels.add(label);
10
+ }
11
+ return [...labels].sort((a, b) => a.localeCompare(b));
12
+ }
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** CLI package version. Stamped by scripts/version.sh from the repo VERSION file — do not hand-edit. */
2
- export const NAMI_CLI_VERSION = "3.4.11-dev.202609222222";
2
+ export const NAMI_CLI_VERSION = "3.4.11-dev.202609222238";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namiml/cli",
3
- "version": "3.4.11-dev.202609222222",
3
+ "version": "3.4.11-dev.202609222238",
4
4
  "description": "Nami SDK developer CLI — terminal test app, implementation and flow checks",
5
5
  "keywords": [
6
6
  "nami",
@@ -35,7 +35,7 @@
35
35
  "access": "public"
36
36
  },
37
37
  "dependencies": {
38
- "@namiml/sdk-core": "3.4.11-dev.202609222222",
38
+ "@namiml/sdk-core": "3.4.11-dev.202609222238",
39
39
  "commander": "^15",
40
40
  "env-paths": "^4",
41
41
  "ink": "^6.0.0",