@quonfig/cli 0.0.14 → 0.0.16

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.
Files changed (39) hide show
  1. package/README.md +93 -37
  2. package/dist/commands/init.d.ts +1 -0
  3. package/dist/commands/init.js +18 -0
  4. package/dist/commands/migrate.js +1 -1
  5. package/dist/commands/pull.d.ts +14 -1
  6. package/dist/commands/pull.js +64 -4
  7. package/dist/commands/push.d.ts +53 -0
  8. package/dist/commands/push.js +370 -0
  9. package/dist/commands/workspace/bootstrap.d.ts +7 -0
  10. package/dist/commands/workspace/bootstrap.js +58 -2
  11. package/dist/commands/workspace/switch.d.ts +3 -0
  12. package/dist/commands/workspace/switch.js +25 -3
  13. package/dist/commands/workspace.d.ts +1 -0
  14. package/dist/commands/workspace.js +55 -0
  15. package/dist/migrate/local-write.d.ts +1 -1
  16. package/dist/migrate/local-write.js +1 -1
  17. package/dist/migrate/push-to-cloud.d.ts +1 -1
  18. package/dist/migrate/push-to-cloud.js +1 -1
  19. package/dist/push/bare-path-diff.d.ts +40 -0
  20. package/dist/push/bare-path-diff.js +126 -0
  21. package/dist/push/confirm.d.ts +10 -0
  22. package/dist/push/confirm.js +108 -0
  23. package/dist/push/diff-summary.d.ts +56 -0
  24. package/dist/push/diff-summary.js +115 -0
  25. package/dist/push/identity-check.d.ts +53 -0
  26. package/dist/push/identity-check.js +135 -0
  27. package/dist/push/run-push.d.ts +107 -0
  28. package/dist/push/run-push.js +318 -0
  29. package/dist/util/get-client.js +1 -1
  30. package/dist/util/gitea-api.d.ts +15 -1
  31. package/dist/util/gitea-api.js +1 -0
  32. package/dist/util/gitea-token-storage.d.ts +7 -0
  33. package/dist/util/quonfig-json.d.ts +44 -0
  34. package/dist/util/quonfig-json.js +65 -0
  35. package/dist/verify/validate.js +6 -2
  36. package/oclif.manifest.json +113 -3
  37. package/package.json +1 -1
  38. /package/dist/{migrate/push-strategy.d.ts → util/clone-and-stack-push.d.ts} +0 -0
  39. /package/dist/{migrate/push-strategy.js → util/clone-and-stack-push.js} +0 -0
package/README.md CHANGED
@@ -14,7 +14,7 @@ $ npm install -g @quonfig/cli
14
14
  $ qfg COMMAND
15
15
  running command...
16
16
  $ qfg (--version)
17
- @quonfig/cli/0.0.14 linux-x64 node-v24.4.1
17
+ @quonfig/cli/0.0.16 linux-x64 node-v24.4.1
18
18
  $ qfg --help [COMMAND]
19
19
  USAGE
20
20
  $ qfg COMMAND
@@ -46,6 +46,7 @@ USAGE
46
46
  * [`qfg migrate status`](#qfg-migrate-status)
47
47
  * [`qfg override [NAME]`](#qfg-override-name)
48
48
  * [`qfg pull`](#qfg-pull)
49
+ * [`qfg push`](#qfg-push)
49
50
  * [`qfg schema NAME`](#qfg-schema-name)
50
51
  * [`qfg sdk-key`](#qfg-sdk-key)
51
52
  * [`qfg sdk-key create`](#qfg-sdk-key-create)
@@ -59,7 +60,7 @@ USAGE
59
60
  * [`qfg whoami`](#qfg-whoami)
60
61
  * [`qfg workspace`](#qfg-workspace)
61
62
  * [`qfg workspace bootstrap`](#qfg-workspace-bootstrap)
62
- * [`qfg workspace switch`](#qfg-workspace-switch)
63
+ * [`qfg workspace switch [SLUG]`](#qfg-workspace-switch-slug)
63
64
 
64
65
  ## `qfg config-schema`
65
66
 
@@ -88,7 +89,7 @@ EXAMPLES
88
89
  $ qfg config-schema --json-schema # full JSON Schema document (copy into your editor)
89
90
  ```
90
91
 
91
- _See code: [src/commands/config-schema.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/config-schema.ts)_
92
+ _See code: [src/commands/config-schema.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/config-schema.ts)_
92
93
 
93
94
  ## `qfg create NAME`
94
95
 
@@ -160,7 +161,7 @@ EXAMPLES
160
161
  $ qfg set-rollout my.new.flag --environment production --true-percent 20
161
162
  ```
162
163
 
163
- _See code: [src/commands/create.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/create.ts)_
164
+ _See code: [src/commands/create.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/create.ts)_
164
165
 
165
166
  ## `qfg flag info [NAME]`
166
167
 
@@ -360,7 +361,7 @@ EXAMPLES
360
361
  $ qfg generate --targets node-ts -o ./dist # combine with targets
361
362
  ```
362
363
 
363
- _See code: [src/commands/generate.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/generate.ts)_
364
+ _See code: [src/commands/generate.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/generate.ts)_
364
365
 
365
366
  ## `qfg generate-new-hex-key`
366
367
 
@@ -383,7 +384,7 @@ EXAMPLES
383
384
  $ qfg generate-new-hex-key
384
385
  ```
385
386
 
386
- _See code: [src/commands/generate-new-hex-key.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/generate-new-hex-key.ts)_
387
+ _See code: [src/commands/generate-new-hex-key.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/generate-new-hex-key.ts)_
387
388
 
388
389
  ## `qfg get [NAME]`
389
390
 
@@ -415,7 +416,7 @@ EXAMPLES
415
416
  $ qfg get my.config.name --environment=production
416
417
  ```
417
418
 
418
- _See code: [src/commands/get.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/get.ts)_
419
+ _See code: [src/commands/get.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/get.ts)_
419
420
 
420
421
  ## `qfg info [NAME]`
421
422
 
@@ -463,7 +464,7 @@ EXAMPLES
463
464
  $ qfg info my.config.name --exclude-evaluations # skip 24h stats
464
465
  ```
465
466
 
466
- _See code: [src/commands/info.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/info.ts)_
467
+ _See code: [src/commands/info.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/info.ts)_
467
468
 
468
469
  ## `qfg init [DIRECTORY]`
469
470
 
@@ -472,13 +473,15 @@ Initialize or update a Quonfig workspace
472
473
  ```
473
474
  USAGE
474
475
  $ qfg init [DIRECTORY] [--json] [--interactive] [--no-color] [--verbose] [--dry-run] [--samples]
476
+ [--workspace <value>]
475
477
 
476
478
  ARGUMENTS
477
479
  DIRECTORY [default: .] Target directory (default: current directory)
478
480
 
479
481
  FLAGS
480
- --dry-run Show what would be done without writing anything
481
- --[no-]samples Include sample configs (default: yes on first init, no on update)
482
+ --dry-run Show what would be done without writing anything
483
+ --[no-]samples Include sample configs (default: yes on first init, no on update)
484
+ --workspace=<value> Workspace slug to pin in quonfig.json (Guard 1). If omitted, no pin is written.
482
485
 
483
486
  GLOBAL FLAGS
484
487
  --[no-]interactive Force interactive mode
@@ -501,7 +504,7 @@ EXAMPLES
501
504
  $ qfg init --dry-run
502
505
  ```
503
506
 
504
- _See code: [src/commands/init.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/init.ts)_
507
+ _See code: [src/commands/init.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/init.ts)_
505
508
 
506
509
  ## `qfg interactive`
507
510
 
@@ -531,7 +534,7 @@ EXAMPLES
531
534
  $ qfg
532
535
  ```
533
536
 
534
- _See code: [src/commands/interactive.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/interactive.ts)_
537
+ _See code: [src/commands/interactive.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/interactive.ts)_
535
538
 
536
539
  ## `qfg list`
537
540
 
@@ -571,7 +574,7 @@ EXAMPLES
571
574
  $ qfg list --feature-flags
572
575
  ```
573
576
 
574
- _See code: [src/commands/list.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/list.ts)_
577
+ _See code: [src/commands/list.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/list.ts)_
575
578
 
576
579
  ## `qfg login`
577
580
 
@@ -596,7 +599,7 @@ EXAMPLES
596
599
  $ qfg login --profile myprofile
597
600
  ```
598
601
 
599
- _See code: [src/commands/login.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/login.ts)_
602
+ _See code: [src/commands/login.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/login.ts)_
600
603
 
601
604
  ## `qfg logout`
602
605
 
@@ -619,7 +622,7 @@ EXAMPLES
619
622
  $ qfg logout
620
623
  ```
621
624
 
622
- _See code: [src/commands/logout.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/logout.ts)_
625
+ _See code: [src/commands/logout.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/logout.ts)_
623
626
 
624
627
  ## `qfg mcp`
625
628
 
@@ -652,7 +655,7 @@ EXAMPLES
652
655
  $ qfg mcp --url http://local-app.quonfig-staging.com:3003/api/v1/mcp
653
656
  ```
654
657
 
655
- _See code: [src/commands/mcp.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/mcp.ts)_
658
+ _See code: [src/commands/mcp.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/mcp.ts)_
656
659
 
657
660
  ## `qfg migrate`
658
661
 
@@ -706,7 +709,7 @@ EXAMPLES
706
709
  $ qfg migrate --from launch --api-key $LAUNCH_API_KEY --dir ./quonfig-config --staging
707
710
  ```
708
711
 
709
- _See code: [src/commands/migrate.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/migrate.ts)_
712
+ _See code: [src/commands/migrate.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/migrate.ts)_
710
713
 
711
714
  ## `qfg migrate doctor`
712
715
 
@@ -744,7 +747,7 @@ EXAMPLES
744
747
  $ qfg migrate doctor --json
745
748
  ```
746
749
 
747
- _See code: [src/commands/migrate/doctor.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/migrate/doctor.ts)_
750
+ _See code: [src/commands/migrate/doctor.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/migrate/doctor.ts)_
748
751
 
749
752
  ## `qfg migrate my-code`
750
753
 
@@ -776,7 +779,7 @@ EXAMPLES
776
779
  $ qfg migrate my-code --dry-run
777
780
  ```
778
781
 
779
- _See code: [src/commands/migrate/my-code.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/migrate/my-code.ts)_
782
+ _See code: [src/commands/migrate/my-code.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/migrate/my-code.ts)_
780
783
 
781
784
  ## `qfg migrate status`
782
785
 
@@ -807,7 +810,7 @@ EXAMPLES
807
810
  $ qfg migrate status --json
808
811
  ```
809
812
 
810
- _See code: [src/commands/migrate/status.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/migrate/status.ts)_
813
+ _See code: [src/commands/migrate/status.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/migrate/status.ts)_
811
814
 
812
815
  ## `qfg override [NAME]`
813
816
 
@@ -851,7 +854,7 @@ EXAMPLES
851
854
  $ qfg override my.double.config --value=3.14159
852
855
  ```
853
856
 
854
- _See code: [src/commands/override.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/override.ts)_
857
+ _See code: [src/commands/override.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/override.ts)_
855
858
 
856
859
  ## `qfg pull`
857
860
 
@@ -895,7 +898,55 @@ EXAMPLES
895
898
  $ qfg pull # uses QUONFIG_DIR env var
896
899
  ```
897
900
 
898
- _See code: [src/commands/pull.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/pull.ts)_
901
+ _See code: [src/commands/pull.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/pull.ts)_
902
+
903
+ ## `qfg push`
904
+
905
+ Push local config changes up to your workspace on Quonfig cloud.
906
+
907
+ ```
908
+ USAGE
909
+ $ qfg push [--json] [--interactive] [--no-color] [--verbose] [--dir <value>] [--workspace <value>]
910
+ [--yes] [--skip-validate] [--no-pin-write] [-m <value>]
911
+
912
+ FLAGS
913
+ -m, --message=<value> Commit message for bare-path pushes (ignored on clone-path; push what is already committed)
914
+ --dir=<value> Local directory to push (defaults to QUONFIG_DIR env var)
915
+ --no-pin-write Do not offer to write workspace slug into quonfig.json on success
916
+ --skip-validate Skip `qfg validate` preflight
917
+ --workspace=<value> Workspace slug or UUID (defaults to active profile)
918
+ --yes Skip the standard Y/N confirm. Never skips typed-slug prompts.
919
+
920
+ GLOBAL FLAGS
921
+ --[no-]interactive Force interactive mode
922
+ --json Format output as json.
923
+ --no-color Do not colorize output
924
+ --verbose Verbose output
925
+
926
+ DESCRIPTION
927
+ Push local config changes up to your workspace on Quonfig cloud.
928
+
929
+ Enforces three guards before touching the remote:
930
+ 1. The dir's quonfig.json "workspace" pin must match the backend (if set).
931
+ 2. The dir's git origin must match the backend repo URL (if set).
932
+ 3. A diff summary is shown; destructive changes (10+ deletes, >=25% of
933
+ files, or an unpinned dir) require typing the workspace slug to confirm.
934
+
935
+ qfg push # uses QUONFIG_DIR + profile workspace
936
+ qfg push --dir ./our-config
937
+ qfg push --dir ./our-config --workspace acme-prod
938
+ qfg push --dir ./our-config --yes # skip normal Y/N (never skips typed-slug)
939
+ qfg push --dir ./our-config --skip-validate
940
+
941
+ EXAMPLES
942
+ $ qfg push --dir ./our-config
943
+
944
+ $ qfg push --workspace acme-prod --dir ./our-config
945
+
946
+ $ qfg push --dir ./our-config --yes
947
+ ```
948
+
949
+ _See code: [src/commands/push.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/push.ts)_
899
950
 
900
951
  ## `qfg schema NAME`
901
952
 
@@ -933,7 +984,7 @@ EXAMPLES
933
984
  $ qfg schema my-schema --set-json-schema=@schemas/my-schema.json --protected
934
985
  ```
935
986
 
936
- _See code: [src/commands/schema.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/schema.ts)_
987
+ _See code: [src/commands/schema.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/schema.ts)_
937
988
 
938
989
  ## `qfg sdk-key`
939
990
 
@@ -960,7 +1011,7 @@ EXAMPLES
960
1011
  $ qfg sdk-key revoke <key-id>
961
1012
  ```
962
1013
 
963
- _See code: [src/commands/sdk-key.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/sdk-key.ts)_
1014
+ _See code: [src/commands/sdk-key.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/sdk-key.ts)_
964
1015
 
965
1016
  ## `qfg sdk-key create`
966
1017
 
@@ -992,7 +1043,7 @@ EXAMPLES
992
1043
  $ qfg sdk-key create --environment staging --type browser
993
1044
  ```
994
1045
 
995
- _See code: [src/commands/sdk-key/create.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/sdk-key/create.ts)_
1046
+ _See code: [src/commands/sdk-key/create.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/sdk-key/create.ts)_
996
1047
 
997
1048
  ## `qfg sdk-key list`
998
1049
 
@@ -1021,7 +1072,7 @@ EXAMPLES
1021
1072
  $ qfg sdk-key list --environment production
1022
1073
  ```
1023
1074
 
1024
- _See code: [src/commands/sdk-key/list.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/sdk-key/list.ts)_
1075
+ _See code: [src/commands/sdk-key/list.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/sdk-key/list.ts)_
1025
1076
 
1026
1077
  ## `qfg sdk-key revoke KEYID`
1027
1078
 
@@ -1048,7 +1099,7 @@ EXAMPLES
1048
1099
  $ qfg sdk-key revoke a1b2c3d4-e5f6-7890-abcd-ef1234567890
1049
1100
  ```
1050
1101
 
1051
- _See code: [src/commands/sdk-key/revoke.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/sdk-key/revoke.ts)_
1102
+ _See code: [src/commands/sdk-key/revoke.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/sdk-key/revoke.ts)_
1052
1103
 
1053
1104
  ## `qfg set-default [NAME]`
1054
1105
 
@@ -1119,7 +1170,7 @@ EXAMPLES
1119
1170
  $ qfg pull && qfg config-schema
1120
1171
  ```
1121
1172
 
1122
- _See code: [src/commands/set-default.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/set-default.ts)_
1173
+ _See code: [src/commands/set-default.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/set-default.ts)_
1123
1174
 
1124
1175
  ## `qfg set-rollout [NAME]`
1125
1176
 
@@ -1196,7 +1247,7 @@ EXAMPLES
1196
1247
  $ qfg set-rollout my.variant.flag --environment production --weights "a:33,b:33,c:34" --hash-by user.id
1197
1248
  ```
1198
1249
 
1199
- _See code: [src/commands/set-rollout.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/set-rollout.ts)_
1250
+ _See code: [src/commands/set-rollout.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/set-rollout.ts)_
1200
1251
 
1201
1252
  ## `qfg sync`
1202
1253
 
@@ -1228,7 +1279,7 @@ EXAMPLES
1228
1279
  $ qfg sync --watch --dir ./our-config --interval 30
1229
1280
  ```
1230
1281
 
1231
- _See code: [src/commands/sync.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/sync.ts)_
1282
+ _See code: [src/commands/sync.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/sync.ts)_
1232
1283
 
1233
1284
  ## `qfg toggle [NAME]`
1234
1285
 
@@ -1330,7 +1381,7 @@ EXAMPLES
1330
1381
  $ qfg verify --json
1331
1382
  ```
1332
1383
 
1333
- _See code: [src/commands/verify.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/verify.ts)_
1384
+ _See code: [src/commands/verify.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/verify.ts)_
1334
1385
 
1335
1386
  ## `qfg whoami`
1336
1387
 
@@ -1353,7 +1404,7 @@ EXAMPLES
1353
1404
  $ qfg whoami
1354
1405
  ```
1355
1406
 
1356
- _See code: [src/commands/whoami.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/whoami.ts)_
1407
+ _See code: [src/commands/whoami.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/whoami.ts)_
1357
1408
 
1358
1409
  ## `qfg workspace`
1359
1410
 
@@ -1376,7 +1427,7 @@ EXAMPLES
1376
1427
  $ qfg workspace
1377
1428
  ```
1378
1429
 
1379
- _See code: [src/commands/workspace.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/workspace.ts)_
1430
+ _See code: [src/commands/workspace.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/workspace.ts)_
1380
1431
 
1381
1432
  ## `qfg workspace bootstrap`
1382
1433
 
@@ -1409,15 +1460,18 @@ EXAMPLES
1409
1460
  $ qfg workspace bootstrap --dir ./our-config --skip-validate
1410
1461
  ```
1411
1462
 
1412
- _See code: [src/commands/workspace/bootstrap.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/workspace/bootstrap.ts)_
1463
+ _See code: [src/commands/workspace/bootstrap.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/workspace/bootstrap.ts)_
1413
1464
 
1414
- ## `qfg workspace switch`
1465
+ ## `qfg workspace switch [SLUG]`
1415
1466
 
1416
1467
  Switch to a different workspace
1417
1468
 
1418
1469
  ```
1419
1470
  USAGE
1420
- $ qfg workspace switch [--json] [--interactive] [--no-color] [--verbose]
1471
+ $ qfg workspace switch [SLUG] [--json] [--interactive] [--no-color] [--verbose]
1472
+
1473
+ ARGUMENTS
1474
+ SLUG Workspace slug (or UUID) to switch to. Omit for an interactive picker.
1421
1475
 
1422
1476
  GLOBAL FLAGS
1423
1477
  --[no-]interactive Force interactive mode
@@ -1430,9 +1484,11 @@ DESCRIPTION
1430
1484
 
1431
1485
  EXAMPLES
1432
1486
  $ qfg workspace switch
1487
+
1488
+ $ qfg workspace switch my-workspace-slug
1433
1489
  ```
1434
1490
 
1435
- _See code: [src/commands/workspace/switch.ts](https://github.com/quonfig/cli/blob/v0.0.14/src/commands/workspace/switch.ts)_
1491
+ _See code: [src/commands/workspace/switch.ts](https://github.com/quonfig/cli/blob/v0.0.16/src/commands/workspace/switch.ts)_
1436
1492
  <!-- commandsstop -->
1437
1493
 
1438
1494
  ## Local Development
@@ -8,6 +8,7 @@ export default class Init extends BaseCommand {
8
8
  static flags: {
9
9
  'dry-run': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
10
10
  samples: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
+ workspace: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
11
12
  };
12
13
  run(): Promise<object>;
13
14
  }
@@ -1,6 +1,7 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import { BaseCommand } from '../index.js';
3
3
  import { executeInit, planInit } from '../init/init-workspace.js';
4
+ import { writeWorkspaceSlug } from '../util/quonfig-json.js';
4
5
  export default class Init extends BaseCommand {
5
6
  static args = {
6
7
  directory: Args.string({
@@ -25,6 +26,10 @@ export default class Init extends BaseCommand {
25
26
  allowNo: true,
26
27
  description: 'Include sample configs (default: yes on first init, no on update)',
27
28
  }),
29
+ workspace: Flags.string({
30
+ description: 'Workspace slug to pin in quonfig.json (Guard 1). If omitted, no pin is written.',
31
+ required: false,
32
+ }),
28
33
  };
29
34
  async run() {
30
35
  const { args, flags } = await this.parse(Init);
@@ -45,6 +50,9 @@ export default class Init extends BaseCommand {
45
50
  this.log(` + ${action.description}`);
46
51
  }
47
52
  }
53
+ if (flags.workspace) {
54
+ this.log(` + Pin quonfig.json to workspace "${flags.workspace}"`);
55
+ }
48
56
  if (skipActions.length > 0) {
49
57
  this.log('\nSkipped:');
50
58
  for (const action of skipActions) {
@@ -66,10 +74,20 @@ export default class Init extends BaseCommand {
66
74
  }
67
75
  // Execute for real
68
76
  executeInit(plan, dir);
77
+ // If the user passed --workspace, pin the slug in quonfig.json
78
+ // (Guard 1 in project/plans/cli-git-sync.md). We deliberately keep this
79
+ // non-interactive: flag → write, no flag → no write. Users who need the
80
+ // pin later can run `qfg pull` (backfills) or edit the file directly.
81
+ if (flags.workspace) {
82
+ await writeWorkspaceSlug(dir, flags.workspace);
83
+ }
69
84
  this.log(`${mode} workspace: ${dir === '.' ? process.cwd() : dir}\n`);
70
85
  for (const action of writeActions) {
71
86
  this.log(` + ${action.description}`);
72
87
  }
88
+ if (flags.workspace) {
89
+ this.log(` + Pin quonfig.json to workspace "${flags.workspace}"`);
90
+ }
73
91
  for (const action of skipActions) {
74
92
  this.log(` - ${action.description}`);
75
93
  }
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { BaseCommand } from '../index.js';
5
5
  import { CrossSourceError, assertSourceMatches, readImportState } from '../migrate/import-state.js';
6
6
  import { applyLocalMigration } from '../migrate/local-write.js';
7
- import { PushConflictError } from '../migrate/push-strategy.js';
7
+ import { PushConflictError } from '../util/clone-and-stack-push.js';
8
8
  import { pushMigrationToCloud } from '../migrate/push-to-cloud.js';
9
9
  import { UnknownSourceError, getSource } from '../migrate/registry.js';
10
10
  import { NotYetImplementedError } from '../migrate/source.js';
@@ -8,8 +8,21 @@ export default class Pull extends BaseCommand {
8
8
  workspace: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
9
9
  };
10
10
  run(): Promise<JsonObj | void>;
11
+ /**
12
+ * Local-only backfill of the `workspace` pin in `quonfig.json`.
13
+ *
14
+ * - Missing pin → write it from the token response's `workspaceSlug`.
15
+ * - Pin already set and matches → no-op.
16
+ * - Pin already set and disagrees → log a warning showing both values
17
+ * and LEAVE IT ALONE. Overwriting someone's explicit pin is a Guard 2
18
+ * concern, not a pull concern — the next `qfg push` is where the
19
+ * identity-check dispatches a hard abort.
20
+ *
21
+ * This never throws — a broken `quonfig.json` shouldn't fail `qfg pull`.
22
+ */
23
+ private backfillWorkspacePin;
11
24
  private looksLike401;
12
25
  private maybeWriteQuonfigDir;
13
26
  private refreshAndGetUrl;
14
- private resolveRepoUrl;
27
+ private resolveTokenEntry;
15
28
  }
@@ -5,6 +5,7 @@ import { BaseCommand } from '../index.js';
5
5
  import { getActiveProfile, loadAuthConfig } from '../util/token-storage.js';
6
6
  import { loadGiteaToken, isGiteaTokenExpired, saveGiteaToken } from '../util/gitea-token-storage.js';
7
7
  import { mintAndStoreGiteaReadToken, mintGiteaToken } from '../util/gitea-api.js';
8
+ import { readWorkspaceSlug, writeWorkspaceSlug } from '../util/quonfig-json.js';
8
9
  import { isGitRepo, getRemoteUrl, gitFetch, gitSetRemote, canFastForward, hasDivergedFromRemote, gitMergeFfOnly, gitClone, displayUrl, } from '../util/git-ops.js';
9
10
  export default class Pull extends BaseCommand {
10
11
  static description = `Clone or update a local copy of your workspace config files.
@@ -56,7 +57,8 @@ CLI shortcuts (no JSON editing needed for simple cases):
56
57
  }
57
58
  this.verboseLog('Pull', { workspaceId, dir: resolvedDir });
58
59
  // Get or mint Gitea read token
59
- const repoUrl = await this.resolveRepoUrl(workspaceId);
60
+ const tokenEntry = await this.resolveTokenEntry(workspaceId);
61
+ const repoUrl = tokenEntry.repoUrl;
60
62
  // Perform git operation
61
63
  const isRepo = await isGitRepo(resolvedDir);
62
64
  if (isRepo) {
@@ -132,8 +134,61 @@ CLI shortcuts (no JSON editing needed for simple cases):
132
134
  }
133
135
  // Write QUONFIG_DIR to ~/.quonfig/config if not set
134
136
  await this.maybeWriteQuonfigDir(resolvedDir);
137
+ // Backfill the `workspace` pin in `quonfig.json` if missing
138
+ // (Guard 1 in project/plans/cli-git-sync.md). Local-only write — we
139
+ // intentionally do NOT commit or push here; the user's next `qfg push`
140
+ // will sweep it up naturally.
141
+ await this.backfillWorkspacePin(resolvedDir, tokenEntry, workspaceId);
135
142
  return { dir: resolvedDir, workspaceId };
136
143
  }
144
+ /**
145
+ * Local-only backfill of the `workspace` pin in `quonfig.json`.
146
+ *
147
+ * - Missing pin → write it from the token response's `workspaceSlug`.
148
+ * - Pin already set and matches → no-op.
149
+ * - Pin already set and disagrees → log a warning showing both values
150
+ * and LEAVE IT ALONE. Overwriting someone's explicit pin is a Guard 2
151
+ * concern, not a pull concern — the next `qfg push` is where the
152
+ * identity-check dispatches a hard abort.
153
+ *
154
+ * This never throws — a broken `quonfig.json` shouldn't fail `qfg pull`.
155
+ */
156
+ async backfillWorkspacePin(dir, tokenEntry, workspaceId) {
157
+ let backendSlug = tokenEntry.workspaceSlug;
158
+ if (!backendSlug) {
159
+ // Older cached entries lack the slug. Mint a fresh one solely to learn the
160
+ // slug. Cheap (one API call), and the refresh updates the cache for next time.
161
+ try {
162
+ const fresh = await mintAndStoreGiteaReadToken(workspaceId);
163
+ backendSlug = fresh.workspaceSlug;
164
+ }
165
+ catch (error) {
166
+ this.verboseLog('Pull', `Could not mint token to learn workspace slug: ${String(error)}`);
167
+ return;
168
+ }
169
+ }
170
+ if (!backendSlug) {
171
+ this.verboseLog('Pull', 'Backend did not return a workspaceSlug; skipping pin backfill.');
172
+ return;
173
+ }
174
+ try {
175
+ const existingPin = await readWorkspaceSlug(dir);
176
+ if (!existingPin) {
177
+ await writeWorkspaceSlug(dir, backendSlug);
178
+ this.verboseLog('Pull', `Backfilled workspace pin "${backendSlug}" into quonfig.json.`);
179
+ return;
180
+ }
181
+ if (existingPin !== backendSlug) {
182
+ this.log('');
183
+ this.log(`Warning: quonfig.json pins workspace "${existingPin}", but the backend says this workspace is "${backendSlug}".`);
184
+ this.log('Leaving the existing pin in place. Run `qfg push` to see the full identity check.');
185
+ }
186
+ }
187
+ catch (error) {
188
+ // Non-fatal — pull itself already succeeded.
189
+ this.verboseLog('Pull', `Could not backfill workspace pin: ${String(error)}`);
190
+ }
191
+ }
137
192
  looksLike401(err) {
138
193
  const msg = err instanceof Error ? err.message : String(err);
139
194
  return msg.includes('401') || msg.toLowerCase().includes('authentication failed');
@@ -158,17 +213,22 @@ CLI shortcuts (no JSON editing needed for simple cases):
158
213
  }
159
214
  async refreshAndGetUrl(workspaceId) {
160
215
  const data = await mintGiteaToken(workspaceId, 'read', 'pull');
161
- const entry = { token: data.token, repoUrl: data.repoUrl, expiresAt: data.expiresAt };
216
+ const entry = {
217
+ token: data.token,
218
+ repoUrl: data.repoUrl,
219
+ expiresAt: data.expiresAt,
220
+ workspaceSlug: data.workspaceSlug,
221
+ };
162
222
  await saveGiteaToken(workspaceId, entry);
163
223
  return entry.repoUrl;
164
224
  }
165
- async resolveRepoUrl(workspaceId) {
225
+ async resolveTokenEntry(workspaceId) {
166
226
  let entry = await loadGiteaToken(workspaceId);
167
227
  if (!entry || isGiteaTokenExpired(entry)) {
168
228
  this.verboseLog('Pull', 'Minting new Gitea read token...');
169
229
  entry = await mintAndStoreGiteaReadToken(workspaceId);
170
230
  }
171
- return entry.repoUrl;
231
+ return entry;
172
232
  }
173
233
  }
174
234
  /**
@@ -0,0 +1,53 @@
1
+ import type { JsonObj } from '../result.js';
2
+ import { BaseCommand } from '../index.js';
3
+ import { GiteaTokenResponse } from '../util/gitea-api.js';
4
+ import type { GiteaTokenMintResult } from '../push/run-push.js';
5
+ import { runPush } from '../push/run-push.js';
6
+ export default class Push extends BaseCommand {
7
+ static description: string;
8
+ static examples: string[];
9
+ static flags: {
10
+ dir: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
11
+ workspace: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
12
+ yes: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
13
+ 'skip-validate': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
14
+ 'no-pin-write': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
15
+ message: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
16
+ };
17
+ run(): Promise<JsonObj | void>;
18
+ }
19
+ /**
20
+ * Map a raw `gitea.token` API response into the `GiteaTokenMintResult` shape
21
+ * the push core (`runPush`) consumes. Kept as a pure function so we can unit
22
+ * test the wiring without a network — specifically, to regression-test that
23
+ * `workspaceId` on the result is the UUID the server returned, not the slug.
24
+ *
25
+ * The bug this guards against: before the backend started returning
26
+ * `workspaceId`, this module used `resp.workspaceSlug` as the `workspaceId`
27
+ * field, which caused `checkIdentity` to fail when the user passed
28
+ * `--workspace <UUID>` (requested=UUID, backend.workspaceId=slug → mismatch).
29
+ */
30
+ export declare function giteaResponseToMintResult(resp: GiteaTokenResponse): GiteaTokenMintResult;
31
+ /**
32
+ * Wire the real implementations of every dependency runPush needs. Kept in
33
+ * the command file so the pure core stays free of oclif / git shell imports.
34
+ *
35
+ * Returns a `{deps, cleanup}` pair. The caller MUST call `cleanup()` in a
36
+ * finally block so the bare-path probe clone is removed even on errors.
37
+ */
38
+ export declare function buildRealDeps(cmd: Push): {
39
+ deps: Parameters<typeof runPush>[1];
40
+ cleanup: () => Promise<void>;
41
+ };
42
+ /**
43
+ * Copy the contents of `sourceDir` into `destDir`, skipping `.git/` and any
44
+ * temp push-clone scratch directories. Used as the `applyDelta` callback for
45
+ * bare-path pushes — we mirror local files into a fresh clone so that
46
+ * `cloneAndStackPush`'s `git add --all` + diff will reflect the union of
47
+ * (deletes, modifies, adds) we intend.
48
+ *
49
+ * IMPORTANT: this deletes files in destDir that are not in sourceDir so that
50
+ * deletions make it into the push. Without this the clone-and-stack produces
51
+ * an "adds-only" delta and destructive heuristics would never fire in the UI.
52
+ */
53
+ export declare function copyDirMirror(sourceDir: string, destDir: string): Promise<void>;