@pkgseer/cli 0.1.2 → 0.1.3
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/dist/cli.js +181 -42
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-2wbvwsc9.js → chunk-8dn0z2ja.js} +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
version
|
|
4
|
-
} from "./shared/chunk-
|
|
4
|
+
} from "./shared/chunk-8dn0z2ja.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -202,7 +202,7 @@ var CliDocsListDocument = gql`
|
|
|
202
202
|
packageName
|
|
203
203
|
version
|
|
204
204
|
pages {
|
|
205
|
-
|
|
205
|
+
id
|
|
206
206
|
title
|
|
207
207
|
words
|
|
208
208
|
}
|
|
@@ -465,6 +465,28 @@ var FetchPackageDocDocument = gql`
|
|
|
465
465
|
}
|
|
466
466
|
}
|
|
467
467
|
`;
|
|
468
|
+
var GetDocPageDocument = gql`
|
|
469
|
+
query GetDocPage($pageId: String!) {
|
|
470
|
+
getDocPage(pageId: $pageId) {
|
|
471
|
+
schemaVersion
|
|
472
|
+
page {
|
|
473
|
+
id
|
|
474
|
+
title
|
|
475
|
+
content
|
|
476
|
+
contentFormat
|
|
477
|
+
breadcrumbs
|
|
478
|
+
linkName
|
|
479
|
+
linkTargets
|
|
480
|
+
lastUpdatedAt
|
|
481
|
+
source {
|
|
482
|
+
url
|
|
483
|
+
label
|
|
484
|
+
}
|
|
485
|
+
baseUrl
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
`;
|
|
468
490
|
var SearchPackageDocsDocument = gql`
|
|
469
491
|
query SearchPackageDocs($registry: Registry!, $packageName: String!, $keywords: [String!], $query: String, $includeSnippets: Boolean, $limit: Int, $version: String) {
|
|
470
492
|
searchPackageDocs(
|
|
@@ -548,6 +570,7 @@ var PackageQualityDocumentString = print(PackageQualityDocument);
|
|
|
548
570
|
var ComparePackagesDocumentString = print(ComparePackagesDocument);
|
|
549
571
|
var ListPackageDocsDocumentString = print(ListPackageDocsDocument);
|
|
550
572
|
var FetchPackageDocDocumentString = print(FetchPackageDocDocument);
|
|
573
|
+
var GetDocPageDocumentString = print(GetDocPageDocument);
|
|
551
574
|
var SearchPackageDocsDocumentString = print(SearchPackageDocsDocument);
|
|
552
575
|
var SearchProjectDocsDocumentString = print(SearchProjectDocsDocument);
|
|
553
576
|
function getSdk(client, withWrapper = defaultWrapper) {
|
|
@@ -603,6 +626,9 @@ function getSdk(client, withWrapper = defaultWrapper) {
|
|
|
603
626
|
FetchPackageDoc(variables, requestHeaders) {
|
|
604
627
|
return withWrapper((wrappedRequestHeaders) => client.rawRequest(FetchPackageDocDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "FetchPackageDoc", "query", variables);
|
|
605
628
|
},
|
|
629
|
+
GetDocPage(variables, requestHeaders) {
|
|
630
|
+
return withWrapper((wrappedRequestHeaders) => client.rawRequest(GetDocPageDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "GetDocPage", "query", variables);
|
|
631
|
+
},
|
|
606
632
|
SearchPackageDocs(variables, requestHeaders) {
|
|
607
633
|
return withWrapper((wrappedRequestHeaders) => client.rawRequest(SearchPackageDocsDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "SearchPackageDocs", "query", variables);
|
|
608
634
|
},
|
|
@@ -1220,6 +1246,10 @@ class PkgseerServiceImpl {
|
|
|
1220
1246
|
});
|
|
1221
1247
|
return { data: result.data, errors: result.errors };
|
|
1222
1248
|
}
|
|
1249
|
+
async getDocPage(pageId) {
|
|
1250
|
+
const result = await this.client.GetDocPage({ pageId });
|
|
1251
|
+
return { data: result.data, errors: result.errors };
|
|
1252
|
+
}
|
|
1223
1253
|
async searchPackageDocs(registry, packageName, options) {
|
|
1224
1254
|
const result = await this.client.SearchPackageDocs({
|
|
1225
1255
|
registry,
|
|
@@ -1792,10 +1822,11 @@ function formatScore(score) {
|
|
|
1792
1822
|
// src/commands/docs/get.ts
|
|
1793
1823
|
function parsePackageRef(ref) {
|
|
1794
1824
|
if (!ref.includes("/")) {
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1825
|
+
return {
|
|
1826
|
+
pageId: ref,
|
|
1827
|
+
originalRef: ref,
|
|
1828
|
+
isGlobalId: true
|
|
1829
|
+
};
|
|
1799
1830
|
}
|
|
1800
1831
|
const parts = ref.split("/");
|
|
1801
1832
|
if (parts.length < 2) {
|
|
@@ -1824,7 +1855,7 @@ function parsePackageRef(ref) {
|
|
|
1824
1855
|
}
|
|
1825
1856
|
return { packageName, pageId, originalRef: ref };
|
|
1826
1857
|
}
|
|
1827
|
-
function formatDocPage(data, ref) {
|
|
1858
|
+
function formatDocPage(data, ref, verbose = false) {
|
|
1828
1859
|
const lines = [];
|
|
1829
1860
|
const page = data.page;
|
|
1830
1861
|
if (!page) {
|
|
@@ -1832,6 +1863,49 @@ function formatDocPage(data, ref) {
|
|
|
1832
1863
|
}
|
|
1833
1864
|
lines.push(`[${ref}]`);
|
|
1834
1865
|
lines.push("");
|
|
1866
|
+
if (verbose) {
|
|
1867
|
+
const metadata = [];
|
|
1868
|
+
if (data.schemaVersion) {
|
|
1869
|
+
metadata.push(`Schema Version: ${data.schemaVersion}`);
|
|
1870
|
+
}
|
|
1871
|
+
if (page.id) {
|
|
1872
|
+
metadata.push(`Page ID: ${page.id}`);
|
|
1873
|
+
}
|
|
1874
|
+
if (page.contentFormat) {
|
|
1875
|
+
metadata.push(`Format: ${page.contentFormat}`);
|
|
1876
|
+
}
|
|
1877
|
+
if (page.lastUpdatedAt) {
|
|
1878
|
+
metadata.push(`Last Updated: ${page.lastUpdatedAt}`);
|
|
1879
|
+
}
|
|
1880
|
+
if (page.source) {
|
|
1881
|
+
const sourceParts = [];
|
|
1882
|
+
if (page.source.url) {
|
|
1883
|
+
sourceParts.push(page.source.url);
|
|
1884
|
+
}
|
|
1885
|
+
if (page.source.label) {
|
|
1886
|
+
sourceParts.push(`(${page.source.label})`);
|
|
1887
|
+
}
|
|
1888
|
+
if (sourceParts.length > 0) {
|
|
1889
|
+
metadata.push(`Source: ${sourceParts.join(" ")}`);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
if (page.baseUrl) {
|
|
1893
|
+
metadata.push(`Base URL: ${page.baseUrl}`);
|
|
1894
|
+
}
|
|
1895
|
+
if (page.linkName) {
|
|
1896
|
+
metadata.push(`Link Name: ${page.linkName}`);
|
|
1897
|
+
}
|
|
1898
|
+
if (page.linkTargets && page.linkTargets.length > 0) {
|
|
1899
|
+
metadata.push(`Link Targets: ${page.linkTargets.join(", ")}`);
|
|
1900
|
+
}
|
|
1901
|
+
if (metadata.length > 0) {
|
|
1902
|
+
lines.push("Metadata:");
|
|
1903
|
+
for (const meta of metadata) {
|
|
1904
|
+
lines.push(` ${meta}`);
|
|
1905
|
+
}
|
|
1906
|
+
lines.push("");
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1835
1909
|
if (page.breadcrumbs && page.breadcrumbs.length > 0) {
|
|
1836
1910
|
lines.push(page.breadcrumbs.join(" > "));
|
|
1837
1911
|
lines.push("");
|
|
@@ -1861,13 +1935,66 @@ function extractErrorMessage(error) {
|
|
|
1861
1935
|
}
|
|
1862
1936
|
async function fetchPage(ref, defaultRegistry, defaultVersion, pkgseerService) {
|
|
1863
1937
|
try {
|
|
1938
|
+
if (ref.isGlobalId) {
|
|
1939
|
+
const result2 = await pkgseerService.getDocPage(ref.pageId);
|
|
1940
|
+
if (result2.errors && result2.errors.length > 0) {
|
|
1941
|
+
const errorMsg = result2.errors.map((e) => {
|
|
1942
|
+
if (typeof e === "object" && e !== null) {
|
|
1943
|
+
const parts = [];
|
|
1944
|
+
if ("message" in e) {
|
|
1945
|
+
parts.push(String(e.message));
|
|
1946
|
+
}
|
|
1947
|
+
if ("extensions" in e && e.extensions) {
|
|
1948
|
+
const ext = e.extensions;
|
|
1949
|
+
if (typeof ext === "object" && "code" in ext) {
|
|
1950
|
+
parts.push(`Code: ${ext.code}`);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
return parts.length > 0 ? parts.join(" ") : JSON.stringify(e);
|
|
1954
|
+
}
|
|
1955
|
+
return String(e);
|
|
1956
|
+
}).join("; ");
|
|
1957
|
+
return { ref: ref.originalRef, result: null, error: errorMsg };
|
|
1958
|
+
}
|
|
1959
|
+
if (!result2.data.getDocPage) {
|
|
1960
|
+
return {
|
|
1961
|
+
ref: ref.originalRef,
|
|
1962
|
+
result: null,
|
|
1963
|
+
error: `Page not found: ${ref.pageId}`
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
return {
|
|
1967
|
+
ref: ref.originalRef,
|
|
1968
|
+
result: {
|
|
1969
|
+
schemaVersion: result2.data.getDocPage?.schemaVersion ?? null,
|
|
1970
|
+
page: result2.data.getDocPage?.page ?? null
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1864
1974
|
const registry = ref.registry ? toGraphQLRegistry(ref.registry) : defaultRegistry;
|
|
1865
1975
|
const version2 = ref.version ?? defaultVersion;
|
|
1976
|
+
if (!ref.packageName) {
|
|
1977
|
+
return {
|
|
1978
|
+
ref: ref.originalRef,
|
|
1979
|
+
result: null,
|
|
1980
|
+
error: `Package name required for reference: ${ref.originalRef}`
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1866
1983
|
const result = await pkgseerService.cliDocsGet(registry, ref.packageName, ref.pageId, version2);
|
|
1867
1984
|
if (result.errors && result.errors.length > 0) {
|
|
1868
1985
|
const errorMsg = result.errors.map((e) => {
|
|
1869
|
-
if (typeof e === "object" && e !== null
|
|
1870
|
-
|
|
1986
|
+
if (typeof e === "object" && e !== null) {
|
|
1987
|
+
const parts = [];
|
|
1988
|
+
if ("message" in e) {
|
|
1989
|
+
parts.push(String(e.message));
|
|
1990
|
+
}
|
|
1991
|
+
if ("extensions" in e && e.extensions) {
|
|
1992
|
+
const ext = e.extensions;
|
|
1993
|
+
if (typeof ext === "object" && "code" in ext) {
|
|
1994
|
+
parts.push(`Code: ${ext.code}`);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
return parts.length > 0 ? parts.join(" ") : JSON.stringify(e);
|
|
1871
1998
|
}
|
|
1872
1999
|
return String(e);
|
|
1873
2000
|
}).join("; ");
|
|
@@ -1880,7 +2007,12 @@ async function fetchPage(ref, defaultRegistry, defaultVersion, pkgseerService) {
|
|
|
1880
2007
|
error: `Page not found: ${ref.pageId} for ${ref.packageName}`
|
|
1881
2008
|
};
|
|
1882
2009
|
}
|
|
1883
|
-
return {
|
|
2010
|
+
return {
|
|
2011
|
+
ref: ref.originalRef,
|
|
2012
|
+
result: result.data.fetchPackageDoc ? {
|
|
2013
|
+
page: result.data.fetchPackageDoc.page ?? null
|
|
2014
|
+
} : null
|
|
2015
|
+
};
|
|
1884
2016
|
} catch (error) {
|
|
1885
2017
|
return {
|
|
1886
2018
|
ref: ref.originalRef,
|
|
@@ -1891,7 +2023,7 @@ async function fetchPage(ref, defaultRegistry, defaultVersion, pkgseerService) {
|
|
|
1891
2023
|
}
|
|
1892
2024
|
async function docsGetAction(refs, options, deps) {
|
|
1893
2025
|
if (refs.length === 0) {
|
|
1894
|
-
outputError("At least one page reference required. Format: <package>/<
|
|
2026
|
+
outputError("At least one page reference required. Format: <globally-unique-id> or <package>/<document>", options.json ?? false);
|
|
1895
2027
|
return;
|
|
1896
2028
|
}
|
|
1897
2029
|
const { pkgseerService } = deps;
|
|
@@ -1929,17 +2061,24 @@ ${errorMessages}`, options.json ?? false);
|
|
|
1929
2061
|
if (r.error) {
|
|
1930
2062
|
return { ref: r.ref, error: r.error };
|
|
1931
2063
|
}
|
|
2064
|
+
if (options.verbose) {
|
|
2065
|
+
return {
|
|
2066
|
+
ref: r.ref,
|
|
2067
|
+
...r.result
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
1932
2070
|
const page = r.result?.page;
|
|
1933
2071
|
return {
|
|
1934
2072
|
ref: r.ref,
|
|
1935
2073
|
title: page?.title,
|
|
1936
|
-
content: page?.content
|
|
2074
|
+
content: page?.content,
|
|
2075
|
+
breadcrumbs: page?.breadcrumbs
|
|
1937
2076
|
};
|
|
1938
2077
|
});
|
|
1939
2078
|
output(jsonResults, true);
|
|
1940
2079
|
} else {
|
|
1941
2080
|
if (successes.length > 0) {
|
|
1942
|
-
const pages = successes.filter((r) => r.result !== null).map((r) => formatDocPage(r.result, r.ref));
|
|
2081
|
+
const pages = successes.filter((r) => r.result !== null).map((r) => formatDocPage(r.result, r.ref, options.verbose));
|
|
1943
2082
|
console.log(pages.join(`
|
|
1944
2083
|
|
|
1945
2084
|
---
|
|
@@ -1956,29 +2095,28 @@ var GET_DESCRIPTION = `Fetch one or more documentation pages.
|
|
|
1956
2095
|
Retrieves the full content of documentation pages including
|
|
1957
2096
|
title, breadcrumbs, content (markdown), and source URL.
|
|
1958
2097
|
|
|
1959
|
-
Use 'pkgseer docs list' first to discover available page IDs
|
|
1960
|
-
use the output format from 'pkgseer docs search --refs-only'.
|
|
2098
|
+
Use 'pkgseer docs list' first to discover available page IDs.
|
|
1961
2099
|
|
|
1962
|
-
Format: <
|
|
2100
|
+
Format: <globally-unique-id> (preferred)
|
|
2101
|
+
or <registry>/<package>/<version>/<document> (full form)
|
|
1963
2102
|
or <package>/<document> (short form, requires --registry/--pkg-version)
|
|
1964
2103
|
|
|
1965
2104
|
Examples:
|
|
1966
|
-
#
|
|
2105
|
+
# Globally unique ID (from list output)
|
|
2106
|
+
pkgseer docs get 24293-shared-plugins-during-build-2
|
|
2107
|
+
|
|
2108
|
+
# Full form
|
|
1967
2109
|
pkgseer docs get npm/express/4.18.2/readme
|
|
1968
2110
|
pkgseer docs get hex/postgrex/1.15.0/readme
|
|
1969
2111
|
|
|
1970
|
-
# Short form
|
|
2112
|
+
# Short form
|
|
1971
2113
|
pkgseer docs get express/readme --registry npm --pkg-version 4.18.2
|
|
1972
2114
|
pkgseer docs get postgrex/readme --registry hex
|
|
1973
2115
|
|
|
1974
2116
|
# Multiple pages
|
|
1975
|
-
pkgseer docs get
|
|
1976
|
-
|
|
1977
|
-
# Pipe from search (full form works seamlessly)
|
|
1978
|
-
pkgseer docs search log --package express --refs-only | \\
|
|
1979
|
-
xargs pkgseer docs get`;
|
|
2117
|
+
pkgseer docs get 24293-shared-plugins-during-build-2 npm/express/4.18.2/readme`;
|
|
1980
2118
|
function registerDocsGetCommand(program) {
|
|
1981
|
-
program.command("get <refs...>").summary("Fetch documentation page(s)").description(GET_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-v, --pkg-version <version>", "Package version").option("--json", "Output as JSON").action(async (refs, options) => {
|
|
2119
|
+
program.command("get <refs...>").summary("Fetch documentation page(s)").description(GET_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-v, --pkg-version <version>", "Package version").option("--json", "Output as JSON").option("--verbose", "Include metadata (ID, format, source, links, etc.)").action(async (refs, options) => {
|
|
1982
2120
|
await withCliErrorHandling(options.json ?? false, async () => {
|
|
1983
2121
|
const deps = await createContainer();
|
|
1984
2122
|
await docsGetAction(refs, options, deps);
|
|
@@ -1998,11 +2136,11 @@ function formatDocsList(docs) {
|
|
|
1998
2136
|
lines.push(`Found ${docs.pages.length} pages:`);
|
|
1999
2137
|
lines.push("");
|
|
2000
2138
|
for (const page of docs.pages) {
|
|
2001
|
-
if (!page)
|
|
2139
|
+
if (!page || !page.id)
|
|
2002
2140
|
continue;
|
|
2003
2141
|
const words = page.words ? ` (${formatNumber(page.words)} words)` : "";
|
|
2004
2142
|
lines.push(` ${page.title}${words}`);
|
|
2005
|
-
lines.push(` ID: ${page.
|
|
2143
|
+
lines.push(` ID: ${page.id}`);
|
|
2006
2144
|
lines.push("");
|
|
2007
2145
|
}
|
|
2008
2146
|
lines.push("Use 'pkgseer docs get <package>/<page-id>' to fetch a page.");
|
|
@@ -2020,10 +2158,14 @@ async function docsListAction(packageName, options, deps) {
|
|
|
2020
2158
|
}
|
|
2021
2159
|
if (options.json) {
|
|
2022
2160
|
const pages = result.data.listPackageDocs.pages?.filter((p) => p) ?? [];
|
|
2023
|
-
const slim = pages.map((p) =>
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2161
|
+
const slim = pages.map((p) => {
|
|
2162
|
+
if (!p || !p.id)
|
|
2163
|
+
return null;
|
|
2164
|
+
return {
|
|
2165
|
+
id: p.id,
|
|
2166
|
+
title: p.title
|
|
2167
|
+
};
|
|
2168
|
+
}).filter((p) => p !== null);
|
|
2027
2169
|
output(slim, true);
|
|
2028
2170
|
} else {
|
|
2029
2171
|
console.log(formatDocsList(result.data.listPackageDocs));
|
|
@@ -2031,7 +2173,7 @@ async function docsListAction(packageName, options, deps) {
|
|
|
2031
2173
|
}
|
|
2032
2174
|
var LIST_DESCRIPTION = `List available documentation pages for a package.
|
|
2033
2175
|
|
|
2034
|
-
Shows all documentation pages with titles, page IDs
|
|
2176
|
+
Shows all documentation pages with titles, globally unique page IDs,
|
|
2035
2177
|
word counts, and descriptions. Use the page ID with 'docs get'
|
|
2036
2178
|
to fetch the full content of a specific page.
|
|
2037
2179
|
|
|
@@ -3723,26 +3865,23 @@ function createComparePackagesTool(pkgseerService) {
|
|
|
3723
3865
|
// src/tools/fetch-package-doc.ts
|
|
3724
3866
|
import { z as z4 } from "zod";
|
|
3725
3867
|
var argsSchema2 = {
|
|
3726
|
-
|
|
3727
|
-
package_name: schemas.packageName.describe("Name of the package to fetch documentation for"),
|
|
3728
|
-
page_id: z4.string().max(500).describe("Documentation page identifier (from list_package_docs)"),
|
|
3729
|
-
version: schemas.version
|
|
3868
|
+
page_id: z4.string().max(500).describe("Globally unique documentation page identifier (from list_package_docs)")
|
|
3730
3869
|
};
|
|
3731
3870
|
function createFetchPackageDocTool(pkgseerService) {
|
|
3732
3871
|
return {
|
|
3733
3872
|
name: "fetch_package_doc",
|
|
3734
|
-
description: "Fetches the full content of a specific documentation page. Returns page title, content (markdown/HTML), breadcrumbs, and
|
|
3873
|
+
description: "Fetches the full content of a specific documentation page using a globally unique page ID. Returns complete page metadata including title, content (markdown/HTML), content format, breadcrumbs, source attribution, link metadata, base URL, and last updated timestamp. Use list_package_docs first to get available page IDs.",
|
|
3735
3874
|
schema: argsSchema2,
|
|
3736
|
-
handler: async ({
|
|
3875
|
+
handler: async ({ page_id }, _extra) => {
|
|
3737
3876
|
return withErrorHandling("fetch documentation page", async () => {
|
|
3738
|
-
const result = await pkgseerService.
|
|
3877
|
+
const result = await pkgseerService.getDocPage(page_id);
|
|
3739
3878
|
const graphqlError = handleGraphQLErrors(result.errors);
|
|
3740
3879
|
if (graphqlError)
|
|
3741
3880
|
return graphqlError;
|
|
3742
|
-
if (!result.data.
|
|
3743
|
-
return errorResult(`Documentation page not found: ${page_id}
|
|
3881
|
+
if (!result.data.getDocPage) {
|
|
3882
|
+
return errorResult(`Documentation page not found: ${page_id}`);
|
|
3744
3883
|
}
|
|
3745
|
-
return textResult(JSON.stringify(result.data.
|
|
3884
|
+
return textResult(JSON.stringify(result.data.getDocPage, null, 2));
|
|
3746
3885
|
});
|
|
3747
3886
|
}
|
|
3748
3887
|
};
|
|
@@ -3756,7 +3895,7 @@ var argsSchema3 = {
|
|
|
3756
3895
|
function createListPackageDocsTool(pkgseerService) {
|
|
3757
3896
|
return {
|
|
3758
3897
|
name: "list_package_docs",
|
|
3759
|
-
description: "Lists documentation pages for a package version. Returns page titles,
|
|
3898
|
+
description: "Lists documentation pages for a package version. Returns page titles, globally unique IDs, and metadata. Use this to discover available documentation before fetching specific pages.",
|
|
3760
3899
|
schema: argsSchema3,
|
|
3761
3900
|
handler: async ({ registry, package_name, version: version2 }, _extra) => {
|
|
3762
3901
|
return withErrorHandling("list package documentation", async () => {
|
package/dist/index.js
CHANGED