@sunasteriskrnd/takumi 0.11.0 → 0.12.1

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 +209 -31
  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: "0.11.0",
19818
+ version: "0.12.1",
19819
19819
  description: "CLI tool for bootstrapping and managing Takumi projects",
19820
19820
  type: "module",
19821
19821
  repository: {
@@ -39263,7 +39263,7 @@ async function installSkillsDependencies(skillsDir, options2 = {}) {
39263
39263
  let effectiveSkillsDir = skillsDir;
39264
39264
  if (!existsSync8(scriptPath)) {
39265
39265
  const { homedir: homedir6 } = await import("node:os");
39266
- const globalSkillsDir = join38(homedir6(), ".claude", "skills");
39266
+ const globalSkillsDir = process.env.TKM_GLOBAL_SKILLS_DIR ?? join38(homedir6(), ".claude", "skills");
39267
39267
  const globalScriptPath = join38(globalSkillsDir, scriptName);
39268
39268
  if (existsSync8(globalScriptPath)) {
39269
39269
  logger.debug(`Local install script not found, using global: ${globalScriptPath}`);
@@ -51332,6 +51332,82 @@ var init_auth_command_help = __esm(() => {
51332
51332
  };
51333
51333
  });
51334
51334
 
51335
+ // src/domains/help/commands/artifact-command-help.ts
51336
+ var artifactCommandHelp;
51337
+ var init_artifact_command_help = __esm(() => {
51338
+ artifactCommandHelp = {
51339
+ name: "artifact",
51340
+ description: "Upload, download, and delete Takumi artifacts (upload|download|delete)",
51341
+ usage: "tkm artifact <upload <file|dir> | download <uuid|url> | delete <uuid|url>>",
51342
+ examples: [
51343
+ {
51344
+ command: "tkm artifact upload ./report.html --title 'Weekly Report'",
51345
+ description: "Upload a file (or directory) as a new artifact and print its share URL"
51346
+ },
51347
+ {
51348
+ command: "tkm artifact download <uuid|url> -o ./out --ver 3",
51349
+ description: "Download a specific version's files into a directory"
51350
+ },
51351
+ {
51352
+ command: "tkm artifact delete <uuid|url> --ver 2",
51353
+ description: "Delete ONLY version 2 (soft delete; other versions stay). A pasted URL with ?v=2 does the same"
51354
+ }
51355
+ ],
51356
+ optionGroups: [
51357
+ {
51358
+ title: "Actions",
51359
+ options: [
51360
+ {
51361
+ flags: "upload <file|dir>",
51362
+ description: "Upload a file or directory as a new artifact (or new version with --id)"
51363
+ },
51364
+ {
51365
+ flags: "download <uuid|url>",
51366
+ description: "Download an artifact's files (latest or --ver <n>)"
51367
+ },
51368
+ {
51369
+ flags: "delete <uuid|url>",
51370
+ description: "Soft-delete the whole artifact, or one version with --ver <n>"
51371
+ }
51372
+ ]
51373
+ },
51374
+ {
51375
+ title: "Options",
51376
+ options: [
51377
+ {
51378
+ flags: "--id <uuid|url>",
51379
+ description: "(upload) Existing artifact to overwrite with a new version"
51380
+ },
51381
+ {
51382
+ flags: "--title <title>",
51383
+ description: "(upload) Display title for the artifact (max 200 chars)"
51384
+ },
51385
+ {
51386
+ flags: "-m, --message <message>",
51387
+ description: "(upload) Short note describing this version's change (max 100 chars)"
51388
+ },
51389
+ {
51390
+ flags: "-o, --output <dir>",
51391
+ description: "(download) Output directory; default: slug(title) or uuid in CWD"
51392
+ },
51393
+ {
51394
+ flags: "--ver <n>",
51395
+ description: "(download) Version to download | (delete) version to delete. ?v=<n> in a pasted URL works too; --ver wins when both given"
51396
+ },
51397
+ {
51398
+ flags: "--force",
51399
+ description: "(download) Allow writing into a non-empty directory"
51400
+ },
51401
+ {
51402
+ flags: "-y, --yes",
51403
+ description: "(delete) Skip the confirmation prompt"
51404
+ }
51405
+ ]
51406
+ }
51407
+ ]
51408
+ };
51409
+ });
51410
+
51335
51411
  // src/domains/help/commands/index.ts
51336
51412
  var init_commands2 = __esm(() => {
51337
51413
  init_init_command_help();
@@ -51341,6 +51417,7 @@ var init_commands2 = __esm(() => {
51341
51417
  init_versions_command_help();
51342
51418
  init_config_command_help();
51343
51419
  init_auth_command_help();
51420
+ init_artifact_command_help();
51344
51421
  init_common_options();
51345
51422
  });
51346
51423
 
@@ -51359,7 +51436,8 @@ var init_help_commands = __esm(() => {
51359
51436
  versions: versionsCommandHelp,
51360
51437
  doctor: doctorCommandHelp,
51361
51438
  uninstall: uninstallCommandHelp,
51362
- auth: authCommandHelp
51439
+ auth: authCommandHelp,
51440
+ artifact: artifactCommandHelp
51363
51441
  };
51364
51442
  });
51365
51443
 
@@ -53547,6 +53625,27 @@ async function remove9(token, uuid) {
53547
53625
  throw new ArtifactApiError(res.status, `Delete failed (HTTP ${res.status}): ${msg}`);
53548
53626
  }
53549
53627
  }
53628
+ async function removeVersion(token, uuid, version3) {
53629
+ const base = getServerUrl();
53630
+ const res = await fetch(`${base}/api/artifacts/${encodeURIComponent(uuid)}/versions/${encodeURIComponent(String(version3))}`, {
53631
+ method: "DELETE",
53632
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }
53633
+ });
53634
+ if (!res.ok) {
53635
+ const msg = await parseErrorMessage(res);
53636
+ throw new ArtifactApiError(res.status, `Delete version failed (HTTP ${res.status}): ${msg}`);
53637
+ }
53638
+ try {
53639
+ return await res.json();
53640
+ } catch {
53641
+ return {
53642
+ ok: true,
53643
+ deleted_version: undefined,
53644
+ live_version: undefined,
53645
+ shared_version: undefined
53646
+ };
53647
+ }
53648
+ }
53550
53649
  async function getManifest(token, uuid, version3) {
53551
53650
  const base = getServerUrl();
53552
53651
  const qs = version3 !== undefined ? `?v=${encodeURIComponent(String(version3))}` : "";
@@ -53680,6 +53779,17 @@ function parseArtifactRef(input) {
53680
53779
  const candidate = m2?.[1] ?? "";
53681
53780
  return UUID_V4_RE.test(candidate) ? candidate.toLowerCase() : null;
53682
53781
  }
53782
+ function extractVersionFromRef(ref) {
53783
+ try {
53784
+ const url = new URL(ref.trim());
53785
+ const v2 = url.searchParams.get("v");
53786
+ if (v2 !== null) {
53787
+ const n = Number.parseInt(v2, 10);
53788
+ return Number.isFinite(n) && n > 0 ? n : undefined;
53789
+ }
53790
+ } catch {}
53791
+ return;
53792
+ }
53683
53793
  function warnIfHostMismatch(input) {
53684
53794
  let inputUrl;
53685
53795
  try {
@@ -53710,6 +53820,11 @@ async function artifactDelete(ref, opts) {
53710
53820
  const token = await getToken(opts);
53711
53821
  if (!token)
53712
53822
  return;
53823
+ const version3 = opts.version !== undefined ? opts.version : extractVersionFromRef(ref);
53824
+ if (version3 !== undefined) {
53825
+ await deleteVersion(uuid, version3, token, opts);
53826
+ return;
53827
+ }
53713
53828
  if (!opts.yes) {
53714
53829
  const confirmed = await se({
53715
53830
  message: `Delete artifact ${uuid}? This cannot be undone.`,
@@ -53730,6 +53845,36 @@ async function artifactDelete(ref, opts) {
53730
53845
  await removeEntry(uuid);
53731
53846
  console.log(`Deleted artifact ${uuid}.`);
53732
53847
  }
53848
+ async function deleteVersion(uuid, version3, token, opts) {
53849
+ if (!opts.yes) {
53850
+ const confirmed = await se({
53851
+ message: `Delete version ${version3} of artifact ${uuid}? This cannot be undone.`,
53852
+ initialValue: false
53853
+ });
53854
+ if (lD(confirmed) || !confirmed) {
53855
+ console.log("Cancelled.");
53856
+ return;
53857
+ }
53858
+ }
53859
+ try {
53860
+ const result = await removeVersion(token, uuid, version3);
53861
+ if (result.live_version !== undefined) {
53862
+ console.log(`Deleted version ${version3} of artifact ${uuid}. Live version is now ${result.live_version}.`);
53863
+ } else {
53864
+ console.log(`Deleted version ${version3} of artifact ${uuid}.`);
53865
+ }
53866
+ } catch (err) {
53867
+ if (err instanceof ArtifactApiError && isCannotDeleteLastVersion(err)) {
53868
+ console.error(`Error: cannot delete version ${version3} — it is the last remaining version. ` + `Delete the whole artifact instead: tkm artifact delete ${uuid}`);
53869
+ } else {
53870
+ printApiError(err);
53871
+ }
53872
+ process.exitCode = 1;
53873
+ }
53874
+ }
53875
+ function isCannotDeleteLastVersion(err) {
53876
+ return err.status === 409 && err.message.includes("cannot_delete_last_version");
53877
+ }
53733
53878
 
53734
53879
  // src/commands/artifact/download-command.ts
53735
53880
  import { promises as fs11 } from "node:fs";
@@ -53797,6 +53942,11 @@ var EXT_TO_MIME = {
53797
53942
  html: "text/html",
53798
53943
  htm: "text/html",
53799
53944
  md: "text/markdown",
53945
+ mmd: "text/vnd.mermaid",
53946
+ yaml: "application/yaml",
53947
+ yml: "application/yaml",
53948
+ csv: "text/csv",
53949
+ tsv: "text/tab-separated-values",
53800
53950
  txt: "text/plain",
53801
53951
  css: "text/css",
53802
53952
  js: "text/javascript",
@@ -53905,17 +54055,6 @@ function deriveSlug(title, uuid) {
53905
54055
  const slug = (title ?? "").trim().replace(/[/\\:*?"<>|.]/g, " ").split("").map((c2) => c2.charCodeAt(0) < 32 ? " " : c2).join("").replace(/\s+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "").slice(0, 80);
53906
54056
  return slug || uuid;
53907
54057
  }
53908
- function extractVersionFromRef(ref) {
53909
- try {
53910
- const url = new URL(ref.trim());
53911
- const v2 = url.searchParams.get("v");
53912
- if (v2 !== null) {
53913
- const n = Number.parseInt(v2, 10);
53914
- return Number.isFinite(n) && n > 0 ? n : undefined;
53915
- }
53916
- } catch {}
53917
- return;
53918
- }
53919
54058
  async function artifactDownload(ref, opts) {
53920
54059
  const uuid = parseArtifactRef(ref);
53921
54060
  if (!uuid) {
@@ -54005,9 +54144,13 @@ import { promises as fs13 } from "node:fs";
54005
54144
  import { basename as basename12, extname as extname5, resolve as resolve16 } from "node:path";
54006
54145
 
54007
54146
  // src/domains/artifact/folder-walk.ts
54147
+ var import_ignore3 = __toESM(require_ignore(), 1);
54008
54148
  import { promises as fs12 } from "node:fs";
54009
54149
  import { join as join72, relative as relative14 } from "node:path";
54010
54150
  var MAX_FILE_BYTES = 20 * 1024 * 1024;
54151
+ var IGNORE_FILES = [".gitignore", ".tkmignore"];
54152
+ var HARD_IGNORES = [".git", ".gitignore", ".tkmignore"];
54153
+ var HARD_IGNORE_NAMES = new Set(HARD_IGNORES);
54011
54154
 
54012
54155
  class FolderLimitError extends Error {
54013
54156
  constructor(message) {
@@ -54015,9 +54158,23 @@ class FolderLimitError extends Error {
54015
54158
  this.name = "FolderLimitError";
54016
54159
  }
54017
54160
  }
54018
- async function walkFolder(rootDir) {
54161
+ async function buildIgnoreMatcher(rootDir) {
54162
+ const ig = import_ignore3.default();
54163
+ for (const file of IGNORE_FILES) {
54164
+ try {
54165
+ ig.add(await fs12.readFile(join72(rootDir, file), "utf8"));
54166
+ } catch {}
54167
+ }
54168
+ ig.add(HARD_IGNORES);
54169
+ return ig;
54170
+ }
54171
+ async function walkFolder(rootDir, skipped) {
54172
+ const ig = await buildIgnoreMatcher(rootDir);
54019
54173
  const entries = [];
54020
- await walk(rootDir, rootDir, entries);
54174
+ const collectedSkips = [];
54175
+ await walk(rootDir, rootDir, ig, entries, collectedSkips);
54176
+ if (skipped)
54177
+ skipped.push(...collectedSkips);
54021
54178
  entries.sort((a3, b3) => a3.relPath.localeCompare(b3.relPath));
54022
54179
  const oversized = entries.find((e2) => e2.size > MAX_FILE_BYTES);
54023
54180
  if (oversized) {
@@ -54026,7 +54183,7 @@ async function walkFolder(rootDir) {
54026
54183
  }
54027
54184
  return entries;
54028
54185
  }
54029
- async function walk(rootDir, dir, out) {
54186
+ async function walk(rootDir, dir, ig, out, skipped) {
54030
54187
  let dirents;
54031
54188
  try {
54032
54189
  dirents = await fs12.readdir(dir, { withFileTypes: true });
@@ -54038,11 +54195,21 @@ async function walk(rootDir, dir, out) {
54038
54195
  continue;
54039
54196
  const name = String(dirent.name);
54040
54197
  const absPath = join72(dir, name);
54198
+ const relPath = relative14(rootDir, absPath).split("\\").join("/");
54041
54199
  if (dirent.isDirectory()) {
54042
- await walk(rootDir, absPath, out);
54200
+ if (ig.ignores(`${relPath}/`)) {
54201
+ if (!HARD_IGNORE_NAMES.has(name))
54202
+ skipped.push(`${relPath}/`);
54203
+ continue;
54204
+ }
54205
+ await walk(rootDir, absPath, ig, out, skipped);
54043
54206
  } else if (dirent.isFile()) {
54207
+ if (ig.ignores(relPath)) {
54208
+ if (!HARD_IGNORE_NAMES.has(name))
54209
+ skipped.push(relPath);
54210
+ continue;
54211
+ }
54044
54212
  const stat8 = await fs12.stat(absPath);
54045
- const relPath = relative14(rootDir, absPath).split("\\").join("/");
54046
54213
  out.push({ absPath, relPath, size: stat8.size });
54047
54214
  }
54048
54215
  }
@@ -54186,8 +54353,9 @@ async function uploadFolder(absPath, displayPath, token, opts) {
54186
54353
  const spinner = de();
54187
54354
  spinner.start(`Scanning ${displayPath}…`);
54188
54355
  let entries;
54356
+ const skippedPaths = [];
54189
54357
  try {
54190
- entries = await walkFolder(absPath);
54358
+ entries = await walkFolder(absPath, skippedPaths);
54191
54359
  } catch (err) {
54192
54360
  spinner.stop("Scan failed.", 1);
54193
54361
  if (err instanceof FolderLimitError) {
@@ -54204,6 +54372,11 @@ async function uploadFolder(absPath, displayPath, token, opts) {
54204
54372
  process.exitCode = 1;
54205
54373
  return;
54206
54374
  }
54375
+ if (skippedPaths.length > 0) {
54376
+ const preview = skippedPaths.slice(0, 5).join(", ");
54377
+ const more = skippedPaths.length > 5 ? `, +${skippedPaths.length - 5} more` : "";
54378
+ console.warn(`Skipped ${skippedPaths.length} path(s) matched by .gitignore/.tkmignore: ${preview}${more}`);
54379
+ }
54207
54380
  let files;
54208
54381
  let folderHash;
54209
54382
  try {
@@ -54306,16 +54479,16 @@ async function artifactCommand(action, target, opts) {
54306
54479
  break;
54307
54480
  case "download":
54308
54481
  if (!target)
54309
- return usageError("tkm artifact download <uuid|url> [-o <dir>] [-v <n>] [--force]");
54482
+ return usageError("tkm artifact download <uuid|url> [-o <dir>] [--ver <n>] [--force]");
54310
54483
  await artifactDownload(target, opts);
54311
54484
  break;
54312
54485
  case "delete":
54313
54486
  if (!target)
54314
- return usageError("tkm artifact delete <uuid|url>");
54487
+ return usageError("tkm artifact delete <uuid|url> [--ver <n>]");
54315
54488
  await artifactDelete(target, opts);
54316
54489
  break;
54317
54490
  case undefined:
54318
- usageError("tkm artifact <upload <file> | download <uuid|url> | delete <uuid|url>>");
54491
+ usageError("tkm artifact <upload <file> | download <uuid|url> | delete <uuid|url> [--ver <n>]>");
54319
54492
  break;
54320
54493
  default:
54321
54494
  console.error(`Unknown artifact action: ${action}. Available: upload, download, delete`);
@@ -60842,7 +61015,7 @@ function detectBroadGlob(pattern, pathHint) {
60842
61015
  }
60843
61016
 
60844
61017
  // src/domains/hooks/handlers/guard-breadth-scout/check-ignore.ts
60845
- var import_ignore3 = __toESM(require_ignore(), 1);
61018
+ var import_ignore4 = __toESM(require_ignore(), 1);
60846
61019
  import { existsSync as existsSync44, readFileSync as readFileSync13 } from "node:fs";
60847
61020
  import { dirname as dirname24, join as join93 } from "node:path";
60848
61021
  var BUILTIN_HEAVY_DIR_LINES = [
@@ -60903,7 +61076,7 @@ function cleanLines(lines) {
60903
61076
  }
60904
61077
  function buildIgnoreConfig(lines, sourceLabel) {
60905
61078
  const cleaned = cleanLines(lines);
60906
- const ig = import_ignore3.default().add(cleaned);
61079
+ const ig = import_ignore4.default().add(cleaned);
60907
61080
  const patterns = cleaned.filter((line) => !line.startsWith("!"));
60908
61081
  return { ig, patterns, sourceLabel };
60909
61082
  }
@@ -60943,7 +61116,7 @@ function findMatchingIgnoreRule(candidatePath, config) {
60943
61116
  let matched = null;
60944
61117
  for (const pattern of config.patterns) {
60945
61118
  try {
60946
- if (import_ignore3.default().add(pattern).ignores(rel))
61119
+ if (import_ignore4.default().add(pattern).ignores(rel))
60947
61120
  matched = pattern;
60948
61121
  } catch {}
60949
61122
  }
@@ -68150,7 +68323,7 @@ function registerTempDir(dir) {
68150
68323
 
68151
68324
  // src/domains/installation/download-manager.ts
68152
68325
  init_types2();
68153
- var import_ignore4 = __toESM(require_ignore(), 1);
68326
+ var import_ignore5 = __toESM(require_ignore(), 1);
68154
68327
 
68155
68328
  // src/domains/installation/download/file-downloader.ts
68156
68329
  init_logger();
@@ -72045,11 +72218,11 @@ class DownloadManager {
72045
72218
  ig;
72046
72219
  userExcludePatterns = [];
72047
72220
  constructor() {
72048
- this.ig = import_ignore4.default().add(EXCLUDE_PATTERNS);
72221
+ this.ig = import_ignore5.default().add(EXCLUDE_PATTERNS);
72049
72222
  }
72050
72223
  setExcludePatterns(patterns) {
72051
72224
  this.userExcludePatterns = patterns;
72052
- this.ig = import_ignore4.default().add([...EXCLUDE_PATTERNS, ...this.userExcludePatterns]);
72225
+ this.ig = import_ignore5.default().add([...EXCLUDE_PATTERNS, ...this.userExcludePatterns]);
72053
72226
  if (patterns.length > 0) {
72054
72227
  logger.info(`Added ${patterns.length} custom exclude pattern(s)`);
72055
72228
  patterns.forEach((p2) => logger.debug(` - ${p2}`));
@@ -77653,8 +77826,13 @@ function registerCommands(cli) {
77653
77826
  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, path12, options2) => {
77654
77827
  await apiCommand(action, service, path12, options2);
77655
77828
  });
77656
- cli.command("artifact [action] [target]", "Manage Takumi artifacts (upload <file> | download <uuid|url> | delete <uuid|url>)").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>", "Short note describing this version's change (max 100 chars)").option("-o, --output <dir>", "(download) Output directory; default: slug(title) or uuid in CWD").option("-v, --version <n>", "(download) Download a specific version number").option("--force", "(download) Allow writing into a non-empty directory").option("-y, --yes", "Skip delete confirmation prompt").action(async (action, target, opts) => {
77657
- const version3 = opts.version !== undefined ? Number.parseInt(opts.version, 10) : undefined;
77829
+ cli.command("artifact [action] [target]", "Manage Takumi artifacts (upload <file> | download <uuid|url> | delete <uuid|url>)").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>", "Short note describing this version's change (max 100 chars)").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").action(async (action, target, opts) => {
77830
+ const version3 = opts.ver !== undefined ? Number.parseInt(String(opts.ver), 10) : undefined;
77831
+ if (version3 !== undefined && (!Number.isInteger(version3) || version3 < 1)) {
77832
+ console.error(`Invalid version: ${opts.ver}. Use a positive integer, e.g. --ver 2`);
77833
+ process.exitCode = 1;
77834
+ return;
77835
+ }
77658
77836
  await artifactCommand(action, target, { ...opts, version: version3 });
77659
77837
  });
77660
77838
  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 = {}) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sunasteriskrnd/takumi",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "CLI tool for bootstrapping and managing Takumi projects",
5
5
  "type": "module",
6
6
  "repository": {