artifact-cafe 0.0.15 → 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.
- package/dist/index.js +1006 -394
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -497,245 +497,573 @@ function elementLabel(el) {
|
|
|
497
497
|
return classes.length ? `<${el.tag}.${classes.join(".")}>` : `<${el.tag}>`;
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
-
// ../shared/src/
|
|
500
|
+
// ../shared/src/data.ts
|
|
501
|
+
var DATA_MANIFEST_PATH = ".artifact/data.json";
|
|
502
|
+
var DATA_NAME_RE = /^[a-z][a-z0-9_]*$/;
|
|
503
|
+
var DATA_NAME_MAX = 64;
|
|
504
|
+
var DATA_RESERVED_FIELDS = /* @__PURE__ */ new Set([
|
|
505
|
+
"id",
|
|
506
|
+
"created_at",
|
|
507
|
+
"updated_at",
|
|
508
|
+
"created_by",
|
|
509
|
+
"mine"
|
|
510
|
+
]);
|
|
511
|
+
var DATA_LIMITS = {
|
|
512
|
+
artifactRateWindowSeconds: 3600,
|
|
513
|
+
artifactReadHourlyLimit: 1e4,
|
|
514
|
+
artifactWriteHourlyLimit: 1e3,
|
|
515
|
+
defaultReadRate: "120/minute/ip",
|
|
516
|
+
defaultWriteRate: "10/minute/ip",
|
|
517
|
+
maxCollections: 10,
|
|
518
|
+
maxFieldsPerCollection: 50,
|
|
519
|
+
maxListLimit: 200,
|
|
520
|
+
maxManifestBytes: 64 * 1024,
|
|
521
|
+
maxRecordBytes: 16 * 1024,
|
|
522
|
+
maxRecordsPerArtifact: 1e4
|
|
523
|
+
};
|
|
524
|
+
var DATA_ACCESS_DEFAULTS = {
|
|
525
|
+
delete: "owner",
|
|
526
|
+
insert: "owner",
|
|
527
|
+
read: "public",
|
|
528
|
+
update: "owner"
|
|
529
|
+
};
|
|
530
|
+
var DATA_RATE_LIMIT_RE = /^([1-9]\d{0,5})\/(hour|minute)\/ip$/;
|
|
531
|
+
var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
532
|
+
function validateFieldValue(name, field, value, errors) {
|
|
533
|
+
switch (field.type) {
|
|
534
|
+
case "string": {
|
|
535
|
+
if (typeof value !== "string") {
|
|
536
|
+
errors.push(`${name}: expected a string`);
|
|
537
|
+
return value;
|
|
538
|
+
}
|
|
539
|
+
const s = field.trim ? value.trim() : value;
|
|
540
|
+
if (field.maxLength !== void 0 && s.length > field.maxLength) {
|
|
541
|
+
errors.push(`${name}: longer than maxLength ${field.maxLength}`);
|
|
542
|
+
}
|
|
543
|
+
return s;
|
|
544
|
+
}
|
|
545
|
+
case "number":
|
|
546
|
+
case "integer": {
|
|
547
|
+
if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
|
|
548
|
+
errors.push(`${name}: expected a ${field.type}`);
|
|
549
|
+
return value;
|
|
550
|
+
}
|
|
551
|
+
if (field.type === "integer" && !Number.isInteger(value)) {
|
|
552
|
+
errors.push(`${name}: expected an integer`);
|
|
553
|
+
}
|
|
554
|
+
if (field.minimum !== void 0 && value < field.minimum) {
|
|
555
|
+
errors.push(`${name}: below minimum ${field.minimum}`);
|
|
556
|
+
}
|
|
557
|
+
if (field.maximum !== void 0 && value > field.maximum) {
|
|
558
|
+
errors.push(`${name}: above maximum ${field.maximum}`);
|
|
559
|
+
}
|
|
560
|
+
return value;
|
|
561
|
+
}
|
|
562
|
+
case "boolean": {
|
|
563
|
+
if (typeof value !== "boolean") {
|
|
564
|
+
errors.push(`${name}: expected a boolean`);
|
|
565
|
+
}
|
|
566
|
+
return value;
|
|
567
|
+
}
|
|
568
|
+
case "datetime": {
|
|
569
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
570
|
+
errors.push(`${name}: expected an ISO 8601 datetime string`);
|
|
571
|
+
}
|
|
572
|
+
return value;
|
|
573
|
+
}
|
|
574
|
+
case "url": {
|
|
575
|
+
if (typeof value !== "string" || !canParseUrl(value)) {
|
|
576
|
+
errors.push(`${name}: expected a valid URL`);
|
|
577
|
+
}
|
|
578
|
+
return value;
|
|
579
|
+
}
|
|
580
|
+
case "email": {
|
|
581
|
+
if (typeof value !== "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
582
|
+
errors.push(`${name}: expected an email address`);
|
|
583
|
+
}
|
|
584
|
+
return value;
|
|
585
|
+
}
|
|
586
|
+
case "array": {
|
|
587
|
+
if (!Array.isArray(value)) {
|
|
588
|
+
errors.push(`${name}: expected an array`);
|
|
589
|
+
return value;
|
|
590
|
+
}
|
|
591
|
+
if (field.maxLength !== void 0 && value.length > field.maxLength) {
|
|
592
|
+
errors.push(`${name}: more than maxLength ${field.maxLength} items`);
|
|
593
|
+
}
|
|
594
|
+
return value;
|
|
595
|
+
}
|
|
596
|
+
case "object": {
|
|
597
|
+
if (!isPlainObject(value)) {
|
|
598
|
+
errors.push(`${name}: expected an object`);
|
|
599
|
+
}
|
|
600
|
+
return value;
|
|
601
|
+
}
|
|
602
|
+
default: {
|
|
603
|
+
errors.push(`${name}: unknown field type`);
|
|
604
|
+
return value;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
function canParseUrl(value) {
|
|
609
|
+
try {
|
|
610
|
+
return Boolean(new URL(value));
|
|
611
|
+
} catch {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function validateRecordInput(collection, input, opts = {}) {
|
|
616
|
+
if (!isPlainObject(input)) {
|
|
617
|
+
return { errors: ["record body must be a JSON object"], ok: false };
|
|
618
|
+
}
|
|
619
|
+
const errors = [];
|
|
620
|
+
const data2 = {};
|
|
621
|
+
for (const key of Object.keys(input)) {
|
|
622
|
+
if (DATA_RESERVED_FIELDS.has(key)) {
|
|
623
|
+
errors.push(`${key}: reserved field (server-stamped)`);
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
const field = collection.fields[key];
|
|
627
|
+
if (!(Object.hasOwn(collection.fields, key) && field)) {
|
|
628
|
+
errors.push(`${key}: not a declared field`);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const value = input[key];
|
|
632
|
+
if (value === null || value === void 0) {
|
|
633
|
+
if (!field.required) {
|
|
634
|
+
data2[key] = null;
|
|
635
|
+
} else if (opts.partial) {
|
|
636
|
+
errors.push(`${key}: required (cannot be null)`);
|
|
637
|
+
}
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
data2[key] = validateFieldValue(key, field, value, errors);
|
|
641
|
+
}
|
|
642
|
+
if (opts.partial) {
|
|
643
|
+
if (Object.keys(data2).length === 0 && errors.length === 0) {
|
|
644
|
+
errors.push("no fields to update");
|
|
645
|
+
}
|
|
646
|
+
} else {
|
|
647
|
+
for (const [name, field] of Object.entries(collection.fields)) {
|
|
648
|
+
const present = data2[name] !== void 0 && data2[name] !== null;
|
|
649
|
+
if (present) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
if (field.default !== void 0) {
|
|
653
|
+
data2[name] = field.default;
|
|
654
|
+
} else if (field.required) {
|
|
655
|
+
errors.push(`${name}: required`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (errors.length > 0) {
|
|
660
|
+
return { errors, ok: false };
|
|
661
|
+
}
|
|
662
|
+
return { data: data2, ok: true };
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// ../shared/src/data-api.ts
|
|
501
666
|
import { z as z8 } from "zod";
|
|
502
|
-
var
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
667
|
+
var dataRecordSchema = z8.object({
|
|
668
|
+
created_at: z8.string(),
|
|
669
|
+
created_by: z8.string().nullable(),
|
|
670
|
+
id: z8.string(),
|
|
671
|
+
updated_at: z8.string()
|
|
672
|
+
}).catchall(z8.unknown());
|
|
673
|
+
var dataPublicRecordSchema = z8.object({
|
|
674
|
+
created_at: z8.string(),
|
|
675
|
+
created_by: z8.never().optional(),
|
|
676
|
+
id: z8.string(),
|
|
677
|
+
mine: z8.boolean(),
|
|
678
|
+
updated_at: z8.string()
|
|
679
|
+
}).catchall(z8.unknown());
|
|
680
|
+
var dataPublicListResponseSchema = z8.object({
|
|
681
|
+
nextCursor: z8.string().nullable(),
|
|
682
|
+
records: z8.array(dataPublicRecordSchema)
|
|
683
|
+
});
|
|
684
|
+
var dataListResponseSchema = z8.object({
|
|
685
|
+
nextCursor: z8.string().nullable(),
|
|
686
|
+
records: z8.array(dataRecordSchema)
|
|
687
|
+
});
|
|
688
|
+
var dataCollectionSummarySchema = z8.object({
|
|
689
|
+
access: z8.record(z8.string(), z8.string()).optional(),
|
|
690
|
+
count: z8.number().int().nonnegative().optional(),
|
|
691
|
+
name: z8.string(),
|
|
692
|
+
rateLimit: z8.string().optional()
|
|
693
|
+
});
|
|
694
|
+
var dataSummaryResponseSchema = z8.object({
|
|
695
|
+
collections: z8.array(dataCollectionSummarySchema),
|
|
696
|
+
enabled: z8.boolean()
|
|
697
|
+
});
|
|
698
|
+
var dataClearResponseSchema = z8.object({
|
|
699
|
+
cleared: z8.number().int().nonnegative()
|
|
507
700
|
});
|
|
508
701
|
|
|
509
|
-
// ../shared/src/
|
|
702
|
+
// ../shared/src/data-manifest.ts
|
|
510
703
|
import { z as z9 } from "zod";
|
|
511
|
-
var
|
|
512
|
-
|
|
513
|
-
|
|
704
|
+
var nameSchema = z9.string().max(DATA_NAME_MAX).regex(DATA_NAME_RE, "must match ^[a-z][a-z0-9_]*$");
|
|
705
|
+
var fieldTypeSchema = z9.enum([
|
|
706
|
+
"string",
|
|
707
|
+
"number",
|
|
708
|
+
"integer",
|
|
709
|
+
"boolean",
|
|
710
|
+
"datetime",
|
|
711
|
+
"url",
|
|
712
|
+
"email",
|
|
713
|
+
"array",
|
|
714
|
+
"object"
|
|
715
|
+
]);
|
|
716
|
+
var fieldSchema = z9.strictObject({
|
|
717
|
+
default: z9.unknown().optional(),
|
|
718
|
+
maximum: z9.number().optional(),
|
|
719
|
+
maxLength: z9.number().int().positive().optional(),
|
|
720
|
+
minimum: z9.number().optional(),
|
|
721
|
+
required: z9.boolean().optional(),
|
|
722
|
+
trim: z9.boolean().optional(),
|
|
723
|
+
type: fieldTypeSchema
|
|
724
|
+
}).superRefine((field, ctx) => {
|
|
725
|
+
if (field.default === void 0) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (!defaultMatchesType(field)) {
|
|
729
|
+
ctx.addIssue({
|
|
730
|
+
code: "custom",
|
|
731
|
+
message: `default value does not match type "${field.type}"`,
|
|
732
|
+
path: ["default"]
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
function defaultMatchesType(field) {
|
|
737
|
+
if (field.required && field.default === null) {
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
740
|
+
return validateRecordInput(
|
|
741
|
+
{
|
|
742
|
+
access: DATA_ACCESS_DEFAULTS,
|
|
743
|
+
fields: { value: { ...field, default: void 0 } }
|
|
744
|
+
},
|
|
745
|
+
{ value: field.default }
|
|
746
|
+
).ok;
|
|
747
|
+
}
|
|
748
|
+
var accessLevelSchema = z9.enum(["public", "creator", "owner", "none"]);
|
|
749
|
+
var accessSchema = z9.strictObject({
|
|
750
|
+
delete: accessLevelSchema.default("owner"),
|
|
751
|
+
insert: accessLevelSchema.default("owner"),
|
|
752
|
+
read: accessLevelSchema.default("public"),
|
|
753
|
+
update: accessLevelSchema.default("owner")
|
|
754
|
+
}).prefault({});
|
|
755
|
+
var collectionSchema = z9.strictObject({
|
|
756
|
+
access: accessSchema,
|
|
757
|
+
fields: z9.record(nameSchema, fieldSchema),
|
|
758
|
+
publicMutation: z9.literal("open").optional(),
|
|
759
|
+
rateLimit: z9.string().regex(DATA_RATE_LIMIT_RE, 'expected "N/hour/ip" or "N/minute/ip"').optional()
|
|
760
|
+
}).superRefine((collection, ctx) => {
|
|
761
|
+
const fieldNames = Object.keys(collection.fields);
|
|
762
|
+
if (fieldNames.length > DATA_LIMITS.maxFieldsPerCollection) {
|
|
763
|
+
ctx.addIssue({
|
|
764
|
+
code: "custom",
|
|
765
|
+
message: `at most ${DATA_LIMITS.maxFieldsPerCollection} fields per collection`,
|
|
766
|
+
path: ["fields"]
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
for (const name of fieldNames) {
|
|
770
|
+
if (DATA_RESERVED_FIELDS.has(name)) {
|
|
771
|
+
ctx.addIssue({
|
|
772
|
+
code: "custom",
|
|
773
|
+
message: `"${name}" is reserved (server-stamped)`,
|
|
774
|
+
path: ["fields", name]
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
for (const action of ["update", "delete"]) {
|
|
779
|
+
if (collection.access[action] === "public" && collection.publicMutation !== "open") {
|
|
780
|
+
ctx.addIssue({
|
|
781
|
+
code: "custom",
|
|
782
|
+
message: `access.${action} may only be "public" alongside "publicMutation": "open"`,
|
|
783
|
+
path: ["access", action]
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
var dataManifestSchema = z9.strictObject({
|
|
789
|
+
collections: z9.record(nameSchema, collectionSchema)
|
|
790
|
+
}).superRefine((manifest, ctx) => {
|
|
791
|
+
if (Object.keys(manifest.collections).length > DATA_LIMITS.maxCollections) {
|
|
792
|
+
ctx.addIssue({
|
|
793
|
+
code: "custom",
|
|
794
|
+
message: `at most ${DATA_LIMITS.maxCollections} collections`,
|
|
795
|
+
path: ["collections"]
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
function parseDataManifest(text) {
|
|
800
|
+
if (new TextEncoder().encode(text).length > DATA_LIMITS.maxManifestBytes) {
|
|
801
|
+
return {
|
|
802
|
+
errors: [`manifest larger than ${DATA_LIMITS.maxManifestBytes} bytes`],
|
|
803
|
+
ok: false
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
let raw;
|
|
807
|
+
try {
|
|
808
|
+
raw = JSON.parse(text);
|
|
809
|
+
} catch (err) {
|
|
810
|
+
return {
|
|
811
|
+
errors: [`not valid JSON: ${err instanceof Error ? err.message : err}`],
|
|
812
|
+
ok: false
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
const parsed = dataManifestSchema.safeParse(raw);
|
|
816
|
+
if (!parsed.success) {
|
|
817
|
+
return {
|
|
818
|
+
errors: parsed.error.issues.map(
|
|
819
|
+
(issue) => issue.path.length ? `${issue.path.join(".")}: ${issue.message}` : issue.message
|
|
820
|
+
),
|
|
821
|
+
ok: false
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
return { config: parsed.data, ok: true };
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// ../shared/src/errors.ts
|
|
828
|
+
import { z as z10 } from "zod";
|
|
829
|
+
var apiErrorSchema = z10.object({
|
|
830
|
+
code: z10.string(),
|
|
831
|
+
docsUrl: z10.string().url(),
|
|
832
|
+
message: z10.string(),
|
|
833
|
+
retryAfter: z10.number().int().positive().optional(),
|
|
834
|
+
scope: z10.enum(["ip", "artifact"]).optional()
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
// ../shared/src/feedback.ts
|
|
838
|
+
import { z as z11 } from "zod";
|
|
839
|
+
var feedbackRequestSchema = z11.object({
|
|
840
|
+
email: z11.string().trim().toLowerCase().email().optional().or(z11.literal("")),
|
|
841
|
+
message: z11.string().trim().min(1).max(4e3)
|
|
514
842
|
});
|
|
515
|
-
var feedbackResponseSchema =
|
|
516
|
-
sent:
|
|
843
|
+
var feedbackResponseSchema = z11.object({
|
|
844
|
+
sent: z11.literal(true)
|
|
517
845
|
});
|
|
518
846
|
|
|
519
847
|
// ../shared/src/grants.ts
|
|
520
|
-
import { z as
|
|
521
|
-
var grantRoleSchema =
|
|
522
|
-
var createGrantSchema =
|
|
523
|
-
email:
|
|
848
|
+
import { z as z12 } from "zod";
|
|
849
|
+
var grantRoleSchema = z12.enum(["viewer"]);
|
|
850
|
+
var createGrantSchema = z12.object({
|
|
851
|
+
email: z12.string().trim().toLowerCase().email().optional(),
|
|
524
852
|
role: grantRoleSchema.default("viewer"),
|
|
525
|
-
userId:
|
|
853
|
+
userId: z12.string().min(1).optional()
|
|
526
854
|
}).refine((body) => Boolean(body.email) !== Boolean(body.userId), {
|
|
527
855
|
message: "provide exactly one of email or userId"
|
|
528
856
|
});
|
|
529
|
-
var grantSchema =
|
|
530
|
-
createdAt:
|
|
531
|
-
email:
|
|
532
|
-
expiresAt:
|
|
533
|
-
id:
|
|
857
|
+
var grantSchema = z12.object({
|
|
858
|
+
createdAt: z12.string(),
|
|
859
|
+
email: z12.string().email().nullable(),
|
|
860
|
+
expiresAt: z12.string().nullable(),
|
|
861
|
+
id: z12.string(),
|
|
534
862
|
// The subject's avatar once they have an account; null otherwise.
|
|
535
|
-
image:
|
|
863
|
+
image: z12.string().nullable(),
|
|
536
864
|
// Present on the invited account after redemption / when granting an
|
|
537
865
|
// existing user; null for a still-pending email invite.
|
|
538
|
-
name:
|
|
539
|
-
redeemedAt:
|
|
866
|
+
name: z12.string().nullable(),
|
|
867
|
+
redeemedAt: z12.string().nullable(),
|
|
540
868
|
role: grantRoleSchema,
|
|
541
869
|
// Shown as "· pending" beside the role until the link is redeemed.
|
|
542
|
-
status:
|
|
870
|
+
status: z12.enum(["pending", "active"]),
|
|
543
871
|
// Display fragment of the live invitation token, or null once redeemed.
|
|
544
|
-
tokenPrefix:
|
|
545
|
-
userId:
|
|
872
|
+
tokenPrefix: z12.string().nullable(),
|
|
873
|
+
userId: z12.string().nullable()
|
|
546
874
|
});
|
|
547
875
|
var createdGrantSchema = grantSchema.extend({
|
|
548
|
-
url:
|
|
876
|
+
url: z12.string().url()
|
|
549
877
|
});
|
|
550
|
-
var grantListResponseSchema =
|
|
551
|
-
grants:
|
|
878
|
+
var grantListResponseSchema = z12.object({
|
|
879
|
+
grants: z12.array(grantSchema),
|
|
552
880
|
// The artifact's current general-access value, so the share surface can
|
|
553
881
|
// render the "General access" line without a second read.
|
|
554
|
-
visibility:
|
|
882
|
+
visibility: z12.enum(["public", "unlisted", "workspace", "private"]),
|
|
555
883
|
// The owning workspace's logo, so the share modal can draw the workspace
|
|
556
884
|
// itself rather than a generic glyph. Null when none was uploaded.
|
|
557
|
-
workspaceLogo:
|
|
885
|
+
workspaceLogo: z12.string().nullable().optional(),
|
|
558
886
|
// The owning workspace's display name, so "People with access" can name the
|
|
559
887
|
// audience `workspace` visibility actually admits ("All Acme members") rather
|
|
560
888
|
// than leaving an empty list to imply nobody. Null for a workspace-less
|
|
561
889
|
// artifact; optional so an older server still parses.
|
|
562
|
-
workspaceName:
|
|
890
|
+
workspaceName: z12.string().nullable().optional()
|
|
563
891
|
});
|
|
564
|
-
var revokeGrantResponseSchema =
|
|
565
|
-
revoked:
|
|
892
|
+
var revokeGrantResponseSchema = z12.object({
|
|
893
|
+
revoked: z12.literal(true)
|
|
566
894
|
});
|
|
567
|
-
var acceptGrantSchema =
|
|
568
|
-
token:
|
|
895
|
+
var acceptGrantSchema = z12.object({
|
|
896
|
+
token: z12.string().min(1)
|
|
569
897
|
});
|
|
570
|
-
var acceptGrantResponseSchema =
|
|
571
|
-
artifactId:
|
|
898
|
+
var acceptGrantResponseSchema = z12.object({
|
|
899
|
+
artifactId: z12.string(),
|
|
572
900
|
// The review shell URL to land on, carrying ?invited=1 for the banner.
|
|
573
|
-
artifactUrl:
|
|
574
|
-
slug:
|
|
575
|
-
title:
|
|
576
|
-
});
|
|
577
|
-
var workspaceGuestSchema =
|
|
578
|
-
artifacts:
|
|
579
|
-
|
|
580
|
-
artifactId:
|
|
581
|
-
grantId:
|
|
582
|
-
slug:
|
|
583
|
-
status:
|
|
584
|
-
title:
|
|
901
|
+
artifactUrl: z12.string().url(),
|
|
902
|
+
slug: z12.string(),
|
|
903
|
+
title: z12.string()
|
|
904
|
+
});
|
|
905
|
+
var workspaceGuestSchema = z12.object({
|
|
906
|
+
artifacts: z12.array(
|
|
907
|
+
z12.object({
|
|
908
|
+
artifactId: z12.string(),
|
|
909
|
+
grantId: z12.string(),
|
|
910
|
+
slug: z12.string(),
|
|
911
|
+
status: z12.enum(["pending", "active"]),
|
|
912
|
+
title: z12.string()
|
|
585
913
|
})
|
|
586
914
|
),
|
|
587
|
-
email:
|
|
588
|
-
image:
|
|
589
|
-
name:
|
|
590
|
-
userId:
|
|
591
|
-
});
|
|
592
|
-
var workspaceGuestListResponseSchema =
|
|
593
|
-
guests:
|
|
594
|
-
});
|
|
595
|
-
var sharedWithMeArtifactSchema =
|
|
596
|
-
artifactId:
|
|
597
|
-
slug:
|
|
598
|
-
title:
|
|
599
|
-
url:
|
|
915
|
+
email: z12.string().nullable(),
|
|
916
|
+
image: z12.string().nullable(),
|
|
917
|
+
name: z12.string().nullable(),
|
|
918
|
+
userId: z12.string().nullable()
|
|
919
|
+
});
|
|
920
|
+
var workspaceGuestListResponseSchema = z12.object({
|
|
921
|
+
guests: z12.array(workspaceGuestSchema)
|
|
922
|
+
});
|
|
923
|
+
var sharedWithMeArtifactSchema = z12.object({
|
|
924
|
+
artifactId: z12.string(),
|
|
925
|
+
slug: z12.string(),
|
|
926
|
+
title: z12.string(),
|
|
927
|
+
url: z12.string().url(),
|
|
600
928
|
// Who shared it, for the "Shared with me" list.
|
|
601
|
-
workspaceName:
|
|
929
|
+
workspaceName: z12.string()
|
|
602
930
|
});
|
|
603
|
-
var sharedWithMeListResponseSchema =
|
|
604
|
-
artifacts:
|
|
931
|
+
var sharedWithMeListResponseSchema = z12.object({
|
|
932
|
+
artifacts: z12.array(sharedWithMeArtifactSchema)
|
|
605
933
|
});
|
|
606
934
|
|
|
607
935
|
// ../shared/src/html-meta.ts
|
|
608
936
|
var HEAD_SCAN_CHARS = 64 * 1024;
|
|
609
937
|
|
|
610
938
|
// ../shared/src/me.ts
|
|
611
|
-
import { z as
|
|
939
|
+
import { z as z14 } from "zod";
|
|
612
940
|
|
|
613
941
|
// ../shared/src/workspaces.ts
|
|
614
|
-
import { z as
|
|
942
|
+
import { z as z13 } from "zod";
|
|
615
943
|
var WORKSPACE_HANDLE_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,30}[a-z0-9])$/;
|
|
616
|
-
var workspaceHandleUnavailableReasonSchema =
|
|
944
|
+
var workspaceHandleUnavailableReasonSchema = z13.enum([
|
|
617
945
|
"held",
|
|
618
946
|
"invalid",
|
|
619
947
|
"reserved",
|
|
620
948
|
"taken"
|
|
621
949
|
]);
|
|
622
|
-
var workspaceHandleAvailabilitySchema =
|
|
623
|
-
available:
|
|
624
|
-
handle:
|
|
950
|
+
var workspaceHandleAvailabilitySchema = z13.object({
|
|
951
|
+
available: z13.boolean(),
|
|
952
|
+
handle: z13.string(),
|
|
625
953
|
// Human-readable sentence for the field hint; null when available.
|
|
626
|
-
message:
|
|
954
|
+
message: z13.string().nullable(),
|
|
627
955
|
reason: workspaceHandleUnavailableReasonSchema.nullable()
|
|
628
956
|
});
|
|
629
|
-
var workspaceRoleSchema =
|
|
630
|
-
var workspaceInviteRoleSchema =
|
|
631
|
-
var workspaceHandleSchema =
|
|
957
|
+
var workspaceRoleSchema = z13.enum(["owner", "admin", "member"]);
|
|
958
|
+
var workspaceInviteRoleSchema = z13.enum(["admin", "member"]);
|
|
959
|
+
var workspaceHandleSchema = z13.string().min(3).max(32).regex(WORKSPACE_HANDLE_PATTERN, {
|
|
632
960
|
message: "handle must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
633
961
|
});
|
|
634
|
-
var createWorkspaceSchema =
|
|
962
|
+
var createWorkspaceSchema = z13.object({
|
|
635
963
|
handle: workspaceHandleSchema.optional(),
|
|
636
|
-
name:
|
|
964
|
+
name: z13.string().trim().min(1).max(100)
|
|
637
965
|
});
|
|
638
|
-
var updateWorkspaceSchema =
|
|
966
|
+
var updateWorkspaceSchema = z13.object({
|
|
639
967
|
handle: workspaceHandleSchema.optional(),
|
|
640
|
-
name:
|
|
968
|
+
name: z13.string().trim().min(1).max(100).optional()
|
|
641
969
|
}).refine((body) => body.handle !== void 0 || body.name !== void 0, {
|
|
642
970
|
message: "provide at least one of name or handle"
|
|
643
971
|
});
|
|
644
|
-
var workspaceSchema =
|
|
645
|
-
createdAt:
|
|
646
|
-
handle:
|
|
972
|
+
var workspaceSchema = z13.object({
|
|
973
|
+
createdAt: z13.string(),
|
|
974
|
+
handle: z13.string(),
|
|
647
975
|
// ISO timestamp when `{handle}.artifact.cafe` was provisioned, or null.
|
|
648
976
|
// Set by the one-time, self-serve enable flow (§4.4); never auto at
|
|
649
977
|
// create, and always null for personal workspaces.
|
|
650
|
-
hostnameProvisionedAt:
|
|
651
|
-
id:
|
|
978
|
+
hostnameProvisionedAt: z13.string().nullable(),
|
|
979
|
+
id: z13.string(),
|
|
652
980
|
// Auto-provisioned solo workspace, one per user: non-deletable, takes
|
|
653
981
|
// no members, renameable like any workspace.
|
|
654
|
-
isPersonal:
|
|
982
|
+
isPersonal: z13.boolean(),
|
|
655
983
|
// Uploaded logo URL, or null for the initials fallback. Set through the
|
|
656
984
|
// dedicated /workspaces/:id/avatar route, never the generic update.
|
|
657
|
-
logo:
|
|
658
|
-
name:
|
|
985
|
+
logo: z13.string().nullable(),
|
|
986
|
+
name: z13.string(),
|
|
659
987
|
role: workspaceRoleSchema.nullable(),
|
|
660
|
-
updatedAt:
|
|
988
|
+
updatedAt: z13.string()
|
|
661
989
|
});
|
|
662
|
-
var workspaceListResponseSchema =
|
|
663
|
-
workspaces:
|
|
990
|
+
var workspaceListResponseSchema = z13.object({
|
|
991
|
+
workspaces: z13.array(workspaceSchema)
|
|
664
992
|
});
|
|
665
|
-
var deleteWorkspaceResponseSchema =
|
|
666
|
-
deleted:
|
|
993
|
+
var deleteWorkspaceResponseSchema = z13.object({
|
|
994
|
+
deleted: z13.literal(true)
|
|
667
995
|
});
|
|
668
|
-
var workspaceMemberSchema =
|
|
669
|
-
createdAt:
|
|
670
|
-
email:
|
|
671
|
-
image:
|
|
672
|
-
name:
|
|
996
|
+
var workspaceMemberSchema = z13.object({
|
|
997
|
+
createdAt: z13.string(),
|
|
998
|
+
email: z13.string().email(),
|
|
999
|
+
image: z13.string().nullable(),
|
|
1000
|
+
name: z13.string(),
|
|
673
1001
|
role: workspaceRoleSchema,
|
|
674
|
-
userId:
|
|
1002
|
+
userId: z13.string()
|
|
675
1003
|
});
|
|
676
|
-
var workspaceMemberListResponseSchema =
|
|
677
|
-
members:
|
|
1004
|
+
var workspaceMemberListResponseSchema = z13.object({
|
|
1005
|
+
members: z13.array(workspaceMemberSchema)
|
|
678
1006
|
});
|
|
679
|
-
var updateWorkspaceMemberSchema =
|
|
1007
|
+
var updateWorkspaceMemberSchema = z13.object({
|
|
680
1008
|
role: workspaceRoleSchema
|
|
681
1009
|
});
|
|
682
|
-
var removeWorkspaceMemberResponseSchema =
|
|
683
|
-
removed:
|
|
1010
|
+
var removeWorkspaceMemberResponseSchema = z13.object({
|
|
1011
|
+
removed: z13.literal(true)
|
|
684
1012
|
});
|
|
685
|
-
var createWorkspaceInviteSchema =
|
|
686
|
-
email:
|
|
687
|
-
expiresAt:
|
|
688
|
-
maxUses:
|
|
1013
|
+
var createWorkspaceInviteSchema = z13.object({
|
|
1014
|
+
email: z13.string().trim().toLowerCase().email().optional(),
|
|
1015
|
+
expiresAt: z13.string().datetime(),
|
|
1016
|
+
maxUses: z13.number().int().positive().optional(),
|
|
689
1017
|
role: workspaceInviteRoleSchema
|
|
690
1018
|
});
|
|
691
|
-
var workspaceInviteSchema =
|
|
692
|
-
createdAt:
|
|
693
|
-
email:
|
|
694
|
-
expiresAt:
|
|
695
|
-
id:
|
|
696
|
-
maxUses:
|
|
1019
|
+
var workspaceInviteSchema = z13.object({
|
|
1020
|
+
createdAt: z13.string(),
|
|
1021
|
+
email: z13.string().email().nullable(),
|
|
1022
|
+
expiresAt: z13.string(),
|
|
1023
|
+
id: z13.string(),
|
|
1024
|
+
maxUses: z13.number().int().positive().nullable(),
|
|
697
1025
|
role: workspaceInviteRoleSchema,
|
|
698
|
-
tokenPrefix:
|
|
699
|
-
useCount:
|
|
1026
|
+
tokenPrefix: z13.string(),
|
|
1027
|
+
useCount: z13.number().int().nonnegative()
|
|
700
1028
|
});
|
|
701
1029
|
var createdWorkspaceInviteSchema = workspaceInviteSchema.extend({
|
|
702
|
-
url:
|
|
1030
|
+
url: z13.string().url()
|
|
703
1031
|
});
|
|
704
|
-
var workspaceInviteListResponseSchema =
|
|
705
|
-
invites:
|
|
1032
|
+
var workspaceInviteListResponseSchema = z13.object({
|
|
1033
|
+
invites: z13.array(workspaceInviteSchema)
|
|
706
1034
|
});
|
|
707
|
-
var revokeWorkspaceInviteResponseSchema =
|
|
708
|
-
revoked:
|
|
1035
|
+
var revokeWorkspaceInviteResponseSchema = z13.object({
|
|
1036
|
+
revoked: z13.literal(true)
|
|
709
1037
|
});
|
|
710
|
-
var acceptWorkspaceInviteSchema =
|
|
711
|
-
token:
|
|
1038
|
+
var acceptWorkspaceInviteSchema = z13.object({
|
|
1039
|
+
token: z13.string().min(1)
|
|
712
1040
|
});
|
|
713
|
-
var acceptWorkspaceInviteResponseSchema =
|
|
1041
|
+
var acceptWorkspaceInviteResponseSchema = z13.object({
|
|
714
1042
|
role: workspaceRoleSchema,
|
|
715
1043
|
workspace: workspaceSchema
|
|
716
1044
|
});
|
|
717
|
-
var folderVisibilitySchema =
|
|
718
|
-
var artifactVisibilitySchema =
|
|
1045
|
+
var folderVisibilitySchema = z13.enum(["public", "workspace"]);
|
|
1046
|
+
var artifactVisibilitySchema = z13.enum([
|
|
719
1047
|
"public",
|
|
720
1048
|
"unlisted",
|
|
721
1049
|
"workspace",
|
|
722
1050
|
"private"
|
|
723
1051
|
]);
|
|
724
|
-
var createFolderSchema =
|
|
1052
|
+
var createFolderSchema = z13.object({
|
|
725
1053
|
// Optional custom icon; omitted (or null) leaves the default glyph.
|
|
726
1054
|
emoji: iconEmojiSchema.optional(),
|
|
727
|
-
name:
|
|
1055
|
+
name: z13.string().trim().min(1).max(100),
|
|
728
1056
|
visibility: folderVisibilitySchema
|
|
729
1057
|
});
|
|
730
|
-
var updateFolderSchema =
|
|
1058
|
+
var updateFolderSchema = z13.object({
|
|
731
1059
|
// With `visibility`: bulk-update the folder's artifacts to match
|
|
732
1060
|
// (SPEC-2 §3.4 "Changing a folder's visibility offers a bulk update").
|
|
733
|
-
cascade:
|
|
1061
|
+
cascade: z13.boolean().optional(),
|
|
734
1062
|
// Explicit null clears the icon back to the default glyph, so this is
|
|
735
1063
|
// a field where "sent as null" and "omitted" mean different things.
|
|
736
1064
|
emoji: iconEmojiSchema.optional(),
|
|
737
|
-
name:
|
|
738
|
-
sortOrder:
|
|
1065
|
+
name: z13.string().trim().min(1).max(100).optional(),
|
|
1066
|
+
sortOrder: z13.number().int().min(0).optional(),
|
|
739
1067
|
visibility: folderVisibilitySchema.optional()
|
|
740
1068
|
}).refine(
|
|
741
1069
|
(body) => body.name !== void 0 || body.visibility !== void 0 || body.sortOrder !== void 0 || body.emoji !== void 0,
|
|
@@ -743,93 +1071,93 @@ var updateFolderSchema = z11.object({
|
|
|
743
1071
|
).refine((body) => !body.cascade || body.visibility !== void 0, {
|
|
744
1072
|
message: "cascade requires visibility"
|
|
745
1073
|
});
|
|
746
|
-
var folderSchema =
|
|
747
|
-
createdAt:
|
|
1074
|
+
var folderSchema = z13.object({
|
|
1075
|
+
createdAt: z13.string(),
|
|
748
1076
|
// Custom icon, or null to render the default folder glyph.
|
|
749
|
-
emoji:
|
|
750
|
-
id:
|
|
751
|
-
name:
|
|
1077
|
+
emoji: z13.string().nullable(),
|
|
1078
|
+
id: z13.string(),
|
|
1079
|
+
name: z13.string(),
|
|
752
1080
|
// Permalink segment for the public folder page (/w/{handle}/f/{slug}).
|
|
753
1081
|
// Server-derived from the name; renames regenerate it.
|
|
754
|
-
slug:
|
|
755
|
-
sortOrder:
|
|
756
|
-
updatedAt:
|
|
1082
|
+
slug: z13.string(),
|
|
1083
|
+
sortOrder: z13.number().int(),
|
|
1084
|
+
updatedAt: z13.string(),
|
|
757
1085
|
visibility: folderVisibilitySchema,
|
|
758
|
-
workspaceId:
|
|
1086
|
+
workspaceId: z13.string()
|
|
759
1087
|
});
|
|
760
|
-
var folderListResponseSchema =
|
|
761
|
-
folders:
|
|
1088
|
+
var folderListResponseSchema = z13.object({
|
|
1089
|
+
folders: z13.array(folderSchema)
|
|
762
1090
|
});
|
|
763
|
-
var deleteFolderResponseSchema =
|
|
764
|
-
deleted:
|
|
1091
|
+
var deleteFolderResponseSchema = z13.object({
|
|
1092
|
+
deleted: z13.literal(true)
|
|
765
1093
|
});
|
|
766
|
-
var workspaceArtifactSchema =
|
|
767
|
-
createdAt:
|
|
768
|
-
currentVersionNumber:
|
|
1094
|
+
var workspaceArtifactSchema = z13.object({
|
|
1095
|
+
createdAt: z13.string(),
|
|
1096
|
+
currentVersionNumber: z13.number().int().nullable(),
|
|
769
1097
|
// Custom icon, or null to render the default document glyph.
|
|
770
|
-
emoji:
|
|
771
|
-
folderId:
|
|
772
|
-
id:
|
|
773
|
-
openCommentCount:
|
|
1098
|
+
emoji: z13.string().nullable(),
|
|
1099
|
+
folderId: z13.string().nullable(),
|
|
1100
|
+
id: z13.string(),
|
|
1101
|
+
openCommentCount: z13.number().int().nonnegative(),
|
|
774
1102
|
reviewMode: reviewModeSchema,
|
|
775
|
-
slug:
|
|
776
|
-
title:
|
|
777
|
-
updatedAt:
|
|
778
|
-
url:
|
|
779
|
-
versionCount:
|
|
1103
|
+
slug: z13.string(),
|
|
1104
|
+
title: z13.string(),
|
|
1105
|
+
updatedAt: z13.string(),
|
|
1106
|
+
url: z13.string().url(),
|
|
1107
|
+
versionCount: z13.number().int().nonnegative(),
|
|
780
1108
|
visibility: artifactVisibilitySchema
|
|
781
1109
|
});
|
|
782
|
-
var workspaceArtifactListResponseSchema =
|
|
783
|
-
artifacts:
|
|
1110
|
+
var workspaceArtifactListResponseSchema = z13.object({
|
|
1111
|
+
artifacts: z13.array(workspaceArtifactSchema)
|
|
784
1112
|
});
|
|
785
|
-
var workspaceArtifactSearchResultSchema =
|
|
786
|
-
id:
|
|
787
|
-
slug:
|
|
788
|
-
title:
|
|
1113
|
+
var workspaceArtifactSearchResultSchema = z13.object({
|
|
1114
|
+
id: z13.string(),
|
|
1115
|
+
slug: z13.string(),
|
|
1116
|
+
title: z13.string()
|
|
789
1117
|
});
|
|
790
|
-
var workspaceArtifactSearchResponseSchema =
|
|
791
|
-
results:
|
|
1118
|
+
var workspaceArtifactSearchResponseSchema = z13.object({
|
|
1119
|
+
results: z13.array(workspaceArtifactSearchResultSchema)
|
|
792
1120
|
});
|
|
793
1121
|
|
|
794
1122
|
// ../shared/src/me.ts
|
|
795
|
-
var personSchema =
|
|
796
|
-
image:
|
|
797
|
-
name:
|
|
1123
|
+
var personSchema = z14.object({
|
|
1124
|
+
image: z14.string().nullable(),
|
|
1125
|
+
name: z14.string()
|
|
798
1126
|
});
|
|
799
|
-
var ownArtifactSchema =
|
|
1127
|
+
var ownArtifactSchema = z14.object({
|
|
800
1128
|
// Everyone else who has published a version, oldest first. Any member may
|
|
801
1129
|
// publish a new one (SPEC-2 §5.1), so an artifact's byline is often more
|
|
802
1130
|
// than one person and the card stacks their faces rather than crediting
|
|
803
1131
|
// whoever shipped last. Excludes `publishedBy`; empty on a solo artifact.
|
|
804
|
-
coPublishers:
|
|
805
|
-
createdAt:
|
|
1132
|
+
coPublishers: z14.array(personSchema),
|
|
1133
|
+
createdAt: z14.string(),
|
|
806
1134
|
// SPEC-2 §5.1 — who *made* the artifact: the author of its first
|
|
807
1135
|
// finalized version, the same row `viewerCanManage` is decided on. Not
|
|
808
1136
|
// the same person as `publishedBy` once an agent ships v2, which is why
|
|
809
1137
|
// the dashboard filters on both. Null for a token-only publish.
|
|
810
|
-
createdBy:
|
|
811
|
-
currentVersionNumber:
|
|
1138
|
+
createdBy: z14.object({ image: z14.string().nullable(), name: z14.string() }).nullable(),
|
|
1139
|
+
currentVersionNumber: z14.number().nullable(),
|
|
812
1140
|
// Custom icon, or null to render the default document glyph.
|
|
813
|
-
emoji:
|
|
1141
|
+
emoji: z14.string().nullable(),
|
|
814
1142
|
// Owned artifacts normally never expire; non-null only while an
|
|
815
1143
|
// expiry is still set (e.g. an unclaimed publish surfaced here later).
|
|
816
|
-
expiresAt:
|
|
1144
|
+
expiresAt: z14.string().nullable(),
|
|
817
1145
|
// SPEC-2 §8.4 — the containing folder for workspace-owned artifacts;
|
|
818
1146
|
// both null for personal artifacts. `folderId` drives the dashboard's
|
|
819
1147
|
// move control and folder filter; `folderName` is the display label,
|
|
820
1148
|
// `folderEmoji` its icon (null = the default folder glyph).
|
|
821
|
-
folderEmoji:
|
|
822
|
-
folderId:
|
|
823
|
-
folderName:
|
|
1149
|
+
folderEmoji: z14.string().nullable(),
|
|
1150
|
+
folderId: z14.string().nullable(),
|
|
1151
|
+
folderName: z14.string().nullable(),
|
|
824
1152
|
// Whether a publish token is still active — revocable from the
|
|
825
1153
|
// dashboard (§6.18); the token itself is never returned.
|
|
826
|
-
hasPublishToken:
|
|
1154
|
+
hasPublishToken: z14.boolean(),
|
|
827
1155
|
// Owner preference: hide the version list from reviewers in the review
|
|
828
1156
|
// shell. Managers still see it.
|
|
829
|
-
hideVersions:
|
|
830
|
-
id:
|
|
831
|
-
openThreads:
|
|
832
|
-
protected:
|
|
1157
|
+
hideVersions: z14.boolean(),
|
|
1158
|
+
id: z14.string(),
|
|
1159
|
+
openThreads: z14.number(),
|
|
1160
|
+
protected: z14.boolean(),
|
|
833
1161
|
// Who published the current version (falling back to the claiming
|
|
834
1162
|
// owner) — the dashboard shows a name and avatar per artifact so a
|
|
835
1163
|
// shared workspace reads as people's work, not anonymous rows. Null
|
|
@@ -838,44 +1166,44 @@ var ownArtifactSchema = z12.object({
|
|
|
838
1166
|
// SPEC-2 §5.8 — the review dial, so the dashboard's settings dialog can
|
|
839
1167
|
// render current state without a second fetch.
|
|
840
1168
|
reviewMode: reviewModeSchema,
|
|
841
|
-
slug:
|
|
842
|
-
title:
|
|
843
|
-
totalViews:
|
|
1169
|
+
slug: z14.string(),
|
|
1170
|
+
title: z14.string(),
|
|
1171
|
+
totalViews: z14.number(),
|
|
844
1172
|
// SPEC §6.20 — messages by others newer than the viewer's last-seen
|
|
845
1173
|
// watermark. 0 for signed-out/API-key-less contexts.
|
|
846
|
-
unreadComments:
|
|
847
|
-
updatedAt:
|
|
848
|
-
url:
|
|
849
|
-
versionCount:
|
|
1174
|
+
unreadComments: z14.number(),
|
|
1175
|
+
updatedAt: z14.string(),
|
|
1176
|
+
url: z14.string(),
|
|
1177
|
+
versionCount: z14.number(),
|
|
850
1178
|
// SPEC-2 §5.1 — whether the caller clears the manage bar for this
|
|
851
1179
|
// artifact (workspace admin, or the person who first published it).
|
|
852
1180
|
// Only a manager's folder move inherits the folder's visibility, so the
|
|
853
1181
|
// dashboard warns exactly the people the change can actually affect, and
|
|
854
1182
|
// only a manager is offered the Share entry (§5.7).
|
|
855
|
-
viewerCanManage:
|
|
1183
|
+
viewerCanManage: z14.boolean(),
|
|
856
1184
|
// SPEC-2 §5.2 — the artifact's own visibility, the single enforcement
|
|
857
1185
|
// source. The dashboard needs it to tell whether a folder move would
|
|
858
1186
|
// widen exposure before it sends the PATCH (§3.4), and to render the
|
|
859
1187
|
// audience glyph — one per level — on rows and cards (§5.7).
|
|
860
1188
|
visibility: artifactVisibilitySchema
|
|
861
1189
|
});
|
|
862
|
-
var ownArtifactsResponseSchema =
|
|
863
|
-
artifacts:
|
|
1190
|
+
var ownArtifactsResponseSchema = z14.object({
|
|
1191
|
+
artifacts: z14.array(ownArtifactSchema)
|
|
864
1192
|
});
|
|
865
|
-
var revokePublishTokenResponseSchema =
|
|
866
|
-
revoked:
|
|
1193
|
+
var revokePublishTokenResponseSchema = z14.object({
|
|
1194
|
+
revoked: z14.boolean()
|
|
867
1195
|
});
|
|
868
1196
|
|
|
869
1197
|
// ../shared/src/owner.ts
|
|
870
|
-
import { z as
|
|
1198
|
+
import { z as z15 } from "zod";
|
|
871
1199
|
var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{1,61}[a-z0-9])$/;
|
|
872
|
-
var claimResponseSchema =
|
|
873
|
-
artifactId:
|
|
874
|
-
slug:
|
|
875
|
-
title:
|
|
876
|
-
url:
|
|
1200
|
+
var claimResponseSchema = z15.object({
|
|
1201
|
+
artifactId: z15.string(),
|
|
1202
|
+
slug: z15.string(),
|
|
1203
|
+
title: z15.string(),
|
|
1204
|
+
url: z15.string().url()
|
|
877
1205
|
});
|
|
878
|
-
var updateArtifactSchema =
|
|
1206
|
+
var updateArtifactSchema = z15.object({
|
|
879
1207
|
// Custom icon; explicit null clears it back to the default glyph.
|
|
880
1208
|
// Part of what the artifact *is* (like the title), so it takes the
|
|
881
1209
|
// same manage rights as title/slug — not the member-level folder move.
|
|
@@ -883,159 +1211,159 @@ var updateArtifactSchema = z13.object({
|
|
|
883
1211
|
// SPEC-2 §6.4 — placement + visibility. `folderId: null` clears the
|
|
884
1212
|
// folder; `workspaceId: null` moves the artifact out of its workspace
|
|
885
1213
|
// (§5.5 ownership transfer).
|
|
886
|
-
folderId:
|
|
1214
|
+
folderId: z15.string().min(1).nullable().optional(),
|
|
887
1215
|
// Keep the version list out of the review shell. Presentation only:
|
|
888
1216
|
// versions stay immutable, and a `/vN/` URL still resolves.
|
|
889
|
-
hideVersions:
|
|
890
|
-
slug:
|
|
1217
|
+
hideVersions: z15.boolean().optional(),
|
|
1218
|
+
slug: z15.string().min(3).max(63).regex(SLUG_PATTERN, {
|
|
891
1219
|
message: "slug must be lowercase letters, digits, and hyphens, with no leading/trailing hyphen"
|
|
892
1220
|
}).optional(),
|
|
893
|
-
title:
|
|
1221
|
+
title: z15.string().min(1).max(200).optional(),
|
|
894
1222
|
visibility: artifactVisibilitySchema.optional(),
|
|
895
|
-
workspaceId:
|
|
1223
|
+
workspaceId: z15.string().min(1).nullable().optional()
|
|
896
1224
|
}).refine(
|
|
897
1225
|
(body) => body.title !== void 0 || body.slug !== void 0 || body.visibility !== void 0 || body.folderId !== void 0 || body.workspaceId !== void 0 || body.emoji !== void 0 || body.hideVersions !== void 0,
|
|
898
1226
|
{
|
|
899
1227
|
message: "provide at least one of title, slug, visibility, folderId, workspaceId, emoji, hideVersions"
|
|
900
1228
|
}
|
|
901
1229
|
);
|
|
902
|
-
var deleteArtifactResponseSchema =
|
|
903
|
-
deleted:
|
|
1230
|
+
var deleteArtifactResponseSchema = z15.object({
|
|
1231
|
+
deleted: z15.literal(true)
|
|
904
1232
|
});
|
|
905
1233
|
|
|
906
1234
|
// ../shared/src/presence.ts
|
|
907
|
-
import { z as
|
|
1235
|
+
import { z as z16 } from "zod";
|
|
908
1236
|
var AGENT_STATES = ["listening", "editing", "publishing"];
|
|
909
|
-
var agentStateSchema =
|
|
1237
|
+
var agentStateSchema = z16.enum(AGENT_STATES);
|
|
910
1238
|
var AGENT_PRESENCE = {
|
|
911
1239
|
editingTtlSeconds: 300,
|
|
912
1240
|
heartbeatSeconds: 15,
|
|
913
1241
|
shellPollSeconds: 15,
|
|
914
1242
|
ttlSeconds: 45
|
|
915
1243
|
};
|
|
916
|
-
var agentHeartbeatBodySchema =
|
|
1244
|
+
var agentHeartbeatBodySchema = z16.object({
|
|
917
1245
|
state: agentStateSchema.optional()
|
|
918
1246
|
});
|
|
919
|
-
var agentHeartbeatResponseSchema =
|
|
920
|
-
listening:
|
|
1247
|
+
var agentHeartbeatResponseSchema = z16.object({
|
|
1248
|
+
listening: z16.literal(true),
|
|
921
1249
|
state: agentStateSchema.optional(),
|
|
922
|
-
ttlSeconds:
|
|
1250
|
+
ttlSeconds: z16.number().int().positive()
|
|
923
1251
|
});
|
|
924
|
-
var agentPresenceSchema =
|
|
925
|
-
agent:
|
|
926
|
-
listening:
|
|
1252
|
+
var agentPresenceSchema = z16.object({
|
|
1253
|
+
agent: z16.object({ id: z16.string().nullable(), label: z16.string() }).nullable().default(null),
|
|
1254
|
+
listening: z16.boolean(),
|
|
927
1255
|
state: agentStateSchema.nullable().optional(),
|
|
928
|
-
versionNumber:
|
|
1256
|
+
versionNumber: z16.number().int().positive().nullable()
|
|
929
1257
|
});
|
|
930
1258
|
|
|
931
1259
|
// ../shared/src/protection.ts
|
|
932
|
-
import { z as
|
|
1260
|
+
import { z as z17 } from "zod";
|
|
933
1261
|
var PASSWORD_MIN = 6;
|
|
934
1262
|
var PASSWORD_MAX = 128;
|
|
935
|
-
var gatePasswordSchema =
|
|
936
|
-
var setProtectionSchema =
|
|
1263
|
+
var gatePasswordSchema = z17.string().min(PASSWORD_MIN, `Password must be at least ${PASSWORD_MIN} characters.`).max(PASSWORD_MAX, `Password must be at most ${PASSWORD_MAX} characters.`);
|
|
1264
|
+
var setProtectionSchema = z17.object({
|
|
937
1265
|
password: gatePasswordSchema.nullable()
|
|
938
1266
|
});
|
|
939
|
-
var protectionResponseSchema =
|
|
940
|
-
protected:
|
|
1267
|
+
var protectionResponseSchema = z17.object({
|
|
1268
|
+
protected: z17.boolean()
|
|
941
1269
|
});
|
|
942
|
-
var unlockSchema =
|
|
943
|
-
password:
|
|
1270
|
+
var unlockSchema = z17.object({
|
|
1271
|
+
password: z17.string().min(1).max(1024)
|
|
944
1272
|
});
|
|
945
|
-
var unlockResponseSchema =
|
|
946
|
-
unlocked:
|
|
1273
|
+
var unlockResponseSchema = z17.object({
|
|
1274
|
+
unlocked: z17.literal(true)
|
|
947
1275
|
});
|
|
948
1276
|
|
|
949
1277
|
// ../shared/src/report.ts
|
|
950
|
-
import { z as
|
|
951
|
-
var reportRequestSchema =
|
|
952
|
-
reason:
|
|
1278
|
+
import { z as z18 } from "zod";
|
|
1279
|
+
var reportRequestSchema = z18.object({
|
|
1280
|
+
reason: z18.string().max(1e3).optional()
|
|
953
1281
|
});
|
|
954
|
-
var reportResponseSchema =
|
|
955
|
-
reported:
|
|
1282
|
+
var reportResponseSchema = z18.object({
|
|
1283
|
+
reported: z18.literal(true)
|
|
956
1284
|
});
|
|
957
1285
|
|
|
958
1286
|
// ../shared/src/reviewers.ts
|
|
959
|
-
import { z as
|
|
960
|
-
var artifactReviewerSchema =
|
|
961
|
-
avatarUrl:
|
|
962
|
-
displayName:
|
|
1287
|
+
import { z as z19 } from "zod";
|
|
1288
|
+
var artifactReviewerSchema = z19.object({
|
|
1289
|
+
avatarUrl: z19.string().nullable(),
|
|
1290
|
+
displayName: z19.string(),
|
|
963
1291
|
// Exactly one of userId/guestId is set, per `kind`.
|
|
964
|
-
guestId:
|
|
1292
|
+
guestId: z19.string().nullable(),
|
|
965
1293
|
// "account", not commentAuthorSchema's "member" — the whole point here is
|
|
966
1294
|
// that these people need not be workspace members. A guest can be named in
|
|
967
1295
|
// a comment but never notified: guest_identities carries a display name and
|
|
968
1296
|
// no email, so there is nowhere to send anything.
|
|
969
|
-
kind:
|
|
970
|
-
userId:
|
|
1297
|
+
kind: z19.enum(["account", "guest"]),
|
|
1298
|
+
userId: z19.string().nullable()
|
|
971
1299
|
});
|
|
972
|
-
var artifactReviewerListResponseSchema =
|
|
973
|
-
reviewers:
|
|
1300
|
+
var artifactReviewerListResponseSchema = z19.object({
|
|
1301
|
+
reviewers: z19.array(artifactReviewerSchema)
|
|
974
1302
|
});
|
|
975
1303
|
|
|
976
1304
|
// ../shared/src/stats.ts
|
|
977
|
-
import { z as
|
|
978
|
-
var avgViewSecondsSchema =
|
|
979
|
-
var versionViewStatsSchema =
|
|
1305
|
+
import { z as z20 } from "zod";
|
|
1306
|
+
var avgViewSecondsSchema = z20.number().int().nonnegative().nullable();
|
|
1307
|
+
var versionViewStatsSchema = z20.object({
|
|
980
1308
|
avgViewSeconds: avgViewSecondsSchema,
|
|
981
1309
|
// Views from inside the review shell's iframe vs. direct visits to
|
|
982
1310
|
// the render URL. views = embeddedViews + directViews.
|
|
983
|
-
directViews:
|
|
984
|
-
embeddedViews:
|
|
1311
|
+
directViews: z20.number().int().nonnegative(),
|
|
1312
|
+
embeddedViews: z20.number().int().nonnegative(),
|
|
985
1313
|
// Distinct hashed viewer IPs across the version's lifetime.
|
|
986
|
-
uniqueViewers:
|
|
987
|
-
versionNumber:
|
|
988
|
-
views:
|
|
1314
|
+
uniqueViewers: z20.number().int().nonnegative(),
|
|
1315
|
+
versionNumber: z20.number().int().positive(),
|
|
1316
|
+
views: z20.number().int().nonnegative()
|
|
989
1317
|
});
|
|
990
|
-
var dailyViewsSchema =
|
|
1318
|
+
var dailyViewsSchema = z20.object({
|
|
991
1319
|
avgViewSeconds: avgViewSecondsSchema,
|
|
992
1320
|
// UTC day, YYYY-MM-DD.
|
|
993
|
-
day:
|
|
994
|
-
uniqueViewers:
|
|
995
|
-
views:
|
|
1321
|
+
day: z20.string(),
|
|
1322
|
+
uniqueViewers: z20.number().int().nonnegative(),
|
|
1323
|
+
views: z20.number().int().nonnegative()
|
|
996
1324
|
});
|
|
997
|
-
var artifactStatsResponseSchema =
|
|
1325
|
+
var artifactStatsResponseSchema = z20.object({
|
|
998
1326
|
avgViewSeconds: avgViewSecondsSchema,
|
|
999
|
-
daily:
|
|
1327
|
+
daily: z20.array(dailyViewsSchema),
|
|
1000
1328
|
// Distinct hashed viewer IPs across all versions and days.
|
|
1001
|
-
totalUniqueViewers:
|
|
1002
|
-
totalViews:
|
|
1003
|
-
versions:
|
|
1329
|
+
totalUniqueViewers: z20.number().int().nonnegative(),
|
|
1330
|
+
totalViews: z20.number().int().nonnegative(),
|
|
1331
|
+
versions: z20.array(versionViewStatsSchema)
|
|
1004
1332
|
});
|
|
1005
1333
|
|
|
1006
1334
|
// ../shared/src/versions.ts
|
|
1007
|
-
import { z as
|
|
1008
|
-
var versionSourceSchema =
|
|
1009
|
-
agentName:
|
|
1010
|
-
type:
|
|
1335
|
+
import { z as z21 } from "zod";
|
|
1336
|
+
var versionSourceSchema = z21.object({
|
|
1337
|
+
agentName: z21.string().nullable(),
|
|
1338
|
+
type: z21.string()
|
|
1011
1339
|
});
|
|
1012
|
-
var versionChangesSchema =
|
|
1013
|
-
added:
|
|
1014
|
-
changed:
|
|
1015
|
-
removed:
|
|
1340
|
+
var versionChangesSchema = z21.object({
|
|
1341
|
+
added: z21.array(z21.string()),
|
|
1342
|
+
changed: z21.array(z21.string()),
|
|
1343
|
+
removed: z21.array(z21.string())
|
|
1016
1344
|
});
|
|
1017
|
-
var versionSummarySchema =
|
|
1345
|
+
var versionSummarySchema = z21.object({
|
|
1018
1346
|
// Absent for the first version — there is no previous version to diff against.
|
|
1019
1347
|
changes: versionChangesSchema.nullable(),
|
|
1020
|
-
createdAt:
|
|
1021
|
-
entryUrl:
|
|
1022
|
-
fileCount:
|
|
1023
|
-
id:
|
|
1024
|
-
isCurrent:
|
|
1348
|
+
createdAt: z21.string(),
|
|
1349
|
+
entryUrl: z21.string().url(),
|
|
1350
|
+
fileCount: z21.number().int().nonnegative(),
|
|
1351
|
+
id: z21.string(),
|
|
1352
|
+
isCurrent: z21.boolean(),
|
|
1025
1353
|
// Author-supplied bullet-point changelog; null when the publish omitted it.
|
|
1026
|
-
notes:
|
|
1354
|
+
notes: z21.string().nullable(),
|
|
1027
1355
|
// The account that published this version, with their own avatar where
|
|
1028
1356
|
// they've set one. Any workspace member can publish a new version
|
|
1029
1357
|
// (SPEC-2 §5.1), so "which teammate" outranks "which agent" — the agent
|
|
1030
1358
|
// is the tool, this is the person. Null for token-only publishes, which
|
|
1031
1359
|
// carry no account.
|
|
1032
|
-
publishedBy:
|
|
1360
|
+
publishedBy: z21.object({ image: z21.string().nullable(), name: z21.string() }).nullable(),
|
|
1033
1361
|
source: versionSourceSchema,
|
|
1034
|
-
totalSizeBytes:
|
|
1035
|
-
versionNumber:
|
|
1362
|
+
totalSizeBytes: z21.number().int().nonnegative(),
|
|
1363
|
+
versionNumber: z21.number().int().positive()
|
|
1036
1364
|
});
|
|
1037
|
-
var versionListResponseSchema =
|
|
1038
|
-
versions:
|
|
1365
|
+
var versionListResponseSchema = z21.object({
|
|
1366
|
+
versions: z21.array(versionSummarySchema)
|
|
1039
1367
|
});
|
|
1040
1368
|
|
|
1041
1369
|
// src/agent-identity.ts
|
|
@@ -1120,6 +1448,13 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
1120
1448
|
(d) => agentHeartbeatResponseSchema.parse(d)
|
|
1121
1449
|
);
|
|
1122
1450
|
},
|
|
1451
|
+
async clearData(artifactId, collection) {
|
|
1452
|
+
return request(
|
|
1453
|
+
`${base}/api/v1/artifacts/${artifactId}/data/${encodeURIComponent(collection)}`,
|
|
1454
|
+
{ headers, method: "DELETE" },
|
|
1455
|
+
(d) => dataClearResponseSchema.parse(d)
|
|
1456
|
+
);
|
|
1457
|
+
},
|
|
1123
1458
|
// SPEC-2 §6.3 — create a folder in a workspace. Account key required
|
|
1124
1459
|
// (owner/admin; in a personal workspace that's always the user).
|
|
1125
1460
|
async createFolder(workspaceId, body) {
|
|
@@ -1202,6 +1537,13 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
1202
1537
|
(d) => artifactResponseSchema.parse(d)
|
|
1203
1538
|
);
|
|
1204
1539
|
},
|
|
1540
|
+
async getDataSummary(artifactId) {
|
|
1541
|
+
return request(
|
|
1542
|
+
`${base}/api/v1/artifacts/${artifactId}/data`,
|
|
1543
|
+
{ headers, method: "GET" },
|
|
1544
|
+
(d) => dataSummaryResponseSchema.parse(d)
|
|
1545
|
+
);
|
|
1546
|
+
},
|
|
1205
1547
|
// SPEC §6.18 — everything owned by the account this client's API
|
|
1206
1548
|
// key (or session) belongs to.
|
|
1207
1549
|
async listArtifacts() {
|
|
@@ -1211,6 +1553,21 @@ function createApiClient(apiUrl2, publishToken) {
|
|
|
1211
1553
|
(d) => artifactListResponseSchema.parse(d)
|
|
1212
1554
|
);
|
|
1213
1555
|
},
|
|
1556
|
+
async listData(artifactId, collection, query = {}) {
|
|
1557
|
+
const params = new URLSearchParams();
|
|
1558
|
+
if (query.limit !== void 0) {
|
|
1559
|
+
params.set("limit", String(query.limit));
|
|
1560
|
+
}
|
|
1561
|
+
if (query.cursor !== void 0) {
|
|
1562
|
+
params.set("cursor", query.cursor);
|
|
1563
|
+
}
|
|
1564
|
+
const qs = params.toString();
|
|
1565
|
+
return request(
|
|
1566
|
+
`${base}/api/v1/artifacts/${artifactId}/data/${encodeURIComponent(collection)}${qs ? `?${qs}` : ""}`,
|
|
1567
|
+
{ headers, method: "GET" },
|
|
1568
|
+
(d) => dataListResponseSchema.parse(d)
|
|
1569
|
+
);
|
|
1570
|
+
},
|
|
1214
1571
|
async listFolders(workspaceId) {
|
|
1215
1572
|
return request(
|
|
1216
1573
|
`${base}/api/v1/workspaces/${workspaceId}/folders`,
|
|
@@ -1346,17 +1703,17 @@ import {
|
|
|
1346
1703
|
writeFileSync as writeFileSync2
|
|
1347
1704
|
} from "fs";
|
|
1348
1705
|
import path2 from "path";
|
|
1349
|
-
import { z as
|
|
1706
|
+
import { z as z23 } from "zod";
|
|
1350
1707
|
|
|
1351
1708
|
// src/credentials.ts
|
|
1352
1709
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
1353
1710
|
import { homedir } from "os";
|
|
1354
1711
|
import path from "path";
|
|
1355
|
-
import { z as
|
|
1712
|
+
import { z as z22 } from "zod";
|
|
1356
1713
|
var DEFAULT_API_URL = "https://artifact.cafe";
|
|
1357
|
-
var credentialsSchema =
|
|
1358
|
-
keys:
|
|
1359
|
-
version:
|
|
1714
|
+
var credentialsSchema = z22.object({
|
|
1715
|
+
keys: z22.record(z22.string(), z22.string()),
|
|
1716
|
+
version: z22.literal(1)
|
|
1360
1717
|
});
|
|
1361
1718
|
function credentialsPath() {
|
|
1362
1719
|
return path.join(homedir(), ".artifactcafe", "credentials");
|
|
@@ -1406,24 +1763,24 @@ function resolveApiKey(apiUrl2, flag) {
|
|
|
1406
1763
|
}
|
|
1407
1764
|
|
|
1408
1765
|
// src/config.ts
|
|
1409
|
-
var configSchema =
|
|
1410
|
-
apiUrl:
|
|
1411
|
-
artifactId:
|
|
1412
|
-
folder:
|
|
1413
|
-
folderId:
|
|
1766
|
+
var configSchema = z23.object({
|
|
1767
|
+
apiUrl: z23.string(),
|
|
1768
|
+
artifactId: z23.string(),
|
|
1769
|
+
folder: z23.string().optional(),
|
|
1770
|
+
folderId: z23.string().optional(),
|
|
1414
1771
|
// Optional: a folder linked by `link` to an artifact you own carries no
|
|
1415
1772
|
// token — owner actions authorize via your stored login instead. A first
|
|
1416
1773
|
// publish still writes the anonymous-mode token here.
|
|
1417
|
-
publishToken:
|
|
1418
|
-
slug:
|
|
1419
|
-
url:
|
|
1420
|
-
workspace:
|
|
1421
|
-
workspaceId:
|
|
1422
|
-
workspaceKind:
|
|
1423
|
-
});
|
|
1424
|
-
var projectConfigSchema =
|
|
1425
|
-
folder:
|
|
1426
|
-
workspace:
|
|
1774
|
+
publishToken: z23.string().optional(),
|
|
1775
|
+
slug: z23.string(),
|
|
1776
|
+
url: z23.string(),
|
|
1777
|
+
workspace: z23.string().optional(),
|
|
1778
|
+
workspaceId: z23.string().optional(),
|
|
1779
|
+
workspaceKind: z23.enum(["personal", "workspace"]).optional()
|
|
1780
|
+
});
|
|
1781
|
+
var projectConfigSchema = z23.object({
|
|
1782
|
+
folder: z23.string().trim().min(1).optional(),
|
|
1783
|
+
workspace: z23.string().trim().min(1)
|
|
1427
1784
|
}).strict();
|
|
1428
1785
|
var CONFIG_DIR = ".artifactcafe";
|
|
1429
1786
|
var CONFIG_FILE = "config.json";
|
|
@@ -1459,7 +1816,7 @@ function readProjectConfig(dir) {
|
|
|
1459
1816
|
JSON.parse(readFileSync2(file, "utf8"))
|
|
1460
1817
|
);
|
|
1461
1818
|
} catch (error) {
|
|
1462
|
-
const message = error instanceof
|
|
1819
|
+
const message = error instanceof z23.ZodError ? error.issues[0]?.message ?? "invalid project target" : "invalid JSON";
|
|
1463
1820
|
throw new Error(`Invalid ${file}: ${message}.`);
|
|
1464
1821
|
}
|
|
1465
1822
|
}
|
|
@@ -1669,27 +2026,27 @@ async function comments(opts) {
|
|
|
1669
2026
|
});
|
|
1670
2027
|
return;
|
|
1671
2028
|
}
|
|
1672
|
-
const
|
|
2029
|
+
const data2 = await client.exportComments(target.artifactId, {
|
|
1673
2030
|
status: status2,
|
|
1674
2031
|
version
|
|
1675
2032
|
});
|
|
1676
2033
|
if (opts.json) {
|
|
1677
|
-
console.log(JSON.stringify(
|
|
2034
|
+
console.log(JSON.stringify(data2, null, 2));
|
|
1678
2035
|
return;
|
|
1679
2036
|
}
|
|
1680
|
-
printHuman(
|
|
2037
|
+
printHuman(data2, url, status2, version);
|
|
1681
2038
|
}
|
|
1682
2039
|
async function deriveReviewUrl(client, target) {
|
|
1683
2040
|
const artifact = await client.getArtifact(target.artifactId);
|
|
1684
2041
|
return reviewUrl(target.apiUrl, artifact.slug);
|
|
1685
2042
|
}
|
|
1686
2043
|
var sleep = (s) => new Promise((r) => setTimeout(r, s * 1e3));
|
|
1687
|
-
function messageCounts(
|
|
1688
|
-
return new Map(
|
|
2044
|
+
function messageCounts(data2) {
|
|
2045
|
+
return new Map(data2.comments.map((t) => [t.id, 1 + t.replies.length]));
|
|
1689
2046
|
}
|
|
1690
|
-
function diffThreads(baseline,
|
|
2047
|
+
function diffThreads(baseline, data2) {
|
|
1691
2048
|
const changed = [];
|
|
1692
|
-
for (const t of
|
|
2049
|
+
for (const t of data2.comments) {
|
|
1693
2050
|
const before = baseline.get(t.id);
|
|
1694
2051
|
if (before === void 0) {
|
|
1695
2052
|
changed.push({ ...t, change: "new" });
|
|
@@ -1732,8 +2089,8 @@ async function waitForFeedback(client, config, opts) {
|
|
|
1732
2089
|
while (Date.now() < deadline) {
|
|
1733
2090
|
await sleep(Math.min(pollSeconds(), (deadline - Date.now()) / 1e3));
|
|
1734
2091
|
await heartbeat();
|
|
1735
|
-
const
|
|
1736
|
-
if (diffThreads(baseline,
|
|
2092
|
+
const data2 = await client.exportComments(config.artifactId, query);
|
|
2093
|
+
if (diffThreads(baseline, data2).length === 0) {
|
|
1737
2094
|
continue;
|
|
1738
2095
|
}
|
|
1739
2096
|
await sleep(settleSeconds());
|
|
@@ -1766,7 +2123,7 @@ async function waitForFeedback(client, config, opts) {
|
|
|
1766
2123
|
}
|
|
1767
2124
|
process.exit(WAIT_TIMEOUT_EXIT);
|
|
1768
2125
|
}
|
|
1769
|
-
function printWaitResult(config,
|
|
2126
|
+
function printWaitResult(config, data2, changed, json) {
|
|
1770
2127
|
if (json) {
|
|
1771
2128
|
console.log(
|
|
1772
2129
|
JSON.stringify(
|
|
@@ -1774,7 +2131,7 @@ function printWaitResult(config, data, changed, json) {
|
|
|
1774
2131
|
artifactId: config.artifactId,
|
|
1775
2132
|
threads: changed,
|
|
1776
2133
|
timedOut: false,
|
|
1777
|
-
versionNumber:
|
|
2134
|
+
versionNumber: data2.versionNumber
|
|
1778
2135
|
},
|
|
1779
2136
|
null,
|
|
1780
2137
|
2
|
|
@@ -1793,22 +2150,22 @@ ${changed.length} thread${plural} with new feedback \u2014 ${config.url}
|
|
|
1793
2150
|
printThread(thread);
|
|
1794
2151
|
}
|
|
1795
2152
|
}
|
|
1796
|
-
function printHuman(
|
|
2153
|
+
function printHuman(data2, url, status2, version) {
|
|
1797
2154
|
const statusLabel = status2 === "all" ? "" : `${status2} `;
|
|
1798
2155
|
let scope = "across all versions";
|
|
1799
2156
|
if (version === "current") {
|
|
1800
|
-
scope =
|
|
2157
|
+
scope = data2.versionNumber === null ? "on the current version" : `on v${data2.versionNumber}`;
|
|
1801
2158
|
}
|
|
1802
|
-
if (
|
|
2159
|
+
if (data2.comments.length === 0) {
|
|
1803
2160
|
console.log(`No ${statusLabel}comments ${scope}.`);
|
|
1804
2161
|
return;
|
|
1805
2162
|
}
|
|
1806
|
-
const plural =
|
|
2163
|
+
const plural = data2.comments.length === 1 ? "" : "s";
|
|
1807
2164
|
console.log(
|
|
1808
|
-
`${
|
|
2165
|
+
`${data2.comments.length} ${statusLabel}thread${plural} ${scope} \u2014 ${url}
|
|
1809
2166
|
`
|
|
1810
2167
|
);
|
|
1811
|
-
for (const thread of
|
|
2168
|
+
for (const thread of data2.comments) {
|
|
1812
2169
|
printThread(thread);
|
|
1813
2170
|
}
|
|
1814
2171
|
}
|
|
@@ -1853,18 +2210,161 @@ function indent(text, pad) {
|
|
|
1853
2210
|
}
|
|
1854
2211
|
|
|
1855
2212
|
// src/dashboard.ts
|
|
1856
|
-
import
|
|
2213
|
+
import path8 from "path";
|
|
1857
2214
|
|
|
1858
2215
|
// src/publish.ts
|
|
1859
2216
|
import { exec } from "child_process";
|
|
1860
|
-
import { readFileSync as
|
|
1861
|
-
import
|
|
2217
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
2218
|
+
import path7 from "path";
|
|
1862
2219
|
import { createInterface } from "readline";
|
|
1863
2220
|
|
|
2221
|
+
// src/data.ts
|
|
2222
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2223
|
+
import path5 from "path";
|
|
2224
|
+
function inspectDataManifest(files) {
|
|
2225
|
+
const info = { announcements: [] };
|
|
2226
|
+
const manifestFile = files.find((f) => f.path === DATA_MANIFEST_PATH);
|
|
2227
|
+
if (manifestFile) {
|
|
2228
|
+
const parsed = parseDataManifest(
|
|
2229
|
+
readFileSync3(manifestFile.absPath, "utf8")
|
|
2230
|
+
);
|
|
2231
|
+
if (!parsed.ok) {
|
|
2232
|
+
throw new Error(
|
|
2233
|
+
`${DATA_MANIFEST_PATH} is invalid:
|
|
2234
|
+
${(parsed.errors ?? []).map((e) => ` - ${e}`).join("\n")}`
|
|
2235
|
+
);
|
|
2236
|
+
}
|
|
2237
|
+
info.config = parsed.config;
|
|
2238
|
+
for (const [name, collection] of Object.entries(
|
|
2239
|
+
parsed.config?.collections ?? {}
|
|
2240
|
+
)) {
|
|
2241
|
+
const bits = [
|
|
2242
|
+
`read: ${collection.access.read}`,
|
|
2243
|
+
`insert: ${collection.access.insert}`
|
|
2244
|
+
];
|
|
2245
|
+
if (collection.access.update !== "owner") {
|
|
2246
|
+
bits.push(`update: ${collection.access.update}`);
|
|
2247
|
+
}
|
|
2248
|
+
if (collection.access.delete !== "owner") {
|
|
2249
|
+
bits.push(`delete: ${collection.access.delete}`);
|
|
2250
|
+
}
|
|
2251
|
+
if (collection.rateLimit) {
|
|
2252
|
+
bits.push(collection.rateLimit);
|
|
2253
|
+
}
|
|
2254
|
+
info.announcements.push(`\u26C1 Data enabled: ${name} (${bits.join(", ")})`);
|
|
2255
|
+
}
|
|
2256
|
+
if (info.announcements.length === 0) {
|
|
2257
|
+
info.announcements.push(
|
|
2258
|
+
"\u26C1 Data disabled (empty collections in .artifact/data.json)"
|
|
2259
|
+
);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
return info;
|
|
2263
|
+
}
|
|
2264
|
+
function requireLinkedArtifact(dir) {
|
|
2265
|
+
const resolved = path5.resolve(dir);
|
|
2266
|
+
const config = readConfig(resolved);
|
|
2267
|
+
if (!config) {
|
|
2268
|
+
throw new Error(
|
|
2269
|
+
`No artifact linked in ${resolved} \u2014 run \`artifact-cafe publish\` there first (.artifactcafe/config.json not found).`
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
return {
|
|
2273
|
+
apiUrl: process.env.ARTIFACT_CAFE_URL ?? config.apiUrl,
|
|
2274
|
+
artifactId: config.artifactId,
|
|
2275
|
+
publishToken: resolveApiKey(process.env.ARTIFACT_CAFE_URL ?? config.apiUrl) ?? config.publishToken ?? process.env.ARTIFACT_CAFE_TOKEN,
|
|
2276
|
+
url: config.url
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
async function dataLs(opts) {
|
|
2280
|
+
const linked = requireLinkedArtifact(opts.dir);
|
|
2281
|
+
const client = createApiClient(linked.apiUrl, linked.publishToken);
|
|
2282
|
+
const records = [];
|
|
2283
|
+
let cursor;
|
|
2284
|
+
do {
|
|
2285
|
+
const page = await client.listData(linked.artifactId, opts.collection, {
|
|
2286
|
+
cursor,
|
|
2287
|
+
limit: 200
|
|
2288
|
+
});
|
|
2289
|
+
records.push(...page.records);
|
|
2290
|
+
cursor = page.nextCursor ?? void 0;
|
|
2291
|
+
} while (cursor && (opts.limit === void 0 || records.length < opts.limit));
|
|
2292
|
+
const shown = opts.limit === void 0 ? records : records.slice(0, opts.limit);
|
|
2293
|
+
if (opts.json) {
|
|
2294
|
+
console.log(
|
|
2295
|
+
JSON.stringify({ collection: opts.collection, records: shown }, null, 2)
|
|
2296
|
+
);
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
if (shown.length === 0) {
|
|
2300
|
+
console.log(`No records in "${opts.collection}".`);
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
const plural = shown.length === 1 ? "" : "s";
|
|
2304
|
+
console.log(
|
|
2305
|
+
`${shown.length} record${plural} in "${opts.collection}" \u2014 ${linked.url}
|
|
2306
|
+
`
|
|
2307
|
+
);
|
|
2308
|
+
for (const record of shown) {
|
|
2309
|
+
const { created_at, created_by, id, updated_at: _u, ...fields } = record;
|
|
2310
|
+
const author = created_by ?? "anonymous";
|
|
2311
|
+
console.log(`\u25CF ${id} \xB7 ${String(created_at).slice(0, 10)} \xB7 ${author}`);
|
|
2312
|
+
console.log(` ${JSON.stringify(fields)}`);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
async function dataExport(opts) {
|
|
2316
|
+
const linked = requireLinkedArtifact(opts.dir);
|
|
2317
|
+
const client = createApiClient(linked.apiUrl, linked.publishToken);
|
|
2318
|
+
const summary = await client.getDataSummary(linked.artifactId);
|
|
2319
|
+
const collections = {};
|
|
2320
|
+
for (const { name } of summary.collections) {
|
|
2321
|
+
const records = [];
|
|
2322
|
+
let cursor;
|
|
2323
|
+
do {
|
|
2324
|
+
const page = await client.listData(linked.artifactId, name, {
|
|
2325
|
+
cursor,
|
|
2326
|
+
limit: 200
|
|
2327
|
+
});
|
|
2328
|
+
records.push(...page.records);
|
|
2329
|
+
cursor = page.nextCursor ?? void 0;
|
|
2330
|
+
} while (cursor);
|
|
2331
|
+
collections[name] = records;
|
|
2332
|
+
}
|
|
2333
|
+
console.log(
|
|
2334
|
+
JSON.stringify(
|
|
2335
|
+
{
|
|
2336
|
+
artifactId: linked.artifactId,
|
|
2337
|
+
collections,
|
|
2338
|
+
enabled: summary.enabled,
|
|
2339
|
+
url: linked.url
|
|
2340
|
+
},
|
|
2341
|
+
null,
|
|
2342
|
+
2
|
|
2343
|
+
)
|
|
2344
|
+
);
|
|
2345
|
+
}
|
|
2346
|
+
async function dataClear(opts) {
|
|
2347
|
+
if (!opts.yes) {
|
|
2348
|
+
throw new Error(
|
|
2349
|
+
`Clearing "${opts.collection}" deletes every record in it. Re-run with --yes to confirm.`
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
const linked = requireLinkedArtifact(opts.dir);
|
|
2353
|
+
const client = createApiClient(linked.apiUrl, linked.publishToken);
|
|
2354
|
+
const result = await client.clearData(linked.artifactId, opts.collection);
|
|
2355
|
+
if (opts.json) {
|
|
2356
|
+
console.log(
|
|
2357
|
+
JSON.stringify({ cleared: result.cleared, collection: opts.collection })
|
|
2358
|
+
);
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
2361
|
+
console.log(`Cleared ${result.cleared} record(s) from "${opts.collection}".`);
|
|
2362
|
+
}
|
|
2363
|
+
|
|
1864
2364
|
// src/manifest.ts
|
|
1865
2365
|
import { createHash } from "crypto";
|
|
1866
|
-
import { readdirSync, readFileSync as
|
|
1867
|
-
import
|
|
2366
|
+
import { readdirSync, readFileSync as readFileSync4, statSync } from "fs";
|
|
2367
|
+
import path6 from "path";
|
|
1868
2368
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".artifactcafe"]);
|
|
1869
2369
|
var IGNORED_FILES = /* @__PURE__ */ new Set([".DS_Store", "artifact-cafe.json"]);
|
|
1870
2370
|
function isIgnoredFile(name) {
|
|
@@ -1913,7 +2413,7 @@ var MIME_TYPES = {
|
|
|
1913
2413
|
".xml": "application/xml"
|
|
1914
2414
|
};
|
|
1915
2415
|
function mimeFor(filePath) {
|
|
1916
|
-
return MIME_TYPES[
|
|
2416
|
+
return MIME_TYPES[path6.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
1917
2417
|
}
|
|
1918
2418
|
function scanDirectory(dir) {
|
|
1919
2419
|
const files = [];
|
|
@@ -1921,7 +2421,7 @@ function scanDirectory(dir) {
|
|
|
1921
2421
|
function walk(current, relPrefix) {
|
|
1922
2422
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
1923
2423
|
const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
|
|
1924
|
-
const abs =
|
|
2424
|
+
const abs = path6.join(current, entry.name);
|
|
1925
2425
|
if (entry.isSymbolicLink()) {
|
|
1926
2426
|
skipped.push(rel);
|
|
1927
2427
|
continue;
|
|
@@ -1946,7 +2446,7 @@ function scanDirectory(dir) {
|
|
|
1946
2446
|
skipped.push(rel);
|
|
1947
2447
|
continue;
|
|
1948
2448
|
}
|
|
1949
|
-
const content =
|
|
2449
|
+
const content = readFileSync4(abs);
|
|
1950
2450
|
files.push({
|
|
1951
2451
|
absPath: abs,
|
|
1952
2452
|
hash: `sha256_${createHash("sha256").update(content).digest("hex")}`,
|
|
@@ -1992,7 +2492,7 @@ var REVIEW_LABEL = {
|
|
|
1992
2492
|
readonly: "read-only \u2014 existing comments only"
|
|
1993
2493
|
};
|
|
1994
2494
|
async function publish(opts) {
|
|
1995
|
-
const dir =
|
|
2495
|
+
const dir = path7.resolve(opts.dir);
|
|
1996
2496
|
const config = readConfig(dir);
|
|
1997
2497
|
if (opts.password !== void 0) {
|
|
1998
2498
|
const parsed = gatePasswordSchema.safeParse(opts.password);
|
|
@@ -2025,6 +2525,7 @@ async function publish(opts) {
|
|
|
2025
2525
|
throw new Error(`Nothing to publish in ${dir} (after ignore rules).`);
|
|
2026
2526
|
}
|
|
2027
2527
|
const entryPath = resolveEntry(files, opts.entry);
|
|
2528
|
+
const dataInfo = inspectDataManifest(files);
|
|
2028
2529
|
const log = (msg) => {
|
|
2029
2530
|
if (!opts.json) {
|
|
2030
2531
|
console.log(msg);
|
|
@@ -2085,7 +2586,7 @@ async function publish(opts) {
|
|
|
2085
2586
|
}
|
|
2086
2587
|
await uploadFile(
|
|
2087
2588
|
upload.uploadUrl,
|
|
2088
|
-
|
|
2589
|
+
readFileSync5(file.absPath),
|
|
2089
2590
|
file.mimeType
|
|
2090
2591
|
);
|
|
2091
2592
|
}
|
|
@@ -2133,6 +2634,7 @@ async function publish(opts) {
|
|
|
2133
2634
|
JSON.stringify(
|
|
2134
2635
|
{
|
|
2135
2636
|
artifactId: result.artifactId,
|
|
2637
|
+
...dataInfo.config ? { data: dataInfo.config } : {},
|
|
2136
2638
|
claimed: result.claimed,
|
|
2137
2639
|
destination: placement.destination,
|
|
2138
2640
|
destinationSource: placement.destinationSource,
|
|
@@ -2211,6 +2713,11 @@ ${result.claimUrl}
|
|
|
2211
2713
|
`);
|
|
2212
2714
|
}
|
|
2213
2715
|
}
|
|
2716
|
+
if (!opts.json) {
|
|
2717
|
+
for (const announcement of dataInfo.announcements) {
|
|
2718
|
+
console.log(announcement);
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2214
2721
|
if (opts.open && !opts.json && process.stdout.isTTY) {
|
|
2215
2722
|
openInBrowser(result.url);
|
|
2216
2723
|
}
|
|
@@ -2280,9 +2787,9 @@ async function resolvePlacement(params) {
|
|
|
2280
2787
|
};
|
|
2281
2788
|
}
|
|
2282
2789
|
if (shouldPromptForWorkspace(opts)) {
|
|
2283
|
-
const
|
|
2284
|
-
if (
|
|
2285
|
-
const choice = await pickWorkspace(
|
|
2790
|
+
const data3 = await createApiClient(apiUrl2, apiKey).listWorkspaces();
|
|
2791
|
+
if (data3.workspaces.length > 1) {
|
|
2792
|
+
const choice = await pickWorkspace(data3.workspaces);
|
|
2286
2793
|
return {
|
|
2287
2794
|
destination: choice.workspace.isPersonal ? "personal" : "workspace",
|
|
2288
2795
|
destinationLabel: choice.workspace.isPersonal ? "Personal" : choice.workspace.name,
|
|
@@ -2308,19 +2815,19 @@ async function resolvePlacement(params) {
|
|
|
2308
2815
|
throw new Error("--folder requires --workspace or a stored workspace.");
|
|
2309
2816
|
}
|
|
2310
2817
|
const client = createApiClient(apiUrl2, apiKey);
|
|
2311
|
-
const
|
|
2818
|
+
const data2 = await client.listWorkspaces();
|
|
2312
2819
|
const workspaceHandle = opts.workspace ?? config?.workspace ?? projectConfig?.workspace;
|
|
2313
2820
|
let workspace;
|
|
2314
2821
|
if (workspaceHandle) {
|
|
2315
|
-
workspace =
|
|
2822
|
+
workspace = data2.workspaces.find(
|
|
2316
2823
|
(candidate) => candidate.handle === workspaceHandle
|
|
2317
2824
|
);
|
|
2318
2825
|
} else if (config?.workspaceId) {
|
|
2319
|
-
workspace =
|
|
2826
|
+
workspace = data2.workspaces.find(
|
|
2320
2827
|
(candidate) => candidate.id === config.workspaceId
|
|
2321
2828
|
);
|
|
2322
2829
|
} else {
|
|
2323
|
-
workspace =
|
|
2830
|
+
workspace = data2.workspaces.find((candidate) => candidate.isPersonal);
|
|
2324
2831
|
}
|
|
2325
2832
|
if (!workspace) {
|
|
2326
2833
|
const target = workspaceHandle ? `workspace with handle "${workspaceHandle}"` : "personal workspace";
|
|
@@ -2458,7 +2965,7 @@ function openInBrowser(url) {
|
|
|
2458
2965
|
|
|
2459
2966
|
// src/dashboard.ts
|
|
2460
2967
|
function openDashboard(opts) {
|
|
2461
|
-
const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(
|
|
2968
|
+
const base = process.env.ARTIFACT_CAFE_URL ?? readConfig(path8.resolve(opts.dir))?.apiUrl ?? DEFAULT_API_URL;
|
|
2462
2969
|
const url = `${base.replace(/\/$/, "")}/dashboard`;
|
|
2463
2970
|
console.log(url);
|
|
2464
2971
|
if (process.stdout.isTTY) {
|
|
@@ -2544,21 +3051,21 @@ async function folders(opts) {
|
|
|
2544
3051
|
await createFolder(client, workspace, opts);
|
|
2545
3052
|
return;
|
|
2546
3053
|
}
|
|
2547
|
-
const
|
|
3054
|
+
const data2 = await client.listFolders(workspace.id);
|
|
2548
3055
|
if (opts.json) {
|
|
2549
|
-
console.log(JSON.stringify(
|
|
3056
|
+
console.log(JSON.stringify(data2, null, 2));
|
|
2550
3057
|
return;
|
|
2551
3058
|
}
|
|
2552
|
-
if (
|
|
3059
|
+
if (data2.folders.length === 0) {
|
|
2553
3060
|
console.log(`No folders in workspace ${workspace.handle}.`);
|
|
2554
3061
|
return;
|
|
2555
3062
|
}
|
|
2556
|
-
const plural =
|
|
3063
|
+
const plural = data2.folders.length === 1 ? "" : "s";
|
|
2557
3064
|
console.log(
|
|
2558
|
-
`${
|
|
3065
|
+
`${data2.folders.length} folder${plural} in ${workspace.handle}
|
|
2559
3066
|
`
|
|
2560
3067
|
);
|
|
2561
|
-
for (const folder of
|
|
3068
|
+
for (const folder of data2.folders) {
|
|
2562
3069
|
printFolder(folder);
|
|
2563
3070
|
}
|
|
2564
3071
|
}
|
|
@@ -2596,7 +3103,7 @@ function printFolder(folder) {
|
|
|
2596
3103
|
}
|
|
2597
3104
|
|
|
2598
3105
|
// src/link.ts
|
|
2599
|
-
import
|
|
3106
|
+
import path9 from "path";
|
|
2600
3107
|
import { createInterface as createInterface2 } from "readline";
|
|
2601
3108
|
|
|
2602
3109
|
// src/list.ts
|
|
@@ -2653,28 +3160,28 @@ async function list(opts) {
|
|
|
2653
3160
|
await listWorkspace(client, opts);
|
|
2654
3161
|
return;
|
|
2655
3162
|
}
|
|
2656
|
-
const [
|
|
3163
|
+
const [data2, groups] = await Promise.all([
|
|
2657
3164
|
client.listArtifacts(),
|
|
2658
3165
|
teamGroups(client)
|
|
2659
3166
|
]);
|
|
2660
3167
|
if (opts.json) {
|
|
2661
3168
|
console.log(
|
|
2662
|
-
JSON.stringify({ artifacts:
|
|
3169
|
+
JSON.stringify({ artifacts: data2.artifacts, workspaces: groups }, null, 2)
|
|
2663
3170
|
);
|
|
2664
3171
|
return;
|
|
2665
3172
|
}
|
|
2666
|
-
const total =
|
|
3173
|
+
const total = data2.artifacts.length + groups.reduce((sum, group) => sum + group.artifacts.length, 0);
|
|
2667
3174
|
if (total === 0) {
|
|
2668
3175
|
console.log(
|
|
2669
3176
|
"No artifacts you can publish to yet \u2014 publish while logged in, or claim one via its claim link."
|
|
2670
3177
|
);
|
|
2671
3178
|
return;
|
|
2672
3179
|
}
|
|
2673
|
-
if (
|
|
2674
|
-
const plural =
|
|
2675
|
-
console.log(`${
|
|
3180
|
+
if (data2.artifacts.length > 0) {
|
|
3181
|
+
const plural = data2.artifacts.length === 1 ? "" : "s";
|
|
3182
|
+
console.log(`${data2.artifacts.length} artifact${plural}
|
|
2676
3183
|
`);
|
|
2677
|
-
for (const artifact of
|
|
3184
|
+
for (const artifact of data2.artifacts) {
|
|
2678
3185
|
printArtifact(artifact);
|
|
2679
3186
|
}
|
|
2680
3187
|
}
|
|
@@ -2825,7 +3332,7 @@ async function link(opts) {
|
|
|
2825
3332
|
}
|
|
2826
3333
|
chosen = await pickArtifact(artifacts);
|
|
2827
3334
|
}
|
|
2828
|
-
const dir =
|
|
3335
|
+
const dir = path9.resolve(opts.dir);
|
|
2829
3336
|
writeConfig(dir, {
|
|
2830
3337
|
apiUrl: apiUrl2,
|
|
2831
3338
|
artifactId: chosen.id,
|
|
@@ -2993,7 +3500,7 @@ function logout(opts) {
|
|
|
2993
3500
|
}
|
|
2994
3501
|
|
|
2995
3502
|
// src/message.ts
|
|
2996
|
-
import
|
|
3503
|
+
import path10 from "path";
|
|
2997
3504
|
async function readStdin2() {
|
|
2998
3505
|
if (process.stdin.isTTY) {
|
|
2999
3506
|
return "";
|
|
@@ -3017,7 +3524,7 @@ async function editMessage(opts) {
|
|
|
3017
3524
|
'Nothing to save. Pass the new text with --body "\u2026", or pipe it on stdin.'
|
|
3018
3525
|
);
|
|
3019
3526
|
}
|
|
3020
|
-
const dir =
|
|
3527
|
+
const dir = path10.resolve(opts.dir);
|
|
3021
3528
|
const target = resolveArtifactTarget(dir, {
|
|
3022
3529
|
apiKey: opts.apiKey,
|
|
3023
3530
|
artifact: opts.artifact
|
|
@@ -3037,7 +3544,7 @@ async function deleteMessage(opts) {
|
|
|
3037
3544
|
"Pass the message id to delete \u2014 get it from `artifact-cafe comments --json`."
|
|
3038
3545
|
);
|
|
3039
3546
|
}
|
|
3040
|
-
const dir =
|
|
3547
|
+
const dir = path10.resolve(opts.dir);
|
|
3041
3548
|
const target = resolveArtifactTarget(dir, {
|
|
3042
3549
|
apiKey: opts.apiKey,
|
|
3043
3550
|
artifact: opts.artifact
|
|
@@ -3052,12 +3559,12 @@ async function deleteMessage(opts) {
|
|
|
3052
3559
|
}
|
|
3053
3560
|
|
|
3054
3561
|
// src/move.ts
|
|
3055
|
-
import
|
|
3562
|
+
import path11 from "path";
|
|
3056
3563
|
async function move(opts) {
|
|
3057
3564
|
if (Boolean(opts.folder) === Boolean(opts.unfiled)) {
|
|
3058
3565
|
throw new Error("Pass exactly one of --folder <name-or-id> or --unfiled.");
|
|
3059
3566
|
}
|
|
3060
|
-
const dir =
|
|
3567
|
+
const dir = path11.resolve(opts.dir);
|
|
3061
3568
|
const target = resolveArtifactTarget(dir, {
|
|
3062
3569
|
apiKey: opts.apiKey,
|
|
3063
3570
|
artifact: opts.artifact
|
|
@@ -3078,8 +3585,8 @@ async function move(opts) {
|
|
|
3078
3585
|
let folderId = null;
|
|
3079
3586
|
let folderName = null;
|
|
3080
3587
|
if (opts.folder) {
|
|
3081
|
-
const
|
|
3082
|
-
const matches =
|
|
3588
|
+
const data2 = await client.listFolders(artifact.workspaceId);
|
|
3589
|
+
const matches = data2.folders.filter(
|
|
3083
3590
|
(candidate) => candidate.name === opts.folder || candidate.id === opts.folder
|
|
3084
3591
|
);
|
|
3085
3592
|
if (matches.length === 0) {
|
|
@@ -3147,9 +3654,9 @@ async function move(opts) {
|
|
|
3147
3654
|
}
|
|
3148
3655
|
|
|
3149
3656
|
// src/open.ts
|
|
3150
|
-
import
|
|
3657
|
+
import path12 from "path";
|
|
3151
3658
|
async function openArtifact(opts) {
|
|
3152
|
-
const dir =
|
|
3659
|
+
const dir = path12.resolve(opts.dir);
|
|
3153
3660
|
const target = resolveArtifactTarget(dir, {
|
|
3154
3661
|
apiKey: opts.apiKey,
|
|
3155
3662
|
artifact: opts.artifact
|
|
@@ -3167,7 +3674,7 @@ async function openArtifact(opts) {
|
|
|
3167
3674
|
}
|
|
3168
3675
|
|
|
3169
3676
|
// src/protect.ts
|
|
3170
|
-
import
|
|
3677
|
+
import path13 from "path";
|
|
3171
3678
|
async function protect(opts) {
|
|
3172
3679
|
if (opts.off && opts.password !== void 0) {
|
|
3173
3680
|
throw new Error("Pass either --password or --off, not both.");
|
|
@@ -3184,7 +3691,7 @@ async function protect(opts) {
|
|
|
3184
3691
|
throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
|
|
3185
3692
|
}
|
|
3186
3693
|
}
|
|
3187
|
-
const dir =
|
|
3694
|
+
const dir = path13.resolve(opts.dir);
|
|
3188
3695
|
const target = resolveArtifactTarget(dir, {
|
|
3189
3696
|
apiKey: opts.apiKey,
|
|
3190
3697
|
artifact: opts.artifact
|
|
@@ -3209,7 +3716,7 @@ async function protect(opts) {
|
|
|
3209
3716
|
}
|
|
3210
3717
|
|
|
3211
3718
|
// src/reply.ts
|
|
3212
|
-
import
|
|
3719
|
+
import path14 from "path";
|
|
3213
3720
|
async function readStdin3() {
|
|
3214
3721
|
if (process.stdin.isTTY) {
|
|
3215
3722
|
return "";
|
|
@@ -3233,7 +3740,7 @@ async function replyToThread(opts) {
|
|
|
3233
3740
|
'Nothing to post. Pass the reply with --body "\u2026", or pipe it on stdin.'
|
|
3234
3741
|
);
|
|
3235
3742
|
}
|
|
3236
|
-
const dir =
|
|
3743
|
+
const dir = path14.resolve(opts.dir);
|
|
3237
3744
|
const target = resolveArtifactTarget(dir, {
|
|
3238
3745
|
apiKey: opts.apiKey,
|
|
3239
3746
|
artifact: opts.artifact
|
|
@@ -3262,7 +3769,7 @@ async function replyToThread(opts) {
|
|
|
3262
3769
|
}
|
|
3263
3770
|
|
|
3264
3771
|
// src/resolve.ts
|
|
3265
|
-
import
|
|
3772
|
+
import path15 from "path";
|
|
3266
3773
|
async function changeThreadStatus(opts) {
|
|
3267
3774
|
const threadId = opts.thread?.trim();
|
|
3268
3775
|
if (!threadId) {
|
|
@@ -3270,7 +3777,7 @@ async function changeThreadStatus(opts) {
|
|
|
3270
3777
|
`Pass the thread id to ${opts.action} \u2014 get it from \`artifact-cafe comments --json\`.`
|
|
3271
3778
|
);
|
|
3272
3779
|
}
|
|
3273
|
-
const dir =
|
|
3780
|
+
const dir = path15.resolve(opts.dir);
|
|
3274
3781
|
const target = resolveArtifactTarget(dir, {
|
|
3275
3782
|
apiKey: opts.apiKey,
|
|
3276
3783
|
artifact: opts.artifact
|
|
@@ -3307,7 +3814,7 @@ async function changeThreadStatus(opts) {
|
|
|
3307
3814
|
}
|
|
3308
3815
|
|
|
3309
3816
|
// src/review.ts
|
|
3310
|
-
import
|
|
3817
|
+
import path16 from "path";
|
|
3311
3818
|
var OUTCOME = {
|
|
3312
3819
|
off: "Plain artifact \u2014 the review tools are hidden entirely",
|
|
3313
3820
|
open: "Open for review \u2014 anyone who can open it can comment",
|
|
@@ -3321,7 +3828,7 @@ async function review(opts) {
|
|
|
3321
3828
|
);
|
|
3322
3829
|
}
|
|
3323
3830
|
const mode = parsed.data;
|
|
3324
|
-
const dir =
|
|
3831
|
+
const dir = path16.resolve(opts.dir);
|
|
3325
3832
|
const target = resolveArtifactTarget(dir, {
|
|
3326
3833
|
apiKey: opts.apiKey,
|
|
3327
3834
|
artifact: opts.artifact
|
|
@@ -3343,10 +3850,10 @@ async function review(opts) {
|
|
|
3343
3850
|
}
|
|
3344
3851
|
|
|
3345
3852
|
// src/share.ts
|
|
3346
|
-
import
|
|
3853
|
+
import path17 from "path";
|
|
3347
3854
|
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
3348
3855
|
async function share(opts) {
|
|
3349
|
-
const dir =
|
|
3856
|
+
const dir = path17.resolve(opts.dir);
|
|
3350
3857
|
const target = resolveArtifactTarget(dir, {
|
|
3351
3858
|
apiKey: opts.apiKey,
|
|
3352
3859
|
artifact: opts.artifact
|
|
@@ -3401,7 +3908,7 @@ General access: ${visibility}`);
|
|
|
3401
3908
|
console.log("");
|
|
3402
3909
|
}
|
|
3403
3910
|
async function unshare(opts) {
|
|
3404
|
-
const dir =
|
|
3911
|
+
const dir = path17.resolve(opts.dir);
|
|
3405
3912
|
const target = resolveArtifactTarget(dir, {
|
|
3406
3913
|
apiKey: opts.apiKey,
|
|
3407
3914
|
artifact: opts.artifact
|
|
@@ -3424,7 +3931,7 @@ async function unshare(opts) {
|
|
|
3424
3931
|
}
|
|
3425
3932
|
|
|
3426
3933
|
// src/status.ts
|
|
3427
|
-
import
|
|
3934
|
+
import path18 from "path";
|
|
3428
3935
|
async function status(opts) {
|
|
3429
3936
|
if (!AGENT_STATES.includes(opts.state)) {
|
|
3430
3937
|
throw new Error(
|
|
@@ -3432,7 +3939,7 @@ async function status(opts) {
|
|
|
3432
3939
|
);
|
|
3433
3940
|
}
|
|
3434
3941
|
const state = opts.state;
|
|
3435
|
-
const dir =
|
|
3942
|
+
const dir = path18.resolve(opts.dir);
|
|
3436
3943
|
const target = resolveArtifactTarget(dir, {
|
|
3437
3944
|
apiKey: opts.apiKey,
|
|
3438
3945
|
artifact: opts.artifact
|
|
@@ -3458,40 +3965,40 @@ async function status(opts) {
|
|
|
3458
3965
|
}
|
|
3459
3966
|
|
|
3460
3967
|
// src/update.ts
|
|
3461
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as
|
|
3968
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
3462
3969
|
import { createRequire } from "module";
|
|
3463
3970
|
import { homedir as homedir2 } from "os";
|
|
3464
|
-
import
|
|
3465
|
-
import { z as
|
|
3971
|
+
import path19 from "path";
|
|
3972
|
+
import { z as z24 } from "zod";
|
|
3466
3973
|
var REGISTRY_LATEST_URL = "https://registry.npmjs.org/artifact-cafe/latest";
|
|
3467
3974
|
var SKILL_SOURCE_URL = "https://raw.githubusercontent.com/Artifact-Cafe/skill/main/artifact-cafe/SKILL.md";
|
|
3468
3975
|
var SKILL_UPDATE_COMMAND = "npx skills add artifact-cafe/skill --skill artifact-cafe -g";
|
|
3469
3976
|
var CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3470
3977
|
var FETCH_TIMEOUT_MS = 2e3;
|
|
3471
3978
|
var SKILL_VERSION_PATTERN = /\*\*Skill version:\s*(\d+\.\d+\.\d+)\*\*/;
|
|
3472
|
-
var cacheSchema =
|
|
3473
|
-
checkedAt:
|
|
3474
|
-
cliLatest:
|
|
3475
|
-
skillLatest:
|
|
3476
|
-
version:
|
|
3979
|
+
var cacheSchema = z24.object({
|
|
3980
|
+
checkedAt: z24.number(),
|
|
3981
|
+
cliLatest: z24.string().nullable(),
|
|
3982
|
+
skillLatest: z24.string().nullable(),
|
|
3983
|
+
version: z24.literal(1)
|
|
3477
3984
|
});
|
|
3478
3985
|
function cliVersion() {
|
|
3479
3986
|
const pkg = createRequire(import.meta.url)("../package.json");
|
|
3480
3987
|
return pkg.version;
|
|
3481
3988
|
}
|
|
3482
3989
|
function cachePath() {
|
|
3483
|
-
return
|
|
3990
|
+
return path19.join(homedir2(), ".artifactcafe", "update-check.json");
|
|
3484
3991
|
}
|
|
3485
3992
|
function readCache() {
|
|
3486
3993
|
try {
|
|
3487
|
-
return cacheSchema.parse(JSON.parse(
|
|
3994
|
+
return cacheSchema.parse(JSON.parse(readFileSync6(cachePath(), "utf8")));
|
|
3488
3995
|
} catch {
|
|
3489
3996
|
return null;
|
|
3490
3997
|
}
|
|
3491
3998
|
}
|
|
3492
3999
|
function writeCache(cache) {
|
|
3493
4000
|
try {
|
|
3494
|
-
mkdirSync3(
|
|
4001
|
+
mkdirSync3(path19.dirname(cachePath()), { mode: 448, recursive: true });
|
|
3495
4002
|
writeFileSync3(cachePath(), `${JSON.stringify(cache, null, 2)}
|
|
3496
4003
|
`);
|
|
3497
4004
|
} catch {
|
|
@@ -3528,7 +4035,7 @@ async function fetchText(url) {
|
|
|
3528
4035
|
}
|
|
3529
4036
|
async function fetchLatestVersions(previous) {
|
|
3530
4037
|
const [cli, skill] = await Promise.all([
|
|
3531
|
-
fetchJson(REGISTRY_LATEST_URL).then((body) =>
|
|
4038
|
+
fetchJson(REGISTRY_LATEST_URL).then((body) => z24.object({ version: z24.string() }).parse(body).version).catch(() => null),
|
|
3532
4039
|
fetchText(SKILL_SOURCE_URL).then((body) => SKILL_VERSION_PATTERN.exec(body)?.[1] ?? null).catch(() => null)
|
|
3533
4040
|
]);
|
|
3534
4041
|
return {
|
|
@@ -3544,12 +4051,12 @@ function installedSkillVersions() {
|
|
|
3544
4051
|
const versions = [];
|
|
3545
4052
|
for (const root of roots) {
|
|
3546
4053
|
for (const dir of dirs) {
|
|
3547
|
-
const file =
|
|
4054
|
+
const file = path19.join(root, dir, "skills", "artifact-cafe", "SKILL.md");
|
|
3548
4055
|
if (!existsSync3(file)) {
|
|
3549
4056
|
continue;
|
|
3550
4057
|
}
|
|
3551
4058
|
try {
|
|
3552
|
-
const found = SKILL_VERSION_PATTERN.exec(
|
|
4059
|
+
const found = SKILL_VERSION_PATTERN.exec(readFileSync6(file, "utf8"));
|
|
3553
4060
|
if (found?.[1]) {
|
|
3554
4061
|
versions.push(found[1]);
|
|
3555
4062
|
}
|
|
@@ -3618,19 +4125,19 @@ async function workspaces(opts) {
|
|
|
3618
4125
|
`Listing workspaces needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
|
|
3619
4126
|
);
|
|
3620
4127
|
}
|
|
3621
|
-
const
|
|
4128
|
+
const data2 = await createApiClient(apiUrl2, key).listWorkspaces();
|
|
3622
4129
|
if (opts.json) {
|
|
3623
|
-
console.log(JSON.stringify(
|
|
4130
|
+
console.log(JSON.stringify(data2, null, 2));
|
|
3624
4131
|
return;
|
|
3625
4132
|
}
|
|
3626
|
-
if (
|
|
4133
|
+
if (data2.workspaces.length === 0) {
|
|
3627
4134
|
console.log("No workspaces yet \u2014 create one from the dashboard.");
|
|
3628
4135
|
return;
|
|
3629
4136
|
}
|
|
3630
|
-
const plural =
|
|
3631
|
-
console.log(`${
|
|
4137
|
+
const plural = data2.workspaces.length === 1 ? "" : "s";
|
|
4138
|
+
console.log(`${data2.workspaces.length} workspace${plural}
|
|
3632
4139
|
`);
|
|
3633
|
-
for (const workspace of
|
|
4140
|
+
for (const workspace of data2.workspaces) {
|
|
3634
4141
|
printWorkspace(workspace);
|
|
3635
4142
|
}
|
|
3636
4143
|
}
|
|
@@ -4599,6 +5106,110 @@ var dashboard = defineCommand({
|
|
|
4599
5106
|
}
|
|
4600
5107
|
}
|
|
4601
5108
|
});
|
|
5109
|
+
var dataLsCommand = defineCommand({
|
|
5110
|
+
args: {
|
|
5111
|
+
collection: {
|
|
5112
|
+
description: "Collection name",
|
|
5113
|
+
required: true,
|
|
5114
|
+
type: "positional"
|
|
5115
|
+
},
|
|
5116
|
+
dir: {
|
|
5117
|
+
default: ".",
|
|
5118
|
+
description: "Published directory",
|
|
5119
|
+
type: "positional"
|
|
5120
|
+
},
|
|
5121
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
5122
|
+
limit: { description: "Max records to show", type: "string" }
|
|
5123
|
+
},
|
|
5124
|
+
meta: {
|
|
5125
|
+
description: "List records in a collection",
|
|
5126
|
+
name: "ls"
|
|
5127
|
+
},
|
|
5128
|
+
async run({ args }) {
|
|
5129
|
+
try {
|
|
5130
|
+
await dataLs({
|
|
5131
|
+
collection: args.collection,
|
|
5132
|
+
dir: args.dir,
|
|
5133
|
+
json: Boolean(args.json),
|
|
5134
|
+
limit: args.limit === void 0 ? void 0 : Number(args.limit)
|
|
5135
|
+
});
|
|
5136
|
+
} catch (err) {
|
|
5137
|
+
printCliError(err, Boolean(args.json));
|
|
5138
|
+
process.exit(1);
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
});
|
|
5142
|
+
var dataExportCommand = defineCommand({
|
|
5143
|
+
args: {
|
|
5144
|
+
dir: {
|
|
5145
|
+
default: ".",
|
|
5146
|
+
description: "Published directory",
|
|
5147
|
+
type: "positional"
|
|
5148
|
+
},
|
|
5149
|
+
// Output is always JSON; accepted so --json works uniformly across
|
|
5150
|
+
// every command (SPEC-2 milestone 20 → CLI integration).
|
|
5151
|
+
json: {
|
|
5152
|
+
description: "Machine-readable output (always on)",
|
|
5153
|
+
type: "boolean"
|
|
5154
|
+
}
|
|
5155
|
+
},
|
|
5156
|
+
meta: {
|
|
5157
|
+
description: "Export every collection's records as JSON",
|
|
5158
|
+
name: "export"
|
|
5159
|
+
},
|
|
5160
|
+
async run({ args }) {
|
|
5161
|
+
try {
|
|
5162
|
+
await dataExport({ dir: args.dir });
|
|
5163
|
+
} catch (err) {
|
|
5164
|
+
printCliError(err, true);
|
|
5165
|
+
process.exit(1);
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
});
|
|
5169
|
+
var dataClearCommand = defineCommand({
|
|
5170
|
+
args: {
|
|
5171
|
+
collection: {
|
|
5172
|
+
description: "Collection name",
|
|
5173
|
+
required: true,
|
|
5174
|
+
type: "positional"
|
|
5175
|
+
},
|
|
5176
|
+
dir: {
|
|
5177
|
+
default: ".",
|
|
5178
|
+
description: "Published directory",
|
|
5179
|
+
type: "positional"
|
|
5180
|
+
},
|
|
5181
|
+
json: { description: "Machine-readable output", type: "boolean" },
|
|
5182
|
+
yes: { description: "Confirm deleting every record", type: "boolean" }
|
|
5183
|
+
},
|
|
5184
|
+
meta: {
|
|
5185
|
+
description: "Delete every record in a collection",
|
|
5186
|
+
name: "clear"
|
|
5187
|
+
},
|
|
5188
|
+
async run({ args }) {
|
|
5189
|
+
try {
|
|
5190
|
+
await dataClear({
|
|
5191
|
+
collection: args.collection,
|
|
5192
|
+
dir: args.dir,
|
|
5193
|
+
json: Boolean(args.json),
|
|
5194
|
+
yes: Boolean(args.yes)
|
|
5195
|
+
});
|
|
5196
|
+
} catch (err) {
|
|
5197
|
+
printCliError(err, Boolean(args.json));
|
|
5198
|
+
process.exit(1);
|
|
5199
|
+
}
|
|
5200
|
+
}
|
|
5201
|
+
});
|
|
5202
|
+
var data = defineCommand({
|
|
5203
|
+
meta: {
|
|
5204
|
+
description: "Read or clear the artifact's shared records",
|
|
5205
|
+
name: "data"
|
|
5206
|
+
},
|
|
5207
|
+
subCommands: {
|
|
5208
|
+
clear: dataClearCommand,
|
|
5209
|
+
export: dataExportCommand,
|
|
5210
|
+
ls: dataLsCommand
|
|
5211
|
+
}
|
|
5212
|
+
});
|
|
4602
5213
|
var main = defineCommand({
|
|
4603
5214
|
meta: {
|
|
4604
5215
|
description: "Publish AI-generated artifacts for review \u2014 https://artifact.cafe",
|
|
@@ -4621,6 +5232,7 @@ var main = defineCommand({
|
|
|
4621
5232
|
comment: comment2,
|
|
4622
5233
|
comments: comments2,
|
|
4623
5234
|
dashboard,
|
|
5235
|
+
data,
|
|
4624
5236
|
delete: del,
|
|
4625
5237
|
edit,
|
|
4626
5238
|
folders: folders2,
|