@sunasteriskrnd/takumi 1.0.0-dev.64 → 1.0.0-dev.66

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 +385 -47
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -19815,7 +19815,7 @@ var package_default;
19815
19815
  var init_package = __esm(() => {
19816
19816
  package_default = {
19817
19817
  name: "@sunasteriskrnd/takumi",
19818
- version: "1.0.0-dev.64",
19818
+ version: "1.0.0-dev.66",
19819
19819
  description: "CLI tool for bootstrapping and managing Takumi projects",
19820
19820
  type: "module",
19821
19821
  repository: {
@@ -20012,7 +20012,7 @@ function getLegacyManifestPath(providerRoot) {
20012
20012
  return join4(providerRoot, LEGACY_MANIFEST_FILENAME);
20013
20013
  }
20014
20014
  function getCliVersion() {
20015
- return "1.0.0-dev.64"?.trim() || process.env.npm_package_version?.trim() || "unknown";
20015
+ return "1.0.0-dev.66"?.trim() || process.env.npm_package_version?.trim() || "unknown";
20016
20016
  }
20017
20017
  function getCliUserAgent() {
20018
20018
  return `${TAKUMI_CLI_NPM_PACKAGE_NAME}/${getCliVersion()}`;
@@ -51450,6 +51450,10 @@ var init_artifact_action_help = __esm(() => {
51450
51450
  {
51451
51451
  flags: "-m, --message <message>",
51452
51452
  description: "Short note describing this version's change (max 100 chars)"
51453
+ },
51454
+ {
51455
+ flags: "--open",
51456
+ description: "Open the artifact in the browser after a successful upload"
51453
51457
  }
51454
51458
  ]
51455
51459
  }
@@ -51692,15 +51696,65 @@ var init_artifact_comments_help = __esm(() => {
51692
51696
  };
51693
51697
  });
51694
51698
 
51699
+ // src/domains/help/commands/artifact-open-help.ts
51700
+ var artifactOpenHelp;
51701
+ var init_artifact_open_help = __esm(() => {
51702
+ artifactOpenHelp = {
51703
+ name: "artifact open",
51704
+ description: "Open the artifact viewer (or the dashboard with no ref) in the browser",
51705
+ usage: "tkm artifact open [uuid|url] [options]",
51706
+ examples: [
51707
+ {
51708
+ command: "tkm artifact open",
51709
+ description: "Open the artifacts dashboard (no artifact reference)"
51710
+ },
51711
+ {
51712
+ command: "tkm artifact open <uuid|url>",
51713
+ description: "Open the latest version of an artifact in the viewer"
51714
+ },
51715
+ {
51716
+ command: "tkm artifact open <uuid|url> --ver 3 --file README.md",
51717
+ description: "Deep-link to version 3 and a specific file inside the artifact"
51718
+ }
51719
+ ],
51720
+ optionGroups: [
51721
+ {
51722
+ title: "Options",
51723
+ options: [
51724
+ {
51725
+ flags: "--ver <n>",
51726
+ description: "Version to open. ?v=<n> in a pasted URL works too; --ver wins when both given"
51727
+ },
51728
+ {
51729
+ flags: "--file <path>",
51730
+ description: "Deep-link to a file INSIDE the artifact (not a local file — contrast with `comments reply --file`, which reads a local file's contents)"
51731
+ },
51732
+ {
51733
+ flags: "--thread <uuid>",
51734
+ description: "Deep-link to a comment thread by id"
51735
+ }
51736
+ ]
51737
+ }
51738
+ ],
51739
+ sections: [
51740
+ {
51741
+ title: "Notes",
51742
+ content: "Makes no API calls — an invalid reference is rejected locally, and a valid-looking but nonexistent artifact simply 404s in the browser."
51743
+ }
51744
+ ]
51745
+ };
51746
+ });
51747
+
51695
51748
  // src/domains/help/commands/artifact-command-help.ts
51696
51749
  var artifactCommandHelp;
51697
51750
  var init_artifact_command_help = __esm(() => {
51698
51751
  init_artifact_action_help();
51699
51752
  init_artifact_comments_help();
51753
+ init_artifact_open_help();
51700
51754
  artifactCommandHelp = {
51701
51755
  name: "artifact",
51702
- description: "List, search, upload, download, and delete Takumi artifacts, and work with their review comments",
51703
- usage: "tkm artifact <list | search <term> | upload <file|dir> | download <uuid|url> | delete <uuid|url> | comments <verb> <uuid|url> [id]>",
51756
+ description: "List, search, upload, download, delete, and open Takumi artifacts, and work with their review comments",
51757
+ usage: "tkm artifact <list | search <term> | upload <file|dir> | download <uuid|url> | delete <uuid|url> | open [uuid|url] | comments <verb> <uuid|url> [id]>",
51704
51758
  examples: [
51705
51759
  {
51706
51760
  command: "tkm artifact upload ./report.html --title 'Weekly Report'",
@@ -51739,6 +51793,10 @@ var init_artifact_command_help = __esm(() => {
51739
51793
  flags: "delete <uuid|url>",
51740
51794
  description: "Soft-delete the whole artifact, or one version with --ver <n>"
51741
51795
  },
51796
+ {
51797
+ flags: "open [uuid|url]",
51798
+ description: "Open the viewer (or dashboard with no ref) in the browser"
51799
+ },
51742
51800
  {
51743
51801
  flags: "comments <verb> <uuid|url> [id]",
51744
51802
  description: "Read and act on review comments (list|reply|solve|reopen|delete)"
@@ -51758,6 +51816,7 @@ var init_artifact_command_help = __esm(() => {
51758
51816
  upload: artifactUploadHelp,
51759
51817
  download: artifactDownloadHelp,
51760
51818
  delete: artifactDeleteHelp,
51819
+ open: artifactOpenHelp,
51761
51820
  comments: artifactCommentsHelp
51762
51821
  }
51763
51822
  };
@@ -51842,6 +51901,7 @@ var init_commands2 = __esm(() => {
51842
51901
  init_artifact_command_help();
51843
51902
  init_artifact_comments_help();
51844
51903
  init_artifact_action_help();
51904
+ init_artifact_open_help();
51845
51905
  init_mcp_command_help();
51846
51906
  init_common_options();
51847
51907
  });
@@ -52920,6 +52980,9 @@ ${API_KEY_VAR}=${apiKey}
52920
52980
  writeFileSync(envPath, content, "utf-8");
52921
52981
  }
52922
52982
  // src/domains/takumi-api/api-error-handler.ts
52983
+ var LICENSE_REQUIRED_REMEDIATION = "Your Takumi license is inactive or expired. Contact your Sun* admin to renew access.";
52984
+ var LICENSE_REQUIRED_EXIT_CODE = 4;
52985
+
52923
52986
  class CkApiError extends Error {
52924
52987
  code;
52925
52988
  status;
@@ -52945,10 +53008,16 @@ function parseApiError(status, body) {
52945
53008
  const message = parsed?.message || parsed?.error || `API error (HTTP ${status})`;
52946
53009
  const retryAfter = parsed?.retryAfter;
52947
53010
  if (parsed?.code && isKnownErrorCode(parsed.code)) {
52948
- return new CkApiError(parsed.code, message, status, retryAfter);
53011
+ const code2 = parsed.code;
53012
+ return new CkApiError(code2, withRemediation(code2, message), status, retryAfter);
52949
53013
  }
52950
53014
  const code = mapStatusToErrorCode(status);
52951
- return new CkApiError(code, message, status, retryAfter);
53015
+ return new CkApiError(code, withRemediation(code, message), status, retryAfter);
53016
+ }
53017
+ function withRemediation(code, message) {
53018
+ if (code === "LICENSE_REQUIRED")
53019
+ return `${message} ${LICENSE_REQUIRED_REMEDIATION}`;
53020
+ return message;
52952
53021
  }
52953
53022
  function isKnownErrorCode(code) {
52954
53023
  const known = new Set([
@@ -53967,38 +54036,83 @@ function showApiHelp() {
53967
54036
  console.log(" takumi api reviewweb seo-backlinks <domain> SEO backlinks");
53968
54037
  console.log();
53969
54038
  }
54039
+ // src/commands/artifact/action-flag-scope.ts
54040
+ var ACTION_SCOPED_FLAGS = {
54041
+ comments: ["status", "author", "cursor", "quiet", "file"],
54042
+ open: ["file", "thread"],
54043
+ upload: ["open", "id"]
54044
+ };
54045
+ var ALL_SCOPED_FLAGS = Array.from(new Set(Object.values(ACTION_SCOPED_FLAGS).flat()));
54046
+ function owningActions(flag) {
54047
+ return Object.entries(ACTION_SCOPED_FLAGS).filter(([, flags]) => flags.includes(flag)).map(([owner]) => `\`artifact ${owner}\``);
54048
+ }
54049
+ function strayFlagDetail(action, opts) {
54050
+ const allowed = ACTION_SCOPED_FLAGS[action] ?? [];
54051
+ const stray = ALL_SCOPED_FLAGS.filter((flag) => !allowed.includes(flag) && opts[flag] !== undefined && opts[flag] !== false);
54052
+ if (stray.length === 0)
54053
+ return null;
54054
+ return stray.map((flag) => `--${flag} (only ${owningActions(flag).join(" or ")})`).join(", ");
54055
+ }
54056
+
53970
54057
  // src/commands/artifact/refs.ts
53971
54058
  init_auth_client();
53972
54059
  var UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
53973
54060
  function isArtifactUuid(id) {
53974
- return UUID_V4_RE.test(id.trim());
54061
+ return UUID_V4_RE.test(String(id).trim());
53975
54062
  }
53976
54063
  function parseArtifactRef(input) {
53977
- const s = input.trim();
54064
+ const s = String(input).trim();
53978
54065
  if (UUID_V4_RE.test(s))
53979
54066
  return s.toLowerCase();
53980
54067
  const m2 = s.match(/\/artifacts\/([^/?#]+)/i);
53981
54068
  const candidate = m2?.[1] ?? "";
53982
54069
  return UUID_V4_RE.test(candidate) ? candidate.toLowerCase() : null;
53983
54070
  }
53984
- function extractVersionFromRef(ref) {
54071
+ var HOST_FIRST_RE = /^[a-z0-9][a-z0-9.-]*(:\d+)?\//i;
54072
+ function tryUrl(s) {
53985
54073
  try {
53986
- const url = new URL(ref.trim());
53987
- const v2 = url.searchParams.get("v");
53988
- if (v2 !== null) {
53989
- const n = Number.parseInt(v2, 10);
53990
- return Number.isFinite(n) && n > 0 ? n : undefined;
53991
- }
53992
- } catch {}
53993
- return;
53994
- }
53995
- function warnIfHostMismatch(input) {
53996
- let inputUrl;
53997
- try {
53998
- inputUrl = new URL(input.trim());
54074
+ return new URL(s);
53999
54075
  } catch {
54076
+ return null;
54077
+ }
54078
+ }
54079
+ function toUrl(ref) {
54080
+ const s = String(ref).trim();
54081
+ if (!s.includes("/"))
54082
+ return null;
54083
+ const direct = tryUrl(s);
54084
+ if (direct && /^https?:$/.test(direct.protocol))
54085
+ return direct;
54086
+ return HOST_FIRST_RE.test(s) ? tryUrl(`https://${s}`) : null;
54087
+ }
54088
+ function extractVersionFromRef(ref) {
54089
+ const url = toUrl(ref);
54090
+ if (!url)
54091
+ return;
54092
+ const v2 = url.searchParams.get("v");
54093
+ if (v2 === null || !/^[1-9]\d*$/.test(v2.trim()))
54000
54094
  return;
54095
+ return Number(v2);
54096
+ }
54097
+ function extractDeepLinkFromRef(ref) {
54098
+ const url = toUrl(ref);
54099
+ if (!url)
54100
+ return {};
54101
+ const parts = {};
54102
+ const file = url.searchParams.get("file");
54103
+ if (file !== null && file.trim() !== "") {
54104
+ parts.file = file;
54001
54105
  }
54106
+ const thread = url.searchParams.get("thread");
54107
+ if (thread !== null && thread.trim() !== "") {
54108
+ parts.thread = thread;
54109
+ }
54110
+ return parts;
54111
+ }
54112
+ function warnIfHostMismatch(input) {
54113
+ const inputUrl = toUrl(input);
54114
+ if (!inputUrl)
54115
+ return;
54002
54116
  let serverHost;
54003
54117
  try {
54004
54118
  serverHost = new URL(getServerUrl()).host;
@@ -55140,6 +55254,100 @@ async function artifactList(term, opts) {
55140
55254
  logger.info(footerLine2(payload));
55141
55255
  }
55142
55256
 
55257
+ // src/domains/sessions/browser-open.ts
55258
+ init_open();
55259
+ function tryOpenBrowser(target) {
55260
+ open_default(target).catch(() => {});
55261
+ }
55262
+
55263
+ // src/commands/artifact/open-in-browser.ts
55264
+ init_environment();
55265
+ function openOrPrint(url, opts) {
55266
+ console.log(url);
55267
+ if (isNonInteractive()) {
55268
+ return;
55269
+ }
55270
+ (opts?._openBrowser ?? tryOpenBrowser)(url);
55271
+ }
55272
+
55273
+ // src/commands/artifact/viewer-url.ts
55274
+ init_auth_client();
55275
+ function buildViewerUrl(parts) {
55276
+ const base = getServerUrl();
55277
+ if (!parts.uuid) {
55278
+ return `${base}/app/artifacts`;
55279
+ }
55280
+ const params = new URLSearchParams;
55281
+ const entries = [
55282
+ ["v", parts.version],
55283
+ ["file", parts.file],
55284
+ ["thread", parts.thread]
55285
+ ];
55286
+ for (const [key, value] of entries) {
55287
+ if (value !== undefined && String(value) !== "") {
55288
+ params.set(key, String(value));
55289
+ }
55290
+ }
55291
+ const query = params.toString();
55292
+ return `${base}/app/artifacts/${parts.uuid}${query ? `?${query}` : ""}`;
55293
+ }
55294
+
55295
+ // src/commands/artifact/open-command.ts
55296
+ function singleValue(label, value) {
55297
+ if (Array.isArray(value)) {
55298
+ console.error(`${label} may only be given once`);
55299
+ process.exitCode = 1;
55300
+ return null;
55301
+ }
55302
+ return String(value).trim();
55303
+ }
55304
+ function normalizeFilePath(file) {
55305
+ return file.replace(/\\/g, "/").replace(/^(\.\/|\/)+/, "");
55306
+ }
55307
+ async function artifactOpen(ref, opts) {
55308
+ if (ref === undefined) {
55309
+ if (opts.version !== undefined || opts.file !== undefined || opts.thread !== undefined) {
55310
+ console.error("tkm artifact open — --ver/--file/--thread require an artifact reference");
55311
+ process.exitCode = 1;
55312
+ return;
55313
+ }
55314
+ openOrPrint(buildViewerUrl({}), opts);
55315
+ return;
55316
+ }
55317
+ const uuid = parseArtifactRef(ref);
55318
+ if (!uuid) {
55319
+ console.error(`Invalid artifact id or URL: ${ref}`);
55320
+ process.exitCode = 1;
55321
+ return;
55322
+ }
55323
+ const version3 = opts.version ?? extractVersionFromRef(ref);
55324
+ const deep = extractDeepLinkFromRef(ref);
55325
+ const rawFile = opts.file ?? deep.file;
55326
+ let file;
55327
+ if (rawFile !== undefined) {
55328
+ const single = singleValue("--file", rawFile);
55329
+ if (single === null)
55330
+ return;
55331
+ file = normalizeFilePath(single);
55332
+ }
55333
+ const rawThread = opts.thread ?? deep.thread;
55334
+ let thread;
55335
+ if (rawThread !== undefined) {
55336
+ const single = singleValue("--thread", rawThread);
55337
+ if (single === null)
55338
+ return;
55339
+ if (!isArtifactUuid(single)) {
55340
+ console.error(`Invalid thread id (must be a UUID): ${single}`);
55341
+ process.exitCode = 1;
55342
+ return;
55343
+ }
55344
+ thread = single.toLowerCase();
55345
+ }
55346
+ warnIfHostMismatch(ref);
55347
+ const url = buildViewerUrl({ uuid, version: version3, file, thread });
55348
+ openOrPrint(url, opts);
55349
+ }
55350
+
55143
55351
  // src/commands/artifact/upload-command.ts
55144
55352
  init_dist2();
55145
55353
  import { promises as fs14 } from "node:fs";
@@ -55273,6 +55481,13 @@ async function artifactUpload(filePath, opts) {
55273
55481
  process.exitCode = 1;
55274
55482
  }
55275
55483
  }
55484
+ function printResultUrl(url, opts) {
55485
+ if (opts.open) {
55486
+ openOrPrint(url, opts);
55487
+ } else {
55488
+ console.log(url);
55489
+ }
55490
+ }
55276
55491
  async function uploadFile(absPath, displayPath, size, token, opts) {
55277
55492
  const ext = extname6(absPath).toLowerCase().slice(1);
55278
55493
  if (!ALLOWED_EXTENSIONS.has(ext)) {
@@ -55349,7 +55564,7 @@ async function uploadFile(absPath, displayPath, size, token, opts) {
55349
55564
  await upsertFolderEntry(absPath, result.uuid, result.version, folderHash);
55350
55565
  spinner.stop("Uploaded.");
55351
55566
  printUploadSummary(summary);
55352
- console.log(result.url);
55567
+ printResultUrl(result.url, opts);
55353
55568
  }
55354
55569
  async function uploadFolder(absPath, displayPath, token, opts) {
55355
55570
  const spinner = de();
@@ -55464,15 +55679,23 @@ async function uploadFolder(absPath, displayPath, token, opts) {
55464
55679
  await upsertFolderEntry(absPath, result.uuid, result.version, folderHash);
55465
55680
  spinner.stop("Uploaded.");
55466
55681
  printUploadSummary(summary);
55467
- console.log(result.url);
55682
+ printResultUrl(result.url, opts);
55468
55683
  }
55469
55684
 
55470
55685
  // src/commands/artifact/artifact-command.ts
55686
+ var ARTIFACT_ACTIONS = new Set([
55687
+ "list",
55688
+ "search",
55689
+ "upload",
55690
+ "download",
55691
+ "delete",
55692
+ "open",
55693
+ "comments"
55694
+ ]);
55471
55695
  function usageError2(hint) {
55472
55696
  console.error(`Usage: ${hint}`);
55473
55697
  process.exitCode = 1;
55474
55698
  }
55475
- var COMMENTS_ONLY_FLAGS = ["status", "author", "cursor", "quiet", "file"];
55476
55699
  async function artifactCommand(action, target, ...rest) {
55477
55700
  if (rest.length !== 1 && rest.length !== 3) {
55478
55701
  throw new TypeError(`artifactCommand: expected 3 or 5 arguments, received ${rest.length + 2}`);
@@ -55480,13 +55703,13 @@ async function artifactCommand(action, target, ...rest) {
55480
55703
  const opts = rest[rest.length - 1];
55481
55704
  const arg3 = rest.length === 3 ? rest[0] : undefined;
55482
55705
  const arg4 = rest.length === 3 ? rest[1] : undefined;
55483
- if (action !== undefined && action !== "comments") {
55484
- if (arg3 !== undefined && arg3 !== null) {
55706
+ if (action !== undefined && ARTIFACT_ACTIONS.has(action)) {
55707
+ if (action !== "comments" && arg3 !== undefined && arg3 !== null) {
55485
55708
  return usageError2(`tkm artifact ${action} — unexpected extra argument "${arg3}"`);
55486
55709
  }
55487
- const stray = COMMENTS_ONLY_FLAGS.filter((flag) => opts[flag] !== undefined);
55488
- if (stray.length > 0) {
55489
- return usageError2(`tkm artifact ${action} — ${stray.map((f3) => `--${f3}`).join(", ")} ${stray.length === 1 ? "applies" : "apply"} only to \`artifact comments\``);
55710
+ const detail = strayFlagDetail(action, opts);
55711
+ if (detail !== null) {
55712
+ return usageError2(`tkm artifact ${action} — ${detail}`);
55490
55713
  }
55491
55714
  }
55492
55715
  switch (action) {
@@ -55522,11 +55745,14 @@ async function artifactCommand(action, target, ...rest) {
55522
55745
  }
55523
55746
  await commentsCommand(target, arg3, arg4, opts);
55524
55747
  break;
55748
+ case "open":
55749
+ await artifactOpen(target, opts);
55750
+ break;
55525
55751
  case undefined:
55526
- usageError2("tkm artifact <list | search <term> | upload <file> | download <uuid|url> | delete <uuid|url> [--ver <n>] | comments <verb> <ref> [id]>");
55752
+ usageError2("tkm artifact <list | search <term> | upload <file> | download <uuid|url> | delete <uuid|url> [--ver <n>] | open [uuid|url] | comments <verb> <ref> [id]>");
55527
55753
  break;
55528
55754
  default:
55529
- console.error(`Unknown artifact action: ${action}. Available: list, search, upload, download, delete, comments`);
55755
+ console.error(`Unknown artifact action: ${action}. Available: list, search, upload, download, delete, open, comments`);
55530
55756
  process.exitCode = 1;
55531
55757
  }
55532
55758
  }
@@ -56146,12 +56372,6 @@ async function configCommand(action, keyOrOptions, valueOrOptions, options2) {
56146
56372
  };
56147
56373
  return configUICommand(uiOpts);
56148
56374
  }
56149
- // src/domains/sessions/browser-open.ts
56150
- init_open();
56151
- function tryOpenBrowser(target) {
56152
- open_default(target).catch(() => {});
56153
- }
56154
-
56155
56375
  // src/domains/sessions/server.ts
56156
56376
  init_logger();
56157
56377
  import * as http from "node:http";
@@ -80048,6 +80268,57 @@ function assetNameFor(kit) {
80048
80268
  return `takumi-${kit}-package.zip`;
80049
80269
  }
80050
80270
 
80271
+ class WorkerAssetError extends Error {
80272
+ status;
80273
+ reason;
80274
+ body;
80275
+ constructor(status2, reason, body) {
80276
+ super(`Worker asset fetch failed (HTTP ${status2}): ${reason}`);
80277
+ this.name = "WorkerAssetError";
80278
+ this.status = status2;
80279
+ this.reason = reason;
80280
+ this.body = body;
80281
+ }
80282
+ }
80283
+ async function parseWorkerAssetError(res) {
80284
+ let body = null;
80285
+ try {
80286
+ body = await res.json();
80287
+ } catch {
80288
+ body = null;
80289
+ }
80290
+ if (res.status === 404)
80291
+ return new WorkerAssetError(res.status, "NOT_FOUND", body);
80292
+ if (res.status === 503)
80293
+ return new WorkerAssetError(res.status, "TRANSIENT", body);
80294
+ if (res.status === 403 && body?.error === "LICENSE_REQUIRED") {
80295
+ return new WorkerAssetError(res.status, "LICENSE_REQUIRED", body);
80296
+ }
80297
+ return new WorkerAssetError(res.status, "UNKNOWN", body);
80298
+ }
80299
+
80300
+ class LicenseRequiredError extends Error {
80301
+ status = 403;
80302
+ reason;
80303
+ constructor(reason) {
80304
+ super(LICENSE_REQUIRED_REMEDIATION);
80305
+ this.name = "LicenseRequiredError";
80306
+ this.reason = reason ?? "no_license_or_entitlement";
80307
+ }
80308
+ }
80309
+ async function licenseErrorFromResponse(res) {
80310
+ if (res.status !== 403)
80311
+ return null;
80312
+ let body = null;
80313
+ try {
80314
+ body = await res.json();
80315
+ } catch {
80316
+ body = null;
80317
+ }
80318
+ if (body?.error === "LICENSE_REQUIRED")
80319
+ return new LicenseRequiredError(body.reason);
80320
+ return null;
80321
+ }
80051
80322
  class WorkerSource {
80052
80323
  serverUrl;
80053
80324
  kit;
@@ -80074,6 +80345,9 @@ class WorkerSource {
80074
80345
  if (res.status === 401)
80075
80346
  throw new NotLoggedInError;
80076
80347
  if (!res.ok) {
80348
+ const licenseError = await licenseErrorFromResponse(res);
80349
+ if (licenseError)
80350
+ throw licenseError;
80077
80351
  throw new Error(`Worker source: GET ${url} failed (HTTP ${res.status})`);
80078
80352
  }
80079
80353
  return ReleaseEntrySchema.parse(await res.json());
@@ -80085,6 +80359,9 @@ class WorkerSource {
80085
80359
  if (res.status === 401)
80086
80360
  throw new NotLoggedInError;
80087
80361
  if (!res.ok) {
80362
+ const licenseError = await licenseErrorFromResponse(res);
80363
+ if (licenseError)
80364
+ throw licenseError;
80088
80365
  throw new Error(`Worker source: GET ${url} failed (HTTP ${res.status})`);
80089
80366
  }
80090
80367
  const parsed = ReleaseListResponseSchema.parse(await res.json());
@@ -80099,6 +80376,9 @@ class WorkerSource {
80099
80376
  if (res.status === 404)
80100
80377
  throw new NotInIndexError(tag);
80101
80378
  if (!res.ok) {
80379
+ const licenseError = await licenseErrorFromResponse(res);
80380
+ if (licenseError)
80381
+ throw licenseError;
80102
80382
  throw new Error(`Worker source: GET ${url} failed (HTTP ${res.status})`);
80103
80383
  }
80104
80384
  return ReleaseEntrySchema.parse(await res.json());
@@ -80109,12 +80389,8 @@ class WorkerSource {
80109
80389
  const res = await fetch(url, { headers });
80110
80390
  if (res.status === 401)
80111
80391
  throw new NotLoggedInError;
80112
- if (res.status === 404) {
80113
- throw new Error(`Asset not found: ${this.kit}/${tag}/${asset}`);
80114
- }
80115
- if (!res.ok) {
80116
- throw new Error(`Worker source: GET ${url} failed (HTTP ${res.status})`);
80117
- }
80392
+ if (!res.ok)
80393
+ throw await parseWorkerAssetError(res);
80118
80394
  return res;
80119
80395
  }
80120
80396
  }
@@ -80431,6 +80707,11 @@ async function createVersionPrompt(kit, choices, _defaultIndex, allowManualEntry
80431
80707
  return selected;
80432
80708
  }
80433
80709
  async function handleSelectionError(error, kit, allowManualEntry, retryCallback) {
80710
+ if (error instanceof LicenseRequiredError) {
80711
+ le(VersionDisplayFormatter.formatError("License required", error.message), import_picocolors21.default.red("License Required"));
80712
+ process.exitCode = LICENSE_REQUIRED_EXIT_CODE;
80713
+ return null;
80714
+ }
80434
80715
  logger.error(`Version selection error: ${error.message}`);
80435
80716
  if (error.message.includes("401") || error.message.includes("403")) {
80436
80717
  le(VersionDisplayFormatter.formatError("Authentication failed", "Please check your GitHub token with: tkm auth"), import_picocolors21.default.red("Authentication Error"));
@@ -92358,6 +92639,27 @@ function normalizeSkillGroupFlag(value) {
92358
92639
  }
92359
92640
 
92360
92641
  // src/cli/command-registry.ts
92642
+ var ARTIFACT_STRING_FLAGS = [
92643
+ "id",
92644
+ "title",
92645
+ "message",
92646
+ "output",
92647
+ "access",
92648
+ "status",
92649
+ "author",
92650
+ "cursor",
92651
+ "file",
92652
+ "thread"
92653
+ ];
92654
+ function stringifyFlags(opts, keys) {
92655
+ const out = {};
92656
+ for (const key of keys) {
92657
+ const value = opts[key];
92658
+ if (value !== undefined && !Array.isArray(value))
92659
+ out[key] = String(value);
92660
+ }
92661
+ return out;
92662
+ }
92361
92663
  function parsePositiveIntFlag(raw, label, example, max) {
92362
92664
  if (raw === undefined)
92363
92665
  return;
@@ -92488,7 +92790,7 @@ function registerCommands(cli) {
92488
92790
  cli.command("api [action] [service] [path]", "Interact with Takumi API and proxy services").option("--method <method>", "HTTP method for proxy requests (default: GET)").option("--body <json>", "Request body as JSON string (proxy only)").option("--query <json>", "Query params as JSON string (proxy only)").option("--key <key>", "API key to use (setup only)").option("--force", "Force re-setup even if key exists (setup only)").option("--json", "Output raw JSON instead of formatted display").option("--locale <locale>", "Locale for vidcap summary/caption (default: en)").option("--max-results <n>", "Max results for vidcap search").option("--second <s>", "Timestamp in seconds for vidcap screenshot").option("--order <order>", "Sort order for vidcap comments (time/relevance)").option("--format <fmt>", "Summary format for reviewweb (bullet/paragraph)").option("--max-length <n>", "Max summary length for reviewweb").option("--instructions <text>", "Extraction instructions for reviewweb extract").option("--template <json>", "JSON template for reviewweb extract").option("--type <type>", "Link type filter for reviewweb links (web/image/file/all)").option("--country <code>", "Country code for reviewweb SEO commands").action(async (action, service, path11, options2) => {
92489
92791
  await apiCommand(action, service, path11, options2);
92490
92792
  });
92491
- cli.command("artifact [action] [target] [arg3] [arg4]", "Manage Takumi artifacts (list | search <term> | upload <file> | download <uuid|url> | delete <uuid|url> | comments <verb> <ref> [id])").option("--id <uuid|url>", "Existing artifact (UUID or viewer URL) to overwrite on upload").option("--title <title>", "Display title for the artifact (max 200 chars)").option("-m, --message <message>", "(upload) Version note, max 100 chars · (comments reply) Comment body").option("-o, --output <dir>", "(download) Output directory; default: slug(title) or uuid in CWD").option("--ver <n>", "Version number (download: download it; delete: delete only that version)").option("--force", "(download) Allow writing into a non-empty directory").option("-y, --yes", "Skip delete confirmation prompt").option("--access <level>", "(list) Filter by access level: private | restricted | organization").option("--page <n>", "(list) Page number, 1-based (default: 1)").option("--limit <n>", "(list) Rows per page · (comments list) Threads per page · max 100").option("--json", "Machine-readable JSON output (list/search/comments)").option("--status <status>", "(comments list) Filter by status: open | solved | all (default: open)").option("--author <author>", "(comments list) Filter by author name, email, or user id").option("--cursor <cursor>", "(comments list) Pagination cursor from a previous page").option("--quiet", "Print only ids, one per line (comments)").option("--file <path>", "(comments reply) Read the message body from this file").action(async (action, target, arg3, arg4, opts) => {
92793
+ cli.command("artifact [action] [target] [arg3] [arg4]", "Manage Takumi artifacts (list | search <term> | upload <file> | download <uuid|url> | delete <uuid|url> | open [uuid|url] | comments <verb> <ref> [id])").option("--id <uuid|url>", "Existing artifact (UUID or viewer URL) to overwrite on upload").option("--title <title>", "Display title for the artifact (max 200 chars)").option("-m, --message <message>", "(upload) Version note, max 100 chars · (comments reply) Comment body").option("-o, --output <dir>", "(download) Output directory; default: slug(title) or uuid in CWD").option("--ver <n>", "Version number (download: download it; delete: delete only that version; open: deep-link)").option("--force", "(download) Allow writing into a non-empty directory").option("-y, --yes", "Skip delete confirmation prompt").option("--access <level>", "(list) Filter by access level: private | restricted | organization").option("--page <n>", "(list) Page number, 1-based (default: 1)").option("--limit <n>", "(list) Rows per page · (comments list) Threads per page · max 100").option("--json", "Machine-readable JSON output (list/search/comments)").option("--status <status>", "(comments list) Filter by status: open | solved | all (default: open)").option("--author <author>", "(comments list) Filter by author name, email, or user id").option("--cursor <cursor>", "(comments list) Pagination cursor from a previous page").option("--quiet", "Print only ids, one per line (comments)").option("--file <path>", "(open) Deep-link to a file · (comments reply) Read the message body from this file").option("--thread <uuid>", "(open) Deep-link to a comment thread").option("--open", "(upload) Open the artifact in the browser after a successful upload").action(async (action, target, arg3, arg4, opts) => {
92492
92794
  const version3 = parsePositiveIntFlag(opts.ver, "version", "--ver 2");
92493
92795
  if (version3 === null)
92494
92796
  return;
@@ -92498,7 +92800,13 @@ function registerCommands(cli) {
92498
92800
  const limit = parsePositiveIntFlag(opts.limit, "limit", "--limit 25", ARTIFACT_LIST_LIMIT_MAX);
92499
92801
  if (limit === null)
92500
92802
  return;
92501
- await artifactCommand(action, target, arg3, arg4, { ...opts, version: version3, page, limit });
92803
+ await artifactCommand(action, target, arg3, arg4, {
92804
+ ...opts,
92805
+ ...stringifyFlags(opts, ARTIFACT_STRING_FLAGS),
92806
+ version: version3,
92807
+ page,
92808
+ limit
92809
+ });
92502
92810
  });
92503
92811
  cli.command("auth [action]", "Sign in/out, refresh, and check Takumi session (login|logout|refresh|status)").option("--json", "Machine-readable JSON output (status, refresh)").option("-f, --force", "Force a token refresh even when the current token is still valid (refresh only)").action(async (action, options2 = {}) => {
92504
92812
  switch (action) {
@@ -92671,6 +92979,36 @@ function getPackageVersion3() {
92671
92979
  return packageVersion2;
92672
92980
  }
92673
92981
 
92982
+ // src/domains/installation/release-source.ts
92983
+ init_auth_client();
92984
+ class WorkerAssetError2 extends Error {
92985
+ status;
92986
+ reason;
92987
+ body;
92988
+ constructor(status2, reason, body) {
92989
+ super(`Worker asset fetch failed (HTTP ${status2}): ${reason}`);
92990
+ this.name = "WorkerAssetError";
92991
+ this.status = status2;
92992
+ this.reason = reason;
92993
+ this.body = body;
92994
+ }
92995
+ }
92996
+ class LicenseRequiredError2 extends Error {
92997
+ status = 403;
92998
+ reason;
92999
+ constructor(reason) {
93000
+ super(LICENSE_REQUIRED_REMEDIATION);
93001
+ this.name = "LicenseRequiredError";
93002
+ this.reason = reason ?? "no_license_or_entitlement";
93003
+ }
93004
+ }
93005
+ function isLicenseRequired(error) {
93006
+ return error instanceof LicenseRequiredError2 || error instanceof WorkerAssetError2 && error.reason === "LICENSE_REQUIRED";
93007
+ }
93008
+
93009
+ // src/domains/takumi-api/api-error-handler.ts
93010
+ var LICENSE_REQUIRED_EXIT_CODE2 = 4;
93011
+
92674
93012
  // src/shared/logger.ts
92675
93013
  init_output_manager();
92676
93014
  var import_picocolors34 = __toESM(require_picocolors(), 1);
@@ -93114,7 +93452,7 @@ var parsed = cli.parse(process.argv, { run: false });
93114
93452
  }
93115
93453
  } catch (error) {
93116
93454
  console.error("CLI error:", error instanceof Error ? error.message : error);
93117
- process.exitCode = 1;
93455
+ process.exitCode = isLicenseRequired(error) ? LICENSE_REQUIRED_EXIT_CODE2 : 1;
93118
93456
  if (output2.isJson()) {
93119
93457
  await output2.flushJson();
93120
93458
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sunasteriskrnd/takumi",
3
- "version": "1.0.0-dev.64",
3
+ "version": "1.0.0-dev.66",
4
4
  "description": "CLI tool for bootstrapping and managing Takumi projects",
5
5
  "type": "module",
6
6
  "repository": {