instar 1.3.622 → 1.3.624

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.
@@ -247,6 +247,21 @@ export function buildFollowMeIssuePayload(card, offers, pinValue) {
247
247
  }
248
248
 
249
249
  /** Pending Logins panel: device code / verification URL (as TEXT) + TTL + reissues. */
250
+ // Only render a verification URL as a TAPPABLE link when it is https AND points at a
251
+ // known provider sign-in host. Anything else falls back to plain text (preserves the
252
+ // "never make an arbitrary href clickable" intent while giving a real one-tap sign-in
253
+ // for the legitimate provider OAuth URLs).
254
+ const PROVIDER_LOGIN_HOSTS = ['claude.com', 'claude.ai', 'anthropic.com', 'openai.com', 'auth.openai.com', 'accounts.google.com', 'google.com'];
255
+ function trustedLoginUrl(raw) {
256
+ if (typeof raw !== 'string' || !raw) return null;
257
+ try {
258
+ const u = new URL(raw);
259
+ if (u.protocol !== 'https:') return null;
260
+ const host = u.hostname.toLowerCase();
261
+ return PROVIDER_LOGIN_HOSTS.some((h) => host === h || host.endsWith('.' + h)) ? u.href : null;
262
+ } catch { return null; }
263
+ }
264
+
250
265
  export function renderPendingLogins(doc, target, logins, now = Date.now()) {
251
266
  if (!target) return;
252
267
  target.replaceChildren();
@@ -256,25 +271,61 @@ export function renderPendingLogins(doc, target, logins, now = Date.now()) {
256
271
  }
257
272
  for (const l of logins) {
258
273
  const row = el(doc, 'div', 'sub-pending');
259
- const head = el(doc, 'div', 'sub-pending-head');
260
- head.appendChild(el(doc, 'span', 'sub-pending-label', sanitizeForDisplay(l && l.label, 'label')));
261
- const ttl = l && l.ttlExpiresAt ? countdown(l.ttlExpiresAt, now) : '';
262
- head.appendChild(el(doc, 'span', 'sub-pending-ttl', ttl ? `expires in ${ttl}` : 'expired'));
263
- row.appendChild(head);
264
- // Flow heads-up (e.g. the Claude two-code sequence) — shown before the code so
265
- // the operator knows what to expect. Plain text, sanitized; never a live href.
266
- if (l && l.notice) {
267
- row.appendChild(el(doc, 'div', 'sub-pending-notice', sanitizeForDisplay(l.notice, 'summary')));
274
+ // Non-sensitive identifiers for the code-submit handler (never a credential).
275
+ row.setAttribute('data-login-id', sanitizeForDisplay(l && l.id, 'label'));
276
+ if (l && (l.machineId || l.machineNickname)) row.setAttribute('data-machine-id', sanitizeForDisplay(l.machineId, 'label'));
277
+
278
+ // Lead with a plain-language headline naming what to do + where (machine).
279
+ const machine = sanitizeForDisplay(l && (l.machineNickname || l.machineId), 'label');
280
+ const who = sanitizeForDisplay(l && l.label, 'label');
281
+ const headline = machine
282
+ ? `Sign in to finish setting up ${who} on ${machine}`
283
+ : `Sign in to finish setting up ${who}`;
284
+ row.appendChild(el(doc, 'div', 'sub-pending-headline', headline));
285
+
286
+ // The PRIMARY action: one tappable "Sign in" link to the provider's own OAuth URL.
287
+ // Falls back to copy-text only if the URL isn't a trusted provider sign-in host.
288
+ const href = trustedLoginUrl(l && l.verificationUrl);
289
+ if (href) {
290
+ const a = doc.createElement('a');
291
+ a.setAttribute('href', href);
292
+ a.setAttribute('target', '_blank');
293
+ a.setAttribute('rel', 'noopener noreferrer');
294
+ a.setAttribute('class', 'sub-pending-signin');
295
+ a.textContent = 'Sign in';
296
+ row.appendChild(a);
297
+ } else if (l && l.verificationUrl) {
298
+ row.appendChild(el(doc, 'div', 'sub-pending-url', sanitizeForDisplay(l.verificationUrl, 'url')));
268
299
  }
300
+
301
+ // A device code (only some flows) — shown compactly under the button.
269
302
  if (l && l.userCode) {
270
303
  row.appendChild(el(doc, 'div', 'sub-pending-code', `Code: ${sanitizeForDisplay(l.userCode, 'code')}`));
271
304
  }
272
- // Verification URL shown as TEXT for the operator to copy — never a live href.
273
- row.appendChild(el(doc, 'div', 'sub-pending-url', sanitizeForDisplay(l && l.verificationUrl, 'url')));
274
- const rc = l && Number(l.reissueCount);
275
- if (Number.isFinite(rc) && rc > 0) {
276
- row.appendChild(el(doc, 'div', 'sub-pending-reissue', `Re-issued ${rc} time${rc === 1 ? '' : 's'}`));
305
+
306
+ // url-code-paste flow (Claude): after signing in, the provider hands the operator a
307
+ // CODE to paste back. Give them a field for it right here — so it goes straight to the
308
+ // machine doing the login (off-chat), not relayed by hand. (ws52-code-paste-back)
309
+ if (l && l.kind === 'url-code-paste') {
310
+ row.appendChild(el(doc, 'div', 'sub-pending-codehint', 'After you sign in, paste the code the page gives you here:'));
311
+ const input = doc.createElement('input');
312
+ input.setAttribute('type', 'text');
313
+ input.setAttribute('class', 'sub-pending-code-input');
314
+ input.setAttribute('placeholder', 'Paste your sign-in code');
315
+ input.setAttribute('autocomplete', 'off');
316
+ row.appendChild(input);
317
+ const submit = el(doc, 'button', 'sub-pending-code-submit', 'Submit code');
318
+ submit.setAttribute('data-submit-code', '1');
319
+ row.appendChild(submit);
277
320
  }
321
+
322
+ // One short secondary line: the TTL, and the flow notice only if present (trimmed).
323
+ const ttl = l && l.ttlExpiresAt ? countdown(l.ttlExpiresAt, now) : '';
324
+ if (ttl) row.appendChild(el(doc, 'div', 'sub-pending-ttl', `Link expires in ${ttl}`));
325
+ if (l && l.notice) {
326
+ row.appendChild(el(doc, 'div', 'sub-pending-notice', sanitizeForDisplay(l.notice, 'summary')));
327
+ }
328
+ // (No "re-issued N times" noise — it confused more than it informed.)
278
329
  target.appendChild(row);
279
330
  }
280
331
  }
@@ -295,6 +346,7 @@ const URLS = {
295
346
  // operator's single dashboard (WS5.2 seam #3) — without it the device-code link never appears here.
296
347
  pending: '/subscription-pool/pending-logins?scope=pool',
297
348
  inUse: '/subscription-pool/in-use',
349
+ submitCode: '/subscription-pool/follow-me/submit-code', // POST — paste-back the sign-in code (ws52-code-paste-back)
298
350
  scan: '/subscription-pool/follow-me/scan', // POST — follow-me consent offers (one-tap card)
299
351
  issue: '/mandate/issue-for-machine', // POST (PIN-gated) — Approve issues the mandate
300
352
  };
@@ -372,6 +424,7 @@ export function createController(opts) {
372
424
  const inUseAccountId = inUseBody && inUseBody.activeAccountId ? inUseBody.activeAccountId : null;
373
425
  renderAccounts(doc, els.accounts, accounts, now(), inUseAccountId);
374
426
  renderPendingLogins(doc, els.pending, logins, now());
427
+ wireCodeSubmit();
375
428
  // The one-tap follow-me Approve card(s) — rendered into els.followMe from the scan offers
376
429
  // (ws52-operator-tap-not-text Part A). Silent when there are none. The Approve click is wired
377
430
  // once (delegated) so re-renders never stack listeners.
@@ -424,6 +477,57 @@ export function createController(opts) {
424
477
  s.textContent = text;
425
478
  }
426
479
 
480
+ // Delegated, wired ONCE: a "Submit code" tap on a pending-login row reads the pasted
481
+ // sign-in code and POSTs it (with the login's id + machineId) to the fronting relay,
482
+ // which carries it to the machine doing the login (off-chat). The code is never stored
483
+ // client-side beyond the input; it's cleared on success. (ws52-code-paste-back)
484
+ function wireCodeSubmit() {
485
+ if (state.codeSubmitWired || !els.pending) return;
486
+ state.codeSubmitWired = true;
487
+ els.pending.addEventListener('click', (ev) => {
488
+ const btn = ev.target && ev.target.closest ? ev.target.closest('[data-submit-code]') : null;
489
+ if (!btn || !els.pending.contains(btn)) return;
490
+ const row = btn.closest('.sub-pending');
491
+ if (!row) return;
492
+ const input = row.querySelector('.sub-pending-code-input');
493
+ const code = input ? input.value.trim() : '';
494
+ const id = row.getAttribute('data-login-id');
495
+ const machineId = row.getAttribute('data-machine-id') || undefined;
496
+ if (!code) { setRowStatus(row, 'Paste the code the sign-in page gave you, then tap Submit.'); return; }
497
+ if (!id) { setRowStatus(row, 'Couldn’t identify this login — please refresh.'); return; }
498
+ setRowStatus(row, 'Sending your code…');
499
+ btn.setAttribute('disabled', '1');
500
+ void (async () => {
501
+ try {
502
+ const r = await postJson(URLS.submitCode, { machineId, id, code });
503
+ if (r.ok && r.json && r.json.outcome === 'validated') {
504
+ setRowStatus(row, 'Done — this machine is set up with the account.');
505
+ if (input) input.value = '';
506
+ } else if (r.ok && r.json && r.json.outcome === 'submitted') {
507
+ setRowStatus(row, 'Code sent — finishing sign-in…');
508
+ if (input) input.value = '';
509
+ } else if (r.ok && r.json && r.json.outcome === 'held') {
510
+ setRowStatus(row, 'Signed in, but the account didn’t match what was approved — check with the operator.');
511
+ btn.removeAttribute('disabled');
512
+ } else {
513
+ const msg = (r.json && (r.json.error || r.json.reason)) ? (r.json.error || r.json.reason) : `failed (${r.status})`;
514
+ setRowStatus(row, `Couldn’t submit the code: ${msg}`);
515
+ btn.removeAttribute('disabled');
516
+ }
517
+ } catch (e) {
518
+ setRowStatus(row, 'Couldn’t reach the server — try again.');
519
+ btn.removeAttribute('disabled');
520
+ }
521
+ })();
522
+ });
523
+ }
524
+
525
+ function setRowStatus(row, text) {
526
+ let s = row.querySelector('.sub-pending-status');
527
+ if (!s) { s = el(doc, 'div', 'sub-pending-status', ''); row.appendChild(s); }
528
+ s.textContent = text;
529
+ }
530
+
427
531
  function reschedule() {
428
532
  if (!state.active) return;
429
533
  if (state.timerId != null) cancel(state.timerId);
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAwBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAkH7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAy4CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CA8eN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAs7etE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAwBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAkH7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAy4CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CA8eN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAi8etE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
@@ -9874,7 +9874,7 @@ export async function startServer(options) {
9874
9874
  // a 2nd account never clobbers the 1st.
9875
9875
  const { PendingLoginStore } = await import('../core/PendingLoginStore.js');
9876
9876
  const { EnrollmentWizard } = await import('../core/EnrollmentWizard.js');
9877
- const { FrameworkLoginDriver } = await import('../core/FrameworkLoginDriver.js');
9877
+ const { FrameworkLoginDriver, enrollPaneSessionName } = await import('../core/FrameworkLoginDriver.js');
9878
9878
  const DEFAULT_ENROLL_LOGIN_COMMANDS = {
9879
9879
  'claude-code': 'claude auth login',
9880
9880
  'codex-cli': 'codex login',
@@ -9909,7 +9909,20 @@ export async function startServer(options) {
9909
9909
  })
9910
9910
  : undefined,
9911
9911
  driveLogin: new FrameworkLoginDriver({
9912
- capture: async (session) => sessionManager.captureOutput(session, 120) || '',
9912
+ // Capture with `tmux capture-pane -J` so a hard-WRAPPED verification URL is
9913
+ // joined back into one line (the 2026-06-18 "code=t" truncation bug). Falls
9914
+ // back to the generic capture if tmux/the -J capture is unavailable;
9915
+ // parseArtifact ALSO de-wraps defensively.
9916
+ capture: async (session) => {
9917
+ const tmuxPath = detectTmuxPath();
9918
+ if (tmuxPath) {
9919
+ try {
9920
+ return execFileSync(tmuxPath, ['capture-pane', '-p', '-J', '-S', '-300', '-t', `=${session}:`], { encoding: 'utf-8', timeout: 5000 }) || '';
9921
+ }
9922
+ catch { /* @silent-fallback-ok — fall through to the generic capture */ }
9923
+ }
9924
+ return sessionManager.captureOutput(session, 120) || '';
9925
+ },
9913
9926
  spawn: async ({ framework, configHome }) => {
9914
9927
  const tmuxPath = detectTmuxPath();
9915
9928
  if (!tmuxPath)
@@ -9920,8 +9933,7 @@ export async function startServer(options) {
9920
9933
  const cmd = configHome
9921
9934
  ? `env CLAUDE_CONFIG_DIR=${JSON.stringify(configHome)} ${baseCmd}`
9922
9935
  : baseCmd;
9923
- const slug = (configHome ?? framework).replace(/[^a-zA-Z0-9]+/g, '-').slice(-24);
9924
- const session = `instar-enroll-${framework}-${slug}`;
9936
+ const session = enrollPaneSessionName(framework, configHome);
9925
9937
  try {
9926
9938
  execFileSync(tmuxPath, ['kill-session', '-t', `=${session}`], { stdio: 'ignore' });
9927
9939
  }