@notis_ai/cli 0.2.7 → 0.2.9
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 +56 -2
- package/dist/scaffolds/notis-database/app/page.tsx +38 -41
- package/dist/scaffolds/notis-database/lib/types.ts +5 -7
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +1839 -0
- package/dist/scaffolds/notis-database/notis.config.ts +48 -1
- package/dist/scaffolds/notis-database/package-lock.json +3935 -0
- package/dist/scaffolds/notis-database/package.json +2 -1
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/package.json +4 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectActionBar.tsx +7 -2
- package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +196 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useMultiSelect.ts +38 -2
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/index.ts +30 -1
- package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/runtime.ts +76 -17
- package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +4 -1
- package/dist/scaffolds/notis-journal/CHANGELOG.md +4 -0
- package/dist/scaffolds/notis-journal/app/page.tsx +1 -0
- package/dist/scaffolds/notis-journal/package-lock.json +4615 -0
- package/dist/scaffolds/notis-journal/package.json +2 -2
- package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +52 -1
- package/dist/scaffolds/notis-notes/app/page.tsx +39 -46
- package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +596 -0
- package/dist/scaffolds/notis-notes/lib/visible-properties.ts +144 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +752 -0
- package/dist/scaffolds/notis-notes/notis.config.ts +48 -3
- package/dist/scaffolds/notis-notes/package-lock.json +4636 -0
- package/dist/scaffolds/notis-notes/package.json +7 -3
- package/dist/scaffolds/notis-notes/packages/sdk/package.json +36 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +196 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +83 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +220 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +15 -0
- package/dist/scaffolds/notis-random/CHANGELOG.md +12 -2
- package/dist/scaffolds/notis-random/app/history/page.tsx +5 -4
- package/dist/scaffolds/notis-random/app/page.tsx +141 -74
- package/dist/scaffolds/notis-random/components/ui/button.tsx +50 -0
- package/dist/scaffolds/notis-random/components/ui/card.tsx +16 -0
- package/dist/scaffolds/notis-random/components/ui/input.tsx +23 -0
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +26 -7
- package/dist/scaffolds/notis-random/lib/rng.ts +179 -19
- package/dist/scaffolds/notis-random/lib/roll-record.ts +103 -16
- package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +753 -0
- package/dist/scaffolds/notis-random/notis.config.ts +47 -3
- package/dist/scaffolds/notis-random/package-lock.json +4513 -0
- package/dist/scaffolds/notis-random/package.json +6 -2
- package/dist/scaffolds/notis-random/packages/sdk/package.json +36 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +196 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +83 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +220 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +15 -0
- package/dist/scaffolds/notis-random/tailwind.config.ts +8 -1
- package/dist/scaffolds.json +8 -17
- package/package.json +2 -1
- package/skills/notis-apps/SKILL.md +438 -144
- package/skills/notis-apps/cli.md +16 -1
- package/skills/notis-cli/SKILL.md +11 -4
- package/skills/notis-onboarding/BRIEF.md +93 -0
- package/skills/notis-query/cli.md +1 -1
- package/src/cli.js +50 -12
- package/src/command-specs/apps.js +309 -41
- package/src/command-specs/auth.js +107 -0
- package/src/command-specs/index.js +4 -0
- package/src/command-specs/meta.js +68 -6
- package/src/command-specs/onboarding.js +304 -0
- package/src/command-specs/tools.js +8 -4
- package/src/runtime/app-dev-server.js +3 -2
- package/src/runtime/app-dev-sessions.js +14 -40
- package/src/runtime/app-platform.js +65 -4
- package/src/runtime/desktop-auth.js +86 -17
- package/src/runtime/errors.js +1 -0
- package/src/runtime/oauth.js +1105 -0
- package/src/runtime/output.js +16 -2
- package/src/runtime/profiles.js +542 -18
- package/src/runtime/telemetry.js +92 -0
- package/src/runtime/transport.js +66 -11
- package/template/.harness/index.html.tmpl +30 -2
- package/template/packages/sdk/src/config.ts +38 -1
- package/dist/scaffolds/notis-affiliate-prospects/CHANGELOG.md +0 -7
- package/dist/scaffolds/notis-affiliate-prospects/README.md +0 -20
- package/dist/scaffolds/notis-affiliate-prospects/app/globals.css +0 -42
- package/dist/scaffolds/notis-affiliate-prospects/app/layout.tsx +0 -7
- package/dist/scaffolds/notis-affiliate-prospects/app/page.tsx +0 -248
- package/dist/scaffolds/notis-affiliate-prospects/app/prospects/page.tsx +0 -134
- package/dist/scaffolds/notis-affiliate-prospects/app/segments/page.tsx +0 -108
- package/dist/scaffolds/notis-affiliate-prospects/components/ui/card.tsx +0 -35
- package/dist/scaffolds/notis-affiliate-prospects/index.html +0 -12
- package/dist/scaffolds/notis-affiliate-prospects/lib/affiliate-data.ts +0 -218
- package/dist/scaffolds/notis-affiliate-prospects/lib/demo-prospects.ts +0 -52
- package/dist/scaffolds/notis-affiliate-prospects/lib/notis-tools.ts +0 -100
- package/dist/scaffolds/notis-affiliate-prospects/lib/utils.ts +0 -25
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-fixtures.json +0 -187
- package/dist/scaffolds/notis-affiliate-prospects/notis.config.ts +0 -65
- package/dist/scaffolds/notis-affiliate-prospects/package.json +0 -32
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/config.ts +0 -90
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useDatabase.ts +0 -76
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useUpsertDocument.ts +0 -50
- package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/styles.css +0 -38
- package/dist/scaffolds/notis-affiliate-prospects/postcss.config.mjs +0 -6
- package/dist/scaffolds/notis-affiliate-prospects/src/dev-main.tsx +0 -72
- package/dist/scaffolds/notis-affiliate-prospects/src/mock-runtime.ts +0 -242
- package/dist/scaffolds/notis-affiliate-prospects/tailwind.config.ts +0 -51
- package/dist/scaffolds/notis-affiliate-prospects/tsconfig.json +0 -23
- package/dist/scaffolds/notis-affiliate-prospects/vite.config.ts +0 -11
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useBackend.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotis.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotisNavigation.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTool.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTools.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTopBarSearch.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/provider.tsx +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/ui.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/vite.ts +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/tsconfig.json +0 -0
- /package/dist/scaffolds/{notis-affiliate-prospects → notis-random}/components.json +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { loginWithOAuth, logoutOAuth } from '../runtime/oauth.js';
|
|
2
|
+
|
|
3
|
+
async function loginHandler(ctx) {
|
|
4
|
+
const result = await loginWithOAuth(ctx.runtime, ctx.options, ctx.output);
|
|
5
|
+
if (result.desktopFastPath) {
|
|
6
|
+
return ctx.output.emitSuccess({
|
|
7
|
+
command: 'login',
|
|
8
|
+
data: {
|
|
9
|
+
authenticated: true,
|
|
10
|
+
credential_source: result.credentialSource || 'desktop',
|
|
11
|
+
},
|
|
12
|
+
humanSummary: 'Notis Desktop already provides a valid CLI credential.',
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (result.agentAuthorization) {
|
|
16
|
+
return ctx.output.emitSuccess({
|
|
17
|
+
command: 'login',
|
|
18
|
+
data: result.agentAuthorization,
|
|
19
|
+
humanSummary: 'Open the authorization URL in a browser to continue.',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return ctx.output.emitSuccess({
|
|
23
|
+
command: 'login',
|
|
24
|
+
data: {
|
|
25
|
+
authenticated: true,
|
|
26
|
+
credential_source: 'oauth',
|
|
27
|
+
profile: ctx.runtime.profileName,
|
|
28
|
+
user_id: result.profile.oauth_user_id,
|
|
29
|
+
scopes: result.profile.oauth_scopes,
|
|
30
|
+
access_expires_at: result.profile.oauth_access_expires_at,
|
|
31
|
+
refresh_expires_at: result.profile.oauth_refresh_expires_at,
|
|
32
|
+
},
|
|
33
|
+
humanSummary: `Notis CLI is authorized for profile "${ctx.runtime.profileName}".`,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function logoutHandler(ctx) {
|
|
38
|
+
const result = await logoutOAuth(ctx.runtime, {
|
|
39
|
+
allProfiles: Boolean(ctx.options.allProfiles),
|
|
40
|
+
});
|
|
41
|
+
return ctx.output.emitSuccess({
|
|
42
|
+
command: 'logout',
|
|
43
|
+
data: {
|
|
44
|
+
oauth_connected: false,
|
|
45
|
+
cleared_profiles: result.profiles,
|
|
46
|
+
},
|
|
47
|
+
humanSummary: ctx.options.allProfiles
|
|
48
|
+
? 'OAuth credentials were removed from all CLI profiles.'
|
|
49
|
+
: `OAuth credentials were removed from profile "${ctx.runtime.profileName}".`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const authCommandSpecs = [
|
|
54
|
+
{
|
|
55
|
+
command_path: ['login'],
|
|
56
|
+
summary: 'Authorize the Notis CLI in a browser with scoped OAuth access.',
|
|
57
|
+
when_to_use:
|
|
58
|
+
'Use this on a machine where Notis Desktop is unavailable, signed out, or should not own CLI authentication.',
|
|
59
|
+
args_schema: {
|
|
60
|
+
arguments: [],
|
|
61
|
+
options: [
|
|
62
|
+
{ flags: '--no-browser', description: 'Print the authorization URL without opening a browser.' },
|
|
63
|
+
{ flags: '--print-url', description: 'Print the authorization URL even when opening a browser.' },
|
|
64
|
+
{ flags: '--paste-code', description: 'Use the copy-paste callback for SSH and headless machines.' },
|
|
65
|
+
{ flags: '--force', description: 'Create an independent OAuth grant even when Desktop is signed in.' },
|
|
66
|
+
{ flags: '--timeout-seconds <n>', description: 'How long to wait for authorization (default 300).' },
|
|
67
|
+
{ flags: '--scope <scope>', description: 'OAuth permission to request (repeatable).', collect: true },
|
|
68
|
+
{ flags: '--code <code>', description: 'Redeem the code shown in the browser after a non-interactive login.' },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
examples: [
|
|
72
|
+
'notis login',
|
|
73
|
+
'notis login --no-browser --print-url',
|
|
74
|
+
'notis login --paste-code',
|
|
75
|
+
'notis login --code 4f3c2b1a',
|
|
76
|
+
'notis login --force',
|
|
77
|
+
],
|
|
78
|
+
output_schema:
|
|
79
|
+
'Returns credential_source, profile, user_id, scopes, and credential expiries; agent mode returns authorize_url and expires_in.',
|
|
80
|
+
mutates: true,
|
|
81
|
+
idempotent: true,
|
|
82
|
+
require_auth: false,
|
|
83
|
+
related_commands: ['notis logout', 'notis doctor', 'notis whoami'],
|
|
84
|
+
backend_call: { type: 'oauth', name: 'authorization_code+pkce' },
|
|
85
|
+
handler: loginHandler,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
command_path: ['logout'],
|
|
89
|
+
summary: 'Revoke and remove the scoped OAuth credential for the active CLI profile.',
|
|
90
|
+
when_to_use:
|
|
91
|
+
'Use this to disconnect the command line without signing Notis Desktop out.',
|
|
92
|
+
args_schema: {
|
|
93
|
+
arguments: [],
|
|
94
|
+
options: [
|
|
95
|
+
{ flags: '--all-profiles', description: 'Remove OAuth credentials from every CLI profile.' },
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
examples: ['notis logout', 'notis logout --all-profiles'],
|
|
99
|
+
output_schema: 'Returns oauth_connected=false and the profiles whose OAuth credentials were removed.',
|
|
100
|
+
mutates: true,
|
|
101
|
+
idempotent: true,
|
|
102
|
+
require_auth: false,
|
|
103
|
+
related_commands: ['notis login', 'notis doctor'],
|
|
104
|
+
backend_call: { type: 'oauth', name: 'revocation' },
|
|
105
|
+
handler: logoutHandler,
|
|
106
|
+
},
|
|
107
|
+
];
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { appsCommandSpecs } from './apps.js';
|
|
2
2
|
import { toolsCommandSpecs } from './tools.js';
|
|
3
3
|
import { metaCommandSpecs } from './meta.js';
|
|
4
|
+
import { onboardingCommandSpecs } from './onboarding.js';
|
|
4
5
|
import { diagnosticCommandSpecs } from './diagnostics.js';
|
|
5
6
|
import { smokeCommandSpecs } from './smoke.js';
|
|
7
|
+
import { authCommandSpecs } from './auth.js';
|
|
6
8
|
|
|
7
9
|
export const GROUP_SUMMARIES = {
|
|
8
10
|
apps: 'Develop, deploy, and submit Notis Apps.',
|
|
@@ -12,6 +14,8 @@ export const GROUP_SUMMARIES = {
|
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
export const COMMAND_SPECS = [
|
|
17
|
+
...authCommandSpecs,
|
|
18
|
+
...onboardingCommandSpecs,
|
|
15
19
|
...appsCommandSpecs,
|
|
16
20
|
...toolsCommandSpecs,
|
|
17
21
|
...diagnosticCommandSpecs,
|
|
@@ -1,19 +1,52 @@
|
|
|
1
1
|
import { COMPOSIO_SEARCH_TOOLS, healthCheck, probeAuth } from './helpers.js';
|
|
2
2
|
import { findCommandSpec, formatDescribe } from '../runtime/help.js';
|
|
3
|
-
import { createExpiredAuthError } from '../runtime/desktop-auth.js';
|
|
4
|
-
import {
|
|
3
|
+
import { createExpiredAuthError, getDesktopAuthRecovery } from '../runtime/desktop-auth.js';
|
|
4
|
+
import {
|
|
5
|
+
credentialIsExpired,
|
|
6
|
+
getJwtCanonicalUserId,
|
|
7
|
+
getProfile,
|
|
8
|
+
loadConfig,
|
|
9
|
+
} from '../runtime/profiles.js';
|
|
10
|
+
import { ensureFreshOAuthCredential } from '../runtime/oauth.js';
|
|
11
|
+
|
|
12
|
+
export const DOCTOR_TOOL_ROUNDTRIP_TIMEOUT_MS = 90_000;
|
|
13
|
+
|
|
14
|
+
export function doctorToolRoundtripRuntime(runtime) {
|
|
15
|
+
return {
|
|
16
|
+
...runtime,
|
|
17
|
+
timeoutMs: Math.max(runtime.timeoutMs || 0, DOCTOR_TOOL_ROUNDTRIP_TIMEOUT_MS),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
5
20
|
|
|
6
21
|
async function doctorHandler(ctx) {
|
|
7
22
|
const checks = {
|
|
8
23
|
config: 'ok',
|
|
9
24
|
auth: 'missing',
|
|
25
|
+
identity: 'ok',
|
|
10
26
|
health: 'unknown',
|
|
11
27
|
tool_roundtrip: 'unknown',
|
|
12
28
|
};
|
|
13
29
|
|
|
30
|
+
if (ctx.runtime.credentialKind === 'oauth') {
|
|
31
|
+
try {
|
|
32
|
+
await ensureFreshOAuthCredential(ctx.runtime);
|
|
33
|
+
} catch {
|
|
34
|
+
// Doctor still reports the remaining health and recovery checks when a
|
|
35
|
+
// refresh endpoint is unavailable or rejects the stored credential.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let profile = getProfile(loadConfig(ctx.runtime.worktreeRuntime), ctx.runtime.profileName);
|
|
14
39
|
checks.auth = ctx.runtime.jwt
|
|
15
|
-
? (
|
|
40
|
+
? (credentialIsExpired(ctx.runtime, profile) ? 'expired' : 'configured')
|
|
16
41
|
: 'missing';
|
|
42
|
+
const desktopUserId = getJwtCanonicalUserId(profile.jwt);
|
|
43
|
+
if (
|
|
44
|
+
desktopUserId
|
|
45
|
+
&& profile.oauth_user_id
|
|
46
|
+
&& desktopUserId !== profile.oauth_user_id
|
|
47
|
+
) {
|
|
48
|
+
checks.identity = 'error';
|
|
49
|
+
}
|
|
17
50
|
|
|
18
51
|
try {
|
|
19
52
|
await healthCheck(ctx.runtime);
|
|
@@ -24,8 +57,15 @@ async function doctorHandler(ctx) {
|
|
|
24
57
|
|
|
25
58
|
if (ctx.runtime.jwt) {
|
|
26
59
|
try {
|
|
27
|
-
|
|
60
|
+
// Tool discovery may have to query several connected MCP servers on a
|
|
61
|
+
// cold local backend. A diagnostic must not report a false failure just
|
|
62
|
+
// because that legitimate roundtrip exceeds the general 30s default.
|
|
63
|
+
const payload = await probeAuth(doctorToolRoundtripRuntime(ctx.runtime));
|
|
28
64
|
checks.tool_roundtrip = Array.isArray(payload.toolkit_connection_statuses) ? 'ok' : 'error';
|
|
65
|
+
profile = getProfile(loadConfig(ctx.runtime.worktreeRuntime), ctx.runtime.profileName);
|
|
66
|
+
checks.auth = ctx.runtime.jwt
|
|
67
|
+
? (credentialIsExpired(ctx.runtime, profile) ? 'expired' : 'configured')
|
|
68
|
+
: 'missing';
|
|
29
69
|
} catch {
|
|
30
70
|
checks.tool_roundtrip = 'error';
|
|
31
71
|
}
|
|
@@ -33,7 +73,7 @@ async function doctorHandler(ctx) {
|
|
|
33
73
|
|
|
34
74
|
const hints = [];
|
|
35
75
|
if (checks.auth === 'missing') {
|
|
36
|
-
hints.push({
|
|
76
|
+
hints.push(...getDesktopAuthRecovery(ctx.runtime, { mode: 'missing' }).hints);
|
|
37
77
|
} else if (checks.auth === 'expired') {
|
|
38
78
|
hints.push(...createExpiredAuthError(ctx.runtime).hints);
|
|
39
79
|
}
|
|
@@ -43,12 +83,27 @@ async function doctorHandler(ctx) {
|
|
|
43
83
|
if (checks.tool_roundtrip === 'error') {
|
|
44
84
|
hints.push({ command: 'notis whoami', reason: 'Verify your account and permissions' });
|
|
45
85
|
}
|
|
86
|
+
if (checks.identity === 'error') {
|
|
87
|
+
hints.push({
|
|
88
|
+
command: 'notis logout',
|
|
89
|
+
reason: 'Desktop and OAuth credentials identify different accounts; remove the independent OAuth grant or sign Desktop back into the same account',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
46
92
|
|
|
47
93
|
return ctx.output.emitSuccess({
|
|
48
94
|
command: ctx.spec.command_path.join(' '),
|
|
49
95
|
data: {
|
|
50
96
|
profile: ctx.runtime.profileName,
|
|
51
97
|
api_base: ctx.runtime.apiBase,
|
|
98
|
+
credential_source: ctx.runtime.credentialKind || null,
|
|
99
|
+
...(ctx.runtime.credentialKind === 'oauth'
|
|
100
|
+
? {
|
|
101
|
+
oauth_client_id: profile.oauth_client_id || null,
|
|
102
|
+
oauth_scopes: profile.oauth_scopes || [],
|
|
103
|
+
oauth_access_expires_at: profile.oauth_access_expires_at || null,
|
|
104
|
+
oauth_refresh_expires_at: profile.oauth_refresh_expires_at || null,
|
|
105
|
+
}
|
|
106
|
+
: {}),
|
|
52
107
|
checks,
|
|
53
108
|
},
|
|
54
109
|
humanSummary: `Doctor checks completed for profile ${ctx.runtime.profileName}`,
|
|
@@ -72,16 +127,23 @@ function decodeJwtUserId(jwt) {
|
|
|
72
127
|
}
|
|
73
128
|
}
|
|
74
129
|
|
|
130
|
+
export function activeRuntimeUserId(runtime) {
|
|
131
|
+
return runtime.credentialKind === 'oauth'
|
|
132
|
+
? runtime.oauthUserId
|
|
133
|
+
: decodeJwtUserId(runtime.jwt);
|
|
134
|
+
}
|
|
135
|
+
|
|
75
136
|
async function whoamiHandler(ctx) {
|
|
76
137
|
const payload = await probeAuth(ctx.runtime);
|
|
77
138
|
const toolkits = payload.toolkit_connection_statuses || [];
|
|
78
|
-
const userId =
|
|
139
|
+
const userId = activeRuntimeUserId(ctx.runtime);
|
|
79
140
|
|
|
80
141
|
return ctx.output.emitSuccess({
|
|
81
142
|
command: ctx.spec.command_path.join(' '),
|
|
82
143
|
data: {
|
|
83
144
|
profile: ctx.runtime.profileName,
|
|
84
145
|
api_base: ctx.runtime.apiBase,
|
|
146
|
+
credential_source: ctx.runtime.credentialKind || null,
|
|
85
147
|
user_id: userId,
|
|
86
148
|
toolkit_count: toolkits.length,
|
|
87
149
|
toolkits: toolkits.map((t) => t.toolkit),
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { CliError, EXIT_CODES } from '../runtime/errors.js';
|
|
6
|
+
import { getDesktopAuthRecovery, waitForDesktopAuth } from '../runtime/desktop-auth.js';
|
|
7
|
+
import {
|
|
8
|
+
credentialIsExpired,
|
|
9
|
+
getProfile,
|
|
10
|
+
loadConfig,
|
|
11
|
+
} from '../runtime/profiles.js';
|
|
12
|
+
import { ensureFreshOAuthCredential, loginWithOAuth } from '../runtime/oauth.js';
|
|
13
|
+
|
|
14
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const BUNDLED_BRIEF_PATH = join(HERE, '..', '..', 'skills', 'notis-onboarding', 'BRIEF.md');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The brief is served rather than bundled so it tracks the deployed server. The
|
|
19
|
+
* bundled copy is a fallback for an offline or unreachable API, and the payload
|
|
20
|
+
* says which one the caller got so a stale brief is diagnosable.
|
|
21
|
+
*/
|
|
22
|
+
async function fetchBrief(apiBase, timeoutMs) {
|
|
23
|
+
try {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const timer = setTimeout(() => controller.abort(), Math.min(timeoutMs || 30_000, 30_000));
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(`${apiBase.replace(/\/$/, '')}/signup/onboarding-brief`, {
|
|
28
|
+
signal: controller.signal,
|
|
29
|
+
});
|
|
30
|
+
if (response.ok) {
|
|
31
|
+
const payload = await response.json();
|
|
32
|
+
if (payload?.markdown) {
|
|
33
|
+
return { markdown: payload.markdown, source: 'server' };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} finally {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// fall through to the bundled copy
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
return { markdown: readFileSync(BUNDLED_BRIEF_PATH, 'utf-8'), source: 'bundled' };
|
|
45
|
+
} catch {
|
|
46
|
+
return { markdown: null, source: null };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function requestSignupLink(apiBase, { email, useCases }) {
|
|
51
|
+
const response = await fetch(`${apiBase.replace(/\/$/, '')}/signup/agent`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: { 'Content-Type': 'application/json' },
|
|
54
|
+
body: JSON.stringify({
|
|
55
|
+
email,
|
|
56
|
+
...(useCases && useCases.length ? { preferred_use_cases: useCases } : {}),
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
const payload = await response.json().catch(() => null);
|
|
60
|
+
|
|
61
|
+
if (response.status === 429) {
|
|
62
|
+
// Requesting another link invalidates the previous one, so retrying is
|
|
63
|
+
// actively harmful. Surface the cooldown and stop.
|
|
64
|
+
throw new CliError({
|
|
65
|
+
code: 'signup_throttled',
|
|
66
|
+
message: payload?.message || 'A sign-in link was just sent to this address.',
|
|
67
|
+
exitCode: EXIT_CODES.usage,
|
|
68
|
+
details: payload || {},
|
|
69
|
+
hints: [
|
|
70
|
+
{
|
|
71
|
+
command: 'Open the newest Notis email on this machine',
|
|
72
|
+
reason: 'Requesting another link makes earlier links stop working',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
throw new CliError({
|
|
80
|
+
code: 'signup_failed',
|
|
81
|
+
message: payload?.message || `Signup failed with status ${response.status}`,
|
|
82
|
+
exitCode: EXIT_CODES.backend,
|
|
83
|
+
details: payload || {},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return payload || {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function startHandler(ctx) {
|
|
91
|
+
const { runtime, options, output } = ctx;
|
|
92
|
+
const apiBase = runtime.apiBase;
|
|
93
|
+
if (runtime.credentialKind === 'oauth') {
|
|
94
|
+
try {
|
|
95
|
+
await ensureFreshOAuthCredential(runtime);
|
|
96
|
+
} catch {
|
|
97
|
+
// A failed refresh is equivalent to no usable session here. Interactive
|
|
98
|
+
// starts may still authorize again; brief-only and agent runs surface
|
|
99
|
+
// the normal authentication recovery below.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
let authenticated = Boolean(runtime.jwt)
|
|
103
|
+
&& !credentialIsExpired(
|
|
104
|
+
runtime,
|
|
105
|
+
getProfile(loadConfig(runtime.worktreeRuntime), runtime.profileName),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Safe to re-run: an already-authenticated machine skips straight to the brief.
|
|
109
|
+
// Agents retry commands, and a second signup would invalidate the first email.
|
|
110
|
+
if (authenticated || options.briefOnly) {
|
|
111
|
+
if (!authenticated) {
|
|
112
|
+
const recovery = getDesktopAuthRecovery(runtime, { mode: 'missing' });
|
|
113
|
+
throw new CliError({
|
|
114
|
+
code: 'auth_missing',
|
|
115
|
+
message: 'This machine is not signed in to Notis yet.',
|
|
116
|
+
exitCode: EXIT_CODES.auth,
|
|
117
|
+
hints: recovery.hints,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const brief = await fetchBrief(apiBase, runtime.timeoutMs);
|
|
121
|
+
return output.emitSuccess({
|
|
122
|
+
command: 'start',
|
|
123
|
+
data: { authenticated: true, brief: brief.markdown, brief_source: brief.source },
|
|
124
|
+
humanSummary: 'Notis CLI is authenticated on this machine.',
|
|
125
|
+
renderHuman: () => brief.markdown || 'Notis CLI is authenticated.',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
!options.email
|
|
131
|
+
&& !runtime.agentMode
|
|
132
|
+
&& !runtime.nonInteractive
|
|
133
|
+
&& options.wait !== false
|
|
134
|
+
) {
|
|
135
|
+
let authorization;
|
|
136
|
+
let authorizationError;
|
|
137
|
+
try {
|
|
138
|
+
authorization = await loginWithOAuth(runtime, { browser: true }, output);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
// Keep the original OAuth failure while checking whether another process
|
|
141
|
+
// completed authentication before this attempt failed.
|
|
142
|
+
authorizationError = error;
|
|
143
|
+
authorization = null;
|
|
144
|
+
}
|
|
145
|
+
if (authorization?.agentAuthorization) {
|
|
146
|
+
return output.emitSuccess({
|
|
147
|
+
command: 'start',
|
|
148
|
+
data: { authenticated: false, ...authorization.agentAuthorization },
|
|
149
|
+
humanSummary: 'Open the authorization URL to finish signing in.',
|
|
150
|
+
renderHuman: () => `Authorize Notis CLI: ${authorization.agentAuthorization.authorize_url}`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// A failed authorization leaves the stale credential that got us here in
|
|
154
|
+
// place, so re-apply the same expiry test used above rather than reporting
|
|
155
|
+
// a machine as signed in on the strength of a dead token.
|
|
156
|
+
authenticated = Boolean(runtime.jwt)
|
|
157
|
+
&& !credentialIsExpired(runtime, getProfile(loadConfig(runtime.worktreeRuntime), runtime.profileName));
|
|
158
|
+
if (authenticated) {
|
|
159
|
+
const brief = await fetchBrief(apiBase, runtime.timeoutMs);
|
|
160
|
+
return output.emitSuccess({
|
|
161
|
+
command: 'start',
|
|
162
|
+
data: {
|
|
163
|
+
authenticated: true,
|
|
164
|
+
credential_source: runtime.credentialKind,
|
|
165
|
+
brief: brief.markdown,
|
|
166
|
+
brief_source: brief.source,
|
|
167
|
+
},
|
|
168
|
+
humanSummary: 'Notis CLI is authenticated. Follow the onboarding brief below.',
|
|
169
|
+
renderHuman: () => brief.markdown || 'Notis CLI is authenticated.',
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
if (authorizationError) {
|
|
173
|
+
throw authorizationError;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!options.email) {
|
|
178
|
+
// The one point in the flow that has to stop and talk to the human.
|
|
179
|
+
throw new CliError({
|
|
180
|
+
code: 'signup_email_required',
|
|
181
|
+
message: 'An email address is required to create or access a Notis account.',
|
|
182
|
+
exitCode: EXIT_CODES.usage,
|
|
183
|
+
hints: [
|
|
184
|
+
{
|
|
185
|
+
command: 'Ask the user for their email address, then rerun with --email <address>',
|
|
186
|
+
reason: 'Notis sends a sign-in link there to prove they own the address',
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const signup = await requestSignupLink(apiBase, {
|
|
193
|
+
email: options.email,
|
|
194
|
+
useCases: options.useCase,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Commander stores the negatable `--no-wait` flag as `options.wait === false`
|
|
198
|
+
// and never sets `options.noWait` — the same footgun already fixed for
|
|
199
|
+
// `--no-open` in apps.js. Reading `noWait` made the flag a no-op, so an agent
|
|
200
|
+
// that asked not to wait blocked for the full timeout instead.
|
|
201
|
+
if (options.wait === false) {
|
|
202
|
+
return output.emitSuccess({
|
|
203
|
+
command: 'start',
|
|
204
|
+
data: { authenticated: false, next_action: 'open_email_link', ...signup },
|
|
205
|
+
humanSummary: `A sign-in link is on its way to ${options.email}.`,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const timeoutMs = Number(options.waitTimeoutMs) > 0 ? Number(options.waitTimeoutMs) : 300_000;
|
|
210
|
+
const jwt = await waitForDesktopAuth({
|
|
211
|
+
loadConfig,
|
|
212
|
+
getJwt: (config, profileName) => {
|
|
213
|
+
const profile = getProfile(config, profileName);
|
|
214
|
+
if (
|
|
215
|
+
profile.jwt
|
|
216
|
+
&& !credentialIsExpired(
|
|
217
|
+
{ credentialKind: 'desktop', jwt: profile.jwt },
|
|
218
|
+
profile,
|
|
219
|
+
)
|
|
220
|
+
) {
|
|
221
|
+
return profile.jwt;
|
|
222
|
+
}
|
|
223
|
+
if (
|
|
224
|
+
profile.oauth_access_token
|
|
225
|
+
&& !credentialIsExpired(
|
|
226
|
+
{ credentialKind: 'oauth', jwt: profile.oauth_access_token },
|
|
227
|
+
profile,
|
|
228
|
+
)
|
|
229
|
+
) {
|
|
230
|
+
return profile.oauth_access_token;
|
|
231
|
+
}
|
|
232
|
+
return undefined;
|
|
233
|
+
},
|
|
234
|
+
isJwtExpired: () => false,
|
|
235
|
+
profileName: runtime.profileName,
|
|
236
|
+
timeoutMs,
|
|
237
|
+
onTick: (remaining) => {
|
|
238
|
+
if (runtime.outputMode !== 'json' && remaining % 15_000 < 2_000) {
|
|
239
|
+
output.note?.(`Waiting for Notis Desktop to sign in (${Math.round(remaining / 1000)}s left)...`);
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (!jwt) {
|
|
245
|
+
throw new CliError({
|
|
246
|
+
code: 'auth_timeout',
|
|
247
|
+
message: 'Timed out waiting for Notis Desktop to authenticate the CLI.',
|
|
248
|
+
exitCode: EXIT_CODES.auth,
|
|
249
|
+
details: { desktop_download_url: signup.desktop_download_url || null },
|
|
250
|
+
hints: [
|
|
251
|
+
{
|
|
252
|
+
command: `Install Notis Desktop: ${signup.desktop_download_url || 'https://notis.ai/channels/desktop-app'}`,
|
|
253
|
+
reason: 'The desktop app is what writes the CLI credential',
|
|
254
|
+
},
|
|
255
|
+
{ command: 'notis login', reason: 'Authorize this machine directly in a browser' },
|
|
256
|
+
{ command: 'notis start --brief-only', reason: 'Resume once authentication is complete' },
|
|
257
|
+
{ command: 'notis doctor', reason: 'Re-check config, auth, and API reachability' },
|
|
258
|
+
],
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const brief = await fetchBrief(apiBase, runtime.timeoutMs);
|
|
263
|
+
return output.emitSuccess({
|
|
264
|
+
command: 'start',
|
|
265
|
+
data: { authenticated: true, brief: brief.markdown, brief_source: brief.source },
|
|
266
|
+
humanSummary: 'Notis CLI is authenticated. Follow the onboarding brief below.',
|
|
267
|
+
renderHuman: () => brief.markdown || 'Notis CLI is authenticated.',
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export const onboardingCommandSpecs = [
|
|
272
|
+
{
|
|
273
|
+
command_path: ['start'],
|
|
274
|
+
summary: 'Create or access a Notis account and authorize the CLI.',
|
|
275
|
+
when_to_use:
|
|
276
|
+
'Run this first on a new machine, before anything that needs auth. Safe to re-run: an already-signed-in machine just reprints the onboarding brief.',
|
|
277
|
+
args_schema: {
|
|
278
|
+
arguments: [],
|
|
279
|
+
options: [
|
|
280
|
+
{ flags: '--email <email>', description: 'Email to create or sign in the Notis account with.' },
|
|
281
|
+
{ flags: '--use-case <slug>', description: 'Primary use case (repeatable).', collect: true },
|
|
282
|
+
{
|
|
283
|
+
flags: '--wait-timeout-ms <n>',
|
|
284
|
+
description: 'How long to wait for the desktop sign-in (default 300000).',
|
|
285
|
+
},
|
|
286
|
+
{ flags: '--no-wait', description: 'Send the link and exit without waiting.' },
|
|
287
|
+
{ flags: '--brief-only', description: 'Print the onboarding brief for an already-signed-in profile.' },
|
|
288
|
+
],
|
|
289
|
+
},
|
|
290
|
+
examples: [
|
|
291
|
+
'notis start --email you@example.com',
|
|
292
|
+
'notis start --brief-only',
|
|
293
|
+
'notis start --email you@example.com --json',
|
|
294
|
+
],
|
|
295
|
+
output_schema:
|
|
296
|
+
'Returns {authenticated, brief, brief_source} once signed in, or {next_action, cooldown_seconds, desktop_download_url} while waiting.',
|
|
297
|
+
mutates: true,
|
|
298
|
+
idempotent: true,
|
|
299
|
+
require_auth: false,
|
|
300
|
+
related_commands: ['notis doctor', 'notis tools link'],
|
|
301
|
+
backend_call: { type: 'http', name: 'POST /signup/agent' },
|
|
302
|
+
handler: startHandler,
|
|
303
|
+
},
|
|
304
|
+
];
|
|
@@ -453,8 +453,12 @@ async function toolsExecHandler(ctx) {
|
|
|
453
453
|
arguments_: {
|
|
454
454
|
tools: [{ tool_slug: requestedToolName, arguments: args }],
|
|
455
455
|
},
|
|
456
|
-
|
|
457
|
-
|
|
456
|
+
// The server is authoritative about provider/MCP effects. A tool name that
|
|
457
|
+
// looks like a read is not proof of read-only behavior, so generic
|
|
458
|
+
// executions always carry a key and use mutation-safe transport retries.
|
|
459
|
+
// Proven reads are still executed in the read lane by hosted MCP.
|
|
460
|
+
mutating: true,
|
|
461
|
+
idempotencyKey,
|
|
458
462
|
fileBindings,
|
|
459
463
|
});
|
|
460
464
|
ctx.output.emitProgress({
|
|
@@ -470,8 +474,8 @@ async function toolsExecHandler(ctx) {
|
|
|
470
474
|
requestId: result.requestId,
|
|
471
475
|
meta: {
|
|
472
476
|
mutating: targetMutating,
|
|
473
|
-
mutation_classification: targetMutating === null ? 'unknown' : '
|
|
474
|
-
idempotency_key:
|
|
477
|
+
mutation_classification: targetMutating === null ? 'unknown' : 'name_hint_only',
|
|
478
|
+
idempotency_key: idempotencyKey,
|
|
475
479
|
},
|
|
476
480
|
renderHuman: () => JSON.stringify(result.payload, null, 2),
|
|
477
481
|
});
|
|
@@ -288,12 +288,13 @@ function renderHarnessHtml({ state, manifest, appConfig, route, harnessOptions,
|
|
|
288
288
|
/**
|
|
289
289
|
* Start the dev server for one or more apps.
|
|
290
290
|
*
|
|
291
|
-
* @param {{apps: Array<{slug: string, projectDir: string, appId?: string, targetAppId?: string, userId?: string}>, port: number, watch?: boolean, harness?: { mode?: string, apiBase?: string, jwt?: string }, log?: (m: string) => void, logError?: (m: string) => void}} options
|
|
291
|
+
* @param {{apps: Array<{slug: string, projectDir: string, appId?: string, targetAppId?: string, userId?: string}>, port: number, watch?: boolean, sessionsFilePath?: string, harness?: { mode?: string, apiBase?: string, jwt?: string }, log?: (m: string) => void, logError?: (m: string) => void}} options
|
|
292
292
|
*/
|
|
293
293
|
export async function startAppDevServer({
|
|
294
294
|
apps,
|
|
295
295
|
port,
|
|
296
296
|
watch = true,
|
|
297
|
+
sessionsFilePath,
|
|
297
298
|
harness = {},
|
|
298
299
|
log = (msg) => process.stdout.write(`${msg}\n`),
|
|
299
300
|
logError = (msg) => process.stderr.write(`${msg}\n`),
|
|
@@ -466,7 +467,7 @@ export async function startAppDevServer({
|
|
|
466
467
|
devSlug: state.slug,
|
|
467
468
|
targetAppId: appId,
|
|
468
469
|
lastHeartbeatAt: now,
|
|
469
|
-
});
|
|
470
|
+
}, sessionsFilePath);
|
|
470
471
|
const response = {
|
|
471
472
|
ok: true,
|
|
472
473
|
app_id: appId,
|