@notis_ai/cli 0.2.6 → 0.2.8
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 +17 -0
- 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-database/packages/sdk/package.json +36 -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-database/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- 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/useBackend.ts +41 -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-database/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/index.ts +83 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/runtime.ts +220 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-database/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-database/packages/sdk/tsconfig.json +15 -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/components.json +20 -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 -6
- package/package.json +2 -1
- package/skills/notis-apps/SKILL.md +449 -144
- package/skills/notis-apps/cli.md +15 -0
- package/skills/notis-cli/SKILL.md +8 -4
- package/skills/notis-onboarding/BRIEF.md +93 -0
- package/src/cli.js +52 -7
- package/src/command-specs/apps.js +154 -21
- package/src/command-specs/diagnostics.js +674 -0
- package/src/command-specs/helpers.js +4 -1
- package/src/command-specs/index.js +8 -0
- package/src/command-specs/onboarding.js +216 -0
- package/src/command-specs/smoke.js +386 -0
- package/src/command-specs/tools.js +189 -5
- package/src/runtime/app-platform.js +65 -4
- package/src/runtime/desktop-auth.js +66 -17
- package/src/runtime/errors.js +1 -0
- package/src/runtime/output.js +21 -3
- package/src/runtime/profiles.js +214 -18
- package/src/runtime/telemetry.js +92 -0
- package/src/runtime/transport.js +29 -1
- package/template/.harness/index.html.tmpl +30 -2
- package/template/packages/sdk/src/config.ts +38 -1
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
validateArguments,
|
|
18
18
|
} from './helpers.js';
|
|
19
19
|
|
|
20
|
-
async function resolveJsonInput(value, label) {
|
|
20
|
+
export async function resolveJsonInput(value, label) {
|
|
21
21
|
if (!value) return undefined;
|
|
22
22
|
|
|
23
23
|
if (value === '-') {
|
|
@@ -39,6 +39,159 @@ async function resolveJsonInput(value, label) {
|
|
|
39
39
|
return parseJson(value, label);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
const READ_ACTIONS = new Set([
|
|
43
|
+
'CHECK',
|
|
44
|
+
'DESCRIBE',
|
|
45
|
+
'DOWNLOAD',
|
|
46
|
+
'FETCH',
|
|
47
|
+
'FIND',
|
|
48
|
+
'GET',
|
|
49
|
+
'INSPECT',
|
|
50
|
+
'LIST',
|
|
51
|
+
'LOOKUP',
|
|
52
|
+
'PREVIEW',
|
|
53
|
+
'QUERY',
|
|
54
|
+
'READ',
|
|
55
|
+
'SEARCH',
|
|
56
|
+
'STATUS',
|
|
57
|
+
'VERIFY',
|
|
58
|
+
]);
|
|
59
|
+
const WRITE_ACTIONS = new Set([
|
|
60
|
+
'AUTHENTIFY',
|
|
61
|
+
'CANCEL',
|
|
62
|
+
'CONNECT',
|
|
63
|
+
'COPY',
|
|
64
|
+
'CREATE',
|
|
65
|
+
'DELETE',
|
|
66
|
+
'DEPLOY',
|
|
67
|
+
'DISCARD',
|
|
68
|
+
'EDIT',
|
|
69
|
+
'GRANT',
|
|
70
|
+
'INCREMENT',
|
|
71
|
+
'INSERT',
|
|
72
|
+
'LINK',
|
|
73
|
+
'MOVE',
|
|
74
|
+
'POST',
|
|
75
|
+
'PUBLISH',
|
|
76
|
+
'REFUND',
|
|
77
|
+
'REMOVE',
|
|
78
|
+
'RENAME',
|
|
79
|
+
'SAVE',
|
|
80
|
+
'SEND',
|
|
81
|
+
'SUBMIT',
|
|
82
|
+
'UPDATE',
|
|
83
|
+
'UPLOAD',
|
|
84
|
+
'UPSERT',
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
function splitSqlStatements(query) {
|
|
88
|
+
const statements = [];
|
|
89
|
+
let current = '';
|
|
90
|
+
let quote = null;
|
|
91
|
+
let dollarTag = null;
|
|
92
|
+
for (let index = 0; index < query.length; index += 1) {
|
|
93
|
+
const char = query[index];
|
|
94
|
+
if (dollarTag) {
|
|
95
|
+
if (query.startsWith(dollarTag, index)) {
|
|
96
|
+
current += dollarTag;
|
|
97
|
+
index += dollarTag.length - 1;
|
|
98
|
+
dollarTag = null;
|
|
99
|
+
} else {
|
|
100
|
+
current += char;
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (quote) {
|
|
105
|
+
current += char;
|
|
106
|
+
if (char === quote) {
|
|
107
|
+
if (query[index + 1] === quote) {
|
|
108
|
+
current += query[index + 1];
|
|
109
|
+
index += 1;
|
|
110
|
+
} else {
|
|
111
|
+
quote = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (char === "'" || char === '"') {
|
|
117
|
+
quote = char;
|
|
118
|
+
current += char;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (char === '$') {
|
|
122
|
+
const match = query.slice(index).match(/^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/);
|
|
123
|
+
if (match) {
|
|
124
|
+
dollarTag = match[0];
|
|
125
|
+
current += dollarTag;
|
|
126
|
+
index += dollarTag.length - 1;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (char === ';') {
|
|
131
|
+
if (current.trim()) statements.push(current.trim());
|
|
132
|
+
current = '';
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
current += char;
|
|
136
|
+
}
|
|
137
|
+
if (current.trim()) statements.push(current.trim());
|
|
138
|
+
return statements;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function classifySqlMutation(query) {
|
|
142
|
+
const statements = splitSqlStatements(query);
|
|
143
|
+
if (statements.length > 1) {
|
|
144
|
+
const classifications = statements.map(classifySqlMutation);
|
|
145
|
+
if (classifications.includes(true)) return true;
|
|
146
|
+
if (classifications.every((classification) => classification === false)) return false;
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const statement = statements[0] || '';
|
|
150
|
+
if (statement) {
|
|
151
|
+
const normalizedStatement = statement.toUpperCase();
|
|
152
|
+
if (/^EXPLAIN\b/.test(normalizedStatement)) {
|
|
153
|
+
const executes = /^EXPLAIN\s+(?:\([^)]*\bANALYZE\b[^)]*\)\s*|ANALYZE\s+)/.test(normalizedStatement);
|
|
154
|
+
if (!executes) return false;
|
|
155
|
+
return /\b(ALTER|CREATE|DELETE|DROP|GRANT|INSERT|MERGE|REVOKE|TRUNCATE|UPDATE)\b/.test(normalizedStatement);
|
|
156
|
+
}
|
|
157
|
+
if (/^SHOW\b/.test(normalizedStatement)) return false;
|
|
158
|
+
// PostgreSQL SELECT can call side-effecting functions. Without catalog
|
|
159
|
+
// knowledge it is not provably read-only, so preserve idempotency and report
|
|
160
|
+
// an unknown classification instead of claiming mutating=false.
|
|
161
|
+
if (/^SELECT\b/.test(normalizedStatement)) return null;
|
|
162
|
+
if (/^WITH\b/.test(normalizedStatement)) {
|
|
163
|
+
return /\b(ALTER|CREATE|DELETE|DROP|GRANT|INSERT|MERGE|REVOKE|TRUNCATE|UPDATE)\b/.test(normalizedStatement)
|
|
164
|
+
? true
|
|
165
|
+
: null;
|
|
166
|
+
}
|
|
167
|
+
if (/^(ALTER|CREATE|DELETE|DROP|GRANT|INSERT|MERGE|REVOKE|TRUNCATE|UPDATE)\b/.test(normalizedStatement)) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function classifyToolMutation(toolName, args = {}) {
|
|
175
|
+
const normalized = localNotisToolSlug(toolName).toUpperCase();
|
|
176
|
+
if (normalized.includes('EXECUTE_SQL') && typeof args.query === 'string') {
|
|
177
|
+
const query = args.query
|
|
178
|
+
.replace(/\/\*[\s\S]*?\*\//g, ' ')
|
|
179
|
+
.replace(/--[^\n]*/g, ' ')
|
|
180
|
+
.trim();
|
|
181
|
+
const sqlClassification = classifySqlMutation(query);
|
|
182
|
+
if (sqlClassification !== null) return sqlClassification;
|
|
183
|
+
}
|
|
184
|
+
if (/(?:^|_)(?:FIND|GET|LOOKUP|SEARCH)_OR_(?:CREATE|INSERT|SAVE|UPDATE|UPSERT)(?:_|$)/.test(normalized)) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
const tokens = normalized.split(/[^A-Z0-9]+/).filter(Boolean);
|
|
188
|
+
for (const token of tokens) {
|
|
189
|
+
if (WRITE_ACTIONS.has(token)) return true;
|
|
190
|
+
if (READ_ACTIONS.has(token)) return false;
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
42
195
|
const LONG_RUNNING_TOOL_TIMEOUT_MS = 600000;
|
|
43
196
|
const LONG_RUNNING_TOOL_NAMES = new Set([
|
|
44
197
|
'LOCAL_NOTIS_GENERATE_IMAGE_OPENAI',
|
|
@@ -67,7 +220,7 @@ function guessContentType(filePath) {
|
|
|
67
220
|
return EXTENSION_CONTENT_TYPES.get(lower.slice(dotIndex)) || 'application/octet-stream';
|
|
68
221
|
}
|
|
69
222
|
|
|
70
|
-
async function hashFileSha256(localPath) {
|
|
223
|
+
export async function hashFileSha256(localPath) {
|
|
71
224
|
const hash = createHash('sha256');
|
|
72
225
|
for await (const chunk of createReadStream(localPath)) {
|
|
73
226
|
hash.update(chunk);
|
|
@@ -123,7 +276,7 @@ async function parseFileBindingSpec(spec, index) {
|
|
|
123
276
|
};
|
|
124
277
|
}
|
|
125
278
|
|
|
126
|
-
async function parseFileBindings(rawFileOptions) {
|
|
279
|
+
export async function parseFileBindings(rawFileOptions) {
|
|
127
280
|
const values = Array.isArray(rawFileOptions)
|
|
128
281
|
? rawFileOptions
|
|
129
282
|
: (rawFileOptions ? [rawFileOptions] : []);
|
|
@@ -227,7 +380,12 @@ async function toolsDescribeHandler(ctx) {
|
|
|
227
380
|
|
|
228
381
|
async function toolsExecHandler(ctx) {
|
|
229
382
|
const requestedToolName = localNotisToolSlug(ctx.args.toolName);
|
|
383
|
+
let targetMutating = classifyToolMutation(requestedToolName);
|
|
230
384
|
ensureLongRunningToolTimeout(ctx, requestedToolName);
|
|
385
|
+
ctx.output.emitProgress({
|
|
386
|
+
phase: 'prepare',
|
|
387
|
+
message: `Resolving ${requestedToolName}`,
|
|
388
|
+
});
|
|
231
389
|
const fileBindings = await parseFileBindings(ctx.options.file);
|
|
232
390
|
|
|
233
391
|
if (ctx.options.getSchema) {
|
|
@@ -243,10 +401,16 @@ async function toolsExecHandler(ctx) {
|
|
|
243
401
|
});
|
|
244
402
|
}
|
|
245
403
|
|
|
246
|
-
|
|
404
|
+
if (ctx.options.argumentsFile && ctx.options.arguments) {
|
|
405
|
+
throw usageError('Use either --arguments or --arguments-file, not both.');
|
|
406
|
+
}
|
|
407
|
+
const rawArguments = ctx.options.argumentsFile
|
|
408
|
+
? `@${ctx.options.argumentsFile}`
|
|
409
|
+
: (ctx.options.arguments || '{}');
|
|
247
410
|
const args = rawArguments === '-' || rawArguments.startsWith('@')
|
|
248
411
|
? await resolveJsonInput(rawArguments, 'arguments') || {}
|
|
249
412
|
: parseMaybeJson(rawArguments, 'arguments') || {};
|
|
413
|
+
targetMutating = classifyToolMutation(requestedToolName, args);
|
|
250
414
|
|
|
251
415
|
if (ctx.options.dryRun) {
|
|
252
416
|
if (fileBindings.length) {
|
|
@@ -278,6 +442,10 @@ async function toolsExecHandler(ctx) {
|
|
|
278
442
|
}
|
|
279
443
|
|
|
280
444
|
const idempotencyKey = nextIdempotencyKey(ctx.globalOptions);
|
|
445
|
+
ctx.output.emitProgress({
|
|
446
|
+
phase: 'execute',
|
|
447
|
+
message: `Calling ${requestedToolName}`,
|
|
448
|
+
});
|
|
281
449
|
|
|
282
450
|
const result = await runToolCommand({
|
|
283
451
|
runtime: ctx.runtime,
|
|
@@ -285,16 +453,30 @@ async function toolsExecHandler(ctx) {
|
|
|
285
453
|
arguments_: {
|
|
286
454
|
tools: [{ tool_slug: requestedToolName, arguments: args }],
|
|
287
455
|
},
|
|
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.
|
|
288
460
|
mutating: true,
|
|
289
461
|
idempotencyKey,
|
|
290
462
|
fileBindings,
|
|
291
463
|
});
|
|
464
|
+
ctx.output.emitProgress({
|
|
465
|
+
phase: 'complete',
|
|
466
|
+
message: `Finished ${requestedToolName}`,
|
|
467
|
+
requestId: result.requestId,
|
|
468
|
+
});
|
|
292
469
|
|
|
293
470
|
return ctx.output.emitSuccess({
|
|
294
471
|
command: ctx.spec.command_path.join(' '),
|
|
295
472
|
data: result.payload,
|
|
296
473
|
humanSummary: `Executed tool ${requestedToolName}`,
|
|
297
|
-
|
|
474
|
+
requestId: result.requestId,
|
|
475
|
+
meta: {
|
|
476
|
+
mutating: targetMutating,
|
|
477
|
+
mutation_classification: targetMutating === null ? 'unknown' : 'name_hint_only',
|
|
478
|
+
idempotency_key: idempotencyKey,
|
|
479
|
+
},
|
|
298
480
|
renderHuman: () => JSON.stringify(result.payload, null, 2),
|
|
299
481
|
});
|
|
300
482
|
}
|
|
@@ -441,6 +623,7 @@ export const toolsCommandSpecs = [
|
|
|
441
623
|
arguments: [{ token: '<tool-name>', description: 'Tool name returned by `notis tools search`.' }],
|
|
442
624
|
options: [
|
|
443
625
|
{ flags: '--arguments <json>', description: 'JSON object, @file path, or - for stdin.' },
|
|
626
|
+
{ flags: '--arguments-file <path>', description: 'Read the JSON arguments object from a file.' },
|
|
444
627
|
{ flags: '--file <argument-path=local-path>', description: 'Upload a local file into a file-uploadable tool argument. Repeatable.', collect: true },
|
|
445
628
|
{ flags: '--get-schema', description: 'Display the tool parameter schema without executing.' },
|
|
446
629
|
{ flags: '--dry-run', description: 'Validate arguments against the tool schema without executing.' },
|
|
@@ -452,6 +635,7 @@ export const toolsCommandSpecs = [
|
|
|
452
635
|
'notis tools exec LOCAL_NOTIS_DATABASE_QUERY --get-schema',
|
|
453
636
|
'notis tools exec LOCAL_NOTIS_DATABASE_QUERY --dry-run --arguments \'{"database_slug":"tasks","query":{}}\'',
|
|
454
637
|
'notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments @query.json',
|
|
638
|
+
'notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments-file query.json',
|
|
455
639
|
'notis tools exec LOCAL_NOTIS_DATABASE_QUERY --arguments - < query.json',
|
|
456
640
|
'notis tools exec composio-dropbox-upload_file --arguments \'{"path":"/target/in/dropbox.pdf"}\' --file content=./Invoice.pdf',
|
|
457
641
|
],
|
|
@@ -231,7 +231,13 @@ export function detectProjectWarnings(projectDir, appConfig = null) {
|
|
|
231
231
|
warnings.push('Missing Tailwind config.');
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
|
|
234
|
+
const readsWorkspaceDatabases =
|
|
235
|
+
appConfig?.capabilities?.workspaceDatabases === 'read';
|
|
236
|
+
if (
|
|
237
|
+
appConfig
|
|
238
|
+
&& !readsWorkspaceDatabases
|
|
239
|
+
&& (!Array.isArray(appConfig.databases) || appConfig.databases.length === 0)
|
|
240
|
+
) {
|
|
235
241
|
warnings.push('No database references declared in notis.config.ts.');
|
|
236
242
|
}
|
|
237
243
|
|
|
@@ -490,13 +496,24 @@ export async function runProjectScript({ projectDir, scriptName, env = {}, stdio
|
|
|
490
496
|
stdio,
|
|
491
497
|
env: { ...process.env, ...env },
|
|
492
498
|
});
|
|
499
|
+
let capturedOutput = '';
|
|
500
|
+
for (const stream of [child.stdout, child.stderr]) {
|
|
501
|
+
stream?.on('data', (chunk) => {
|
|
502
|
+
capturedOutput += chunk.toString();
|
|
503
|
+
});
|
|
504
|
+
}
|
|
493
505
|
child.on('error', rejectPromise);
|
|
494
506
|
child.on('exit', (code) => {
|
|
495
507
|
if (code === 0) {
|
|
496
508
|
resolvePromise();
|
|
497
509
|
return;
|
|
498
510
|
}
|
|
499
|
-
|
|
511
|
+
const detail = capturedOutput.trim();
|
|
512
|
+
rejectPromise(
|
|
513
|
+
new Error(
|
|
514
|
+
`npm run ${scriptName} failed with exit code ${code}${detail ? `:\n${detail}` : ''}`,
|
|
515
|
+
),
|
|
516
|
+
);
|
|
500
517
|
});
|
|
501
518
|
});
|
|
502
519
|
}
|
|
@@ -707,7 +724,14 @@ export function generateManifest(appConfig, projectDir) {
|
|
|
707
724
|
return entry;
|
|
708
725
|
});
|
|
709
726
|
|
|
710
|
-
|
|
727
|
+
// Database entries may be a bare slug (structure only) or an object opting
|
|
728
|
+
// into shipping rows to installers. Normalize to the manifest's snake_case.
|
|
729
|
+
const databases = (appConfig.databases || []).map((entry) => {
|
|
730
|
+
if (typeof entry === 'string') return entry;
|
|
731
|
+
if (!entry || typeof entry !== 'object' || !entry.slug) return entry;
|
|
732
|
+
const seedDocuments = entry.seedDocuments ?? entry.seed_documents;
|
|
733
|
+
return seedDocuments === true ? { slug: entry.slug, seed_documents: true } : entry.slug;
|
|
734
|
+
});
|
|
711
735
|
const categories = normalizeCategories(appConfig.categories || []);
|
|
712
736
|
const metadata = { screenshots: resolveListingScreenshots(projectDir, appConfig) };
|
|
713
737
|
const appSlug = safeKebab(appConfig.name);
|
|
@@ -786,12 +810,29 @@ export function generateManifest(appConfig, projectDir) {
|
|
|
786
810
|
css: 'bundle/app.css',
|
|
787
811
|
},
|
|
788
812
|
databases,
|
|
813
|
+
capabilities: normalizeAppCapabilities(appConfig.capabilities),
|
|
789
814
|
tools: appConfig.tools || [],
|
|
790
815
|
skills,
|
|
791
816
|
onboarding: appConfig.onboarding || null,
|
|
792
817
|
};
|
|
793
818
|
}
|
|
794
819
|
|
|
820
|
+
/**
|
|
821
|
+
* Keeps only capabilities the platform actually understands, at the exact
|
|
822
|
+
* values it accepts. An unknown key or value is dropped rather than passed
|
|
823
|
+
* through, so a typo can never reach the server as a permission grant.
|
|
824
|
+
*/
|
|
825
|
+
export function normalizeAppCapabilities(capabilities) {
|
|
826
|
+
if (!capabilities || typeof capabilities !== 'object') {
|
|
827
|
+
return {};
|
|
828
|
+
}
|
|
829
|
+
const normalized = {};
|
|
830
|
+
if (capabilities.workspaceDatabases === 'read') {
|
|
831
|
+
normalized.workspaceDatabases = 'read';
|
|
832
|
+
}
|
|
833
|
+
return normalized;
|
|
834
|
+
}
|
|
835
|
+
|
|
795
836
|
export function resolveConfiguredAppSkills(appConfig, projectDir) {
|
|
796
837
|
const configured = Array.isArray(appConfig.skills) ? appConfig.skills : [];
|
|
797
838
|
const projectRoot = resolve(projectDir);
|
|
@@ -831,13 +872,14 @@ export function resolveConfiguredAppSkills(appConfig, projectDir) {
|
|
|
831
872
|
/**
|
|
832
873
|
* Build the app bundle: generate entry file, run `vite build`, package into .notis/output/.
|
|
833
874
|
*/
|
|
834
|
-
export async function buildArtifact(projectDir) {
|
|
875
|
+
export async function buildArtifact(projectDir, { stdio = 'inherit' } = {}) {
|
|
835
876
|
await prepareArtifactBuild(projectDir);
|
|
836
877
|
|
|
837
878
|
// Run Vite build
|
|
838
879
|
await runProjectScript({
|
|
839
880
|
projectDir,
|
|
840
881
|
scriptName: 'build',
|
|
882
|
+
stdio,
|
|
841
883
|
});
|
|
842
884
|
|
|
843
885
|
// Verify the canonical `.notis/output/bundle` packaging contract.
|
|
@@ -948,8 +990,13 @@ export function scaffoldProject({ projectDir, appName, fromSlug = null }) {
|
|
|
948
990
|
if (existsSync(pkgPath)) {
|
|
949
991
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
950
992
|
pkg.name = appName.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
|
|
993
|
+
// A scaffold creates a new app identity, even when its source comes from a
|
|
994
|
+
// versioned Store example. New apps must therefore start their own release
|
|
995
|
+
// history instead of inheriting the example app's registry version.
|
|
996
|
+
pkg.notisAppVersion = '0.1.0';
|
|
951
997
|
ensureScaffoldLocalSdk(projectDir, pkg);
|
|
952
998
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
999
|
+
normalizeScaffoldLockfile(projectDir, pkg);
|
|
953
1000
|
}
|
|
954
1001
|
|
|
955
1002
|
// Update notis.config.ts with the app name
|
|
@@ -983,6 +1030,20 @@ export function scaffoldProject({ projectDir, appName, fromSlug = null }) {
|
|
|
983
1030
|
return { projectDir };
|
|
984
1031
|
}
|
|
985
1032
|
|
|
1033
|
+
function normalizeScaffoldLockfile(projectDir, pkg) {
|
|
1034
|
+
const lockPath = join(projectDir, 'package-lock.json');
|
|
1035
|
+
if (!existsSync(lockPath)) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
const lockfile = JSON.parse(readFileSync(lockPath, 'utf-8'));
|
|
1040
|
+
lockfile.name = pkg.name;
|
|
1041
|
+
if (lockfile.packages?.['']) {
|
|
1042
|
+
lockfile.packages[''].name = pkg.name;
|
|
1043
|
+
}
|
|
1044
|
+
writeFileSync(lockPath, JSON.stringify(lockfile, null, 2) + '\n');
|
|
1045
|
+
}
|
|
1046
|
+
|
|
986
1047
|
function ensureScaffoldLocalSdk(projectDir, pkg) {
|
|
987
1048
|
let shouldInstallLocalSdk = false;
|
|
988
1049
|
for (const dependencyGroup of ['dependencies', 'devDependencies']) {
|
|
@@ -24,7 +24,15 @@ function defaultDesktopAppName(apiBase) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Recovery hints for an unusable desktop-managed credential.
|
|
29
|
+
*
|
|
30
|
+
* `mode` distinguishes the two cases an agent has to act on differently:
|
|
31
|
+
* "expired" means a session existed and the desktop app can renew it, while
|
|
32
|
+
* "missing" means this machine was never signed in — telling that caller to
|
|
33
|
+
* renew something is misleading.
|
|
34
|
+
*/
|
|
35
|
+
export function getDesktopAuthRecovery(runtime, { mode = 'expired' } = {}) {
|
|
28
36
|
const desktopRunning = isPidRunning(runtime.desktopPid);
|
|
29
37
|
const appName = runtime.desktopAppName || defaultDesktopAppName(runtime.apiBase);
|
|
30
38
|
let command = 'Start the Notis desktop app';
|
|
@@ -32,22 +40,63 @@ export function getDesktopAuthRecovery(runtime) {
|
|
|
32
40
|
command = `open -a ${quoteShellArgument(appName)}`;
|
|
33
41
|
}
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
let reason;
|
|
44
|
+
if (mode === 'missing') {
|
|
45
|
+
reason = desktopRunning
|
|
46
|
+
? `Sign in to ${appName} to authenticate the CLI on this machine, then retry`
|
|
47
|
+
: `Install and sign in to ${appName} to authenticate the CLI on this machine, then retry`;
|
|
48
|
+
} else {
|
|
49
|
+
reason = desktopRunning
|
|
50
|
+
? `Bring ${appName} forward so it can renew CLI authentication, then retry`
|
|
51
|
+
: `Start ${appName} to renew expired CLI authentication, then retry`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const hints = [{ command, reason }];
|
|
55
|
+
if (mode === 'missing') {
|
|
56
|
+
// Leads the list: a machine that was never signed in may not even have an
|
|
57
|
+
// account yet, and `notis start` covers both cases.
|
|
58
|
+
hints.unshift({
|
|
59
|
+
command: 'notis start --email <address>',
|
|
60
|
+
reason: 'Create or access a Notis account and sign this machine in',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
hints.push({
|
|
64
|
+
command: 'notis doctor',
|
|
65
|
+
reason: 'Retry the auth and API checks after the desktop app is ready',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return { desktopRunning, appName, hints };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Block until the desktop app writes an unexpired JWT into the CLI profile.
|
|
73
|
+
*
|
|
74
|
+
* Polls the local config file only — never the server. The desktop app is what
|
|
75
|
+
* mints the credential, and `transport.js` re-reads the profile before every
|
|
76
|
+
* request, so this loop exists purely so the caller's process can wait rather
|
|
77
|
+
* than fail and make the user re-run the command.
|
|
78
|
+
*/
|
|
79
|
+
export async function waitForDesktopAuth({
|
|
80
|
+
loadConfig,
|
|
81
|
+
getJwt,
|
|
82
|
+
isJwtExpired,
|
|
83
|
+
profileName = 'default',
|
|
84
|
+
timeoutMs = 300_000,
|
|
85
|
+
intervalMs = 2_000,
|
|
86
|
+
onTick,
|
|
87
|
+
} = {}) {
|
|
88
|
+
const deadline = Date.now() + timeoutMs;
|
|
89
|
+
for (;;) {
|
|
90
|
+
const jwt = getJwt(loadConfig(), profileName);
|
|
91
|
+
if (jwt && !isJwtExpired(jwt)) {
|
|
92
|
+
return jwt;
|
|
93
|
+
}
|
|
94
|
+
if (Date.now() >= deadline) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (onTick) onTick(Math.max(0, deadline - Date.now()));
|
|
98
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
99
|
+
}
|
|
51
100
|
}
|
|
52
101
|
|
|
53
102
|
export function createExpiredAuthError(runtime) {
|
package/src/runtime/errors.js
CHANGED
package/src/runtime/output.js
CHANGED
|
@@ -22,6 +22,13 @@ function formatTable(rows, columns) {
|
|
|
22
22
|
return [header, divider, ...body].join('\n');
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function formatHint(hint) {
|
|
26
|
+
if (typeof hint?.message === 'string' && hint.message) {
|
|
27
|
+
return ` ${hint.message}`;
|
|
28
|
+
}
|
|
29
|
+
return ` ${hint?.command || ''} ${hint?.reason || ''}`.trimEnd();
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
function yamlScalar(value) {
|
|
26
33
|
if (value === null || value === undefined) {
|
|
27
34
|
return 'null';
|
|
@@ -94,6 +101,14 @@ export class OutputManager {
|
|
|
94
101
|
process.stderr.write(`${message}\n`);
|
|
95
102
|
}
|
|
96
103
|
|
|
104
|
+
emitProgress({ phase, message, requestId = null }) {
|
|
105
|
+
if (this.runtime.quiet) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const suffix = requestId ? ` (${requestId})` : '';
|
|
109
|
+
process.stderr.write(`[${phase}] ${message}${suffix}\n`);
|
|
110
|
+
}
|
|
111
|
+
|
|
97
112
|
emitSuccess({
|
|
98
113
|
ok = true,
|
|
99
114
|
command,
|
|
@@ -116,7 +131,10 @@ export class OutputManager {
|
|
|
116
131
|
meta: {
|
|
117
132
|
profile: this.runtime.profileName,
|
|
118
133
|
api_base: this.runtime.apiBase,
|
|
119
|
-
mutating:
|
|
134
|
+
mutating:
|
|
135
|
+
meta.mutating === true
|
|
136
|
+
? true
|
|
137
|
+
: (meta.mutating === false ? false : null),
|
|
120
138
|
...meta,
|
|
121
139
|
},
|
|
122
140
|
};
|
|
@@ -142,7 +160,7 @@ export class OutputManager {
|
|
|
142
160
|
}
|
|
143
161
|
|
|
144
162
|
if (hints.length) {
|
|
145
|
-
const lines = hints.map(
|
|
163
|
+
const lines = hints.map(formatHint);
|
|
146
164
|
process.stdout.write(`\nNext:\n${lines.join('\n')}\n`);
|
|
147
165
|
}
|
|
148
166
|
|
|
@@ -171,7 +189,7 @@ export class OutputManager {
|
|
|
171
189
|
|
|
172
190
|
process.stderr.write(`Error: ${error.message}\n`);
|
|
173
191
|
for (const hint of error.hints || []) {
|
|
174
|
-
process.stderr.write(
|
|
192
|
+
process.stderr.write(`${formatHint(hint)}\n`);
|
|
175
193
|
}
|
|
176
194
|
return error.exitCode || EXIT_CODES.unexpected;
|
|
177
195
|
}
|