@rex0220/kintone-sql-tools 1.4.1 → 1.9.0

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-cli/ksql.js CHANGED
@@ -4367,9 +4367,9 @@ async function executeBatch(sql, client, options = {}) {
4367
4367
  }
4368
4368
  for (const s of analysis.statements) {
4369
4369
  if (!s.isDml || s.tempTablesReferenced.length === 0) continue;
4370
- if (s.statementType === "INSERT_SELECT" && s.tempOnlySource) continue;
4370
+ if (s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT") continue;
4371
4371
  throw new BatchAnalysisError(
4372
- s.statementType === "INSERT_SELECT" ? `ArgumentError: INSERT_SELECT in a batch must select from temp tables only. (statement ${s.index})` : `ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
4372
+ `ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
4373
4373
  s.index
4374
4374
  );
4375
4375
  }
@@ -4405,8 +4405,18 @@ async function executeBatch(sql, client, options = {}) {
4405
4405
  }
4406
4406
  try {
4407
4407
  const remaining = deadline !== null ? deadline - Date.now() : null;
4408
+ const userConfirm = options.confirm;
4409
+ const stmtOptions = userConfirm ? {
4410
+ ...options,
4411
+ confirm: (count, operation) => userConfirm(count, operation, {
4412
+ statementIndex: i,
4413
+ statementCount: statements.length,
4414
+ statementType: info.statementType,
4415
+ targetAppId: info.targetAppId
4416
+ })
4417
+ } : options;
4408
4418
  const outcome = await runWithDeadline(
4409
- executeBatchStatement(statements[i], info, countedClient, options, cacheContext, tempTables),
4419
+ executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables),
4410
4420
  remaining
4411
4421
  );
4412
4422
  results.push({ ...base, status: "success", ...outcome });
@@ -4457,6 +4467,9 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
4457
4467
  if (stmt.type === "INSERT_SELECT") {
4458
4468
  return { result: await executeInsertSelect(stmt, client, options, cacheContext, tempTables) };
4459
4469
  }
4470
+ if (stmt.type === "UPSERT_SELECT") {
4471
+ return { result: await executeUpsertSelect(stmt, client, options, cacheContext, tempTables) };
4472
+ }
4460
4473
  throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
4461
4474
  }
4462
4475
  return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
@@ -5778,8 +5791,8 @@ function evalOrderKeyForRow(key, row) {
5778
5791
  return evalStringFunc(key.expr, row);
5779
5792
  }
5780
5793
  }
5781
- async function executeUpsertSelect(stmt, client, options, cacheContext) {
5782
- const selectResult = await executeSelect(stmt.select, client, options, cacheContext);
5794
+ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache) {
5795
+ const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
5783
5796
  const { rows, columns } = selectResult;
5784
5797
  if (columns.length !== stmt.fields.length) {
5785
5798
  throw new Error(
@@ -5988,12 +6001,18 @@ function buildPlanForBatchQuery(query, info) {
5988
6001
  lines.push(
5989
6002
  `INSERT INTO APP${query.appId} ... SELECT\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30BD\u30FC\u30B9\u3002\u5B9F\u884C\u6642\u306B\u4EF6\u6570\u78BA\u5B9A \u2192 dmlMaxRows \u9069\u7528\uFF09`
5990
6003
  );
6004
+ } else if (query.type === "UPSERT_SELECT") {
6005
+ lines.push(
6006
+ `UPSERT INTO APP${query.appId} ... SELECT\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30BD\u30FC\u30B9\u3002\u7167\u5408\u5F8C\u306B insert + update \u5408\u8A08\u78BA\u5B9A \u2192 dmlMaxRows \u9069\u7528\uFF09`
6007
+ );
5991
6008
  }
5992
6009
  lines.push(" mode: FULL_SCAN\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u53C2\u7167\uFF09");
5993
6010
  lines.push(
5994
6011
  ` temp: ${info.tempTablesReferenced.join(", ")}\uFF08\u30A4\u30F3\u30E1\u30E2\u30EA\u8D70\u67FB\u3002\u5B9F\u4F53\u5316\u524D\u306E\u305F\u3081\u884C\u6570\u4E0D\u660E\uFF09`
5995
6012
  );
5996
- const apps = info.appIds.filter((a) => query.type !== "INSERT_SELECT" || a !== query.appId);
6013
+ const apps = info.appIds.filter(
6014
+ (a) => query.type !== "INSERT_SELECT" && query.type !== "UPSERT_SELECT" || a !== query.appId
6015
+ );
5997
6016
  if (apps.length > 0) {
5998
6017
  lines.push(` app: ${apps.map((a) => `APP${a}`).join(", ")}`);
5999
6018
  }
@@ -35271,9 +35271,9 @@ async function executeBatch(sql, client, options = {}) {
35271
35271
  }
35272
35272
  for (const s of analysis.statements) {
35273
35273
  if (!s.isDml || s.tempTablesReferenced.length === 0) continue;
35274
- if (s.statementType === "INSERT_SELECT" && s.tempOnlySource) continue;
35274
+ if (s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT") continue;
35275
35275
  throw new BatchAnalysisError(
35276
- s.statementType === "INSERT_SELECT" ? `ArgumentError: INSERT_SELECT in a batch must select from temp tables only. (statement ${s.index})` : `ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
35276
+ `ArgumentError: temp table references in ${s.statementType} are not supported yet.`,
35277
35277
  s.index
35278
35278
  );
35279
35279
  }
@@ -35309,8 +35309,18 @@ async function executeBatch(sql, client, options = {}) {
35309
35309
  }
35310
35310
  try {
35311
35311
  const remaining = deadline !== null ? deadline - Date.now() : null;
35312
+ const userConfirm = options.confirm;
35313
+ const stmtOptions = userConfirm ? {
35314
+ ...options,
35315
+ confirm: (count, operation) => userConfirm(count, operation, {
35316
+ statementIndex: i,
35317
+ statementCount: statements.length,
35318
+ statementType: info.statementType,
35319
+ targetAppId: info.targetAppId
35320
+ })
35321
+ } : options;
35312
35322
  const outcome = await runWithDeadline(
35313
- executeBatchStatement(statements[i], info, countedClient, options, cacheContext, tempTables),
35323
+ executeBatchStatement(statements[i], info, countedClient, stmtOptions, cacheContext, tempTables),
35314
35324
  remaining
35315
35325
  );
35316
35326
  results.push({ ...base, status: "success", ...outcome });
@@ -35361,6 +35371,9 @@ async function executeBatchStatement(stmt, info, client, options, cacheContext,
35361
35371
  if (stmt.type === "INSERT_SELECT") {
35362
35372
  return { result: await executeInsertSelect(stmt, client, options, cacheContext, tempTables) };
35363
35373
  }
35374
+ if (stmt.type === "UPSERT_SELECT") {
35375
+ return { result: await executeUpsertSelect(stmt, client, options, cacheContext, tempTables) };
35376
+ }
35364
35377
  throw new Error(`ArgumentError: temp table references in ${stmt.type} are not supported yet.`);
35365
35378
  }
35366
35379
  return { result: await executeParsedStatement(stmt, client, options, cacheContext) };
@@ -36682,8 +36695,8 @@ function evalOrderKeyForRow(key, row) {
36682
36695
  return evalStringFunc(key.expr, row);
36683
36696
  }
36684
36697
  }
36685
- async function executeUpsertSelect(stmt, client, options, cacheContext) {
36686
- const selectResult = await executeSelect(stmt.select, client, options, cacheContext);
36698
+ async function executeUpsertSelect(stmt, client, options, cacheContext, cteCache) {
36699
+ const selectResult = cteCache !== void 0 && cteCache.size > 0 ? await executeQueryWithCte(stmt.select, client, options, cteCache, cacheContext) : await executeSelect(stmt.select, client, options, cacheContext);
36687
36700
  const { rows, columns } = selectResult;
36688
36701
  if (columns.length !== stmt.fields.length) {
36689
36702
  throw new Error(
@@ -36892,12 +36905,18 @@ function buildPlanForBatchQuery(query, info) {
36892
36905
  lines.push(
36893
36906
  `INSERT INTO APP${query.appId} ... SELECT\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30BD\u30FC\u30B9\u3002\u5B9F\u884C\u6642\u306B\u4EF6\u6570\u78BA\u5B9A \u2192 dmlMaxRows \u9069\u7528\uFF09`
36894
36907
  );
36908
+ } else if (query.type === "UPSERT_SELECT") {
36909
+ lines.push(
36910
+ `UPSERT INTO APP${query.appId} ... SELECT\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u30BD\u30FC\u30B9\u3002\u7167\u5408\u5F8C\u306B insert + update \u5408\u8A08\u78BA\u5B9A \u2192 dmlMaxRows \u9069\u7528\uFF09`
36911
+ );
36895
36912
  }
36896
36913
  lines.push(" mode: FULL_SCAN\uFF08\u4E00\u6642\u30C6\u30FC\u30D6\u30EB\u53C2\u7167\uFF09");
36897
36914
  lines.push(
36898
36915
  ` temp: ${info.tempTablesReferenced.join(", ")}\uFF08\u30A4\u30F3\u30E1\u30E2\u30EA\u8D70\u67FB\u3002\u5B9F\u4F53\u5316\u524D\u306E\u305F\u3081\u884C\u6570\u4E0D\u660E\uFF09`
36899
36916
  );
36900
- const apps = info.appIds.filter((a) => query.type !== "INSERT_SELECT" || a !== query.appId);
36917
+ const apps = info.appIds.filter(
36918
+ (a) => query.type !== "INSERT_SELECT" && query.type !== "UPSERT_SELECT" || a !== query.appId
36919
+ );
36901
36920
  if (apps.length > 0) {
36902
36921
  lines.push(` app: ${apps.map((a) => `APP${a}`).join(", ")}`);
36903
36922
  }
@@ -38271,6 +38290,24 @@ function requireDmlApproval(input, toolName, suffix = "") {
38271
38290
  }
38272
38291
  return Number(input.dmlMaxRows);
38273
38292
  }
38293
+ function containsSelectBasedDml(statements) {
38294
+ return statements.some(
38295
+ (s) => s.statementType === "INSERT_SELECT" || s.statementType === "UPSERT_SELECT"
38296
+ );
38297
+ }
38298
+ function resolveMutateRuntimeMaxRecords(statements, dmlMaxRows) {
38299
+ return containsSelectBasedDml(statements) ? void 0 : dmlMaxRows + 1;
38300
+ }
38301
+ var READ_LIMIT_MESSAGE_FRAGMENT = "\u53D6\u5F97\u4EF6\u6570\u304C\u4E0A\u9650";
38302
+ var SELECT_BASED_DML_READ_LIMIT_HINT = "SELECT-based DML \u306E\u30BD\u30FC\u30B9\u8AAD\u307F\u53D6\u308A\u4E0A\u9650\u306F dmlMaxRows \u3067\u306F\u306A\u304F maxRecords \u89E3\u6C7A\u5024(KSQL_MAX_RECORDS / profile \u306E query.maxRecords\u3001\u65E2\u5B9A 500)\u3067\u5236\u5FA1\u3055\u308C\u307E\u3059\u3002dmlMaxRows \u306F\u5F71\u97FF\u884C\u6570\u30AC\u30FC\u30C9\u3067\u3059\u3002";
38303
+ function appendSelectBasedDmlReadLimitHint(err) {
38304
+ if (err instanceof Error && err.message.includes(READ_LIMIT_MESSAGE_FRAGMENT)) {
38305
+ const hinted = new Error(`${err.message} ${SELECT_BASED_DML_READ_LIMIT_HINT}`);
38306
+ hinted.name = err.name;
38307
+ return hinted;
38308
+ }
38309
+ return err;
38310
+ }
38274
38311
  function toToolResult(payload, isError = false) {
38275
38312
  return {
38276
38313
  content: [
@@ -38436,14 +38473,6 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
38436
38473
  for (const s of validation.statements) {
38437
38474
  if (!s.isDml) continue;
38438
38475
  const at = ` (statement ${s.index})`;
38439
- if (s.statementType === "INSERT_SELECT" && !s.tempOnlySource) {
38440
- throw new Error(
38441
- `ArgumentError: INSERT_SELECT in a batch must select from temp tables only.${at}`
38442
- );
38443
- }
38444
- if (s.statementType === "UPSERT_SELECT") {
38445
- throw new Error(`ArgumentError: ${s.statementType} is not supported by ksql_mutate yet.${at}`);
38446
- }
38447
38476
  if ((s.statementType === "UPDATE" || s.statementType === "DELETE") && !s.hasWhere) {
38448
38477
  throw new Error(`ArgumentError: ${s.statementType} without WHERE is blocked by ksql_mutate.${at}`);
38449
38478
  }
@@ -38460,10 +38489,13 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
38460
38489
  `ArgumentError: batch INSERT rows (${staticInsertTotal}) exceed dmlTotalMaxRows (${dmlTotalMaxRows}).`
38461
38490
  );
38462
38491
  }
38492
+ const selectBasedDml = containsSelectBasedDml(validation.statements);
38463
38493
  const runtime = await createRuntime(serverOptions, {
38464
38494
  sql: input.sql,
38465
38495
  profile: input.profile,
38466
- maxRecords: dmlMaxRows + 1,
38496
+ // SELECT-based DML を含む場合は dmlMaxRows で読み取りを絞らない(案A。
38497
+ // resolveMutateRuntimeMaxRecords の doc コメント参照)
38498
+ maxRecords: resolveMutateRuntimeMaxRecords(validation.statements, dmlMaxRows),
38467
38499
  fetchParallel: input.fetchParallel,
38468
38500
  onLimit: DEFAULT_ON_LIMIT,
38469
38501
  timeout: input.timeout
@@ -38489,7 +38521,17 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
38489
38521
  return true;
38490
38522
  }
38491
38523
  });
38492
- return toBatchQueryPayload(batchResult);
38524
+ const payload = toBatchQueryPayload(batchResult);
38525
+ if (selectBasedDml) {
38526
+ for (const entry of payload.statements) {
38527
+ if (entry.type !== "INSERT_SELECT" && entry.type !== "UPSERT_SELECT") continue;
38528
+ const error51 = entry.error;
38529
+ if (typeof error51?.message !== "string") continue;
38530
+ if (!error51.message.includes(READ_LIMIT_MESSAGE_FRAGMENT)) continue;
38531
+ entry.error = { ...error51, message: `${error51.message} ${SELECT_BASED_DML_READ_LIMIT_HINT}` };
38532
+ }
38533
+ }
38534
+ return payload;
38493
38535
  }
38494
38536
  async function mutate(input) {
38495
38537
  const dmlMaxRows = requireDmlApproval(input, "ksql_mutate");
@@ -38500,40 +38542,40 @@ function createKsqlMcpTools(serverOptions, deps = {}) {
38500
38542
  if (!validation.isDml) {
38501
38543
  throw new Error(`ArgumentError: ${validation.statementType} is not allowed by ksql_mutate. Use ksql_query.`);
38502
38544
  }
38503
- if (validation.statementType === "INSERT_SELECT") {
38504
- throw new Error(
38505
- "ArgumentError: INSERT_SELECT is not supported by ksql_mutate as a single statement. Wrap it in a batch: CREATE TEMP TABLE #t AS SELECT ...; INSERT INTO APPx (...) SELECT ... FROM #t;"
38506
- );
38507
- }
38508
- if (validation.statementType === "UPSERT_SELECT") {
38509
- throw new Error(`ArgumentError: ${validation.statementType} is not supported by ksql_mutate yet.`);
38510
- }
38511
38545
  if ((validation.statementType === "UPDATE" || validation.statementType === "DELETE") && !validation.hasWhere) {
38512
38546
  throw new Error(`ArgumentError: ${validation.statementType} without WHERE is blocked by ksql_mutate.`);
38513
38547
  }
38514
38548
  if (validation.insertValuesCount !== null && validation.insertValuesCount > dmlMaxRows) {
38515
38549
  throw new Error(`ArgumentError: INSERT rows (${validation.insertValuesCount}) exceed dmlMaxRows (${dmlMaxRows}).`);
38516
38550
  }
38551
+ const selectBasedDml = containsSelectBasedDml(validation.statements);
38517
38552
  const runtime = await createRuntime(serverOptions, {
38518
38553
  sql: input.sql,
38519
38554
  profile: input.profile,
38520
- maxRecords: dmlMaxRows + 1,
38555
+ // SELECT-based DML は dmlMaxRows で読み取りを絞らない(案A。
38556
+ // resolveMutateRuntimeMaxRecords の doc コメント参照)
38557
+ maxRecords: resolveMutateRuntimeMaxRecords(validation.statements, dmlMaxRows),
38521
38558
  fetchParallel: input.fetchParallel,
38522
38559
  onLimit: DEFAULT_ON_LIMIT,
38523
38560
  timeout: input.timeout
38524
38561
  });
38525
- const result = await executeSql(runtime.sql, runtime.client, {
38526
- maxRecords: runtime.maxRecords,
38527
- fetchParallel: runtime.fetchParallel,
38528
- onLimitReached: runtime.onLimit,
38529
- cacheContext: runtime.cacheContext,
38530
- confirm: async (count, operation) => {
38531
- if (count > dmlMaxRows) {
38532
- throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed dmlMaxRows (${dmlMaxRows}).`);
38562
+ let result;
38563
+ try {
38564
+ result = await executeSql(runtime.sql, runtime.client, {
38565
+ maxRecords: runtime.maxRecords,
38566
+ fetchParallel: runtime.fetchParallel,
38567
+ onLimitReached: runtime.onLimit,
38568
+ cacheContext: runtime.cacheContext,
38569
+ confirm: async (count, operation) => {
38570
+ if (count > dmlMaxRows) {
38571
+ throw new Error(`ArgumentError: ${operation} affected rows (${count}) exceed dmlMaxRows (${dmlMaxRows}).`);
38572
+ }
38573
+ return true;
38533
38574
  }
38534
- return true;
38535
- }
38536
- });
38575
+ });
38576
+ } catch (err) {
38577
+ throw selectBasedDml ? appendSelectBasedDmlReadLimitHint(err) : err;
38578
+ }
38537
38579
  if (result.type === "SELECT") {
38538
38580
  throw new Error(`ArgumentError: ksql_mutate returned unexpected result type ${result.type}.`);
38539
38581
  }
@@ -38720,7 +38762,7 @@ var mutateInputSchema = external_exports.object({
38720
38762
  profile,
38721
38763
  allowDml: external_exports.literal(true).describe("Must be true to acknowledge that this call writes to kintone."),
38722
38764
  confirmText: external_exports.literal("yes").describe('Must be the literal string "yes" to confirm execution.'),
38723
- dmlMaxRows: external_exports.number().int().positive().describe("Per-statement cap on affected rows. The call fails before writing if any statement would exceed it."),
38765
+ dmlMaxRows: external_exports.number().int().positive().describe("Per-statement cap on affected rows. The call fails before writing if any statement would exceed it; for UPSERT it counts inserts + updates. It does NOT limit source reads of INSERT/UPSERT ... SELECT: those follow the runtime maxRecords resolution (KSQL_MAX_RECORDS / profile query.maxRecords, default 500; temp tables hold at most 10000 rows), so choose it by intended write count only."),
38724
38766
  fetchParallel,
38725
38767
  timeout,
38726
38768
  dmlTotalMaxRows: external_exports.number().int().positive().describe("Batch (multi-statement) only: cap on total affected rows across the whole batch (default: per-statement dmlMaxRows only). DML batches always run fail-fast.").optional()
@@ -38763,7 +38805,7 @@ var runSavedQueryInputSchema = external_exports.object({
38763
38805
  timeout,
38764
38806
  allowDml: external_exports.literal(true).describe("Required for DML saved queries: must be true to acknowledge writes.").optional(),
38765
38807
  confirmText: external_exports.literal("yes").describe('Required for DML saved queries: must be the literal string "yes".').optional(),
38766
- dmlMaxRows: external_exports.number().int().positive().describe("Required for DML saved queries: per-statement cap on affected rows.").optional()
38808
+ dmlMaxRows: external_exports.number().int().positive().describe("Required for DML saved queries: per-statement cap on affected rows; for UPSERT it counts inserts + updates. It does NOT limit source reads of INSERT/UPSERT ... SELECT: those follow the runtime maxRecords resolution (KSQL_MAX_RECORDS / profile query.maxRecords, default 500; temp tables hold at most 10000 rows). Note: this tool's maxRecords / onLimit inputs apply to read-only saved queries only.").optional()
38767
38809
  });
38768
38810
  var validateInputShape = validateInputSchema.shape;
38769
38811
  var explainInputShape = explainInputSchema.shape;
@@ -38812,7 +38854,7 @@ Options:
38812
38854
  -h, --help Show help
38813
38855
  `);
38814
38856
  }
38815
- var SERVER_VERSION = true ? "1.4.1" : "0.0.0-dev";
38857
+ var SERVER_VERSION = true ? "1.9.0" : "0.0.0-dev";
38816
38858
  function createServer(args) {
38817
38859
  const server = new McpServer({
38818
38860
  name: "ksql-mcp",
@@ -38839,7 +38881,7 @@ function createServer(args) {
38839
38881
  }, tools.queryTool);
38840
38882
  server.registerTool("ksql_mutate", {
38841
38883
  title: "Run mutating kSQL",
38842
- description: "Execute DML kSQL with explicit allowDml, confirmText, and dmlMaxRows safety controls. Supports multi-statement DML batches with temp tables. INSERT INTO app ... SELECT is allowed in a batch when it selects only from temp tables (CREATE TEMP TABLE #t AS SELECT ...; INSERT INTO APPx (...) SELECT ... FROM #t;). Standalone INSERT_SELECT and UPSERT_SELECT are rejected.",
38884
+ description: "Execute DML kSQL with explicit allowDml, confirmText, and dmlMaxRows safety controls. Supports multi-statement DML batches with temp tables. INSERT/UPSERT INTO app ... SELECT supports app sources, temp tables, or joins of both. For UPSERT, dmlMaxRows counts inserts + updates. dmlMaxRows caps affected rows only, not source reads: the source SELECT reads up to the runtime maxRecords (KSQL_MAX_RECORDS / profile query.maxRecords, default 500); temp tables hold at most 10000 rows.",
38843
38885
  inputSchema: mutateInputShape
38844
38886
  }, tools.mutateTool);
38845
38887
  server.registerTool("ksql_describe_app", {
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rex0220/kintone-sql-tools",
3
- "version": "1.4.1",
3
+ "version": "1.9.0",
4
4
  "description": "kintone SQL plugin and CLI tools (ksql)",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -34,7 +34,7 @@
34
34
  "prepack": "npm run build:cli && npm run build:mcp && npm run build:mcpb",
35
35
  "start": "run-p develop upload",
36
36
  "develop": "node build.mjs --watch",
37
- "upload": "rex0220-plugin-uploader -f dist/ksql-plugin-v1.3.0.zip --watch"
37
+ "upload": "node scripts/upload-plugin.cjs"
38
38
  },
39
39
  "files": [
40
40
  "dist-cli/",