@playcraft/cli 0.0.55 → 0.0.57
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/README.md +18 -0
- package/REMIX.md +115 -19
- package/dist/atom-tree/fusion-hints.js +8 -3
- package/dist/atom-tree/fusion-intent.js +62 -0
- package/dist/atom-tree/selection.js +3 -1
- package/dist/commands/create.js +299 -85
- package/dist/commands/image.js +79 -69
- package/dist/commands/levels.js +29 -0
- package/dist/commands/patterns.js +75 -0
- package/dist/commands/remix.js +93 -0
- package/dist/commands/skills.js +48 -17
- package/dist/index.js +9 -5
- package/dist/remix/clone-source.js +208 -0
- package/dist/remix/clone.js +256 -0
- package/dist/remix/local-git.js +56 -0
- package/dist/remix/pull.js +64 -0
- package/dist/remix/push.js +70 -0
- package/dist/scaffold/templates.js +1 -1
- package/dist/utils/agent-api-client.js +10 -3
- package/dist/utils/remix-image-attachments.js +4 -32
- package/dist/utils/sandbox-path.js +20 -0
- package/package.json +3 -3
- package/project-template/.claude/agents/creative-director.md +114 -0
- package/project-template/.claude/agents/developer.md +5 -2
- package/project-template/.claude/agents/game-designer.md +41 -20
- package/project-template/.claude/agents/refs/README.md +8 -0
- package/project-template/.claude/agents/refs/creative-director-guide.md +35 -0
- package/project-template/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
- package/project-template/.claude/agents/refs/creative-discovery-guide.md +76 -0
- package/project-template/.claude/agents/refs/creative-north-star-examples.md +82 -0
- package/project-template/.claude/agents/refs/developer-dev-handoff.md +1 -1
- package/project-template/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
- package/project-template/.claude/agents/refs/game-designer-gameplay-sufficiency.md +4 -4
- package/project-template/.claude/agents/reviewer.md +11 -0
- package/project-template/.claude/hooks/README.md +12 -12
- package/project-template/.claude/hooks/block-developer-preflight.mjs +49 -13
- package/project-template/.claude/hooks/block-ring1-tasklist.mjs +1 -1
- package/project-template/.claude/hooks/user-prompt.mjs +3 -13
- package/project-template/.claude/hooks/validate-workflow-stop.mjs +209 -12
- package/project-template/.claude/settings.json +2 -2
- package/project-template/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
- package/project-template/.claude/skills/session-analyzer/scripts/resolve-session.mjs +0 -1
- package/project-template/.claude/snapshot.mjs +0 -1
- package/project-template/.codebuddy/settings.json +1 -1
- package/project-template/.cursor/rules/playcraft-orchestrator.mdc +1 -1
- package/project-template/CLAUDE.md +9 -9
- package/project-template/docs/project-state.md +1 -1
- package/project-template/docs/team/agent-conduct.md +18 -18
- package/project-template/docs/team/collaboration.md +19 -7
- package/project-template/docs/team/workflow-changelog.md +15 -0
- package/project-template/fusion-patterns/catalog.json +77 -0
- package/project-template/package.json +2 -0
- package/project-template/skills/_shared/scripts/create-playable.mjs +31 -11
- package/project-template/skills/_shared/scripts/dev-start.mjs +3 -3
- package/project-template/skills/_shared/scripts/disclosure-config.mjs +65 -39
- package/project-template/skills/_shared/scripts/harness-smoke.mjs +3 -1
- package/project-template/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
- package/project-template/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
- package/project-template/skills/_shared/scripts/slice-set.mjs +29 -2
- package/project-template/skills/_shared/scripts/state-get.mjs +124 -7
- package/project-template/skills/_shared/scripts/state-store.mjs +25 -2
- package/project-template/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
- package/project-template/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
- package/project-template/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
- package/project-template/skills/playable-record/scripts/record-playable.mjs +2 -1
- package/project-template/skills/playable-record/scripts/verify-contract.mjs +4 -4
- package/project-template/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
- package/project-template/skills/playable-record/scripts/verify-fusion.mjs +37 -2
- package/project-template/skills/playable-record/scripts/verify-slice.mjs +2 -2
- package/project-template/templates/design-brief.template.md +34 -0
- package/project-template/templates/design-methodology.md +1 -2
- package/project-template/templates/design.template.md +65 -0
- package/project-template/templates/developer-log.template.md +12 -1
- package/project-template/templates/five-axis-framework.md +12 -0
- package/project-template/templates/game-designer-creative-process.template.md +99 -0
- package/project-template/templates/level-sketch.template.json +56 -0
- package/project-template/templates/north-star.template.md +43 -0
- package/project-template/templates/project-state.template.md +1 -1
- package/project-template/vite.config.ts +4 -5
- package/project-templates/phaser/.claude/agents/creative-director.md +114 -0
- package/project-templates/phaser/.claude/agents/game-designer.md +41 -20
- package/project-templates/phaser/.claude/agents/refs/creative-director-guide.md +35 -0
- package/project-templates/phaser/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
- package/project-templates/phaser/.claude/agents/refs/creative-discovery-guide.md +76 -0
- package/project-templates/phaser/.claude/agents/refs/creative-north-star-examples.md +82 -0
- package/project-templates/phaser/.claude/agents/refs/developer-dev-handoff.md +1 -1
- package/project-templates/phaser/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
- package/project-templates/phaser/.claude/agents/reviewer.md +11 -0
- package/project-templates/phaser/.claude/hooks/block-ring1-tasklist.mjs +2 -2
- package/project-templates/phaser/.claude/hooks/user-prompt.mjs +17 -18
- package/project-templates/phaser/.claude/hooks/validate-workflow-stop.mjs +209 -12
- package/project-templates/phaser/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
- package/project-templates/phaser/.cursor/rules/playcraft-orchestrator.mdc +1 -1
- package/project-templates/phaser/CLAUDE.md +88 -54
- package/project-templates/phaser/docs/DESIGN.md +25 -0
- package/project-templates/phaser/docs/project-state.md +1 -1
- package/project-templates/phaser/docs/team/collaboration.md +19 -7
- package/project-templates/phaser/docs/team/workflow-changelog.md +14 -0
- package/project-templates/phaser/fusion-patterns/catalog.json +77 -0
- package/project-templates/phaser/package.json +3 -1
- package/project-templates/phaser/skills/_shared/scripts/dev-start.mjs +3 -3
- package/project-templates/phaser/skills/_shared/scripts/disclosure-config.mjs +65 -39
- package/project-templates/phaser/skills/_shared/scripts/harness-smoke.mjs +3 -1
- package/project-templates/phaser/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
- package/project-templates/phaser/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
- package/project-templates/phaser/skills/_shared/scripts/state-get.mjs +124 -7
- package/project-templates/phaser/skills/_shared/scripts/state-store.mjs +25 -2
- package/project-templates/phaser/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
- package/project-templates/phaser/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
- package/project-templates/phaser/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
- package/project-templates/phaser/skills/playable-record/scripts/record-playable.mjs +2 -1
- package/project-templates/phaser/skills/playable-record/scripts/verify-contract.mjs +4 -4
- package/project-templates/phaser/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
- package/project-templates/phaser/skills/playable-record/scripts/verify-fusion.mjs +37 -2
- package/project-templates/phaser/skills/playable-record/scripts/verify-slice.mjs +2 -2
- package/project-templates/phaser/templates/design-brief.template.md +42 -11
- package/project-templates/phaser/templates/design-methodology.md +1 -2
- package/project-templates/phaser/templates/design.template.md +77 -0
- package/project-templates/phaser/templates/five-axis-framework.md +50 -1
- package/project-templates/phaser/templates/game-designer-creative-process.template.md +99 -0
- package/project-templates/phaser/templates/level-sketch.template.json +56 -0
- package/project-templates/phaser/templates/north-star.template.md +43 -0
- package/project-templates/phaser/templates/project-state.template.md +1 -1
- package/project-templates/phaser/vite.config.ts +4 -5
- package/project-templates/threejs/.claude/agents/creative-director.md +114 -0
- package/project-templates/threejs/.claude/agents/game-designer.md +41 -20
- package/project-templates/threejs/.claude/agents/refs/creative-director-guide.md +35 -0
- package/project-templates/threejs/.claude/agents/refs/creative-director-kickoff-guide.md +52 -0
- package/project-templates/threejs/.claude/agents/refs/creative-discovery-guide.md +76 -0
- package/project-templates/threejs/.claude/agents/refs/creative-north-star-examples.md +82 -0
- package/project-templates/threejs/.claude/agents/refs/developer-dev-handoff.md +1 -1
- package/project-templates/threejs/.claude/agents/refs/game-designer-fusion-depth-playbook.md +28 -0
- package/project-templates/threejs/.claude/agents/reviewer.md +11 -0
- package/project-templates/threejs/.claude/hooks/block-ring1-tasklist.mjs +2 -2
- package/project-templates/threejs/.claude/hooks/user-prompt.mjs +17 -18
- package/project-templates/threejs/.claude/hooks/validate-workflow-stop.mjs +209 -12
- package/project-templates/threejs/.claude/skills/playcraft-playable-optimization/SKILL.md +20 -20
- package/project-templates/threejs/.cursor/rules/playcraft-orchestrator.mdc +1 -1
- package/project-templates/threejs/CLAUDE.md +88 -54
- package/project-templates/threejs/docs/DESIGN.md +25 -0
- package/project-templates/threejs/docs/project-state.md +1 -1
- package/project-templates/threejs/docs/team/collaboration.md +19 -7
- package/project-templates/threejs/docs/team/workflow-changelog.md +14 -0
- package/project-templates/threejs/fusion-patterns/catalog.json +77 -0
- package/project-templates/threejs/package.json +3 -1
- package/project-templates/threejs/skills/_shared/scripts/dev-start.mjs +3 -3
- package/project-templates/threejs/skills/_shared/scripts/disclosure-config.mjs +65 -39
- package/project-templates/threejs/skills/_shared/scripts/harness-smoke.mjs +3 -1
- package/project-templates/threejs/skills/_shared/scripts/lib/fusion-intent.mjs +88 -0
- package/project-templates/threejs/skills/_shared/scripts/lib/skill-fidelity.mjs +185 -0
- package/project-templates/threejs/skills/_shared/scripts/state-get.mjs +124 -7
- package/project-templates/threejs/skills/_shared/scripts/state-store.mjs +25 -2
- package/project-templates/threejs/skills/_shared/scripts/validate-level-sketch.mjs +444 -0
- package/project-templates/threejs/skills/_shared/scripts/verify-design-consistency.mjs +728 -0
- package/project-templates/threejs/skills/playable-record/scripts/lib/dev-server.mjs +46 -11
- package/project-templates/threejs/skills/playable-record/scripts/record-playable.mjs +2 -1
- package/project-templates/threejs/skills/playable-record/scripts/verify-contract.mjs +4 -4
- package/project-templates/threejs/skills/playable-record/scripts/verify-firstwin.mjs +3 -3
- package/project-templates/threejs/skills/playable-record/scripts/verify-fusion.mjs +37 -2
- package/project-templates/threejs/skills/playable-record/scripts/verify-slice.mjs +2 -2
- package/project-templates/threejs/templates/design-brief.template.md +42 -11
- package/project-templates/threejs/templates/design-methodology.md +1 -2
- package/project-templates/threejs/templates/design.template.md +77 -0
- package/project-templates/threejs/templates/five-axis-framework.md +50 -1
- package/project-templates/threejs/templates/game-designer-creative-process.template.md +99 -0
- package/project-templates/threejs/templates/level-sketch.template.json +56 -0
- package/project-templates/threejs/templates/north-star.template.md +43 -0
- package/project-templates/threejs/templates/project-state.template.md +1 -1
- package/project-templates/threejs/vite.config.ts +4 -5
- package/project-template/docs/DESIGN.md +0 -65
package/dist/commands/remix.js
CHANGED
|
@@ -5,6 +5,9 @@ import { readFileSync } from 'node:fs';
|
|
|
5
5
|
import { PlayCraftApiError, PlayCraftHttpClient, isPlayCraftApiError } from '../utils/playcraft-http-client.js';
|
|
6
6
|
import { IMAGE_OPTION_HELP, buildReferenceImagesPrompt, readImageAttachments, uploadImageAttachments, } from '../utils/remix-image-attachments.js';
|
|
7
7
|
import { initTemplateCommand } from '../remix/init-template.js';
|
|
8
|
+
import { remixClone, gitCredentialCommand, isRemoteCloneSource } from '../remix/clone.js';
|
|
9
|
+
import { remixPush } from '../remix/push.js';
|
|
10
|
+
import { remixPull } from '../remix/pull.js';
|
|
8
11
|
// Register autocomplete prompt
|
|
9
12
|
inquirer.registerPrompt('autocomplete', autocomplete);
|
|
10
13
|
const JSON_OUTPUT_HELP = '输出一条 JSON (JavaScript Object Notation, JavaScript 对象表示法) 结果';
|
|
@@ -1266,4 +1269,94 @@ export function registerRemixCommands(program) {
|
|
|
1266
1269
|
.option('--token <token>', PAT_HELP)
|
|
1267
1270
|
.option('--json', JSON_OUTPUT_HELP)
|
|
1268
1271
|
.action((opts) => remixRun({ ...opts, images: opts.image, timeout: Math.max(1, Number(opts.timeout) || 1800) }));
|
|
1272
|
+
remix.command('clone')
|
|
1273
|
+
.description('经 PlayCraft Git 代理克隆 Remix 源码到本地(支持粘贴详情页 / Editor / 工蜂 URL)')
|
|
1274
|
+
.argument('[source]', 'Remix 详情页、Editor 地址,或工蜂/Git 分支页 URL')
|
|
1275
|
+
.argument('[dir]', '本地目标目录(默认 <projectId>-<branch>)')
|
|
1276
|
+
.option('--project-id <id>', '项目 ID(未传 URL 时必填)')
|
|
1277
|
+
.option('--branch <name>', 'Remix 分支名(未传 URL 或工蜂地址无 /tree 时必填)')
|
|
1278
|
+
.option('--install', 'clone 后执行 npm install')
|
|
1279
|
+
.option('--dev', 'install 后执行 npm run dev')
|
|
1280
|
+
.option('--url <url>', SERVICE_ADDRESS_HELP)
|
|
1281
|
+
.option('--token <token>', PAT_HELP)
|
|
1282
|
+
.option('--json', JSON_OUTPUT_HELP)
|
|
1283
|
+
.action(async (sourceArg, dirArg, opts) => {
|
|
1284
|
+
try {
|
|
1285
|
+
let source = sourceArg ? String(sourceArg) : undefined;
|
|
1286
|
+
let dir = dirArg ? String(dirArg) : undefined;
|
|
1287
|
+
// `clone ./outdir --project-id … --branch …` → first arg is dir
|
|
1288
|
+
if (source && !isRemoteCloneSource(source)) {
|
|
1289
|
+
dir = source;
|
|
1290
|
+
source = undefined;
|
|
1291
|
+
}
|
|
1292
|
+
await remixClone({
|
|
1293
|
+
source,
|
|
1294
|
+
dir,
|
|
1295
|
+
projectId: opts.projectId ? String(opts.projectId) : undefined,
|
|
1296
|
+
branch: opts.branch ? String(opts.branch) : undefined,
|
|
1297
|
+
install: Boolean(opts.install || opts.dev),
|
|
1298
|
+
dev: Boolean(opts.dev),
|
|
1299
|
+
url: opts.url,
|
|
1300
|
+
token: opts.token,
|
|
1301
|
+
json: Boolean(opts.json),
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
catch (error) {
|
|
1305
|
+
handleCommandError('remix.clone', opts, undefined, error);
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
remix
|
|
1309
|
+
.command('push')
|
|
1310
|
+
.description('经 PlayCraft Git 代理推送当前 Remix(注入已登录 PAT,避免 Windows Git Credential Manager 弹窗)')
|
|
1311
|
+
.argument('[git-args...]', '透传给 git push 的参数(可选)')
|
|
1312
|
+
.option('--dir <path>', '仓库目录(默认当前目录)')
|
|
1313
|
+
.option('--url <url>', SERVICE_ADDRESS_HELP)
|
|
1314
|
+
.option('--token <token>', PAT_HELP)
|
|
1315
|
+
.option('--json', JSON_OUTPUT_HELP)
|
|
1316
|
+
.action(async (gitArgs, opts) => {
|
|
1317
|
+
try {
|
|
1318
|
+
await remixPush({
|
|
1319
|
+
dir: opts.dir ? String(opts.dir) : undefined,
|
|
1320
|
+
url: opts.url,
|
|
1321
|
+
token: opts.token,
|
|
1322
|
+
json: Boolean(opts.json),
|
|
1323
|
+
gitArgs: Array.isArray(gitArgs) ? gitArgs.map(String) : [],
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
catch (error) {
|
|
1327
|
+
handleCommandError('remix.push', opts, undefined, error);
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
remix
|
|
1331
|
+
.command('pull')
|
|
1332
|
+
.description('经 PlayCraft Git 代理拉取当前 Remix(注入已登录 PAT,避免 Windows Git Credential Manager 弹窗)')
|
|
1333
|
+
.argument('[git-args...]', '透传给 git pull 的参数(可选)')
|
|
1334
|
+
.option('--dir <path>', '仓库目录(默认当前目录)')
|
|
1335
|
+
.option('--url <url>', SERVICE_ADDRESS_HELP)
|
|
1336
|
+
.option('--token <token>', PAT_HELP)
|
|
1337
|
+
.option('--json', JSON_OUTPUT_HELP)
|
|
1338
|
+
.action(async (gitArgs, opts) => {
|
|
1339
|
+
try {
|
|
1340
|
+
await remixPull({
|
|
1341
|
+
dir: opts.dir ? String(opts.dir) : undefined,
|
|
1342
|
+
url: opts.url,
|
|
1343
|
+
token: opts.token,
|
|
1344
|
+
json: Boolean(opts.json),
|
|
1345
|
+
gitArgs: Array.isArray(gitArgs) ? gitArgs.map(String) : [],
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
catch (error) {
|
|
1349
|
+
handleCommandError('remix.pull', opts, undefined, error);
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
/** Register `playcraft git-credential`(clone 写入的 helper;拉/推请用 remix pull / push). */
|
|
1354
|
+
export function registerGitCredentialCommand(program) {
|
|
1355
|
+
program
|
|
1356
|
+
.command('git-credential')
|
|
1357
|
+
.description('Git credential helper(一般勿手动调用;请用 playcraft remix pull / push)')
|
|
1358
|
+
.argument('[action]', 'get | store | erase', 'get')
|
|
1359
|
+
.action((action) => {
|
|
1360
|
+
gitCredentialCommand(action);
|
|
1361
|
+
});
|
|
1269
1362
|
}
|
package/dist/commands/skills.js
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import * as fs from 'node:fs';
|
|
22
22
|
import * as path from 'node:path';
|
|
23
23
|
import { getFusionHintsForGameplayAtoms, } from '../atom-tree/fusion-hints.js';
|
|
24
|
+
import { detectFusionIntent, fusionCompositeScoreBoost, fusionSingleMechanicPenalty, isFusionCompositeManifest, } from '../atom-tree/fusion-intent.js';
|
|
24
25
|
import { collectPinnedDependencies, loadSkillManifestIndex, parseAtomTreeJson, parseSelectedAtomRefsFromAtomTreeProject, isUninitializedAtomTreeSkeleton, resolveSkillsDirs, validateAtomTreeSelectionProject, } from '../atom-tree/selection.js';
|
|
25
26
|
import { buildAtomTreeSkeleton } from '../atom-tree/build-skeleton.js';
|
|
26
27
|
import { validateAsrCoverageProject } from '../asr/validate-asr-coverage.js';
|
|
@@ -83,6 +84,10 @@ const INTENT_TAG_ALIASES = {
|
|
|
83
84
|
mahjong: ['tile', 'tiles', '牌面'],
|
|
84
85
|
麻将: ['tile', 'tiles', 'mahjong', '牌面'],
|
|
85
86
|
solitaire: ['solitaire', 'klondike', 'card'],
|
|
87
|
+
消除: ['elimination', 'match-3', 'match3', 'match'],
|
|
88
|
+
箭头: ['arrow', 'path', 'slide-out'],
|
|
89
|
+
fusion: ['融合', 'hybrid', 'combine'],
|
|
90
|
+
融合: ['fusion', 'hybrid', 'combine', 'match-3', 'arrow'],
|
|
86
91
|
};
|
|
87
92
|
const ANCHOR_MEDIA_ROLE_LABELS = {
|
|
88
93
|
tileTextureSet: { label: '牌面/瓦片贴图组', layer: '视觉资产层' },
|
|
@@ -250,7 +255,7 @@ function getCategoryLayer(category, atomId) {
|
|
|
250
255
|
return '玩法层';
|
|
251
256
|
}
|
|
252
257
|
// ─── Manifest 索引器 ─────────────────────────────────────────────────────────
|
|
253
|
-
function loadSkillIndex(skillsDirs) {
|
|
258
|
+
export function loadSkillIndex(skillsDirs) {
|
|
254
259
|
const entries = [];
|
|
255
260
|
const seen = new Set();
|
|
256
261
|
for (const dir of skillsDirs) {
|
|
@@ -281,7 +286,10 @@ function loadSkillIndex(skillsDirs) {
|
|
|
281
286
|
// ─── 推荐算法 ────────────────────────────────────────────────────────────────
|
|
282
287
|
function getGameplayType(manifest) {
|
|
283
288
|
const gen = manifest['generation'];
|
|
284
|
-
|
|
289
|
+
if (gen?.gameplayType)
|
|
290
|
+
return gen.gameplayType;
|
|
291
|
+
const pgs = manifest['pgs'];
|
|
292
|
+
return pgs?.gameplayType;
|
|
285
293
|
}
|
|
286
294
|
function isEngineManifest(manifest) {
|
|
287
295
|
return manifest.category === 'engine' || manifest.bundleType === 'aicomponent';
|
|
@@ -315,7 +323,7 @@ function pinAtomIdToBareName(pinAtomId) {
|
|
|
315
323
|
/**
|
|
316
324
|
* 仅按 intent 打分(discovery 相位)。不假设目标引擎。
|
|
317
325
|
*/
|
|
318
|
-
export function scoreSkillIntentOnly(entry, intentTags) {
|
|
326
|
+
export function scoreSkillIntentOnly(entry, intentTags, rawIntent = '') {
|
|
319
327
|
if (intentTags.length === 0)
|
|
320
328
|
return 0;
|
|
321
329
|
const { manifest } = entry;
|
|
@@ -333,6 +341,8 @@ export function scoreSkillIntentOnly(entry, intentTags) {
|
|
|
333
341
|
}
|
|
334
342
|
if (manifest.category === 'engine')
|
|
335
343
|
score += 1;
|
|
344
|
+
score += fusionCompositeScoreBoost(manifest, intentTags, rawIntent);
|
|
345
|
+
score += fusionSingleMechanicPenalty(manifest, intentTags, rawIntent);
|
|
336
346
|
return score;
|
|
337
347
|
}
|
|
338
348
|
/**
|
|
@@ -392,7 +402,7 @@ export function inferSuggestedEngines(scored, intentTags, indexMap) {
|
|
|
392
402
|
* 计算一个 skill 对给定 engine + intent 的匹配分数。
|
|
393
403
|
* 得分越高,越应该被推荐。
|
|
394
404
|
*/
|
|
395
|
-
export function scoreSkill(entry, engine, intentTags) {
|
|
405
|
+
export function scoreSkill(entry, engine, intentTags, rawIntent = '') {
|
|
396
406
|
const { manifest } = entry;
|
|
397
407
|
let score = 0;
|
|
398
408
|
// 1. 引擎精确匹配:renderBackend 或 engineVariants 中有指定引擎
|
|
@@ -418,6 +428,8 @@ export function scoreSkill(entry, engine, intentTags) {
|
|
|
418
428
|
});
|
|
419
429
|
if (importsEngine)
|
|
420
430
|
score += 5;
|
|
431
|
+
score += fusionCompositeScoreBoost(manifest, intentTags, rawIntent);
|
|
432
|
+
score += fusionSingleMechanicPenalty(manifest, intentTags, rawIntent);
|
|
421
433
|
return score;
|
|
422
434
|
}
|
|
423
435
|
/**
|
|
@@ -452,9 +464,12 @@ function collectDependencies(atomId, index, engine, visited = new Set(), depth =
|
|
|
452
464
|
}
|
|
453
465
|
else {
|
|
454
466
|
// 无 variant,使用顶层 imports
|
|
467
|
+
// 只对 requires 做递归传递依赖展开;compatibleWith 只记录直接引用
|
|
455
468
|
for (const imp of entry.manifest.imports ?? []) {
|
|
456
|
-
if (imp.pinAtomId
|
|
457
|
-
|
|
469
|
+
if (!imp.pinAtomId || visited.has(imp.pinAtomId))
|
|
470
|
+
continue;
|
|
471
|
+
deps.push(imp.pinAtomId);
|
|
472
|
+
if (imp.edgeKind === 'requires') {
|
|
458
473
|
deps.push(...collectDependencies(imp.pinAtomId, index, engine, visited, depth + 1));
|
|
459
474
|
}
|
|
460
475
|
}
|
|
@@ -563,9 +578,10 @@ function buildIntentOnlyMatchResult(entries, intentTags, suggestedEngines, index
|
|
|
563
578
|
/**
|
|
564
579
|
* 仅 --intent:先推断引擎,再对首选引擎做完整 match(一轮输出全部内容)。
|
|
565
580
|
*/
|
|
566
|
-
export function buildAutoMatchResult(entries, intentTags) {
|
|
581
|
+
export function buildAutoMatchResult(entries, intentTags, rawIntent = '') {
|
|
582
|
+
const intentBlob = rawIntent || intentTags.join(' ');
|
|
567
583
|
const scored = entries
|
|
568
|
-
.map((entry) => ({ entry, score: scoreSkillIntentOnly(entry, intentTags) }))
|
|
584
|
+
.map((entry) => ({ entry, score: scoreSkillIntentOnly(entry, intentTags, intentBlob) }))
|
|
569
585
|
.filter(({ score }) => score >= SCORE_THRESHOLD_DISCOVERY);
|
|
570
586
|
const indexMap = new Map(entries.map((e) => [e.atomId, e]));
|
|
571
587
|
const suggestedEngines = inferSuggestedEngines(scored, intentTags, indexMap);
|
|
@@ -573,7 +589,7 @@ export function buildAutoMatchResult(entries, intentTags) {
|
|
|
573
589
|
if (!engine) {
|
|
574
590
|
return buildIntentOnlyMatchResult(entries, intentTags, suggestedEngines, indexMap);
|
|
575
591
|
}
|
|
576
|
-
const full = buildMatchResult(entries, engine, intentTags);
|
|
592
|
+
const full = buildMatchResult(entries, engine, intentTags, intentBlob);
|
|
577
593
|
const gap = suggestedEngines.length > 1 ? suggestedEngines[0].score - suggestedEngines[1].score : 99;
|
|
578
594
|
return {
|
|
579
595
|
...full,
|
|
@@ -582,8 +598,20 @@ export function buildAutoMatchResult(entries, intentTags) {
|
|
|
582
598
|
engineAmbiguous: suggestedEngines.length > 1 && gap < 5,
|
|
583
599
|
};
|
|
584
600
|
}
|
|
585
|
-
function selectMatchSeedAtomIds(scored, engine, intentTags) {
|
|
601
|
+
function selectMatchSeedAtomIds(scored, engine, intentTags, rawIntent = '') {
|
|
586
602
|
const seeds = new Set();
|
|
603
|
+
const fusionIntent = detectFusionIntent(intentTags, rawIntent);
|
|
604
|
+
if (fusionIntent) {
|
|
605
|
+
const composites = scored.filter(({ entry, score }) => isGameplayManifest(entry.manifest) &&
|
|
606
|
+
isFusionCompositeManifest(entry.manifest) &&
|
|
607
|
+
score >= SCORE_THRESHOLD_FULL &&
|
|
608
|
+
passesGameplayIntentFilter(entry, intentTags));
|
|
609
|
+
if (composites.length > 0) {
|
|
610
|
+
for (const { entry } of composites)
|
|
611
|
+
seeds.add(entry.atomId);
|
|
612
|
+
return seeds;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
587
615
|
for (const { entry, score } of scored) {
|
|
588
616
|
const { manifest } = entry;
|
|
589
617
|
if (isEngineManifest(manifest) && pinAtomIdToEngine(entry.atomId, manifest) === engine) {
|
|
@@ -674,19 +702,20 @@ function buildItemReason(entry, engine, intentTags, score, via, dependencyReason
|
|
|
674
702
|
return `标签:${matchedTags.join(', ')}`;
|
|
675
703
|
return score !== null ? `score=${score}` : '推荐';
|
|
676
704
|
}
|
|
677
|
-
export function buildMatchResult(entries, engine, intentTags) {
|
|
705
|
+
export function buildMatchResult(entries, engine, intentTags, rawIntent = '') {
|
|
706
|
+
const intentBlob = rawIntent || intentTags.join(' ');
|
|
678
707
|
const indexMap = new Map(entries.map((e) => [e.atomId, e]));
|
|
679
708
|
const scoreById = new Map();
|
|
680
709
|
const expandedIntent = expandIntentTags(intentTags);
|
|
681
710
|
const scored = entries
|
|
682
711
|
.map((entry) => {
|
|
683
|
-
const score = scoreSkill(entry, engine, expandedIntent);
|
|
712
|
+
const score = scoreSkill(entry, engine, expandedIntent, intentBlob);
|
|
684
713
|
scoreById.set(entry.atomId, score);
|
|
685
714
|
return { entry, score };
|
|
686
715
|
})
|
|
687
716
|
.filter(({ score }) => score >= SCORE_THRESHOLD_FULL)
|
|
688
717
|
.sort((a, b) => b.score - a.score);
|
|
689
|
-
const seedIds = selectMatchSeedAtomIds(scored, engine, expandedIntent);
|
|
718
|
+
const seedIds = selectMatchSeedAtomIds(scored, engine, expandedIntent, intentBlob);
|
|
690
719
|
const requiredIds = expandSeedClosure(seedIds, indexMap, engine);
|
|
691
720
|
const seedIdSet = seedIds;
|
|
692
721
|
const dependencyReasonMap = buildDependencyReasonMap(requiredIds, indexMap, engine);
|
|
@@ -1475,9 +1504,10 @@ export function registerSkillsCommands(program) {
|
|
|
1475
1504
|
' 示例:playcraft skills match --engine phaser --intent "match3,grid" --json');
|
|
1476
1505
|
process.exit(1);
|
|
1477
1506
|
}
|
|
1507
|
+
const intentBlob = opts.intent;
|
|
1478
1508
|
const result = opts.engine
|
|
1479
|
-
? buildMatchResult(entries, opts.engine, intentTags)
|
|
1480
|
-
: buildAutoMatchResult(entries, intentTags);
|
|
1509
|
+
? buildMatchResult(entries, opts.engine, intentTags, intentBlob)
|
|
1510
|
+
: buildAutoMatchResult(entries, intentTags, intentBlob);
|
|
1481
1511
|
if (opts.json) {
|
|
1482
1512
|
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
1483
1513
|
return;
|
|
@@ -1909,9 +1939,10 @@ export function registerSkillsCommands(program) {
|
|
|
1909
1939
|
process.exit(1);
|
|
1910
1940
|
}
|
|
1911
1941
|
}
|
|
1942
|
+
const intentBlob = opts.intent;
|
|
1912
1943
|
const result = opts.engine
|
|
1913
|
-
? buildMatchResult(entries, opts.engine, intentTags)
|
|
1914
|
-
: buildAutoMatchResult(entries, intentTags);
|
|
1944
|
+
? buildMatchResult(entries, opts.engine, intentTags, intentBlob)
|
|
1945
|
+
: buildAutoMatchResult(entries, intentTags, intentBlob);
|
|
1915
1946
|
if (!result.engine) {
|
|
1916
1947
|
console.log('无法从 intent 推断运行引擎。请指定:--engine phaser 或 --engine threejs');
|
|
1917
1948
|
console.log(`示例:playcraft skills init-atom-tree --intent "${intentTags.join(',')}" --engine phaser --force`);
|
package/dist/index.js
CHANGED
|
@@ -21,8 +21,10 @@ import { registerImageCommands } from './commands/image.js';
|
|
|
21
21
|
import { registerAudioCommands } from './commands/audio.js';
|
|
22
22
|
import { registerPrefabCommands } from './commands/prefab.js';
|
|
23
23
|
import { registerRecommendCommands } from './commands/recommend.js';
|
|
24
|
-
import { registerRemixCommands } from './commands/remix.js';
|
|
24
|
+
import { registerRemixCommands, registerGitCredentialCommand } from './commands/remix.js';
|
|
25
25
|
import { registerSkillsCommands } from './commands/skills.js';
|
|
26
|
+
import { registerLevelsCommands } from './commands/levels.js';
|
|
27
|
+
import { registerPatternsCommands } from './commands/patterns.js';
|
|
26
28
|
import { registerPradCommands } from './commands/prad.js';
|
|
27
29
|
import { register3DCommands } from './commands/3d.js';
|
|
28
30
|
import { CLI_ROOT_DESCRIPTION, getCliTopicsHelpText, registerRootProgramHelp, } from './cli-root-help.js';
|
|
@@ -30,10 +32,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
30
32
|
const __dirname = dirname(__filename);
|
|
31
33
|
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
32
34
|
const program = new Command();
|
|
33
|
-
program
|
|
34
|
-
.name('playcraft')
|
|
35
|
-
.description(CLI_ROOT_DESCRIPTION)
|
|
36
|
-
.version(packageJson.version);
|
|
35
|
+
program.name('playcraft').description(CLI_ROOT_DESCRIPTION).version(packageJson.version);
|
|
37
36
|
registerRootProgramHelp(program);
|
|
38
37
|
// 版本检查(非阻塞,后台执行)
|
|
39
38
|
// 只在非 upgrade 命令时检查,避免重复提示
|
|
@@ -45,6 +44,8 @@ program
|
|
|
45
44
|
.command('create [project-name]')
|
|
46
45
|
.description('从模板创建新的可玩广告项目')
|
|
47
46
|
.option('-d, --dir <path>', '在指定目录下创建项目', process.cwd())
|
|
47
|
+
.option('--port <number>', '本地 Agent 端口(非交互模式默认 2468)', '2468')
|
|
48
|
+
.option('--non-interactive', '跳过交互式问答,使用命令行参数或默认值')
|
|
48
49
|
.option('-t, --template <id>', '模板: agent | phaser-playable | three-playable', 'agent')
|
|
49
50
|
.option('--here', '在当前目录(或 --dir 指定目录)直接初始化,不创建子目录')
|
|
50
51
|
.option('-f, --force', '非交互模式:目标目录非空时仍覆盖同名文件')
|
|
@@ -359,8 +360,11 @@ registerPrefabCommands(program);
|
|
|
359
360
|
registerRecommendCommands(program);
|
|
360
361
|
// Remix workflow (playcraft remix <command>)
|
|
361
362
|
registerRemixCommands(program);
|
|
363
|
+
registerGitCredentialCommand(program);
|
|
362
364
|
// 开发时 Skill 发现与脚手架 (playcraft skills <command>)
|
|
363
365
|
registerSkillsCommands(program);
|
|
366
|
+
registerLevelsCommands(program);
|
|
367
|
+
registerPatternsCommands(program);
|
|
364
368
|
registerPradCommands(program);
|
|
365
369
|
// 3D model utilities (playcraft 3d <command>)
|
|
366
370
|
register3DCommands(program);
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse sources accepted by `playcraft remix clone`:
|
|
3
|
+
* - PlayCraft remix detail: /projects/:id/remixes/:branch
|
|
4
|
+
* - PlayCraft remix editor: /editor/remix/:id?branch=
|
|
5
|
+
* - Gongfeng / GitLab / GitHub branch tree or bare repo URL
|
|
6
|
+
*/
|
|
7
|
+
/** 工蜂内网 / 外网域名互为别名(详情页可能展示任一)。 */
|
|
8
|
+
export const GONGFENG_HOST_ALIASES = ['git.woa.com', 'git.tencent.com'];
|
|
9
|
+
const GIT_HOST_HINTS = [
|
|
10
|
+
...GONGFENG_HOST_ALIASES,
|
|
11
|
+
'github.com',
|
|
12
|
+
'gitlab.com',
|
|
13
|
+
'gitee.com',
|
|
14
|
+
];
|
|
15
|
+
function looksLikeHttpUrl(value) {
|
|
16
|
+
return /^https?:\/\//i.test(value.trim());
|
|
17
|
+
}
|
|
18
|
+
/** Canonical host for Gongfeng alias matching (woa ↔ tencent). */
|
|
19
|
+
export function canonicalizeGitHost(hostname) {
|
|
20
|
+
const host = hostname.trim().toLowerCase();
|
|
21
|
+
if (GONGFENG_HOST_ALIASES.includes(host)) {
|
|
22
|
+
return 'git.woa.com';
|
|
23
|
+
}
|
|
24
|
+
return host;
|
|
25
|
+
}
|
|
26
|
+
function isGongfengHost(hostname) {
|
|
27
|
+
const host = hostname.trim().toLowerCase();
|
|
28
|
+
return GONGFENG_HOST_ALIASES.includes(host);
|
|
29
|
+
}
|
|
30
|
+
function decodePathSegment(segment) {
|
|
31
|
+
try {
|
|
32
|
+
return decodeURIComponent(segment);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return segment;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Strip credentials and trailing .git / slash for comparison. Gongfeng hosts are aliased. */
|
|
39
|
+
export function normalizeGitRepositoryUrl(raw) {
|
|
40
|
+
let url = raw.trim();
|
|
41
|
+
// git@host:group/repo.git → https://host/group/repo
|
|
42
|
+
const scp = /^git@([^:]+):(.+)$/.exec(url);
|
|
43
|
+
if (scp) {
|
|
44
|
+
url = `https://${scp[1]}/${scp[2]}`;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const u = new URL(url);
|
|
48
|
+
u.hash = '';
|
|
49
|
+
u.search = '';
|
|
50
|
+
u.username = '';
|
|
51
|
+
u.password = '';
|
|
52
|
+
u.hostname = canonicalizeGitHost(u.hostname);
|
|
53
|
+
let pathname = u.pathname.replace(/\/+$/, '');
|
|
54
|
+
pathname = pathname.replace(/\.git$/i, '');
|
|
55
|
+
return `${u.protocol}//${u.host}${pathname}`.toLowerCase();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return url
|
|
59
|
+
.replace(/\.git$/i, '')
|
|
60
|
+
.replace(/\/+$/, '')
|
|
61
|
+
.toLowerCase();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* From a host tree/blob URL, extract repo base + optional branch.
|
|
66
|
+
* Supports:
|
|
67
|
+
* .../tree/<branch>
|
|
68
|
+
* .../-/tree/<branch> (GitLab)
|
|
69
|
+
* .../blob/<branch>/...
|
|
70
|
+
*/
|
|
71
|
+
export function parseGitHostTreeUrl(raw) {
|
|
72
|
+
let input = raw.trim();
|
|
73
|
+
if (!looksLikeHttpUrl(input) && !input.startsWith('git@'))
|
|
74
|
+
return null;
|
|
75
|
+
// scp-style
|
|
76
|
+
if (input.startsWith('git@')) {
|
|
77
|
+
const scp = /^git@([^:]+):(.+)$/.exec(input);
|
|
78
|
+
if (!scp)
|
|
79
|
+
return null;
|
|
80
|
+
input = `https://${scp[1]}/${scp[2]}`;
|
|
81
|
+
}
|
|
82
|
+
let u;
|
|
83
|
+
try {
|
|
84
|
+
u = new URL(input);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const host = u.hostname.toLowerCase();
|
|
90
|
+
const isKnownHost = isGongfengHost(host) ||
|
|
91
|
+
GIT_HOST_HINTS.some((h) => host === h || host.endsWith(`.${h}`)) ||
|
|
92
|
+
host.includes('git.');
|
|
93
|
+
// PlayCraft portal hosts are handled elsewhere
|
|
94
|
+
if (!isKnownHost && !u.pathname.includes('/tree/') && !u.pathname.includes('/-/tree/')) {
|
|
95
|
+
// Still allow any https URL that looks like *.git
|
|
96
|
+
if (!u.pathname.endsWith('.git') && !input.endsWith('.git')) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const path = u.pathname;
|
|
101
|
+
const gitlabTree = /\/-\/tree\/([^/]+)(?:\/|$)/.exec(path);
|
|
102
|
+
const plainTree = /\/tree\/([^/]+)(?:\/|$)/.exec(path);
|
|
103
|
+
const blob = /\/blob\/([^/]+)(?:\/|$)/.exec(path);
|
|
104
|
+
const match = gitlabTree || plainTree || blob;
|
|
105
|
+
let repositoryPath = path;
|
|
106
|
+
let branch;
|
|
107
|
+
if (match) {
|
|
108
|
+
branch = decodePathSegment(match[1]);
|
|
109
|
+
repositoryPath = path.slice(0, match.index);
|
|
110
|
+
}
|
|
111
|
+
repositoryPath = repositoryPath.replace(/\.git$/i, '').replace(/\/+$/, '');
|
|
112
|
+
if (!repositoryPath || repositoryPath === '/')
|
|
113
|
+
return null;
|
|
114
|
+
const repositoryUrl = `${u.protocol}//${u.host}${repositoryPath}`;
|
|
115
|
+
return { repositoryUrl, branch };
|
|
116
|
+
}
|
|
117
|
+
export function parsePlaycraftRemixUrl(raw) {
|
|
118
|
+
if (!looksLikeHttpUrl(raw))
|
|
119
|
+
return null;
|
|
120
|
+
let u;
|
|
121
|
+
try {
|
|
122
|
+
u = new URL(raw.trim());
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
// /projects/:projectId/remixes/:branch
|
|
128
|
+
const detail = /^\/projects\/([^/]+)\/remixes\/([^/]+)\/?$/.exec(u.pathname);
|
|
129
|
+
if (detail) {
|
|
130
|
+
const projectId = decodePathSegment(detail[1]);
|
|
131
|
+
const branch = decodePathSegment(detail[2]);
|
|
132
|
+
if (!projectId || !branch)
|
|
133
|
+
return null;
|
|
134
|
+
return {
|
|
135
|
+
kind: 'playcraft-detail',
|
|
136
|
+
projectId,
|
|
137
|
+
branch,
|
|
138
|
+
origin: u.origin,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// /editor/remix/:projectId?branch=
|
|
142
|
+
const editor = /^\/editor\/remix\/([^/]+)\/?$/.exec(u.pathname);
|
|
143
|
+
if (editor) {
|
|
144
|
+
const projectId = decodePathSegment(editor[1]);
|
|
145
|
+
const branchRaw = u.searchParams.get('branch') || u.searchParams.get('branchId');
|
|
146
|
+
if (!projectId || !branchRaw?.trim())
|
|
147
|
+
return null;
|
|
148
|
+
return {
|
|
149
|
+
kind: 'playcraft-editor',
|
|
150
|
+
projectId,
|
|
151
|
+
branch: decodePathSegment(branchRaw.trim()),
|
|
152
|
+
origin: u.origin,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Resolve clone source from a pasted URL or explicit flags.
|
|
159
|
+
* Throws with a user-facing Chinese message on conflict / missing fields.
|
|
160
|
+
*/
|
|
161
|
+
export function resolveRemixCloneTarget(input) {
|
|
162
|
+
const source = input.source?.trim();
|
|
163
|
+
const flagProjectId = input.projectId?.trim();
|
|
164
|
+
const flagBranch = input.branch?.trim();
|
|
165
|
+
const sourceLooksRemote = Boolean(source) && (looksLikeHttpUrl(source) || source.startsWith('git@'));
|
|
166
|
+
if (source && sourceLooksRemote) {
|
|
167
|
+
const playcraft = parsePlaycraftRemixUrl(source);
|
|
168
|
+
if (playcraft && (playcraft.kind === 'playcraft-detail' || playcraft.kind === 'playcraft-editor')) {
|
|
169
|
+
if (flagProjectId && flagProjectId !== playcraft.projectId) {
|
|
170
|
+
throw new Error('URL 中的项目 ID 与 --project-id 不一致。');
|
|
171
|
+
}
|
|
172
|
+
if (flagBranch && flagBranch !== playcraft.branch) {
|
|
173
|
+
throw new Error('URL 中的分支与 --branch 不一致。');
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
kind: playcraft.kind,
|
|
177
|
+
projectId: playcraft.projectId,
|
|
178
|
+
branch: playcraft.branch,
|
|
179
|
+
origin: playcraft.origin,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const gitHost = parseGitHostTreeUrl(source);
|
|
183
|
+
if (gitHost) {
|
|
184
|
+
const branch = flagBranch || gitHost.branch;
|
|
185
|
+
if (!branch) {
|
|
186
|
+
throw new Error('工蜂/Git 地址未包含分支,请在 URL 使用 /tree/<branch>,或额外传入 --branch。');
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
kind: 'git-host',
|
|
190
|
+
repositoryUrl: gitHost.repositoryUrl,
|
|
191
|
+
branch,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
throw new Error('无法识别该 URL。请粘贴 Remix 详情页、Remix Editor 地址,或工蜂分支页(…/tree/<branch>)。');
|
|
195
|
+
}
|
|
196
|
+
if (flagProjectId && flagBranch) {
|
|
197
|
+
return { kind: 'flags', projectId: flagProjectId, branch: flagBranch };
|
|
198
|
+
}
|
|
199
|
+
throw new Error('请传入 Remix 详情/编辑器 URL,或工蜂 Git 地址;也可使用 --project-id 与 --branch。');
|
|
200
|
+
}
|
|
201
|
+
export function isRemoteCloneSource(value) {
|
|
202
|
+
const v = value?.trim();
|
|
203
|
+
return Boolean(v && (looksLikeHttpUrl(v) || v.startsWith('git@')));
|
|
204
|
+
}
|
|
205
|
+
/** @deprecated use isRemoteCloneSource */
|
|
206
|
+
export function isHttpCloneSource(value) {
|
|
207
|
+
return isRemoteCloneSource(value);
|
|
208
|
+
}
|