@notis_ai/cli 0.2.0-beta.191.1 → 0.2.0-beta.196.1
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 +479 -80
- package/config/portal-route-roots.json +48 -0
- package/dist/agent-hooks/notis-agent-hook.mjs +29072 -2784
- package/dist/base-skills/notis-apps/SKILL.md +88 -67
- package/dist/base-skills/notis-apps/references/architecture.md +114 -167
- package/dist/base-skills/notis-apps/references/design.md +26 -107
- package/dist/base-skills/notis-apps/references/memory.md +58 -0
- package/dist/base-skills/notis-apps/references/reading.md +52 -74
- package/dist/base-skills/notis-apps/references/release.md +104 -142
- package/dist/base-skills/notis-apps/references/sdk.md +113 -60
- package/dist/base-skills/notis-apps/references/troubleshooting.md +22 -21
- package/dist/base-skills/notis-apps/references/views.md +142 -0
- package/dist/base-skills/notis-cli/SKILL.md +4 -4
- package/dist/base-skills/notis-cli/references/app-delivery.md +12 -11
- package/dist/base-skills/notis-cli/references/native-databases.md +6 -6
- package/dist/base-skills/notis-cli/references/tool-examples.md +4 -4
- package/dist/base-skills/notis-query/SKILL.md +34 -60
- package/dist/base-skills/notis-query/references/database-discovery.md +59 -69
- package/dist/base-skills/notis-query/references/documents.md +63 -50
- package/dist/base-skills/notis-query/references/query.md +57 -524
- package/dist/sdk/src/bundleModule.ts +79 -0
- package/dist/sdk/src/components/DocumentEditor.tsx +6 -3
- package/dist/sdk/src/components/DocumentPage.tsx +141 -0
- package/dist/sdk/src/components/RecordComponents.tsx +33 -0
- package/dist/sdk/src/components/RenderChartData.tsx +19 -0
- package/dist/sdk/src/config.ts +4 -26
- package/dist/sdk/src/documents.ts +2 -0
- package/dist/sdk/src/hooks/useAction.ts +28 -0
- package/dist/sdk/src/hooks/useCloudComputer.ts +35 -4
- package/dist/sdk/src/hooks/useCloudComputerShell.ts +114 -0
- package/dist/sdk/src/hooks/useHandover.ts +5 -4
- package/dist/sdk/src/hooks/useNativeDocumentBody.ts +50 -0
- package/dist/sdk/src/hooks/useNativeDocuments.ts +83 -0
- package/dist/sdk/src/hooks/useNotis.ts +3 -0
- package/dist/sdk/src/hooks/useNotisNavigation.ts +26 -11
- package/dist/sdk/src/hooks/useQuery.ts +7 -4
- package/dist/sdk/src/hooks/useViewParams.ts +121 -0
- package/dist/sdk/src/hooks/useViewerReads.ts +129 -0
- package/dist/sdk/src/index.ts +36 -0
- package/dist/sdk/src/interactions/shortcuts.tsx +17 -3
- package/dist/sdk/src/nativeData.ts +39 -0
- package/dist/sdk/src/presentation.tsx +82 -0
- package/dist/sdk/src/provider.tsx +9 -4
- package/dist/sdk/src/queryCache.ts +5 -3
- package/dist/sdk/src/queryErrors.ts +51 -0
- package/dist/sdk/src/runtime.ts +146 -8
- package/dist/sdk/src/space.ts +425 -0
- package/dist/sdk/src/styles.css +96 -72
- package/dist/sdk/src/vite.ts +11 -1
- package/dist/skill-sync/index.js +458 -68
- package/dist/skill-sync/index.js.map +4 -4
- package/dist/skill-sync-worker.mjs +451 -77
- package/dist/space-harness/host.js +66 -0
- package/dist/view-renderer/dist/host.css +1 -0
- package/dist/view-renderer/dist/host.js +1042 -0
- package/dist/view-renderer/dist/theme.css +73 -0
- package/dist/view-renderer/src/dom.js +180 -0
- package/dist/view-renderer/src/errors.js +8 -0
- package/dist/view-renderer/src/host.tsx +173 -0
- package/dist/view-renderer/src/index.js +4 -0
- package/dist/view-renderer/src/pool.js +76 -0
- package/dist/view-renderer/src/remote-broker.js +59 -0
- package/dist/view-renderer/src/renderer.js +201 -0
- package/package.json +11 -5
- package/skills/notis-apps/cli.md +270 -163
- package/skills/notis-cli/AGENT_INSTRUCTIONS.md +2 -2
- package/skills/notis-onboarding/BRIEF.md +7 -3
- package/src/command-specs/apps.js +1 -8
- package/src/command-specs/files.js +29 -0
- package/src/command-specs/helpers.js +1 -0
- package/src/command-specs/index.js +10 -4
- package/src/command-specs/skills.js +229 -0
- package/src/command-specs/spaces.js +572 -0
- package/src/command-specs/views.js +69 -0
- package/src/runtime/agent-browser.js +30 -7
- package/src/runtime/app-boundary-validator.js +7 -4
- package/src/runtime/app-data-literals.js +82 -0
- package/src/runtime/app-platform.js +321 -253
- package/src/runtime/confined-space-build.js +74 -0
- package/src/runtime/confined-space-verify.js +49 -0
- package/src/runtime/native-record-files.js +47 -0
- package/src/runtime/retired-sdk-files.js +20 -0
- package/src/runtime/skill-sync/cloud-client.ts +26 -4
- package/src/runtime/skill-sync/index.ts +154 -35
- package/src/runtime/skill-sync/local-scanner.ts +125 -17
- package/src/runtime/skill-sync/native-identity.ts +102 -0
- package/src/runtime/skill-sync/native-plan.ts +59 -0
- package/src/runtime/skill-sync/sync-plan.ts +16 -9
- package/src/runtime/skill-sync/types.ts +32 -6
- package/src/runtime/source-root.js +32 -0
- package/src/runtime/space-collection-scaffold.js +66 -0
- package/src/runtime/space-database-structures.js +229 -0
- package/src/runtime/space-fixture-broker.js +50 -0
- package/src/runtime/space-lock.js +337 -0
- package/src/runtime/space-platform.js +105 -0
- package/src/runtime/space-source.js +281 -0
- package/src/runtime/space-test-server.js +327 -0
- package/src/runtime/space-verification.js +89 -0
- package/src/runtime/space-verify-browser-worker.js +51 -0
- package/src/runtime/space-view-manifest.js +254 -0
- package/src/runtime/sync-skills.js +1 -1
- package/src/runtime/transport.js +8 -1
- package/src/runtime/view-render.js +49 -0
- package/src/space-harness-host.ts +103 -0
- package/template/app/globals.css +1 -0
- package/src/command-specs/reports.js +0 -97
|
@@ -94,9 +94,13 @@ through the connection and show them the actual result: today's calendar events,
|
|
|
94
94
|
their most recent email threads, the databases in their Notion. A connected
|
|
95
95
|
integration is a claim; their own data on screen is proof.
|
|
96
96
|
|
|
97
|
-
If they connect nothing,
|
|
98
|
-
|
|
99
|
-
`
|
|
97
|
+
If they connect nothing, you may offer a Space from the Space Store instead, but
|
|
98
|
+
only on an account that has the Store: it is not open to every account yet. Check
|
|
99
|
+
with `notis spaces store list` first. If it answers that Spaces or the Store are
|
|
100
|
+
not available for this account (`spaces_unavailable` or
|
|
101
|
+
`spaces_store_unavailable`), skip this fallback without retrying and go to
|
|
102
|
+
step 3. Otherwise install one with
|
|
103
|
+
`notis spaces store install <listing-id> --request-id <stable-id>`.
|
|
100
104
|
|
|
101
105
|
## 3. Finish onboarding
|
|
102
106
|
|
|
@@ -1257,22 +1257,15 @@ async function appsDeployHandler(ctx) {
|
|
|
1257
1257
|
try { release.close(); }
|
|
1258
1258
|
catch { warnings.push('The app was updated, but the temporary release directory needs local cleanup.'); }
|
|
1259
1259
|
|
|
1260
|
-
// One review report per skill this release changed; share the link when the user should review it.
|
|
1261
|
-
const skillReviews = (Array.isArray(result.payload.skill_reviews) ? result.payload.skill_reviews : [])
|
|
1262
|
-
.filter((review) => review && typeof review.review_url === 'string');
|
|
1263
1260
|
return ctx.output.emitSuccess({
|
|
1264
1261
|
command: ctx.spec.command_path.join(' '),
|
|
1265
1262
|
data: {
|
|
1266
1263
|
app_id: appId,
|
|
1267
1264
|
version: deployedVersion,
|
|
1268
1265
|
idempotency_key: idempotencyKey,
|
|
1269
|
-
...(skillReviews.length ? { skill_reviews: skillReviews } : {}),
|
|
1270
1266
|
},
|
|
1271
1267
|
warnings,
|
|
1272
|
-
humanSummary:
|
|
1273
|
-
`Deployed to app ${appId} (version ${deployedVersion})`,
|
|
1274
|
-
...skillReviews.map((review) => `Review ${review.skill} changes: ${review.review_url}`),
|
|
1275
|
-
].join('\n'),
|
|
1268
|
+
humanSummary: `Deployed to app ${appId} (version ${deployedVersion})`,
|
|
1276
1269
|
meta: { mutating: true, idempotency_key: idempotencyKey },
|
|
1277
1270
|
});
|
|
1278
1271
|
} finally {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { uploadNativeRecordFileWithCliRuntime } from '../runtime/native-record-files.js';
|
|
2
|
+
import { parseJson } from './helpers.js';
|
|
3
|
+
import { usageError } from '../runtime/errors.js';
|
|
4
|
+
|
|
5
|
+
export const filesCommandSpecs = [{
|
|
6
|
+
command_path: ['files', 'upload'],
|
|
7
|
+
summary: 'Upload a local file for one native record you can edit.',
|
|
8
|
+
when_to_use: 'Save file bytes without a cloud computer; attach the returned file reference through a normal revision-checked row update.',
|
|
9
|
+
args_schema: { arguments: [{ token: '<file>', key: 'file', description: 'Local regular file up to 8 MB.' }], options: [
|
|
10
|
+
{ flags: '--target <json>', description: 'Exact native target returned by schema discovery.' },
|
|
11
|
+
{ flags: '--record-key <id>', description: 'Stable record key to attach this file to.' },
|
|
12
|
+
{ flags: '--schema-revision <number>', description: 'Current native schema revision.' },
|
|
13
|
+
{ flags: '--request-id <uuid>', description: 'Stable UUID; reuse with identical bytes after an uncertain response.' },
|
|
14
|
+
{ flags: '--name <name>', description: 'Attachment name; defaults to the local basename.' },
|
|
15
|
+
{ flags: '--content-type <mime>', description: 'File MIME type; inferred from known extensions.' },
|
|
16
|
+
] },
|
|
17
|
+
examples: ['notis files upload ./report.html --target \'{"database_id":"<database-id>"}\' --record-key <record-key> --schema-revision 1 --request-id <uuid>'],
|
|
18
|
+
mutates: true, idempotent: true, require_auth: true,
|
|
19
|
+
backend_call: { type: 'http', path: '/portal_native_files/upload' },
|
|
20
|
+
async handler(ctx) {
|
|
21
|
+
if (!ctx.options.target || ctx.options.schemaRevision === undefined) throw usageError('Pass the discovered native --target and --schema-revision.');
|
|
22
|
+
const result = await uploadNativeRecordFileWithCliRuntime({ runtime: ctx.runtime, filePath: ctx.args.file,
|
|
23
|
+
target: parseJson(ctx.options.target, '--target'), recordKey: ctx.options.recordKey,
|
|
24
|
+
schemaRevision: Number(ctx.options.schemaRevision), requestId: ctx.options.requestId,
|
|
25
|
+
...(ctx.options.name ? { name: ctx.options.name } : {}), ...(ctx.options.contentType ? { contentType: ctx.options.contentType } : {}) });
|
|
26
|
+
return ctx.output.emitSuccess({ command: 'files upload', data: result,
|
|
27
|
+
humanSummary: 'Uploaded and verified the file. Attach the returned file reference with a revision-checked record update.', meta: { mutating: true } });
|
|
28
|
+
},
|
|
29
|
+
}];
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { reportsCommandSpecs } from './reports.js';
|
|
2
1
|
import { appsCommandSpecs } from './apps.js';
|
|
3
2
|
import { toolsCommandSpecs } from './tools.js';
|
|
4
3
|
import { metaCommandSpecs } from './meta.js';
|
|
@@ -10,14 +9,19 @@ import { profileCommandSpecs } from './profile.js';
|
|
|
10
9
|
import { handoverCommandSpecs } from './handover.js';
|
|
11
10
|
import { agentsCommandSpecs } from './agents.js';
|
|
12
11
|
import { skillsCommandSpecs } from './skills.js';
|
|
12
|
+
import { spacesCommandSpecs } from './spaces.js';
|
|
13
|
+
import { viewsCommandSpecs } from './views.js';
|
|
14
|
+
import { filesCommandSpecs } from './files.js';
|
|
13
15
|
|
|
14
16
|
export const GROUP_SUMMARIES = {
|
|
15
|
-
|
|
17
|
+
files: 'Upload local files for native records with verified byte receipts.',
|
|
18
|
+
views: 'Discover and render the views you can open.',
|
|
19
|
+
spaces: 'Build independent Spaces and their declared capabilities.',
|
|
16
20
|
apps: 'Develop, deploy, and submit Notis Apps.',
|
|
17
21
|
agents: 'Install Notis context into local coding agents.',
|
|
18
22
|
handover: 'Hand the branch you are on to a Notis agent, hosted or your own Codex/Claude.',
|
|
19
23
|
tools: 'Discover and execute generic tools exposed through Notis.',
|
|
20
|
-
skills: '
|
|
24
|
+
skills: 'List, edit and link Notis Skills, and keep local-agent skills synchronized.',
|
|
21
25
|
profile: 'Switch between signed-in accounts and their API endpoints.',
|
|
22
26
|
debug: 'Inspect effective runtime context, worker identity, and trace costs.',
|
|
23
27
|
smoke: 'Run deterministic connected-service smoke tests with guaranteed cleanup.',
|
|
@@ -29,8 +33,10 @@ export const COMMAND_SPECS = [
|
|
|
29
33
|
...onboardingCommandSpecs,
|
|
30
34
|
...agentsCommandSpecs,
|
|
31
35
|
...skillsCommandSpecs,
|
|
36
|
+
...spacesCommandSpecs,
|
|
37
|
+
...viewsCommandSpecs,
|
|
38
|
+
...filesCommandSpecs,
|
|
32
39
|
...appsCommandSpecs,
|
|
33
|
-
...reportsCommandSpecs,
|
|
34
40
|
...handoverCommandSpecs,
|
|
35
41
|
...toolsCommandSpecs,
|
|
36
42
|
...diagnosticCommandSpecs,
|
|
@@ -2,6 +2,81 @@ import { getJwtSubject } from '../runtime/profiles.js';
|
|
|
2
2
|
import { reconcileAllSkills } from '../runtime/sync-skills.js';
|
|
3
3
|
import { ensureFreshOAuthCredential } from '../runtime/oauth.js';
|
|
4
4
|
import { installSkillSyncService } from '../runtime/skill-sync-service.js';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
import { httpRequest } from '../runtime/transport.js';
|
|
8
|
+
import { usageError } from '../runtime/errors.js';
|
|
9
|
+
import { parseJson } from './helpers.js';
|
|
10
|
+
|
|
11
|
+
async function nativeSkillAuthoring(ctx, body, path = '/portal_skills/native-authoring') {
|
|
12
|
+
// A Space target, destination or link change touches Spaces: declare the protocol.
|
|
13
|
+
const spaceTarget = Boolean(body.target?.space_id || body.destination?.space_id || body.links);
|
|
14
|
+
const result = await httpRequest({ runtime: ctx.runtime, method: 'POST', path, body,
|
|
15
|
+
...(spaceTarget ? { spacesProtocol: 1 } : {}) });
|
|
16
|
+
return ctx.output.emitSuccess({ command: ctx.spec.command_path.join(' '), data: result.payload,
|
|
17
|
+
humanSummary: JSON.stringify(result.payload, null, 2),
|
|
18
|
+
meta: { mutating: body.operation !== 'read' && !body.dry_run, request_id: result.requestId } });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function exactKeys(value, keys) {
|
|
22
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
23
|
+
&& Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function skillTarget(value, updating) {
|
|
27
|
+
const direct = updating ? ['skill_id', 'access_revision'] : ['skill_id'];
|
|
28
|
+
const space = updating ? ['space_id', 'binding_id', 'binding_revision', 'expected_skill_id', 'space_revision'] : ['space_id', 'binding_id'];
|
|
29
|
+
if (!exactKeys(value, direct) && !exactKeys(value, space)) {
|
|
30
|
+
throw usageError('Use one exact direct or Space target from the Skill inventory or skills read.');
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function requestKey(ctx) {
|
|
36
|
+
const request = ctx.options.requestId || ctx.globalOptions.idempotencyKey;
|
|
37
|
+
if (typeof request !== 'string' || !request || request.length > 200) throw usageError('Pass --request-id <stable-key>.');
|
|
38
|
+
return request;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function linkLabel(link) {
|
|
42
|
+
return `${link.space_name || link.space_id}/${link.alias}${link.available === false ? ' (unavailable)' : ''}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One line per Skill: name, ID, kind, editability and its Space links. */
|
|
46
|
+
export function skillInventoryLines(page) {
|
|
47
|
+
const entries = Array.isArray(page?.skills) ? page.skills : [];
|
|
48
|
+
if (!entries.length) return 'No Skills match.';
|
|
49
|
+
const lines = entries.map(entry => {
|
|
50
|
+
const state = entry.editable ? 'editable' : `read-only${entry.read_only_reason ? ` (${entry.read_only_reason})` : ''}`;
|
|
51
|
+
const links = (entry.links || []).map(linkLabel);
|
|
52
|
+
return `${entry.name || entry.skill_id} ${entry.skill_id} ${entry.kind} ${state} links: ${links.length ? links.join(', ') : 'none'}`;
|
|
53
|
+
});
|
|
54
|
+
if (page.has_more && page.next) lines.push(`More Skills: rerun with --after ${page.next}`);
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** What a link change did and where the resource is listed now. */
|
|
59
|
+
export function linkChangeSummary(result) {
|
|
60
|
+
const parts = [];
|
|
61
|
+
const added = (result?.added || []).map(link => `${link.space_id}/${link.alias}`);
|
|
62
|
+
const removed = (result?.removed || []).map(link => `${link.space_id}/${link.alias}`);
|
|
63
|
+
if (added.length) parts.push(`added: ${added.join(', ')}`);
|
|
64
|
+
if (removed.length) parts.push(`removed: ${removed.join(', ')}`);
|
|
65
|
+
if (result?.became_standalone) parts.push('now your standalone Skill');
|
|
66
|
+
if (result?.replayed) parts.push('confirmed retry');
|
|
67
|
+
const current = (result?.links || []).map(linkLabel);
|
|
68
|
+
return `${parts.join('; ') || 'No change'}. Listed in: ${current.length ? current.join(', ') : 'none'}.`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function linkEntries(value, option, required, optional = []) {
|
|
72
|
+
const parsed = parseJson(value, option);
|
|
73
|
+
const valid = entry => entry && typeof entry === 'object' && !Array.isArray(entry)
|
|
74
|
+
&& required.every(key => key in entry) && Object.keys(entry).every(key => required.includes(key) || optional.includes(key));
|
|
75
|
+
if (!Array.isArray(parsed) || !parsed.length || !parsed.every(valid)) {
|
|
76
|
+
throw usageError(`${option} must be a JSON array of {${[...required, ...optional.map(key => `${key}?`)].join(', ')}} entries.`);
|
|
77
|
+
}
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
5
80
|
|
|
6
81
|
async function loadSkillSyncEngine() {
|
|
7
82
|
return import('../../dist/skill-sync/index.js');
|
|
@@ -48,6 +123,160 @@ export async function syncSkillsHandler(ctx, {
|
|
|
48
123
|
}
|
|
49
124
|
|
|
50
125
|
export const skillsCommandSpecs = [
|
|
126
|
+
{
|
|
127
|
+
command_path: ['skills', 'list'],
|
|
128
|
+
summary: 'List every Skill you can see, with its kind, editability and Space links.',
|
|
129
|
+
when_to_use: 'Find a Skill and its exact edit target before reading or editing it, or see what a Space and its sub-Spaces list. Links to Spaces you cannot open are omitted; an unavailable link gives no access.',
|
|
130
|
+
args_schema: { arguments: [], options: [
|
|
131
|
+
{ flags: '--space <id>', description: 'Only Skills listed by this Space and, by default, its sub-Spaces.' },
|
|
132
|
+
{ flags: '--no-child-spaces', description: 'With --space: direct links of that Space only.' },
|
|
133
|
+
{ flags: '--include-disabled', description: 'Include Skills you disabled.' },
|
|
134
|
+
{ flags: '--after <cursor>', description: 'Continue from the next cursor of the previous page.' },
|
|
135
|
+
{ flags: '--limit <n>', description: 'Page size between 1 and 100 (default 50).' },
|
|
136
|
+
] },
|
|
137
|
+
examples: ['notis skills list', 'notis skills list --space <space-id> --no-child-spaces --json'],
|
|
138
|
+
mutates: false, idempotent: true, require_auth: true,
|
|
139
|
+
backend_call: { type: 'http', path: '/portal_skills/inventory' },
|
|
140
|
+
async handler(ctx) {
|
|
141
|
+
const body = {};
|
|
142
|
+
if (ctx.options.space !== undefined) body.space_id = ctx.options.space;
|
|
143
|
+
if (ctx.options.childSpaces === false) {
|
|
144
|
+
if (body.space_id === undefined) throw usageError('--no-child-spaces needs --space <id>.');
|
|
145
|
+
body.include_child_spaces = false;
|
|
146
|
+
}
|
|
147
|
+
if (ctx.options.includeDisabled) body.include_disabled = true;
|
|
148
|
+
if (ctx.options.after !== undefined) body.after = ctx.options.after;
|
|
149
|
+
if (ctx.options.limit !== undefined) {
|
|
150
|
+
const limit = Number(ctx.options.limit);
|
|
151
|
+
if (!/^\d+$/.test(String(ctx.options.limit)) || limit < 1 || limit > 100) throw usageError('Pass --limit between 1 and 100.');
|
|
152
|
+
body.limit = limit;
|
|
153
|
+
}
|
|
154
|
+
const result = await httpRequest({ runtime: ctx.runtime, method: 'POST', path: '/portal_skills/inventory', body });
|
|
155
|
+
return ctx.output.emitSuccess({ command: 'skills list', data: result.payload,
|
|
156
|
+
humanSummary: skillInventoryLines(result.payload), meta: { mutating: false, request_id: result.requestId } });
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
command_path: ['skills', 'create'],
|
|
161
|
+
summary: 'Create a native Skill, standalone or linked to an editable Space.',
|
|
162
|
+
when_to_use: 'Create complete instructions or files without provider installation. Choose an optional Space destination, dry-run, and keep the exact request key on retry.',
|
|
163
|
+
args_schema: { arguments: [{ token: '<file>', key: 'file', description: 'JSON containing name, optional description, and skill_md or base64 files.' }],
|
|
164
|
+
options: [{ flags: '--request-id <key>', description: 'Stable creation intent key; reuse after an uncertain reply.' },
|
|
165
|
+
{ flags: '--dry-run', description: 'Validate without creating an identity or uploading files.' }] },
|
|
166
|
+
examples: ['notis skills create ./skill.json --request-id create-1 --dry-run'],
|
|
167
|
+
mutates: true, idempotent: true, require_auth: true,
|
|
168
|
+
backend_call: { type: 'http', path: '/portal_skills/native-create' },
|
|
169
|
+
handler(ctx) {
|
|
170
|
+
const payload = parseJson(readFileSync(resolve(ctx.args.file), 'utf8'), 'Skill creation file');
|
|
171
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)
|
|
172
|
+
|| Object.keys(payload).some(key => !['destination', 'name', 'description', 'skill_md', 'files'].includes(key))) {
|
|
173
|
+
throw usageError('Provide a name, complete instructions or files, and an optional current destination; never override ownership.');
|
|
174
|
+
}
|
|
175
|
+
const destination = payload.destination ?? { kind: 'standalone' };
|
|
176
|
+
if (!(exactKeys(destination, ['kind']) && destination.kind === 'standalone')
|
|
177
|
+
&& !exactKeys(destination, ['space_id', 'alias', 'revision'])) throw usageError('Choose a standalone destination or exact Space, alias and revision.');
|
|
178
|
+
const request = ctx.options.requestId || ctx.globalOptions.idempotencyKey;
|
|
179
|
+
if (typeof request !== 'string' || !request || request.length > 200) throw usageError('Pass --request-id <stable-key>.');
|
|
180
|
+
return nativeSkillAuthoring(ctx, { ...payload, operation: 'create', destination,
|
|
181
|
+
request_id: request, dry_run: Boolean(ctx.options.dryRun) }, '/portal_skills/native-create');
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
command_path: ['skills', 'settings', 'read'],
|
|
186
|
+
summary: 'Read a native Skill personal enablement, agent targets and settings revision.',
|
|
187
|
+
when_to_use: 'Inspect direct-owner preferences without changing shared instructions or Space access.',
|
|
188
|
+
args_schema: { arguments: [{ token: '<skill-id>', key: 'skillId', description: 'Exact native Skill ID.' }], options: [] },
|
|
189
|
+
mutates: false, idempotent: true, require_auth: true,
|
|
190
|
+
backend_call: { type: 'http', path: '/portal_skills/native-settings' },
|
|
191
|
+
handler(ctx) { return nativeSkillAuthoring(ctx, { operation: 'read', target: { skill_id: ctx.args.skillId } }, '/portal_skills/native-settings'); },
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
command_path: ['skills', 'settings', 'update'],
|
|
195
|
+
summary: 'Update personal native Skill settings with a current revision and stable retry key.',
|
|
196
|
+
when_to_use: 'Enable or disable your agents without changing a shared Skill definition or another Space.',
|
|
197
|
+
args_schema: { arguments: [{ token: '<file>', key: 'file', description: 'JSON with target, revision and patch (enabled or agent_targets).' }],
|
|
198
|
+
options: [{ flags: '--request-id <key>', description: 'Stable settings intent; reuse after a lost reply.' },
|
|
199
|
+
{ flags: '--dry-run', description: 'Validate current ownership and revision without changing settings.' }] },
|
|
200
|
+
mutates: true, idempotent: true, require_auth: true,
|
|
201
|
+
backend_call: { type: 'http', path: '/portal_skills/native-settings' },
|
|
202
|
+
handler(ctx) {
|
|
203
|
+
const payload = parseJson(readFileSync(resolve(ctx.args.file), 'utf8'), 'Skill settings file');
|
|
204
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)
|
|
205
|
+
|| Object.keys(payload).sort().join(',') !== 'patch,revision,target'
|
|
206
|
+
|| !payload.target || Object.keys(payload.target).sort().join(',') !== 'access_revision,skill_id') {
|
|
207
|
+
throw usageError('Use a current standalone target, settings revision and personal settings patch.');
|
|
208
|
+
}
|
|
209
|
+
const request = ctx.options.requestId || ctx.globalOptions.idempotencyKey;
|
|
210
|
+
if (typeof request !== 'string' || !request || request.length > 200) throw usageError('Pass --request-id <stable-key>.');
|
|
211
|
+
return nativeSkillAuthoring(ctx, { ...payload, operation: 'update', request_id: request, dry_run: Boolean(ctx.options.dryRun) }, '/portal_skills/native-settings');
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
command_path: ['skills', 'read'],
|
|
216
|
+
summary: 'Inspect a native Skill through direct ownership or an editable Space link.',
|
|
217
|
+
when_to_use: 'Read the exact target and content version before editing. A Space target uses its current Editor authority, never a personal fallback.',
|
|
218
|
+
args_schema: { arguments: [{ token: '[skill-id]', key: 'skillId', description: 'Exact directly owned Skill ID; omit when passing --target.' }],
|
|
219
|
+
options: [{ flags: '--files', description: 'Include every verified supporting file as base64.' },
|
|
220
|
+
{ flags: '--target <json>', description: 'Exact {skill_id} or {space_id,binding_id} read target; do not combine with a Skill ID.' }] },
|
|
221
|
+
examples: ['notis skills read <skill-id> --files', 'notis skills read --target \'{"space_id":"<id>","binding_id":"<binding>"}\' --files'],
|
|
222
|
+
mutates: false, idempotent: true, require_auth: true,
|
|
223
|
+
backend_call: { type: 'http', path: '/portal_skills/native-authoring' },
|
|
224
|
+
handler(ctx) {
|
|
225
|
+
if (Boolean(ctx.args.skillId) === Boolean(ctx.options.target)) throw usageError('Pass either a Skill ID or --target <json>.');
|
|
226
|
+
const target = skillTarget(ctx.options.target ? parseJson(ctx.options.target, '--target') : { skill_id: ctx.args.skillId }, false);
|
|
227
|
+
return nativeSkillAuthoring(ctx, { operation: 'read', target,
|
|
228
|
+
include_files: Boolean(ctx.options.files) });
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
command_path: ['skills', 'update'],
|
|
233
|
+
summary: 'Edit a native Skill through its direct or Space target, preserving its ID and supporting files.',
|
|
234
|
+
when_to_use: 'Use the target/version from skills list or skills read, dry-run, then reuse the same request key and bytes to save or recover a lost reply. The same edit file works for a standalone or a Space target; an optional links key changes where the Skill is listed in the same transaction.',
|
|
235
|
+
args_schema: { arguments: [{ token: '<file>', key: 'file', description: 'JSON with direct or Space target, version, skill_md or base64 files, optional name/description and links {add,remove}.' }],
|
|
236
|
+
options: [{ flags: '--request-id <key>', description: 'Stable edit intent key; reuse after lost replies.' },
|
|
237
|
+
{ flags: '--dry-run', description: 'Check current access, content and revisions without uploading or saving.' }] },
|
|
238
|
+
examples: ['notis skills update ./skill-edit.json --request-id edit-1 --dry-run'],
|
|
239
|
+
mutates: true, idempotent: true, require_auth: true,
|
|
240
|
+
backend_call: { type: 'http', path: '/portal_skills/native-authoring' },
|
|
241
|
+
handler(ctx) {
|
|
242
|
+
const payload = parseJson(readFileSync(resolve(ctx.args.file), 'utf8'), 'Skill edit file');
|
|
243
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)
|
|
244
|
+
|| Object.keys(payload).some(key => !['target', 'version', 'skill_md', 'files', 'name', 'description', 'links'].includes(key))) {
|
|
245
|
+
throw usageError('Use an exact target, version, skill_md or files, and optional links; never override ownership.');
|
|
246
|
+
}
|
|
247
|
+
skillTarget(payload.target, true);
|
|
248
|
+
if ('links' in payload && (!payload.links || typeof payload.links !== 'object' || Array.isArray(payload.links)
|
|
249
|
+
|| !Object.keys(payload.links).length || Object.keys(payload.links).some(key => !['add', 'remove'].includes(key)))) {
|
|
250
|
+
throw usageError('links must be an object with add and/or remove arrays.');
|
|
251
|
+
}
|
|
252
|
+
return nativeSkillAuthoring(ctx, { ...payload, operation: 'update', request_id: requestKey(ctx),
|
|
253
|
+
dry_run: Boolean(ctx.options.dryRun) });
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
command_path: ['skills', 'links'],
|
|
258
|
+
summary: 'Add, remove or move a Skill between Spaces; the Skill keeps its ID.',
|
|
259
|
+
when_to_use: 'Change where a Skill is listed without editing its content. A move is one add plus one remove in one transaction. You must be an Editor of every Space you touch and able to edit the Skill; removing its last available link keeps it as your own standalone Skill.',
|
|
260
|
+
args_schema: { arguments: [{ token: '<skill-id>', key: 'skillId', description: 'Exact Skill ID from skills list.' }], options: [
|
|
261
|
+
{ flags: '--add <json>', description: 'JSON array of {space_id, space_revision, alias?} links to add; alias defaults to the Skill name.' },
|
|
262
|
+
{ flags: '--remove <json>', description: 'JSON array of {binding_id, binding_revision} links to remove, from skills list.' },
|
|
263
|
+
{ flags: '--request-id <key>', description: 'Stable intent key; reuse it unchanged when retrying this change.' },
|
|
264
|
+
] },
|
|
265
|
+
examples: ['notis skills links <skill-id> --add \'[{"space_id":"<space-id>","space_revision":3}]\' --request-id link-1',
|
|
266
|
+
'notis skills links <skill-id> --add \'[{"space_id":"<to-space-id>","space_revision":3}]\' --remove \'[{"binding_id":"<binding-id>","binding_revision":2}]\' --request-id move-1'],
|
|
267
|
+
mutates: true, idempotent: true, require_auth: true,
|
|
268
|
+
backend_call: { type: 'http', path: '/portal_spaces/resource-links' },
|
|
269
|
+
async handler(ctx) {
|
|
270
|
+
const links = {};
|
|
271
|
+
if (ctx.options.add !== undefined) links.add = linkEntries(ctx.options.add, '--add', ['space_id', 'space_revision'], ['alias']);
|
|
272
|
+
if (ctx.options.remove !== undefined) links.remove = linkEntries(ctx.options.remove, '--remove', ['binding_id', 'binding_revision']);
|
|
273
|
+
if (!Object.keys(links).length) throw usageError('Pass --add and/or --remove.');
|
|
274
|
+
const body = { kind: 'skill', resource_id: ctx.args.skillId, links, request_id: requestKey(ctx) };
|
|
275
|
+
const result = await httpRequest({ runtime: ctx.runtime, method: 'POST', path: '/portal_spaces/resource-links', body, spacesProtocol: 1 });
|
|
276
|
+
return ctx.output.emitSuccess({ command: 'skills links', data: result.payload,
|
|
277
|
+
humanSummary: linkChangeSummary(result.payload), meta: { mutating: true, request_id: result.requestId } });
|
|
278
|
+
},
|
|
279
|
+
},
|
|
51
280
|
{
|
|
52
281
|
command_path: ['skills', 'sync'],
|
|
53
282
|
summary: 'Synchronize account skills and keep the three Notis base skills current.',
|