viveworker 0.1.11 → 0.2.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.
@@ -285,6 +285,73 @@ async function runSetup(cliOptions) {
285
285
  console.log(t(locale, "cli.setup.qrCaDownload"));
286
286
  await printQrCode(caDownloadIpUrl);
287
287
  }
288
+
289
+ const claudeSettingsPath = resolvePath(
290
+ cliOptions.claudeSettingsFile || path.join(os.homedir(), ".claude", "settings.json")
291
+ );
292
+ const claudeHomeExists = await fileExists(path.dirname(claudeSettingsPath));
293
+ if (cliOptions.claudeSettingsFile || claudeHomeExists) {
294
+ await installClaudeHooks({
295
+ envFile,
296
+ claudeSettingsFile: cliOptions.claudeSettingsFile,
297
+ sessionSecret,
298
+ });
299
+ } else {
300
+ console.log("");
301
+ console.log(t(locale, "cli.setup.claudeHooksSkipped"));
302
+ }
303
+ }
304
+
305
+ async function installClaudeHooks({ envFile, claudeSettingsFile, sessionSecret }) {
306
+ const hookScript = path.join(packageRoot, "scripts", "viveworker-claude-hook.mjs");
307
+ const nodePath = process.execPath;
308
+ const settingsFile = resolvePath(
309
+ claudeSettingsFile || path.join(os.homedir(), ".claude", "settings.json")
310
+ );
311
+
312
+ const hookCommand = `'${nodePath}' '${hookScript}' --env-file '${envFile}'`;
313
+
314
+ let settings = {};
315
+ try {
316
+ const raw = await fs.readFile(settingsFile, "utf8");
317
+ settings = JSON.parse(raw);
318
+ } catch {
319
+ // File missing or invalid — start fresh
320
+ }
321
+
322
+ if (!settings.hooks) {
323
+ settings.hooks = {};
324
+ }
325
+
326
+ const hookEntry = (timeout) => ({
327
+ hooks: [{ type: "command", command: hookCommand, timeout }],
328
+ });
329
+
330
+ const fileToolMatcher = "Write|Edit|MultiEdit";
331
+ const approvalMatcher = "Bash|WebFetch|WebSearch|Write|Edit|MultiEdit";
332
+ const interactiveMatcher = "ExitPlanMode|AskUserQuestion";
333
+
334
+ settings.hooks.UserPromptSubmit = [hookEntry(15)];
335
+ settings.hooks.Notification = [hookEntry(15)];
336
+ settings.hooks.Stop = [hookEntry(15)];
337
+ settings.hooks.PermissionRequest = [{ ...hookEntry(900), matcher: approvalMatcher }];
338
+ settings.hooks.PreToolUse = [
339
+ { ...hookEntry(30), matcher: fileToolMatcher },
340
+ { ...hookEntry(900), matcher: interactiveMatcher },
341
+ ];
342
+ settings.hooks.PostToolUse = [
343
+ { ...hookEntry(30), matcher: `Read|${approvalMatcher}|${interactiveMatcher}` },
344
+ ];
345
+ settings.hooks.PostToolUseFailure = [
346
+ { ...hookEntry(15), matcher: `${approvalMatcher}|${interactiveMatcher}` },
347
+ ];
348
+ settings.hooks.SessionEnd = [hookEntry(15)];
349
+
350
+ await fs.mkdir(path.dirname(settingsFile), { recursive: true });
351
+ await fs.writeFile(settingsFile, JSON.stringify(settings, null, 2) + "\n", "utf8");
352
+
353
+ console.log("");
354
+ console.log(`Claude hooks installed: ${settingsFile}`);
288
355
  }
289
356
 
290
357
  async function runStart(cliOptions) {
@@ -533,6 +600,7 @@ function parseArgs(argv) {
533
600
  vapidPrivateKey: "",
534
601
  locale: "",
535
602
  mkcertTrustStores: "",
603
+ claudeSettingsFile: "",
536
604
  };
537
605
 
538
606
  if (argv[0] && !argv[0].startsWith("-")) {
@@ -611,6 +679,9 @@ function parseArgs(argv) {
611
679
  index += 1;
612
680
  } else if (arg === "--pair") {
613
681
  parsed.pair = true;
682
+ } else if (arg === "--claude-settings-file") {
683
+ parsed.claudeSettingsFile = next;
684
+ index += 1;
614
685
  } else if (arg === "--help" || arg === "-h") {
615
686
  parsed.command = "help";
616
687
  } else {
package/web/app.css CHANGED
@@ -3286,3 +3286,174 @@ button:disabled {
3286
3286
  padding: 0.4rem 0.46rem;
3287
3287
  }
3288
3288
  }
3289
+
3290
+ /* Action button busy/loading state */
3291
+ button[data-action-url].is-loading {
3292
+ display: inline-flex;
3293
+ align-items: center;
3294
+ justify-content: center;
3295
+ gap: 0.5rem;
3296
+ cursor: wait;
3297
+ opacity: 0.9;
3298
+ }
3299
+ button[data-action-url][aria-busy="true"]:not(.is-loading) {
3300
+ opacity: 0.5;
3301
+ cursor: not-allowed;
3302
+ }
3303
+ .action-spinner {
3304
+ display: inline-block;
3305
+ width: 0.95em;
3306
+ height: 0.95em;
3307
+ border-radius: 50%;
3308
+ border: 2px solid currentColor;
3309
+ border-right-color: transparent;
3310
+ animation: action-spinner-rotate 0.7s linear infinite;
3311
+ flex-shrink: 0;
3312
+ }
3313
+ @keyframes action-spinner-rotate {
3314
+ to { transform: rotate(360deg); }
3315
+ }
3316
+
3317
+ .provider-badge {
3318
+ display: inline-flex;
3319
+ align-items: center;
3320
+ gap: 0.3rem;
3321
+ padding: 0.1rem 0.45rem 0.1rem 0.3rem;
3322
+ font-size: 0.7rem;
3323
+ font-weight: 600;
3324
+ line-height: 1.4;
3325
+ border-radius: 999px;
3326
+ border: 1px solid currentColor;
3327
+ white-space: nowrap;
3328
+ }
3329
+ .provider-badge__icon {
3330
+ display: inline-flex;
3331
+ align-items: center;
3332
+ justify-content: center;
3333
+ width: 1.05em;
3334
+ height: 1.05em;
3335
+ border-radius: 50%;
3336
+ background: currentColor;
3337
+ color: #0b0f14;
3338
+ font-size: 0.65em;
3339
+ font-weight: 800;
3340
+ font-family: var(--font-ui);
3341
+ }
3342
+ .provider-badge--codex {
3343
+ color: #6aa9ff;
3344
+ background: rgba(106, 169, 255, 0.08);
3345
+ }
3346
+ .provider-badge--claude {
3347
+ color: #f5a45a;
3348
+ background: rgba(245, 164, 90, 0.1);
3349
+ }
3350
+
3351
+ .provider-filter {
3352
+ display: inline-flex;
3353
+ align-items: center;
3354
+ gap: 0.25rem;
3355
+ padding: 0.2rem;
3356
+ margin: 0.5rem 0;
3357
+ background: rgba(255, 255, 255, 0.04);
3358
+ border: 1px solid rgba(255, 255, 255, 0.08);
3359
+ border-radius: 999px;
3360
+ width: fit-content;
3361
+ }
3362
+ .provider-filter__button {
3363
+ appearance: none;
3364
+ background: transparent;
3365
+ color: var(--text-muted, #9aa6b2);
3366
+ border: 0;
3367
+ padding: 0.3rem 0.85rem;
3368
+ font-size: 0.78rem;
3369
+ font-weight: 600;
3370
+ border-radius: 999px;
3371
+ cursor: pointer;
3372
+ transition: background 0.15s ease, color 0.15s ease;
3373
+ }
3374
+ .provider-filter__button:hover {
3375
+ color: #fff;
3376
+ }
3377
+ .provider-filter__button.is-active {
3378
+ background: rgba(255, 255, 255, 0.12);
3379
+ color: #fff;
3380
+ }
3381
+
3382
+ .claude-question-item {
3383
+ display: flex;
3384
+ flex-direction: column;
3385
+ gap: 0.5rem;
3386
+ padding: 0.75rem 0;
3387
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
3388
+ }
3389
+ .claude-question-item:last-of-type {
3390
+ border-bottom: none;
3391
+ }
3392
+ .claude-question-text {
3393
+ margin: 0 0 0.25rem 0;
3394
+ font-size: 1rem;
3395
+ line-height: 1.4;
3396
+ }
3397
+ .claude-question-options {
3398
+ display: flex;
3399
+ flex-direction: column;
3400
+ gap: 0.4rem;
3401
+ }
3402
+ .claude-question-option {
3403
+ display: flex;
3404
+ align-items: flex-start;
3405
+ gap: 0.55rem;
3406
+ padding: 0.6rem 0.75rem;
3407
+ background: rgba(255, 255, 255, 0.04);
3408
+ border: 1px solid rgba(255, 255, 255, 0.08);
3409
+ border-radius: 0.6rem;
3410
+ cursor: pointer;
3411
+ flex-wrap: wrap;
3412
+ }
3413
+ .claude-question-option:hover {
3414
+ background: rgba(255, 255, 255, 0.07);
3415
+ }
3416
+ .claude-question-option input[type="radio"],
3417
+ .claude-question-option input[type="checkbox"] {
3418
+ margin-top: 0.2rem;
3419
+ flex-shrink: 0;
3420
+ }
3421
+ .claude-question-option__label {
3422
+ flex: 1 1 auto;
3423
+ min-width: 0;
3424
+ font-weight: 600;
3425
+ }
3426
+ .claude-question-option__desc {
3427
+ flex: 1 1 100%;
3428
+ margin-left: 1.5rem;
3429
+ font-size: 0.85rem;
3430
+ opacity: 0.75;
3431
+ line-height: 1.35;
3432
+ }
3433
+ .claude-question-note {
3434
+ width: 100%;
3435
+ box-sizing: border-box;
3436
+ margin-top: 0.25rem;
3437
+ padding: 0.5rem 0.6rem;
3438
+ background: rgba(0, 0, 0, 0.35);
3439
+ color: inherit;
3440
+ border: 1px solid rgba(255, 255, 255, 0.08);
3441
+ border-radius: 0.5rem;
3442
+ font: inherit;
3443
+ resize: vertical;
3444
+ }
3445
+ .claude-question-actions {
3446
+ display: flex;
3447
+ justify-content: flex-end;
3448
+ margin-top: 0.75rem;
3449
+ }
3450
+ .claude-question-notice {
3451
+ margin: 0.5rem 0 0 0;
3452
+ font-size: 0.85rem;
3453
+ opacity: 0.8;
3454
+ }
3455
+ .claude-question-error {
3456
+ margin: 0.5rem 0 0 0;
3457
+ font-size: 0.85rem;
3458
+ color: #ff8a8a;
3459
+ }