Skip to content

Commit 37e83fb

Browse files
feat(cli): auto-install TanStack Intent during scaffolding (#442)
* feat(cli): auto-install TanStack Intent during scaffolding Run `npx @tanstack/intent install` after `tanstack create` and `tanstack add`, gated by a new `--intent` / `--no-intent` flag (default on). Failures are reported as warnings so they don't block scaffolding. https://claude.ai/code/session_01KZU2LM3E8hncoXxZguUu8P * fix(cli): drop unsupported --yes flag from intent install The intent CLI already runs non-interactively, no extra flag needed. https://claude.ai/code/session_01KZU2LM3E8hncoXxZguUu8P * chore: add changeset for intent auto-install https://claude.ai/code/session_01KZU2LM3E8hncoXxZguUu8P --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 04712e8 commit 37e83fb

10 files changed

Lines changed: 97 additions & 1 deletion

File tree

.changeset/intent-auto-install.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@tanstack/cli': minor
3+
'@tanstack/create': minor
4+
---
5+
6+
feat(cli): auto-install TanStack Intent during scaffolding
7+
8+
`tanstack create` and `tanstack add` now run `npx @tanstack/intent install`
9+
after dependency installation, wiring up skill mappings for coding agents.
10+
The behavior is controlled by a new `--intent` / `--no-intent` flag (default
11+
on) and persists to `.cta.json` so subsequent `add` invocations honor the
12+
original choice. Failures are surfaced as warnings instead of aborting the
13+
scaffold.

packages/cli/src/cli.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ function getCreateTelemetryProperties(projectName: string, options: CliOptions)
183183
framework: options.framework ? sanitizeId(options.framework) : undefined,
184184
git: options.git,
185185
install: options.install !== false,
186+
intent: options.intent !== false,
186187
interactive: !!options.interactive,
187188
json: !!options.json,
188189
non_interactive: !!options.nonInteractive || !!options.yes,
@@ -221,6 +222,7 @@ function getResolvedCreateTelemetryProperties(
221222
framework: sanitizeId(finalOptions.framework.id),
222223
git: finalOptions.git,
223224
install: finalOptions.install !== false,
225+
intent: finalOptions.intent,
224226
package_manager: finalOptions.packageManager,
225227
router_only: !!cliOptions.routerOnly,
226228
toolchain: toolchain ? sanitizeId(toolchain.id) : undefined,
@@ -875,6 +877,14 @@ export function cli({
875877
.option('--json', 'output JSON for automation', false)
876878
.option('--git', 'create a git repository')
877879
.option('--no-git', 'do not create a git repository')
880+
.option(
881+
'--intent',
882+
'set up TanStack Intent skill mappings for coding agents',
883+
)
884+
.option(
885+
'--no-intent',
886+
'skip TanStack Intent setup',
887+
)
878888
.option(
879889
'--target-dir <path>',
880890
'the target directory for the application root',
@@ -1441,7 +1451,15 @@ Remove your node_modules directory and package lock file and re-install.`,
14411451
'Name of the add-ons (or add-ons separated by spaces or commas)',
14421452
)
14431453
.option('--forced', 'Force the add-on to be added', false)
1444-
.action(async (addOns: Array<string>, options: { forced: boolean }) => {
1454+
.option(
1455+
'--intent',
1456+
'set up TanStack Intent skill mappings for coding agents',
1457+
)
1458+
.option(
1459+
'--no-intent',
1460+
'skip TanStack Intent setup',
1461+
)
1462+
.action(async (addOns: Array<string>, options: { forced: boolean; intent?: boolean }) => {
14451463
try {
14461464
await runWithTelemetry(
14471465
'add',
@@ -1472,6 +1490,7 @@ Remove your node_modules directory and package lock file and re-install.`,
14721490
if (selectedAddOns.length) {
14731491
await addToApp(environment, selectedAddOns, resolve(process.cwd()), {
14741492
forced: options.forced,
1493+
intent: options.intent,
14751494
})
14761495
}
14771496
return
@@ -1484,6 +1503,7 @@ Remove your node_modules directory and package lock file and re-install.`,
14841503
})
14851504
await addToApp(environment, parsedAddOns, resolve(process.cwd()), {
14861505
forced: options.forced,
1506+
intent: options.intent,
14871507
})
14881508
},
14891509
)

packages/cli/src/command-line.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ export async function normalizeOptions(
546546
DEFAULT_PACKAGE_MANAGER,
547547
git: cliOptions.git ?? true,
548548
install: cliOptions.install,
549+
intent: cliOptions.intent ?? true,
549550
chosenAddOns,
550551
addOnOptions: {
551552
...populateAddOnOptionsDefaults(chosenAddOns),

packages/cli/src/dev-watch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ export class DevWatchManager {
272272
targetDir: this.tempDir,
273273
git: false,
274274
install: packageMetadataChanged,
275+
intent: false,
275276
}
276277

277278
// Show package installation indicator if needed

packages/cli/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface CliOptions {
2020
devWatch?: string
2121
runDev?: boolean
2222
install?: boolean
23+
intent?: boolean
2324
addOnConfig?: string
2425
force?: boolean
2526
routerOnly?: boolean

packages/create/src/add-to-app.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import { mergePackageJSON } from './package-json.js'
1919
import { runSpecialSteps } from './special-steps/index.js'
2020
import { loadStarter } from './custom-add-ons/starter.js'
21+
import { setupIntent } from './integrations/intent.js'
2122

2223
import type { Environment, Options } from './types.js'
2324
import type { PersistedOptions } from './config-file.js'
@@ -54,6 +55,7 @@ async function createOptions(
5455
]),
5556
targetDir,
5657
starter,
58+
intent: json.intent ?? false,
5759
} as Options
5860
}
5961

@@ -230,6 +232,7 @@ export async function addToApp(
230232
cwd: string,
231233
options?: {
232234
forced?: boolean
235+
intent?: boolean
233236
},
234237
) {
235238
const persistedOptions = await getCurrentConfiguration(environment, cwd)
@@ -318,6 +321,10 @@ export async function addToApp(
318321

319322
await runNewCommands(environment, persistedOptions, cwd, output)
320323

324+
const intent = options?.intent ?? persistedOptions.intent ?? true
325+
newOptions.intent = intent
326+
await setupIntent(environment, cwd, newOptions)
327+
321328
environment.startStep({
322329
id: 'write-config-file',
323330
type: 'file',

packages/create/src/create-app.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { resolvePackageJSONLatest } from './npm-resolver.js'
1313
import { createTemplateFile } from './template-file.js'
1414
import { installShadcnComponents } from './integrations/shadcn.js'
1515
import { setupGit } from './integrations/git.js'
16+
import { setupIntent } from './integrations/intent.js'
1617
import { runSpecialSteps } from './special-steps/index.js'
1718

1819
import type { Environment, FileBundleHandler, Options } from './types.js'
@@ -294,6 +295,8 @@ async function runCommandsAndInstallDependencies(
294295
}
295296

296297
await installShadcnComponents(environment, options.targetDir, options)
298+
299+
await setupIntent(environment, options.targetDir, options)
297300
}
298301

299302
async function seedEnvValues(environment: Environment, options: Options) {

packages/create/src/custom-add-ons/shared.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export async function createAppOptionsFromPersisted(
8282
starter: json.starter ? await loadStarter(json.starter) : undefined,
8383
chosenAddOns,
8484
addOnOptions: populateAddOnOptionsDefaults(chosenAddOns),
85+
intent: json.intent ?? false,
8586
}
8687
}
8788

@@ -103,6 +104,7 @@ export function createSerializedOptionsFromPersisted(
103104
framework: json.framework,
104105
starter: json.starter,
105106
addOnOptions: {},
107+
intent: json.intent ?? false,
106108
}
107109
}
108110

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { resolve } from 'node:path'
2+
3+
import { packageManagerExecute } from '../package-manager.js'
4+
5+
import type { Environment, Options } from '../types.js'
6+
7+
export async function setupIntent(
8+
environment: Environment,
9+
targetDir: string,
10+
options: Options,
11+
) {
12+
if (!options.intent) {
13+
return
14+
}
15+
16+
const s = environment.spinner()
17+
s.start('Setting up TanStack Intent skill mappings...')
18+
environment.startStep({
19+
id: 'setup-intent',
20+
type: 'command',
21+
message: 'Setting up TanStack Intent skill mappings...',
22+
})
23+
24+
try {
25+
await packageManagerExecute(
26+
environment,
27+
resolve(targetDir),
28+
options.packageManager,
29+
'@tanstack/intent',
30+
['install'],
31+
)
32+
environment.finishStep('setup-intent', 'TanStack Intent configured')
33+
s.stop('TanStack Intent configured')
34+
} catch (error) {
35+
const message =
36+
error instanceof Error ? error.message : 'Unknown error'
37+
environment.finishStep(
38+
'setup-intent',
39+
`TanStack Intent setup skipped: ${message}`,
40+
)
41+
s.stop('TanStack Intent setup skipped')
42+
environment.warn(
43+
'TanStack Intent setup failed',
44+
`Continuing without it. You can run it later with: npx @tanstack/intent install\n\n${message}`,
45+
)
46+
}
47+
}

packages/create/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ export interface Options {
214214
packageManager: PackageManager
215215
git: boolean
216216
install?: boolean
217+
intent: boolean
217218

218219
chosenAddOns: Array<AddOn>
219220
addOnOptions: Record<string, Record<string, any>>

0 commit comments

Comments
 (0)