artifact-cafe 0.0.4 → 0.0.5

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 (2) hide show
  1. package/dist/index.js +355 -119
  2. package/package.json +9 -9
package/dist/index.js CHANGED
@@ -1,10 +1,37 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { defineCommand, runMain } from "citty";
4
+ import { defineCommand, runMain, showUsage } from "citty";
5
+
6
+ // src/banner.ts
7
+ var AMBER = "\x1B[38;2;214;138;57m";
8
+ var BOLD = "\x1B[1m";
9
+ var RESET = "\x1B[0m";
10
+ var MUG = [
11
+ " \u2596",
12
+ " \u2598 \u259D",
13
+ " \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
14
+ " \u2502 \u251C\u2500\u256E",
15
+ " \u2502 \u2502 \u2502",
16
+ " \u2502 \u251C\u2500\u256F",
17
+ " \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F",
18
+ " \u203E\u203E\u203E\u203E\u203E\u203E\u203E\u203E"
19
+ ];
20
+ var WORDMARK = " artifact.cafe";
21
+ function printBanner() {
22
+ const color = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
23
+ const paint = (line, codes) => color ? `${codes}${line}${RESET}` : line;
24
+ console.log();
25
+ for (const line of MUG) {
26
+ console.log(paint(line, AMBER));
27
+ }
28
+ console.log();
29
+ console.log(paint(WORDMARK, AMBER + BOLD));
30
+ console.log();
31
+ }
5
32
 
6
33
  // src/comments.ts
7
- import path2 from "path";
34
+ import path3 from "path";
8
35
 
9
36
  // ../shared/src/account.ts
10
37
  import { z } from "zod";
@@ -519,37 +546,99 @@ async function uploadFile(uploadUrl, content, mimeType) {
519
546
  // src/config.ts
520
547
  import {
521
548
  appendFileSync,
522
- existsSync,
523
- mkdirSync,
524
- readFileSync,
525
- writeFileSync
549
+ existsSync as existsSync2,
550
+ mkdirSync as mkdirSync2,
551
+ readFileSync as readFileSync2,
552
+ writeFileSync as writeFileSync2
526
553
  } from "fs";
554
+ import path2 from "path";
555
+ import { z as z13 } from "zod";
556
+
557
+ // src/credentials.ts
558
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
559
+ import { homedir } from "os";
527
560
  import path from "path";
528
561
  import { z as z12 } from "zod";
529
- var configSchema = z12.object({
530
- apiUrl: z12.string(),
531
- artifactId: z12.string(),
532
- publishToken: z12.string(),
533
- slug: z12.string(),
534
- url: z12.string()
562
+ var DEFAULT_API_URL = "https://artifact.cafe";
563
+ var credentialsSchema = z12.object({
564
+ keys: z12.record(z12.string(), z12.string()),
565
+ version: z12.literal(1)
566
+ });
567
+ function credentialsPath() {
568
+ return path.join(homedir(), ".artifactcafe", "credentials");
569
+ }
570
+ function apiOrigin(apiUrl2) {
571
+ return new URL(apiUrl2).origin;
572
+ }
573
+ function readCredentials() {
574
+ const file = credentialsPath();
575
+ if (!existsSync(file)) {
576
+ return { keys: {}, version: 1 };
577
+ }
578
+ try {
579
+ return credentialsSchema.parse(JSON.parse(readFileSync(file, "utf8")));
580
+ } catch {
581
+ return { keys: {}, version: 1 };
582
+ }
583
+ }
584
+ function writeCredentials(credentials) {
585
+ const file = credentialsPath();
586
+ mkdirSync(path.dirname(file), { mode: 448, recursive: true });
587
+ writeFileSync(file, `${JSON.stringify(credentials, null, 2)}
588
+ `, {
589
+ mode: 384
590
+ });
591
+ }
592
+ function storedApiKey(apiUrl2) {
593
+ return readCredentials().keys[apiOrigin(apiUrl2)] ?? null;
594
+ }
595
+ function storeApiKey(apiUrl2, key) {
596
+ const credentials = readCredentials();
597
+ credentials.keys[apiOrigin(apiUrl2)] = key;
598
+ writeCredentials(credentials);
599
+ }
600
+ function removeApiKey(apiUrl2) {
601
+ const credentials = readCredentials();
602
+ const origin = apiOrigin(apiUrl2);
603
+ if (!(origin in credentials.keys)) {
604
+ return false;
605
+ }
606
+ delete credentials.keys[origin];
607
+ writeCredentials(credentials);
608
+ return true;
609
+ }
610
+ function resolveApiKey(apiUrl2, flag) {
611
+ return flag ?? process.env.ARTIFACT_CAFE_API_KEY ?? storedApiKey(apiUrl2) ?? void 0;
612
+ }
613
+
614
+ // src/config.ts
615
+ var configSchema = z13.object({
616
+ apiUrl: z13.string(),
617
+ artifactId: z13.string(),
618
+ // Optional: a folder linked by `link` to an artifact you own carries no
619
+ // token — owner actions authorize via your stored login instead. A first
620
+ // publish still writes the anonymous-mode token here.
621
+ publishToken: z13.string().optional(),
622
+ slug: z13.string(),
623
+ url: z13.string()
535
624
  });
536
625
  var CONFIG_DIR = ".artifactcafe";
537
626
  var CONFIG_FILE = "config.json";
538
627
  function readConfig(dir) {
539
- const file = path.join(dir, CONFIG_DIR, CONFIG_FILE);
540
- if (!existsSync(file)) {
628
+ const file = path2.join(dir, CONFIG_DIR, CONFIG_FILE);
629
+ if (!existsSync2(file)) {
541
630
  return null;
542
631
  }
543
632
  try {
544
- return configSchema.parse(JSON.parse(readFileSync(file, "utf8")));
633
+ return configSchema.parse(JSON.parse(readFileSync2(file, "utf8")));
545
634
  } catch {
546
635
  return null;
547
636
  }
548
637
  }
549
638
  function writeConfig(dir, config) {
550
- mkdirSync(path.join(dir, CONFIG_DIR), { recursive: true });
551
- writeFileSync(
552
- path.join(dir, CONFIG_DIR, CONFIG_FILE),
639
+ mkdirSync2(path2.join(dir, CONFIG_DIR), { recursive: true });
640
+ writeFileSync2(
641
+ path2.join(dir, CONFIG_DIR, CONFIG_FILE),
553
642
  `${JSON.stringify(config, null, 2)}
554
643
  `,
555
644
  { mode: 384 }
@@ -557,11 +646,11 @@ function writeConfig(dir, config) {
557
646
  ensureGitignored(dir);
558
647
  }
559
648
  function ensureGitignored(dir) {
560
- const gitignore = path.join(dir, ".gitignore");
561
- if (!existsSync(gitignore)) {
649
+ const gitignore = path2.join(dir, ".gitignore");
650
+ if (!existsSync2(gitignore)) {
562
651
  return;
563
652
  }
564
- const lines = readFileSync(gitignore, "utf8").split(/\r?\n/);
653
+ const lines = readFileSync2(gitignore, "utf8").split(/\r?\n/);
565
654
  const present = lines.some((l) => {
566
655
  const t = l.trim();
567
656
  return t === CONFIG_DIR || t === `${CONFIG_DIR}/` || t === `/${CONFIG_DIR}` || t === `/${CONFIG_DIR}/`;
@@ -576,6 +665,23 @@ ${CONFIG_DIR}/
576
665
  );
577
666
  }
578
667
  }
668
+ function reviewUrl(apiUrl2, slug) {
669
+ return `${apiUrl2.replace(/\/$/, "")}/a/${slug}`;
670
+ }
671
+ function resolveArtifactTarget(dir, opts = {}) {
672
+ const config = readConfig(dir);
673
+ const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config?.apiUrl ?? DEFAULT_API_URL;
674
+ const artifactId = opts.artifact ?? process.env.ARTIFACT_CAFE_ARTIFACT_ID ?? config?.artifactId ?? null;
675
+ if (!artifactId) {
676
+ throw new Error(
677
+ `No artifact linked in ${dir}. Run \`artifact-cafe publish\` here, \`artifact-cafe link\` to bind one you own, or pass --artifact <id> (or set ARTIFACT_CAFE_ARTIFACT_ID) to act on an artifact linked elsewhere.`
678
+ );
679
+ }
680
+ const folderToken = artifactId === config?.artifactId ? config.publishToken : void 0;
681
+ const credential = folderToken ?? process.env.ARTIFACT_CAFE_TOKEN ?? resolveApiKey(apiUrl2, opts.apiKey);
682
+ const url = artifactId === config?.artifactId ? config.url : void 0;
683
+ return { apiUrl: apiUrl2, artifactId, credential, url };
684
+ }
579
685
 
580
686
  // src/comments.ts
581
687
  var STATUSES = ["open", "resolved", "all"];
@@ -596,17 +702,16 @@ async function comments(opts) {
596
702
  }
597
703
  const status = opts.status;
598
704
  const version = opts.version;
599
- const dir = path2.resolve(opts.dir);
600
- const config = readConfig(dir);
601
- if (!config) {
602
- throw new Error(
603
- `No artifact linked in ${dir} \u2014 run \`artifact-cafe publish\` there first (.artifactcafe/config.json not found).`
604
- );
605
- }
606
- const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config.apiUrl;
607
- const client = createApiClient(apiUrl2, config.publishToken);
705
+ const dir = path3.resolve(opts.dir);
706
+ const target = resolveArtifactTarget(dir, {
707
+ apiKey: opts.apiKey,
708
+ artifact: opts.artifact
709
+ });
710
+ const client = createApiClient(target.apiUrl, target.credential);
711
+ const url = target.url ?? (opts.json ? "" : await deriveReviewUrl(client, target));
712
+ const ref = { artifactId: target.artifactId, url };
608
713
  if (opts.wait) {
609
- await waitForFeedback(client, config, {
714
+ await waitForFeedback(client, ref, {
610
715
  json: opts.json,
611
716
  status,
612
717
  timeoutSeconds: opts.timeout,
@@ -614,7 +719,7 @@ async function comments(opts) {
614
719
  });
615
720
  return;
616
721
  }
617
- const data = await client.exportComments(config.artifactId, {
722
+ const data = await client.exportComments(target.artifactId, {
618
723
  status,
619
724
  version
620
725
  });
@@ -622,7 +727,11 @@ async function comments(opts) {
622
727
  console.log(JSON.stringify(data, null, 2));
623
728
  return;
624
729
  }
625
- printHuman(data, config.url, status, version);
730
+ printHuman(data, url, status, version);
731
+ }
732
+ async function deriveReviewUrl(client, target) {
733
+ const artifact = await client.getArtifact(target.artifactId);
734
+ return reviewUrl(target.apiUrl, artifact.slug);
626
735
  }
627
736
  var sleep = (s) => new Promise((r) => setTimeout(r, s * 1e3));
628
737
  function messageCounts(data) {
@@ -777,63 +886,6 @@ function indent(text, pad) {
777
886
  // src/dashboard.ts
778
887
  import path6 from "path";
779
888
 
780
- // src/credentials.ts
781
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
782
- import { homedir } from "os";
783
- import path3 from "path";
784
- import { z as z13 } from "zod";
785
- var DEFAULT_API_URL = "https://artifact.cafe";
786
- var credentialsSchema = z13.object({
787
- keys: z13.record(z13.string(), z13.string()),
788
- version: z13.literal(1)
789
- });
790
- function credentialsPath() {
791
- return path3.join(homedir(), ".artifactcafe", "credentials");
792
- }
793
- function apiOrigin(apiUrl2) {
794
- return new URL(apiUrl2).origin;
795
- }
796
- function readCredentials() {
797
- const file = credentialsPath();
798
- if (!existsSync2(file)) {
799
- return { keys: {}, version: 1 };
800
- }
801
- try {
802
- return credentialsSchema.parse(JSON.parse(readFileSync2(file, "utf8")));
803
- } catch {
804
- return { keys: {}, version: 1 };
805
- }
806
- }
807
- function writeCredentials(credentials) {
808
- const file = credentialsPath();
809
- mkdirSync2(path3.dirname(file), { mode: 448, recursive: true });
810
- writeFileSync2(file, `${JSON.stringify(credentials, null, 2)}
811
- `, {
812
- mode: 384
813
- });
814
- }
815
- function storedApiKey(apiUrl2) {
816
- return readCredentials().keys[apiOrigin(apiUrl2)] ?? null;
817
- }
818
- function storeApiKey(apiUrl2, key) {
819
- const credentials = readCredentials();
820
- credentials.keys[apiOrigin(apiUrl2)] = key;
821
- writeCredentials(credentials);
822
- }
823
- function removeApiKey(apiUrl2) {
824
- const credentials = readCredentials();
825
- const origin = apiOrigin(apiUrl2);
826
- if (!(origin in credentials.keys)) {
827
- return false;
828
- }
829
- delete credentials.keys[origin];
830
- writeCredentials(credentials);
831
- return true;
832
- }
833
- function resolveApiKey(apiUrl2, flag) {
834
- return flag ?? process.env.ARTIFACT_CAFE_API_KEY ?? storedApiKey(apiUrl2) ?? void 0;
835
- }
836
-
837
889
  // src/publish.ts
838
890
  import { exec } from "child_process";
839
891
  import { readFileSync as readFileSync4 } from "fs";
@@ -1169,6 +1221,102 @@ function printCliError(err, json) {
1169
1221
  }
1170
1222
  }
1171
1223
 
1224
+ // src/link.ts
1225
+ import path7 from "path";
1226
+ import { createInterface } from "readline";
1227
+ async function link(opts) {
1228
+ const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
1229
+ /\/$/,
1230
+ ""
1231
+ );
1232
+ const key = resolveApiKey(apiUrl2, opts.apiKey);
1233
+ if (!key) {
1234
+ throw new Error(
1235
+ `Linking needs an account \u2014 run \`artifact-cafe login\` first (no API key for ${apiOrigin(apiUrl2)}).`
1236
+ );
1237
+ }
1238
+ const { artifacts } = await createApiClient(apiUrl2, key).listArtifacts();
1239
+ if (artifacts.length === 0) {
1240
+ throw new Error(
1241
+ "No artifacts in your account yet \u2014 publish one first, or claim one via its claim link."
1242
+ );
1243
+ }
1244
+ const selector = opts.artifact ?? opts.slug;
1245
+ let chosen;
1246
+ if (opts.artifact) {
1247
+ chosen = artifacts.find((a) => a.id === opts.artifact);
1248
+ } else if (opts.slug) {
1249
+ chosen = artifacts.find((a) => a.slug === opts.slug);
1250
+ }
1251
+ if (selector && !chosen) {
1252
+ throw new Error(
1253
+ `No artifact "${selector}" in your account \u2014 run \`artifact-cafe list\` to see them.`
1254
+ );
1255
+ }
1256
+ if (opts.json && !chosen) {
1257
+ console.log(JSON.stringify({ artifacts }, null, 2));
1258
+ return;
1259
+ }
1260
+ if (!chosen) {
1261
+ if (!process.stdin.isTTY) {
1262
+ throw new Error(
1263
+ "Pick an artifact with --artifact <id> or --slug <slug> (or --json to list them). Interactive selection needs a terminal."
1264
+ );
1265
+ }
1266
+ chosen = await pickArtifact(artifacts);
1267
+ }
1268
+ const dir = path7.resolve(opts.dir);
1269
+ writeConfig(dir, {
1270
+ apiUrl: apiUrl2,
1271
+ artifactId: chosen.id,
1272
+ slug: chosen.slug,
1273
+ url: chosen.url
1274
+ // No publishToken — this folder authorizes via your login, not a secret.
1275
+ });
1276
+ if (opts.json) {
1277
+ console.log(
1278
+ JSON.stringify(
1279
+ {
1280
+ artifactId: chosen.id,
1281
+ linked: true,
1282
+ slug: chosen.slug,
1283
+ url: chosen.url
1284
+ },
1285
+ null,
1286
+ 2
1287
+ )
1288
+ );
1289
+ return;
1290
+ }
1291
+ console.log(`
1292
+ \u2713 Linked ${dir} \u2192 "${chosen.title}" \u2014 ${chosen.url}`);
1293
+ console.log(
1294
+ " Owner actions (publish, comments --wait, protect) use your login.\n"
1295
+ );
1296
+ }
1297
+ function pickArtifact(artifacts) {
1298
+ console.log("Your artifacts:\n");
1299
+ artifacts.forEach((a, i) => {
1300
+ const v = a.currentVersionNumber ? `v${a.currentVersionNumber}` : "\u2014";
1301
+ console.log(
1302
+ ` ${String(i + 1).padStart(2)}. ${a.title} \xB7 ${v} \xB7 ${a.url}`
1303
+ );
1304
+ });
1305
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1306
+ return new Promise((resolve, reject) => {
1307
+ rl.question(`
1308
+ Link which? [1-${artifacts.length}]: `, (answer) => {
1309
+ rl.close();
1310
+ const n = Number(answer.trim());
1311
+ if (!(Number.isInteger(n) && n >= 1 && n <= artifacts.length)) {
1312
+ reject(new Error(`Enter a number between 1 and ${artifacts.length}.`));
1313
+ return;
1314
+ }
1315
+ resolve(artifacts[n - 1]);
1316
+ });
1317
+ });
1318
+ }
1319
+
1172
1320
  // src/list.ts
1173
1321
  async function list(opts) {
1174
1322
  const apiUrl2 = (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(
@@ -1226,13 +1374,13 @@ function relativeTime(iso) {
1226
1374
  }
1227
1375
 
1228
1376
  // src/login.ts
1229
- import { createInterface } from "readline";
1377
+ import { createInterface as createInterface2 } from "readline";
1230
1378
  function apiUrl() {
1231
1379
  return (process.env.ARTIFACT_CAFE_URL ?? DEFAULT_API_URL).replace(/\/$/, "");
1232
1380
  }
1233
1381
  function promptSecret(question) {
1234
1382
  return new Promise((resolve) => {
1235
- const rl = createInterface({
1383
+ const rl = createInterface2({
1236
1384
  input: process.stdin,
1237
1385
  output: process.stdout
1238
1386
  });
@@ -1340,23 +1488,27 @@ function logout(opts) {
1340
1488
  }
1341
1489
 
1342
1490
  // src/open.ts
1343
- import path7 from "path";
1344
- function openArtifact(opts) {
1345
- const dir = path7.resolve(opts.dir);
1346
- const config = readConfig(dir);
1347
- if (!config) {
1348
- throw new Error(
1349
- `No artifact linked in ${dir} \u2014 run \`artifact-cafe publish\` there first (.artifactcafe/config.json not found).`
1350
- );
1351
- }
1352
- console.log(config.url);
1491
+ import path8 from "path";
1492
+ async function openArtifact(opts) {
1493
+ const dir = path8.resolve(opts.dir);
1494
+ const target = resolveArtifactTarget(dir, {
1495
+ apiKey: opts.apiKey,
1496
+ artifact: opts.artifact
1497
+ });
1498
+ const url = target.url ?? reviewUrl(
1499
+ target.apiUrl,
1500
+ (await createApiClient(target.apiUrl, target.credential).getArtifact(
1501
+ target.artifactId
1502
+ )).slug
1503
+ );
1504
+ console.log(url);
1353
1505
  if (process.stdout.isTTY) {
1354
- openInBrowser(config.url);
1506
+ openInBrowser(url);
1355
1507
  }
1356
1508
  }
1357
1509
 
1358
1510
  // src/protect.ts
1359
- import path8 from "path";
1511
+ import path9 from "path";
1360
1512
  async function protect(opts) {
1361
1513
  if (opts.off && opts.password !== void 0) {
1362
1514
  throw new Error("Pass either --password or --off, not both.");
@@ -1373,26 +1525,26 @@ async function protect(opts) {
1373
1525
  throw new Error(parsed.error.issues[0]?.message ?? "Invalid password.");
1374
1526
  }
1375
1527
  }
1376
- const dir = path8.resolve(opts.dir);
1377
- const config = readConfig(dir);
1378
- if (!config) {
1379
- throw new Error(
1380
- `No artifact linked in ${dir} \u2014 run \`artifact-cafe publish\` there first (.artifactcafe/config.json not found).`
1381
- );
1382
- }
1383
- const apiUrl2 = process.env.ARTIFACT_CAFE_URL ?? config.apiUrl;
1384
- const token = config.publishToken ?? process.env.ARTIFACT_CAFE_TOKEN;
1385
- const client = createApiClient(apiUrl2, token);
1386
- const res = await client.setProtection(config.artifactId, password);
1528
+ const dir = path9.resolve(opts.dir);
1529
+ const target = resolveArtifactTarget(dir, {
1530
+ apiKey: opts.apiKey,
1531
+ artifact: opts.artifact
1532
+ });
1533
+ const client = createApiClient(target.apiUrl, target.credential);
1534
+ const res = await client.setProtection(target.artifactId, password);
1387
1535
  if (opts.json) {
1388
1536
  console.log(JSON.stringify({ protected: res.protected }, null, 2));
1389
1537
  return;
1390
1538
  }
1539
+ const url = target.url ?? reviewUrl(
1540
+ target.apiUrl,
1541
+ (await client.getArtifact(target.artifactId)).slug
1542
+ );
1391
1543
  console.log(
1392
1544
  res.protected ? `
1393
- \u2713 Password protection on \u2014 ${config.url}
1545
+ \u2713 Password protection on \u2014 ${url}
1394
1546
  ` : `
1395
- \u2713 Password protection removed \u2014 ${config.url}
1547
+ \u2713 Password protection removed \u2014 ${url}
1396
1548
  `
1397
1549
  );
1398
1550
  }
@@ -1517,8 +1669,56 @@ var list2 = defineCommand({
1517
1669
  }
1518
1670
  }
1519
1671
  });
1672
+ var link2 = defineCommand({
1673
+ args: {
1674
+ "api-key": {
1675
+ description: "Link with this API key (default: stored login)",
1676
+ type: "string"
1677
+ },
1678
+ artifact: {
1679
+ description: "Link this artifact id (skip the picker)",
1680
+ type: "string"
1681
+ },
1682
+ dir: {
1683
+ default: ".",
1684
+ description: "Directory to link",
1685
+ type: "positional"
1686
+ },
1687
+ json: { description: "Machine-readable output", type: "boolean" },
1688
+ slug: {
1689
+ description: "Link this artifact slug (skip the picker)",
1690
+ type: "string"
1691
+ }
1692
+ },
1693
+ meta: {
1694
+ description: "Link a folder to one of your artifacts (pick from your account)",
1695
+ name: "link"
1696
+ },
1697
+ async run({ args }) {
1698
+ try {
1699
+ await link({
1700
+ apiKey: args["api-key"],
1701
+ artifact: args.artifact,
1702
+ dir: args.dir,
1703
+ json: Boolean(args.json),
1704
+ slug: args.slug
1705
+ });
1706
+ } catch (err) {
1707
+ printCliError(err, Boolean(args.json));
1708
+ process.exit(1);
1709
+ }
1710
+ }
1711
+ });
1520
1712
  var comments2 = defineCommand({
1521
1713
  args: {
1714
+ "api-key": {
1715
+ description: "Authorize with an account API key (default: stored login)",
1716
+ type: "string"
1717
+ },
1718
+ artifact: {
1719
+ description: "Act on an artifact ID instead of the linked folder",
1720
+ type: "string"
1721
+ },
1522
1722
  dir: {
1523
1723
  default: ".",
1524
1724
  description: "Published directory",
@@ -1558,6 +1758,8 @@ var comments2 = defineCommand({
1558
1758
  );
1559
1759
  }
1560
1760
  await comments({
1761
+ apiKey: args["api-key"],
1762
+ artifact: args.artifact,
1561
1763
  dir: args.dir,
1562
1764
  json: Boolean(args.json),
1563
1765
  status: args.status,
@@ -1573,6 +1775,14 @@ var comments2 = defineCommand({
1573
1775
  });
1574
1776
  var protect2 = defineCommand({
1575
1777
  args: {
1778
+ "api-key": {
1779
+ description: "Authorize with an account API key (default: stored login)",
1780
+ type: "string"
1781
+ },
1782
+ artifact: {
1783
+ description: "Act on an artifact ID instead of the linked folder",
1784
+ type: "string"
1785
+ },
1576
1786
  dir: {
1577
1787
  default: ".",
1578
1788
  description: "Published directory",
@@ -1589,6 +1799,8 @@ var protect2 = defineCommand({
1589
1799
  async run({ args }) {
1590
1800
  try {
1591
1801
  await protect({
1802
+ apiKey: args["api-key"],
1803
+ artifact: args.artifact,
1592
1804
  dir: args.dir,
1593
1805
  json: Boolean(args.json),
1594
1806
  off: Boolean(args.off),
@@ -1602,6 +1814,14 @@ var protect2 = defineCommand({
1602
1814
  });
1603
1815
  var open = defineCommand({
1604
1816
  args: {
1817
+ "api-key": {
1818
+ description: "Authorize with an account API key (default: stored login)",
1819
+ type: "string"
1820
+ },
1821
+ artifact: {
1822
+ description: "Open an artifact ID instead of the linked folder",
1823
+ type: "string"
1824
+ },
1605
1825
  dir: {
1606
1826
  default: ".",
1607
1827
  description: "Published directory",
@@ -1609,9 +1829,13 @@ var open = defineCommand({
1609
1829
  }
1610
1830
  },
1611
1831
  meta: { description: "Open the linked artifact's review URL", name: "open" },
1612
- run({ args }) {
1832
+ async run({ args }) {
1613
1833
  try {
1614
- openArtifact({ dir: args.dir });
1834
+ await openArtifact({
1835
+ apiKey: args["api-key"],
1836
+ artifact: args.artifact,
1837
+ dir: args.dir
1838
+ });
1615
1839
  } catch (err) {
1616
1840
  printCliError(err, false);
1617
1841
  process.exit(1);
@@ -1644,9 +1868,21 @@ var main = defineCommand({
1644
1868
  description: "Publish AI-generated artifacts for review \u2014 https://artifact.cafe",
1645
1869
  name: "artifact-cafe"
1646
1870
  },
1871
+ // Bare `artifact-cafe` (no subcommand) → brand banner + usage. citty
1872
+ // still calls this parent run *after* a matched subcommand, so bail when
1873
+ // a subcommand token is present (any non-flag arg; citty has already
1874
+ // rejected unknown ones with E_UNKNOWN_COMMAND before we get here).
1875
+ async run({ cmd, rawArgs }) {
1876
+ if (rawArgs.some((arg) => !arg.startsWith("-"))) {
1877
+ return;
1878
+ }
1879
+ printBanner();
1880
+ await showUsage(cmd);
1881
+ },
1647
1882
  subCommands: {
1648
1883
  comments: comments2,
1649
1884
  dashboard,
1885
+ link: link2,
1650
1886
  list: list2,
1651
1887
  login: login2,
1652
1888
  logout: logout2,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artifact-cafe",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Publish AI-generated artifacts for review on artifact.cafe",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,19 +9,19 @@
9
9
  "files": [
10
10
  "dist"
11
11
  ],
12
- "scripts": {
13
- "build": "tsup",
14
- "typecheck": "tsc --noEmit",
15
- "dev": "tsup --watch"
16
- },
17
12
  "dependencies": {
18
13
  "citty": "^0.1.6",
19
14
  "zod": "^4.1.0"
20
15
  },
21
16
  "devDependencies": {
22
- "@artifact-cafe/shared": "workspace:*",
23
17
  "@types/node": "^24.0.0",
24
18
  "tsup": "^8.5.0",
25
- "typescript": "^5.9.0"
19
+ "typescript": "^5.9.0",
20
+ "@artifact-cafe/shared": "0.1.0"
21
+ },
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "typecheck": "tsc --noEmit",
25
+ "dev": "tsup --watch"
26
26
  }
27
- }
27
+ }