@xmemo/client
Advanced tools
+1
-1
| { | ||
| "name": "@xmemo/client", | ||
| "version": "0.4.179", | ||
| "version": "0.4.180", | ||
| "description": "Privacy-first CLI and MCP setup helper for XMemo.", | ||
@@ -5,0 +5,0 @@ "mcpName": "io.github.yonro/xmemo", |
| # XMemo Skill Change Log | ||
| ## 1.1.4 | ||
| - `scripts/xmemo-skill.mjs`: add a bounded, token-free `clientDiagnostics` | ||
| block to `doctor --json`, including read-only discovery service/capability | ||
| summary and a concrete next credential-check or sign-in command. | ||
| - Diagnostics: when discovery is unavailable, report a stable degraded status | ||
| without failing an otherwise healthy doctor operation or changing any auth, | ||
| write, or restart-continuity behavior. | ||
| - Tests and Skill documentation: cover authenticated, anonymous, and degraded | ||
| discovery output while preserving the no-Authorization-header guarantee for | ||
| `doctor --anonymous`. | ||
| ## 1.1.3 | ||
| - `scripts/xmemo-skill.mjs`: report a clear empty-state result when a successful | ||
| `restore-state` response contains no saved state, while preserving the | ||
| requested key and an explicit empty-content marker for valid state objects. | ||
| - Tests: cover empty and partially populated state-restore responses so the | ||
| standalone command does not print `undefined` to users. | ||
| ## 1.1.2 | ||
| - `SKILL.md` and references: distinguish the public generic | ||
| `/v1/skill/operations` discovery list from the formal-account-only direct | ||
| restart-continuity routes. This prevents a missing restart entry in | ||
| `standalone_skill.operations` from being misread as an unavailable command. | ||
| - Documentation and tests: clarify that temporary agents never receive restart | ||
| continuity, that discovery alone is not authorization, and that an | ||
| unauthenticated `401` is route reachability rather than a write-capability | ||
| proof. | ||
| ## 1.1.1 | ||
| - `scripts/xmemo-skill.mjs`: add formal-account `restart-snapshot` and | ||
| `restart-restore` commands for the Memory OS v0.4.335 full-continuity | ||
| contract, without replacing the lightweight `save-state` / `restore-state` | ||
| workflow or widening temporary-agent permissions. | ||
| - `scripts/xmemo-skill.mjs`: validate restart snapshot limits, TTLs, metadata, | ||
| and restore booleans; keep normal output bounded to IDs/timestamps while | ||
| retaining redacted `--json` output for trusted callers. | ||
| - `SKILL.md` and references: explain when to use single-state handoff, | ||
| full restart continuity, or native MCP restart tools. | ||
| - Tests: pin the advertised runtime version to the newest change-log heading so | ||
| a released section is never reopened for new work. | ||
| ## 1.1.0 | ||
@@ -4,0 +49,0 @@ |
@@ -84,2 +84,4 @@ # XMemo Skill Operations | ||
| | `restore-state` | Restore current task handoff state | | ||
| | `restart-snapshot` | Save active state, recent events, TODOs, and pending decisions as one restart snapshot | | ||
| | `restart-restore` | Restore the latest or a selected restart snapshot | | ||
| | `todo-add` | Create a TODO item | | ||
@@ -94,2 +96,18 @@ | `todo-list` | List TODO items | | ||
| ## Discovery boundary | ||
| The public `/.well-known/agent-discovery.json` operation list is a contract for | ||
| the generic `POST /v1/skill/operations` dispatcher. It intentionally does not | ||
| enumerate every direct standalone endpoint. In particular, | ||
| `restart-snapshot` and `restart-restore` use `/v1/restart/snapshot` and | ||
| `/v1/restart/restore` directly, so they do not appear in | ||
| `standalone_skill.operations`. | ||
| This is a routing boundary, not permission evidence. A formal account still | ||
| needs authorization for each restart request; an unauthenticated `401` only | ||
| proves that the protected route is reachable. Do not create a real snapshot | ||
| just to test a deployment. Temporary-agent discovery intentionally exposes no | ||
| restart workflow, and temporary credentials remain limited to `remember`, | ||
| `recall`, and `search`. | ||
| ## Examples | ||
@@ -133,2 +151,31 @@ | ||
| ### Preserve full restart continuity | ||
| Use a restart snapshot when the next agent/session needs more than the single | ||
| active-state slot: | ||
| ```text | ||
| node scripts/xmemo-skill.mjs restart-snapshot | ||
| node scripts/xmemo-skill.mjs restart-restore | ||
| ``` | ||
| `restart-snapshot` captures the active state plus bounded recent timeline, | ||
| TODO, and pending-decision context. `restart-restore` selects the latest | ||
| accessible snapshot when no ID is supplied; the service may synthesize one | ||
| from current active state when no explicit snapshot exists. Select a specific | ||
| snapshot or session only when needed: | ||
| ```text | ||
| node scripts/xmemo-skill.mjs restart-snapshot --session_id handoff-a --timeline_limit 20 | ||
| node scripts/xmemo-skill.mjs restart-restore --source_session_id handoff-a --target_session_id handoff-b | ||
| ``` | ||
| All limits are client-validated against the hosted contract. Snapshot item | ||
| limits accept `0..100`; `--ttl_seconds` accepts `0..2592000` (30 days). | ||
| The direct REST responses can contain the captured continuity pack, so normal | ||
| human output prints only status, ID, and time fields. Use `--json` only when a | ||
| trusted caller needs the complete redacted response. Native MCP hosts should | ||
| use `create_restart_snapshot` and `restore_restart_snapshot` instead of | ||
| spawning the script. | ||
| ### Add a TODO | ||
@@ -186,2 +233,5 @@ | ||
| - `save-state` / `restore-state` map to `update_state` / `_get_active_state_item` under the hood; they capture/resume server-side active task state. | ||
| - `restart-snapshot` / `restart-restore` call `/v1/restart/snapshot` and | ||
| `/v1/restart/restore` directly and require a formal credential with memory | ||
| read/write access. Temporary agent credentials cannot use them. | ||
| - Offline memory storage or local sync is not implemented. |
@@ -114,2 +114,5 @@ # XMemo Skill Troubleshooting | ||
| | `Authentication failed (HTTP 401)` | Token invalid/expired | Run `login` or add a new token | | ||
| | Restart command is missing from `agent-discovery` operations | That list covers only the generic `/v1/skill/operations` dispatcher; restart continuity uses dedicated protected routes | Use the bundled Skill command with a formal credential; do not infer access from discovery alone or test by creating a real snapshot | | ||
| | `Restart snapshot not found` | The requested ID/session is unavailable in the current scope | Omit the selector to restore the latest accessible snapshot, or run `restart-snapshot` first | | ||
| | Restart command reports temporary access | Temporary sandboxes expose only memory save/recall/search | Complete formal account claim/login, then retry | | ||
| | `Remote XMemo server is not reachable` | Network or service outage | Check network/VPN/proxy | | ||
@@ -116,0 +119,0 @@ | `XMemo base URL must use HTTPS` | Insecure non-loopback service URL | Use HTTPS, or localhost HTTP only for local development | |
@@ -16,3 +16,3 @@ #!/usr/bin/env node | ||
| const SKILL_VERSION = '1.1.0'; | ||
| const SKILL_VERSION = '1.1.4'; | ||
| const credentialsPath = path.join(os.homedir(), '.xmemo', 'skill-credentials.json'); | ||
@@ -26,2 +26,3 @@ const registrationPath = path.join(os.homedir(), '.xmemo', 'skill-registration.json'); | ||
| const MAX_RESPONSE_BYTES = 8_388_608; | ||
| const MAX_STATE_TTL_SECONDS = 2_592_000; | ||
| const DEFAULT_TEMPORARY_LIMITS = Object.freeze({ | ||
@@ -35,2 +36,3 @@ max_items: 100, | ||
| 'remember', 'recall', 'search', 'save-state', 'restore-state', 'state-save', 'state-restore', | ||
| 'restart-snapshot', 'restart-restore', | ||
| 'todo-add', 'todo-list', 'todo-done', 'expense-add', 'doctor', | ||
@@ -50,2 +52,4 @@ ]); | ||
| 'state-restore': new Set(['key', 'state_key', 'bucket', 'scope']), | ||
| 'restart-snapshot': new Set(['session_id', 'state_key', 'timeline_limit', 'reminder_limit', 'decision_limit', 'metadata', 'bucket', 'scope', 'path', 'ttl_seconds']), | ||
| 'restart-restore': new Set(['snapshot_id', 'source_session_id', 'target_session_id', 'state_key', 'restore_state', 'record_restore_event', 'ttl_seconds', 'bucket', 'scope']), | ||
| 'todo-add': new Set(['content', 'due_at', 'bucket', 'scope', 'path']), | ||
@@ -193,2 +197,4 @@ 'todo-list': new Set(['bucket', 'scope', 'status']), | ||
| 'state-restore': 'state-restore --key <key> (legacy alias)', | ||
| 'restart-snapshot': 'restart-snapshot [--state_key <key>] [--session_id <id>] [--ttl_seconds <0..2592000>]', | ||
| 'restart-restore': 'restart-restore [--snapshot_id <id> | --source_session_id <id>] [--target_session_id <id>]', | ||
| 'todo-add': 'todo-add --content <text>', | ||
@@ -204,3 +210,3 @@ 'todo-list': 'todo-list', | ||
| console.log(`XMemo Standalone Skill Runtime\n\nUsage:\n ${SCRIPT_COMMAND} <command> [options]\n\nCommands:\n login --allow-plaintext Start formal device login and explicitly permit local token storage\n register --reason <unattended|declined> --allow-plaintext\n Start limited temporary memory only when formal login is unavailable\n logout Revoke and remove a local credential\n auth status [--verify] Show local or verified auth status\n auth-status [--verify] Alias for auth status\n auth add --from-stdin --allow-plaintext\n Store a formal token read from standard input\n auth claim-status [--allow-plaintext]\n Check temporary-account claim status\n auth claim-confirm [--allow-plaintext]\n Confirm a pending human claim and accept formal token handoff\n auth claim-deny [--allow-plaintext]\n Decline a pending bind and keep isolated temporary access\n remember --content <text> --path <path>\n recall --query <text> [--limit <n>] [--compact]\n search --query <text> [--limit <n>] [--compact]\n save-state --key <key> [--content <text>] (aliases: state-save)\n restore-state --key <key> (aliases: state-restore)\n todo-add --content <text>\n todo-list\n todo-done --id <todo_id>\n expense-add --item <text> --amount <number> --currency <code>\n doctor [--anonymous]\n\nCredential resolution:\n XMEMO_KEY Preferred; never copied to the local credential file\n User credential file Read only as a fallback\n\nGlobal options:\n --json Print the API response as JSON\n --base-url <url> Override ${DEFAULT_BASE_URL}; HTTPS or loopback HTTP only\n --timeout-ms <ms> Per-request timeout (default: ${DEFAULT_TIMEOUT_MS})\n --compact Shorten recall/search content for terminals\n --allow-plaintext Explicitly permit unencrypted user-file credential storage\n --version Show the Skill runtime version\n --help, -h Show this help\n\nRun \`${SCRIPT_COMMAND} <command> --help\` for command-specific usage.`); | ||
| console.log(`XMemo Standalone Skill Runtime\n\nUsage:\n ${SCRIPT_COMMAND} <command> [options]\n\nCommands:\n login --allow-plaintext Start formal device login and explicitly permit local token storage\n register --reason <unattended|declined> --allow-plaintext\n Start limited temporary memory only when formal login is unavailable\n logout Revoke and remove a local credential\n auth status [--verify] Show local or verified auth status\n auth-status [--verify] Alias for auth status\n auth add --from-stdin --allow-plaintext\n Store a formal token read from standard input\n auth claim-status [--allow-plaintext]\n Check temporary-account claim status\n auth claim-confirm [--allow-plaintext]\n Confirm a pending human claim and accept formal token handoff\n auth claim-deny [--allow-plaintext]\n Decline a pending bind and keep isolated temporary access\n remember --content <text> --path <path>\n recall --query <text> [--limit <n>] [--compact]\n search --query <text> [--limit <n>] [--compact]\n save-state --key <key> [--content <text>] (aliases: state-save)\n restore-state --key <key> (aliases: state-restore)\n restart-snapshot Save a full restart-continuity snapshot\n restart-restore Restore the latest or selected restart snapshot\n todo-add --content <text>\n todo-list\n todo-done --id <todo_id>\n expense-add --item <text> --amount <number> --currency <code>\n doctor [--anonymous]\n\nCredential resolution:\n XMEMO_KEY Preferred; never copied to the local credential file\n User credential file Read only as a fallback\n\nGlobal options:\n --json Print the API response as JSON\n --base-url <url> Override ${DEFAULT_BASE_URL}; HTTPS or loopback HTTP only\n --timeout-ms <ms> Per-request timeout (default: ${DEFAULT_TIMEOUT_MS})\n --compact Shorten recall/search content for terminals\n --allow-plaintext Explicitly permit unencrypted user-file credential storage\n --version Show the Skill runtime version\n --help, -h Show this help\n\nRun \`${SCRIPT_COMMAND} <command> --help\` for command-specific usage.`); | ||
| } | ||
@@ -315,6 +321,15 @@ | ||
| if (flags.limit !== undefined) parsePositiveInteger(flags.limit, '--limit', 100); | ||
| if (flags.ttl_seconds !== undefined) parseIntegerInRange(flags.ttl_seconds, '--ttl_seconds', 0, 604_800); | ||
| if (flags.ttl_seconds !== undefined) { | ||
| const ttlMax = command.startsWith('restart-') ? MAX_STATE_TTL_SECONDS : 604_800; | ||
| const parsedTtl = parseIntegerInRange(flags.ttl_seconds, '--ttl_seconds', 0, ttlMax); | ||
| if (command.startsWith('restart-')) flags.ttl_seconds = parsedTtl; | ||
| } | ||
| if (flags.metadata !== undefined) flags.metadata = parseJsonObject(flags.metadata, '--metadata'); | ||
| if (flags.explain !== undefined) flags.explain = parseStrictBoolean(flags.explain, '--explain'); | ||
| if (flags.prefer_working !== undefined) flags.prefer_working = parseStrictBoolean(flags.prefer_working, '--prefer_working'); | ||
| if (flags.restore_state !== undefined) flags.restore_state = parseStrictBoolean(flags.restore_state, '--restore_state'); | ||
| if (flags.record_restore_event !== undefined) flags.record_restore_event = parseStrictBoolean(flags.record_restore_event, '--record_restore_event'); | ||
| for (const key of ['timeline_limit', 'reminder_limit', 'decision_limit']) { | ||
| if (flags[key] !== undefined) flags[key] = parseIntegerInRange(flags[key], `--${key}`, 0, 100); | ||
| } | ||
| if (flags.threshold !== undefined) { | ||
@@ -403,2 +418,89 @@ const threshold = Number(flags.threshold); | ||
| function discoveryString(value) { | ||
| if (typeof value !== 'string') return null; | ||
| const sanitized = sanitizeTerminalText(value).trim(); | ||
| return sanitized ? sanitized.slice(0, 200) : null; | ||
| } | ||
| function discoveryStringList(value, maxItems = 24) { | ||
| if (!Array.isArray(value)) return []; | ||
| return value | ||
| .filter((item) => typeof item === 'string') | ||
| .map(discoveryString) | ||
| .filter(Boolean) | ||
| .slice(0, maxItems); | ||
| } | ||
| function summarizeDoctorDiscovery(discovery, discoveryUrl) { | ||
| const standalone = discovery?.standalone_skill ?? discovery?.integrations?.standalone_skill ?? {}; | ||
| return { | ||
| status: 'available', | ||
| url: discoveryUrl, | ||
| schemaVersion: discoveryString(discovery?.schema_version), | ||
| protocol: discoveryString(discovery?.protocol), | ||
| service: discoveryString(discovery?.service), | ||
| standaloneSkill: { | ||
| status: discoveryString(standalone.status), | ||
| runtimeModel: discoveryString(standalone.runtime_model), | ||
| operations: discoveryStringList(standalone.operations), | ||
| defaultScopes: discoveryStringList(standalone.auth?.default_scopes), | ||
| }, | ||
| }; | ||
| } | ||
| function discoveryFailureCode(error) { | ||
| const message = String(error?.message ?? '').toLowerCase(); | ||
| if (message.includes('timed out')) return 'timeout'; | ||
| if (message.includes('non-json')) return 'invalid_response'; | ||
| return 'request_failed'; | ||
| } | ||
| async function fetchDoctorDiscovery(baseUrl, timeoutMs) { | ||
| const discoveryUrl = new URL('/.well-known/agent-discovery.json', baseUrl).toString(); | ||
| try { | ||
| const res = await makeHttpRequest(baseUrl, '/.well-known/agent-discovery.json', 'GET', null, {}, timeoutMs); | ||
| if (res.statusCode < 200 || res.statusCode >= 300) { | ||
| return { | ||
| status: 'unavailable', | ||
| url: discoveryUrl, | ||
| errorCode: 'http_error', | ||
| httpStatus: res.statusCode ?? null, | ||
| }; | ||
| } | ||
| return summarizeDoctorDiscovery(parseJsonResponse(res, 'Doctor discovery'), discoveryUrl); | ||
| } catch (error) { | ||
| return { | ||
| status: 'unavailable', | ||
| url: discoveryUrl, | ||
| errorCode: discoveryFailureCode(error), | ||
| }; | ||
| } | ||
| } | ||
| function doctorNextAction({ credential, anonymous }) { | ||
| if (!anonymous && !credential) { | ||
| return { | ||
| command: `${SCRIPT_COMMAND} login --allow-plaintext`, | ||
| reason: 'Sign in before using account-scoped memory operations.', | ||
| }; | ||
| } | ||
| return { | ||
| command: `${SCRIPT_COMMAND} auth status --verify`, | ||
| reason: 'Verify the credential separately when an authenticated follow-up is needed.', | ||
| }; | ||
| } | ||
| function withDoctorDiagnostics(data, discovery, nextAction) { | ||
| const report = data && typeof data === 'object' && !Array.isArray(data) | ||
| ? { ...data } | ||
| : { ok: true, result: data }; | ||
| return { | ||
| ...report, | ||
| clientDiagnostics: { | ||
| discovery, | ||
| nextAction, | ||
| }, | ||
| }; | ||
| } | ||
| // HTTP request helper | ||
@@ -1088,3 +1190,3 @@ function makeHttpRequest(baseUrl, apiPath, method, body = null, headers = {}, timeoutMs = DEFAULT_TIMEOUT_MS) { | ||
| // 4. REST OPERATIONS (remember, recall, search, update, forget, state-save, state-restore, todo-*, expense-*, doctor) | ||
| // 4. REST OPERATIONS (memory, state, restart continuity, TODO, ledger, and diagnostics) | ||
| const credential = command === 'doctor' && options.anonymous ? null : await getStoredCredential(); | ||
@@ -1096,2 +1198,5 @@ const token = credential?.token; | ||
| try { | ||
| const discovery = options.json | ||
| ? await fetchDoctorDiscovery(options.baseUrl, options.timeoutMs) | ||
| : null; | ||
| const res = await makeHttpRequest(options.baseUrl, '/v1/skill/operations', 'POST', { | ||
@@ -1107,3 +1212,6 @@ operation: 'doctor', | ||
| if (options.json) { | ||
| console.log(safeJson(data)); | ||
| console.log(safeJson(withDoctorDiagnostics(data, discovery, doctorNextAction({ | ||
| credential, | ||
| anonymous: options.anonymous, | ||
| })))); | ||
| } else { | ||
@@ -1139,2 +1247,31 @@ console.log(`XMemo Service Status: OK\nAuthentication: Missing/Unauthenticated`); | ||
| if (command === 'restart-snapshot' || command === 'restart-restore') { | ||
| const endpoint = command === 'restart-snapshot' ? '/v1/restart/snapshot' : '/v1/restart/restore'; | ||
| const label = command === 'restart-snapshot' ? 'Restart snapshot' : 'Restart restore'; | ||
| try { | ||
| const res = await makeHttpRequest(options.baseUrl, endpoint, 'POST', flags, { | ||
| 'Authorization': `Bearer ${token}` | ||
| }, options.timeoutMs); | ||
| const data = parseJsonResponse(res, `${label} request`); | ||
| const succeeded = res.statusCode >= 200 && res.statusCode < 300; | ||
| if (options.json) { | ||
| console.log(safeJson(data)); | ||
| process.exit(succeeded ? 0 : 1); | ||
| } | ||
| if (!succeeded) { | ||
| console.error(`${label} failed: ${apiErrorMessage(data)} (HTTP ${res.statusCode})`); | ||
| process.exit(1); | ||
| } | ||
| if (command === 'restart-snapshot') { | ||
| console.log(`✅ Restart snapshot saved.\nID: ${sanitizeTerminalText(extractId(data))}${data.expires_at ? `\nExpires: ${sanitizeTerminalText(data.expires_at)}` : ''}`); | ||
| } else { | ||
| console.log(`✅ Restart snapshot restored.\nID: ${sanitizeTerminalText(extractId(data))}${data.restored_at ? `\nRestored: ${sanitizeTerminalText(data.restored_at)}` : ''}`); | ||
| } | ||
| } catch (e) { | ||
| console.error(`${label} failed:`, e.message); | ||
| process.exit(1); | ||
| } | ||
| return; | ||
| } | ||
| // Normalize commands for operations mapping | ||
@@ -1146,2 +1283,5 @@ let opName = command; | ||
| try { | ||
| const discovery = command === 'doctor' && options.json | ||
| ? await fetchDoctorDiscovery(options.baseUrl, options.timeoutMs) | ||
| : null; | ||
| const res = await makeHttpRequest(options.baseUrl, '/v1/skill/operations', 'POST', { | ||
@@ -1157,3 +1297,6 @@ operation: opName, | ||
| if (options.json) { | ||
| console.log(safeJson(data)); | ||
| const output = opName === 'doctor' | ||
| ? withDoctorDiagnostics(data, discovery, doctorNextAction({ credential, anonymous: false })) | ||
| : data; | ||
| console.log(safeJson(output)); | ||
| process.exit(succeeded ? 0 : 1); | ||
@@ -1196,3 +1339,12 @@ } | ||
| } else if (opName === 'state-restore') { | ||
| console.log(`Working State restored:\nKey: ${sanitizeTerminalText(data.result?.state_key)}\nContent: ${formatMemoryContent(data.result?.content, false)}`); | ||
| const state = data.result; | ||
| if (!state || typeof state !== 'object') { | ||
| console.log('No saved working state found for the requested key.'); | ||
| } else { | ||
| const stateKey = state.state_key || flags.key || flags.state_key || '(unknown)'; | ||
| const content = state.content === undefined || state.content === null || state.content === '' | ||
| ? '(empty)' | ||
| : state.content; | ||
| console.log(`Working State restored:\nKey: ${sanitizeTerminalText(stateKey)}\nContent: ${formatMemoryContent(content, false)}`); | ||
| } | ||
| } else if (opName === 'remember') { | ||
@@ -1199,0 +1351,0 @@ console.log(`✅ Saved to XMemo.\nID: ${sanitizeTerminalText(extractId(data.result))}`); |
| --- | ||
| name: xmemo-memory | ||
| description: Persistent user-owned memory for agents with standalone runtime execution. Use when an agent should remember, recall, search memory, save or restore handoff state, manage TODOs, record expenses, diagnose XMemo auth, or operate XMemo even when MCP tools are not configured. | ||
| description: Persistent user-owned memory for agents with standalone runtime execution. Use when an agent should remember, recall, search memory, preserve restart continuity, manage TODOs, record expenses, diagnose XMemo auth, or operate XMemo even when MCP tools are not configured. | ||
| --- | ||
@@ -19,2 +19,16 @@ | ||
| ## Hosted Discovery Boundary | ||
| The public `agent-discovery` field `standalone_skill.operations` describes the | ||
| generic commands accepted by `POST /v1/skill/operations`; it is not the full | ||
| standalone command catalogue. `restart-snapshot` and `restart-restore` use the | ||
| separate direct endpoints `/v1/restart/snapshot` and `/v1/restart/restore`, so | ||
| they are deliberately absent from that operations list. | ||
| Do not infer that a restart command is available merely because a discovery | ||
| document mentions a memory scope. It requires a formal account credential and | ||
| the service must authorize the specific request. The temporary-agent manifest | ||
| intentionally omits restart continuity: temporary access stays limited to | ||
| `remember`, `recall`, and `search` in its isolated sandbox. | ||
| Credential lookup always prefers the `XMEMO_KEY` environment variable. When it | ||
@@ -83,4 +97,6 @@ is present, the script does not copy its value into a local credential file. | ||
| architecture notes, release procedures, and verified troubleshooting steps. | ||
| - **Preserve handoffs.** Use `save-state` and `restore-state` at milestones or | ||
| before stopping. | ||
| - **Preserve handoffs.** Use `save-state` / `restore-state` for one active | ||
| task slot. Use `restart-snapshot` / `restart-restore` when a restart needs | ||
| the broader continuity pack: active state, recent events, TODOs, and pending | ||
| decisions. | ||
| - **Record concrete expenses.** Use `expense-add` when the user states a concrete | ||
@@ -102,2 +118,4 @@ purchase or income. | ||
| node scripts/xmemo-skill.mjs restore-state --key active_task | ||
| node scripts/xmemo-skill.mjs restart-snapshot | ||
| node scripts/xmemo-skill.mjs restart-restore | ||
| node scripts/xmemo-skill.mjs todo-add --content "..." | ||
@@ -114,4 +132,14 @@ node scripts/xmemo-skill.mjs todo-list | ||
| `--help`, `--version`, per-request timeouts with `--timeout-ms`, and compact | ||
| recall/search output with `--compact`. It never prints token values or prefixes. | ||
| recall/search output with `--compact`. `doctor --json` adds a bounded | ||
| `clientDiagnostics` object: a read-only discovery summary and a `nextAction` | ||
| command for the next credential check or formal sign-in. If discovery is | ||
| unavailable, `clientDiagnostics.discovery.status` is `unavailable`; a successful | ||
| doctor health check still succeeds. It never prints token values or prefixes. | ||
| When native XMemo MCP tools are present, use `create_restart_snapshot` and | ||
| `restore_restart_snapshot` for the same full-continuity workflow. The bundled | ||
| commands keep that capability available to standalone Skill hosts. These | ||
| restart commands require a formal account credential; temporary sandboxes | ||
| remain limited to `remember`, `recall`, and `search`. | ||
| ## Direct CLI Commands | ||
@@ -151,3 +179,5 @@ | ||
| `doctor --anonymous` performs the same service-health check without sending an | ||
| Authorization header. | ||
| Authorization header. Both forms use only an unauthenticated, read-only | ||
| discovery request for their JSON capability summary; discovery failure does not | ||
| block an otherwise successful health check. | ||
@@ -166,3 +196,5 @@ For detailed examples, read `references/operations.md`. For auth, network, and service diagnosis, read `references/troubleshooting.md`. | ||
| - Secrets, tokens, API keys, OAuth codes, cookies, session IDs, or private keys. | ||
| - Secrets, tokens, API keys, OAuth codes, cookies, authentication session IDs, | ||
| or private keys. Optional restart `session_id` values must be non-secret | ||
| correlation labels, never login/session credentials. | ||
| - Private customer data or sensitive personal data unless the user explicitly asks | ||
@@ -169,0 +201,0 @@ and the memory tool supports the required privacy policy. |
+88
-14
@@ -22,3 +22,3 @@ /** | ||
| recall: 'Recall the most relevant saved memories before answering.', | ||
| recall_context: 'Build a context pack from XMemo memories for complex tasks.', | ||
| recall_context: 'Read a multi-memory context pack. Requires memory:read and does not change content. Use when many memories need explicit budgets; use recall for lightweight answer or get_project_context for project snapshot. max_items/max_tokens bound rendered output.', | ||
| memory_stats: 'Show aggregate statistics for XMemo memories.', | ||
@@ -28,3 +28,3 @@ update_memory: 'Update the content or metadata of an existing memory.', | ||
| restore_memory: 'Restore a previously deleted memory.', | ||
| add_expense: 'Record one expense in the XMemo Ledger.', | ||
| add_expense: "Create one XMemo Ledger transaction and backing memory. Requires memory:write; it records a new transaction or reuses a semantic duplicate, and never deletes Ledger records. Use it for a purchase, income, refund, or transfer; use list_ledger_transactions or get_monthly_ledger_summary for reads. amount must be positive; transaction_type defaults to expense; blank transaction_date uses today's UTC date.", | ||
| list_ledger_transactions: 'Show XMemo Ledger records.', | ||
@@ -37,6 +37,6 @@ get_monthly_ledger_summary: 'Summarize Ledger totals by month and currency.', | ||
| list_memory_versions: 'List available versions for a memory.', | ||
| get_timeline: 'Show recent timeline events.', | ||
| get_timeline: 'Read authorized timeline events newest first. Requires memory:read and makes no memory changes. Use it for recent history or session resumption; use recall_context for semantic multi-memory context. limit is clamped to 1-500; session_id and event_type are exact filters.', | ||
| record_event: 'Record a significant session event, milestone, or decision.', | ||
| update_state: 'Save the current working state during long-running work.', | ||
| get_project_context: 'Build project-scoped context from XMemo memories.' | ||
| update_state: 'Create or replace one scoped working-state record for resuming a task, next action, or blocker. Requires memory:write; it versions that state slot and refreshes its expiry without deleting other memories. Use remember for durable facts or record_event for history. Provide content or a structured state field; ttl_seconds=0 means no expiry.', | ||
| get_project_context: "Read one authorized project's bounded context pack: state, TODOs, decisions, timeline, recent memories, and optional durable recall. Requires memory:read; it does not mutate project memories, and access is audit-logged. Use an exact project_id for a whole-project snapshot; otherwise use recall_context. max_items/max_tokens bound the whole pack; recent_hours affects only timeline; durable_query requires include_durable_context." | ||
| }; | ||
@@ -53,3 +53,15 @@ | ||
| recall_context: { | ||
| query: { type: 'string', description: 'Natural-language question or search text.' } | ||
| query: { type: 'string', description: 'Natural-language query used to rank memories for the context pack.' }, | ||
| max_items: { type: 'integer', default: 8, description: 'Maximum memories rendered in the context pack.' }, | ||
| max_tokens: { type: 'integer', default: 1500, description: 'Approximate token budget for the rendered context pack.' }, | ||
| limit: { type: 'integer', default: 0, description: 'Candidate-result limit; 0 derives it from the item/token budgets.' }, | ||
| path_filter: { type: 'string', default: '%', description: 'Case-insensitive memory-path pattern; % matches all paths.' }, | ||
| bucket: { type: 'string', default: '%', description: 'Accessible bucket filter; % includes all accessible buckets.' }, | ||
| scope: { type: 'string', default: '', description: 'Optional authorized scope; blank uses the token default.' }, | ||
| team_id: { type: 'string', default: '', description: 'Optional exact authorized team filter.' }, | ||
| memory_type: { type: 'string', default: 'auto', description: 'Memory type filter; auto searches the normal mixed set.' }, | ||
| prefer_working: { type: 'boolean', default: true, description: 'True prioritizes active working/session-state signals.' }, | ||
| output_json: { type: 'boolean', default: false, description: 'True returns the full structured pack; false returns rendered context text.' }, | ||
| agent_id: { type: 'string', default: '', description: 'Optional client-supplied agent label for memory attribution.' }, | ||
| agent_instance_id: { type: 'string', default: '', description: 'Optional stable, non-secret agent instance ID for per-client attribution.' } | ||
| }, | ||
@@ -70,4 +82,20 @@ update_memory: { | ||
| add_expense: { | ||
| item: { type: 'string', description: 'The purchased item or service.' }, | ||
| amount: { type: 'number', description: 'Positive transaction amount.' } | ||
| item: { type: 'string', description: 'Purchased item, income source, refund, or transfer label.' }, | ||
| amount: { type: 'number', description: 'Positive transaction amount; zero and negative values are rejected.' }, | ||
| transaction_type: { type: 'string', default: 'expense', description: 'Transaction to create: expense, income, refund, or transfer.' }, | ||
| currency: { type: 'string', default: 'CNY', description: 'Currency code or label; labels such as yen or RMB are normalized to codes.' }, | ||
| transaction_date: { type: 'string', default: '', description: "YYYY-MM-DD transaction date; blank uses today's UTC date." }, | ||
| category: { type: 'string', default: '', description: 'Optional Ledger category, such as food, transport, or electronics.' }, | ||
| merchant: { type: 'string', default: '', description: 'Optional merchant, payer, payee, or store name.' }, | ||
| payment_method: { type: 'string', default: '', description: 'Optional payment method, such as card, cash, Alipay, or WeChat Pay.' }, | ||
| note: { type: 'string', default: '', description: 'Optional note stored with the transaction.' }, | ||
| path: { type: 'string', default: 'finance/ledger/expenses', description: 'Memory path; the default follows transaction_type for non-expenses.' }, | ||
| bucket: { type: 'string', default: 'private', description: 'Bucket for the backing memory; defaults to private.' }, | ||
| scope: { type: 'string', default: '', description: 'Optional authorized scope; must match project_id when both are set.' }, | ||
| team_id: { type: 'string', default: '', description: 'Optional team attribution within the authorized scope.' }, | ||
| agent_id: { type: 'string', default: '', description: 'Optional client-supplied agent label for memory attribution.' }, | ||
| agent_instance_id: { type: 'string', default: '', description: 'Optional stable, non-secret agent instance ID for per-client attribution.' }, | ||
| device_id: { type: 'string', default: '', description: 'Optional client-supplied device identifier for attribution.' }, | ||
| device_label: { type: 'string', default: '', description: 'Optional human-readable device label for attribution.' }, | ||
| project_id: { type: 'string', default: '', description: 'Optional exact authorized project ID; stores the transaction in its private scope.' } | ||
| }, | ||
@@ -102,10 +130,32 @@ list_ledger_transactions: { | ||
| }, | ||
| get_timeline: { | ||
| limit: { type: 'integer', default: 20, description: 'Maximum events to return; values are clamped to 1-500.' }, | ||
| bucket: { type: 'string', default: '%', description: 'Accessible bucket filter; % includes all accessible buckets.' }, | ||
| scope: { type: 'string', default: '', description: 'Optional authorized scope; blank uses the token default.' }, | ||
| session_id: { type: 'string', default: '', description: 'Optional exact session ID filter.' }, | ||
| event_type: { type: 'string', default: '', description: 'Optional exact event type after lowercase normalization.' } | ||
| }, | ||
| update_state: { | ||
| state_key: { type: 'string', description: 'Working-state key to save.' }, | ||
| current_task: { type: 'string', description: 'Current task or work item.' }, | ||
| next_action: { type: 'string', description: 'Next action for later resume.' } | ||
| content: { type: 'string', default: '', description: 'Free-form state body; otherwise provide at least one structured state field.' }, | ||
| state_key: { type: 'string', default: 'active_task', description: 'Normalized state slot; the same owner, bucket, scope, and key updates that slot.' }, | ||
| current_task: { type: 'string', default: '', description: 'Current task; used to build the state body when content is blank.' }, | ||
| next_action: { type: 'string', default: '', description: 'Next action; used to build the state body when content is blank.' }, | ||
| blocked_reason: { type: 'string', default: '', description: 'Blocker; used to build the state body when content is blank.' }, | ||
| metadata_json: { type: 'string', default: '{}', description: 'JSON object merged into the working-state metadata.' }, | ||
| ttl_seconds: { type: 'integer', default: 86400, description: 'Expiry in seconds from 0 to 2592000; 0 means no expiry.' }, | ||
| bucket: { type: 'string', default: 'work', description: 'Bucket containing the working-state slot; defaults to work.' }, | ||
| scope: { type: 'string', default: '', description: 'Scope containing the working-state slot; blank uses the token default.' } | ||
| }, | ||
| get_project_context: { | ||
| query: { type: 'string', description: 'Project-context query.' }, | ||
| project_id: { type: 'string', description: 'Optional project identifier.' } | ||
| project_id: { type: 'string', description: 'Exact authorized project ID; project names are not accepted.' }, | ||
| bucket: { type: 'string', default: '%', description: 'Accessible bucket filter; % includes all accessible buckets.' }, | ||
| team_id: { type: 'string', default: '', description: 'Optional exact authorized team focus; other team rows are excluded.' }, | ||
| max_items: { type: 'integer', default: 100, description: 'Whole-pack item budget from 1 to 1000.' }, | ||
| max_tokens: { type: 'integer', default: 8000, description: 'Whole-pack approximate token budget from 1 to 50000.' }, | ||
| include_durable_context: { type: 'boolean', default: true, description: 'Include semantic durable recall; false omits that section.' }, | ||
| durable_query: { type: 'string', default: '', description: 'Query only for durable recall; ignored when include_durable_context is false.' }, | ||
| recent_hours: { type: 'integer', default: 168, description: 'Timeline lookback from 1 to 8760 hours; other sections are unaffected.' }, | ||
| output_json: { type: 'boolean', default: false, description: 'True returns the full structured pack; false returns a text summary.' }, | ||
| agent_id: { type: 'string', default: '', description: 'Optional client-supplied agent label for memory attribution.' }, | ||
| agent_instance_id: { type: 'string', default: '', description: 'Optional stable, non-secret agent instance ID for per-client attribution.' } | ||
| } | ||
@@ -126,4 +176,11 @@ }; | ||
| record_event: ['content'], | ||
| get_project_context: ['project_id'], | ||
| }; | ||
| const READ_ONLY_STATIC_TOOLS = new Set([ | ||
| 'recall_context', | ||
| 'get_project_context', | ||
| 'get_timeline' | ||
| ]); | ||
| const STATIC_TOOL_NAMES = [ | ||
@@ -159,3 +216,20 @@ 'get_mcp_identity', | ||
| required: STATIC_TOOL_REQUIRED[name] || [] | ||
| } | ||
| }, | ||
| outputSchema: { | ||
| type: 'object', | ||
| properties: { | ||
| result: { type: 'string', description: 'Human-readable text or JSON requested by output_json.' } | ||
| }, | ||
| required: ['result'] | ||
| }, | ||
| ...(READ_ONLY_STATIC_TOOLS.has(name) || name === 'update_state' || name === 'add_expense' | ||
| ? { | ||
| annotations: { | ||
| readOnlyHint: READ_ONLY_STATIC_TOOLS.has(name), | ||
| destructiveHint: false, | ||
| idempotentHint: READ_ONLY_STATIC_TOOLS.has(name), | ||
| openWorldHint: false | ||
| } | ||
| } | ||
| : {}) | ||
| })); | ||
@@ -162,0 +236,0 @@ |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
444028
5.34%6874
3.32%