@cognite/dune 0.3.7 → 0.4.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.
@@ -6,6 +6,9 @@ to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>app.json'
6
6
  "description": "<%= description %>",
7
7
  "externalId": "<%= name %>",
8
8
  "versionTag": "0.0.1",
9
+ <% if (infra === 'appsApi') { -%>
10
+ "infra": "appsApi",
11
+ <% } -%>
9
12
  "deployments": [
10
13
  {
11
14
  "org": "<%= org %>",
@@ -61,7 +61,21 @@ const CHECKLIST_STEPS = [
61
61
  ] as const;
62
62
 
63
63
  function App() {
64
- const { sdk, isLoading } = useDune();
64
+ const { sdk, isLoading, error } = useDune();
65
+
66
+ if (error) {
67
+ return (
68
+ <main className="min-h-screen bg-muted/50 text-foreground">
69
+ <section className="mx-auto flex min-h-screen w-full max-w-lg flex-col justify-center p-4 sm:p-8">
70
+ <div className="mx-auto w-full max-w-sm">
71
+ <Alert variant="destructive" aria-label="Authentication error" aria-live="assertive">
72
+ <AlertDescription>{error}</AlertDescription>
73
+ </Alert>
74
+ </div>
75
+ </section>
76
+ </main>
77
+ );
78
+ }
65
79
 
66
80
  if (isLoading) {
67
81
  return (
@@ -1,7 +1,11 @@
1
1
  ---
2
2
  to: '<%= useCurrentDir ? "" : ((directoryName || name) + "/") %>src/main.tsx'
3
3
  ---
4
+ <% if (infra === 'appsApi') { -%>
5
+ import { AppSdkAuthProvider } from '@cognite/dune';
6
+ <% } else { -%>
4
7
  import { DuneAuthProvider } from '@cognite/dune';
8
+ <% } -%>
5
9
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
6
10
  import React from 'react';
7
11
  import ReactDOM from 'react-dom/client';
@@ -22,9 +26,15 @@ const queryClient = new QueryClient({
22
26
  ReactDOM.createRoot(document.getElementById('root')!).render(
23
27
  <React.StrictMode>
24
28
  <QueryClientProvider client={queryClient}>
29
+ <% if (infra === 'appsApi') { -%>
30
+ <AppSdkAuthProvider>
31
+ <App />
32
+ </AppSdkAuthProvider>
33
+ <% } else { -%>
25
34
  <DuneAuthProvider>
26
35
  <App />
27
36
  </DuneAuthProvider>
37
+ <% } -%>
28
38
  </QueryClientProvider>
29
39
  </React.StrictMode>
30
40
  );
package/bin/cli.js CHANGED
@@ -31,7 +31,7 @@ const defaultTemplates = resolve(dirname(fileURLToPath(import.meta.url)), '..',
31
31
  * @param {Function} onAppName - Callback to track the app name
32
32
  * @returns {() => object} Function that returns a prompter object with prompt method
33
33
  */
34
- export function createAppPrompter(isCurrentDir, dirName, enquirer, onAppName) {
34
+ export function createAppPrompter(isCurrentDir, dirName, enquirer, onAppName, infra) {
35
35
  // IMPORTANT: All code paths below must set useCurrentDir (true or false) because
36
36
  // templates rely on it being defined. It's never undefined.
37
37
  return () => ({
@@ -52,8 +52,7 @@ export function createAppPrompter(isCurrentDir, dirName, enquirer, onAppName) {
52
52
  if (result.name && onAppName) {
53
53
  onAppName(result.name);
54
54
  }
55
- // Add useCurrentDir variable for templates
56
- return { ...result, useCurrentDir: true, directoryName: undefined };
55
+ return { ...result, useCurrentDir: true, directoryName: undefined, infra };
57
56
  }
58
57
  // If directory argument is provided, pre-fill the name prompt with directory name
59
58
  if (dirName) {
@@ -71,15 +70,13 @@ export function createAppPrompter(isCurrentDir, dirName, enquirer, onAppName) {
71
70
  if (result.name && onAppName) {
72
71
  onAppName(result.name);
73
72
  }
74
- // Pass directoryName for folder structure, name for template content
75
- return { ...result, directoryName: dirName, useCurrentDir: false };
73
+ return { ...result, directoryName: dirName, useCurrentDir: false, infra };
76
74
  }
77
75
  const result = await enquirer.default.prompt(prompts);
78
76
  if (result.name && onAppName) {
79
77
  onAppName(result.name);
80
78
  }
81
- // No directory provided, so directoryName is undefined (templates will use name)
82
- return { ...result, useCurrentDir: false, directoryName: undefined };
79
+ return { ...result, useCurrentDir: false, directoryName: undefined, infra };
83
80
  },
84
81
  });
85
82
  }
@@ -93,12 +90,15 @@ async function main() {
93
90
  // TODO(DUNE-362): simplify directory resolution logic
94
91
  // Parse directory argument
95
92
  // Support: npx @cognite/dune create . or npx @cognite/dune .
93
+ const useNewInfra = args.includes('--newinfra');
94
+ const filteredArgs = args.filter((a) => a !== '--newinfra');
95
+ const infra = useNewInfra ? 'appsApi' : undefined;
96
+
96
97
  let dirArg;
97
98
  if (command === '.') {
98
- // If first arg is ".", treat it as directory argument with no command
99
99
  dirArg = '.';
100
100
  } else if (command === 'create' || command === 'new') {
101
- dirArg = args[1];
101
+ dirArg = filteredArgs[1];
102
102
  }
103
103
  // When !command: no args, dirArg stays undefined (create app interactively)
104
104
  const isCurrentDir = dirArg === '.' || dirArg === './';
@@ -125,7 +125,7 @@ async function main() {
125
125
  logger: new Logger(console.log.bind(console)),
126
126
  createPrompter: createAppPrompter(isCurrentDir, dirName, enquirer, (name) => {
127
127
  appName = name;
128
- }),
128
+ }, infra),
129
129
  exec: async (action, body) => {
130
130
  const { execa } = await import('execa');
131
131
  const opts = body && body.length > 0 ? { input: body } : {};
@@ -210,14 +210,18 @@ Commands:
210
210
  skills Manage AI agent skills (pull, push, list)
211
211
  help Show this help message
212
212
 
213
+ Options:
214
+ --newinfra Use the new App Hosting infrastructure (create only)
215
+
213
216
  Examples:
214
- npx @cognite/dune # Create a new app (interactive)
215
- npx @cognite/dune create # Create a new app (interactive)
216
- npx @cognite/dune create [directory] # Create app in [directory] subfolder
217
- npx @cognite/dune create . # Create app in current directory (. is special)
218
- npx @cognite/dune deploy # Deploy with env credentials
219
- npx @cognite/dune deploy:interactive # Deploy with browser login
220
- npx @cognite/dune skills pull # Pull AI skills into your project
217
+ npx @cognite/dune # Create a new app (interactive)
218
+ npx @cognite/dune create # Create a new app (interactive)
219
+ npx @cognite/dune create --newinfra # Create app using new App Hosting infra
220
+ npx @cognite/dune create [directory] # Create app in [directory] subfolder
221
+ npx @cognite/dune create . # Create app in current directory (. is special)
222
+ npx @cognite/dune deploy # Deploy with env credentials
223
+ npx @cognite/dune deploy:interactive # Deploy with browser login
224
+ npx @cognite/dune skills pull # Pull AI skills into your project
221
225
 
222
226
  For programmatic usage:
223
227
  import { DuneAuthProvider, useDune } from "@cognite/dune/auth"
@@ -2,19 +2,7 @@ import { execSync } from 'node:child_process';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
 
5
- /**
6
- * Generate Fusion app URL
7
- */
8
- function generateFusionUrl(deployment, appExternalId, versionTag) {
9
- const { org, project, baseUrl } = deployment;
10
- const cluster = baseUrl?.split('//')[1];
11
-
12
- if (!org || !project || !cluster) {
13
- return null;
14
- }
15
-
16
- return `https://${org}.fusion.cognite.com/${project}/streamlit-apps/dune/${appExternalId}-${versionTag}?cluster=${cluster}&workspace=industrial-tools`;
17
- }
5
+ import { generateFusionUrl } from './utils/fusion-url.js';
18
6
 
19
7
  /**
20
8
  * Load app.json from a directory
@@ -214,6 +202,7 @@ export async function handleDeploy(args) {
214
202
  name: appConfig.name,
215
203
  description: appConfig.description,
216
204
  versionTag: appConfig.versionTag,
205
+ infra: appConfig.infra,
217
206
  },
218
207
  cwd
219
208
  );
@@ -229,7 +218,7 @@ export async function handleDeploy(args) {
229
218
  }
230
219
 
231
220
  // Generate and display the app URL
232
- const appUrl = generateFusionUrl(deployment, appConfig.externalId, appConfig.versionTag);
221
+ const appUrl = generateFusionUrl(deployment, appConfig.externalId, appConfig.versionTag, appConfig.infra);
233
222
  if (appUrl) {
234
223
  console.log(`\n🔗 Open your app:\n ${appUrl}`);
235
224
  }
@@ -10,20 +10,7 @@ import os from 'node:os';
10
10
  import path, { resolve } from 'node:path';
11
11
 
12
12
  import { AuthenticationFlow } from './auth/authentication-flow.js';
13
-
14
- /**
15
- * Generate Fusion app URL
16
- */
17
- function generateFusionUrl(deployment, appExternalId, versionTag) {
18
- const { org, project, baseUrl } = deployment;
19
- const cluster = baseUrl?.split('//')[1];
20
-
21
- if (!org || !project || !cluster) {
22
- return null;
23
- }
24
-
25
- return `https://${org}.fusion.cognite.com/${project}/streamlit-apps/dune/${appExternalId}-${versionTag}?cluster=${cluster}&workspace=industrial-tools`;
26
- }
13
+ import { generateFusionUrl } from './utils/fusion-url.js';
27
14
 
28
15
  // Default OAuth configuration for CDF
29
16
  const DEFAULT_CONFIG = {
@@ -324,40 +311,52 @@ export async function handleDeployInteractive(args) {
324
311
  console.log('\n✅ Authentication successful!');
325
312
  console.log(`\n📤 Deploying to ${deployment.project}...`);
326
313
 
327
- // Import CogniteClient and deployer classes
328
- const { CogniteClient } = await import('@cognite/sdk');
329
- const { CdfApplicationDeployer, ApplicationPackager } = await import('../dist/deploy/index.js');
330
-
331
- // Create SDK with the obtained token
332
- const sdk = new CogniteClient({
333
- appId: appConfig.externalId,
334
- project: deployment.project,
335
- baseUrl: deployment.baseUrl,
336
- getToken: async () => tokens.access_token,
337
- });
338
- await sdk.authenticate();
314
+ const { ApplicationPackager } = await import('../dist/deploy/index.js');
339
315
 
340
316
  // Package the application
341
317
  const distPath = `${cwd}/dist`;
342
318
  const packager = new ApplicationPackager(distPath);
343
319
  const zipFilename = await packager.createZip('app.zip', true);
344
320
 
345
- // Deploy to CDF
346
- const deployer = new CdfApplicationDeployer(sdk);
347
- await deployer.deploy(
348
- appConfig.externalId,
349
- appConfig.name,
350
- appConfig.description,
351
- appConfig.versionTag,
352
- zipFilename,
353
- deployment.published
354
- );
355
-
356
- // Clean up zip file
357
321
  try {
358
- unlinkSync(zipFilename);
359
- } catch {
360
- // Ignore cleanup errors
322
+ const { CogniteClient } = await import('@cognite/sdk');
323
+ const sdk = new CogniteClient({
324
+ appId: appConfig.externalId,
325
+ project: deployment.project,
326
+ baseUrl: deployment.baseUrl,
327
+ getToken: async () => tokens.access_token,
328
+ });
329
+ await sdk.authenticate();
330
+
331
+ if (appConfig.infra === 'appsApi') {
332
+ const { AppHostingDeployer } = await import('../dist/deploy/index.js');
333
+ const deployer = new AppHostingDeployer(sdk);
334
+ await deployer.deploy(
335
+ appConfig.externalId,
336
+ appConfig.name,
337
+ appConfig.description,
338
+ appConfig.versionTag,
339
+ zipFilename,
340
+ deployment.published || published,
341
+ );
342
+ } else {
343
+ const { CdfApplicationDeployer } = await import('../dist/deploy/index.js');
344
+ const deployer = new CdfApplicationDeployer(sdk);
345
+ await deployer.deploy(
346
+ appConfig.externalId,
347
+ appConfig.name,
348
+ appConfig.description,
349
+ appConfig.versionTag,
350
+ zipFilename,
351
+ deployment.published || published,
352
+ );
353
+ }
354
+ } finally {
355
+ try {
356
+ unlinkSync(zipFilename);
357
+ } catch {
358
+ // Ignore cleanup errors
359
+ }
361
360
  }
362
361
 
363
362
  console.log(`\n✅ Successfully deployed ${appConfig.name} to ${deployment.project}`);
@@ -369,7 +368,7 @@ export async function handleDeployInteractive(args) {
369
368
  }
370
369
 
371
370
  // Generate and display the app URL
372
- const appUrl = generateFusionUrl(deployment, appConfig.externalId, appConfig.versionTag);
371
+ const appUrl = generateFusionUrl(deployment, appConfig.externalId, appConfig.versionTag, appConfig.infra);
373
372
  if (appUrl) {
374
373
  console.log(`\n🔗 Open your app:\n ${appUrl}`);
375
374
  }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Generate a Fusion app URL from deployment config and app metadata.
3
+ *
4
+ * @param {object} deployment - Deployment config (org, project, baseUrl)
5
+ * @param {string} appExternalId - App external ID
6
+ * @param {string} versionTag - Version tag (used for legacy infra URL only)
7
+ * @param {string} [infra] - Infrastructure type; "appsApi" uses the new dune-app-host path
8
+ * @returns {string|null} URL, or null if required fields are missing
9
+ */
10
+ export function generateFusionUrl(deployment, appExternalId, versionTag, infra) {
11
+ const { org, project, baseUrl } = deployment;
12
+ let cluster = null;
13
+ try {
14
+ cluster = baseUrl ? new URL(baseUrl).host : null;
15
+ } catch {
16
+ // baseUrl is not a valid URL; cluster stays null and we return null below
17
+ }
18
+
19
+ if (!org || !project || !cluster) {
20
+ return null;
21
+ }
22
+
23
+ if (infra === 'appsApi') {
24
+ return `https://${org}.fusion.cognite.com/${project}/dune-app-host/app/${encodeURIComponent(appExternalId)}?cluster=${cluster}`;
25
+ }
26
+
27
+ return `https://${org}.fusion.cognite.com/${project}/streamlit-apps/dune/${encodeURIComponent(appExternalId)}-${versionTag}?cluster=${cluster}&workspace=industrial-tools`;
28
+ }
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { generateFusionUrl } from './fusion-url.js';
4
+
5
+ const BASE_DEPLOYMENT = {
6
+ org: 'acme',
7
+ project: 'my-project',
8
+ baseUrl: 'https://cognite.test',
9
+ };
10
+
11
+ describe(generateFusionUrl.name, () => {
12
+ it('returns null when org is missing', () => {
13
+ const result = generateFusionUrl({ ...BASE_DEPLOYMENT, org: '' }, 'app-id', '1.0.0');
14
+ expect(result).toBeNull();
15
+ });
16
+
17
+ it('returns null when project is missing', () => {
18
+ const result = generateFusionUrl({ ...BASE_DEPLOYMENT, project: '' }, 'app-id', '1.0.0');
19
+ expect(result).toBeNull();
20
+ });
21
+
22
+ it('returns null when baseUrl yields no cluster', () => {
23
+ // 'no-protocol' is not a valid URL, so new URL() throws and cluster stays null
24
+ const result = generateFusionUrl({ ...BASE_DEPLOYMENT, baseUrl: 'no-protocol' }, 'app-id', '1.0.0');
25
+ expect(result).toBeNull();
26
+ });
27
+
28
+ it('returns legacy URL when infra is undefined', () => {
29
+ const result = generateFusionUrl(BASE_DEPLOYMENT, 'my-app', '1.2.3');
30
+ expect(result).toBe(
31
+ 'https://acme.fusion.cognite.com/my-project/streamlit-apps/dune/my-app-1.2.3?cluster=cognite.test&workspace=industrial-tools'
32
+ );
33
+ });
34
+
35
+ it('returns App Hosting URL when infra is appsApi', () => {
36
+ const result = generateFusionUrl(BASE_DEPLOYMENT, 'my-app', '1.2.3', 'appsApi');
37
+ expect(result).toBe(
38
+ 'https://acme.fusion.cognite.com/my-project/dune-app-host/app/my-app?cluster=cognite.test'
39
+ );
40
+ });
41
+
42
+ it('extracts cluster from baseUrl hostname', () => {
43
+ const result = generateFusionUrl(
44
+ { ...BASE_DEPLOYMENT, baseUrl: 'https://az-ams-sp-002.cognitedata.com' },
45
+ 'my-app',
46
+ '1.0.0'
47
+ );
48
+ expect(result).toContain('cluster=az-ams-sp-002.cognitedata.com');
49
+ });
50
+ });
@@ -3,6 +3,19 @@ import * as react from 'react';
3
3
  import { ReactNode } from 'react';
4
4
  import { CogniteClient } from '@cognite/sdk';
5
5
 
6
+ interface AppSdkAuthProviderProps {
7
+ children: ReactNode;
8
+ loadingComponent?: ReactNode;
9
+ errorComponent?: (error: string) => ReactNode;
10
+ }
11
+ /**
12
+ * Auth provider for apps running in the new Fusion app host (dune-app-host).
13
+ * Uses @cognite/app-sdk's Comlink handshake to connect to the host and obtain
14
+ * tokens on-demand. Exposes the same DuneContextValue ({ sdk, isLoading,
15
+ * error }) as DuneAuthProvider so useDune() works unchanged.
16
+ */
17
+ declare const AppSdkAuthProvider: ({ children, loadingComponent, errorComponent, }: AppSdkAuthProviderProps) => react_jsx_runtime.JSX.Element;
18
+
6
19
  interface DuneContextValue {
7
20
  sdk: CogniteClient;
8
21
  isLoading: boolean;
@@ -35,4 +48,4 @@ declare const getToken: (clientId: string, clientSecret: string) => Promise<any>
35
48
  declare const createCDFSDK: (config: CDFConfig) => Promise<CogniteClient>;
36
49
  declare const EMPTY_SDK: CogniteClient;
37
50
 
38
- export { type CDFConfig, DuneAuthProvider, DuneAuthProviderContext, type DuneAuthProviderProps, type DuneContextValue, EMPTY_SDK, createCDFSDK, getToken, useDune };
51
+ export { AppSdkAuthProvider, type CDFConfig, DuneAuthProvider, DuneAuthProviderContext, type DuneAuthProviderProps, type DuneContextValue, EMPTY_SDK, createCDFSDK, getToken, useDune };
@@ -1,12 +1,14 @@
1
1
  import {
2
+ AppSdkAuthProvider,
2
3
  DuneAuthProvider,
3
4
  DuneAuthProviderContext,
4
5
  EMPTY_SDK,
5
6
  createCDFSDK,
6
7
  getToken,
7
8
  useDune
8
- } from "../chunk-53VTKDSC.js";
9
+ } from "../chunk-XWZCFZUH.js";
9
10
  export {
11
+ AppSdkAuthProvider,
10
12
  DuneAuthProvider,
11
13
  DuneAuthProviderContext,
12
14
  EMPTY_SDK,
@@ -1,3 +1,8 @@
1
+ // src/auth/app-sdk-auth-provider.tsx
2
+ import { connectToHostApp } from "@cognite/app-sdk";
3
+ import { CogniteClient as CogniteClient3 } from "@cognite/sdk";
4
+ import { useEffect as useEffect2, useState as useState2 } from "react";
5
+
1
6
  // src/auth/dune-auth-provider.tsx
2
7
  import { CogniteClient as CogniteClient2 } from "@cognite/sdk";
3
8
  import { createContext, useEffect, useState } from "react";
@@ -131,6 +136,50 @@ var FusionIframeAuthenticationInnerProvider = ({
131
136
  return /* @__PURE__ */ jsx(DuneAuthProviderContext.Provider, { value: { sdk, isLoading, error }, children });
132
137
  };
133
138
 
139
+ // src/auth/app-sdk-auth-provider.tsx
140
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
141
+ var AppSdkAuthProvider = ({
142
+ children,
143
+ loadingComponent = /* @__PURE__ */ jsx2("div", { children: "Loading CDF authentication..." }),
144
+ errorComponent = (error) => /* @__PURE__ */ jsxs2("div", { children: [
145
+ "Authentication error: ",
146
+ error
147
+ ] })
148
+ }) => {
149
+ const [sdk, setSdk] = useState2(EMPTY_SDK);
150
+ const [isLoading, setIsLoading] = useState2(true);
151
+ const [error, setError] = useState2();
152
+ useEffect2(() => {
153
+ connectToHostApp().then(async ({ api }) => {
154
+ const [project, baseUrl] = await Promise.all([
155
+ api.getProject(),
156
+ api.getBaseUrl()
157
+ ]);
158
+ const client = new CogniteClient3({
159
+ appId: "dune-app",
160
+ project,
161
+ baseUrl,
162
+ oidcTokenProvider: async () => api.getAccessToken()
163
+ });
164
+ await client.authenticate();
165
+ setSdk(client);
166
+ setError(void 0);
167
+ }).catch((err) => {
168
+ const message = err instanceof Error ? err.message : String(err);
169
+ setError(message);
170
+ }).finally(() => {
171
+ setIsLoading(false);
172
+ });
173
+ }, []);
174
+ if (error && errorComponent) {
175
+ return /* @__PURE__ */ jsx2(Fragment2, { children: errorComponent(error) });
176
+ }
177
+ if (isLoading && loadingComponent) {
178
+ return /* @__PURE__ */ jsx2(Fragment2, { children: loadingComponent });
179
+ }
180
+ return /* @__PURE__ */ jsx2(DuneAuthProviderContext.Provider, { value: { sdk, isLoading, error }, children });
181
+ };
182
+
134
183
  // src/auth/use-dune.ts
135
184
  import { useContext } from "react";
136
185
  var useDune = () => {
@@ -147,5 +196,6 @@ export {
147
196
  EMPTY_SDK,
148
197
  DuneAuthProviderContext,
149
198
  DuneAuthProvider,
199
+ AppSdkAuthProvider,
150
200
  useDune
151
201
  };
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+
3
+ // cli/cli.ts
4
+ import { Command } from "commander";
5
+
6
+ // cli/commands/skills.ts
7
+ import { execFileSync } from "child_process";
8
+ import { createRequire } from "module";
9
+ import { InvalidArgumentError } from "commander";
10
+ var SKILL_SOURCE = "cognitedata/dune-skills";
11
+ var SUPPORTED_AGENTS = ["claude-code", "cursor"];
12
+ var AGENT_FLAGS = SUPPORTED_AGENTS.flatMap((agent) => ["-a", agent]);
13
+ var SKILLS_BIN = createRequire(import.meta.url).resolve("skills/bin/cli.mjs");
14
+ function execSkillsCli(args, options = {}) {
15
+ execFileSync(process.execPath, [SKILLS_BIN, ...args], {
16
+ stdio: "inherit",
17
+ cwd: process.cwd(),
18
+ ...options
19
+ });
20
+ }
21
+ function validateSource(value) {
22
+ if (!/^[\w.-]+\/[\w.-]+$/.test(value)) {
23
+ throw new InvalidArgumentError(
24
+ "Expected owner/repo format (e.g., cognitedata/dune-skills)"
25
+ );
26
+ }
27
+ return value;
28
+ }
29
+ function buildPullArgs(options) {
30
+ const args = ["add", options.source, ...AGENT_FLAGS];
31
+ if (options.skill) {
32
+ args.push("--skill", options.skill);
33
+ } else if (!options.interactive) {
34
+ args.push("--skill", "*", "-y");
35
+ }
36
+ if (options.global) {
37
+ args.push("--global");
38
+ }
39
+ return args;
40
+ }
41
+ function handlePull(options) {
42
+ console.log(`\u{1F504} Pulling skills from ${options.source}...`);
43
+ execSkillsCli(buildPullArgs(options));
44
+ console.log("\n\u2705 Skills pulled successfully");
45
+ }
46
+ function registerSkillsCommand(parent) {
47
+ const skills = parent.command("skills").description("Manage AI agent skills for your Dune app");
48
+ skills.command("pull").description("Pull all skills into your project").option(
49
+ "--source <owner/repo>",
50
+ "Skills repository",
51
+ validateSource,
52
+ SKILL_SOURCE
53
+ ).option("--skill <name>", "Pull a specific skill by name").option("-i, --interactive", "Interactively select which skills to install", false).option("--global", "Install skills globally", false).action(handlePull);
54
+ skills.command("list").description("List installed skills").action(() => {
55
+ execSkillsCli(["list"]);
56
+ });
57
+ }
58
+
59
+ // cli/cli.ts
60
+ var program = new Command();
61
+ program.name("dune").description("Build and deploy React apps to Cognite Data Fusion").version("0.4.0");
62
+ registerSkillsCommand(program);
63
+ program.parse();
@@ -17,10 +17,50 @@ type App = {
17
17
  name: string;
18
18
  description: string;
19
19
  versionTag: string;
20
+ /** When set to "appsApi", deploy uses the App Hosting API instead of Files API */
21
+ infra?: "appsApi";
20
22
  };
21
23
 
22
24
  declare const deploy: (deployment: Deployment, app: App, folder: string) => Promise<void>;
23
25
 
26
+ /**
27
+ * App Hosting API Deployment
28
+ *
29
+ * Handles deployment of packaged applications via the new App Hosting API
30
+ * instead of CDF Files API + dataset.
31
+ */
32
+
33
+ declare class AppHostingDeployer {
34
+ private client;
35
+ constructor(client: CogniteClient);
36
+ private get appsBasePath();
37
+ /**
38
+ * Ensure the app exists in the App Hosting service. Creates it if missing.
39
+ */
40
+ ensureApp(externalId: string, name: string, description: string): Promise<void>;
41
+ /**
42
+ * Upload a new version of the app as a zip file.
43
+ *
44
+ * `entryPath` must be a relative path (no leading slash). The App
45
+ * Hosting backend rejects absolute paths as a security measure against
46
+ * archive traversal, see `ZipCentralDirectory.normalizeEntryPath` in
47
+ * infrastructure/services/app-hosting.
48
+ */
49
+ uploadVersion(appExternalId: string, version: string, zipPath: string, entryPath?: string): Promise<void>;
50
+ /**
51
+ * Publish the version and set it as the ACTIVE alias.
52
+ */
53
+ publishAndActivate(appExternalId: string, version: string): Promise<void>;
54
+ /**
55
+ * Full deployment: ensure app exists, upload version, and optionally publish and activate.
56
+ *
57
+ * When `published` is false the version is uploaded in DRAFT state with no alias —
58
+ * it is stored but not served to end users. Pass `published: true` to transition the
59
+ * version to PUBLISHED and set it as the ACTIVE alias so it starts receiving traffic.
60
+ */
61
+ deploy(appExternalId: string, name: string, description: string, versionTag: string, zipPath: string, published?: boolean): Promise<void>;
62
+ }
63
+
24
64
  /**
25
65
  * CDF Application Deployment
26
66
  *
@@ -95,4 +135,4 @@ declare const getSdk: (deployment: Deployment, folder: string) => Promise<Cognit
95
135
  */
96
136
  declare const getToken: (deployment: Deployment) => Promise<string>;
97
137
 
98
- export { type App, ApplicationPackager, CdfApplicationDeployer, type Deployment, deploy, getSdk, getToken };
138
+ export { type App, AppHostingDeployer, ApplicationPackager, CdfApplicationDeployer, type Deployment, deploy, getSdk, getToken };